Update status when summary is updated (#128769)

This commit is contained in:
Nicolas Chaulet 2022-03-31 07:25:44 -04:00 committed by GitHub
parent c2db728d3c
commit abe70c5256
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 69 additions and 24 deletions

View file

@ -285,6 +285,32 @@ describe('PluginStatusService', () => {
]);
});
it('updates when a plugin status observable emits with the same level but a different summary', async () => {
const service = new PluginsStatusService({
core$: coreAllAvailable$,
pluginDependencies: new Map([['a', []]]),
});
const statusUpdates: Array<Record<PluginName, ServiceStatus>> = [];
const subscription = service
.getAll$()
// the first emission happens right after core services emit (see explanation above)
.pipe(skip(1))
.subscribe((pluginStatuses) => statusUpdates.push(pluginStatuses));
const aStatus$ = new BehaviorSubject<ServiceStatus>({
level: ServiceStatusLevels.available,
summary: 'summary initial',
});
service.set('a', aStatus$);
aStatus$.next({ level: ServiceStatusLevels.available, summary: 'summary updated' });
subscription.unsubscribe();
expect(statusUpdates).toEqual([
{ a: { level: ServiceStatusLevels.available, summary: 'summary initial' } },
{ a: { level: ServiceStatusLevels.available, summary: 'summary updated' } },
]);
});
it('emits an unavailable status if first emission times out, then continues future emissions', async () => {
const service = new PluginsStatusService(
{

View file

@ -71,7 +71,12 @@ export class PluginsStatusService {
this.coreSubscription = deps.core$
.pipe(debounceTime(10))
.subscribe((coreStatus: CoreStatus) => this.updateCoreAndPluginStatuses(coreStatus));
.subscribe((coreStatus: CoreStatus) => {
this.coreStatus = coreStatus;
this.updateRootPluginsStatuses();
this.updateDependantStatuses(this.rootPlugins);
this.emitCurrentStatus();
});
}
/**
@ -96,8 +101,19 @@ export class PluginsStatusService {
this.reportedStatusSubscriptions[plugin] = status$
// Set a timeout for externally-defined status Observables
.pipe(timeoutWith(this.statusTimeoutMs, status$.pipe(startWith(defaultStatus))))
.subscribe((status) => this.updatePluginReportedStatus(plugin, status));
.pipe(
timeoutWith(this.statusTimeoutMs, status$.pipe(startWith(defaultStatus))),
distinctUntilChanged()
)
.subscribe((status) => {
const levelChanged = this.updatePluginReportedStatus(plugin, status);
if (levelChanged) {
this.updateDependantStatuses([plugin]);
}
this.emitCurrentStatus();
});
}
/**
@ -233,16 +249,14 @@ export class PluginsStatusService {
}
/**
* Updates the core services statuses and plugins' statuses
* according to the latest status reported by core services.
* @param {CoreStatus} coreStatus the latest status of core services
* Updates the root plugins statuses according to the current core services status
*/
private updateCoreAndPluginStatuses(coreStatus: CoreStatus): void {
this.coreStatus = coreStatus!;
private updateRootPluginsStatuses(): void {
const derivedStatus = getSummaryStatus(Object.entries(this.coreStatus), {
allAvailableSummary: `All dependencies are available`,
});
// note that the derived status is the same for all root plugins
this.rootPlugins.forEach((plugin) => {
this.pluginData[plugin].derivedStatus = derivedStatus;
if (!this.isReportingStatus[plugin]) {
@ -250,18 +264,18 @@ export class PluginsStatusService {
this.pluginStatus[plugin] = derivedStatus;
}
});
this.updatePluginsStatuses(this.rootPlugins);
}
/**
* Determine the derived statuses of the specified plugins and their dependencies,
* updating them on the pluginData structure
* Optionally, if the plugins have not registered a custom status Observable, update their "current" status as well.
* @param {PluginName[]} plugins The names of the plugins to be updated
* Update the derived statuses of the specified plugins' dependant plugins,
* If impacted plugins have not registered a custom status Observable, update their "current" status as well.
* @param {PluginName[]} plugins The names of the plugins whose dependant plugins must be updated
*/
private updatePluginsStatuses(plugins: PluginName[]): void {
const toCheck = new Set<PluginName>(plugins);
private updateDependantStatuses(plugins: PluginName[]): void {
const toCheck = new Set<PluginName>();
plugins.forEach((plugin) =>
this.pluginData[plugin].reverseDependencies.forEach((revDep) => toCheck.add(revDep))
);
// Note that we are updating the plugins in an ordered fashion.
// This way, when updating plugin X (at depth = N),
@ -276,9 +290,6 @@ export class PluginsStatusService {
this.pluginData[current].reverseDependencies.forEach((revDep) => toCheck.add(revDep));
}
}
this.pluginData$.next(this.pluginData);
this.pluginStatus$.next({ ...this.pluginStatus });
}
/**
@ -328,15 +339,23 @@ export class PluginsStatusService {
* Updates the reported status for the given plugin, along with the status of its dependencies tree.
* @param {PluginName} plugin The name of the plugin whose reported status must be updated
* @param {ServiceStatus} reportedStatus The newly reported status for that plugin
* @return {boolean} true if the level of the reported status changed
*/
private updatePluginReportedStatus(plugin: PluginName, reportedStatus: ServiceStatus): void {
const previousReportedLevel = this.pluginData[plugin].reportedStatus?.level;
private updatePluginReportedStatus(plugin: PluginName, reportedStatus: ServiceStatus): boolean {
const previousReportedStatus = this.pluginData[plugin].reportedStatus;
this.pluginData[plugin].reportedStatus = reportedStatus;
this.pluginStatus[plugin] = reportedStatus;
if (reportedStatus.level !== previousReportedLevel) {
this.updatePluginsStatuses([plugin]);
}
return previousReportedStatus?.level !== reportedStatus.level;
}
/**
* Emit the current status to internal Subjects, effectively propagating it to observers.
*/
private emitCurrentStatus(): void {
this.pluginData$.next(this.pluginData);
// we must clone the plugin status to prevent future modifications from updating current emission
this.pluginStatus$.next({ ...this.pluginStatus });
}
}