Skip to content

Commit

Permalink
Update listWorkflows route handler to list archived workflows (#766)
Browse files Browse the repository at this point in the history
Add field "listType" to listWorkflows route handler to choose between listing regular workflows or listing archived workflows
Modify schema to enforce required fields for the archival use case
Change time filters to use ISO string instead of ns timestamp
  • Loading branch information
adhityamamallan authored Dec 27, 2024
1 parent b68d1bb commit bb5b60a
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ describe('getListWorkflowExecutionsQuery', () => {
workflowStatus: 'WORKFLOW_EXECUTION_CLOSE_STATUS_TERMINATED',
sortColumn: 'CloseTime',
sortOrder: 'ASC',
timeColumn: 'StartTime',
timeRangeStart: '1712066100000000',
timeRangeEnd: '1712096100000000',
});
Expand All @@ -18,15 +19,16 @@ describe('getListWorkflowExecutionsQuery', () => {
it('should return query for running status', () => {
const query = getListWorkflowExecutionsQuery({
search: 'mocksearchterm',
timeColumn: 'StartTime',
workflowStatus: 'WORKFLOW_EXECUTION_CLOSE_STATUS_INVALID',
});
expect(query).toEqual(
'(WorkflowType = "mocksearchterm" OR WorkflowID = "mocksearchterm" OR RunID = "mocksearchterm") AND CloseTime = missing ORDER BY StartTime DESC'
);
});

it('should return default query with no params', () => {
const query = getListWorkflowExecutionsQuery({});
it('should return default query with no params except for time column', () => {
const query = getListWorkflowExecutionsQuery({ timeColumn: 'StartTime' });
expect(query).toEqual('ORDER BY StartTime DESC');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ import { type SortOrder } from '@/utils/sort-by';
import { WORKFLOW_STATUSES } from '@/views/shared/workflow-status-tag/workflow-status-tag.constants';
import type { WorkflowStatus } from '@/views/shared/workflow-status-tag/workflow-status-tag.types';

import { type TimeColumn } from '../list-workflows.types';

export default function getListWorkflowExecutionsQuery({
search,
workflowStatus,
sortColumn,
sortOrder,
timeColumn,
timeRangeStart,
timeRangeEnd,
}: {
search?: string;
workflowStatus?: WorkflowStatus;
sortColumn?: string;
sortOrder?: SortOrder;
timeColumn: TimeColumn;
timeRangeStart?: string;
timeRangeEnd?: string;
}) {
Expand All @@ -37,11 +41,11 @@ export default function getListWorkflowExecutionsQuery({
}

if (timeRangeStart) {
searchQueries.push(`StartTime > "${timeRangeStart}"`);
searchQueries.push(`${timeColumn} > "${timeRangeStart}"`);
}

if (timeRangeEnd) {
searchQueries.push(`StartTime <= "${timeRangeEnd}"`);
searchQueries.push(`${timeColumn} <= "${timeRangeEnd}"`);
}

return (
Expand Down
38 changes: 22 additions & 16 deletions src/route-handlers/list-workflows/list-workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,29 @@ export async function listWorkflows(
);
}

const listWorkflowsParams = {
domain: decodedParams.domain,
pageSize: queryParams.pageSize,
nextPageToken: queryParams.nextPage,
query:
queryParams.inputType === 'query'
? queryParams.query
: getListWorkflowExecutionsQuery({
search: queryParams.search,
workflowStatus: queryParams.status,
sortColumn: queryParams.sortColumn,
sortOrder: queryParams.sortOrder,
timeColumn: queryParams.timeColumn,
timeRangeStart: queryParams.timeRangeStart,
timeRangeEnd: queryParams.timeRangeEnd,
}),
};

try {
const res = await ctx.grpcClusterMethods.listWorkflows({
domain: decodedParams.domain,
pageSize: queryParams.pageSize,
nextPageToken: queryParams.nextPage,
query:
queryParams.inputType === 'query'
? queryParams.query
: getListWorkflowExecutionsQuery({
search: queryParams.search,
workflowStatus: queryParams.status,
sortColumn: queryParams.sortColumn,
sortOrder: queryParams.sortOrder,
timeRangeStart: queryParams.timeRangeStart,
timeRangeEnd: queryParams.timeRangeEnd,
}),
});
const res =
queryParams.listType === 'archived'
? await ctx.grpcClusterMethods.archivedWorkflows(listWorkflowsParams)
: await ctx.grpcClusterMethods.listWorkflows(listWorkflowsParams);

const response: ListWorkflowsResponse = {
workflows: mapExecutionsToWorkflows(res.executions),
Expand Down
2 changes: 2 additions & 0 deletions src/route-handlers/list-workflows/list-workflows.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export type ListWorkflowsRequestQueryParams = z.input<
typeof listWorkflowsQueryParamSchema
>;

export type TimeColumn = ListWorkflowsRequestQueryParams['timeColumn'];

export type ListWorkflowsResponse = {
workflows: Array<DomainWorkflow>;
nextPage: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,58 @@
import { z } from 'zod';

import getTimestampNsFromISO from '@/utils/datetime/get-timestamp-ns-from-iso';
import { SORT_ORDERS } from '@/utils/sort-by';
import isWorkflowStatus from '@/views/shared/workflow-status-tag/helpers/is-workflow-status';
import { type WorkflowStatus } from '@/views/shared/workflow-status-tag/workflow-status-tag.types';

const listWorkflowsQueryParamSchema = z.object({
pageSize: z
.string()
.transform((val) => parseInt(val, 10))
.pipe(
z.number().positive({ message: 'Page size must be a positive integer' })
),
inputType: z.enum(['search', 'query']),
search: z.string().optional(),
query: z.string().optional(),
status: z
.custom<WorkflowStatus>(isWorkflowStatus, {
message: 'Invalid workflow status',
})
.optional(),
timeRangeStart: z
.string()
.datetime()
.transform(getTimestampNsFromISO)
.optional(),
timeRangeEnd: z
.string()
.datetime()
.transform(getTimestampNsFromISO)
.optional(),
sortColumn: z.string().optional(),
sortOrder: z.enum(SORT_ORDERS, { message: 'Invalid sort order' }).optional(),
nextPage: z.string().optional(),
});
const listWorkflowsQueryParamSchema = z
.object({
pageSize: z
.string()
.transform((val) => parseInt(val, 10))
.pipe(
z.number().positive({ message: 'Page size must be a positive integer' })
),
listType: z.enum(['default', 'archived']),
inputType: z.enum(['search', 'query']),
search: z.string().optional(),
query: z.string().optional(),
status: z
.custom<WorkflowStatus>(isWorkflowStatus, {
message: 'Invalid workflow status',
})
.optional(),
timeColumn: z
.enum(['StartTime', 'CloseTime'])
.optional()
.default('StartTime'),
timeRangeStart: z.string().datetime().optional(),
timeRangeEnd: z.string().datetime().optional(),
sortColumn: z.string().optional(),
sortOrder: z
.enum(SORT_ORDERS, { message: 'Invalid sort order' })
.optional(),
nextPage: z.string().optional(),
})
.superRefine((queryParams, ctx) => {
if (
queryParams.listType === 'archived' &&
queryParams.inputType === 'search'
) {
if (queryParams.timeColumn === 'StartTime') {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Cannot search for archived workflows by start time',
});
}

if (!queryParams.timeRangeStart || !queryParams.timeRangeEnd) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
'Start and End time need to be passed for searching archived workflows',
});
}
}
});

export default listWorkflowsQueryParamSchema;
1 change: 1 addition & 0 deletions src/views/domain-workflows/hooks/use-list-workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export default function useListWorkflows({
url: `/api/domains/${domain}/${cluster}/workflows`,
query: {
...requestQueryParams,
listType: 'default',
pageSize: pageSize.toString(),
nextPage: pageParam as string,
} as const satisfies ListWorkflowsRequestQueryParams,
Expand Down

0 comments on commit bb5b60a

Please sign in to comment.