Skip to content

Commit 6564266

Browse files
authored
Merge pull request #999
* fix(24801): fix warnings with unsupported formats from AJV * fix(24801): fix warnings with unsupported formats from AJV * fix(24801): redesign the schema for the maxPublish option * fix(24801): refactor the min and max publishes * fix(24801): add types for the Publish Quota model * fix(24801): add custom validation for publish arguments * test(24801): add tests * test(24801): fix tests * fix(24801): fix translations * fix(24801): fix constant
1 parent e39ab02 commit 6564266

File tree

10 files changed

+145
-45
lines changed

10 files changed

+145
-45
lines changed

hivemq-edge-frontend/src/components/rjsf/Form/validation.utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ export const customFormatsValidator = customizeValidator(
129129
customFormats: {
130130
// TODO[26559] This is a hack to remove the error; fix at source
131131
['boolean']: () => true,
132+
// TODO[33325] This is a hack to remove the error; fix at source
133+
interpolation: () => true,
132134
'mqtt-topic': (topic) => validationTopic(topic) === undefined,
133135
'mqtt-tag': (tag) => validationTag(tag) === undefined,
134136
'mqtt-topic-filter': (topicFilter) => validationTopicFilter(topicFilter) === undefined,

hivemq-edge-frontend/src/extensions/datahub/api/__generated__/schemas/BehaviorPolicyData.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hivemq-edge-frontend/src/extensions/datahub/components/forms/CodeEditor.spec.cy.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe('CodeEditor', () => {
3737
cy.mountWithProviders(<JSONSchemaEditor {...MOCK_WIDGET_PROPS} value={MOCK_JSONSCHEMA_SCHEMA} />)
3838
})
3939

40-
it.only('should render the fallback editor', () => {
40+
it('should render the fallback editor', () => {
4141
Cypress.on('uncaught:exception', () => {
4242
// returning false here prevents Cypress from failing the test
4343
return false

hivemq-edge-frontend/src/extensions/datahub/components/forms/ReactFlowSchemaForm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import type {
1212
} from '@rjsf/utils'
1313
import { getTemplate, getUiOptions, TranslatableString } from '@rjsf/utils'
1414
import type { GenericObjectType } from '@rjsf/utils'
15-
import validator from '@rjsf/validator-ajv8'
1615
import { Alert, AlertTitle, Box, Divider, FormControl, Heading, List, ListIcon, ListItem, Text } from '@chakra-ui/react'
1716
import { WarningIcon } from '@chakra-ui/icons'
1817

1918
import { ArrayFieldItemTemplate } from '@/components/rjsf/ArrayFieldItemTemplate.tsx'
2019
import { ArrayFieldTemplate } from '@/components/rjsf/ArrayFieldTemplate.tsx'
20+
import { customFormatsValidator } from '@/components/rjsf/Form/validation.utils.ts'
2121

2222
// overriding the heading definition
2323
function TitleFieldTemplate<T = unknown, S extends StrictRJSFSchema = RJSFSchema>({
@@ -171,7 +171,7 @@ export const ReactFlowSchemaForm: FC<ReactFlowSchemaFormProps> = (props) => {
171171
ErrorListTemplate,
172172
TitleFieldTemplate,
173173
}}
174-
validator={validator}
174+
validator={customFormatsValidator}
175175
// TODO[NVL] Not sure we want to hide the validation when readonly
176176
noValidate={!isNodeEditable}
177177
uiSchema={{

hivemq-edge-frontend/src/extensions/datahub/designer/behavior_policy/BehaviorPolicyPanel.spec.cy.tsx

Lines changed: 94 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,23 @@ const wrapper: React.JSXElementConstructor<{ children: React.ReactNode }> = ({ c
2525
>
2626
{children}
2727
<Button variant="primary" type="submit" form="datahub-node-form">
28-
SUBMIT{' '}
28+
SUBMIT
2929
</Button>
3030
</MockStoreWrapper>
3131
)
3232

33+
const cy_selectBehaviourModel = (index: number) => {
34+
cy.get('label#root_model-label + div').click()
35+
36+
cy.get('label#root_model-label + div').find("[role='listbox']").find("[role='option']").eq(index).click()
37+
}
38+
3339
describe('BehaviorPolicyPanel', () => {
3440
beforeEach(() => {
3541
cy.viewport(800, 800)
3642
cy.intercept('/api/v1/data-hub/behavior-validation/policies', {
3743
items: [mockBehaviorPolicy],
38-
})
44+
}).as('getNodePayload')
3945
})
4046

4147
it('should render the fields for the panel', () => {
@@ -61,26 +67,6 @@ describe('BehaviorPolicyPanel', () => {
6167
cy.get('label#root_model-label + div').find("[role='listbox']").find("[role='option']").eq(0).click()
6268

6369
cy.get('h2').eq(0).should('contain.text', 'Publish')
64-
cy.get('label#field-\\:r9\\:-label').should('contain.text', 'minPublishes')
65-
cy.get('label#field-\\:r9\\:-label + div > input').should('have.value', '0')
66-
cy.get('label#field-\\:rb\\:-label').should('contain.text', 'maxPublishes')
67-
cy.get('label#field-\\:rb\\:-label + div > input').should('have.value', '-1')
68-
69-
cy.get("button[type='submit']").click()
70-
cy.get('@onSubmit')
71-
.should('have.been.calledOnceWith', Cypress.sinon.match.object)
72-
.its('firstCall.args.0')
73-
.should('deep.include', {
74-
status: 'submitted',
75-
formData: {
76-
id: 'a123',
77-
model: 'Publish.quota',
78-
arguments: {
79-
minPublishes: 0,
80-
maxPublishes: -1,
81-
},
82-
},
83-
})
8470
})
8571

8672
it('should be accessible', () => {
@@ -90,4 +76,90 @@ describe('BehaviorPolicyPanel', () => {
9076
cy.checkAccessibility()
9177
cy.percySnapshot('Component: BehaviorPolicyPanel')
9278
})
79+
80+
context('Behaviour models', () => {
81+
it('should render the Quota options', () => {
82+
const onSubmit = cy.stub().as('onSubmit')
83+
84+
cy.mountWithProviders(<BehaviorPolicyPanel selectedNode="3" onFormSubmit={onSubmit} />, { wrapper })
85+
cy.get('#root_id').type('a123')
86+
87+
cy_selectBehaviourModel(0)
88+
89+
cy.get('h2').eq(0).should('contain.text', 'Publish.quota options')
90+
cy.get('label[for="root_arguments_minPublishes"]').should('contain.text', 'minPublishes')
91+
cy.get('label[for="root_arguments_minPublishes"] + div > input').should('have.value', '0')
92+
cy.get('label[for="root_arguments_maxPublishes"]').should('contain.text', 'maxPublishes')
93+
cy.get('label[for="root_arguments_maxPublishes"] + div > input').should('have.value', '-1')
94+
95+
cy.get("button[type='submit']").click()
96+
cy.get('@onSubmit')
97+
.should('have.been.calledOnceWith', Cypress.sinon.match.object)
98+
.its('firstCall.args.0')
99+
.should('deep.include', {
100+
status: 'submitted',
101+
formData: {
102+
id: 'a123',
103+
model: 'Publish.quota',
104+
arguments: {
105+
minPublishes: 0,
106+
maxPublishes: -1,
107+
},
108+
},
109+
})
110+
})
111+
112+
it('should render id errors', () => {
113+
cy.mountWithProviders(<BehaviorPolicyPanel selectedNode="3" onFormSubmit={cy.stub} />, { wrapper })
114+
cy.get("button[type='submit']").click()
115+
116+
cy.get('[role="group"]:has(> label#root_id-label) + ul > li').as('idErrors')
117+
cy.get('@idErrors').should('have.length', 2)
118+
cy.get('@idErrors').eq(0).should('contain.text', "must have required property 'id'")
119+
cy.get('@idErrors').eq(1).should('contain.text', 'Cannot load the existing policies for checking ids')
120+
121+
cy.get('[role="alert"]')
122+
.should('contain.text', "must have required property 'id'")
123+
.should('contain.text', 'Cannot load the existing policies for checking ids')
124+
})
125+
126+
it('should render arguments errors', () => {
127+
cy.mountWithProviders(<BehaviorPolicyPanel selectedNode="3" onFormSubmit={cy.stub} />, { wrapper })
128+
129+
cy.get('#root_id').type('a123')
130+
cy_selectBehaviourModel(0)
131+
132+
cy.get('label[for="root_arguments_minPublishes"] + div > input').clear()
133+
cy.get('label[for="root_arguments_minPublishes"] + div > input').type('-50')
134+
cy.get('label[for="root_arguments_maxPublishes"] + div > input').clear()
135+
cy.get('label[for="root_arguments_maxPublishes"] + div > input').type('-50')
136+
cy.get("button[type='submit']").click()
137+
138+
cy.get('[role="group"]:has(> label[for="root_arguments_minPublishes"]) + ul > li').as('minPublishesErrors')
139+
cy.get('@minPublishesErrors').should('have.length', 1)
140+
cy.get('@minPublishesErrors').eq(0).should('contain.text', 'must be >= 0')
141+
142+
cy.get('[role="group"]:has(> label[for="root_arguments_maxPublishes"]) + ul > li').as('maxPublishesErrors')
143+
cy.get('@maxPublishesErrors').should('have.length', 1)
144+
cy.get('@maxPublishesErrors').eq(0).should('contain.text', 'must be >= -1')
145+
146+
cy.get('label[for="root_arguments_minPublishes"] + div > input').clear()
147+
cy.get('label[for="root_arguments_minPublishes"] + div > input').type('200')
148+
cy.get('label[for="root_arguments_maxPublishes"] + div > input').clear()
149+
cy.get('label[for="root_arguments_maxPublishes"] + div > input').type('100')
150+
cy.get("button[type='submit']").click()
151+
152+
cy.get('[role="group"]:has(> label[for="root_arguments_minPublishes"]) + ul > li').as('minPublishesErrors')
153+
cy.get('@minPublishesErrors').should('have.length', 1)
154+
cy.get('@minPublishesErrors')
155+
.eq(0)
156+
.should('contain.text', 'The minimum publish quota must be lesser than the maximum publish quota')
157+
158+
cy.get('[role="group"]:has(> label[for="root_arguments_maxPublishes"]) + ul > li').as('maxPublishesErrors')
159+
cy.get('@maxPublishesErrors').should('have.length', 1)
160+
cy.get('@maxPublishesErrors')
161+
.eq(0)
162+
.should('contain.text', 'The maximum publish quota must be greater than the minimum publish quota')
163+
})
164+
})
93165
})

hivemq-edge-frontend/src/extensions/datahub/designer/behavior_policy/BehaviorPolicyPanel.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ import { useTranslation } from 'react-i18next'
55
import { Card, CardBody } from '@chakra-ui/react'
66
import type { CustomValidator } from '@rjsf/utils'
77

8-
import type { BehaviorPolicyData, PanelProps } from '@datahub/types.ts'
8+
import ErrorMessage from '@/components/ErrorMessage.tsx'
9+
910
import { useGetAllBehaviorPolicies } from '@datahub/api/hooks/DataHubBehaviorPoliciesService/useGetAllBehaviorPolicies.ts'
1011
import { ReactFlowSchemaForm } from '@datahub/components/forms/ReactFlowSchemaForm.tsx'
1112
import { MOCK_BEHAVIOR_POLICY_SCHEMA } from '@datahub/designer/behavior_policy/BehaviorPolicySchema.ts'
1213
import useDataHubDraftStore from '@datahub/hooks/useDataHubDraftStore.ts'
1314
import { usePolicyGuards } from '@datahub/hooks/usePolicyGuards.ts'
14-
import ErrorMessage from '@/components/ErrorMessage.tsx'
15+
import type { BehaviorPolicyData, PanelProps, PublishQuotaArguments } from '@datahub/types.ts'
16+
import { BehaviorPolicyType } from '@datahub/types.ts'
17+
18+
const UNLIMITED_PUBLISH = -1
1519

1620
export const BehaviorPolicyPanel: FC<PanelProps> = ({ selectedNode, onFormSubmit }) => {
1721
const { t } = useTranslation('datahub')
@@ -30,6 +34,17 @@ export const BehaviorPolicyPanel: FC<PanelProps> = ({ selectedNode, onFormSubmit
3034
const isIdNotUnique = Boolean(allPolicies.items?.find((e) => e.id === formData?.id))
3135
if (isIdNotUnique) errors['id']?.addError(t('error.validation.behaviourPolicy.notUnique'))
3236
}
37+
if (formData?.model === BehaviorPolicyType.PUBLISH_QUOTA) {
38+
const { maxPublishes, minPublishes } = formData.arguments as PublishQuotaArguments
39+
if (maxPublishes !== UNLIMITED_PUBLISH && maxPublishes < minPublishes) {
40+
errors?.['arguments']?.['maxPublishes']?.addError(
41+
t('error.validation.behaviourPolicy.publishQuota.maxLessThanMin')
42+
)
43+
errors?.['arguments']?.['minPublishes']?.addError(
44+
t('error.validation.behaviourPolicy.publishQuota.minMoreThanMax')
45+
)
46+
}
47+
}
3348
return errors
3449
}
3550

hivemq-edge-frontend/src/extensions/datahub/designer/operation/OperationPanel.spec.cy.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ describe('OperationPanel', () => {
170170
},
171171
}
172172

173-
it.only('should render the form', () => {
173+
it('should render the form', () => {
174174
cy.mountWithProviders(<OperationPanel selectedNode="my-node" />, {
175175
wrapper: getWrapperWith([node]),
176176
})

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,11 @@
369369
},
370370
"behaviourPolicy": {
371371
"notLoading": "Cannot load the existing policies for checking ids",
372-
"notUnique": "The id is already in use by another behavior policy"
372+
"notUnique": "The id is already in use by another behavior policy",
373+
"publishQuota": {
374+
"maxLessThanMin": "The maximum publish quota must be greater than or equal to the minimum publish quota",
375+
"minMoreThanMax": "The minimum publish quota must be less than or equal to the maximum publish quota"
376+
}
373377
},
374378
"operation": {
375379
"notLoading": "Cannot load the existing operations for checking ids",

hivemq-edge-frontend/src/extensions/datahub/types.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,17 +277,23 @@ export namespace OperationData {
277277
}
278278
}
279279

280-
// TODO[18757] Add to the OpenAPI specs; see https://hivemq.kanbanize.com/ctrl_board/4/cards/18757/details/
280+
// TODO[33539] Add to the OpenAPI specs; see https://hivemq.kanbanize.com/ctrl_board/4/cards/18757/details/
281281
export enum BehaviorPolicyType {
282282
MQTT_EVENT = 'Mqtt.events',
283283
PUBLISH_DUPLICATE = 'Publish.duplicate',
284284
PUBLISH_QUOTA = 'Publish.quota',
285285
}
286286

287+
// TODO[33539] Add to the OpenAPI specs; see https://hivemq.kanbanize.com/ctrl_board/4/cards/18757/details/
288+
export interface PublishQuotaArguments {
289+
minPublishes: number
290+
maxPublishes: number
291+
}
292+
287293
export interface BehaviorPolicyData extends DataHubNodeData {
288294
id: string
289295
model: BehaviorPolicyType
290-
arguments?: Record<string, string | number>
296+
arguments?: PublishQuotaArguments | Record<string, string | number>
291297
core?: BehaviorPolicy
292298
}
293299

hivemq-edge-frontend/src/extensions/datahub/utils/node.utils.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ import type { Connection, Edge, HandleProps, Node } from '@xyflow/react'
22
import { getConnectedEdges, getIncomers, getOutgoers } from '@xyflow/react'
33
import { v4 as uuidv4 } from 'uuid'
44
import type { TFunction } from 'i18next'
5-
import validator from '@rjsf/validator-ajv8'
65
import { RiPassExpiredLine, RiPassPendingLine, RiPassValidLine } from 'react-icons/ri'
76

87
import i18n from '@/config/i18n.config.ts'
98

10-
import { MOCK_JSONSCHEMA_SCHEMA } from '../__test-utils__/schema.mocks.ts'
9+
import { DataPolicyValidator } from '@/api/__generated__'
10+
import { customFormatsValidator } from '@/components/rjsf/Form/validation.utils.ts'
11+
12+
import { MOCK_JSONSCHEMA_SCHEMA } from '@datahub/__test-utils__/schema.mocks.ts'
13+
import { CustomNodeJSONSchema } from '@datahub/config/schemas.config.ts'
14+
import { PolicyCheckErrors } from '@datahub/designer/validation.errors.ts'
1115
import type {
1216
ClientFilterData,
1317
DataHubNodeData,
@@ -17,7 +21,7 @@ import type {
1721
TopicFilterData,
1822
ValidatorData,
1923
ValidDropConnection,
20-
} from '../types.ts'
24+
} from '@datahub/types.ts'
2125
import {
2226
BehaviorPolicyData,
2327
DataHubNodeType,
@@ -30,10 +34,7 @@ import {
3034
SchemaType,
3135
StrategyType,
3236
TransitionData,
33-
} from '../types.ts'
34-
import { DataPolicyValidator } from '@/api/__generated__'
35-
import { CustomNodeJSONSchema } from '@datahub/config/schemas.config.ts'
36-
import { PolicyCheckErrors } from '@datahub/designer/validation.errors.ts'
37+
} from '@datahub/types.ts'
3738

3839
export const getNodeId = (stub = 'node') => `${stub}_${uuidv4()}`
3940

@@ -392,7 +393,7 @@ export const renderResourceName = (name: string | undefined, version: number | u
392393
export const validateNode = (newNode: Node) => {
393394
if (!newNode.type) return { isValid: false }
394395
const schema = CustomNodeJSONSchema[newNode.type]
395-
const validate = validator.ajv.compile(schema)
396+
const validate = customFormatsValidator.ajv.compile(schema)
396397
const isValid = validate(newNode.data)
397398
return {
398399
isValid,

0 commit comments

Comments
 (0)