feat: add support for status filtering in scaffolder endpoint

Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi>
This commit is contained in:
Heikki Hellgren
2024-07-30 11:14:29 +03:00
parent e7dba9ff5d
commit c544f811b8
10 changed files with 86 additions and 26 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-scaffolder-backend': patch
'@backstage/plugin-scaffolder-node': patch
---
Add support for status filtering in scaffolder tasks endpoint
+2 -2
View File
@@ -427,7 +427,7 @@ export class DatabaseTaskStore implements TaskStore {
// (undocumented)
heartbeatTask(taskId: string): Promise<void>;
// (undocumented)
list(options: { createdBy?: string }): Promise<{
list(options: { createdBy?: string; status?: TaskStatus_2 }): Promise<{
tasks: SerializedTask_2[];
}>;
// (undocumented)
@@ -646,7 +646,7 @@ export interface TaskStore {
// (undocumented)
heartbeatTask(taskId: string): Promise<void>;
// (undocumented)
list?(options: { createdBy?: string }): Promise<{
list?(options: { createdBy?: string; status?: TaskStatus }): Promise<{
tasks: SerializedTask[];
}>;
// (undocumented)
@@ -95,6 +95,33 @@ describe('DatabaseTaskStore', () => {
expect(tasks[0].id).toBeDefined();
});
it('should list filtered created tasks by status', async () => {
const { store } = await createStore();
const { taskId } = await store.createTask({
spec: {} as TaskSpec,
createdBy: 'me',
});
await store.createTask({
spec: {} as TaskSpec,
createdBy: 'him',
});
const message = `This task was marked as stale as it exceeded its timeout`;
await store.completeTask({
taskId,
status: 'cancelled',
eventBody: { message },
});
const { tasks } = await store.list({ status: 'open' });
expect(tasks.length).toBe(1);
expect(tasks[0].createdBy).toBe('him');
expect(tasks[0].status).toBe('open');
expect(tasks[0].id).toBeDefined();
});
it('should sent an event to start cancelling the task', async () => {
const { store } = await createStore();
@@ -22,19 +22,19 @@ import { Knex } from 'knex';
import { v4 as uuid } from 'uuid';
import {
TaskStore,
TaskStoreEmitOptions,
TaskStoreListEventsOptions,
TaskStoreCreateTaskOptions,
TaskStoreCreateTaskResult,
TaskStoreShutDownTaskOptions,
TaskStoreEmitOptions,
TaskStoreListEventsOptions,
TaskStoreRecoverTaskOptions,
TaskStoreShutDownTaskOptions,
} from './types';
import {
SerializedTaskEvent,
SerializedTask,
TaskStatus,
SerializedTaskEvent,
TaskEventType,
TaskSecrets,
TaskStatus,
} from '@backstage/plugin-scaffolder-node';
import { DateTime, Duration } from 'luxon';
import { TaskRecovery, TaskSpec } from '@backstage/plugin-scaffolder-common';
@@ -182,6 +182,7 @@ export class DatabaseTaskStore implements TaskStore {
async list(options: {
createdBy?: string;
status?: TaskStatus;
}): Promise<{ tasks: SerializedTask[] }> {
const queryBuilder = this.db<RawDbTaskRow>('tasks');
@@ -191,6 +192,10 @@ export class DatabaseTaskStore implements TaskStore {
});
}
if (options.status) {
queryBuilder.where({ status: options.status });
}
const results = await queryBuilder.orderBy('created_at', 'desc').select();
const tasks = results.map(result => ({
@@ -20,13 +20,14 @@ import { JsonObject, JsonValue, Observable } from '@backstage/types';
import { Logger } from 'winston';
import ObservableImpl from 'zen-observable';
import {
TaskSecrets,
SerializedTask,
SerializedTaskEvent,
TaskBroker,
TaskBrokerDispatchOptions,
TaskCompletionState,
TaskContext,
TaskSecrets,
TaskStatus,
} from '@backstage/plugin-scaffolder-node';
import { InternalTaskSecrets, TaskStore } from './types';
import { readDuration } from './helper';
@@ -279,13 +280,17 @@ export class StorageTaskBroker implements TaskBroker {
async list(options?: {
createdBy?: string;
status?: TaskStatus;
}): Promise<{ tasks: SerializedTask[] }> {
if (!this.storage.list) {
throw new Error(
'TaskStore does not implement the list method. Please implement the list method to be able to list tasks',
);
}
return await this.storage.list({ createdBy: options?.createdBy });
return await this.storage.list({
createdBy: options?.createdBy,
status: options?.status,
});
}
private deferredDispatch = defer();
@@ -14,20 +14,20 @@
* limitations under the License.
*/
import { JsonValue, JsonObject, HumanDuration } from '@backstage/types';
import { HumanDuration, JsonObject, JsonValue } from '@backstage/types';
import { TaskSpec, TaskStep } from '@backstage/plugin-scaffolder-common';
import { TaskSecrets } from '@backstage/plugin-scaffolder-node';
import {
TemplateAction,
TaskStatus as _TaskStatus,
TaskCompletionState as _TaskCompletionState,
SerializedTask as _SerializedTask,
TaskEventType as _TaskEventType,
SerializedTaskEvent as _SerializedTaskEvent,
TaskBrokerDispatchResult as _TaskBrokerDispatchResult,
TaskBrokerDispatchOptions as _TaskBrokerDispatchOptions,
TaskContext as _TaskContext,
TaskBroker as _TaskBroker,
TaskBrokerDispatchOptions as _TaskBrokerDispatchOptions,
TaskBrokerDispatchResult as _TaskBrokerDispatchResult,
TaskCompletionState as _TaskCompletionState,
TaskContext as _TaskContext,
TaskEventType as _TaskEventType,
TaskSecrets,
TaskStatus as _TaskStatus,
TemplateAction,
} from '@backstage/plugin-scaffolder-node';
/**
@@ -190,7 +190,10 @@ export interface TaskStore {
tasks: { taskId: string }[];
}>;
list?(options: { createdBy?: string }): Promise<{ tasks: SerializedTask[] }>;
list?(options: {
createdBy?: string;
status?: TaskStatus;
}): Promise<{ tasks: SerializedTask[] }>;
emitLogEvent(options: TaskStoreEmitOptions): Promise<void>;
@@ -432,10 +432,11 @@ describe('createRouter', () => {
});
const response = await request(app).get(
`/v2/tasks?createdBy=user:default/foo`,
`/v2/tasks?createdBy=user:default/foo&status=completed`,
);
expect(taskBroker.list).toHaveBeenCalledWith({
createdBy: 'user:default/foo',
status: 'completed',
});
expect(response.status).toEqual(200);
@@ -15,10 +15,10 @@
*/
import {
createLegacyAuthAdapters,
HostDiscovery,
PluginDatabaseManager,
UrlReader,
createLegacyAuthAdapters,
} from '@backstage/backend-common';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
import { CatalogApi } from '@backstage/catalog-client';
@@ -35,22 +35,22 @@ import { ScmIntegrations } from '@backstage/integration';
import { HumanDuration, JsonObject, JsonValue } from '@backstage/types';
import {
TaskSpec,
TemplateEntityStepV1beta3,
TemplateEntityV1beta3,
templateEntityV1beta3Validator,
TemplateParametersV1beta3,
TemplateEntityStepV1beta3,
} from '@backstage/plugin-scaffolder-common';
import {
RESOURCE_TYPE_SCAFFOLDER_ACTION,
RESOURCE_TYPE_SCAFFOLDER_TEMPLATE,
scaffolderActionPermissions,
scaffolderTaskPermissions,
scaffolderTemplatePermissions,
taskCancelPermission,
taskCreatePermission,
taskReadPermission,
templateParameterReadPermission,
templateStepReadPermission,
scaffolderTaskPermissions,
} from '@backstage/plugin-scaffolder-common/alpha';
import express from 'express';
import Router from 'express-promise-router';
@@ -58,8 +58,9 @@ import { validate } from 'jsonschema';
import { Logger } from 'winston';
import { z } from 'zod';
import {
TemplateAction,
TaskBroker,
TaskStatus,
TemplateAction,
TemplateFilter,
TemplateGlobal,
} from '@backstage/plugin-scaffolder-node';
@@ -587,8 +588,17 @@ export async function createRouter(
);
}
const [statusQuery] = [req.query.status].flat();
if (
typeof statusQuery !== 'string' &&
typeof statusQuery !== 'undefined'
) {
throw new InputError('status query parameter must be a string');
}
const tasks = await taskBroker.list({
createdBy: userEntityRef,
status: statusQuery ? (statusQuery as TaskStatus) : undefined,
});
res.status(200).json(tasks);
+1 -1
View File
@@ -315,7 +315,7 @@ export interface TaskBroker {
// (undocumented)
get(taskId: string): Promise<SerializedTask>;
// (undocumented)
list?(options?: { createdBy?: string }): Promise<{
list?(options?: { createdBy?: string; status?: TaskStatus }): Promise<{
tasks: SerializedTask[];
}>;
// (undocumented)
+4 -1
View File
@@ -180,5 +180,8 @@ export interface TaskBroker {
get(taskId: string): Promise<SerializedTask>;
list?(options?: { createdBy?: string }): Promise<{ tasks: SerializedTask[] }>;
list?(options?: {
createdBy?: string;
status?: TaskStatus;
}): Promise<{ tasks: SerializedTask[] }>;
}