From 2bdceb0b32ce49519ffa403919e16ccd06074c9e Mon Sep 17 00:00:00 2001 From: Alejandro Hernandez Date: Mon, 1 Apr 2024 16:00:05 -0400 Subject: [PATCH] Allow some special characters on TagsQuery component --- .../src/example-types/filters/tagsQuery.js | 4 +- .../exampleTypes/ExpandableTagsQuery/index.js | 9 +-- .../react/src/greyVest/ExpandableTagsInput.js | 28 +++------ packages/react/src/greyVest/TagsInput.js | 44 +++----------- .../react/src/greyVest/TagsInput.stories.js | 25 ++++++++ packages/react/src/greyVest/utils.js | 59 +++++++++++-------- 6 files changed, 81 insertions(+), 88 deletions(-) create mode 100644 packages/react/src/greyVest/TagsInput.stories.js diff --git a/packages/provider-elasticsearch/src/example-types/filters/tagsQuery.js b/packages/provider-elasticsearch/src/example-types/filters/tagsQuery.js index 90ff4e6ec..f74b9d0f3 100644 --- a/packages/provider-elasticsearch/src/example-types/filters/tagsQuery.js +++ b/packages/provider-elasticsearch/src/example-types/filters/tagsQuery.js @@ -33,7 +33,9 @@ let addQuotesAndDistance = _.curry((tag, text) => { let replaceReservedChars = _.flow( _.toString, // Replace characters with white space ` ` - _.replace(/([+\-=&|!(){}[\]^"~*?:\\/<>;,$'])/g, ' ') + _.replace(/([&|!(){}[\]^"~*?\\<>;,$'])/g, ' '), + // Escape these characters + _.replace(/([+\-=:/])/g, '\\$1') ) let tagToQueryString = (tag) => { diff --git a/packages/react/src/exampleTypes/ExpandableTagsQuery/index.js b/packages/react/src/exampleTypes/ExpandableTagsQuery/index.js index f045af4fb..655ca55e0 100644 --- a/packages/react/src/exampleTypes/ExpandableTagsQuery/index.js +++ b/packages/react/src/exampleTypes/ExpandableTagsQuery/index.js @@ -18,7 +18,7 @@ import ActionsMenu from '../TagsQuery/ActionsMenu.js' import { useOutsideClick } from '@chakra-ui/react-use-outside-click' import { sanitizeTagInputs } from 'contexture-elasticsearch/utils/keywordGenerations.js' import KeywordGenerations from './KeywordGenerations.js' -import { wordRegex, wordRegexWithDot } from '../../greyVest/utils.js' +import { sanitizeQueryStringTag } from '../../greyVest/utils.js' let innerHeightLimit = 40 @@ -133,10 +133,6 @@ let TagsWrapper = observer( popoverOffsetY, theme: { Icon, TagsInput, Tag, Popover }, joinOptions, - // Allow tag keywords to contain dots in them if the user is searching for exact - // words instead of their variations. - wordsMatchPattern = node.exact ? wordRegexWithDot : wordRegex, - sanitizeTags = true, splitCommas = true, maxTags = 1000, hasPopover, @@ -195,9 +191,8 @@ let TagsWrapper = observer( 0 ? _.map(tagValueField, node.tags) diff --git a/packages/react/src/greyVest/ExpandableTagsInput.js b/packages/react/src/greyVest/ExpandableTagsInput.js index 239fca789..9dc7e5ea4 100644 --- a/packages/react/src/greyVest/ExpandableTagsInput.js +++ b/packages/react/src/greyVest/ExpandableTagsInput.js @@ -2,7 +2,7 @@ import React from 'react' import _ from 'lodash/fp.js' import { observer } from 'mobx-react' import { Tag as DefaultTag, Flex } from './index.js' -import { sanitizeTagWords, splitTagOnComma, wordRegex } from './utils.js' +import { createTags } from './utils.js' export let Tags = ({ reverse = false, @@ -38,7 +38,7 @@ let isValidInput = (tag, tags) => !_.isEmpty(tag) && !_.includes(tag, tags) let ExpandableTagsInput = ({ tags, - addTags, + addTags: setTags, removeTag, submit = _.noop, tagStyle, @@ -48,30 +48,18 @@ let ExpandableTagsInput = ({ autoFocus, onBlur = _.noop, onInputChange = _.noop, - maxWordsPerTag = 100, - maxCharsPerTagWord = 100, - wordsMatchPattern = wordRegex, onTagClick = _.noop, - sanitizeTags = true, + sanitizeTagFn, Tag = DefaultTag, ...props }) => { - let sanitizeTagFn = sanitizeTagWords( - wordsMatchPattern, - maxWordsPerTag, - maxCharsPerTagWord - ) - - addTags = _.flow( - _.trim, - (tags) => (splitCommas ? splitTagOnComma(tags) : _.castArray(tags)), - (tags) => (sanitizeTags ? _.map(sanitizeTagFn, tags) : tags), - _.difference(_, tags), - addTags - ) - let [currentInput, setCurrentInput] = React.useState('') + let addTags = (input) => { + let newTags = createTags({ input, splitCommas, sanitizeTagFn }) + setTags(_.difference(newTags, tags)) + } + return (
diff --git a/packages/react/src/greyVest/TagsInput.js b/packages/react/src/greyVest/TagsInput.js index 801ab42b3..fe3d2d071 100644 --- a/packages/react/src/greyVest/TagsInput.js +++ b/packages/react/src/greyVest/TagsInput.js @@ -1,10 +1,9 @@ import React, { forwardRef } from 'react' import _ from 'lodash/fp.js' -import { observable } from '../utils/mobx.js' -import { observer, inject } from 'mobx-react' +import { observer } from 'mobx-react' import Flex from './Flex.js' import DefaultTag from './Tag.js' -import { sanitizeTagWords, splitTagOnComma, wordRegex } from './utils.js' +import { createTags } from './utils.js' let isValidInput = (tag, tags) => !_.isEmpty(tag) && !_.includes(tag, tags) @@ -12,7 +11,7 @@ let TagsInput = forwardRef( ( { tags, - addTags, + addTags: setTags, removeTag, submit = _.noop, tagStyle, @@ -22,10 +21,7 @@ let TagsInput = forwardRef( onBlur = _.noop, onInputChange = _.noop, onTagClick = _.noop, - maxWordsPerTag = 100, - maxCharsPerTagWord = 100, - wordsMatchPattern = wordRegex, - sanitizeTags = true, + sanitizeTagFn, Tag = DefaultTag, ...props }, @@ -34,19 +30,10 @@ let TagsInput = forwardRef( let containerRef = React.useRef() let [currentInput, setCurrentInput] = React.useState('') - let sanitizeTagFn = sanitizeTagWords( - wordsMatchPattern, - maxWordsPerTag, - maxCharsPerTagWord - ) - - addTags = _.flow( - _.trim, - (tags) => (splitCommas ? splitTagOnComma(tags) : _.castArray(tags)), - (tags) => (sanitizeTags ? _.map(sanitizeTagFn, tags) : tags), - _.difference(_, tags), - addTags - ) + let addTags = (input) => { + let newTags = createTags({ input, splitCommas, sanitizeTagFn }) + setTags(_.difference(newTags, tags)) + } return (
@@ -118,19 +105,4 @@ let TagsInput = forwardRef( } ) -// Just uses an internal observable array -export let MockTagsInput = inject(() => { - let tags = observable([]) - return { - tags, - addTags(tag) { - tags.push(tag) - }, - removeTag(tag) { - tags = _.without(tag, tags) - }, - } -})(TagsInput) -MockTagsInput.displayName = 'MockTagsInput' - export default observer(TagsInput) diff --git a/packages/react/src/greyVest/TagsInput.stories.js b/packages/react/src/greyVest/TagsInput.stories.js new file mode 100644 index 000000000..43ef0dd09 --- /dev/null +++ b/packages/react/src/greyVest/TagsInput.stories.js @@ -0,0 +1,25 @@ +import _ from 'lodash/fp.js' +import React from 'react' +import TagsInput from './TagsInput.js' + +export default { + component: TagsInput, +} + +export const Default = () => { + let [tags, setTags] = React.useState([ + 'janitor', + 'soap', + 'cleaner', + 'cleaning', + 'clean', + ]) + return ( + setTags((current) => _.union(tags, current))} + removeTag={(tag) => setTags((current) => _.pull(tag, current))} + tagStyle={{ background: 'lavender' }} + /> + ) +} diff --git a/packages/react/src/greyVest/utils.js b/packages/react/src/greyVest/utils.js index ad7a3d6c8..24a4e88d9 100644 --- a/packages/react/src/greyVest/utils.js +++ b/packages/react/src/greyVest/utils.js @@ -6,36 +6,47 @@ export let openBinding = (...lens) => ({ onClose: F.off(...lens), }) +let maxWordsPerTag = 100 +let maxCharsPerTagWord = 100 + +// Strip these characters when splitting a tag into words. We do this to display +// tags that are closer to what we send to elastic in a `query_string` query. +// +// See https://github.com/smartprocure/contexture-elasticsearch/pull/170 +// +// If in doubt, make a request to the `/{index}/analyze` elasticsearch endpoint +// to see exactly which characters get stripped out of text. +let wordRegex = /[^|> { - let words = _.words.convert({ fixed: false }) - return _.flow( - (string) => words(string, wordsMatchPattern), - _.take(maxWordsPerTag), - _.map((word) => - _.flow( - _.truncate({ length: maxCharsPerTagWord, omission: '' }), - // Remove beginning of line dash and space dash - _.replace(/^-| -/g, ' '), - _.trim - )(word) - ), - _.join(' ') - ) -} +export let sanitizeQueryStringTag = _.flow( + (string) => words(string, wordRegex), + _.take(maxWordsPerTag), + _.map((word) => + _.flow( + _.truncate({ length: maxCharsPerTagWord, omission: '' }), + // Remove beginning of line dash and space dash + // https://github.com/smartprocure/spark/issues/10923 + _.replace(/^-| -/g, ' '), + _.trim + )(word) + ), + _.join(' ') +) // Split a tag on comma into unique words -export let splitTagOnComma = _.flow( - _.trim, +let splitTagOnComma = _.flow( _.split(','), _.invokeMap('trim'), _.compact, _.uniq ) -export let wordRegex = /[-\w]+/g -export let wordRegexWithDot = /[-.\w]+/g +export let createTags = ({ input, splitCommas, sanitizeTagFn }) => + _.flow( + _.trim, + splitCommas ? splitTagOnComma : _.identity, + _.castArray, + sanitizeTagFn ? _.map(sanitizeTagFn) : _.identity + )(input)