Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(27113): Ensure a single policy used in the Designer #743

Merged
merged 17 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions hivemq-edge/src/frontend/src/components/PageContainer.spec.cy.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
/// <reference types="cypress" />
import PageContainer from './PageContainer.tsx'
import { Button } from '@chakra-ui/react'

const MOCK_STATUS_TEXT = 'This is a test'
const MOCK_HEADER = 'This is a test'
const MOCK_SUBHEADER = 'This is below the test'
const MOCK_CONTENT = <div data-testid="the-test-id">This is a dummy component</div>
const MOCK_CTA = <Button>Button</Button>

describe('PageContainer', () => {
beforeEach(() => {
// run these tests as if in a desktop
// browser with a 720p monitor
cy.viewport(800, 250)
})
it('should renders', () => {
cy.mountWithProviders(<PageContainer title={MOCK_STATUS_TEXT}>{MOCK_CONTENT}</PageContainer>)
// cy.get('.chakra-alert__title').should('contain.text', 'This is a test')
// cy.get('.chakra-alert__desc').should('contain.text', 'This is a title')
// cy.get("[role='alert']").should('have.attr', 'data-status', 'error')

it('should render properly', () => {
cy.mountWithProviders(
<PageContainer title={MOCK_HEADER} subtitle={MOCK_SUBHEADER} cta={MOCK_CTA}>
{MOCK_CONTENT}
</PageContainer>
)
cy.get('header h1').should('have.text', 'This is a test')
cy.get('header h1 + p').should('have.text', 'This is below the test')
cy.getByTestId('page-container-cta').find('button').should('have.text', 'Button')
cy.getByTestId('the-test-id').should('have.text', 'This is a dummy component')
})

it('should also render', () => {
cy.mountWithProviders(<PageContainer>{MOCK_CONTENT}</PageContainer>)
it('should be accessible', () => {
cy.injectAxe()
cy.mountWithProviders(
<PageContainer title={MOCK_HEADER} subtitle={MOCK_SUBHEADER} cta={<Button>Button</Button>}>
{MOCK_CONTENT}
</PageContainer>
)

cy.checkAccessibility()
})
})
23 changes: 6 additions & 17 deletions hivemq-edge/src/frontend/src/components/PageContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
import { FC, ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import { Box, Text, Flex, Heading, VisuallyHidden } from '@chakra-ui/react'
import { Box, Text, Flex, Heading, chakra as Chakra } from '@chakra-ui/react'

interface PageContainerProps {
title?: string
title: string
subtitle?: string
children?: ReactNode
cta?: ReactNode
}

const PageContainer: FC<PageContainerProps> = ({ title, subtitle, children, cta }) => {
const { t } = useTranslation()

return (
<Flex flexDirection="column" p={4} pt={6} flexGrow={1}>
<Flex gap="50px" data-testid="page-container-header">
<Box maxW="50vw" pb={6}>
<Heading as="h1">
{title ? (
title
) : (
<VisuallyHidden>
<h1>{t('translation:navigation.mainPage')}</h1>
</VisuallyHidden>
)}
</Heading>
<Flex gap="50px">
<Chakra.header maxW="50vw" pb={6} data-testid="page-container-header">
<Heading as="h1">{title}</Heading>
{subtitle && <Text fontSize="md">{subtitle}</Text>}
</Box>
</Chakra.header>
<Box flexGrow={1} alignItems="flex-end" data-testid="page-container-cta">
{cta}
</Box>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/// <reference types="cypress" />

import { ReactFlowProvider } from 'reactflow'

import { MockStoreWrapper } from '@datahub/__test-utils__/MockStoreWrapper.tsx'
Expand Down Expand Up @@ -38,15 +36,10 @@ describe('CopyPasteStatus', () => {
cy.getByTestId('copy-paste-status').should('contain.text', '2')
})

it('should render properly the readonly status', () => {
cy.mountWithProviders(<CopyPasteStatus nbCopied={2} />, { wrapper: getWrapperWith(DesignerStatus.LOADED) })
cy.getByTestId('edit-status').should('be.visible')
cy.getByTestId('edit-status').find('svg').should('have.attr', 'data-readonly', 'true')
})

it('should render properly the editable status', () => {
it('should be accessible', () => {
cy.injectAxe()
cy.mountWithProviders(<CopyPasteStatus nbCopied={2} />, { wrapper: getWrapperWith(DesignerStatus.DRAFT) })
cy.getByTestId('edit-status').should('be.visible')
cy.getByTestId('edit-status').find('svg').should('have.attr', 'data-readonly', 'false')

cy.checkAccessibility()
})
})
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
import { FC, useMemo } from 'react'
import { Tag, TagLabel, TagLeftIcon } from '@chakra-ui/react'
import { FC } from 'react'
import { ButtonGroup, Tag, TagLabel, TagLeftIcon } from '@chakra-ui/react'
import { LuCopy, LuCopyCheck } from 'react-icons/lu'
import { PiPencilSimpleLineFill, PiPencilSimpleSlashFill } from 'react-icons/pi'

import Panel from '@/components/react-flow/Panel.tsx'
import useDataHubDraftStore from '@datahub/hooks/useDataHubDraftStore.ts'
import { DesignerStatus } from '@datahub/types.ts'
import { useTranslation } from 'react-i18next'

interface CopyPasteStatusProps {
nbCopied: number | undefined
}

const CopyPasteStatus: FC<CopyPasteStatusProps> = ({ nbCopied }) => {
const { status } = useDataHubDraftStore()
const isEditable = useMemo(() => status !== DesignerStatus.LOADED, [status])

const { t } = useTranslation('datahub')
return (
<Panel position="bottom-center">
<Tag size="lg" variant="subtle" userSelect="none" data-testid="edit-status">
<TagLeftIcon
boxSize="18px"
as={isEditable ? PiPencilSimpleLineFill : PiPencilSimpleSlashFill}
data-readonly={!isEditable}
/>
</Tag>
<Tag size="lg" variant="subtle" userSelect="none" data-testid="copy-paste-status">
<TagLeftIcon boxSize="12px" as={nbCopied ? LuCopyCheck : LuCopy} />
<TagLabel>{nbCopied}</TagLabel>
</Tag>
<ButtonGroup variant="outline" isAttached size="sm" aria-label={t('workspace.toolbars.clipboard.aria-label')}>
<Tag
size="lg"
variant="subtle"
userSelect="none"
data-testid="copy-paste-status"
tabIndex={0}
aria-label={t(`workspace.toolbars.clipboard.content`, { count: nbCopied })}
>
<TagLeftIcon boxSize="12px" as={nbCopied ? LuCopyCheck : LuCopy} />
<TagLabel>{nbCopied}</TagLabel>
</Tag>
</ButtonGroup>
</Panel>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,29 @@ const wrapper: FC<PropsWithChildren> = ({ children }) => <ReactFlowProvider>{chi

describe('DesignerToolbox', () => {
beforeEach(() => {
cy.viewport(850, 250)
cy.viewport(850, 600)
})

it('should renders properly', () => {
it('should render properly', () => {
cy.mountWithProviders(<DesignerToolbox />, { wrapper })
cy.getByTestId('toolbox-trigger').should('have.attr', 'aria-expanded', 'false')
cy.getByTestId('toolbox-container').should('not.be.visible')

cy.getByTestId('toolbox-trigger').click()
cy.getByTestId('toolbox-trigger').should('have.attr', 'aria-expanded', 'true')
cy.getByTestId('toolbox-container').should('be.visible')

cy.getByTestId('toolbox-container').find('header').should('have.text', 'Policy Toolbox')
})

it('should be accessible', () => {
cy.injectAxe()
cy.mountWithProviders(<DesignerToolbox />, { wrapper })
cy.getByTestId('toolbox-trigger').click()

// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(100) // Wait for dropdown (ugly)

cy.checkAccessibility()
cy.percySnapshot('Component: DesignerToolbox')
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,51 +1,61 @@
import { FC, useState } from 'react'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { motion } from 'framer-motion'
import { Box, HStack, Icon, IconButton, useDisclosure } from '@chakra-ui/react'
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverHeader,
PopoverBody,
PopoverArrow,
PopoverCloseButton,
Text,
VStack,
IconButton,
Icon,
HStack,
} from '@chakra-ui/react'
import { ChevronDownIcon, ChevronRightIcon } from '@chakra-ui/icons'
import { FaTools } from 'react-icons/fa'
import { LuPanelLeftOpen, LuPanelRightOpen } from 'react-icons/lu'

import Panel from '@/components/react-flow/Panel.tsx'
import { ToolboxNodes } from '@datahub/components/controls/ToolboxNodes.tsx'
import DraftStatus from '@datahub/components/helpers/DraftStatus.tsx'

const DesignerToolbox: FC = () => {
const { t } = useTranslation('datahub')
const { getButtonProps, getDisclosureProps, isOpen } = useDisclosure()
const [hidden, setHidden] = useState(!isOpen)

return (
<Panel position="top-left">
<HStack alignItems="center" userSelect="none">
<Box>
<IconButton
data-testid="toolbox-trigger"
aria-label={t('workspace.toolbox.trigger', { context: !isOpen ? 'open' : 'close' })}
icon={
<>
<Icon as={FaTools} />
<Icon as={isOpen ? LuPanelRightOpen : LuPanelLeftOpen} ml={2} boxSize="24px" />
</>
}
{...getButtonProps()}
px={2}
/>
</Box>
<motion.div
{...getDisclosureProps()}
data-testid="toolbox-container"
hidden={hidden}
initial={false}
onAnimationStart={() => setHidden(false)}
onAnimationComplete={() => setHidden(!isOpen)}
animate={{ width: isOpen ? '100%' : 0 }}
style={{
overflow: 'hidden',
whiteSpace: 'nowrap',
}}
>
<ToolboxNodes />
</motion.div>
<HStack role="group" aria-label={t('workspace.toolbars.draft.aria-label')}>
<Popover>
{({ isOpen }) => (
<>
<PopoverTrigger>
<IconButton
data-testid="toolbox-trigger"
aria-label={t('workspace.toolbox.trigger', { context: !isOpen ? 'open' : 'close' })}
aria-controls="toolbox-content"
icon={
<>
<Icon as={FaTools} />
<Icon as={isOpen ? ChevronDownIcon : ChevronRightIcon} ml={2} boxSize="24px" />
</>
}
px={2}
/>
</PopoverTrigger>
<PopoverContent width="unset" id="toolbox-content" data-testid="toolbox-container">
<PopoverArrow />
<PopoverCloseButton />
<PopoverHeader>{t('workspace.toolbox.panel.aria-label')}</PopoverHeader>
<PopoverBody as={VStack} alignItems="flex-start" maxWidth="12rem">
<Text fontSize="sm">{t('workspace.toolbox.panel.helper')}</Text>
<ToolboxNodes direction="vertical" />
</PopoverBody>
</PopoverContent>
</>
)}
</Popover>
<DraftStatus />
</HStack>
</Panel>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import ToolGroup from '@datahub/components/controls/ToolGroup.tsx'
import { Button } from '@chakra-ui/react'

describe('ToolGroup', () => {
beforeEach(() => {
cy.viewport(800, 600)
})

it('should render the toolbox', () => {
cy.mountWithProviders(
<ToolGroup title="The title" id="my-id">
<Button>Button 1</Button>
<Button>Button 2</Button>
</ToolGroup>
)

cy.getByTestId('toolbox-group-title').should('have.text', 'The title')
cy.getByTestId('toolbox-group-container').find('button').should('have.length', 2)
})

it('should be accessible', () => {
cy.injectAxe()
cy.mountWithProviders(
<ToolGroup title="The title" id="my-id">
<Button>Button 1</Button>
<Button>Button 2</Button>
</ToolGroup>
)

cy.checkAccessibility()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { FC, ReactNode } from 'react'
import { ButtonGroup, type ButtonGroupProps, Heading, HStack, VStack } from '@chakra-ui/react'

interface ToolProps extends ButtonGroupProps {
title: string
id: string
children: ReactNode
}

const ToolGroup: FC<ToolProps> = ({ title, id, children, ...props }) => {
return (
<ButtonGroup variant="outline" size="sm" aria-labelledby={id} {...props}>
<VStack alignItems="flex-start">
<Heading as="h2" size="sm" id={id} data-testid="toolbox-group-title">
{title}
</Heading>
<HStack data-testid="toolbox-group-container">{children}</HStack>
</VStack>
</ButtonGroup>
)
}

export default ToolGroup
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
/// <reference types="cypress" />

import { DataHubNodeType } from '@datahub/types.ts'
import Tool from './Tool.tsx'
import ToolItem from './ToolItem.tsx'

describe('Tool', () => {
beforeEach(() => {
cy.viewport(800, 600)
})

it('should render function', () => {
cy.mountWithProviders(<Tool nodeType={DataHubNodeType.FUNCTION} />)
cy.mountWithProviders(<ToolItem nodeType={DataHubNodeType.FUNCTION} />)

cy.get('button').should('have.attr', 'aria-label', 'JS Function')
cy.get('button').should('have.attr', 'draggable', 'true')
})

it('should render data policy', () => {
cy.mountWithProviders(<Tool nodeType={DataHubNodeType.DATA_POLICY} />)
cy.mountWithProviders(<ToolItem nodeType={DataHubNodeType.DATA_POLICY} />)

cy.get('button').should('have.attr', 'aria-label', 'Data Policy')
cy.get('button').should('have.attr', 'draggable', 'true')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface ToolProps extends ButtonProps {
nodeType: DataHubNodeType
}

const Tool: FC<ToolProps> = ({ nodeType, isDisabled }) => {
const ToolItem: FC<ToolProps> = ({ nodeType, isDisabled }) => {
const { t } = useTranslation('datahub')

const onButtonDragStart = useCallback(
Expand All @@ -38,4 +38,4 @@ const Tool: FC<ToolProps> = ({ nodeType, isDisabled }) => {
)
}

export default Tool
export default ToolItem
Loading
Loading