Skip to content

Commit 6f812eb

Browse files
authored
Merge pull request #1020
* refactor(33990): add a custom footer to the modal * fix(33990): fix layout * refactor(33990): add a replace confirmation to the create new draft * refactor(33990): fix translations * test(33990): add tests * test(33990): add tests * test(33990): refactor the test wrapper * test(33990): fix the wrappers * test(33990): fix the wrappers * fix(33990): fix styling * fix(33990): fix translations
1 parent e1588fb commit 6f812eb

File tree

7 files changed

+198
-20
lines changed

7 files changed

+198
-20
lines changed

hivemq-edge-frontend/src/components/Modal/ConfirmationDialog.spec.cy.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Button } from '@chakra-ui/react'
12
import ConfirmationDialog from './ConfirmationDialog'
23

34
describe('ConfirmationDialog', () => {
@@ -49,6 +50,25 @@ describe('ConfirmationDialog', () => {
4950
cy.getByTestId('confirmation-submit').should('have.text', 'Alternative Text')
5051
})
5152

53+
it('should render secondary footer', () => {
54+
cy.mountWithProviders(
55+
<ConfirmationDialog
56+
isOpen={true}
57+
onClose={cy.stub}
58+
header="Hello"
59+
message="The nice long message"
60+
footer={<Button>A new action</Button>}
61+
/>
62+
)
63+
64+
cy.get('header').should('have.text', 'Hello')
65+
cy.get('footer').within(() => {
66+
cy.get('button').eq(0).should('have.text', 'Cancel')
67+
cy.get('button').eq(1).should('have.text', 'A new action')
68+
cy.get('button').eq(2).should('have.text', 'Delete')
69+
})
70+
})
71+
5272
it('should be accessible ', () => {
5373
cy.injectAxe()
5474
cy.mountWithProviders(

hivemq-edge-frontend/src/components/Modal/ConfirmationDialog.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface ConfirmationDialogProps {
2121
prompt?: string
2222
action?: string | null
2323
onSubmit?: () => void
24+
footer?: React.ReactNode
2425
}
2526

2627
const ConfirmationDialog: FC<ConfirmationDialogProps> = ({
@@ -31,12 +32,18 @@ const ConfirmationDialog: FC<ConfirmationDialogProps> = ({
3132
prompt,
3233
action,
3334
onSubmit,
35+
footer,
3436
}) => {
3537
const { t } = useTranslation()
3638
const cancelRef = useRef<HTMLButtonElement>()
3739

3840
return (
39-
<AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef as RefObject<FocusableElement>} onClose={onClose}>
41+
<AlertDialog
42+
isOpen={isOpen}
43+
leastDestructiveRef={cancelRef as RefObject<FocusableElement>}
44+
onClose={onClose}
45+
size="lg"
46+
>
4047
<AlertDialogOverlay>
4148
<AlertDialogContent>
4249
<AlertDialogHeader fontSize="lg" fontWeight="bold">
@@ -48,18 +55,18 @@ const ConfirmationDialog: FC<ConfirmationDialogProps> = ({
4855
{prompt && <Text data-testid="confirmation-prompt">{prompt}</Text>}
4956
</AlertDialogBody>
5057

51-
<AlertDialogFooter>
58+
<AlertDialogFooter gap={3}>
5259
<Button ref={cancelRef as LegacyRef<HTMLButtonElement>} onClick={onClose} data-testid="confirmation-cancel">
5360
{t('action.cancel')}
5461
</Button>
62+
{footer}
5563
<Button
5664
data-testid="confirmation-submit"
5765
onClick={() => {
5866
onClose()
5967
onSubmit?.()
6068
}}
6169
variant="danger"
62-
ml={3}
6370
>
6471
{action || t('action.delete')}
6572
</Button>

hivemq-edge-frontend/src/extensions/datahub/__test-utils__/react-flow.mocks.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import useDataHubDraftStore from '@datahub/hooks/useDataHubDraftStore.ts'
12
import type { FC, PropsWithChildren } from 'react'
23
import type { Edge, Node } from '@xyflow/react'
3-
import { useLocation } from 'react-router-dom'
4+
import { MemoryRouter, useLocation } from 'react-router-dom'
45
import { Card, CardBody, CardHeader } from '@chakra-ui/react'
56

67
import { BehaviorPolicyTransitionEvent, DataPolicyValidator } from '@/api/__generated__'
@@ -204,17 +205,58 @@ export const getPolicyPublishWrapper = (report?: DryRunResults<unknown, never>[]
204205
return Wrapper
205206
}
206207

208+
// TODO[NVL] Too much code duplication; refactor the whole wrappers as customisable factory
207209
export const getPolicyWrapper = ({ status, nodes }: { status?: DesignerStatus; nodes?: Node[] }) => {
208210
const Wrapper: FC<PropsWithChildren> = ({ children }) => {
211+
const { pathname } = useLocation()
212+
const { nodes: nodeList } = useDataHubDraftStore()
213+
209214
return (
210215
<MockStoreWrapper
211216
config={{
212217
initialState: { status: status, nodes: nodes || [] },
213218
}}
214219
>
215220
{children}
221+
<Card mt={50} size="sm" variant="filled">
222+
<CardHeader>Testing Dashboard</CardHeader>
223+
<CardBody data-testid="test-pathname">{pathname}</CardBody>
224+
<CardBody data-testid="test-nodes">{nodeList.length}</CardBody>
225+
</Card>
216226
</MockStoreWrapper>
217227
)
218228
}
219229
return Wrapper
220230
}
231+
232+
export const getPolicyWrapperWithRouter = ({ status, nodes }: { status?: DesignerStatus; nodes?: Node[] }) => {
233+
const Wrapper: FC<PropsWithChildren> = ({ children }) => {
234+
const { pathname } = useLocation()
235+
const { nodes: nodeList } = useDataHubDraftStore()
236+
237+
return (
238+
<MockStoreWrapper
239+
config={{
240+
initialState: { status: status, nodes: nodes || [] },
241+
}}
242+
>
243+
{children}
244+
<Card mt={50} size="sm" variant="filled">
245+
<CardHeader>Testing Dashboard</CardHeader>
246+
<CardBody data-testid="test-pathname">{pathname}</CardBody>
247+
<CardBody data-testid="test-nodes">{nodeList.length}</CardBody>
248+
</Card>
249+
</MockStoreWrapper>
250+
)
251+
}
252+
253+
const WrapperRouter: FC<PropsWithChildren> = ({ children }) => {
254+
return (
255+
<MemoryRouter initialEntries={['/datahub/CREATE_POLICY']}>
256+
<Wrapper>{children}</Wrapper>
257+
</MemoryRouter>
258+
)
259+
}
260+
261+
return WrapperRouter
262+
}

hivemq-edge-frontend/src/extensions/datahub/components/helpers/DraftCTA.spec.cy.tsx

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,93 @@
1-
import { WrapperTestRoute } from '@/__test-utils__/hooks/WrapperTestRoute.tsx'
1+
import { getPolicyWrapper, MOCK_NODE_DATA_POLICY } from '@datahub/__test-utils__/react-flow.mocks.tsx'
22
import DraftCTA from '@datahub/components/helpers/DraftCTA.tsx'
3+
import { DesignerStatus } from '@datahub/types.ts'
34

45
describe('DraftCTA', () => {
56
beforeEach(() => {
67
cy.viewport(800, 800)
78
})
89

9-
it('should render ', () => {
10-
cy.mountWithProviders(<DraftCTA />, { wrapper: WrapperTestRoute })
10+
it('should render the CTA', () => {
11+
cy.mountWithProviders(<DraftCTA />, {
12+
wrapper: getPolicyWrapper({ status: DesignerStatus.MODIFIED, nodes: [MOCK_NODE_DATA_POLICY] }),
13+
})
1114

1215
cy.get('button').should('contain.text', 'Create a new policy')
1316

14-
cy.getByTestId('test-pathname').should('contain.text', '/')
15-
cy.get('button').click()
16-
cy.getByTestId('test-pathname').should('contain.text', '/datahub/CREATE_POLICY')
17+
cy.get('[role="alertdialog"]').should('not.exist')
18+
cy.getByTestId('test-pathname').should('have.text', '/')
19+
cy.getByTestId('test-nodes').should('have.text', '1')
20+
})
21+
22+
context('Draft confirmation', () => {
23+
it('should navigate if clean', () => {
24+
cy.mountWithProviders(<DraftCTA />, {
25+
wrapper: getPolicyWrapper({ status: DesignerStatus.MODIFIED, nodes: [] }),
26+
})
27+
28+
cy.getByTestId('test-nodes').should('have.text', '0')
29+
cy.get('[role="alertdialog"]').should('not.exist')
30+
cy.get('button').click()
31+
cy.get('[role="alertdialog"]').should('not.exist')
32+
cy.getByTestId('test-pathname').should('have.text', '/datahub/CREATE_POLICY')
33+
})
34+
35+
it('should trigger the confirmation dialog', () => {
36+
cy.mountWithProviders(<DraftCTA />, {
37+
wrapper: getPolicyWrapper({ status: DesignerStatus.MODIFIED, nodes: [MOCK_NODE_DATA_POLICY] }),
38+
})
39+
40+
cy.getByTestId('test-nodes').should('have.text', '1')
41+
cy.get('[role="alertdialog"]').should('not.exist')
42+
cy.get('button').click()
43+
cy.get('section[role="alertdialog"]').within(() => {
44+
cy.get('header').should('contain.text', 'You already have an active draft')
45+
cy.getByTestId('confirmation-message').should(
46+
'have.text',
47+
'If you create a new one, the content of your current draft will be cleared from the canvas. This action cannot be reversed.'
48+
)
49+
cy.get('footer button').eq(0).should('have.text', 'Cancel')
50+
cy.get('footer button').eq(1).should('have.text', 'Open existing draft')
51+
cy.get('footer button').eq(2).should('have.text', 'Create new empty draft')
52+
53+
cy.get('footer button').eq(0).click()
54+
})
55+
cy.get('[role="alertdialog"]').should('not.exist')
56+
cy.getByTestId('test-pathname').should('have.text', '/')
57+
cy.getByTestId('test-nodes').should('have.text', '1')
58+
})
59+
60+
it('should confirm replacement', () => {
61+
cy.mountWithProviders(<DraftCTA />, {
62+
wrapper: getPolicyWrapper({ status: DesignerStatus.MODIFIED, nodes: [MOCK_NODE_DATA_POLICY] }),
63+
})
64+
65+
cy.getByTestId('test-nodes').should('have.text', '1')
66+
cy.get('[role="alertdialog"]').should('not.exist')
67+
cy.get('button').click()
68+
cy.get('section[role="alertdialog"]').within(() => {
69+
cy.get('footer button').eq(2).click()
70+
})
71+
cy.get('[role="alertdialog"]').should('not.exist')
72+
cy.getByTestId('test-pathname').should('have.text', '/datahub/CREATE_POLICY')
73+
cy.getByTestId('test-nodes').should('have.text', '0')
74+
})
75+
76+
it('should confirm navigation', () => {
77+
cy.mountWithProviders(<DraftCTA />, {
78+
wrapper: getPolicyWrapper({ status: DesignerStatus.MODIFIED, nodes: [MOCK_NODE_DATA_POLICY] }),
79+
})
80+
81+
cy.getByTestId('test-nodes').should('have.text', '1')
82+
cy.get('[role="alertdialog"]').should('not.exist')
83+
cy.get('button').click()
84+
cy.get('section[role="alertdialog"]').within(() => {
85+
cy.get('footer button').eq(1).click()
86+
})
87+
cy.get('[role="alertdialog"]').should('not.exist')
88+
cy.getByTestId('test-pathname').should('have.text', '/datahub/CREATE_POLICY')
89+
cy.getByTestId('test-nodes').should('have.text', '1')
90+
})
1791
})
1892

1993
it('should be accessible', () => {

hivemq-edge-frontend/src/extensions/datahub/components/helpers/DraftCTA.tsx

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,56 @@
11
import type { FC } from 'react'
22
import { useTranslation } from 'react-i18next'
33
import { useNavigate } from 'react-router-dom'
4-
import { Button } from '@chakra-ui/react'
4+
import { Button, useDisclosure } from '@chakra-ui/react'
55
import { LuFilePlus } from 'react-icons/lu'
66

7+
import ConfirmationDialog from '@/components/Modal/ConfirmationDialog.tsx'
8+
79
import { DesignerStatus, DesignerPolicyType } from '@datahub/types.ts'
810
import useDataHubDraftStore from '@datahub/hooks/useDataHubDraftStore.ts'
911

1012
const DraftCTA: FC = () => {
1113
const { t } = useTranslation('datahub')
1214
const navigate = useNavigate()
13-
const { setStatus, reset } = useDataHubDraftStore()
15+
const { setStatus, reset, isDirty } = useDataHubDraftStore()
16+
const { isOpen, onOpen, onClose } = useDisclosure()
1417

1518
const handleOnClick = () => {
19+
if (isDirty()) onOpen()
20+
else navigate(`/datahub/${DesignerPolicyType.CREATE_POLICY}`)
21+
}
22+
23+
const handleReplaceDraft = () => {
24+
onClose()
1625
reset()
1726
setStatus(DesignerStatus.DRAFT)
1827
navigate(`/datahub/${DesignerPolicyType.CREATE_POLICY}`)
1928
}
2029

30+
const handleGoDraft = () => {
31+
onClose()
32+
navigate(`/datahub/${DesignerPolicyType.CREATE_POLICY}`)
33+
}
34+
2135
return (
22-
<Button leftIcon={<LuFilePlus />} onClick={handleOnClick} variant="primary">
23-
{t('Listings.policy.action.create')}
24-
</Button>
36+
<>
37+
<Button leftIcon={<LuFilePlus />} onClick={handleOnClick} variant="primary">
38+
{t('Listings.policy.action.create')}
39+
</Button>
40+
<ConfirmationDialog
41+
isOpen={isOpen}
42+
onClose={onClose}
43+
onSubmit={handleReplaceDraft}
44+
message={t('workspace.toolbars.modal.replace.confirmation')}
45+
header={t('workspace.toolbars.modal.replace.header')}
46+
action={t('workspace.controls.replace')}
47+
footer={
48+
<Button data-testid="confirmation-navigate-draft" onClick={handleGoDraft}>
49+
{t('workspace.controls.goDraft')}
50+
</Button>
51+
}
52+
/>
53+
</>
2554
)
2655
}
2756

hivemq-edge-frontend/src/extensions/datahub/hooks/usePolicyGuards.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect } from 'vitest'
22
import { renderHook } from '@testing-library/react'
33

4-
import { getPolicyWrapper, MOCK_NODE_DATA_POLICY } from '@datahub/__test-utils__/react-flow.mocks.tsx'
4+
import { getPolicyWrapperWithRouter, MOCK_NODE_DATA_POLICY } from '@datahub/__test-utils__/react-flow.mocks.tsx'
55
import { DesignerStatus } from '@datahub/types.ts'
66
import { usePolicyGuards } from '@datahub/hooks/usePolicyGuards.ts'
77

@@ -16,7 +16,7 @@ describe('usePolicyGuards', () => {
1616

1717
it('should render guards for a read-only policy', async () => {
1818
const { result } = renderHook(() => usePolicyGuards('my-topic'), {
19-
wrapper: getPolicyWrapper({ status: DesignerStatus.LOADED }),
19+
wrapper: getPolicyWrapperWithRouter({ status: DesignerStatus.LOADED }),
2020
})
2121
expect(result.current.status).toStrictEqual(DesignerStatus.LOADED)
2222
expect(result.current.isPolicyEditable).toBeFalsy()
@@ -29,7 +29,7 @@ describe('usePolicyGuards', () => {
2929

3030
it('should render guards for a modified policy', async () => {
3131
const { result } = renderHook(() => usePolicyGuards('my-topic'), {
32-
wrapper: getPolicyWrapper({ status: DesignerStatus.LOADED }),
32+
wrapper: getPolicyWrapperWithRouter({ status: DesignerStatus.LOADED }),
3333
})
3434
expect(result.current.status).toStrictEqual(DesignerStatus.LOADED)
3535
expect(result.current.isPolicyEditable).toBeFalsy()
@@ -42,7 +42,7 @@ describe('usePolicyGuards', () => {
4242

4343
it('should render guards for a protected node', async () => {
4444
const { result } = renderHook(() => usePolicyGuards('node-id'), {
45-
wrapper: getPolicyWrapper({ status: DesignerStatus.MODIFIED, nodes: [MOCK_NODE_DATA_POLICY] }),
45+
wrapper: getPolicyWrapperWithRouter({ status: DesignerStatus.MODIFIED, nodes: [MOCK_NODE_DATA_POLICY] }),
4646
})
4747
expect(result.current.status).toStrictEqual(DesignerStatus.MODIFIED)
4848
expect(result.current.isPolicyEditable).toBeTruthy()

hivemq-edge-frontend/src/extensions/datahub/locales/en/datahub.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,11 @@
136136
"modal": {
137137
"clear": {
138138
"header": "Do you want to clear the Designer?",
139-
"confirmation": "It will clean the content of the canvas, leaving the Designer empty and ready to create a new draft. The action cannot be reversed"
139+
"confirmation": "It will clean the content of the canvas, leaving the Designer empty and ready to create a new draft. The action cannot be reversed."
140+
},
141+
"replace": {
142+
"header": "You already have an active draft",
143+
"confirmation": "If you create a new one, the content of your current draft will be cleared from the canvas. This action cannot be reversed."
140144
},
141145
"modify": {
142146
"header": "Do you want to modify the policy?",
@@ -151,6 +155,8 @@
151155
"fitView": "Fit to the canvas",
152156
"toggleInteractivity": "Lock the canvas",
153157
"clear": "Clear the Designer",
158+
"replace": "Create new empty draft",
159+
"goDraft": "Open existing draft",
154160
"modify": "Modify the policy",
155161
"clone": "Clone as a new draft",
156162
"shortcuts": "Help using the Designer"

0 commit comments

Comments
 (0)