Skip to content

Commit

Permalink
[FEATURE] Adds validation for create tag dialog
Browse files Browse the repository at this point in the history
To prevent exceptions and make it more user friedly this change validated the tag creation form, so that you can not create an empty tag or a tag that already exists.
  • Loading branch information
markusguenther authored and Sebobo committed Nov 4, 2024
1 parent d7e8843 commit e88d8b9
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ const AddTagButton: React.FC = () => {
const selectedTagId = useRecoilValue(selectedTagIdState);

const onClickCreate = useCallback(() => {
setCreateTagDialogState({ label: '', visible: true });
setCreateTagDialogState({
visible: true,
label: '',
tags: [],
validation: {
valid: false,
errors: [],
},
});
}, [setCreateTagDialogState]);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import * as React from 'react';
import { useCallback } from 'react';
import { useRecoilState } from 'recoil';

import { Button, Label, TextInput } from '@neos-project/react-ui-components';
import { Button, Label, TextInput, Tooltip } from '@neos-project/react-ui-components';

import TAGS from '../queries/tags';
import { useQuery } from '@apollo/client';
import { useIntl, useNotify } from '@media-ui/core';
import { useSelectedAssetCollection } from '@media-ui/feature-asset-collections';
import { useCreateTag } from '@media-ui/feature-asset-tags';
import { useCreateTag, useTagsQuery } from '@media-ui/feature-asset-tags';
import { Dialog } from '@media-ui/core/src/components';

import createTagDialogState from '../state/createTagDialogState';
Expand All @@ -18,10 +20,22 @@ const CreateTagDialog: React.FC = () => {
const Notify = useNotify();
const selectedAssetCollection = useSelectedAssetCollection();
const [dialogState, setDialogState] = useRecoilState(createTagDialogState);
const createPossible = !!(dialogState.label && dialogState.label.trim());
const { createTag } = useCreateTag();
const { tags } = useTagsQuery();

const handleRequestClose = useCallback(() => setDialogState({ visible: false, label: '' }), [setDialogState]);
const handleRequestClose = useCallback(
() =>
setDialogState({
visible: false,
label: '',
tags: [],
validation: {
valid: false,
errors: [],
},
}),
[setDialogState]
);
const handleCreate = useCallback(() => {
setDialogState((state) => ({ ...state, visible: false }));
createTag(dialogState.label, selectedAssetCollection?.id)
Expand All @@ -32,7 +46,32 @@ const CreateTagDialog: React.FC = () => {
return;
});
}, [Notify, setDialogState, createTag, dialogState, translate, selectedAssetCollection]);
const setLabel = useCallback((label) => setDialogState((state) => ({ ...state, label })), [setDialogState]);
const validate = (label) => {
const validationErrors = [];
const trimmedLabel = label.trim();
const tagWithLabelExist = tags?.some((tag) => tag.label === trimmedLabel);

if (trimmedLabel.length === 0) {
validationErrors.push(translate('tagActions.validation.emtpyTagLabl', 'Please provide a tag label'));
}

if (tagWithLabelExist) {
validationErrors.push(translate('tagActions.validation.tagExists', 'A tag with this label already exists'));
}

const validation = {
errors: validationErrors,
valid: validationErrors.length === 0,
};
setDialogState((state) => ({ ...state, validation }));
};
const setLabel = useCallback(
(label) => {
validate(label);
setDialogState((state) => ({ ...state, label }));
},
[setDialogState]
);

return (
<Dialog
Expand All @@ -47,7 +86,7 @@ const CreateTagDialog: React.FC = () => {
key="upload"
style="success"
hoverStyle="success"
disabled={!createPossible}
disabled={!dialogState.validation?.valid}
onClick={handleCreate}
>
{translate('general.create', 'Create')}
Expand All @@ -58,11 +97,22 @@ const CreateTagDialog: React.FC = () => {
<Label>{translate('general.label', 'Label')}</Label>
<TextInput
setFocus
validationerrors={dialogState.validation?.valid ? null : ['This input is invalid']}
required={true}
type="text"
value={dialogState.label}
onChange={setLabel}
onEnterKey={createPossible ? handleCreate : null}
onEnterKey={dialogState.validation?.valid ? handleCreate : null}
/>
{dialogState.validation?.errors?.length > 0 && (
<Tooltip renderInline asError>
<ul>
{dialogState.validation.errors.map((error, index) => (
<li key={index}>{error}</li>
))}
</ul>
</Tooltip>
)}
</div>
</Dialog>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ const createTagDialogState = atom({
default: {
visible: false,
label: '',
tags: [],
validation: {
valid: false,
errors: [],
},
},
});

Expand Down
34 changes: 34 additions & 0 deletions Resources/Private/JavaScript/media-module/tests/tags.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import page from './page-model';
import { ReactSelector } from 'testcafe-react-selectors';
import { SERVER_NAME } from './helpers';

fixture('Tags').page(SERVER_NAME);

const subSection = (name) => console.log('\x1b[33m%s\x1b[0m', ' - ' + name);

test('Clicking first tag updates list and only assets should be shown that are assigned to it', async (t) => {
await t
// Uncollapse the tag list
Expand All @@ -19,3 +22,34 @@ test('Clicking first tag updates list and only assets should be shown that are a
.expect(page.assetCount.innerText)
.eql('12 assets');
});

test('Create a new tag and test validation', async (t) => {
subSection('Check existing tag label validation');
await t
.click(page.assetCollections.withText('All'))
.click(page.collectionTree.findReact('AddTagButton'))
.typeText(ReactSelector('CreateTagDialog').findReact('TextInput'), 'Example tag 1')
.expect(
ReactSelector('CreateTagDialog')
.findReact('TextInput')
.withProps({ validationerrors: ['This input is invalid'] }).exists
)
.ok('Text input should have validation errors')
.expect(ReactSelector('CreateTagDialog').findReact('Button').withProps({ disabled: true }).exists)
.ok('Create button should be disabled')
.expect(ReactSelector('CreateTagDialog').find('ul li').textContent)
.eql('A tag with this label already exists')
.typeText(ReactSelector('CreateTagDialog').findReact('TextInput'), '00')
.expect(ReactSelector('CreateTagDialog').find('ul li').exists)
.notOk('The tooltip should not be visible anymore')
.expect(ReactSelector('CreateTagDialog').findReact('Button').withProps({ disabled: false }).exists)
.ok('Create button should be enabled');

subSection('Check emtpy tag label validation');
await t
.typeText(ReactSelector('CreateTagDialog').findReact('TextInput'), ' ', { replace: true })
.expect(ReactSelector('CreateTagDialog').findReact('Button').withProps({ disabled: true }).exists)
.ok('Create button should be disabled')
.expect(ReactSelector('CreateTagDialog').find('ul li').textContent)
.eql('Please provide a tag label');
});
8 changes: 8 additions & 0 deletions Resources/Private/Translations/de/Main.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,14 @@
<source>Create tag</source>
<target>Tag erstellen</target>
</trans-unit>
<trans-unit id="tagActions.validation.emtpyTagLabl" xml:space="preserve" approved="yes">
<source>Please provide a tag label</source>
<target>Bitte geben Sie einen Tag-Namen ein</target>
</trans-unit>
<trans-unit id="tagActions.validation.tagExists" xml:space="preserve" approved="yes">
<source>This tag is already exists. Please choose a different one.</source>
<target>Dieser Tag existiert bereits. Bitte wählen Sie einen anderen aus.</target>
</trans-unit>
<trans-unit id="tagActions.create.success" xml:space="preserve" approved="yes">
<source>Tag was created</source>
<target>Tag wurde erstellt</target>
Expand Down
6 changes: 6 additions & 0 deletions Resources/Private/Translations/en/Main.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,12 @@
<trans-unit id="tagActions.create.success" xml:space="preserve" approved="yes">
<source>Tag was created</source>
</trans-unit>
<trans-unit id="tagActions.validation.emtpyTagLabl" xml:space="preserve" approved="yes">
<source>Please provide a tag label</source>
</trans-unit>
<trans-unit id="tagActions.validation.tagExists" xml:space="preserve" approved="yes">
<source>This tag is already exists. Please choose a different one.</source>
</trans-unit>
<trans-unit id="tagActions.create.error" xml:space="preserve" approved="yes">
<source>Failed to create tag</source>
</trans-unit>
Expand Down

0 comments on commit e88d8b9

Please sign in to comment.