Merge branch '19294-sharing-dialog-overflow' into main. Closes #19294
[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     if (tagKeyID && vocabulary.tags[tagKeyID] && vocabulary.tags[tagKeyID].values) {
50         const values = vocabulary.tags[tagKeyID].values!;
51         return Object.keys(values).find(k =>
52             (k.toLowerCase() === tagValueLabel.toLowerCase())
53             || values[k].labels.find(
54                 l => l.label.toLowerCase() === tagValueLabel.toLowerCase()) !== undefined)
55             || '';
56     };
57     return '';
58 };
59
60 export const getTagValueLabel = (tagKeyID:string, tagValueID:string, vocabulary: Vocabulary) =>
61     vocabulary.tags[tagKeyID] &&
62     vocabulary.tags[tagKeyID].values &&
63     vocabulary.tags[tagKeyID].values![tagValueID] &&
64     vocabulary.tags[tagKeyID].values![tagValueID].labels.length > 0
65         ? vocabulary.tags[tagKeyID].values![tagValueID].labels[0].label
66         : tagValueID;
67
68 const compare = (a: PropFieldSuggestion, b: PropFieldSuggestion) => {
69     if (a.label < b.label) {return -1;}
70     if (a.label > b.label) {return 1;}
71     return 0;
72 };
73
74 export const getTagValues = (tagKeyID: string, vocabulary: Vocabulary): PropFieldSuggestion[] => {
75     const tag = vocabulary.tags[tagKeyID];
76     return tag && tag.values
77         ? Object.keys(tag.values).map(
78             tagValueID => tag.values![tagValueID].labels && tag.values![tagValueID].labels.length > 0
79                 ? tag.values![tagValueID].labels.map(
80                     lbl => Object.assign({}, {"id": tagValueID, "label": lbl.label}))
81                 : [{"id": tagValueID, "label": tagValueID}])
82             .reduce((prev, curr) => [...prev, ...curr], [])
83             .sort(compare)
84         : [];
85 };
86
87 export const getPreferredTagValues = (tagKeyID: string, vocabulary: Vocabulary, withMatch?: string): PropFieldSuggestion[] => {
88     const tag = vocabulary.tags[tagKeyID];
89     const regex = !!withMatch ? new RegExp(escapeRegExp(withMatch), 'i') : undefined;
90     return tag && tag.values
91         ? Object.keys(tag.values).map(
92             tagValueID => tag.values![tagValueID].labels && tag.values![tagValueID].labels.length > 0
93                 ? {
94                     "id": tagValueID,
95                     "label": tag.values![tagValueID].labels[0].label,
96                     "synonyms": !!withMatch && tag.values![tagValueID].labels.length > 1
97                         ? tag.values![tagValueID].labels.slice(1)
98                             .filter(l => !!regex ? regex.test(l.label) : true)
99                             .map(l => l.label)
100                         : []
101                 }
102                 : {"id": tagValueID, "label": tagValueID, "synonyms": []})
103             .sort(compare)
104         : [];
105 };
106
107 export const getTags = ({ tags }: Vocabulary): PropFieldSuggestion[] => {
108     return tags && Object.keys(tags)
109         ? Object.keys(tags).map(
110             tagID => tags[tagID].labels && tags[tagID].labels.length > 0
111                 ? tags[tagID].labels.map(
112                     lbl => Object.assign({}, {"id": tagID, "label": lbl.label}))
113                 : [{"id": tagID, "label": tagID}])
114             .reduce((prev, curr) => [...prev, ...curr], [])
115             .sort(compare)
116         : [];
117 };
118
119 export const getPreferredTags = ({ tags }: Vocabulary, withMatch?: string): PropFieldSuggestion[] => {
120     const regex = !!withMatch ? new RegExp(escapeRegExp(withMatch), 'i') : undefined;
121     return tags && Object.keys(tags)
122         ? Object.keys(tags).map(
123             tagID => tags[tagID].labels && tags[tagID].labels.length > 0
124                 ? {
125                     "id": tagID,
126                     "label": tags[tagID].labels[0].label,
127                     "synonyms": !!withMatch && tags[tagID].labels.length > 1
128                         ? tags[tagID].labels.slice(1)
129                                 .filter(l => !!regex ? regex.test(l.label) : true)
130                                 .map(lbl => lbl.label)
131                         : []
132                 }
133                 : {"id": tagID, "label": tagID, "synonyms": []})
134             .sort(compare)
135         : [];
136 };
137
138 export const getTagKeyID = (tagKeyLabel: string, vocabulary: Vocabulary) =>
139     Object.keys(vocabulary.tags).find(k => (k.toLowerCase() === tagKeyLabel.toLowerCase())
140         || vocabulary.tags[k].labels.find(
141             l => l.label.toLowerCase() === tagKeyLabel.toLowerCase()) !== undefined)
142         || '';
143
144 export const getTagKeyLabel = (tagKeyID:string, vocabulary: Vocabulary) =>
145     vocabulary.tags[tagKeyID] && vocabulary.tags[tagKeyID].labels.length > 0
146     ? vocabulary.tags[tagKeyID].labels[0].label
147     : tagKeyID;