Migrate upgrade notification to Stimulus (UpgradeController)

pull/9841/head
LB Johnston 2022-08-23 20:43:35 +10:00 zatwierdzone przez LB (Ben Johnston)
rodzic e8fc3d9cf0
commit c8dca0a7f2
9 zmienionych plików z 191 dodań i 103 usunięć

Wyświetl plik

@ -108,6 +108,7 @@ Changelog
* Maintenance: Split up `wagtail.admin.panels` into submodules, existing exports have been preserved (Matt Westcott)
* Maintenance: Refactor userbar styles to use the same stylesheet as other components (Thibaud Colas)
* Maintenance: Add deprecation warnings for `wagtail.core` and other imports deprecated in Wagtail 3.0 (Matt Westcott)
* Maintenance: Migrate admin upgrade notification message implementation to a Stimulus controller (Loveth Omokaro)
4.1.2 (xx.xx.xxxx) - IN DEVELOPMENT

Wyświetl plik

@ -1,28 +1,40 @@
import { initUpgradeNotification } from './initUpgradeNotification';
import { Application } from '@hotwired/stimulus';
import { UpgradeController } from './UpgradeController';
// https://stackoverflow.com/a/51045733
const flushPromises = () => new Promise(setImmediate);
describe('initUpgradeNotification', () => {
describe('UpgradeController', () => {
let application;
const url = 'https://releases.wagtail.org/mock.txt';
const version = '2.3';
document.body.innerHTML = `
<div
class="panel"
id="panel"
data-upgrade-notification
data-current-version="${version}"
style="display:none"
>
beforeEach(() => {
document.body.innerHTML = `
<div
class="panel w-hidden"
id="panel"
data-controller="w-upgrade"
data-w-upgrade-current-version-value="${version}"
data-w-upgrade-hidden-class="w-hidden"
data-w-upgrade-url-value="${url}"
>
<div class="help-block help-warning">
Your version: <strong>${version}</strong>.
New version: <strong id="latest-version" data-upgrade-version></strong>.
<a href="" id="link" data-upgrade-link>Release notes</a>
Your version: <strong>${version}</strong>.
New version: <strong id="latest-version" data-w-upgrade-target="latestVersion">_</strong>.
<a href="" id="link" data-w-upgrade-target="link">Release notes</a>
</div>
</div>
`;
</div>
`;
});
it('should show the notification and update the version & link', async () => {
afterEach(() => {
jest.clearAllMocks();
application.stop();
});
it('should keep the hidden class by default & then show a message when version is out of date', async () => {
const data = {
version: '5.15.1',
url: 'https://docs.wagtail.org/latest/url',
@ -34,34 +46,98 @@ describe('initUpgradeNotification', () => {
},
};
fetch.mockResponseSuccess(JSON.stringify(data));
expect(global.fetch).not.toHaveBeenCalled();
initUpgradeNotification();
fetch.mockResponseSuccess(JSON.stringify(data));
// start application
application = Application.start();
application.register('w-upgrade', UpgradeController);
// trigger next browser render cycle
await Promise.resolve(true);
expect(global.fetch).toHaveBeenCalledWith(
'https://releases.wagtail.io/latest.txt',
'https://releases.wagtail.org/mock.txt',
{ referrerPolicy: 'strict-origin-when-cross-origin' },
);
expect(document.getElementById('panel').style.display).toBe('none');
expect(
document.getElementById('panel').classList.contains('w-hidden'),
).toBe(true);
await flushPromises();
// should remove the hidden class on success
expect(document.getElementById('panel').style.display).toBe('');
expect(
document.getElementById('panel').classList.contains('w-hidden'),
).toBe(false);
// should update the version in the message
expect(document.getElementById('latest-version').innerText).toEqual(
// should update the latest version number in the text
expect(document.getElementById('latest-version').innerText).toBe(
data.version,
);
// should update the link
// should add the latest version link
expect(document.getElementById('link').getAttribute('href')).toEqual(
data.minorUrl,
);
});
it('should not show the message if the current version is up to date', async () => {
const data = {
version: '5.15.1',
url: 'https://docs.wagtail.org/latest/url',
minorUrl: 'https://docs.wagtail.org/latest-minor/url',
lts: {
version: '2.2',
url: 'https://docs.wagtail.org/lts/url',
minorUrl: 'https://docs.wagtail.org/lts-minor/url',
},
};
fetch.mockResponseSuccess(JSON.stringify(data));
expect(global.fetch).not.toHaveBeenCalled();
// start application
application = Application.start();
application.register('w-upgrade', UpgradeController);
// trigger next browser render cycle
await Promise.resolve(true);
expect(
document.getElementById('panel').classList.contains('w-hidden'),
).toBe(true);
});
it('should throw an error if the fetch fails', () => {
// Spy on console.error to verify that it is called with the expected error message
jest.spyOn(console, 'error').mockImplementation(() => {});
// Create a mock for the fetch function that returns a rejected Promise
const mockFetch = jest.fn(() => Promise.reject(new Error('Fetch failed')));
// Replace the global fetch function with the mock
global.fetch = mockFetch;
// start application
application = Application.start();
application.register('w-upgrade', UpgradeController);
// Wait for the catch block to be executed
/* eslint-disable-next-line no-promise-executor-return */
return new Promise((resolve) => setImmediate(resolve)).then(() => {
// Verify that console.error was called with the expected error message
/* eslint-disable-next-line no-console */
expect(console.error).toHaveBeenCalledWith(
`Error fetching ${url}. Error: Error: Fetch failed`,
);
// Restore the original implementation of console.error
/* eslint-disable no-console */
console.error.mockRestore();
});
});
});

Wyświetl plik

@ -1,3 +1,4 @@
import { Controller } from '@hotwired/stimulus';
import { VersionNumber, VersionDeltaType } from '../utils/version';
/**
@ -18,73 +19,82 @@ import { VersionNumber, VersionDeltaType } from '../utils/version';
* }
* }
*/
const initUpgradeNotification = () => {
const container = document.querySelector(
'[data-upgrade-notification]',
) as HTMLElement;
export class UpgradeController extends Controller<HTMLElement> {
static classes = ['hidden'];
static targets = ['latestVersion', 'link'];
static values = {
currentVersion: String,
ltsOnly: { default: false, type: Boolean },
url: { default: 'https://releases.wagtail.org/latest.txt', type: String },
};
if (!container) return;
currentVersionValue: string;
hiddenClass: string;
latestVersionTarget: HTMLElement;
linkTarget: HTMLElement;
ltsOnlyValue: any;
urlValue: string;
const releasesUrl = 'https://releases.wagtail.io/latest.txt';
const currentVersion = new VersionNumber(container.dataset.currentVersion);
const showLTSOnly = container.hasAttribute('data-upgrade-lts-only');
const upgradeVersion = container.querySelector('[data-upgrade-version]');
const upgradeLink = container.querySelector('[data-upgrade-link]');
connect() {
this.checkVersion();
}
fetch(releasesUrl, {
referrerPolicy: 'strict-origin-when-cross-origin',
})
.then((response) => {
if (response.status !== 200) {
checkVersion() {
const releasesUrl = this.urlValue;
const currentVersion = new VersionNumber(this.currentVersionValue);
const showLTSOnly = this.ltsOnlyValue;
fetch(releasesUrl, {
referrerPolicy: 'strict-origin-when-cross-origin',
})
.then((response) => {
if (response.status !== 200) {
throw Error(
`Unexpected response from ${releasesUrl}. Status: ${response.status}`,
);
}
return response.json();
})
.then((payload) => {
let data = payload;
if (data && data.lts && showLTSOnly) {
data = data.lts;
}
if (data && data.version) {
const latestVersion = new VersionNumber(data.version);
const versionDelta = currentVersion.howMuchBehind(latestVersion);
let releaseNotesUrl = null;
if (!versionDelta) {
return;
}
if (
versionDelta === VersionDeltaType.MAJOR ||
versionDelta === VersionDeltaType.MINOR
) {
releaseNotesUrl = data.minorUrl;
} else {
releaseNotesUrl = data.url;
}
if (this.latestVersionTarget instanceof HTMLElement) {
this.latestVersionTarget.innerText = [
data.version,
showLTSOnly ? '(LTS)' : '',
]
.join(' ')
.trim();
}
if (this.linkTarget instanceof HTMLElement) {
this.linkTarget.setAttribute('href', releaseNotesUrl || '');
}
this.element.classList.remove(this.hiddenClass);
}
})
.catch((err) => {
// eslint-disable-next-line no-console
console.error(
`Unexpected response from ${releasesUrl}. Status: ${response.status}`,
);
return false;
}
return response.json();
})
.then((payload) => {
let data = payload;
if (data && data.lts && showLTSOnly) {
data = data.lts;
}
if (data && data.version) {
const latestVersion = new VersionNumber(data.version);
const versionDelta = currentVersion.howMuchBehind(latestVersion);
let releaseNotesUrl = null;
if (!versionDelta) {
return;
}
if (
versionDelta === VersionDeltaType.MAJOR ||
versionDelta === VersionDeltaType.MINOR
) {
releaseNotesUrl = data.minorUrl;
} else {
releaseNotesUrl = data.url;
}
if (upgradeVersion instanceof HTMLElement) {
upgradeVersion.innerText = [data.version, showLTSOnly ? '(LTS)' : '']
.join(' ')
.trim();
}
if (upgradeLink instanceof HTMLElement) {
upgradeLink.setAttribute('href', releaseNotesUrl || '');
}
container.style.display = '';
}
})
.catch((err) => {
// eslint-disable-next-line no-console
console.error(`Error fetching ${releasesUrl}. Error: ${err}`);
});
};
export { initUpgradeNotification };
console.error(`Error fetching ${releasesUrl}. Error: ${err}`);
});
}
}

Wyświetl plik

@ -2,6 +2,7 @@ import type { Definition } from '@hotwired/stimulus';
import { AutoFieldController } from './AutoFieldController';
import { SkipLinkController } from './SkipLinkController';
import { UpgradeController } from './UpgradeController';
/**
* Important: Only add default core controllers that should load with the base admin JS bundle.
@ -9,4 +10,5 @@ import { SkipLinkController } from './SkipLinkController';
export const coreControllerDefinitions: Definition[] = [
{ controllerConstructor: AutoFieldController, identifier: 'w-auto-field' },
{ controllerConstructor: SkipLinkController, identifier: 'w-skip-link' },
{ controllerConstructor: UpgradeController, identifier: 'w-upgrade' },
];

Wyświetl plik

@ -1,4 +1,4 @@
import { Icon, Portal, initDismissibles, initUpgradeNotification } from '../..';
import { Icon, Portal, initDismissibles } from '../..';
import { initModernDropdown, initTooltips } from '../../includes/initTooltips';
import { initTabs } from '../../includes/tabs';
import { dialog } from '../../includes/dialog';
@ -20,7 +20,6 @@ window.wagtail.components = {
* Add in here code to run once the page is loaded.
*/
document.addEventListener('DOMContentLoaded', () => {
initUpgradeNotification();
initTooltips();
initModernDropdown();
initTabs();

Wyświetl plik

@ -10,4 +10,3 @@ export { default as Portal } from './components/Portal/Portal';
export { default as PublicationStatus } from './components/PublicationStatus/PublicationStatus';
export { default as Transition } from './components/Transition/Transition';
export { initDismissibles } from './includes/initDismissibles';
export { initUpgradeNotification } from './includes/initUpgradeNotification';

Wyświetl plik

@ -139,6 +139,7 @@ This feature was developed by Jake Howard.
* Split up `wagtail.admin.panels` into submodules, existing exports have been preserved (Matt Westcott)
* Refactor userbar styles to use the same stylesheet as other components (Thibaud Colas)
* Add deprecation warnings for `wagtail.core` and other imports deprecated in Wagtail 3.0 (Matt Westcott)
* Migrate admin upgrade notification message implementation to a Stimulus controller (Loveth Omokaro)
## Upgrade considerations

Wyświetl plik

@ -1,17 +1,17 @@
{% load i18n wagtailcore_tags wagtailadmin_tags %}
{% wagtail_version as current_version %}
<div
class="w-panel-upgrade-notification panel nice-padding"
data-upgrade-notification
data-current-version="{{ current_version }}"
{% if lts_only %}data-upgrade-lts-only{% endif %}
style="display:none"
class="w-panel-upgrade panel nice-padding w-hidden"
data-controller="w-upgrade"
data-w-upgrade-current-version-value="{{ current_version }}"
{% if lts_only %}data-w-upgrade-lts-only-value="true"{% endif %}
data-w-upgrade-hidden-class="w-hidden"
>
<div class="help-block help-warning">
{% icon name='warning' %}
{% blocktrans trimmed %}
Wagtail upgrade available. Your version: <strong>{{ current_version }}</strong>. New version: <strong data-upgrade-version></strong>.
Wagtail upgrade available. Your version: <strong>{{ current_version }}</strong>. New version: <strong data-w-upgrade-target="latestVersion"></strong>.
{% endblocktrans %}
<a data-upgrade-link href="">{% trans "Read the release notes." %}</a>
<a href="" data-w-upgrade-target="link">{% trans "Read the release notes." %}</a>
</div>
</div>

Wyświetl plik

@ -5,8 +5,8 @@ from wagtail.test.utils import WagtailTestUtils
class TestUpgradeNotificationPanel(TestCase, WagtailTestUtils):
DATA_ATTRIBUTE_UPGRADE_CHECK = "data-upgrade"
DATA_ATTRIBUTE_UPGRADE_CHECK_LTS = "data-upgrade-lts-only"
DATA_ATTRIBUTE_UPGRADE_CHECK = "data-w-upgrade"
DATA_ATTRIBUTE_UPGRADE_CHECK_LTS = "data-w-upgrade-lts-only"
@classmethod
def setUpTestData(cls):