@balena/ui-shared-components 14.0.3 → 14.1.0-build-limit-field-lengths-2d063da12c2b1e3b691fc126191269d24d81aae1-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/RJST/Actions/Tags.js +5 -1
- package/dist/components/TagManagementDialog/AddTagForm.d.ts +8 -3
- package/dist/components/TagManagementDialog/AddTagForm.js +36 -8
- package/dist/components/TagManagementDialog/index.d.ts +4 -1
- package/dist/components/TagManagementDialog/index.js +10 -3
- package/dist/hooks/useTranslations.js +2 -0
- package/package.json +2 -2
|
@@ -8,6 +8,7 @@ import { useQuery } from '@tanstack/react-query';
|
|
|
8
8
|
import { Spinner } from '../../Spinner';
|
|
9
9
|
import { TagManagementDialog } from '../../TagManagementDialog';
|
|
10
10
|
export const Tags = ({ selected, rjstContext, schema, setIsBusyMessage, refresh, onDone, }) => {
|
|
11
|
+
var _a;
|
|
11
12
|
const { t } = useTranslation();
|
|
12
13
|
const { sdk, internalPineFilter, checkedState } = rjstContext;
|
|
13
14
|
const getAllTags = (sdk === null || sdk === void 0 ? void 0 : sdk.tags) && 'getAll' in sdk.tags ? sdk.tags.getAll : null;
|
|
@@ -81,7 +82,10 @@ export const Tags = ({ selected, rjstContext, schema, setIsBusyMessage, refresh,
|
|
|
81
82
|
if (!rjstContext.tagField || !rjstContext.nameField || !items) {
|
|
82
83
|
return null;
|
|
83
84
|
}
|
|
84
|
-
|
|
85
|
+
const tagSchema = (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[rjstContext.tagField];
|
|
86
|
+
return (_jsx(Spinner, { show: isPending, sx: { width: '100%', height: '100%' }, children: _jsx(TagManagementDialog, { items: items, itemType: rjstContext.resource, titleField: getItemName !== null && getItemName !== void 0 ? getItemName : rjstContext.nameField, tagField: rjstContext.tagField, tagSchema: tagSchema != null && typeof tagSchema === 'object'
|
|
87
|
+
? tagSchema
|
|
88
|
+
: undefined, done: async (tagSubmitInfo) => {
|
|
85
89
|
await changeTags(tagSubmitInfo);
|
|
86
90
|
onDone();
|
|
87
91
|
}, cancel: () => {
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { ResourceTagInfo } from './models';
|
|
2
|
-
import type {
|
|
2
|
+
import type { JSONSchema7 as JSONSchema } from 'json-schema';
|
|
3
3
|
interface AddTagFormProps<T> {
|
|
4
|
-
t: TFunction;
|
|
5
4
|
itemType: string;
|
|
5
|
+
/**
|
|
6
|
+
* This is atm only used for constraint validation,
|
|
7
|
+
* but in the future it would be great if this becomes mandatory
|
|
8
|
+
* and we use an autogenerated form.
|
|
9
|
+
*/
|
|
10
|
+
schema?: JSONSchema;
|
|
6
11
|
existingTags: Array<ResourceTagInfo<T>>;
|
|
7
12
|
overridableTags?: Array<ResourceTagInfo<T>>;
|
|
8
13
|
addTag: (tag: ResourceTagInfo<T>) => void;
|
|
9
14
|
}
|
|
10
|
-
export declare const AddTagForm: <T extends object>({ itemType, existingTags, overridableTags, addTag, }: AddTagFormProps<T>) => import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare const AddTagForm: <T extends object>({ itemType, schema, existingTags, overridableTags, addTag, }: AddTagFormProps<T>) => import("react/jsx-runtime").JSX.Element;
|
|
11
16
|
export {};
|
|
@@ -10,33 +10,63 @@ import { stopKeyDownEvent, withPreventDefault, } from '../../utils/eventHandling
|
|
|
10
10
|
import { SimpleConfirmationDialog, } from '../SimpleConfirmationDialog';
|
|
11
11
|
import { useRandomUUID } from '../../hooks/useRandomUUID';
|
|
12
12
|
const RESERVED_NAMESPACES = ['io.resin.', 'io.balena.'];
|
|
13
|
-
const newTagValidationRules = (t, key,
|
|
13
|
+
const newTagValidationRules = (t, schema, existingTags, key, value) => {
|
|
14
|
+
var _a, _b;
|
|
15
|
+
const tagKeySchema = (_a = schema === null || schema === void 0 ? void 0 : schema.properties) === null || _a === void 0 ? void 0 : _a.tag_key;
|
|
16
|
+
const tagValueSchema = (_b = schema === null || schema === void 0 ? void 0 : schema.properties) === null || _b === void 0 ? void 0 : _b.value;
|
|
17
|
+
const tagKeyMaxLength = tagKeySchema != null && typeof tagKeySchema === 'object'
|
|
18
|
+
? tagKeySchema.maxLength
|
|
19
|
+
: null;
|
|
20
|
+
const tagValueMaxLength = tagValueSchema != null && typeof tagValueSchema === 'object'
|
|
21
|
+
? tagValueSchema.maxLength
|
|
22
|
+
: null;
|
|
14
23
|
return [
|
|
15
24
|
{
|
|
16
25
|
test: () => !key || isEmpty(key),
|
|
26
|
+
field: 'tag_key',
|
|
17
27
|
message: t('fields_errors.tag_name_cannot_be_empty'),
|
|
18
28
|
},
|
|
19
29
|
{
|
|
20
30
|
test: () => /\s/.test(key),
|
|
31
|
+
field: 'tag_key',
|
|
21
32
|
message: t('fields_errors.tag_names_cannot_contain_whitespace'),
|
|
22
33
|
},
|
|
23
34
|
{
|
|
24
35
|
test: () => RESERVED_NAMESPACES.some((reserved) => startsWith(key, reserved)),
|
|
36
|
+
field: 'tag_key',
|
|
25
37
|
message: t(`fields_errors.some_tag_keys_are_reserved`, {
|
|
26
38
|
namespace: RESERVED_NAMESPACES.join(', '),
|
|
27
39
|
}),
|
|
28
40
|
},
|
|
29
41
|
{
|
|
30
42
|
test: () => existingTags.some((tag) => tag.state !== 'deleted' && tag.tag_key === key),
|
|
43
|
+
field: 'tag_key',
|
|
31
44
|
message: t('fields_errors.tag_with_same_name_exists'),
|
|
32
45
|
},
|
|
46
|
+
...(tagKeyMaxLength != null
|
|
47
|
+
? [
|
|
48
|
+
{
|
|
49
|
+
test: () => key.length > tagKeyMaxLength,
|
|
50
|
+
field: 'tag_key',
|
|
51
|
+
message: t('fields_errors.tag_name_cannot_longer_than_maximum_characters', { maximum: tagKeyMaxLength }),
|
|
52
|
+
},
|
|
53
|
+
]
|
|
54
|
+
: []),
|
|
55
|
+
...(tagValueMaxLength != null
|
|
56
|
+
? [
|
|
57
|
+
{
|
|
58
|
+
test: () => value.length > tagValueMaxLength,
|
|
59
|
+
field: 'value',
|
|
60
|
+
message: t('fields_errors.tag_value_cannot_longer_than_maximum_characters', { maximum: tagValueMaxLength }),
|
|
61
|
+
},
|
|
62
|
+
]
|
|
63
|
+
: []),
|
|
33
64
|
];
|
|
34
65
|
};
|
|
35
|
-
export const AddTagForm = ({ itemType, existingTags, overridableTags = [], addTag, }) => {
|
|
66
|
+
export const AddTagForm = ({ itemType, schema, existingTags, overridableTags = [], addTag, }) => {
|
|
36
67
|
const { t } = useTranslation();
|
|
37
68
|
const [tagKey, setTagKey] = React.useState('');
|
|
38
69
|
const [value, setValue] = React.useState('');
|
|
39
|
-
const [tagKeyIsInvalid, setTagKeyIsInvalid] = React.useState(false);
|
|
40
70
|
const [error, setError] = React.useState();
|
|
41
71
|
const [canSubmit, setCanSubmit] = React.useState(false);
|
|
42
72
|
const [confirmationDialogOptions, setConfirmationDialogOptions] = React.useState();
|
|
@@ -45,9 +75,8 @@ export const AddTagForm = ({ itemType, existingTags, overridableTags = [], addTa
|
|
|
45
75
|
const formId = useRandomUUID();
|
|
46
76
|
const formUuid = `add-tag-form-${formId}`;
|
|
47
77
|
const checkNewTagValidity = (key) => {
|
|
48
|
-
const failedRule = newTagValidationRules(t, key,
|
|
78
|
+
const failedRule = newTagValidationRules(t, schema, existingTags, key, value).find((rule) => rule.test());
|
|
49
79
|
const hasErrors = !!failedRule;
|
|
50
|
-
setTagKeyIsInvalid(hasErrors);
|
|
51
80
|
setError(failedRule);
|
|
52
81
|
setCanSubmit(!hasErrors);
|
|
53
82
|
return hasErrors;
|
|
@@ -92,7 +121,6 @@ export const AddTagForm = ({ itemType, existingTags, overridableTags = [], addTa
|
|
|
92
121
|
});
|
|
93
122
|
setTagKey('');
|
|
94
123
|
setValue('');
|
|
95
|
-
setTagKeyIsInvalid(false);
|
|
96
124
|
setError(undefined);
|
|
97
125
|
setCanSubmit(false);
|
|
98
126
|
if (tagKeyInput === null || tagKeyInput === void 0 ? void 0 : tagKeyInput.current) {
|
|
@@ -112,13 +140,13 @@ export const AddTagForm = ({ itemType, existingTags, overridableTags = [], addTa
|
|
|
112
140
|
}, fullWidth: true, ref: tagKeyInput, onChange: (e) => {
|
|
113
141
|
setTagKey(e.target.value);
|
|
114
142
|
checkNewTagValidity(e.target.value);
|
|
115
|
-
}, value: tagKey, error:
|
|
143
|
+
}, value: tagKey, error: (error === null || error === void 0 ? void 0 : error.field) === 'tag_key', placeholder: t('labels.tag_name') }), _jsx(TextField, { slotProps: {
|
|
116
144
|
htmlInput: {
|
|
117
145
|
form: formUuid,
|
|
118
146
|
},
|
|
119
147
|
}, fullWidth: true, ref: valueInput, onChange: (e) => {
|
|
120
148
|
setValue(e.target.value);
|
|
121
|
-
}, value: value, placeholder: t('labels.value') }), _jsx("form", { id: formUuid, onSubmit: internalAddTag, children: _jsx(Button, { sx: {
|
|
149
|
+
}, value: value, error: (error === null || error === void 0 ? void 0 : error.field) === 'value', placeholder: t('labels.value') }), _jsx("form", { id: formUuid, onSubmit: internalAddTag, children: _jsx(Button, { sx: {
|
|
122
150
|
width: 120,
|
|
123
151
|
}, onClick: internalAddTag, disabled: !canSubmit, children: t('actions.add_tag') }) }), confirmationDialogOptions && (_jsx(SimpleConfirmationDialog, Object.assign({}, confirmationDialogOptions)))] }), error && _jsx(Callout, { severity: "danger", children: error.message })] }));
|
|
124
152
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { JSONSchema7 as JSONSchema } from 'json-schema';
|
|
1
2
|
import type { ResourceTagSubmitInfo, SubmitInfo, TaggedResource } from './models';
|
|
2
3
|
export interface TagManagementDialogProps<T> {
|
|
3
4
|
/** Selected items to tag */
|
|
@@ -8,9 +9,11 @@ export interface TagManagementDialogProps<T> {
|
|
|
8
9
|
titleField: keyof T | ((item: T) => string);
|
|
9
10
|
/** Tags property in the selected item */
|
|
10
11
|
tagField: keyof T;
|
|
12
|
+
/** The schema of the tag resource */
|
|
13
|
+
tagSchema?: JSONSchema;
|
|
11
14
|
/** On cancel press event */
|
|
12
15
|
cancel: () => void;
|
|
13
16
|
/** On done press event */
|
|
14
17
|
done: (tagSubmitInfo: SubmitInfo<ResourceTagSubmitInfo, ResourceTagSubmitInfo>) => void;
|
|
15
18
|
}
|
|
16
|
-
export declare const TagManagementDialog: <T extends TaggedResource>({ items, itemType, titleField, tagField, cancel, done, }: TagManagementDialogProps<T>) => import("react/jsx-runtime").JSX.Element | null;
|
|
19
|
+
export declare const TagManagementDialog: <T extends TaggedResource>({ items, itemType, titleField, tagField, tagSchema, cancel, done, }: TagManagementDialogProps<T>) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -98,11 +98,16 @@ const PreviousTagProperty = styled(TagProperty) `
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
`;
|
|
101
|
-
export const TagManagementDialog = ({ items, itemType, titleField, tagField, cancel, done, }) => {
|
|
101
|
+
export const TagManagementDialog = ({ items, itemType, titleField, tagField, tagSchema, cancel, done, }) => {
|
|
102
|
+
var _a;
|
|
102
103
|
const { t } = useTranslation();
|
|
103
104
|
const [editingTag, setEditingTag] = React.useState();
|
|
104
105
|
const [tags, setTags] = React.useState();
|
|
105
106
|
const [partialTags, setPartialTags] = React.useState();
|
|
107
|
+
const tagValueSchema = (_a = tagSchema === null || tagSchema === void 0 ? void 0 : tagSchema.properties) === null || _a === void 0 ? void 0 : _a.value;
|
|
108
|
+
const tagValueMaxLength = tagValueSchema != null && typeof tagValueSchema === 'object'
|
|
109
|
+
? tagValueSchema.maxLength
|
|
110
|
+
: null;
|
|
106
111
|
const tagDiffs = React.useMemo(() => getResourceTagSubmitInfo(tags !== null && tags !== void 0 ? tags : []), [tags]);
|
|
107
112
|
React.useEffect(() => {
|
|
108
113
|
const allTags = groupResourcesByTags(items, tagField);
|
|
@@ -191,7 +196,7 @@ export const TagManagementDialog = ({ items, itemType, titleField, tagField, can
|
|
|
191
196
|
: toString(item[titleField]);
|
|
192
197
|
return title || `(${t('no_data.no_name_set')})`;
|
|
193
198
|
};
|
|
194
|
-
return (_jsxs(DialogWithCloseButton, { open: true, title: _jsxs(Stack, { children: [_jsxs(Typography, { variant: "h3", mt: 0, mb: 10, children: [items.length > 1 && _jsxs("span", { children: [t('labels.shared'), " "] }), t('labels.tags')] }), _jsx(CollectionSummary, { items: items.map(getItemTitle).sort(), itemsType: t('resource.' + itemType, { count: items.length }), maxVisibleItemCount: 10 })] }), children: [_jsxs(DialogContent, { children: [_jsx(AddTagForm, { itemType: itemType, existingTags: tags, overridableTags: partialTags, addTag: addTag
|
|
199
|
+
return (_jsxs(DialogWithCloseButton, { open: true, title: _jsxs(Stack, { children: [_jsxs(Typography, { variant: "h3", mt: 0, mb: 10, children: [items.length > 1 && _jsxs("span", { children: [t('labels.shared'), " "] }), t('labels.tags')] }), _jsx(CollectionSummary, { items: items.map(getItemTitle).sort(), itemsType: t('resource.' + itemType, { count: items.length }), maxVisibleItemCount: 10 })] }), children: [_jsxs(DialogContent, { children: [_jsx(AddTagForm, { itemType: itemType, schema: tagSchema, existingTags: tags, overridableTags: partialTags, addTag: addTag }), _jsxs(Table, { children: [_jsxs(TableHead, { children: [_jsx(TableCell, {}), _jsx(TableCell, { children: t('labels.tag_name') }), _jsx(TableCell, { children: t('labels.value') }), _jsx(TableCell, {})] }), _jsx(TableBody, { children: !tags.length ? (_jsxs(TableRow, { children: [_jsx(TableCell, {}), _jsx(TableCell, { children: t(`errors.no_tags_for_selected_itemtype`, {
|
|
195
200
|
count: items.length,
|
|
196
201
|
itemType,
|
|
197
202
|
}) })] })) : (tags.map((tag) => {
|
|
@@ -216,7 +221,9 @@ export const TagManagementDialog = ({ items, itemType, titleField, tagField, can
|
|
|
216
221
|
setEditingTagValue(e.target.value);
|
|
217
222
|
}, onBlur: () => {
|
|
218
223
|
endTagEdit();
|
|
219
|
-
}, value: editingTag.value, placeholder: t('labels.tag_value')
|
|
224
|
+
}, value: editingTag.value, placeholder: t('labels.tag_value'), inputProps: tagValueMaxLength != null
|
|
225
|
+
? { maxLength: tagValueMaxLength }
|
|
226
|
+
: undefined }))] }), _jsx(TableCell, { children: tag.state && (_jsx(Button, { variant: "text", startIcon: _jsx(FontAwesomeIcon, { icon: faUndo }), onClick: () => {
|
|
220
227
|
undoTagChanges(tag);
|
|
221
228
|
}, sx: {
|
|
222
229
|
width: UndoButtonMinWidth,
|
|
@@ -31,6 +31,8 @@ const translationMap = {
|
|
|
31
31
|
'errors.no_tags_for_selected_itemtype_plural': 'The selected {{itemType}}s have no tags in common',
|
|
32
32
|
'fields_errors.tag_name_cannot_be_empty': "The tag name can't be empty.",
|
|
33
33
|
'fields_errors.tag_names_cannot_contain_whitespace': 'Tag names cannot contain whitespace',
|
|
34
|
+
'fields_errors.tag_name_cannot_longer_than_maximum_characters': `The tag name can't be longer than {{maximum}} characters.`,
|
|
35
|
+
'fields_errors.tag_value_cannot_longer_than_maximum_characters': `The tag value can't be longer than {{maximum}} characters.`,
|
|
34
36
|
'fields_errors.some_tag_keys_are_reserved': 'Tag names beginning with {{namespace}} are reserved',
|
|
35
37
|
'fields_errors.tag_with_same_name_exists': 'A tag with the same name already exists',
|
|
36
38
|
'fields_errors.does_not_satisfy_minimum': 'Must be greater than or equal to {{minimum}}',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@balena/ui-shared-components",
|
|
3
|
-
"version": "14.0
|
|
3
|
+
"version": "14.1.0-build-limit-field-lengths-2d063da12c2b1e3b691fc126191269d24d81aae1-1",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"files": [
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
},
|
|
133
133
|
"homepage": "https://github.com/balena-io/ui-shared-components#readme",
|
|
134
134
|
"versionist": {
|
|
135
|
-
"publishedAt": "2025-09-15T12:
|
|
135
|
+
"publishedAt": "2025-09-15T12:30:23.623Z"
|
|
136
136
|
},
|
|
137
137
|
"overrides": {
|
|
138
138
|
"storybook": "$storybook"
|