ef51d250e1328512918baab2c8baa94ca9e3376a
[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 { change, WrappedFieldProps, WrappedFieldMetaProps, WrappedFieldInputProps,
7     Field, FormName } from 'redux-form';
8 import { memoize } from 'lodash';
9 import { Autocomplete } from '~/components/autocomplete/autocomplete';
10 import { Vocabulary, getTags, getTagKeyID, PropFieldSuggestion } from '~/models/vocabulary';
11 import { connectVocabulary, VocabularyProp, buildProps } from '~/views-components/resource-properties-form/property-field-common';
12 import { TAG_KEY_VALIDATION } from '~/validators/validators';
13 import { escapeRegExp } from '~/common/regexp.ts';
14
15 export const PROPERTY_KEY_FIELD_NAME = 'key';
16 export const PROPERTY_KEY_FIELD_ID = 'keyID';
17
18 export const PropertyKeyField = connectVocabulary(
19     ({ vocabulary }: VocabularyProp) =>
20         <Field
21             name={PROPERTY_KEY_FIELD_NAME}
22             component={PropertyKeyInput}
23             vocabulary={vocabulary}
24             validate={getValidation(vocabulary)} />
25 );
26
27 export const PropertyKeyInput = ({ vocabulary, ...props }: WrappedFieldProps & VocabularyProp) =>
28     <FormName children={data => (
29         <Autocomplete
30             label='Key'
31             suggestions={getSuggestions(props.input.value, vocabulary)}
32             onSelect={handleSelect(data.form, props.input, props.meta)}
33             {...buildProps(props)}
34             onBlur={handleBlur(data.form, props.meta, props.input, vocabulary)}
35         />
36     )}/>;
37
38 const getValidation = memoize(
39     (vocabulary: Vocabulary) =>
40         vocabulary.strict_tags
41             ? [...TAG_KEY_VALIDATION, matchTags(vocabulary)]
42             : TAG_KEY_VALIDATION);
43
44 const matchTags = (vocabulary: Vocabulary) =>
45     (value: string) =>
46         getTags(vocabulary).find(tag => tag.label === value)
47             ? undefined
48             : 'Incorrect key';
49
50 const getSuggestions = (value: string, vocabulary: Vocabulary) => {
51     const re = new RegExp(escapeRegExp(value), "i");
52     return getTags(vocabulary).filter(tag => re.test(tag.label) && tag.label !== value);
53 };
54
55 // Attempts to match a manually typed key label with a key ID, when the user
56 // doesn't select the key from the suggestions list.
57 const handleBlur = (
58     formName: string,
59     { dispatch }: WrappedFieldMetaProps,
60     { onBlur, value }: WrappedFieldInputProps,
61     vocabulary: Vocabulary) =>
62     () => {
63         dispatch(change(formName, PROPERTY_KEY_FIELD_ID, getTagKeyID(value, vocabulary)));
64         onBlur(value);
65     };
66
67 // When selecting a property key, save its ID for later usage.
68 const handleSelect = (
69     formName: string,
70     { onChange }: WrappedFieldInputProps,
71     { dispatch }: WrappedFieldMetaProps) => {
72         return (item:PropFieldSuggestion) => {
73             onChange(item.label);
74             dispatch(change(formName, PROPERTY_KEY_FIELD_ID, item.id));
75     };
76 };