Skip to content

Commit 5bda3c5

Browse files
authored
Merge pull request #370
refactor(19824): Fix the UX flow in the designer toolbox * refactor(19824): move the selection listener up the DOM tree * fix(19824): keep the node selected * fix(19824): improve iconography * feat(19824): add conditional wrapper * feat(19824): add support for stepper control * feat(19824): add link to the publish stepper * refactor(19824): update props * refactor(19824): redirect to the check stepper after publishing * refactor(19824): update translations * refactor(19824): relocating the "new draft" CTA * refactor(19824): remove empty footer from the table * test(19824): fix the test with hidden footer * fix(19824): hide CTA is licence not in place * fix(21009): Add navigation to the error node's panel (#376)
1 parent beeb2bf commit 5bda3c5

File tree

18 files changed

+273
-71
lines changed

18 files changed

+273
-71
lines changed

hivemq-edge/src/frontend/.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v16.20.1
1+
v18.16.0

hivemq-edge/src/frontend/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,5 +128,9 @@
128128
},
129129
"msw": {
130130
"workerDirectory": "public"
131+
},
132+
"engines": {
133+
"node": "18",
134+
"pnpm": "8"
131135
}
132136
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { ConditionalWrapper } from '@/components/ConditonalWrapper.tsx'
2+
import { Button, Text } from '@chakra-ui/react'
3+
4+
describe('ConditionalWrapper', () => {
5+
beforeEach(() => {
6+
cy.viewport(800, 250)
7+
})
8+
it('should renders', () => {
9+
cy.mountWithProviders(
10+
<ConditionalWrapper condition={true} wrapper={(children) => <Button data-testid="wrapper">{children}</Button>}>
11+
<Text data-testid="content">Test</Text>
12+
</ConditionalWrapper>
13+
)
14+
cy.getByTestId('content').should('be.visible')
15+
cy.getByTestId('wrapper').should('be.visible')
16+
})
17+
18+
it('should renders', () => {
19+
cy.mountWithProviders(
20+
<ConditionalWrapper condition={false} wrapper={(children) => <Button data-testid="wrapper">{children}</Button>}>
21+
<Text data-testid="content">Test</Text>
22+
</ConditionalWrapper>
23+
)
24+
cy.getByTestId('content').should('be.visible')
25+
cy.getByTestId('wrapper').should('not.exist')
26+
})
27+
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React, { FC } from 'react'
2+
3+
interface ConditionalWrapperProps {
4+
children: React.ReactElement
5+
condition: boolean
6+
wrapper: (children: React.ReactElement) => JSX.Element
7+
}
8+
9+
export const ConditionalWrapper: FC<ConditionalWrapperProps> = ({ condition, wrapper, children }) =>
10+
condition ? wrapper(children) : children

hivemq-edge/src/frontend/src/components/PaginatedTable/PaginatedTable.spec.cy.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe('PaginatedTable', () => {
4343
cy.get('th').should('have.length', 2)
4444
cy.get('th').eq(0).should('contain.text', 'item')
4545
cy.get('th').eq(1).should('contain.text', 'value')
46-
cy.get('tr').should('have.length', 10 + 2)
46+
cy.get('tr').should('have.length', 10 + 1)
4747

4848
cy.get('[aria-label="Go to the first page"]').should('be.visible')
4949
})

hivemq-edge/src/frontend/src/components/PaginatedTable/PaginatedTable.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ const PaginatedTable = <T,>({
8686
getFacetedMinMaxValues: getFacetedMinMaxValues(),
8787
})
8888

89+
const hasFooters =
90+
table
91+
.getFooterGroups()
92+
.map((group) => group.headers.map((header) => header.column.columnDef.footer))
93+
.flat()
94+
.filter(Boolean).length > 0
95+
96+
console.log('XXXXX', hasFooters)
8997
return (
9098
<>
9199
<TableContainer overflowY="auto" overflowX="auto" whiteSpace="normal">
@@ -167,17 +175,19 @@ const PaginatedTable = <T,>({
167175
)
168176
})}
169177
</Tbody>
170-
<Tfoot>
171-
{table.getFooterGroups().map((footerGroup) => (
172-
<Tr key={footerGroup.id}>
173-
{footerGroup.headers.map((header) => (
174-
<Td key={header.id}>
175-
{header.isPlaceholder ? null : flexRender(header.column.columnDef.footer, header.getContext())}
176-
</Td>
177-
))}
178-
</Tr>
179-
))}
180-
</Tfoot>
178+
{hasFooters && (
179+
<Tfoot>
180+
{table.getFooterGroups().map((footerGroup) => (
181+
<Tr key={footerGroup.id}>
182+
{footerGroup.headers.map((header) => (
183+
<Td key={header.id}>
184+
{header.isPlaceholder ? null : flexRender(header.column.columnDef.footer, header.getContext())}
185+
</Td>
186+
))}
187+
</Tr>
188+
))}
189+
</Tfoot>
190+
)}
181191
</Table>
182192
</TableContainer>
183193
{enablePagination && <PaginationBar table={table} pageSizes={pageSizes} />}

hivemq-edge/src/frontend/src/extensions/datahub/__test-utils__/MockStoreWrapper.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,17 @@ interface MockChecksStoreWrapperProps {
4646

4747
export const MockChecksStoreWrapper: FC<MockChecksStoreWrapperProps> = ({ config, children }) => {
4848
const { setNode, setReport } = usePolicyChecksStore()
49+
const { onAddNodes } = useDataHubDraftStore()
4950

5051
useEffect(() => {
5152
const { node, report } = config
52-
if (node) setNode(node)
53+
if (node) {
54+
onAddNodes([{ item: node, type: 'add' }])
55+
setNode(node)
56+
}
5357

5458
if (report) setReport(report)
55-
}, [config, setNode, setReport])
59+
}, [config, onAddNodes, setNode, setReport])
5660

5761
return (
5862
<>

hivemq-edge/src/frontend/src/extensions/datahub/components/DataHubPage.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
11
import { FC } from 'react'
22
import { useTranslation } from 'react-i18next'
3-
import { Outlet } from 'react-router-dom'
4-
import { Box } from '@chakra-ui/react'
3+
import { Outlet, useLocation } from 'react-router-dom'
4+
import { Box, Flex } from '@chakra-ui/react'
55

66
import PageContainer from '@/components/PageContainer.tsx'
77
import { CAPABILITY, useGetCapability } from '@/api/hooks/useFrontendServices/useGetCapability.tsx'
88
import LicenseWarning from '@datahub/components/helpers/LicenseWarning.tsx'
9+
import DraftCTA from '@datahub/components/helpers/DraftCTA.tsx'
910

1011
const DataHubPage: FC = () => {
1112
const { t } = useTranslation('datahub')
1213
const hasDataHub = useGetCapability(CAPABILITY.DATAHUB)
14+
const { pathname } = useLocation()
15+
16+
const isMainPage = pathname === '/datahub'
1317

1418
return (
15-
<PageContainer title={t('page.title') as string} subtitle={t('page.description') as string}>
19+
<PageContainer
20+
title={t('page.title') as string}
21+
subtitle={t('page.description') as string}
22+
cta={
23+
isMainPage &&
24+
hasDataHub && (
25+
<Flex height="100%" justifyContent="flex-end" alignItems="flex-end" pb={6}>
26+
<DraftCTA />
27+
</Flex>
28+
)
29+
}
30+
>
1631
{hasDataHub && <Outlet />}
1732
{!hasDataHub && (
1833
<Box width="100%" mt={20}>

hivemq-edge/src/frontend/src/extensions/datahub/components/controls/DesignerToolbox.tsx

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { FC, useState } from 'react'
2-
import { Panel } from 'reactflow'
2+
import { Panel, useReactFlow } from 'reactflow'
3+
import { useLocation, useNavigate } from 'react-router-dom'
34
import { useTranslation } from 'react-i18next'
45
import { motion } from 'framer-motion'
56
import {
@@ -20,25 +21,46 @@ import {
2021
useSteps,
2122
VStack,
2223
} from '@chakra-ui/react'
23-
import { FaAngleLeft, FaAngleRight, FaTools } from 'react-icons/fa'
24-
import { MdSkipNext, MdSkipPrevious } from 'react-icons/md'
24+
import { FaTools } from 'react-icons/fa'
25+
import { LuPanelLeftOpen, LuPanelRightOpen, LuSkipBack, LuSkipForward } from 'react-icons/lu'
2526

2627
import { ToolboxNodes } from '@datahub/components/controls/ToolboxNodes.tsx'
2728
import { ToolboxDryRun } from '@datahub/components/controls/ToolboxDryRun.tsx'
2829
import { ToolboxPublish } from '@datahub/components/controls/ToolboxPublish.tsx'
2930
import DraftStatus from '@datahub/components/helpers/DraftStatus.tsx'
31+
import { ANIMATION } from '@datahub/utils/datahub.utils.ts'
3032

3133
const stepKeys = ['build', 'check', 'publish']
3234

35+
// eslint-disable-next-line @typescript-eslint/no-namespace
36+
export namespace DesignerToolBoxProps {
37+
export enum Steps {
38+
TOOLBOX_NODES = 0,
39+
TOOLBOX_CHECK = 1,
40+
TOOLBOX_PUBLISH = 2,
41+
}
42+
}
43+
44+
export interface DesignerToolBoxProps {
45+
onActiveStep?: (step: DesignerToolBoxProps.Steps) => void
46+
}
47+
3348
const DesignerToolbox: FC = () => {
3449
const { t } = useTranslation('datahub')
50+
const { fitView } = useReactFlow()
51+
const navigate = useNavigate()
52+
const { pathname } = useLocation()
3553
const { getButtonProps, getDisclosureProps, isOpen } = useDisclosure()
3654
const [hidden, setHidden] = useState(!isOpen)
3755
const { activeStep, setActiveStep } = useSteps({
38-
index: 0,
56+
index: DesignerToolBoxProps.Steps.TOOLBOX_NODES,
3957
count: stepKeys.length,
4058
})
4159

60+
const onActiveStep = (step: number) => {
61+
setActiveStep(step)
62+
}
63+
4264
const steps = stepKeys.map((key) => ({
4365
key: key,
4466
title: t(`workspace.toolbox.panel.${key}.title`),
@@ -55,7 +77,7 @@ const DesignerToolbox: FC = () => {
5577
icon={
5678
<>
5779
<Icon as={FaTools} />
58-
<Icon as={isOpen ? FaAngleLeft : FaAngleRight} />
80+
<Icon as={isOpen ? LuPanelRightOpen : LuPanelLeftOpen} ml={2} boxSize="24px" />
5981
</>
6082
}
6183
{...getButtonProps()}
@@ -109,15 +131,15 @@ const DesignerToolbox: FC = () => {
109131
<IconButton
110132
data-testid="toolbox-navigation-prev"
111133
aria-label={t('workspace.toolbox.navigation.previous')}
112-
icon={<MdSkipPrevious />}
113-
isDisabled={activeStep === 0}
134+
icon={<LuSkipBack />}
135+
isDisabled={activeStep === DesignerToolBoxProps.Steps.TOOLBOX_NODES}
114136
onClick={() => setActiveStep((s) => s - 1)}
115137
/>
116138
<IconButton
117139
data-testid="toolbox-navigation-next"
118140
aria-label={t('workspace.toolbox.navigation.next')}
119-
icon={<MdSkipNext />}
120-
isDisabled={activeStep === 2}
141+
icon={<LuSkipForward />}
142+
isDisabled={activeStep === DesignerToolBoxProps.Steps.TOOLBOX_PUBLISH}
121143
onClick={() => setActiveStep((s) => s + 1)}
122144
/>
123145
</ButtonGroup>
@@ -129,9 +151,21 @@ const DesignerToolbox: FC = () => {
129151
{activeStep === index && (
130152
<>
131153
<Box pt={5} h="100%">
132-
{activeStep === 0 && <ToolboxNodes />}
133-
{activeStep === 1 && <ToolboxDryRun />}
134-
{activeStep === 2 && <ToolboxPublish />}
154+
{activeStep === DesignerToolBoxProps.Steps.TOOLBOX_NODES && <ToolboxNodes />}
155+
{activeStep === DesignerToolBoxProps.Steps.TOOLBOX_CHECK && (
156+
<ToolboxDryRun
157+
onActiveStep={onActiveStep}
158+
onShowNode={(node) =>
159+
fitView({ nodes: [node], padding: 3, duration: ANIMATION.FIT_VIEW_DURATION_MS })
160+
}
161+
onShowEditor={(node) =>
162+
navigate(`node/${node.type}/${node.id}`, { state: { origin: pathname } })
163+
}
164+
/>
165+
)}
166+
{activeStep === DesignerToolBoxProps.Steps.TOOLBOX_PUBLISH && (
167+
<ToolboxPublish onActiveStep={onActiveStep} />
168+
)}
135169
</Box>
136170
</>
137171
)}

hivemq-edge/src/frontend/src/extensions/datahub/components/controls/ToolboxDryRun.spec.cy.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,35 @@ describe('ToolboxDryRun', () => {
4646
cy.getByAriaLabel('Close').click()
4747
})
4848

49+
it('should allow access to node', () => {
50+
cy.mountWithProviders(
51+
<ToolboxDryRun onShowNode={cy.stub().as('onShowNode')} onShowEditor={cy.stub().as('onShowEditor')} />,
52+
{ wrapper }
53+
)
54+
cy.getByTestId('toolbox-policy-check').click()
55+
cy.get('h2 button').eq(0).click()
56+
57+
cy.get('@onShowNode').should('not.have.been.calledWith')
58+
cy.getByTestId('report-error-fitView').click()
59+
cy.get('@onShowNode').should('have.been.calledWithMatch', { id: 'node-id', type: DataHubNodeType.DATA_POLICY })
60+
61+
cy.get('@onShowEditor').should('not.have.been.calledWith')
62+
cy.getByTestId('report-error-config').click()
63+
cy.get('@onShowEditor').should('have.been.calledWithMatch', { id: 'node-id', type: DataHubNodeType.DATA_POLICY })
64+
cy.getByAriaLabel('Close').click()
65+
})
66+
4967
it('should be accessible', () => {
5068
cy.injectAxe()
5169
cy.mountWithProviders(<ToolboxDryRun />, { wrapper })
5270
cy.getByTestId('toolbox-policy-check').click()
53-
cy.checkAccessibility()
71+
cy.get('h2 button').eq(0).click()
72+
cy.checkAccessibility(undefined, {
73+
rules: {
74+
// TODO[NVL] CTooltip seems to generate false positives
75+
'color-contrast': { enabled: false },
76+
},
77+
})
5478
cy.percySnapshot('Component: ToolboxDryRun')
5579
})
5680
})

0 commit comments

Comments
 (0)