From 5d10ec981c8d169452309f976e9aad2a2a5f8961 Mon Sep 17 00:00:00 2001 From: Ray Liu Date: Thu, 5 Dec 2024 16:34:01 +1100 Subject: [PATCH 1/5] add timeline and comment module in sequence runs --- src/api/sequenceRun.ts | 93 ++++- src/api/types/sequence-run.d.ts | 241 ++++++++++++- src/components/common/buttons/Button.tsx | 13 +- src/components/common/timelines/Timeline.tsx | 8 +- .../sequenceRuns/SequenceRunDetailsDrawer.tsx | 36 +- .../sequenceRuns/SequenceRunTable.tsx | 123 ++++--- .../sequenceRuns/SequenceRunTimeline.tsx | 325 ++++++++++++++++++ 7 files changed, 751 insertions(+), 88 deletions(-) create mode 100644 src/modules/runs/components/sequenceRuns/SequenceRunTimeline.tsx diff --git a/src/api/sequenceRun.ts b/src/api/sequenceRun.ts index 57d70ed..8cab2f8 100644 --- a/src/api/sequenceRun.ts +++ b/src/api/sequenceRun.ts @@ -1,8 +1,13 @@ import config from '@/config'; -import createClient from 'openapi-fetch'; +import createClient, { ParamsOption } from 'openapi-fetch'; import type { paths, components } from './types/sequence-run'; -import { useSuspenseQuery, useQuery } from '@tanstack/react-query'; -import { authMiddleware, UseSuspenseQueryOptions, UseQueryOptions } from './utils'; +import { useSuspenseQuery, useQuery, useMutation } from '@tanstack/react-query'; +import { + authMiddleware, + UseSuspenseQueryOptions, + UseQueryOptions, + UseMutationOptions, +} from './utils'; import { env } from '@/utils/commonUtils'; const client = createClient({ baseUrl: config.apiEndpoint.sequenceRun }); @@ -30,15 +35,70 @@ export function createSequenceRunFetchingHook(path: K) { }; } -export function createSequenceRunQueryHook(path: K) { - return function ({ params, reactQuery }: UseQueryOptions) { +export function createSequenceRunQueryHook< + K extends keyof paths, + M extends keyof paths[K] & 'get', + R = paths[K][M] extends { responses: { 200: { content: { 'application/json': infer T } } } } + ? T + : never, +>(path: K) { + const versionedPath = getVersionedPath(path); + return function ({ + params, + reactQuery, + signal, + }: Omit, 'queryKey' | 'queryFn'> & { signal?: AbortSignal }) { + return useQuery({ + ...reactQuery, + queryKey: [versionedPath, params], + queryFn: async ({ signal: querySignal }) => { + // @ts-expect-error: params is dynamic type type for openapi-fetch + const { data } = await client.GET(versionedPath, { + params: params as ParamsOption, + signal: signal || querySignal, + }); + return data as R; + }, + }); + }; +} + +export function createSequenceRunPostMutationHook(path: K) { + return function ({ params, reactQuery, body }: UseMutationOptions) { const versionedPath = getVersionedPath(path); - return useQuery({ + return useMutation({ ...reactQuery, - queryKey: [path, params], - queryFn: async ({ signal }) => { + mutationFn: async () => { // @ts-expect-error: params is dynamic type type for openapi-fetch - const { data } = await client.GET(versionedPath, { params, signal }); + const { data } = await client.POST(versionedPath, { params, body: body }); + return data; + }, + }); + }; +} + +export function createSequenceRunPatchMutationHook(path: K) { + return function ({ params, reactQuery, body }: UseMutationOptions) { + const versionedPath = getVersionedPath(path); + return useMutation({ + ...reactQuery, + mutationFn: async () => { + // @ts-expect-error: params is dynamic type type for openapi-fetch + const { data } = await client.PATCH(versionedPath, { params, body: body }); + return data; + }, + }); + }; +} + +export function createSequenceRunDeleteMutationHook(path: K) { + return function ({ params, reactQuery }: UseMutationOptions) { + const versionedPath = getVersionedPath(path); + return useMutation({ + ...reactQuery, + mutationFn: async () => { + // @ts-expect-error: params is dynamic type type for openapi-fetch + const { data } = await client.DELETE(versionedPath, { params }); return data; }, }); @@ -49,3 +109,18 @@ export type SequenceRunModel = components['schemas']['Sequence']; export const useSequenceRunListModel = createSequenceRunQueryHook('/api/v1/sequence/'); export const useSequenceRunDetailModel = createSequenceRunQueryHook('/api/v1/sequence/{id}/'); +export const useSequenceRunStateListModel = createSequenceRunQueryHook( + '/api/v1/sequence/{orcabusId}/state/' +); +export const useSequenceRunCommentListModel = createSequenceRunQueryHook( + '/api/v1/sequence/{orcabusId}/comment/' +); +export const useSequenceRunCommentCreateModel = createSequenceRunPostMutationHook( + '/api/v1/sequence/{orcabusId}/comment/' +); +export const useSequenceRunCommentUpdateModel = createSequenceRunPatchMutationHook( + '/api/v1/sequence/{orcabusId}/comment/{id}/' +); +export const useSequenceRunCommentDeleteModel = createSequenceRunDeleteMutationHook( + '/api/v1/sequence/{orcabusId}/comment/{id}/soft_delete/' +); diff --git a/src/api/types/sequence-run.d.ts b/src/api/types/sequence-run.d.ts index 4c4875f..350775d 100644 --- a/src/api/types/sequence-run.d.ts +++ b/src/api/types/sequence-run.d.ts @@ -20,6 +20,70 @@ export interface paths { patch?: never; trace?: never; }; + "/api/v1/sequence/{orcabusId}/comment/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["apiV1SequenceCommentList"]; + put?: never; + post: operations["apiV1SequenceCommentCreate"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/sequence/{orcabusId}/comment/{id}/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["apiV1SequenceCommentPartialUpdate"]; + trace?: never; + }; + "/api/v1/sequence/{orcabusId}/comment/{id}/soft_delete/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete: operations["apiV1SequenceCommentSoftDeleteDestroy"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/sequence/{orcabusId}/state/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["apiV1SequenceStateList"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/v1/sequence/{id}/": { parameters: { query?: never; @@ -27,6 +91,7 @@ export interface paths { path?: never; cookie?: never; }; + /** @description Since we have custom orcabus_id prefix for each model, we need to remove the prefix before retrieving it. */ get: operations["apiV1SequenceRetrieve"]; put?: never; post?: never; @@ -40,6 +105,17 @@ export interface paths { export type webhooks = Record; export interface components { schemas: { + Comment: { + readonly orcabusId: string; + comment: string; + associationId: string; + /** Format: date-time */ + readonly createdAt: string; + createdBy: string; + /** Format: date-time */ + readonly updatedAt: string; + isDeleted?: boolean; + }; PaginatedSequenceList: { links: { /** @@ -60,8 +136,19 @@ export interface components { }; results: components["schemas"]["Sequence"][]; }; + PatchedComment: { + readonly orcabusId?: string; + comment?: string; + associationId?: string; + /** Format: date-time */ + readonly createdAt?: string; + createdBy?: string; + /** Format: date-time */ + readonly updatedAt?: string; + isDeleted?: boolean; + }; Sequence: { - readonly id: number; + readonly orcabusId: string; instrumentRunId: string; runVolumeName: string; runFolderPath: string; @@ -76,6 +163,17 @@ export interface components { sampleSheetName?: string | null; sequenceRunId?: string | null; sequenceRunName?: string | null; + v1pre3Id?: string | null; + icaProjectId?: string | null; + apiUrl?: string | null; + }; + State: { + readonly orcabusId: string; + status: string; + /** Format: date-time */ + timestamp: string; + comment?: string | null; + sequence: string; }; /** * @description * `STARTED` - Started @@ -97,14 +195,34 @@ export interface operations { apiV1SequenceList: { parameters: { query?: { + apiUrl?: string | null; + endTime?: string | null; + flowcellBarcode?: string | null; + icaProjectId?: string | null; + instrumentRunId?: string; + orcabusId?: string; /** @description Which field to use when ordering the results. */ ordering?: string; /** @description A page number within the paginated result set. */ page?: number; + reagentBarcode?: string | null; /** @description Number of results to return per page. */ rowsPerPage?: number; + runDataUri?: string; + runFolderPath?: string; + runVolumeName?: string; + sampleSheetName?: string | null; /** @description A search term. */ search?: string; + sequenceRunId?: string | null; + sequenceRunName?: string | null; + startTime?: string; + /** @description * `STARTED` - Started + * * `FAILED` - Failed + * * `SUCCEEDED` - Succeeded + * * `ABORTED` - Aborted */ + status?: "STARTED" | "FAILED" | "SUCCEEDED" | "ABORTED"; + v1pre3Id?: string | null; }; header?: never; path?: never; @@ -122,13 +240,130 @@ export interface operations { }; }; }; + apiV1SequenceCommentList: { + parameters: { + query?: never; + header?: never; + path: { + orcabusId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Comment"][]; + }; + }; + }; + }; + apiV1SequenceCommentCreate: { + parameters: { + query?: never; + header?: never; + path: { + orcabusId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/x-www-form-urlencoded": components["schemas"]["Comment"]; + "multipart/form-data": components["schemas"]["Comment"]; + "application/json": components["schemas"]["Comment"]; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Comment"]; + }; + }; + }; + }; + apiV1SequenceCommentPartialUpdate: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + orcabusId: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/x-www-form-urlencoded": components["schemas"]["PatchedComment"]; + "multipart/form-data": components["schemas"]["PatchedComment"]; + "application/json": components["schemas"]["PatchedComment"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Comment"]; + }; + }; + }; + }; + apiV1SequenceCommentSoftDeleteDestroy: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + orcabusId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No response body */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + apiV1SequenceStateList: { + parameters: { + query?: never; + header?: never; + path: { + orcabusId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["State"][]; + }; + }; + }; + }; apiV1SequenceRetrieve: { parameters: { query?: never; header?: never; path: { - /** @description A unique integer value identifying this sequence. */ - id: number; + id: string; }; cookie?: never; }; diff --git a/src/components/common/buttons/Button.tsx b/src/components/common/buttons/Button.tsx index 2024843..f7e2a52 100644 --- a/src/components/common/buttons/Button.tsx +++ b/src/components/common/buttons/Button.tsx @@ -1,8 +1,9 @@ import { FC, ReactNode, MouseEventHandler } from 'react'; +import { classNames } from '@/utils/commonUtils'; export interface ButtonProps { type?: 'primary' | 'secondary' | 'light' | 'green' | 'red' | 'yellow' | 'gray'; - size?: 'sm' | 'md' | 'lg'; + size?: 'xs' | 'sm' | 'md' | 'lg'; rounded?: boolean; children: ReactNode; onClick?: MouseEventHandler; @@ -58,6 +59,7 @@ const Button: FC = ({ }; const sizeStyles: { [key: string]: string } = { + xs: ' text-xs px-2 py-1 ', sm: ' text-sm px-3 py-1.5 ', md: ' text-md px-4 py-2 ', lg: ' text-lg px-5 py-2.5 ', @@ -69,7 +71,14 @@ const Button: FC = ({ return ( + + + +
Comment
+