From 29c92d44d47f2119ebd1b6b6361916499b80be5e Mon Sep 17 00:00:00 2001 From: zeshanziya Date: Wed, 25 Sep 2024 04:20:40 +0000 Subject: [PATCH] fix timeout issue and improve error handling Signed-off-by: zeshanziya --- .changeset/fast-rats-walk.md | 7 +++ .../src/PlatformshHelper.ts | 21 ++++++++- .../platformsh-backend/src/service/router.ts | 25 +++++++++++ plugins/platformsh-common/src/types.ts | 7 +++ plugins/platformsh/src/api.ts | 44 ++++++++++++++++++- .../EnvironmentsCard.test.tsx | 1 + .../EnvironmentsCard/EnvironmentsCard.tsx | 13 +++++- .../ProjectDetailsCard.test.tsx | 1 + .../EntityTabComponent.test.tsx | 1 + .../PageComponent/PageComponent.test.tsx | 1 + .../ProjectsComponent.test.tsx | 1 + 11 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 .changeset/fast-rats-walk.md diff --git a/.changeset/fast-rats-walk.md b/.changeset/fast-rats-walk.md new file mode 100644 index 0000000..b631695 --- /dev/null +++ b/.changeset/fast-rats-walk.md @@ -0,0 +1,7 @@ +--- +'@axelerant/backstage-plugin-platformsh-backend': patch +'@axelerant/backstage-plugin-platformsh-common': patch +'@axelerant/backstage-plugin-platformsh': patch +--- + +Use a polling mechanism to check the environment action status to fix timeout issue and improve error handling. diff --git a/plugins/platformsh-backend/src/PlatformshHelper.ts b/plugins/platformsh-backend/src/PlatformshHelper.ts index 7be9453..5ed7b4d 100644 --- a/plugins/platformsh-backend/src/PlatformshHelper.ts +++ b/plugins/platformsh-backend/src/PlatformshHelper.ts @@ -172,6 +172,7 @@ export class PlatformshHelper { action: EnvironmentMethods, ) { let successMsg = ''; + let activityId = ''; try { const environment = await this.getEnvironment( project_id, @@ -193,11 +194,12 @@ export class PlatformshHelper { if (action === 'delete') { await environment.delete(); + successMsg = `Environment ${action}d successfully!`; } else if (action in actionMap) { const activity = await actionMap[action](); - await activity.wait(); + activityId = activity.id; + successMsg = `Action ${action} for environment ${env_id} has started and will finish soon.`; } - successMsg = `Environment ${action}d successfully!`; } catch (error) { let errorMsg = ''; if (error instanceof Error) { @@ -216,6 +218,7 @@ export class PlatformshHelper { return { valid: 1, message: successMsg, + activityId: activityId, }; } @@ -254,4 +257,18 @@ export class PlatformshHelper { valid: 1, }; } + + async getEnvironmentActivity( + project_id: string, + env_id: string, + activity_id: string, + ) { + const client = await this.getClient(); + const activity = await client.getEnvironmentActivity( + project_id, + encodeURIComponent(env_id), + activity_id, + ); + return activity; + } } diff --git a/plugins/platformsh-backend/src/service/router.ts b/plugins/platformsh-backend/src/service/router.ts index 85321a2..4a6d5f7 100644 --- a/plugins/platformsh-backend/src/service/router.ts +++ b/plugins/platformsh-backend/src/service/router.ts @@ -175,6 +175,31 @@ export async function createRouter( }, ); + router.post( + '/activity/:id/status', + async ( + req: Request< + { id: string }, + {}, + { environmentId: string; projectId: string } + >, + res, + ) => { + try { + const activity = await platformshHelper.getEnvironmentActivity( + req.body.projectId, + req.body.environmentId, + req.params.id, + ); + console.log(activity, activity.isComplete()); + res.json({ result: { completed: activity.isComplete() } }); + } catch (error) { + logger.error(`Unable to get activity status: ${error}`); + res.status(500).json({ error: 'Unable to get activity status' }); + } + }, + ); + const middleware = MiddlewareFactory.create({ logger, config }); router.use(middleware.error()); diff --git a/plugins/platformsh-common/src/types.ts b/plugins/platformsh-common/src/types.ts index 31cf683..a45170f 100644 --- a/plugins/platformsh-common/src/types.ts +++ b/plugins/platformsh-common/src/types.ts @@ -32,6 +32,7 @@ export type EnvironmentActionResponse = { actionResult: { valid: number; message: string; + activityId: string; }; }; }; @@ -54,3 +55,9 @@ export type EnvironmentMethods = | 'activate' | 'deactivate' | 'delete'; + +export type EnvironmentActivityStatusResponse = { + result: { + completed: boolean; + }; +}; diff --git a/plugins/platformsh/src/api.ts b/plugins/platformsh/src/api.ts index 3e131a7..e6f18fc 100644 --- a/plugins/platformsh/src/api.ts +++ b/plugins/platformsh/src/api.ts @@ -5,6 +5,7 @@ import { } from '@backstage/core-plugin-api'; import { EnvironmentActionResponse, + EnvironmentActivityStatusResponse, ListProjectsResponse, PlatformshEnvironment, PlatformshProject, @@ -21,6 +22,11 @@ export interface PlatformshApi { environmentId: string, action: string, ): Promise; + pollForActivityCompletion( + projectId: string, + environmentId: string, + acticityId: string, + ): Promise; } export const platformshApiRef = createApiRef({ @@ -52,7 +58,12 @@ export class PlatformshClient implements PlatformshApi { ); if (!response.ok) { - const errorBody = await response.json(); + let errorBody; + try { + errorBody = await response.json(); + } catch { + errorBody = {}; + } const errorMessage = errorBody?.error?.message || 'An error occurred'; const errorName = errorBody?.error?.name || 'Error'; @@ -94,4 +105,35 @@ export class PlatformshClient implements PlatformshApi { }, ); } + + async pollForActivityCompletion( + projectId: string, + environmentId: string, + activityId: string, + ): Promise { + return new Promise((resolve, reject) => { + const poll = setInterval(async () => { + try { + const { + result: { completed }, + } = await this.fetchApiData( + `/activity/${activityId}/status`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ environmentId, projectId }), + }, + ); + + if (completed) { + clearInterval(poll); + resolve(); + } + } catch (error) { + clearInterval(poll); + reject(error); + } + }, 5000); + }); + } } diff --git a/plugins/platformsh/src/components/Cards/EnvironmentsCard/EnvironmentsCard.test.tsx b/plugins/platformsh/src/components/Cards/EnvironmentsCard/EnvironmentsCard.test.tsx index 862475a..dc66770 100644 --- a/plugins/platformsh/src/components/Cards/EnvironmentsCard/EnvironmentsCard.test.tsx +++ b/plugins/platformsh/src/components/Cards/EnvironmentsCard/EnvironmentsCard.test.tsx @@ -41,6 +41,7 @@ describe('ProjectDetailsCard', () => { getProjectInfo: jest.fn(), getProjectEnvironments: jest.fn(), doEnvironmentAction: jest.fn(), + pollForActivityCompletion: jest.fn(), }; const permissionApi: jest.Mocked = { diff --git a/plugins/platformsh/src/components/Cards/EnvironmentsCard/EnvironmentsCard.tsx b/plugins/platformsh/src/components/Cards/EnvironmentsCard/EnvironmentsCard.tsx index efd60cd..50fadd9 100644 --- a/plugins/platformsh/src/components/Cards/EnvironmentsCard/EnvironmentsCard.tsx +++ b/plugins/platformsh/src/components/Cards/EnvironmentsCard/EnvironmentsCard.tsx @@ -101,8 +101,19 @@ export const EnvironmentsCard = ({ projectId }: { projectId: string }) => { if (actionResult.valid) { alertApi.post({ message: actionResult.message, - severity: 'success', + severity: actionResult.activityId ? 'info' : 'success', }); + if (actionResult.activityId) { + await platformshApi.pollForActivityCompletion( + projectId, + env_id, + actionResult.activityId, + ); + alertApi.post({ + message: `Environment ${action}d successfully!`, + severity: 'success', + }); + } loadProjectEnvironments(); } else { alertApi.post({ diff --git a/plugins/platformsh/src/components/Cards/ProjectDetailsCard/ProjectDetailsCard.test.tsx b/plugins/platformsh/src/components/Cards/ProjectDetailsCard/ProjectDetailsCard.test.tsx index c67231f..590aba2 100644 --- a/plugins/platformsh/src/components/Cards/ProjectDetailsCard/ProjectDetailsCard.test.tsx +++ b/plugins/platformsh/src/components/Cards/ProjectDetailsCard/ProjectDetailsCard.test.tsx @@ -21,6 +21,7 @@ describe('ProjectDetailsCard', () => { getProjectInfo: jest.fn(), getProjectEnvironments: jest.fn(), doEnvironmentAction: jest.fn(), + pollForActivityCompletion: jest.fn(), }; const Wrapper = ({ children }: { children?: React.ReactNode }) => ( diff --git a/plugins/platformsh/src/components/EntityTabComponent/EntityTabComponent.test.tsx b/plugins/platformsh/src/components/EntityTabComponent/EntityTabComponent.test.tsx index 72562cd..36c3092 100644 --- a/plugins/platformsh/src/components/EntityTabComponent/EntityTabComponent.test.tsx +++ b/plugins/platformsh/src/components/EntityTabComponent/EntityTabComponent.test.tsx @@ -37,6 +37,7 @@ describe('EntityTabComponent', () => { getProjectInfo: jest.fn(), getProjectEnvironments: jest.fn(), doEnvironmentAction: jest.fn(), + pollForActivityCompletion: jest.fn(), }; const Wrapper = ({ children }: { children?: React.ReactNode }) => ( diff --git a/plugins/platformsh/src/components/PageComponent/PageComponent.test.tsx b/plugins/platformsh/src/components/PageComponent/PageComponent.test.tsx index bf82890..c2bf313 100644 --- a/plugins/platformsh/src/components/PageComponent/PageComponent.test.tsx +++ b/plugins/platformsh/src/components/PageComponent/PageComponent.test.tsx @@ -20,6 +20,7 @@ describe('PlatformshPageComponent', () => { getProjectInfo: jest.fn(), getProjectEnvironments: jest.fn(), doEnvironmentAction: jest.fn(), + pollForActivityCompletion: jest.fn(), }; const Wrapper = ({ children }: { children?: React.ReactNode }) => ( diff --git a/plugins/platformsh/src/components/ProjectsComponent/ProjectsComponent.test.tsx b/plugins/platformsh/src/components/ProjectsComponent/ProjectsComponent.test.tsx index 5e26c80..a0a6cc1 100644 --- a/plugins/platformsh/src/components/ProjectsComponent/ProjectsComponent.test.tsx +++ b/plugins/platformsh/src/components/ProjectsComponent/ProjectsComponent.test.tsx @@ -11,6 +11,7 @@ describe('ProjectsComponent', () => { getProjectInfo: jest.fn(), getProjectEnvironments: jest.fn(), doEnvironmentAction: jest.fn(), + pollForActivityCompletion: jest.fn(), }; const Wrapper = ({ children }: { children?: React.ReactNode }) => (