From f5e8228edda606f1c4cc4915f75b828e15d20215 Mon Sep 17 00:00:00 2001 From: Nicolas Van Labeke Date: Fri, 6 Dec 2024 08:48:42 +0000 Subject: [PATCH 01/17] refactor(27975): reorganise hooks for topic filters and tags --- .../src/modules/Device/components/DeviceTagList.tsx | 2 +- .../{Mappings => Device}/hooks/useTagManager.spec.tsx | 4 ++-- .../modules/{Mappings => Device}/hooks/useTagManager.ts | 9 ++++----- .../src/modules/TopicFilters/TopicFilterManager.tsx | 4 ++-- .../TopicFilters/hooks/useTopicFilterManager.spec.ts} | 4 ++-- .../TopicFilters/hooks/useTopicFilterManager.ts} | 4 ++-- 6 files changed, 13 insertions(+), 14 deletions(-) rename hivemq-edge/src/frontend/src/modules/{Mappings => Device}/hooks/useTagManager.spec.tsx (98%) rename hivemq-edge/src/frontend/src/modules/{Mappings => Device}/hooks/useTagManager.ts (96%) rename hivemq-edge/src/frontend/src/{api/hooks/useTopicFilters/useTopicFilterOperations.spec.ts => modules/TopicFilters/hooks/useTopicFilterManager.spec.ts} (84%) rename hivemq-edge/src/frontend/src/{api/hooks/useTopicFilters/useTopicFilterOperations.ts => modules/TopicFilters/hooks/useTopicFilterManager.ts} (97%) diff --git a/hivemq-edge/src/frontend/src/modules/Device/components/DeviceTagList.tsx b/hivemq-edge/src/frontend/src/modules/Device/components/DeviceTagList.tsx index a2923586d0..213cb4c298 100644 --- a/hivemq-edge/src/frontend/src/modules/Device/components/DeviceTagList.tsx +++ b/hivemq-edge/src/frontend/src/modules/Device/components/DeviceTagList.tsx @@ -10,7 +10,7 @@ import ErrorMessage from '@/components/ErrorMessage.tsx' import { PLCTag } from '@/components/MQTT/EntityTag.tsx' import ArrayItemDrawer from '@/components/rjsf/SplitArrayEditor/components/ArrayItemDrawer.tsx' import { formatTagDataPoint } from '@/modules/Device/utils/tags.utils.ts' -import { useTagManager } from '@/modules/Mappings/hooks/useTagManager.ts' +import { useTagManager } from '@/modules/Device/hooks/useTagManager.ts' interface DeviceTagListProps { adapter: Adapter diff --git a/hivemq-edge/src/frontend/src/modules/Mappings/hooks/useTagManager.spec.tsx b/hivemq-edge/src/frontend/src/modules/Device/hooks/useTagManager.spec.tsx similarity index 98% rename from hivemq-edge/src/frontend/src/modules/Mappings/hooks/useTagManager.spec.tsx rename to hivemq-edge/src/frontend/src/modules/Device/hooks/useTagManager.spec.tsx index 5ffda6a5dc..d207fb9a41 100644 --- a/hivemq-edge/src/frontend/src/modules/Mappings/hooks/useTagManager.spec.tsx +++ b/hivemq-edge/src/frontend/src/modules/Device/hooks/useTagManager.spec.tsx @@ -17,9 +17,9 @@ import { } from '@/api/hooks/useProtocolAdapters/__handlers__' import { Adapter, AdaptersList, type DomainTagList, ProtocolAdapter, ProtocolAdaptersList } from '@/api/__generated__' import { AuthProvider } from '@/modules/Auth/AuthProvider.tsx' -import { useTagManager } from '@/modules/Mappings/hooks/useTagManager.ts' +import { useTagManager } from '@/modules/Device/hooks/useTagManager.ts' import { MOCK_ADAPTER_ID } from '@/__test-utils__/mocks.ts' -import { handlers } from '@/api/hooks/useDomainModel/__handlers__/index.ts' +import { handlers } from '@/api/hooks/useDomainModel/__handlers__' const wrapper: React.JSXElementConstructor<{ children: React.ReactElement }> = ({ children }) => ( { const { $schema: sc, ...rest } = tagSchema?.configSchema as RJSFSchema // TODO[28249] Handle manually until backend fixed - const { properties, required } = rest - - const requiredProtocol = [...(required || []), 'protocolId'] + const { properties } = rest const safeSchema = { ...rest, @@ -44,9 +42,10 @@ export const useTagManager = (adapterId: string) => { default: protocol?.id, }, }, - required: requiredProtocol, } + console.log('XXXXXXXXXX safeSchema', safeSchema) + return { // $schema: 'https://json-schema.org/draft/2020-12/schema', definitions: { @@ -110,7 +109,7 @@ export const useTagManager = (adapterId: string) => { ) } - const context: ManagerContextType = { + const context: ManagerContextType = { schema: tagListSchema, uiSchema: { 'ui:submitButtonOptions': { diff --git a/hivemq-edge/src/frontend/src/modules/TopicFilters/TopicFilterManager.tsx b/hivemq-edge/src/frontend/src/modules/TopicFilters/TopicFilterManager.tsx index a75b688f78..53ca3e4df8 100644 --- a/hivemq-edge/src/frontend/src/modules/TopicFilters/TopicFilterManager.tsx +++ b/hivemq-edge/src/frontend/src/modules/TopicFilters/TopicFilterManager.tsx @@ -6,7 +6,7 @@ import { Button, ButtonGroup, Card, CardBody, Text, useDisclosure } from '@chakr import { LuPencil, LuPlus, LuTrash, LuView } from 'react-icons/lu' import { TopicFilter, TopicFilterList } from '@/api/__generated__' -import { useTopicFilterOperations } from '@/api/hooks/useTopicFilters/useTopicFilterOperations.ts' +import { useTopicFilterManager } from '@/modules/TopicFilters/hooks/useTopicFilterManager.ts' import LoaderSpinner from '@/components/Chakra/LoaderSpinner.tsx' import IconButton from '@/components/Chakra/IconButton.tsx' import ErrorMessage from '@/components/ErrorMessage.tsx' @@ -21,7 +21,7 @@ const TopicFilterManager: FC = () => { const { t } = useTranslation() const { isOpen, onOpen, onClose } = useDisclosure() const navigate = useNavigate() - const { data, context, isLoading, isError, onUpdateCollection } = useTopicFilterOperations() + const { data, context, isLoading, isError, onUpdateCollection } = useTopicFilterManager() const handleClose = () => { onClose() diff --git a/hivemq-edge/src/frontend/src/api/hooks/useTopicFilters/useTopicFilterOperations.spec.ts b/hivemq-edge/src/frontend/src/modules/TopicFilters/hooks/useTopicFilterManager.spec.ts similarity index 84% rename from hivemq-edge/src/frontend/src/api/hooks/useTopicFilters/useTopicFilterOperations.spec.ts rename to hivemq-edge/src/frontend/src/modules/TopicFilters/hooks/useTopicFilterManager.spec.ts index 7db89cf281..87d3e7b967 100644 --- a/hivemq-edge/src/frontend/src/api/hooks/useTopicFilters/useTopicFilterOperations.spec.ts +++ b/hivemq-edge/src/frontend/src/modules/TopicFilters/hooks/useTopicFilterManager.spec.ts @@ -3,7 +3,7 @@ import { renderHook, waitFor } from '@testing-library/react' import { server } from '@/__test-utils__/msw/mockServer.ts' import { SimpleWrapper as wrapper } from '@/__test-utils__/hooks/SimpleWrapper.tsx' -import { useTopicFilterOperations } from '@/api/hooks/useTopicFilters/useTopicFilterOperations.ts' +import { useTopicFilterManager } from '@/modules/TopicFilters/hooks/useTopicFilterManager.ts' import { handlers } from '@/api/hooks/useTopicFilters/__handlers__' import '@/config/i18n.config.ts' @@ -14,7 +14,7 @@ describe('useTopicFilterManager', () => { }) it('should return the manager', async () => { - const { result } = renderHook(() => useTopicFilterOperations(), { wrapper }) + const { result } = renderHook(() => useTopicFilterManager(), { wrapper }) expect(result.current.isLoading).toBeTruthy() await waitFor(() => { diff --git a/hivemq-edge/src/frontend/src/api/hooks/useTopicFilters/useTopicFilterOperations.ts b/hivemq-edge/src/frontend/src/modules/TopicFilters/hooks/useTopicFilterManager.ts similarity index 97% rename from hivemq-edge/src/frontend/src/api/hooks/useTopicFilters/useTopicFilterOperations.ts rename to hivemq-edge/src/frontend/src/modules/TopicFilters/hooks/useTopicFilterManager.ts index 59c4c61838..23b616540e 100644 --- a/hivemq-edge/src/frontend/src/api/hooks/useTopicFilters/useTopicFilterOperations.ts +++ b/hivemq-edge/src/frontend/src/modules/TopicFilters/hooks/useTopicFilterManager.ts @@ -16,7 +16,7 @@ interface TopicFilterSchemas { schema: RJSFSchema } -export const useTopicFilterOperations = () => { +export const useTopicFilterManager = () => { const { t } = useTranslation() const toast = useToast() @@ -85,7 +85,7 @@ export const useTopicFilterOperations = () => { toast.promise(updateCollectionMutator.mutateAsync({ requestBody: requestBody }), formatToast('updateCollection')) } - const context: ManagerContextType = { + const context: ManagerContextType = { schema: topicFilterSchemas.schema, uiSchema: { 'ui:submitButtonOptions': { From da6d7fd522ca8b933cd6c468b19641c2a8fd37d5 Mon Sep 17 00:00:00 2001 From: Nicolas Van Labeke Date: Fri, 6 Dec 2024 08:49:28 +0000 Subject: [PATCH 02/17] refactor(27975): rename unique adapter validation --- .../components/drawers/AdapterInstanceDrawer.tsx | 4 ++-- .../ProtocolAdapters/utils/validation-utils.spec.ts | 8 ++++---- .../modules/ProtocolAdapters/utils/validation-utils.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/hivemq-edge/src/frontend/src/modules/ProtocolAdapters/components/drawers/AdapterInstanceDrawer.tsx b/hivemq-edge/src/frontend/src/modules/ProtocolAdapters/components/drawers/AdapterInstanceDrawer.tsx index d288648eed..05cb4294cc 100644 --- a/hivemq-edge/src/frontend/src/modules/ProtocolAdapters/components/drawers/AdapterInstanceDrawer.tsx +++ b/hivemq-edge/src/frontend/src/modules/ProtocolAdapters/components/drawers/AdapterInstanceDrawer.tsx @@ -24,7 +24,7 @@ import { useGetAdapterTypes } from '@/api/hooks/useProtocolAdapters/useGetAdapte import { useListProtocolAdapters } from '@/api/hooks/useProtocolAdapters/useListProtocolAdapters.ts' import LoaderSpinner from '@/components/Chakra/LoaderSpinner.tsx' -import { customValidate } from '@/modules/ProtocolAdapters/utils/validation-utils.ts' +import { customUniqueAdapterValidate } from '@/modules/ProtocolAdapters/utils/validation-utils.ts' import { getRequiredUiSchema } from '@/modules/ProtocolAdapters/utils/uiSchema.utils.ts' import { AdapterContext } from '@/modules/ProtocolAdapters/types.ts' @@ -114,7 +114,7 @@ const AdapterInstanceDrawer: FC = ({ onSubmit={onValidate} // TODO[NVL] Types need fixing // @ts-ignore - customValidate={customValidate(schema, allAdapters, t)} + customValidate={customUniqueAdapterValidate(schema, allAdapters, t)} /> )} diff --git a/hivemq-edge/src/frontend/src/modules/ProtocolAdapters/utils/validation-utils.spec.ts b/hivemq-edge/src/frontend/src/modules/ProtocolAdapters/utils/validation-utils.spec.ts index 581b2ac00f..db1b4124bb 100644 --- a/hivemq-edge/src/frontend/src/modules/ProtocolAdapters/utils/validation-utils.spec.ts +++ b/hivemq-edge/src/frontend/src/modules/ProtocolAdapters/utils/validation-utils.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, vi } from 'vitest' import { FormValidation, RJSFSchema, UiSchema } from '@rjsf/utils' -import { customValidate } from './validation-utils.ts' +import { customUniqueAdapterValidate } from './validation-utils.ts' import { Adapter } from '@/api/__generated__' import { mockAdapter } from '@/api/hooks/useProtocolAdapters/__handlers__' import { MOCK_ADAPTER_ID } from '@/__test-utils__/mocks.ts' @@ -32,7 +32,7 @@ describe('customValidate()', () => { } // @ts-ignore - const customValidateFn = customValidate(mockJSONSchemaId, mockExistingAdapters, mockT) + const customValidateFn = customUniqueAdapterValidate(mockJSONSchemaId, mockExistingAdapters, mockT) expect(customValidateFn).toBeTypeOf('function') customValidateFn({ id: MOCK_ADAPTER_ID }, errors, mockUiSchemaId) expect(addError).toHaveBeenCalledWith('validation.jsonSchema.identifier.unique') @@ -56,7 +56,7 @@ describe('customValidate()', () => { } // @ts-ignore - const customValidateFn = customValidate(mockJSONSchemaId, mockExistingAdapters, mockT) + const customValidateFn = customUniqueAdapterValidate(mockJSONSchemaId, mockExistingAdapters, mockT) expect(customValidateFn).toBeTypeOf('function') customValidateFn({ id: MOCK_ADAPTER_ID }, errors, mockUiSchemaId) expect(addError).not.toHaveBeenCalledWith('validation.jsonSchema.identifier.unique') @@ -86,7 +86,7 @@ describe('customValidate()', () => { } // @ts-ignore - const customValidateFn = customValidate(mockJSONSchemaId, mockExistingAdapters, mockT) + const customValidateFn = customUniqueAdapterValidate(mockJSONSchemaId, mockExistingAdapters, mockT) expect(customValidateFn).toBeTypeOf('function') customValidateFn({ id: MOCK_ADAPTER_ID }, errors, mockUiSchemaId) expect(addError).not.toHaveBeenCalled() diff --git a/hivemq-edge/src/frontend/src/modules/ProtocolAdapters/utils/validation-utils.ts b/hivemq-edge/src/frontend/src/modules/ProtocolAdapters/utils/validation-utils.ts index 0001b95fe8..4280cb8cab 100644 --- a/hivemq-edge/src/frontend/src/modules/ProtocolAdapters/utils/validation-utils.ts +++ b/hivemq-edge/src/frontend/src/modules/ProtocolAdapters/utils/validation-utils.ts @@ -24,7 +24,7 @@ import { AdapterConfig } from '@/modules/ProtocolAdapters/types.ts' * - then (last stand) on conditions from the property name (i.e. formData) * */ -export const customValidate = +export const customUniqueAdapterValidate = (jsonSchema: RJSFSchema, existingAdapters: Adapter[] | undefined, t: TFunction) => (formData: Record, errors: FormValidation, uiSchema?: UiSchema) => { // Check for uniqueness of `id` ONLY if `format` = `identifier` and not `ui:disabled` From 2021a72e3a411daebb7ef5a9d0b5b553fe3539b5 Mon Sep 17 00:00:00 2001 From: Nicolas Van Labeke Date: Fri, 6 Dec 2024 08:50:38 +0000 Subject: [PATCH 03/17] refactor(27975): fix types --- .../components/ArrayItemDrawer.tsx | 7 ++++--- .../Device/components/DeviceTagForm.spec.cy.tsx | 14 ++++---------- .../Mappings/hooks/useNorthboundMappingManager.ts | 2 +- .../Mappings/hooks/useSouthboundMappingManager.ts | 2 +- .../src/frontend/src/modules/Mappings/types.ts | 8 ++++---- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/hivemq-edge/src/frontend/src/components/rjsf/SplitArrayEditor/components/ArrayItemDrawer.tsx b/hivemq-edge/src/frontend/src/components/rjsf/SplitArrayEditor/components/ArrayItemDrawer.tsx index cf7af5d4ec..5c2ba8a94f 100644 --- a/hivemq-edge/src/frontend/src/components/rjsf/SplitArrayEditor/components/ArrayItemDrawer.tsx +++ b/hivemq-edge/src/frontend/src/components/rjsf/SplitArrayEditor/components/ArrayItemDrawer.tsx @@ -18,8 +18,8 @@ import { DomainTagList } from '@/api/__generated__' import DeviceTagForm from '@/modules/Device/components/DeviceTagForm.tsx' import { ManagerContextType } from '@/modules/Mappings/types.ts' -interface DeviceTagDrawerProps { - context: ManagerContextType +interface DeviceTagDrawerProps { + context: ManagerContextType // TODO[NVL] Make the component generic and pass the type onSubmit?: (data: unknown) => void trigger: (disclosureProps: UseDisclosureProps) => JSX.Element @@ -27,7 +27,8 @@ interface DeviceTagDrawerProps { submitLabel?: string } -const ArrayItemDrawer: FC = ({ header, context, onSubmit, trigger, submitLabel }) => { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const ArrayItemDrawer: FC> = ({ header, context, onSubmit, trigger, submitLabel }) => { const { t } = useTranslation('components') const props = useDisclosure() const { isOpen, onClose } = props diff --git a/hivemq-edge/src/frontend/src/modules/Device/components/DeviceTagForm.spec.cy.tsx b/hivemq-edge/src/frontend/src/modules/Device/components/DeviceTagForm.spec.cy.tsx index fda0b61be8..157f2a9308 100644 --- a/hivemq-edge/src/frontend/src/modules/Device/components/DeviceTagForm.spec.cy.tsx +++ b/hivemq-edge/src/frontend/src/modules/Device/components/DeviceTagForm.spec.cy.tsx @@ -3,6 +3,7 @@ import DeviceTagForm from '@/modules/Device/components/DeviceTagForm.tsx' import { ManagerContextType } from '@/modules/Mappings/types.ts' import { createSchema } from '@/modules/Device/utils/tags.utils.ts' +import type { DomainTagList } from '@/api/__generated__' describe('DeviceTagForm', () => { beforeEach(() => { @@ -10,7 +11,7 @@ describe('DeviceTagForm', () => { }) it('should render the errors', () => { - const mockContext: ManagerContextType = { schema: undefined } + const mockContext: ManagerContextType = { schema: undefined } cy.mountWithProviders(, { routerProps: { initialEntries: [`/node/wrong-adapter`] }, }) @@ -23,17 +24,10 @@ describe('DeviceTagForm', () => { it('should render the form', () => { const onSubmit = cy.stub().as('onSubmit') - const mockContext: ManagerContextType = { + const mockContext: ManagerContextType = { schema: createSchema({ properties: { test: { type: 'string' } } }), formData: { - items: [ - { - tag: 'opcua-generator/power/off', - dataPoint: { - test: 'ns=3;i=1002', - }, - }, - ], + items: [], }, } diff --git a/hivemq-edge/src/frontend/src/modules/Mappings/hooks/useNorthboundMappingManager.ts b/hivemq-edge/src/frontend/src/modules/Mappings/hooks/useNorthboundMappingManager.ts index c0479d75d7..b196455ad1 100644 --- a/hivemq-edge/src/frontend/src/modules/Mappings/hooks/useNorthboundMappingManager.ts +++ b/hivemq-edge/src/frontend/src/modules/Mappings/hooks/useNorthboundMappingManager.ts @@ -42,7 +42,7 @@ export const useNorthboundMappingManager = (adapterId: string): MappingManagerTy return promise } - const context: ManagerContextType = { + const context: ManagerContextType = { schema: northboundMappingListSchema, uiSchema: northboundMappingListUISchema, formData: data, diff --git a/hivemq-edge/src/frontend/src/modules/Mappings/hooks/useSouthboundMappingManager.ts b/hivemq-edge/src/frontend/src/modules/Mappings/hooks/useSouthboundMappingManager.ts index cdb60f49bc..8226c1ac36 100644 --- a/hivemq-edge/src/frontend/src/modules/Mappings/hooks/useSouthboundMappingManager.ts +++ b/hivemq-edge/src/frontend/src/modules/Mappings/hooks/useSouthboundMappingManager.ts @@ -43,7 +43,7 @@ export const useSouthboundMappingManager = (adapterId: string): MappingManagerTy return promise } - const context: ManagerContextType = { + const context: ManagerContextType = { schema: southboundMappingListSchema, uiSchema: southboundMappingListUISchema, formData: data, diff --git a/hivemq-edge/src/frontend/src/modules/Mappings/types.ts b/hivemq-edge/src/frontend/src/modules/Mappings/types.ts index 59a339fcac..8132fdc55d 100644 --- a/hivemq-edge/src/frontend/src/modules/Mappings/types.ts +++ b/hivemq-edge/src/frontend/src/modules/Mappings/types.ts @@ -2,9 +2,9 @@ import { GenericObjectType, type RJSFSchema, type UiSchema } from '@rjsf/utils' import { AlertProps } from '@chakra-ui/react' import { ApiError } from '@/api/__generated__' -export interface ManagerContextType { +export interface ManagerContextType { schema?: RJSFSchema - formData?: GenericObjectType + formData?: T uiSchema?: UiSchema } @@ -14,8 +14,8 @@ export enum MappingType { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export interface MappingManagerType { - context: ManagerContextType +export interface MappingManagerType { + context: ManagerContextType data: T | undefined onUpdateCollection: (tags: T) => Promise | undefined onClose: () => void From ede2b87f350195dc41916b14f347acf885b99b5a Mon Sep 17 00:00:00 2001 From: Nicolas Van Labeke Date: Fri, 6 Dec 2024 08:51:59 +0000 Subject: [PATCH 04/17] feat(27975): add selector-based validation for tag and topic --- .../frontend/src/api/schemas/northbound.ui-schema.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/hivemq-edge/src/frontend/src/api/schemas/northbound.ui-schema.ts b/hivemq-edge/src/frontend/src/api/schemas/northbound.ui-schema.ts index 25849e6552..b842ca8e9d 100644 --- a/hivemq-edge/src/frontend/src/api/schemas/northbound.ui-schema.ts +++ b/hivemq-edge/src/frontend/src/api/schemas/northbound.ui-schema.ts @@ -1,4 +1,6 @@ import { UiSchema } from '@rjsf/utils' +import { registerEntitySelectWidget } from '@/components/rjsf/Widgets/EntitySelectWidget.tsx' +import { CustomFormat } from '@/api/types/json-schema.ts' /* istanbul ignore next -- @preserve */ export const northboundMappingListUISchema: UiSchema = { @@ -14,6 +16,15 @@ export const northboundMappingListUISchema: UiSchema = { 'ui:collapsable': { titleKey: 'tagName', }, + tagName: { + 'ui:widget': registerEntitySelectWidget(CustomFormat.MQTT_TAG), + }, + topic: { + 'ui:widget': registerEntitySelectWidget(CustomFormat.MQTT_TOPIC), + 'ui:options': { + create: true, + }, + }, 'ui:addButton': 'Add a mapping', userProperties: { items: { From 8d3fa49ef0250d7ca0efe71f64e7fc7a33154610 Mon Sep 17 00:00:00 2001 From: Nicolas Van Labeke Date: Fri, 6 Dec 2024 08:52:15 +0000 Subject: [PATCH 05/17] feat(27975): add option for creating new elements in a selector --- .../src/components/rjsf/Widgets/EntitySelectWidget.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hivemq-edge/src/frontend/src/components/rjsf/Widgets/EntitySelectWidget.tsx b/hivemq-edge/src/frontend/src/components/rjsf/Widgets/EntitySelectWidget.tsx index afad6fbb4c..4b54921215 100644 --- a/hivemq-edge/src/frontend/src/components/rjsf/Widgets/EntitySelectWidget.tsx +++ b/hivemq-edge/src/frontend/src/components/rjsf/Widgets/EntitySelectWidget.tsx @@ -11,7 +11,7 @@ export const registerEntitySelectWidget = // eslint-disable-next-line react/display-name (type: CustomFormat) => (props: WidgetProps>) => { const { chakraProps, label, id, disabled, readonly, onChange, required, rawErrors, value } = props - const { multiple } = getUiOptions(props.uiSchema) + const { multiple, create } = getUiOptions(props.uiSchema) const { adapterId } = props.formContext const Select = useMemo(() => { @@ -33,7 +33,7 @@ export const registerEntitySelectWidget =