Merge branch '17754-federated-acct-merge'. Closes #17754.
[arvados-workbench2.git] / src / models / vocabulary.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { escapeRegExp } from 'common/regexp';
6 import { isObject, has, every } from 'lodash/fp';
7
8 export interface Vocabulary {
9     strict_tags: boolean;
10     tags: Record<string, Tag>;
11 }
12
13 export interface Label {
14     lang?: string;
15     label: string;
16 }
17
18 export interface TagValue {
19     labels: Label[];
20 }
21
22 export interface Tag {
23     strict?: boolean;
24     labels: Label[];
25     values?: Record<string, TagValue>;
26 }
27
28 export interface PropFieldSuggestion {
29     id: string;
30     label: string;
31     synonyms?: string[];
32 }
33
34 const VOCABULARY_VALIDATORS = [
35     isObject,
36     has('strict_tags'),
37     has('tags'),
38 ];
39
40 export const isVocabulary = (value: any) =>
41     every(validator => validator(value), VOCABULARY_VALIDATORS);
42
43 export const isStrictTag = (tagKeyID: string, vocabulary: Vocabulary) => {
44     const tag = vocabulary.tags[tagKeyID];
45     return tag ? tag.strict : false;
46 };
47
48 export const getTagValueID = (tagKeyID:string, tagValueLabel:string, vocabulary: Vocabulary) =>
49     (tagKeyID && vocabulary.tags[tagKeyID] && vocabulary.tags[tagKeyID].values)
50     ? Object.keys(vocabulary.tags[tagKeyID].values!).find(
51         k => vocabulary.tags[tagKeyID].values![k].labels.find(
52             l => l.label.toLowerCase() === tagValueLabel.toLowerCase()) !== undefined) || ''
53     : '';
54
55 export const getTagValueLabel = (tagKeyID:string, tagValueID:string, vocabulary: Vocabulary) =>
56     vocabulary.tags[tagKeyID] &&
57     vocabulary.tags[tagKeyID].values &&
58     vocabulary.tags[tagKeyID].values![tagValueID] &&
59     vocabulary.tags[tagKeyID].values![tagValueID].labels.length > 0
60         ? vocabulary.tags[tagKeyID].values![tagValueID].labels[0].label
61         : tagValueID;
62
63 const compare = (a: PropFieldSuggestion, b: PropFieldSuggestion) => {
64     if (a.label < b.label) {return -1;}
65     if (a.label > b.label) {return 1;}
66     return 0;
67 };
68
69 export const getTagValues = (tagKeyID: string, vocabulary: Vocabulary): PropFieldSuggestion[] => {
70     const tag = vocabulary.tags[tagKeyID];
71     return tag && tag.values
72         ? Object.keys(tag.values).map(
73             tagValueID => tag.values![tagValueID].labels && tag.values![tagValueID].labels.length > 0
74                 ? tag.values![tagValueID].labels.map(
75                     lbl => Object.assign({}, {"id": tagValueID, "label": lbl.label}))
76                 : [{"id": tagValueID, "label": tagValueID}])
77             .reduce((prev, curr) => [...prev, ...curr], [])
78             .sort(compare)
79         : [];
80 };
81
82 export const getPreferredTagValues = (tagKeyID: string, vocabulary: Vocabulary, withMatch?: string): PropFieldSuggestion[] => {
83     const tag = vocabulary.tags[tagKeyID];
84     const regex = !!withMatch ? new RegExp(escapeRegExp(withMatch), 'i') : undefined;
85     return tag && tag.values
86         ? Object.keys(tag.values).map(
87             tagValueID => tag.values![tagValueID].labels && tag.values![tagValueID].labels.length > 0
88                 ? {
89                     "id": tagValueID,
90                     "label": tag.values![tagValueID].labels[0].label,
91                     "synonyms": !!withMatch && tag.values![tagValueID].labels.length > 1
92                         ? tag.values![tagValueID].labels.slice(1)
93                             .filter(l => !!regex ? regex.test(l.label) : true)
94                             .map(l => l.label)
95                         : []
96                 }
97                 : {"id": tagValueID, "label": tagValueID, "synonyms": []})
98             .sort(compare)
99         : [];
100 };
101
102 export const getTags = ({ tags }: Vocabulary): PropFieldSuggestion[] => {
103     return tags && Object.keys(tags)
104         ? Object.keys(tags).map(
105             tagID => tags[tagID].labels && tags[tagID].labels.length > 0
106                 ? tags[tagID].labels.map(
107                     lbl => Object.assign({}, {"id": tagID, "label": lbl.label}))
108                 : [{"id": tagID, "label": tagID}])
109             .reduce((prev, curr) => [...prev, ...curr], [])
110             .sort(compare)
111         : [];
112 };
113
114 export const getPreferredTags = ({ tags }: Vocabulary, withMatch?: string): PropFieldSuggestion[] => {
115     const regex = !!withMatch ? new RegExp(escapeRegExp(withMatch), 'i') : undefined;
116     return tags && Object.keys(tags)
117         ? Object.keys(tags).map(
118             tagID => tags[tagID].labels && tags[tagID].labels.length > 0
119                 ? {
120                     "id": tagID,
121                     "label": tags[tagID].labels[0].label,
122                     "synonyms": !!withMatch && tags[tagID].labels.length > 1
123                         ? tags[tagID].labels.slice(1)
124                                 .filter(l => !!regex ? regex.test(l.label) : true)
125                                 .map(lbl => lbl.label)
126                         : []
127                 }
128                 : {"id": tagID, "label": tagID, "synonyms": []})
129             .sort(compare)
130         : [];
131 };
132
133 export const getTagKeyID = (tagKeyLabel:string, vocabulary: Vocabulary) =>
134     Object.keys(vocabulary.tags).find(
135         k => vocabulary.tags[k].labels.find(
136             l => l.label.toLowerCase() === tagKeyLabel.toLowerCase()) !== undefined
137         ) || '';
138
139 export const getTagKeyLabel = (tagKeyID:string, vocabulary: Vocabulary) =>
140     vocabulary.tags[tagKeyID] && vocabulary.tags[tagKeyID].labels.length > 0
141     ? vocabulary.tags[tagKeyID].labels[0].label
142     : tagKeyID;