Preserve time execution for a non-running task.

Signed-off-by: Bogdan Nechyporenko <bnechyporenko@bol.com>
This commit is contained in:
Bogdan Nechyporenko
2023-10-16 13:38:10 +02:00
parent 811720c953
commit 7d653f58b1
7 changed files with 164 additions and 9 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder': patch
---
Preserve time execution for a non-running task
+2 -2
View File
@@ -741,8 +741,8 @@ export function createRouter(options: RouterOptions): Promise<express.Router>;
export const createTemplateAction: <
TInputParams extends JsonObject = JsonObject,
TOutputParams extends JsonObject = JsonObject,
TInputSchema extends ZodType<any, ZodTypeDef, any> | Schema = {},
TOutputSchema extends ZodType<any, ZodTypeDef, any> | Schema = {},
TInputSchema extends Schema | ZodType<any, ZodTypeDef, any> = {},
TOutputSchema extends Schema | ZodType<any, ZodTypeDef, any> = {},
TActionInput extends JsonObject = TInputSchema extends ZodType<
any,
any,
+2 -2
View File
@@ -53,8 +53,8 @@ export type ActionContext<
export const createTemplateAction: <
TInputParams extends JsonObject = JsonObject,
TOutputParams extends JsonObject = JsonObject,
TInputSchema extends z.ZodType<any, z.ZodTypeDef, any> | Schema = {},
TOutputSchema extends z.ZodType<any, z.ZodTypeDef, any> | Schema = {},
TInputSchema extends Schema | z.ZodType<any, z.ZodTypeDef, any> = {},
TOutputSchema extends Schema | z.ZodType<any, z.ZodTypeDef, any> = {},
TActionInput extends JsonObject = TInputSchema extends z.ZodType<
any,
any,
+1
View File
@@ -58,6 +58,7 @@
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-permission-react": "workspace:^",
"@backstage/plugin-scaffolder-common": "workspace:^",
"@backstage/plugin-scaffolder-node": "workspace:^",
"@backstage/plugin-scaffolder-react": "workspace:^",
"@backstage/theme": "workspace:^",
"@backstage/types": "workspace:^",
+97
View File
@@ -21,6 +21,11 @@ import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { ScaffolderClient } from './api';
import { EventSourcePolyfill } from 'event-source-polyfill';
import {
SerializedTask,
SerializedTaskEvent,
} from '@backstage/plugin-scaffolder-node';
import { ScaffolderStep } from '@backstage/plugin-scaffolder-react';
const MockedEventSource = EventSourcePolyfill as jest.MockedClass<
typeof EventSourcePolyfill
@@ -327,6 +332,98 @@ describe('api', () => {
});
});
describe('getTask', () => {
it('should return the task enriched with timestamps', async () => {
const taskId = 'e4e4cb25-e743-4b79-8572-87e9d35aa998';
server.use(
rest.get(`${mockBaseUrl}/v2/tasks/:taskId`, (_req, res, ctx) => {
return res(
ctx.json({
createdAt: '2023-10-16T08:21:32.038Z',
id: taskId,
lastHeartbeatAt: '2023-10-16T08:21:54.304Z',
spec: {
apiVersion: 'scaffolder.backstage.io/v1beta3',
steps: [
{
action: 'fetch:template',
id: 'fetch',
},
{
action: 'debug:wait',
id: 'mock-step-1',
},
{
action: 'debug:wait',
id: 'mock-step-2',
},
],
templateInfo: {
entityRef: 'template:default/docs-long-running-template',
},
},
} as SerializedTask),
);
}),
rest.get(`${mockBaseUrl}/v2/tasks/:taskId/events`, (_req, res, ctx) => {
return res(
ctx.json([
{
createdAt: '2023-10-16T08:21:32.062Z',
body: {},
id: 109,
taskId,
type: 'log',
},
{
body: { stepId: 'fetch' },
createdAt: '2023-10-16T08:21:32.062Z',
id: 110,
taskId,
type: 'log',
},
{
body: { stepId: 'fetch' },
createdAt: '2023-10-16T08:21:33.062Z',
id: 111,
taskId,
type: 'log',
},
{
body: { stepId: 'fetch' },
createdAt: '2023-10-16T08:21:34.062Z',
id: 112,
taskId,
type: 'log',
},
{
body: { stepId: 'mock-step-1' },
createdAt: '2023-10-16T08:21:37.062Z',
id: 120,
taskId,
type: 'log',
},
] as SerializedTaskEvent[]),
);
}),
);
const task = await apiClient.getTask(taskId);
const getStep = (stepId: string) =>
task.spec.steps.find(
step => step.id === stepId,
) as unknown as ScaffolderStep;
expect(getStep('fetch').startedAt).toBe('2023-10-16T08:21:32.062Z');
expect(getStep('fetch').endedAt).toBe('2023-10-16T08:21:34.062Z');
expect(getStep('mock-step-1').startedAt).toBe('2023-10-16T08:21:37.062Z');
expect(getStep('mock-step-1').endedAt).toBe('2023-10-16T08:21:37.062Z');
expect(getStep('mock-step-2').startedAt).toBeUndefined();
expect(getStep('mock-step-2').endedAt).toBeUndefined();
});
});
describe('listTasks', () => {
it('should list all tasks', async () => {
server.use(
+56 -5
View File
@@ -38,10 +38,13 @@ import {
ScaffolderDryRunOptions,
ScaffolderDryRunResponse,
TemplateParameterSchema,
ScaffolderStep,
} from '@backstage/plugin-scaffolder-react';
import { TaskStep } from '@backstage/plugin-scaffolder-common';
import queryString from 'qs';
import { EventSourcePolyfill } from 'event-source-polyfill';
import { SerializedTaskEvent } from '@backstage/plugin-scaffolder-node';
/**
* An API to interact with the scaffolder backend.
@@ -170,14 +173,62 @@ export class ScaffolderClient implements ScaffolderApi {
async getTask(taskId: string): Promise<ScaffolderTask> {
const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder');
const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`;
const taskUrl = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`;
const response = await this.fetchApi.fetch(url);
if (!response.ok) {
throw await ResponseError.fromResponse(response);
const taskEventsUrl = `${baseUrl}/v2/tasks/${encodeURIComponent(
taskId,
)}/events`;
const taskResponse = await this.fetchApi.fetch(taskUrl);
if (!taskResponse.ok) {
throw await ResponseError.fromResponse(taskResponse);
}
return await response.json();
const taskEventsResponse = await this.fetchApi.fetch(taskEventsUrl);
if (!taskEventsResponse.ok) {
throw await ResponseError.fromResponse(taskEventsResponse);
}
const task = (await taskResponse.json()) as ScaffolderTask;
const taskEvents =
(await taskEventsResponse.json()) as SerializedTaskEvent[];
const stepIdToTimestamps = taskEvents
.filter(event => event.type === 'log')
.reduce((acc, event) => {
const stepId = event.body.stepId as string;
if (stepId) {
acc.set(stepId, [...(acc.get(stepId) ?? []), event.createdAt]);
}
return acc;
}, new Map<string, string[]>());
const toStartedAt = (stepId: string) => {
const timestamps = stepIdToTimestamps.get(stepId);
return timestamps ? timestamps[0] : undefined;
};
const toEndedAt = (stepId: string) => {
const timestamps = stepIdToTimestamps.get(stepId);
return timestamps ? timestamps[timestamps.length - 1] : undefined;
};
const enrichedTask = {
...task,
spec: {
...task.spec,
steps: task.spec.steps.map(
step =>
({
...step,
startedAt: toStartedAt(step.id),
endedAt: toEndedAt(step.id),
} as TaskStep & ScaffolderStep),
),
},
};
return enrichedTask as ScaffolderTask;
}
streamLogs(options: ScaffolderStreamLogsOptions): Observable<LogEvent> {
+1
View File
@@ -8975,6 +8975,7 @@ __metadata:
"@backstage/plugin-catalog-react": "workspace:^"
"@backstage/plugin-permission-react": "workspace:^"
"@backstage/plugin-scaffolder-common": "workspace:^"
"@backstage/plugin-scaffolder-node": "workspace:^"
"@backstage/plugin-scaffolder-react": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@backstage/theme": "workspace:^"