Skip to content

Commit

Permalink
Merge pull request #539
Browse files Browse the repository at this point in the history
feat(25263): Relocate subscriptions to the workspace

* refactor(25263): refactor the inward/outward icons

* feat(25263): add right side panel for devices

* feat(25263): add types for the subscription manager

* feat(25263): add navigate for the adapter toolbar

* feat(25263): add config panel for subscriptions

* feat(25263): add RJSF wrapper

* feat(25263): add management of the subscription properties from the J…

* feat(25263): add expand/collapse button on the panel

* feat(25263): add form

* feat(25263): add mock outward subscription JSONSchema

* refactor(25263): add support for `create` and `multiple` options in t…

* feat(25263): add right side panel for devices

* chore(25263): a bit of cleaning

* feat(25263): add type for the outward subscriptions

* fix(25263): add submit handling

* fix(25263): fix bug with creatable options

* fix(25263): fix type

* fix(25263): fix layout

* feat(25263): add a custom field for deprecated/hidden feature in a RJ…

* fix(25263): fix type

* test(25263): add tests

* test(25263): add tests

* fix(25263): fix type

* chore(25263): rename component

* test(25263): add tests

* refactor(25263): refactor the subscription manager

* test(25263): add tests

* refactor(25263): refactor the expand button

* test(25263): add tests

* refactor(25263): refactor error handling

* fix(25263): fix props

* fix(25263): fix error message

* test(25263): add tests

* chore(25263): a bit of cleaning

* fix(25263): fix condition

* refactor(25263): fix extraction

* test(25263): remove deprecated only

* refactor(25263): improve literals

* chore(25263): a bit of cleaning
  • Loading branch information
vanch3d committed Sep 5, 2024
1 parent 0449537 commit 7ef1fa9
Show file tree
Hide file tree
Showing 33 changed files with 1,039 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { NodeProps, Position } from 'reactflow'
import { Listener, ProtocolAdapter } from '@/api/__generated__'
import { Listener } from '@/api/__generated__'
import { BrokerClientConfiguration } from '@/api/types/api-broker-client.ts'
import { mockAdapter, mockProtocolAdapter } from '@/api/hooks/useProtocolAdapters/__handlers__'
import { mockBridge } from '@/api/hooks/useGetBridges/__handlers__'
import { mockClientSubscription } from '@/api/hooks/useClientSubscriptions/__handlers__'
import { mockMqttListener } from '@/api/hooks/useGateway/__handlers__'
import { Group, NodeTypes } from '@/modules/Workspace/types.ts'
import { DeviceMetadata, Group, NodeTypes } from '@/modules/Workspace/types.ts'

