791949f543fab120457550a0781fa40068127357
[arvados-workbench2.git] / src / views-components / resource-properties-form / property-key-field.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import { WrappedFieldProps, Field, FormName, reset, change, WrappedFieldInputProps, WrappedFieldMetaProps } from 'redux-form';
7 import { memoize } from 'lodash';
8 import { Autocomplete } from 'components/autocomplete/autocomplete';
9 import { Vocabulary, getTags, getTagKeyID, getTagKeyLabel } from 'models/vocabulary';
10 import {
11     handleSelect,
12     handleBlur,
13     connectVocabulary,
14     VocabularyProp,
15     ValidationProp,
16     buildProps
17 } from 'views-components/resource-properties-form/property-field-common';
18 import { TAG_KEY_VALIDATION } from 'validators/validators';
19 import { escapeRegExp } from 'common/regexp';
20 import { ChangeEvent } from 'react';
21
22 export const PROPERTY_KEY_FIELD_NAME = 'key';
23 export const PROPERTY_KEY_FIELD_ID = 'keyID';
24
25 export const PropertyKeyField = connectVocabulary(
26     ({ vocabulary, skipValidation }: VocabularyProp & ValidationProp) =>
27         <span data-cy='property-field-key'>
28         <Field
29             name={PROPERTY_KEY_FIELD_NAME}
30             component={PropertyKeyInput}
31             vocabulary={vocabulary}
32             validate={skipValidation ? undefined : getValidation(vocabulary)} />
33         </span>
34 );
35
36 const PropertyKeyInput = ({ vocabulary, ...props }: WrappedFieldProps & VocabularyProp) =>
37     <FormName children={data => (
38         <Autocomplete
39             label='Key'
40             suggestions={getSuggestions(props.input.value, vocabulary)}
41             onSelect={handleSelect(PROPERTY_KEY_FIELD_ID, data.form, props.input, props.meta)}
42             onBlur={() => {
43                 // Case-insensitive search for the key in the vocabulary
44                 const foundKeyID = getTagKeyID(props.input.value, vocabulary);
45                 if (foundKeyID !== '') {
46                     props.input.value = getTagKeyLabel(foundKeyID, vocabulary);
47                 }
48                 handleBlur(PROPERTY_KEY_FIELD_ID, data.form, props.meta, props.input, foundKeyID)();
49             }}
50             onChange={(e: ChangeEvent<HTMLInputElement>) => {
51                 const newValue = e.currentTarget.value;
52                 handleChange(data.form, props.input, props.meta, newValue);
53             }}
54             {...buildProps(props)}
55         />
56     )} />;
57
58 const getValidation = memoize(
59     (vocabulary: Vocabulary) =>
60         vocabulary.strict_tags
61             ? [...TAG_KEY_VALIDATION, matchTags(vocabulary)]
62             : TAG_KEY_VALIDATION);
63
64 const matchTags = (vocabulary: Vocabulary) =>
65     (value: string) =>
66         getTags(vocabulary).find(tag => tag.label === value)
67             ? undefined
68             : 'Incorrect key';
69
70 const getSuggestions = (value: string, vocabulary: Vocabulary) => {
71     const re = new RegExp(escapeRegExp(value), "i");
72     return getTags(vocabulary).filter(tag => re.test(tag.label) && tag.label !== value);
73 };
74
75 const handleChange = (
76     formName: string,
77     { onChange }: WrappedFieldInputProps,
78     { dispatch }: WrappedFieldMetaProps,
79     value: string) => {
80         // Properties' values are dependant on the keys, if any value is
81         // pre-existant, a change on the property key should mean that the
82         // previous value is invalid, so we better reset the whole form before
83         // setting the new tag key.
84         dispatch(reset(formName));
85
86         onChange(value);
87         dispatch(change(formName, PROPERTY_KEY_FIELD_NAME, value));
88     };