Skip to content

Commit

Permalink
feat: custom validation properties panel
Browse files Browse the repository at this point in the history
Related to #1162
  • Loading branch information
Skaiir committed Apr 24, 2024
1 parent a3222e9 commit 950da0c
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
SerializationGroup,
ConstraintsGroup,
ValidationGroup,
CustomValidationsGroup,
OptionsGroups,
TableHeaderGroups,
LayoutGroup,
Expand Down Expand Up @@ -66,6 +67,7 @@ export class PropertiesProvider {
SerializationGroup(field, editField),
ConstraintsGroup(field, editField),
ValidationGroup(field, editField),
CustomValidationsGroup(field, editField, getService),
CustomPropertiesGroup(field, editField),
].filter((group) => group != null);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { get, set } from 'min-dash';

import { useService } from '../hooks';

import { FeelEntry, isFeelEntryEdited } from '@bpmn-io/properties-panel';
import { useCallback } from 'preact/hooks';

export function CustomValidationEntry(props) {
const { editField, field, idPrefix, index } = props;

const entries = [
{
component: Condition,
editField,
field,
id: idPrefix + '-condition',
idPrefix,
index,
},
{
component: Message,
editField,
field,
id: idPrefix + '-message',
idPrefix,
index,
},
];

return entries;
}

function Condition(props) {
const { editField, field, id, index } = props;

const debounce = useService('debounce');

const setValue = (value, error) => {
if (error) {
return;
}

const validate = get(field, ['validate']);
const newValidate = set(validate, ['custom', index, 'condition'], value);

return editField(field, 'validate', newValidate);
};

const getValue = () => {
return get(field, ['validate', 'custom', index, 'condition']);
};

const conditionEntryValidate = useCallback((value) => {
if (typeof value !== 'string' || value.length === 0) {
return 'Must not be empty.';
}
}, []);

return FeelEntry({
feel: 'required',
isEdited: isFeelEntryEdited,
debounce,
element: field,
getValue,
id,
label: 'Condition',
setValue,
validate: conditionEntryValidate,
});
}

function Message(props) {
const { editField, field, id, index } = props;

const debounce = useService('debounce');

const setValue = (value, error) => {
if (error) {
return;
}

const validate = get(field, ['validate']);
const newValidate = set(validate, ['custom', index, 'message'], value);

return editField(field, 'validate', newValidate);
};

const getValue = () => {
return get(field, ['validate', 'custom', index, 'message']);
};

const messageEntryValidate = useCallback((value) => {
if (typeof value !== 'string' || value.length === 0) {
return 'Must not be empty.';
}
}, []);

return FeelEntry({
feel: 'optional',
isEdited: isFeelEntryEdited,
debounce,
element: field,
getValue,
id,
label: 'Message if condition not met',
setValue,
validate: messageEntryValidate,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { get, set, without } from 'min-dash';

import { arrayAdd } from '../Util';

import { ListGroup } from '@bpmn-io/properties-panel';

import { VALIDATION_TYPE_OPTIONS } from './ValidationGroup';
import { CustomValidationEntry } from '../entries/CustomValidationEntry';

export function CustomValidationsGroup(field, editField, getService) {
const validate = get(field, ['validate'], {});
const { validatable, keyed } = getService('formFields').get(field.type).config;

const isValidatable = validatable !== undefined ? validatable : keyed;
const isCustomValidation = [undefined, VALIDATION_TYPE_OPTIONS.custom.value].includes(validate.validationType);
const shouldRender = isValidatable && isCustomValidation;

if (!shouldRender) {
return;
}

return {
id: 'custom-validation',
label: 'Custom validations',
tooltip: "Define custom validation rules for this field. Use 'value' to reference the field value.",
component: ListGroup,
...CustomValidationsEntry({ editField, field, id: 'custom-validation-list' }),
};
}

export function CustomValidationsEntry(props) {
const { editField, field, id: idPrefix } = props;

const addEntry = (e) => {
e.stopPropagation();

const customValidations = get(field, ['validate', 'custom'], []);
const newIndex = customValidations.length + 1;

const newValue = {
condition: '=false',
message: 'Error message.',
};

const newArray = arrayAdd(customValidations, newIndex, newValue);
const newValidate = set(field.validate || {}, ['custom'], newArray);

editField(field, ['validate'], newValidate);
};

const removeEntry = (entry) => {
const customValidations = get(field, ['validate', 'custom'], []);
const newArray = without(customValidations, entry);
const newValidate = set(field.validate, ['custom'], newArray);

editField(field, ['validate'], newValidate);
};

const items = get(field, ['validate', 'custom'], []).map((entry, index) => {
const id = idPrefix + '-' + index;

return {
id,
entries: CustomValidationEntry({
editField,
field,
idPrefix,
index,
}),
label: 'Rule ' + (index + 1),
remove: () => removeEntry(entry),
};
});

return {
items,
add: addEntry,
shouldSort: false,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { useService, useVariables } from '../hooks';

import { INPUTS } from '../Util';

const VALIDATION_TYPE_OPTIONS = {
export const VALIDATION_TYPE_OPTIONS = {
custom: {
value: '',
label: 'Custom',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { GeneralGroup } from './GeneralGroup';
export { SerializationGroup } from './SerializationGroup';
export { ConstraintsGroup } from './ConstraintsGroup';
export { ValidationGroup } from './ValidationGroup';
export { CustomValidationsGroup } from './CustomValidationsGroup';
export { OptionsGroups } from './OptionsGroups';
export { CustomPropertiesGroup } from './CustomPropertiesGroup';
export { AppearanceGroup } from './AppearanceGroup';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ ExpressionField.config = {
label: 'Expression',
group: 'basic-input',
keyed: true,
validatable: false,
emptyValue: null,
escapeGridRender: true,
create: (options = {}) => ({
Expand Down

0 comments on commit 950da0c

Please sign in to comment.