From e88d8b905d6d747b26a11a4ac1935e419848f5b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20G=C3=BCnther?= Date: Mon, 4 Nov 2024 16:26:10 +0100 Subject: [PATCH 1/2] [FEATURE] Adds validation for create tag dialog 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. --- .../src/components/AddTagButton.tsx | 10 ++- .../src/components/CreateTagDialog.tsx | 64 +++++++++++++++++-- .../src/state/createTagDialogState.ts | 5 ++ .../JavaScript/media-module/tests/tags.ts | 34 ++++++++++ Resources/Private/Translations/de/Main.xlf | 8 +++ Resources/Private/Translations/en/Main.xlf | 6 ++ 6 files changed, 119 insertions(+), 8 deletions(-) diff --git a/Resources/Private/JavaScript/asset-collections/src/components/AddTagButton.tsx b/Resources/Private/JavaScript/asset-collections/src/components/AddTagButton.tsx index 48e9c3a36..65b3f82c2 100644 --- a/Resources/Private/JavaScript/asset-collections/src/components/AddTagButton.tsx +++ b/Resources/Private/JavaScript/asset-collections/src/components/AddTagButton.tsx @@ -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 ( diff --git a/Resources/Private/JavaScript/asset-tags/src/components/CreateTagDialog.tsx b/Resources/Private/JavaScript/asset-tags/src/components/CreateTagDialog.tsx index f2af832f0..3f22e7148 100644 --- a/Resources/Private/JavaScript/asset-tags/src/components/CreateTagDialog.tsx +++ b/Resources/Private/JavaScript/asset-tags/src/components/CreateTagDialog.tsx @@ -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'; @@ -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) @@ -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 ( { key="upload" style="success" hoverStyle="success" - disabled={!createPossible} + disabled={!dialogState.validation?.valid} onClick={handleCreate} > {translate('general.create', 'Create')} @@ -58,11 +97,22 @@ const CreateTagDialog: React.FC = () => { + {dialogState.validation?.errors?.length > 0 && ( + +
    + {dialogState.validation.errors.map((error, index) => ( +
  • {error}
  • + ))} +
+
+ )}
); diff --git a/Resources/Private/JavaScript/asset-tags/src/state/createTagDialogState.ts b/Resources/Private/JavaScript/asset-tags/src/state/createTagDialogState.ts index 5e9e1d1ce..b0b1b4691 100644 --- a/Resources/Private/JavaScript/asset-tags/src/state/createTagDialogState.ts +++ b/Resources/Private/JavaScript/asset-tags/src/state/createTagDialogState.ts @@ -5,6 +5,11 @@ const createTagDialogState = atom({ default: { visible: false, label: '', + tags: [], + validation: { + valid: false, + errors: [], + }, }, }); diff --git a/Resources/Private/JavaScript/media-module/tests/tags.ts b/Resources/Private/JavaScript/media-module/tests/tags.ts index 52272f1aa..2955b3a13 100644 --- a/Resources/Private/JavaScript/media-module/tests/tags.ts +++ b/Resources/Private/JavaScript/media-module/tests/tags.ts @@ -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 @@ -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'); +}); diff --git a/Resources/Private/Translations/de/Main.xlf b/Resources/Private/Translations/de/Main.xlf index 1b985d9e1..39768f730 100644 --- a/Resources/Private/Translations/de/Main.xlf +++ b/Resources/Private/Translations/de/Main.xlf @@ -412,6 +412,14 @@ Create tag Tag erstellen + + Please provide a tag label + Bitte geben Sie einen Tag-Namen ein + + + This tag is already exists. Please choose a different one. + Dieser Tag existiert bereits. Bitte wählen Sie einen anderen aus. + Tag was created Tag wurde erstellt diff --git a/Resources/Private/Translations/en/Main.xlf b/Resources/Private/Translations/en/Main.xlf index 2cbd5f036..43b3b31fa 100644 --- a/Resources/Private/Translations/en/Main.xlf +++ b/Resources/Private/Translations/en/Main.xlf @@ -316,6 +316,12 @@ Tag was created + + Please provide a tag label + + + This tag is already exists. Please choose a different one. + Failed to create tag From d66a809abe3d5adf3a062c2410bfc3846138d069 Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Mon, 4 Nov 2024 16:36:01 +0100 Subject: [PATCH 2/2] TASK: Code cleanup of tag validation change --- .../asset-collections/src/components/AddTagButton.tsx | 1 - .../asset-tags/src/components/CreateTagDialog.tsx | 5 +---- .../JavaScript/asset-tags/src/state/createTagDialogState.ts | 1 - Resources/Private/JavaScript/media-module/tests/tags.ts | 2 +- Resources/Private/Translations/de/Main.xlf | 6 +++--- Resources/Private/Translations/en/Main.xlf | 6 +++--- 6 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Resources/Private/JavaScript/asset-collections/src/components/AddTagButton.tsx b/Resources/Private/JavaScript/asset-collections/src/components/AddTagButton.tsx index 65b3f82c2..8152764a2 100644 --- a/Resources/Private/JavaScript/asset-collections/src/components/AddTagButton.tsx +++ b/Resources/Private/JavaScript/asset-collections/src/components/AddTagButton.tsx @@ -19,7 +19,6 @@ const AddTagButton: React.FC = () => { setCreateTagDialogState({ visible: true, label: '', - tags: [], validation: { valid: false, errors: [], diff --git a/Resources/Private/JavaScript/asset-tags/src/components/CreateTagDialog.tsx b/Resources/Private/JavaScript/asset-tags/src/components/CreateTagDialog.tsx index 3f22e7148..6c3d53581 100644 --- a/Resources/Private/JavaScript/asset-tags/src/components/CreateTagDialog.tsx +++ b/Resources/Private/JavaScript/asset-tags/src/components/CreateTagDialog.tsx @@ -4,8 +4,6 @@ import { useRecoilState } from 'recoil'; 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, useTagsQuery } from '@media-ui/feature-asset-tags'; @@ -28,7 +26,6 @@ const CreateTagDialog: React.FC = () => { setDialogState({ visible: false, label: '', - tags: [], validation: { valid: false, errors: [], @@ -52,7 +49,7 @@ const CreateTagDialog: React.FC = () => { const tagWithLabelExist = tags?.some((tag) => tag.label === trimmedLabel); if (trimmedLabel.length === 0) { - validationErrors.push(translate('tagActions.validation.emtpyTagLabl', 'Please provide a tag label')); + validationErrors.push(translate('tagActions.validation.emptyTagLabel', 'Please provide a tag label')); } if (tagWithLabelExist) { diff --git a/Resources/Private/JavaScript/asset-tags/src/state/createTagDialogState.ts b/Resources/Private/JavaScript/asset-tags/src/state/createTagDialogState.ts index b0b1b4691..b139b856f 100644 --- a/Resources/Private/JavaScript/asset-tags/src/state/createTagDialogState.ts +++ b/Resources/Private/JavaScript/asset-tags/src/state/createTagDialogState.ts @@ -5,7 +5,6 @@ const createTagDialogState = atom({ default: { visible: false, label: '', - tags: [], validation: { valid: false, errors: [], diff --git a/Resources/Private/JavaScript/media-module/tests/tags.ts b/Resources/Private/JavaScript/media-module/tests/tags.ts index 2955b3a13..bcf7246e6 100644 --- a/Resources/Private/JavaScript/media-module/tests/tags.ts +++ b/Resources/Private/JavaScript/media-module/tests/tags.ts @@ -45,7 +45,7 @@ test('Create a new tag and test validation', async (t) => { .expect(ReactSelector('CreateTagDialog').findReact('Button').withProps({ disabled: false }).exists) .ok('Create button should be enabled'); - subSection('Check emtpy tag label validation'); + subSection('Check empty tag label validation'); await t .typeText(ReactSelector('CreateTagDialog').findReact('TextInput'), ' ', { replace: true }) .expect(ReactSelector('CreateTagDialog').findReact('Button').withProps({ disabled: true }).exists) diff --git a/Resources/Private/Translations/de/Main.xlf b/Resources/Private/Translations/de/Main.xlf index 39768f730..3f80df8a6 100644 --- a/Resources/Private/Translations/de/Main.xlf +++ b/Resources/Private/Translations/de/Main.xlf @@ -412,12 +412,12 @@ Create tag Tag erstellen - + Please provide a tag label Bitte geben Sie einen Tag-Namen ein - This tag is already exists. Please choose a different one. + This tag already exists. Please choose a different one. Dieser Tag existiert bereits. Bitte wählen Sie einen anderen aus. @@ -884,7 +884,7 @@ Tag-Erstellung fehlgeschlagen - This tag is already exists. Please choose a different one. + This tag already exists. Please choose a different one. Dieser Tag existiert bereits. Bitte wählen Sie einen anderen aus. diff --git a/Resources/Private/Translations/en/Main.xlf b/Resources/Private/Translations/en/Main.xlf index 43b3b31fa..d46a81ed2 100644 --- a/Resources/Private/Translations/en/Main.xlf +++ b/Resources/Private/Translations/en/Main.xlf @@ -316,11 +316,11 @@ Tag was created - + Please provide a tag label - This tag is already exists. Please choose a different one. + This tag already exists. Please choose a different one. Failed to create tag @@ -676,7 +676,7 @@ Failed to create tag - This tag is already exists. Please choose a different one. + This tag already exists. Please choose a different one. The specified asset was not found.