Preserve time execution for a non-running task.
Signed-off-by: Bogdan Nechyporenko <bnechyporenko@bol.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Preserve time execution for a non-running task
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:^",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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:^"
|
||||
|
||||
Reference in New Issue
Block a user