export const MOCK_DEFAULT_NODE = {
selected: false,
Expand Down Expand Up @@ -56,7 +56,7 @@ export const MOCK_NODE_GROUP: NodeProps<Group> = {
...MOCK_DEFAULT_NODE,
}

export const MOCK_NODE_DEVICE: NodeProps<ProtocolAdapter> = {
export const MOCK_NODE_DEVICE: NodeProps<DeviceMetadata> = {
id: 'idDevice',
type: NodeTypes.DEVICE_NODE,
data: mockProtocolAdapter,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import DrawerExpandButton from '@/components/Chakra/DrawerExpandButton.tsx'

describe('DrawerExpandButton', () => {
beforeEach(() => {
cy.viewport(400, 150)
})

it('should render expanded properly', () => {
cy.mountWithProviders(<DrawerExpandButton isExpanded={true} toggle={cy.stub().as('toggle')} />)
cy.get('button').should('have.attr', 'data-expanded', 'true')

cy.get('@toggle').should('not.have.been.called')
cy.get('button').click()
cy.get('@toggle').should('have.been.called')
})

it('should render shrunk properly', () => {
cy.mountWithProviders(<DrawerExpandButton isExpanded={false} toggle={cy.stub().as('toggle')} />)
cy.get('button').should('have.attr', 'data-expanded', 'false')

cy.get('@toggle').should('not.have.been.called')
cy.get('button').click()
cy.get('@toggle').should('have.been.called')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { FC } from 'react'
import { IconButton, IconButtonProps } from '@chakra-ui/react'
import { LuExpand, LuShrink } from 'react-icons/lu'
import { useTranslation } from 'react-i18next'

interface DrawerExpandButtonProps extends Omit<IconButtonProps, 'aria-label'> {
isExpanded: boolean
toggle: () => void
}

const DrawerExpandButton: FC<DrawerExpandButtonProps> = ({ isExpanded, toggle, ...props }) => {
const { t } = useTranslation('components')
return (
<IconButton
{...props}
variant="ghost"
colorScheme="gray"
onClick={toggle}
data-expanded={isExpanded}
icon={isExpanded ? <LuShrink /> : <LuExpand />}
style={{
position: 'absolute',
top: 'var(--chakra-space-2)',
right: 0,
width: '32px',
height: '32px',
transform: 'translate(-48px, 0)',
minWidth: 'inherit',
}}
aria-label={isExpanded ? t('DrawerExpandButton.aria-label.shrink') : t('DrawerExpandButton.aria-label.expand')}
/>
)
}

export default DrawerExpandButton
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
CreatableProps,
SelectInstance,
chakraComponents,
Select,
} from 'chakra-react-select'
import { useTranslation } from 'react-i18next'

Expand Down Expand Up @@ -56,13 +57,15 @@ interface TopicCreatableSelectProps<IsMulti extends boolean>
extends Partial<Omit<TopicCreatableSelect<IsMulti>, 'options'>> {
id: string
options: string[]
isCreatable?: boolean
}

const AbstractTopicCreatableSelect = <T extends boolean>({
id,
options,
isLoading,
isMulti,
isCreatable = true,
...rest
}: TopicCreatableSelectProps<T>) => {
const topicOptions = Array.from(new Set([...options]))
Expand All @@ -74,8 +77,10 @@ const AbstractTopicCreatableSelect = <T extends boolean>({
trim: false,
}

const SelectComponent = isCreatable ? CreatableSelect : Select

return (
<CreatableSelect<TopicOption, T, GroupBase<TopicOption>>
<SelectComponent<TopicOption, T, GroupBase<TopicOption>>
aria-label={t('topicCreate.label')}
placeholder={t('topicCreate.placeholder')}
noOptionsMessage={() => t('topicCreate.options.noOptionsMessage')}
Expand Down Expand Up @@ -115,6 +120,7 @@ interface MultiTopicsCreatableSelectProps
extends Omit<TopicCreatableSelectProps<true>, 'value' | 'onChange' | 'options'> {
value: string[]
onChange: (value: string[] | undefined) => void
isCreatable?: boolean
}

export const MultiTopicsCreatableSelect = ({ value, onChange, ...props }: MultiTopicsCreatableSelectProps) => {
Expand All @@ -125,7 +131,7 @@ export const MultiTopicsCreatableSelect = ({ value, onChange, ...props }: MultiT
options={data}
isLoading={!isSuccess}
isMulti={true}
value={value.map<TopicOption>((e) => ({ label: e, value: e, iconColor: 'brand.200' }))}
value={value?.map<TopicOption>((e) => ({ label: e, value: e, iconColor: 'brand.200' }))}
onChange={(m) => onChange(m.map<string>((e) => e.value))}
/>
)
Expand Down
29 changes: 29 additions & 0 deletions hivemq-edge/src/frontend/src/components/react-icons/hm/HmInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { GenIcon, IconBaseProps } from 'react-icons'

export const HmInput = (props: IconBaseProps) =>
GenIcon({
tag: 'svg',
attr: { viewBox: '0 0 22 18' },
child: [
{
tag: 'g',
attr: { transform: 'matrix(1, 0, 0, 1, -290.7229919433594, -240.23300170898438)' },
child: [
{
tag: 'path',
attr: {
d: 'M 310.729 240.233 L 292.729 240.233 C 291.629 240.233 290.729 241.133 290.729 242.233 L 290.723 246.308 L 292.729 246.223 L 292.729 242.213 L 310.729 242.213 L 310.729 256.243 L 292.729 256.243 L 292.729 252.223 L 290.729 252.223 L 290.729 256.233 C 290.729 257.333 291.629 258.213 292.729 258.213 L 310.729 258.213 C 311.829 258.213 312.729 257.333 312.729 256.233 L 312.729 242.233 C 312.729 241.128 311.834 240.233 310.729 240.233 Z',
},
child: [],
},
{
tag: 'path',
attr: {
d: 'M 299.328 254.314 L 304.328 249.314 L 299.328 244.314 L 297.918 245.724 L 300.498 248.314 L 291.328 248.314 L 291.328 250.314 L 300.498 250.314 L 297.918 252.904 L 299.328 254.314 Z',
},
child: [],
},
],
},
],
})(props)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { GenIcon, IconBaseProps } from 'react-icons'

export const HmOutput = (props: IconBaseProps) =>
GenIcon({
tag: 'svg',
attr: { viewBox: '0 0 22 18' },
child: [
{
tag: 'g',
attr: { transform: 'matrix(1, 0, 0, 1, -290.7560119628906, -260.54998779296875)' },
child: [
{
tag: 'path',
attr: {
d: 'M 310.144 276.55 L 293.179 276.55 L 293.179 262.55 L 310.144 262.55 L 310.144 264.55 L 312.567 264.55 L 312.567 262.55 C 312.567 261.445 311.483 260.55 310.144 260.55 L 293.179 260.55 C 291.846 260.55 290.756 261.45 290.756 262.55 L 290.756 276.55 C 290.756 277.65 291.846 278.55 293.179 278.55 L 310.144 278.55 C 311.483 278.55 312.567 277.654 312.567 276.55 L 312.567 274.55 L 310.144 274.55 L 310.144 276.55 Z',
},
child: [],
},
{
tag: 'path',
attr: {
d: 'M 307.452 274.633 L 312.452 269.633 L 307.452 264.633 L 306.042 266.043 L 308.622 268.633 L 299.452 268.633 L 299.452 270.633 L 308.622 270.633 L 306.042 273.223 L 307.452 274.633 Z',
},
child: [],
},
],
},
],
})(props)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { HmInput } from '@/components/react-icons/hm/HmInput.tsx'
import { HmOutput } from '@/components/react-icons/hm/HmOutput.tsx'

export { HmInput, HmOutput }
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { FC } from 'react'
import { FieldProps, getUiOptions, labelValue } from '@rjsf/utils'
import { RJSFSchema } from '@rjsf/utils/src/types.ts'
import { getChakra } from '@rjsf/chakra-ui/lib/utils'
import { Alert, AlertDescription, AlertIcon, AlertStatus, FormControl, FormLabel } from '@chakra-ui/react'
import { AdapterContext } from '@/modules/ProtocolAdapters/types.ts'

export const InternalNotice: FC<FieldProps<unknown, RJSFSchema, AdapterContext>> = (props) => {
const chakraProps = getChakra({ uiSchema: props.uiSchema })
const { message, status } = getUiOptions(props.uiSchema)

return (
<FormControl {...chakraProps}>
{labelValue(
<FormLabel htmlFor={props.id} id={`${props.id}-label`}>
{props.label}
</FormLabel>,
props.hideLabel || !props.label
)}

<Alert status={(status as AlertStatus) || 'info'}>
<AlertIcon />
{message && <AlertDescription maxWidth="sm">{message as string}</AlertDescription>}
</Alert>
</FormControl>
)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { FC } from 'react'
import { BaseInputTemplateProps } from '@rjsf/utils'
import { BaseInputTemplateProps, getUiOptions } from '@rjsf/utils'
import { FormControl, FormLabel } from '@chakra-ui/react'

import { SingleTopicCreatableSelect } from '@/components/MQTT/TopicCreatableSelect.tsx'
import { MultiTopicsCreatableSelect, SingleTopicCreatableSelect } from '@/components/MQTT/TopicCreatableSelect.tsx'
import { useGetEdgeTopics } from '@/hooks/useGetEdgeTopics/useGetEdgeTopics.ts'

import config from '@/config'
Expand All @@ -13,6 +13,7 @@ export const TopicInputTemplate: FC<BaseInputTemplateProps> = (props) => {
branchOnly: config.features.TOPIC_EDITOR_SHOW_BRANCHES,
publishOnly: true,
})
const { create, multiple } = getUiOptions(props.uiSchema)

return (
<FormControl
Expand All @@ -23,7 +24,25 @@ export const TopicInputTemplate: FC<BaseInputTemplateProps> = (props) => {
isInvalid={rawErrors && rawErrors.length > 0}
>
<FormLabel htmlFor={id}>{label}</FormLabel>
<SingleTopicCreatableSelect isLoading={isLoading} options={data} id={id} value={value} onChange={onChange} />
{!multiple && (
<SingleTopicCreatableSelect
isLoading={isLoading}
options={data}
id={id}
value={value}
onChange={onChange}
isCreatable={create === true || create === undefined}
/>
)}
{multiple && (
<MultiTopicsCreatableSelect
isLoading={isLoading}
id={id}
value={value}
onChange={onChange}
isCreatable={create === true || create === undefined}
/>
)}
</FormControl>
)
}
6 changes: 6 additions & 0 deletions hivemq-edge/src/frontend/src/locales/en/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@
"optIn": "Accept all"
}
},
"DrawerExpandButton": {
"aria-label": {
"expand": "Expand",
"shrink": "Shrink"
}
},
"rjsf": {
"CompactArrayField": {
"action": {
Expand Down
6 changes: 6 additions & 0 deletions hivemq-edge/src/frontend/src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,7 @@
"header_BRIDGE_NODE": "Bridge Overview",
"header_CLUSTER_NODE": "Group Overview",
"header_EDGE_NODE": "Edge Overview",
"header_DEVICE_NODE": "Device Overview",
"modify_ADAPTER_NODE": "Modify the adapter",
"modify_BRIDGE_NODE": "Modify the bridge",
"eventLog": {
Expand Down Expand Up @@ -912,5 +913,10 @@
"title": "A new version of $t(branding.appName) is available",
"description": "Visit the GitHub repository to learn more about the latest version of $t(branding.appName)"
}
},
"warnings": {
"deprecated": {
"subscriptions": "The subscriptions are now available through the workspace"
}
}
}
9 changes: 9 additions & 0 deletions hivemq-edge/src/frontend/src/modules/App/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const UnifiedNamespacePage = lazy(() => import('@/modules/UnifiedNamespace/Unifi
const EdgeFlowPage = lazy(() => import('@/modules/Workspace/EdgeFlowPage.tsx'))
const NodePanelController = lazy(() => import('@/modules/Workspace/components/controls/NodePanelController.tsx'))
const EvenLogPage = lazy(() => import('@/modules/EventLog/EvenLogPage.tsx'))
const AdapterSubscriptionManager = lazy(() => import('@/modules/Subscriptions/AdapterSubscriptionManager.tsx'))

import { dataHubRoutes } from '@/extensions/datahub/routes.tsx'

Expand Down Expand Up @@ -73,6 +74,14 @@ export const routes = createBrowserRouter(
path: ':nodeType/:device?/:adapter?/:nodeId',
element: <NodePanelController />,
},
{
path: ':nodeType/:device?/:adapter?/:nodeId/inward',
element: <AdapterSubscriptionManager type="inward" />,
},
{
path: ':nodeType/:device?/:adapter?/:nodeId/outward',
element: <AdapterSubscriptionManager type="outward" />,
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe('AdapterActionMenu', () => {
cy.getByTestId('adapter-action-export').should('not.be.visible')
})

it.only('should be accessible', () => {
it('should be accessible', () => {
cy.injectAxe()
cy.mountWithProviders(<AdapterActionMenu adapter={mockAdapter} />)
cy.getByAriaLabel('Actions').click()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
getRequiredUiSchema,
} from '@/modules/ProtocolAdapters/utils/uiSchema.utils.ts'
import { AdapterContext } from '@/modules/ProtocolAdapters/types.ts'
import { getMainRootFromPath, getTopicPaths } from '@/modules/Workspace/utils/topics-utils.ts'

interface AdapterInstanceDrawerProps {
adapterType?: string
Expand Down Expand Up @@ -69,12 +70,19 @@ const AdapterInstanceDrawer: FC<AdapterInstanceDrawerProps> = ({
const { schema, uiSchema, name, logo, isDiscoverable } = useMemo(() => {
const adapter: ProtocolAdapter | undefined = data?.items?.find((e) => e.id === adapterType)
const { configSchema, uiSchema, capabilities } = adapter || {}

// TODO[NVL] This is still a hack; backend needs to provide identification of subscription properties
const paths = getTopicPaths(configSchema || {})
const subIndex = getMainRootFromPath(paths)
const hideSubscriptionsKey =
import.meta.env.VITE_FLAG_ADAPTER_SCHEMA_HIDE_SUBSCRIPTION === 'true' ? subIndex : undefined

return {
isDiscoverable: Boolean(capabilities?.includes('DISCOVER')),
schema: configSchema,
name: adapter?.name,
logo: adapter?.logoUrl,
uiSchema: getRequiredUiSchema(uiSchema, isNewAdapter),
uiSchema: getRequiredUiSchema(uiSchema, isNewAdapter, hideSubscriptionsKey),
}
}, [data?.items, isNewAdapter, adapterType])

Expand Down
Loading

0 comments on commit 7ef1fa9

Please sign in to comment.