From 227fee9941b77ec5a7589231979b083fb62d4f00 Mon Sep 17 00:00:00 2001 From: Felipe Lalanne Date: Thu, 6 Jun 2024 18:50:59 -0400 Subject: [PATCH] Set the app update status when reporting state Change-type: minor --- src/compose/application-manager.ts | 65 +++++++++++---- .../compose/application-manager.spec.ts | 81 +++++++++++++++++++ 2 files changed, 130 insertions(+), 16 deletions(-) diff --git a/src/compose/application-manager.ts b/src/compose/application-manager.ts index b677bf7d..453834f8 100644 --- a/src/compose/application-manager.ts +++ b/src/compose/application-manager.ts @@ -865,9 +865,9 @@ export async function getLegacyState() { return { local: apps }; } -// TODO: this function is probably more inefficient than it needs to be, since -// it tried to optimize for readability, look for a way to make it simpler -export async function getState() { +type AppsReport = { [uuid: string]: AppState }; + +export async function getState(): Promise { const [services, images] = await Promise.all([ serviceManager.getState(), imageManager.getState(), @@ -980,7 +980,7 @@ export async function getState() { ); // Assemble the state of apps - const state: { [appUuid: string]: AppState } = {}; + const state: AppsReport = {}; for (const { appId, appUuid, @@ -989,21 +989,54 @@ export async function getState() { createdAt, ...svc } of servicesToReport) { - state[appUuid] = { - ...state[appUuid], + const app = state[appUuid] ?? { // Add the release_uuid if the commit has been stored in the database ...(commitsForApp[appId] && { release_uuid: commitsForApp[appId] }), - releases: { - ...state[appUuid]?.releases, - [commit]: { - ...state[appUuid]?.releases[commit], - services: { - ...state[appUuid]?.releases[commit]?.services, - [serviceName]: svc, - }, - }, - }, + releases: {}, }; + + const releases = app.releases; + releases[commit] = releases[commit] ?? { + update_status: 'done', + services: {}, + }; + + releases[commit].services[serviceName] = svc; + + // The update_status precedence order is as follows + // - aborted + // - downloading + // - downloaded + // - applying changes + // - done + if (svc.status === 'Aborted') { + releases[commit].update_status = 'aborted'; + } else if ( + releases[commit].update_status !== 'aborted' && + svc.download_progress != null && + svc.download_progress !== 100 + ) { + releases[commit].update_status = 'downloading'; + } else if ( + !['aborted', 'downloading'].includes(releases[commit].update_status!) && + (svc.download_progress === 100 || svc.status === 'Downloaded') + ) { + releases[commit].update_status = 'downloaded'; + } else if ( + // The `applying changes` state has lower precedence over the aborted/downloading/downloaded + // state + !['aborted', 'downloading', 'downloaded'].includes( + releases[commit].update_status!, + ) && + ['installing', 'installed', 'awaiting handover'].includes( + svc.status.toLowerCase(), + ) + ) { + releases[commit].update_status = 'applying changes'; + } + + // Update the state object + state[appUuid] = app; } return state; } diff --git a/test/integration/compose/application-manager.spec.ts b/test/integration/compose/application-manager.spec.ts index b018ee5b..abfd1231 100644 --- a/test/integration/compose/application-manager.spec.ts +++ b/test/integration/compose/application-manager.spec.ts @@ -2444,6 +2444,7 @@ describe('compose/application-manager', () => { download_progress: 50, }, }, + update_status: 'downloading', }, }, }, @@ -2456,6 +2457,7 @@ describe('compose/application-manager', () => { status: 'Downloaded', }, }, + update_status: 'downloaded', }, newrelease: { services: { @@ -2465,6 +2467,7 @@ describe('compose/application-manager', () => { download_progress: 75, }, }, + update_status: 'downloading', }, }, }, @@ -2548,6 +2551,7 @@ describe('compose/application-manager', () => { download_progress: 0, }, }, + update_status: 'downloading', }, }, }, @@ -2560,6 +2564,81 @@ describe('compose/application-manager', () => { status: 'exited', }, }, + update_status: 'done', + }, + }, + }, + }); + }); + + it('reports aborted state if one of the services/images status is aborted', async () => { + getImagesState.resolves([ + { + name: 'ubuntu:latest', + commit: 'latestrelease', + appUuid: 'myapp', + serviceName: 'ubuntu', + status: 'Downloaded', + }, + { + name: 'node:latest', + commit: 'latestrelease', + appUuid: 'myapp', + serviceName: 'node', + status: 'Downloaded', + downloadProgress: 100, + }, + { + name: 'alpine:latest', + commit: 'latestrelease', + appUuid: 'myapp', + serviceName: 'alpine', + status: 'Aborted', + downloadProgress: 0, + }, + ]); + getServicesState.resolves([ + { + commit: 'latestrelease', + appUuid: 'myapp', + serviceName: 'ubuntu', + status: 'Running', + createdAt: new Date('2021-09-01T13:00:00'), + }, + { + appUuid: 'myapp', + commit: 'latestrelease', + serviceName: 'node', + // we don't have a way to abort a failing service install yet, but + // once we do it will need to use the status field + status: 'Aborted', + createdAt: new Date('2021-09-01T12:00:00'), + }, + ]); + + expect(await applicationManager.getState()).to.deep.equal({ + myapp: { + releases: { + latestrelease: { + services: { + ubuntu: { + image: 'ubuntu:latest', + status: 'Running', + }, + alpine: { + image: 'alpine:latest', + // we don't have a way to abort a failing download yet, but + // once we do it will need to use the status field + status: 'Aborted', + download_progress: 0, + }, + node: { + image: 'node:latest', + status: 'Aborted', + download_progress: 100, + }, + }, + update_status: 'aborted', }, }, }, @@ -2610,6 +2689,7 @@ describe('compose/application-manager', () => { status: 'Awaiting handover', }, }, + update_status: 'applying changes', }, oldrelease: { services: { @@ -2618,6 +2698,7 @@ describe('compose/application-manager', () => { status: 'Handing over', }, }, + update_status: 'done', }, }, },