eb3494b508d042cd0e2ad701d7d1e697e97d3510
[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 * as 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 } 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.ts';
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={handleBlur(PROPERTY_KEY_FIELD_ID, data.form, props.meta, props.input, getTagKeyID(props.input.value, vocabulary))}
43             onChange={(e: ChangeEvent<HTMLInputElement>) => {
44                 const newValue = e.currentTarget.value;
45                 handleChange(data.form, props.input, props.meta, newValue);
46             }}
47             {...buildProps(props)}
48         />
49     )} />;
50
51 const getValidation = memoize(
52     (vocabulary: Vocabulary) =>
53         vocabulary.strict_tags
54             ? [...TAG_KEY_VALIDATION, matchTags(vocabulary)]
55             : TAG_KEY_VALIDATION);
56
57 const matchTags = (vocabulary: Vocabulary) =>
58     (value: string) =>
59         getTags(vocabulary).find(tag => tag.label === value)
60             ? undefined
61             : 'Incorrect key';
62
63 const getSuggestions = (value: string, vocabulary: Vocabulary) => {
64     const re = new RegExp(escapeRegExp(value), "i");
65     return getTags(vocabulary).filter(tag => re.test(tag.label) && tag.label !== value);
66 };
67
68 const handleChange = (
69     formName: string,
70     { onChange }: WrappedFieldInputProps,
71     { dispatch }: WrappedFieldMetaProps,
72     value: string) => {
73         // Properties' values are dependant on the keys, if any value is
74         // pre-existant, a change on the property key should mean that the
75         // previous value is invalid, so we better reset the whole form before
76         // setting the new tag key.
77         dispatch(reset(formName));
78
79         onChange(value);
80         dispatch(change(formName, PROPERTY_KEY_FIELD_NAME, value));
81     };