kopia lustrzana https://github.com/wagtail/wagtail
Migrate upgrade notification to Stimulus (UpgradeController)
rodzic
e8fc3d9cf0
commit
c8dca0a7f2
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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' },
|
||||
];
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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):
|
||||
|
|
Ładowanie…
Reference in New Issue