-
Notifications
You must be signed in to change notification settings - Fork 107
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0dfd1c7
commit d24104c
Showing
16 changed files
with
387 additions
and
97 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
src/views/workflow-actions/__fixtures__/workflow-details-params.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export const mockWorkflowDetailsParams = { | ||
cluster: 'testCluster', | ||
domain: 'testDomain', | ||
workflowId: 'testWorkflowId', | ||
runId: 'testRunId', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
...-actions/workflow-actions-modal-content/__tests__/workflow-actions-modal-content.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { HttpResponse } from 'msw'; | ||
|
||
import { render, screen, userEvent } from '@/test-utils/rtl'; | ||
|
||
import { type CancelWorkflowResponse } from '@/route-handlers/cancel-workflow/cancel-workflow.types'; | ||
|
||
import { mockWorkflowActionsConfig } from '../../__fixtures__/workflow-actions-config'; | ||
import { mockWorkflowDetailsParams } from '../../__fixtures__/workflow-details-params'; | ||
import WorkflowActionsModalContent from '../workflow-actions-modal-content'; | ||
|
||
const mockEnqueue = jest.fn(); | ||
const mockDequeue = jest.fn(); | ||
jest.mock('baseui/snackbar', () => ({ | ||
...jest.requireActual('baseui/snackbar'), | ||
useSnackbar: () => ({ | ||
enqueue: mockEnqueue, | ||
dequeue: mockDequeue, | ||
}), | ||
})); | ||
|
||
describe(WorkflowActionsModalContent.name, () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('renders the modal content as expected', async () => { | ||
setup({}); | ||
|
||
expect(await screen.findAllByText('Mock cancel workflow')).toHaveLength(2); | ||
expect( | ||
screen.getByText('Mock cancel a workflow execution') | ||
).toBeInTheDocument(); | ||
expect( | ||
screen.getByRole('button', { name: 'Mock cancel workflow' }) | ||
).toBeInTheDocument(); | ||
}); | ||
|
||
it('calls onCloseModal when the Go Back button is clicked', async () => { | ||
const { user, mockOnClose } = setup({}); | ||
|
||
const goBackButton = await screen.findByText('Go back'); | ||
await user.click(goBackButton); | ||
|
||
expect(mockOnClose).toHaveBeenCalled(); | ||
}); | ||
|
||
it('calls mockCancelWorkflow, sends toast, and closes modal when the action button is clicked', async () => { | ||
const { user, mockOnClose, mockCancelWorkflow } = setup({}); | ||
|
||
const cancelButton = await screen.findByRole('button', { | ||
name: 'Mock cancel workflow', | ||
}); | ||
await user.click(cancelButton); | ||
|
||
expect(mockCancelWorkflow).toHaveBeenCalled(); | ||
expect(mockEnqueue).toHaveBeenCalledWith( | ||
expect.objectContaining({ | ||
message: 'Mock notification', | ||
}), | ||
undefined | ||
); | ||
expect(mockOnClose).toHaveBeenCalled(); | ||
}); | ||
|
||
it('Displays banner when the action button is clicked and action fails', async () => { | ||
const { user, mockOnClose, mockCancelWorkflow } = setup({ error: true }); | ||
|
||
const cancelButton = await screen.findByRole('button', { | ||
name: 'Mock cancel workflow', | ||
}); | ||
await user.click(cancelButton); | ||
|
||
expect(mockCancelWorkflow).toHaveBeenCalled(); | ||
expect( | ||
await screen.findByText('Failed to cancel workflow') | ||
).toBeInTheDocument(); | ||
expect(mockOnClose).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
|
||
function setup({ error }: { error?: boolean }) { | ||
const user = userEvent.setup(); | ||
const mockOnClose = jest.fn(); | ||
const mockCancelWorkflow = jest.fn(); | ||
|
||
render( | ||
<WorkflowActionsModalContent | ||
action={mockWorkflowActionsConfig[0]} | ||
params={{ ...mockWorkflowDetailsParams }} | ||
onCloseModal={mockOnClose} | ||
/>, | ||
{ | ||
endpointsMocks: [ | ||
{ | ||
path: '/api/domains/:domain/:cluster/workflows/:workflowId/:runId/cancel', | ||
httpMethod: 'POST', | ||
mockOnce: false, | ||
httpResolver: () => { | ||
mockCancelWorkflow(); | ||
if (error) { | ||
return HttpResponse.json( | ||
{ message: 'Failed to cancel workflow' }, | ||
{ status: 500 } | ||
); | ||
} | ||
return HttpResponse.json({} satisfies CancelWorkflowResponse); | ||
}, | ||
}, | ||
], | ||
} | ||
); | ||
|
||
return { user, mockOnClose, mockCancelWorkflow }; | ||
} |
27 changes: 27 additions & 0 deletions
27
.../workflow-actions/workflow-actions-modal-content/workflow-actions-modal-content.styles.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { type Theme, withStyle } from 'baseui'; | ||
import { type BannerOverrides } from 'baseui/banner'; | ||
import { ModalBody, ModalFooter, ModalHeader } from 'baseui/modal'; | ||
|
||
export const styled = { | ||
ModalHeader: withStyle(ModalHeader, ({ $theme }: { $theme: Theme }) => ({ | ||
marginTop: $theme.sizing.scale850, | ||
})), | ||
ModalBody: withStyle(ModalBody, ({ $theme }: { $theme: Theme }) => ({ | ||
marginBottom: $theme.sizing.scale800, | ||
})), | ||
ModalFooter: withStyle(ModalFooter, { | ||
display: 'flex', | ||
justifyContent: 'space-between', | ||
}), | ||
}; | ||
|
||
export const overrides = { | ||
banner: { | ||
Root: { | ||
style: { | ||
marginLeft: 0, | ||
marginRight: 0, | ||
}, | ||
}, | ||
} satisfies BannerOverrides, | ||
}; |
121 changes: 121 additions & 0 deletions
121
src/views/workflow-actions/workflow-actions-modal-content/workflow-actions-modal-content.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { useMutation, useQueryClient } from '@tanstack/react-query'; | ||
import { Banner, HIERARCHY, KIND as BANNER_KIND } from 'baseui/banner'; | ||
import { KIND as BUTTON_KIND, SIZE } from 'baseui/button'; | ||
import { ModalButton } from 'baseui/modal'; | ||
import { useSnackbar } from 'baseui/snackbar'; | ||
import { MdCheckCircle, MdErrorOutline } from 'react-icons/md'; | ||
|
||
import logger from '@/utils/logger'; | ||
import request from '@/utils/request'; | ||
import { type RequestError } from '@/utils/request/request-error'; | ||
|
||
import { type WorkflowActionInputParams } from '../workflow-actions.types'; | ||
|
||
import { overrides, styled } from './workflow-actions-modal-content.styles'; | ||
import { type Props } from './workflow-actions-modal-content.types'; | ||
|
||
export default function WorkflowActionsModalContent<R>({ | ||
action, | ||
params, | ||
onCloseModal, | ||
}: Props<R>) { | ||
const queryClient = useQueryClient(); | ||
const { enqueue, dequeue } = useSnackbar(); | ||
const { mutateAsync, isPending, error } = useMutation< | ||
R, | ||
RequestError, | ||
WorkflowActionInputParams | ||
>( | ||
{ | ||
mutationFn: ({ | ||
domain, | ||
cluster, | ||
workflowId, | ||
runId, | ||
}: WorkflowActionInputParams) => | ||
request( | ||
`/api/domains/${domain}/${cluster}/workflows/${workflowId}/${runId}/${action.apiRoute}`, | ||
{ | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
// TODO: pass the input here when implementing extended workflow actions | ||
}), | ||
} | ||
).then((res) => res.json() as R), | ||
}, | ||
queryClient | ||
); | ||
|
||
return ( | ||
<> | ||
<styled.ModalHeader>{action.label} workflow</styled.ModalHeader> | ||
<styled.ModalBody> | ||
{action.subtitle} | ||
{error && ( | ||
<Banner | ||
hierarchy={HIERARCHY.low} | ||
kind={BANNER_KIND.negative} | ||
overrides={overrides.banner} | ||
artwork={{ | ||
icon: MdErrorOutline, | ||
}} | ||
> | ||
{error.message} | ||
</Banner> | ||
)} | ||
</styled.ModalBody> | ||
<styled.ModalFooter> | ||
<ModalButton | ||
size={SIZE.compact} | ||
kind={BUTTON_KIND.secondary} | ||
onClick={onCloseModal} | ||
> | ||
Go back | ||
</ModalButton> | ||
<ModalButton | ||
size={SIZE.compact} | ||
kind={BUTTON_KIND.primary} | ||
onClick={async () => { | ||
mutateAsync(params).then( | ||
(result) => { | ||
const { | ||
// TODO: input, | ||
...workflowDetailsParams | ||
} = params; | ||
|
||
queryClient.invalidateQueries({ | ||
queryKey: ['describe_workflow', workflowDetailsParams], | ||
}); | ||
|
||
onCloseModal(); | ||
|
||
action.onSuccess({ | ||
result, | ||
inputParams: params, | ||
sendNotification: (message, duration) => | ||
enqueue( | ||
{ | ||
message, | ||
startEnhancer: MdCheckCircle, | ||
actionMessage: 'OK', | ||
actionOnClick: () => dequeue(), | ||
}, | ||
duration | ||
), | ||
}); | ||
}, | ||
(e) => | ||
logger.error( | ||
{ error: e, params }, | ||
'Failed to perform workflow action' | ||
) | ||
); | ||
}} | ||
isLoading={isPending} | ||
> | ||
{action.label} workflow | ||
</ModalButton> | ||
</styled.ModalFooter> | ||
</> | ||
); | ||
} |
10 changes: 10 additions & 0 deletions
10
...s/workflow-actions/workflow-actions-modal-content/workflow-actions-modal-content.types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { | ||
type WorkflowAction, | ||
type WorkflowActionInputParams, | ||
} from '../workflow-actions.types'; | ||
|
||
export type Props<R> = { | ||
action: WorkflowAction<R>; | ||
params: WorkflowActionInputParams; | ||
onCloseModal: () => void; | ||
}; |
Oops, something went wrong.