Merge pull request #9614 from overleaf/ab-limit-tag-length

[web] Restrict the length of tags to 50 characters

GitOrigin-RevId: fc20227e3e2171bf9e27c983105ecc7b198cf882
This commit is contained in:
Davinder Singh 2022-09-15 10:59:00 +01:00 committed by Copybot
parent e65ede1d6a
commit 70e63ca0e3
8 changed files with 86 additions and 9 deletions

View file

@ -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,

View file

@ -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": "",

View file

@ -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<Tag>()
const [tagName, setTagName] = useState<string>()
const [validationError, setValidationError] = useState<string>()
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({
</Modal.Body>
<Modal.Footer>
{validationError && (
<div className="modal-footer-left">
<span className="text-danger error">{validationError}</span>
</div>
)}
{isError && (
<div className="modal-footer-left">
<span className="text-danger error">
@ -81,9 +98,11 @@ export default function CreateTagModal({
<Button
onClick={() => runCreateTag()}
bsStyle="primary"
disabled={status === 'pending' || !tagName?.length}
disabled={
status === 'pending' || !tagName?.length || !!validationError
}
>
{status === 'pending' ? t('creating') + '...' : t('create')}
{status === 'pending' ? t('creating') + '' : t('create')}
</Button>
</Modal.Footer>
</AccessibleModal>

View file

@ -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')}
</Button>
</Modal.Footer>
</AccessibleModal>

View file

@ -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<string>()
const [validationError, setValidationError] = useState<string>()
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({
</Modal.Body>
<Modal.Footer>
{validationError && (
<div className="modal-footer-left">
<span className="text-danger error">{validationError}</span>
</div>
)}
{isError && (
<div className="modal-footer-left">
<span className="text-danger error">
@ -87,9 +104,11 @@ export default function RenameTagModal({
<Button
onClick={() => runRenameTag(tag._id)}
bsStyle="primary"
disabled={status === 'pending' || !newTagName?.length}
disabled={
status === 'pending' || !newTagName?.length || !!validationError
}
>
{status === 'pending' ? t('renaming') + '...' : t('rename')}
{status === 'pending' ? t('renaming') + '' : t('rename')}
</Button>
</Modal.Footer>
</AccessibleModal>

View file

@ -0,0 +1 @@
export const MAX_TAG_LENGTH = 50

View file

@ -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 dont have any repositories"
"you_dont_have_any_repositories": "You dont have any repositories",
"tag_name_cannot_exceed_characters": "Tag name cannot exceed __maxLength__ characters"
}

View file

@ -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()
}
)
})
})
})
})