-
+
+ {/* title */}
+
+ {isFetchingWorkflowRunDetail ? (
+
+
+
+ ) : (
+
{workflowRunDetail?.workflowRunName}
+ )}
+
+ setIsOpenRerunWorkflowDialog(true),
+ disabled: isFetchingWorkflowRunRerunAllowedWorkflows,
+ },
+ ]}
+ className='bg-magpie-light-50 hover:text-magpie-light-500'
+ type='square'
+ />
+
-
-
+
+ {/* details */}
+
+
+
+
+
+ {/* libraries */}
+
+
+
+ }
+ onClose={handleCloseRerunWorkflowDialog}
+ closeBtn={{
+ label: 'Close',
+ onClick: handleCloseRerunWorkflowDialog,
+ }}
+ confirmBtn={{
+ label: 'Rerun',
+ onClick: handleRerunWorkflow,
+ disabled:
+ isFetchingWorkflowRunRerunAllowedWorkflows ||
+ !workflowRunRerunValidateDetail?.isValid ||
+ !selectedDataset,
+ }}
+ />
);
};
diff --git a/src/modules/runs/components/workflowRuns/WorkflowRunFilterHeader.tsx b/src/modules/runs/components/workflowRuns/WorkflowRunFilterHeader.tsx
index 0f3dd5e..3cd83ec 100644
--- a/src/modules/runs/components/workflowRuns/WorkflowRunFilterHeader.tsx
+++ b/src/modules/runs/components/workflowRuns/WorkflowRunFilterHeader.tsx
@@ -126,6 +126,13 @@ const WorkflowRunFilterHeader = () => {
setQueryParams({ workflowRunStatus: 'resolved' });
},
},
+ {
+ label: 'Deprecated',
+ subLabel: '',
+ onClick: () => {
+ setQueryParams({ workflowRunStatus: 'deprecated' });
+ },
+ },
{
label: 'Ongoing',
subLabel: '',
diff --git a/src/modules/runs/components/workflowRuns/WorkflowRunTable.tsx b/src/modules/runs/components/workflowRuns/WorkflowRunTable.tsx
index 4af43aa..963ad77 100644
--- a/src/modules/runs/components/workflowRuns/WorkflowRunTable.tsx
+++ b/src/modules/runs/components/workflowRuns/WorkflowRunTable.tsx
@@ -25,7 +25,7 @@ const WorkflowRunTable = ({ libraryOrcabusId }: { libraryOrcabusId?: string }) =
rowsPerPage: getPaginationParams().rowsPerPage || DEFAULT_PAGE_SIZE,
search: getQueryParams().search || undefined,
workflow__orcabus_id: getQueryParams().workflowTypeId || undefined,
- status: ['succeeded', 'failed', 'aborted', 'resolved'].includes(
+ status: ['succeeded', 'failed', 'aborted', 'resolved', 'deprecated'].includes(
getQueryParams().workflowRunStatus
)
? getQueryParams().workflowRunStatus
diff --git a/src/modules/runs/components/workflowRuns/WorkflowRunTimeline.tsx b/src/modules/runs/components/workflowRuns/WorkflowRunTimeline.tsx
index 8658fa1..5d8c7e6 100644
--- a/src/modules/runs/components/workflowRuns/WorkflowRunTimeline.tsx
+++ b/src/modules/runs/components/workflowRuns/WorkflowRunTimeline.tsx
@@ -11,16 +11,19 @@ import {
useWorkflowRunCommentCreateModel,
useWorkflowRunCommentUpdateModel,
useWorkflowRunCommentDeleteModel,
- useWorkflowRunResolvedStateCreateModel,
- useWorkflowRunResolvedStateUpdateModel,
+ useWorkflowRunStateCreateModel,
+ useWorkflowRunStateUpdateModel,
+ useWorkflowRunStateValidMapModel,
} from '@/api/workflow';
import { keepPreviousData } from '@tanstack/react-query';
import {
- CheckCircleIcon,
+ PlusCircleIcon,
+ PlusIcon,
ChatBubbleLeftRightIcon,
ChatBubbleBottomCenterTextIcon,
WrenchIcon,
TrashIcon,
+ ArrowsUpDownIcon,
} from '@heroicons/react/24/outline';
import toaster from '@/components/common/toaster';
import { Tooltip } from '@/components/common/tooltips';
@@ -32,11 +35,13 @@ import { getBadgeType, statusBackgroundColor } from '@/components/common/badges'
import { dayjs } from '@/utils/dayjs';
import { classNames, getUsername } from '@/utils/commonUtils';
import { BackdropWithText } from '@/components/common/backdrop';
+import { useWorkflowRunContext } from './WorkflowRunContext';
+import { Button } from '@/components/common/buttons';
const WorkflowRunTimeline = () => {
const { orcabusId } = useParams();
const { user } = useAuthContext();
-
+ const { refreshWorkflowRuns, setRefreshWorkflowRuns } = useWorkflowRunContext();
const [currentState, setCurrentState] = useState
(null);
const [selectedPayloadId, setSelectedPayloadId] = useState(null);
const [selectedState, setSelectedState] = useState(null);
@@ -46,10 +51,14 @@ const WorkflowRunTimeline = () => {
const [isOpenDeleteCommentDialog, setIsOpenDeleteCommentDialog] = useState(false);
const [commentId, setCommentId] = useState(null);
const [comment, setComment] = useState('');
- const [isOpenAddResolvedDialog, setIsOpenAddResolvedDialog] = useState(false);
- const [isOpenUpdateResolvedDialog, setIsOpenUpdateResolvedDialog] = useState(false);
- const [resolvedId, setResolvedId] = useState(null);
- const [resolvedComment, setResolvedComment] = useState('');
+
+ const [isReverseOrder, setIsReverseOrder] = useState(false);
+
+ const [isOpenAddStateDialog, setIsOpenAddStateDialog] = useState(false);
+ const [stateStatus, setStateStatus] = useState(null);
+ const [isOpenUpdateStateDialog, setIsOpenUpdateStateDialog] = useState(false);
+ const [stateId, setStateId] = useState(null);
+ const [stateComment, setStateComment] = useState('');
const {
data: workflowStateData,
@@ -62,6 +71,13 @@ const WorkflowRunTimeline = () => {
},
});
+ useEffect(() => {
+ if (refreshWorkflowRuns) {
+ refetchWorkflowState();
+ setRefreshWorkflowRuns(false);
+ }
+ }, [refreshWorkflowRuns, refetchWorkflowState, setRefreshWorkflowRuns]);
+
const {
data: workflowCommentData,
isFetching: isFetchingWorkflowComment,
@@ -73,49 +89,63 @@ const WorkflowRunTimeline = () => {
},
});
+ const { data: workflowRunStateValidMapData } = useWorkflowRunStateValidMapModel({
+ params: { path: { orcabusId: orcabusId?.split('.')[1] as string } },
+ reactQuery: {
+ enabled: !!orcabusId,
+ },
+ });
+
const workflowLastState = workflowStateData?.[workflowStateData.length - 1]?.status;
+ // check if the last state is valid for state creation
+ const isValidCreateState = Object.entries(workflowRunStateValidMapData || {}).some(([, value]) =>
+ (value as string[]).includes(workflowLastState || '')
+ );
+ // find all valid state key who has vale of workflowLastState
+ const validState = Object.entries(workflowRunStateValidMapData || {})
+ .filter(([, value]) => (value as string[]).includes(workflowLastState || ''))
+ .map(([key]) => key);
+
// format data and disply in the table
const workflowStateTimelineData = useMemo(
() =>
workflowStateData
- ? workflowStateData
- .map((state) => ({
- id: state.orcabusId,
- content: (
-
-
Status Updated
-
{state.status}
- {state.status === 'RESOLVED' && (
-
-
- {
- setResolvedId(state.orcabusId);
- setResolvedComment(state.comment || '');
- setIsOpenUpdateResolvedDialog(true);
- }}
- />
-
-
- )}
-
- ),
- datetime: dayjs(state.timestamp).format('YYYY-MM-DD HH:mm'),
- comment: state.comment || '',
- status: state.status,
- iconBackground: statusBackgroundColor(getBadgeType(state.status)),
- payloadId: state?.payload || '',
- eventType: 'stateChange' as const,
- }))
- .reverse()
+ ? workflowStateData.map((state) => ({
+ id: state.orcabusId,
+ content: (
+
+
Status Updated
+
{state.status}
+ {Object.keys(workflowRunStateValidMapData || {}).includes(state.status) && (
+
+
+ {
+ setStateId(state.orcabusId);
+ setStateComment(state.comment || '');
+ setIsOpenUpdateStateDialog(true);
+ }}
+ />
+
+
+ )}
+
+ ),
+ datetime: dayjs(state.timestamp).format('YYYY-MM-DD HH:mm'),
+ comment: state.comment || '',
+ status: state.status,
+ iconBackground: statusBackgroundColor(getBadgeType(state.status)),
+ payloadId: state?.payload || '',
+ eventType: 'stateChange' as const,
+ }))
: [],
- [workflowStateData]
+ [workflowStateData, workflowRunStateValidMapData]
);
const workflowCommentTimelineData = useMemo(
() =>
@@ -222,40 +252,40 @@ const WorkflowRunTimeline = () => {
]);
const {
- mutate: createWorkflowRunResolvedState,
- isSuccess: isCreatedWorkflowRunResolvedState,
- isError: isErrorCreatingWorkflowRunResolvedState,
- reset: resetCreateWorkflowRunResolvedState,
- } = useWorkflowRunResolvedStateCreateModel({
+ mutate: createWorkflowRunState,
+ isSuccess: isCreatedWorkflowRunState,
+ isError: isErrorCreatingWorkflowRunState,
+ reset: resetCreateWorkflowRunState,
+ } = useWorkflowRunStateCreateModel({
params: { path: { orcabusId: orcabusId?.split('.')[1] as string } },
body: {
- status: 'RESOLVED',
- comment: resolvedComment,
+ status: stateStatus,
+ comment: stateComment,
createdBy: user?.email,
},
});
- const handleResolvedEvent = () => {
- createWorkflowRunResolvedState();
- setIsOpenAddResolvedDialog(false);
+ const handleStateCreationEvent = () => {
+ createWorkflowRunState();
+ setIsOpenAddStateDialog(false);
};
useEffect(() => {
- if (isCreatedWorkflowRunResolvedState) {
- toaster.success({ title: 'Resolved Status added' });
+ if (isCreatedWorkflowRunState) {
+ toaster.success({ title: 'State added' });
refetchWorkflowState();
- resetCreateWorkflowRunResolvedState();
- setResolvedComment('');
+ resetCreateWorkflowRunState();
+ setStateComment('');
}
- if (isErrorCreatingWorkflowRunResolvedState) {
- toaster.error({ title: 'Error adding resolved status' });
- resetCreateWorkflowRunResolvedState();
+ if (isErrorCreatingWorkflowRunState) {
+ toaster.error({ title: 'Error adding state status' });
+ resetCreateWorkflowRunState();
}
}, [
- isCreatedWorkflowRunResolvedState,
+ isCreatedWorkflowRunState,
refetchWorkflowState,
- resetCreateWorkflowRunResolvedState,
- isErrorCreatingWorkflowRunResolvedState,
+ resetCreateWorkflowRunState,
+ isErrorCreatingWorkflowRunState,
]);
const handleTimelineSelect = (event: TimelineEvent) => {
@@ -352,44 +382,44 @@ const WorkflowRunTimeline = () => {
]);
const {
- mutate: updateWorkflowRunResolvedState,
- isSuccess: isUpdatedWorkflowRunResolvedState,
- isError: isErrorUpdatingWorkflowRunResolvedState,
- reset: resetUpdateWorkflowRunResolvedState,
- } = useWorkflowRunResolvedStateUpdateModel({
+ mutate: updateWorkflowRunState,
+ isSuccess: isUpdatedWorkflowRunState,
+ isError: isErrorUpdatingWorkflowRunState,
+ reset: resetUpdateWorkflowRunState,
+ } = useWorkflowRunStateUpdateModel({
params: {
path: {
orcabusId: orcabusId?.split('.')[1] as string,
- id: resolvedId?.split('.')[1] as string,
+ id: stateId?.split('.')[1] as string,
},
},
body: {
- comment: resolvedComment,
+ comment: stateComment,
},
});
- const handleUpdateResolved = () => {
- updateWorkflowRunResolvedState();
- setIsOpenUpdateResolvedDialog(false);
+ const handleUpdateState = () => {
+ updateWorkflowRunState();
+ setIsOpenUpdateStateDialog(false);
};
useEffect(() => {
- if (isUpdatedWorkflowRunResolvedState) {
- toaster.success({ title: 'Resolved Event updated successfully' });
+ if (isUpdatedWorkflowRunState) {
+ toaster.success({ title: 'State updated successfully' });
refetchWorkflowState();
- resetUpdateWorkflowRunResolvedState();
- setResolvedComment('');
+ resetUpdateWorkflowRunState();
+ setStateComment('');
}
- if (isErrorUpdatingWorkflowRunResolvedState) {
- toaster.error({ title: 'Error updating resolved event' });
- resetUpdateWorkflowRunResolvedState();
+ if (isErrorUpdatingWorkflowRunState) {
+ toaster.error({ title: 'Error updating state' });
+ resetUpdateWorkflowRunState();
}
}, [
- isUpdatedWorkflowRunResolvedState,
+ isUpdatedWorkflowRunState,
refetchWorkflowState,
- resetUpdateWorkflowRunResolvedState,
- isErrorUpdatingWorkflowRunResolvedState,
+ resetUpdateWorkflowRunState,
+ isErrorUpdatingWorkflowRunState,
]);
return (
@@ -399,39 +429,92 @@ const WorkflowRunTimeline = () => {
)}
-
-
Timeline
-
-
+
+
Timeline
+
+
+ {workflowRuntimelineData.length} events
+
+
•
+
+
+
+
+
+
+
+ {isValidCreateState && (
+
+ )}
+
Payload
diff --git a/src/stories/Button.stories.tsx b/src/stories/Button.stories.tsx
index 20fd140..66e3845 100644
--- a/src/stories/Button.stories.tsx
+++ b/src/stories/Button.stories.tsx
@@ -1,8 +1,9 @@
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
-import { Button } from '@/components/common/buttons';
+import { Button, IconButton } from '@/components/common/buttons';
import { ButtonProps } from '@/components/common/buttons/Button';
-import { CheckCircleIcon, PlusIcon } from '@heroicons/react/20/solid';
+import { IconButtonProps } from '@/components/common/buttons/IconButton';
+import { CheckCircleIcon, PlusIcon, ArrowDownOnSquareIcon } from '@heroicons/react/20/solid';
import { Spinner } from '@/components/common/spinner';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
@@ -83,3 +84,16 @@ export const CircularButtons: Story = {
className: '!p-2 !m-0',
},
};
+
+// Update the story type
+export const IconButtonStory: StoryObj
= {
+ render: () => (
+ console.log('testing')}
+ icon={}
+ tooltip='Export'
+ tooltipPosition='top'
+ tooltipBackground='white'
+ />
+ ),
+};
diff --git a/src/stories/Charts.stories.tsx b/src/stories/Charts.stories.tsx
index 141f720..e411b31 100644
--- a/src/stories/Charts.stories.tsx
+++ b/src/stories/Charts.stories.tsx
@@ -1,12 +1,8 @@
import type { Meta, StoryObj } from '@storybook/react';
import { PieChart, GanttChart, ScatterplotMatrix, StackedBarChart } from '@/components/charts';
-
-import {
- generateMockWorkflowRunData,
- groupBySubjectAndCount,
-} from '@/api/_mock/mockWorkflowRunData';
-import { workflowTypeData } from '@/api/_mock/mockSeedData';
-import { scatterPlotData } from '@/api/_mock/mockScatterPlotData';
+import { generateMockWorkflowRunData, groupBySubjectAndCount } from './_mock/mockWorkflowRunData';
+import { workflowTypeData } from './_mock/mockSeedData';
+import { scatterPlotData } from './_mock/mockScatterPlotData';
const ganttData = generateMockWorkflowRunData();
diff --git a/src/stories/Diagram.stories.tsx b/src/stories/Diagram.stories.tsx
index cae3f80..58fa8ca 100644
--- a/src/stories/Diagram.stories.tsx
+++ b/src/stories/Diagram.stories.tsx
@@ -1,6 +1,6 @@
import { WorkflowDiagram } from '@/components/diagrams';
import type { Meta, StoryObj } from '@storybook/react';
-import { mockStatusData } from '@/api/_mock/mockWorkflowStatusData';
+import { mockStatusData } from './_mock/mockWorkflowStatusData';
const meta: Meta = {
title: 'Components/Diagrams',
diff --git a/src/api/_mock/mockScatterPlotData.ts b/src/stories/_mock/mockScatterPlotData.ts
similarity index 99%
rename from src/api/_mock/mockScatterPlotData.ts
rename to src/stories/_mock/mockScatterPlotData.ts
index 2e43259..2fe5dd3 100644
--- a/src/api/_mock/mockScatterPlotData.ts
+++ b/src/stories/_mock/mockScatterPlotData.ts
@@ -1,3 +1,6 @@
+interface ScatterPlotData {
+ [name: string]: string | number | null;
+}
export const columns = [
'species',
'island',
@@ -8,7 +11,7 @@ export const columns = [
'sex',
];
-export const scatterPlotData = [
+export const scatterPlotData: ScatterPlotData[] = [
{
species: 'Adelie',
island: 'Torgersen',
diff --git a/src/api/_mock/mockSeedData.ts b/src/stories/_mock/mockSeedData.ts
similarity index 100%
rename from src/api/_mock/mockSeedData.ts
rename to src/stories/_mock/mockSeedData.ts
diff --git a/src/stories/_mock/mockWorkflowData.ts b/src/stories/_mock/mockWorkflowData.ts
new file mode 100644
index 0000000..57a3dc3
--- /dev/null
+++ b/src/stories/_mock/mockWorkflowData.ts
@@ -0,0 +1,5 @@
+interface TaskInput {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ [key: string]: any;
+}
+export const mockWorkflowData: Record = {};
diff --git a/src/api/_mock/mockWorkflowRunData.ts b/src/stories/_mock/mockWorkflowRunData.ts
similarity index 89%
rename from src/api/_mock/mockWorkflowRunData.ts
rename to src/stories/_mock/mockWorkflowRunData.ts
index 03492b5..5b22826 100644
--- a/src/api/_mock/mockWorkflowRunData.ts
+++ b/src/stories/_mock/mockWorkflowRunData.ts
@@ -41,7 +41,7 @@ interface TaskOutput {
taskName: string;
libraryIds: string[];
endStatus: string;
- subjectId?: string;
+ subjectId: string;
}
const extractAnchorText = (htmlString: string): string => {
@@ -82,16 +82,21 @@ const groupTasksByPortalRunId = (tasks: any[]) => {
// endStatus: string;
// }
+interface TaskInput {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ [key: string]: any;
+}
+
interface TaskRunData {
[taskName: string]: TaskOutput[];
}
export const generateMockWorkflowRunData = () => {
- const tasksData = { ...mockWorkflowData };
+ const tasksData: { [key: string]: TaskInput[] } = { ...mockWorkflowData };
Object.keys(tasksData).forEach((key) => {
tasksData[key] = groupTasksByPortalRunId(tasksData[key]);
});
- return tasksData as unknown as TaskRunData;
+ return tasksData as TaskRunData;
};
// Assuming the file exports mockData array
@@ -108,6 +113,9 @@ export function groupBySubjectAndCount(): SubjectWorkflowCount {
const result: SubjectWorkflowCount = {};
Object.keys(mockWorkflowData).forEach((workflowType) => {
+ console.log(`Processing workflow type: ${workflowType}`);
+ console.log(`Data for workflow:`, mockWorkflowData[workflowType]);
+
mockWorkflowData[workflowType].forEach((item) => {
const subjectID = extractAnchorText(item.SubjectID);
@@ -125,15 +133,3 @@ export function groupBySubjectAndCount(): SubjectWorkflowCount {
console.log(result);
return result;
}
-
-//{
-// SBJ05554: {
-// bcl_convert: 2,
-// wgs_alignment_qc: 1,
-// },
-// SBJ05555: {
-// bcl_convert: 1,
-// wgs_alignment_qc: 3,
-// },
-// // More subjects...
-// }
diff --git a/src/api/_mock/mockWorkflowStatusData.ts b/src/stories/_mock/mockWorkflowStatusData.ts
similarity index 100%
rename from src/api/_mock/mockWorkflowStatusData.ts
rename to src/stories/_mock/mockWorkflowStatusData.ts