From 70e63ca0e3cb307a88d065359061e35d5800c3a9 Mon Sep 17 00:00:00 2001 From: Davinder Singh Date: Thu, 15 Sep 2022 10:59:00 +0100 Subject: [PATCH] Merge pull request #9614 from overleaf/ab-limit-tag-length [web] Restrict the length of tags to 50 characters GitOrigin-RevId: fc20227e3e2171bf9e27c983105ecc7b198cf882 --- .../web/app/src/Features/Tags/TagsHandler.js | 8 ++++++ .../web/frontend/extracted-translations.json | 4 ++- .../components/sidebar/create-tag-modal.tsx | 25 ++++++++++++++--- .../components/sidebar/delete-tag-modal.tsx | 2 +- .../components/sidebar/rename-tag-modal.tsx | 25 ++++++++++++++--- .../js/features/project-list/util/tag.ts | 1 + services/web/locales/en.json | 3 ++- .../test/unit/src/Tags/TagsHandlerTests.js | 27 +++++++++++++++++++ 8 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 services/web/frontend/js/features/project-list/util/tag.ts diff --git a/services/web/app/src/Features/Tags/TagsHandler.js b/services/web/app/src/Features/Tags/TagsHandler.js index 7531e24225..2b516b3f57 100644 --- a/services/web/app/src/Features/Tags/TagsHandler.js +++ b/services/web/app/src/Features/Tags/TagsHandler.js @@ -1,6 +1,8 @@ const { Tag } = require('../../models/Tag') const { promisifyAll } = require('../../util/promises') +const MAX_TAG_LENGTH = 50 + function getAllTags(userId, callback) { Tag.find({ user_id: userId }, callback) } @@ -9,6 +11,9 @@ function createTag(userId, name, callback) { if (!callback) { callback = function () {} } + if (name.length > MAX_TAG_LENGTH) { + return callback(new Error('Exceeded max tag length')) + } Tag.create({ user_id: userId, name }, function (err, tag) { // on duplicate key error return existing tag if (err && err.code === 11000) { @@ -22,6 +27,9 @@ function renameTag(userId, tagId, name, callback) { if (!callback) { callback = function () {} } + if (name.length > MAX_TAG_LENGTH) { + return callback(new Error('Exceeded max tag length')) + } Tag.updateOne( { _id: tagId, diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 22a34b59dd..608b246923 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -23,6 +23,7 @@ "approaching_compile_timeout_limit_upgrade_for_more_compile_time": "", "archive": "", "archive_projects": "", + "archived": "", "archived_projects": "", "archiving_projects_wont_affect_collaborators": "", "are_you_still_at": "", @@ -564,6 +565,7 @@ "sync_to_github": "", "tab_connecting": "", "tab_no_longer_connected": "", + "tag_name_cannot_exceed_characters": "", "tags": "", "tags_slash_folders": "", "take_short_survey": "", @@ -593,8 +595,8 @@ "too_recently_compiled": "", "total_words": "", "trash": "", - "trashed": "", "trash_projects": "", + "trashed": "", "trashed_projects": "", "trashing_projects_wont_affect_collaborators": "", "trial_last_day": "", diff --git a/services/web/frontend/js/features/project-list/components/sidebar/create-tag-modal.tsx b/services/web/frontend/js/features/project-list/components/sidebar/create-tag-modal.tsx index 27b682d0c8..569d62a264 100644 --- a/services/web/frontend/js/features/project-list/components/sidebar/create-tag-modal.tsx +++ b/services/web/frontend/js/features/project-list/components/sidebar/create-tag-modal.tsx @@ -1,10 +1,11 @@ -import { useCallback, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { Button, Form, Modal } from 'react-bootstrap' import { useTranslation } from 'react-i18next' import { Tag } from '../../../../../../app/src/Features/Tags/types' import AccessibleModal from '../../../../shared/components/accessible-modal' import useAsync from '../../../../shared/hooks/use-async' import { createTag } from '../../util/api' +import { MAX_TAG_LENGTH } from '../../util/tag' type CreateTagModalProps = { show: boolean @@ -21,6 +22,7 @@ export default function CreateTagModal({ const { isError, runAsync, status } = useAsync() const [tagName, setTagName] = useState() + const [validationError, setValidationError] = useState() const runCreateTag = useCallback(() => { if (tagName) { @@ -38,6 +40,16 @@ export default function CreateTagModal({ [runCreateTag] ) + useEffect(() => { + if (tagName && tagName.length > MAX_TAG_LENGTH) { + setValidationError( + t('tag_name_cannot_exceed_characters', { maxLength: MAX_TAG_LENGTH }) + ) + } else if (validationError) { + setValidationError(undefined) + } + }, [tagName, t, validationError]) + if (!show) { return null } @@ -68,6 +80,11 @@ export default function CreateTagModal({ + {validationError && ( +
+ {validationError} +
+ )} {isError && (
@@ -81,9 +98,11 @@ export default function CreateTagModal({ diff --git a/services/web/frontend/js/features/project-list/components/sidebar/delete-tag-modal.tsx b/services/web/frontend/js/features/project-list/components/sidebar/delete-tag-modal.tsx index 996aa85b71..f362f91ab2 100644 --- a/services/web/frontend/js/features/project-list/components/sidebar/delete-tag-modal.tsx +++ b/services/web/frontend/js/features/project-list/components/sidebar/delete-tag-modal.tsx @@ -70,7 +70,7 @@ export default function DeleteTagModal({ bsStyle="primary" disabled={status === 'pending'} > - {status === 'pending' ? t('deleting') + '...' : t('delete')} + {status === 'pending' ? t('deleting') + '…' : t('delete')} diff --git a/services/web/frontend/js/features/project-list/components/sidebar/rename-tag-modal.tsx b/services/web/frontend/js/features/project-list/components/sidebar/rename-tag-modal.tsx index 9ad003333c..37ddda55ef 100644 --- a/services/web/frontend/js/features/project-list/components/sidebar/rename-tag-modal.tsx +++ b/services/web/frontend/js/features/project-list/components/sidebar/rename-tag-modal.tsx @@ -1,10 +1,11 @@ -import { useCallback, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { Button, Form, Modal } from 'react-bootstrap' import { useTranslation } from 'react-i18next' import { Tag } from '../../../../../../app/src/Features/Tags/types' import AccessibleModal from '../../../../shared/components/accessible-modal' import useAsync from '../../../../shared/hooks/use-async' import { renameTag } from '../../util/api' +import { MAX_TAG_LENGTH } from '../../util/tag' type RenameTagModalProps = { tag?: Tag @@ -21,6 +22,7 @@ export default function RenameTagModal({ const { isError, runAsync, status } = useAsync() const [newTagName, setNewTageName] = useState() + const [validationError, setValidationError] = useState() const runRenameTag = useCallback( (tagId: string) => { @@ -43,6 +45,16 @@ export default function RenameTagModal({ [tag, runRenameTag] ) + useEffect(() => { + if (newTagName && newTagName.length > MAX_TAG_LENGTH) { + setValidationError( + t('tag_name_cannot_exceed_characters', { maxLength: MAX_TAG_LENGTH }) + ) + } else if (validationError) { + setValidationError(undefined) + } + }, [newTagName, t, validationError]) + if (!tag) { return null } @@ -74,6 +86,11 @@ export default function RenameTagModal({ + {validationError && ( +
+ {validationError} +
+ )} {isError && (
@@ -87,9 +104,11 @@ export default function RenameTagModal({ diff --git a/services/web/frontend/js/features/project-list/util/tag.ts b/services/web/frontend/js/features/project-list/util/tag.ts new file mode 100644 index 0000000000..758f52c523 --- /dev/null +++ b/services/web/frontend/js/features/project-list/util/tag.ts @@ -0,0 +1 @@ +export const MAX_TAG_LENGTH = 50 diff --git a/services/web/locales/en.json b/services/web/locales/en.json index e906184c14..3ae615cc11 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1862,5 +1862,6 @@ "descending": "Descending", "reverse_x_sort_order" : "Reverse __x__ sort order", "create_first_project": "Create First Project", - "you_dont_have_any_repositories": "You don’t have any repositories" + "you_dont_have_any_repositories": "You don’t have any repositories", + "tag_name_cannot_exceed_characters": "Tag name cannot exceed __maxLength__ characters" } diff --git a/services/web/test/unit/src/Tags/TagsHandlerTests.js b/services/web/test/unit/src/Tags/TagsHandlerTests.js index 7b79bdac05..260abba36b 100644 --- a/services/web/test/unit/src/Tags/TagsHandlerTests.js +++ b/services/web/test/unit/src/Tags/TagsHandlerTests.js @@ -65,6 +65,19 @@ describe('TagsHandler', function () { }) }) + describe('when tag is too long', function () { + it('should throw an error', function (done) { + this.TagsHandler.createTag( + this.tag.user_id, + 'this is a tag that is very very very very very very long', + err => { + expect(err.message).to.equal('Exceeded max tag length') + done() + } + ) + }) + }) + describe('when insert has duplicate key error error', function () { beforeEach(function () { this.duplicateKeyError = new Error('Duplicate') @@ -255,5 +268,19 @@ describe('TagsHandler', function () { ) }) }) + + describe('when tag is too long', function () { + it('should throw an error', function (done) { + this.TagsHandler.renameTag( + this.userId, + this.tagId, + 'this is a tag that is very very very very very very long', + err => { + expect(err.message).to.equal('Exceeded max tag length') + done() + } + ) + }) + }) }) })