From: Lucas Di Pentima Date: Thu, 11 Nov 2021 17:53:00 +0000 (-0300) Subject: Merge branch '17944-vocabulary-endpoint-retrieval' into main. Closes #17944 X-Git-Tag: 2.4.0~32 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/eabdb7bdd468b09c633ddb8b33fd8095ad27bb60?hp=ad17f8efd3c26dda57264156cfc9a80c51c3a639 Merge branch '17944-vocabulary-endpoint-retrieval' into main. Closes #17944 Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima --- diff --git a/README.md b/README.md index 8bb50dbe..4ec4bd1c 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,6 @@ Currently this configuration schema is supported: ``` { "API_HOST": "string", - "VOCABULARY_URL": "string", "FILE_VIEWERS_CONFIG_URL": "string", } ``` @@ -93,12 +92,6 @@ The Arvados base URL. The `REACT_APP_ARVADOS_API_HOST` environment variable can be used to set the default URL if the run time configuration is unreachable. -### VOCABULARY_URL -Local path, or any URL that allows cross-origin requests. See -[Vocabulary JSON file example](public/vocabulary-example.json). - -To use the URL defined in the Arvados cluster configuration, remove the entire `VOCABULARY_URL` entry from the runtime configuration. Found in `/config.json` by default. - ## FILE_VIEWERS_CONFIG_URL Local path, or any URL that allows cross-origin requests. See: diff --git a/cypress/integration/collection.spec.js b/cypress/integration/collection.spec.js index fb126af6..3e06d7e5 100644 --- a/cypress/integration/collection.spec.js +++ b/cypress/integration/collection.spec.js @@ -97,14 +97,37 @@ describe('Collection panel tests', function () { }); // Confirm proper vocabulary labels are displayed on the UI. cy.get('[data-cy=collection-properties-panel]') - .should('contain', 'Color') - .and('contain', 'Magenta'); + .should('contain', 'Color: Magenta'); // Confirm proper vocabulary IDs were saved on the backend. cy.doRequest('GET', `/arvados/v1/collections/${this.testCollection.uuid}`) .its('body').as('collection') .then(function () { expect(this.collection.properties.IDTAGCOLORS).to.equal('IDVALCOLORS3'); }); + + // Case-insensitive on-blur auto-selection test + // Key: Size (IDTAGSIZES) - Value: Small (IDVALSIZES2) + cy.get('[data-cy=resource-properties-form]').within(() => { + cy.get('[data-cy=property-field-key]').within(() => { + cy.get('input').type('sIzE'); + }); + cy.get('[data-cy=property-field-value]').within(() => { + cy.get('input').type('sMaLL'); + }); + // Cannot "type()" TAB on Cypress so let's click another field + // to trigger the onBlur event. + cy.get('[data-cy=property-field-key]').click(); + cy.root().submit(); + }); + // Confirm proper vocabulary labels are displayed on the UI. + cy.get('[data-cy=collection-properties-panel]') + .should('contain', 'Size: S'); + // Confirm proper vocabulary IDs were saved on the backend. + cy.doRequest('GET', `/arvados/v1/collections/${this.testCollection.uuid}`) + .its('body').as('collection') + .then(function () { + expect(this.collection.properties.IDTAGSIZES).to.equal('IDVALSIZES2'); + }); }); }); diff --git a/src/common/config.ts b/src/common/config.ts index 56f7c488..2518c95e 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -51,7 +51,6 @@ export interface ClusterConfigJSON { }; Workbench: { ArvadosDocsite: string; - VocabularyURL: string; FileViewersConfigURL: string; WelcomePageHTML: string; InactivePageHTML: string; @@ -204,15 +203,10 @@ remove the entire ${varName} entry from ${WORKBENCH_CONFIG_URL}`); } config.fileViewersConfigUrl = fileViewerConfigUrl; - let vocabularyUrl; if (workbenchConfig.VOCABULARY_URL !== undefined) { - warnLocalConfig("VOCABULARY_URL"); - vocabularyUrl = workbenchConfig.VOCABULARY_URL; + console.warn(`A value for VOCABULARY_URL was found in ${WORKBENCH_CONFIG_URL}. It will be ignored as the cluster already provides its own endpoint, you can safely remove it.`) } - else { - vocabularyUrl = config.clusterConfig.Workbench.VocabularyURL || "/vocabulary-example.json"; - } - config.vocabularyUrl = vocabularyUrl; + config.vocabularyUrl = getVocabularyURL(workbenchConfig.API_HOST); return { config, apiHost: workbenchConfig.API_HOST }; }); @@ -240,7 +234,6 @@ export const mockClusterConfigJSON = (config: Partial): Clust }, Workbench: { ArvadosDocsite: "", - VocabularyURL: "", FileViewersConfigURL: "", WelcomePageHTML: "", InactivePageHTML: "", @@ -315,5 +308,7 @@ const getDefaultConfig = (): WorkbenchConfig => { export const ARVADOS_API_PATH = "arvados/v1"; export const CLUSTER_CONFIG_PATH = "arvados/v1/config"; +export const VOCABULARY_PATH = "arvados/v1/vocabulary"; export const DISCOVERY_DOC_PATH = "discovery/v1/apis/arvados/v1/rest"; -export const getClusterConfigURL = (apiHost: string) => `${window.location.protocol}//${apiHost}/${CLUSTER_CONFIG_PATH}?nocache=${(new Date()).getTime()}`; +export const getClusterConfigURL = (apiHost: string) => `https://${apiHost}/${CLUSTER_CONFIG_PATH}?nocache=${(new Date()).getTime()}`; +export const getVocabularyURL = (apiHost: string) => `https://${apiHost}/${VOCABULARY_PATH}?nocache=${(new Date()).getTime()}`; diff --git a/src/models/vocabulary.ts b/src/models/vocabulary.ts index 03f28c07..3c542844 100644 --- a/src/models/vocabulary.ts +++ b/src/models/vocabulary.ts @@ -47,7 +47,7 @@ export const getTagValueID = (tagKeyID:string, tagValueLabel:string, vocabulary: (tagKeyID && vocabulary.tags[tagKeyID] && vocabulary.tags[tagKeyID].values) ? Object.keys(vocabulary.tags[tagKeyID].values!).find( k => vocabulary.tags[tagKeyID].values![k].labels.find( - l => l.label === tagValueLabel) !== undefined) || '' + l => l.label.toLowerCase() === tagValueLabel.toLowerCase()) !== undefined) || '' : ''; export const getTagValueLabel = (tagKeyID:string, tagValueID:string, vocabulary: Vocabulary) => @@ -94,7 +94,7 @@ export const getTags = ({ tags }: Vocabulary) => { export const getTagKeyID = (tagKeyLabel:string, vocabulary: Vocabulary) => Object.keys(vocabulary.tags).find( k => vocabulary.tags[k].labels.find( - l => l.label === tagKeyLabel) !== undefined + l => l.label.toLowerCase() === tagKeyLabel.toLowerCase()) !== undefined ) || ''; export const getTagKeyLabel = (tagKeyID:string, vocabulary: Vocabulary) => diff --git a/src/services/vocabulary-service/vocabulary-service.ts b/src/services/vocabulary-service/vocabulary-service.ts index ff2de159..38163f77 100644 --- a/src/services/vocabulary-service/vocabulary-service.ts +++ b/src/services/vocabulary-service/vocabulary-service.ts @@ -10,9 +10,9 @@ export class VocabularyService { private url: string ) { } - getVocabulary() { - return Axios - .get(this.url) - .then(response => response.data); + async getVocabulary() { + const response = await Axios + .get(this.url); + return response.data; } } diff --git a/src/store/vocabulary/vocabulary-actions.ts b/src/store/vocabulary/vocabulary-actions.ts index 2ca344bb..d73c01fe 100644 --- a/src/store/vocabulary/vocabulary-actions.ts +++ b/src/store/vocabulary/vocabulary-actions.ts @@ -10,7 +10,6 @@ import { isVocabulary } from 'models/vocabulary'; export const loadVocabulary = async (dispatch: Dispatch, _: {}, { vocabularyService }: ServiceRepository) => { const vocabulary = await vocabularyService.getVocabulary(); - dispatch(propertiesActions.SET_PROPERTY({ key: VOCABULARY_PROPERTY_NAME, value: isVocabulary(vocabulary) diff --git a/src/views-components/resource-properties-form/property-key-field.tsx b/src/views-components/resource-properties-form/property-key-field.tsx index 029d44cc..791949f5 100644 --- a/src/views-components/resource-properties-form/property-key-field.tsx +++ b/src/views-components/resource-properties-form/property-key-field.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { WrappedFieldProps, Field, FormName, reset, change, WrappedFieldInputProps, WrappedFieldMetaProps } from 'redux-form'; import { memoize } from 'lodash'; import { Autocomplete } from 'components/autocomplete/autocomplete'; -import { Vocabulary, getTags, getTagKeyID } from 'models/vocabulary'; +import { Vocabulary, getTags, getTagKeyID, getTagKeyLabel } from 'models/vocabulary'; import { handleSelect, handleBlur, @@ -39,7 +39,14 @@ const PropertyKeyInput = ({ vocabulary, ...props }: WrappedFieldProps & Vocabula label='Key' suggestions={getSuggestions(props.input.value, vocabulary)} onSelect={handleSelect(PROPERTY_KEY_FIELD_ID, data.form, props.input, props.meta)} - onBlur={handleBlur(PROPERTY_KEY_FIELD_ID, data.form, props.meta, props.input, getTagKeyID(props.input.value, vocabulary))} + onBlur={() => { + // Case-insensitive search for the key in the vocabulary + const foundKeyID = getTagKeyID(props.input.value, vocabulary); + if (foundKeyID !== '') { + props.input.value = getTagKeyLabel(foundKeyID, vocabulary); + } + handleBlur(PROPERTY_KEY_FIELD_ID, data.form, props.meta, props.input, foundKeyID)(); + }} onChange={(e: ChangeEvent) => { const newValue = e.currentTarget.value; handleChange(data.form, props.input, props.meta, newValue); diff --git a/src/views-components/resource-properties-form/property-value-field.tsx b/src/views-components/resource-properties-form/property-value-field.tsx index a2b53b3c..b023e412 100644 --- a/src/views-components/resource-properties-form/property-value-field.tsx +++ b/src/views-components/resource-properties-form/property-value-field.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { WrappedFieldProps, Field, formValues, FormName, WrappedFieldInputProps, WrappedFieldMetaProps, change } from 'redux-form'; import { compose } from 'redux'; import { Autocomplete } from 'components/autocomplete/autocomplete'; -import { Vocabulary, isStrictTag, getTagValues, getTagValueID } from 'models/vocabulary'; +import { Vocabulary, isStrictTag, getTagValues, getTagValueID, getTagValueLabel } from 'models/vocabulary'; import { PROPERTY_KEY_FIELD_ID, PROPERTY_KEY_FIELD_NAME } from 'views-components/resource-properties-form/property-key-field'; import { handleSelect, @@ -60,7 +60,14 @@ const PropertyValueInput = ({ vocabulary, propertyKeyId, propertyKeyName, ...pro disabled={props.disabled} suggestions={getSuggestions(props.input.value, propertyKeyId, vocabulary)} onSelect={handleSelect(PROPERTY_VALUE_FIELD_ID, data.form, props.input, props.meta)} - onBlur={handleBlur(PROPERTY_VALUE_FIELD_ID, data.form, props.meta, props.input, getTagValueID(propertyKeyId, props.input.value, vocabulary))} + onBlur={() => { + // Case-insensitive search for the value in the vocabulary + const foundValueID = getTagValueID(propertyKeyId, props.input.value, vocabulary); + if (foundValueID !== '') { + props.input.value = getTagValueLabel(propertyKeyId, foundValueID, vocabulary); + } + handleBlur(PROPERTY_VALUE_FIELD_ID, data.form, props.meta, props.input, foundValueID)(); + }} onChange={(e: ChangeEvent) => { const newValue = e.currentTarget.value; const tagValueID = getTagValueID(propertyKeyId, newValue, vocabulary); diff --git a/tools/arvados_config.yml b/tools/arvados_config.yml index 369046e6..55dc8a02 100644 --- a/tools/arvados_config.yml +++ b/tools/arvados_config.yml @@ -4,6 +4,7 @@ Clusters: SystemRootToken: systemusertesttoken1234567890aoeuidhtnsqjkxbmwvzpy API: RequestTimeout: 30s + VocabularyPath: "" TLS: Insecure: true Collections: diff --git a/public/vocabulary-example.json b/tools/example-vocabulary.json similarity index 100% rename from public/vocabulary-example.json rename to tools/example-vocabulary.json diff --git a/tools/run-integration-tests.sh b/tools/run-integration-tests.sh index 159bfc1c..bf4c3ba4 100755 --- a/tools/run-integration-tests.sh +++ b/tools/run-integration-tests.sh @@ -70,6 +70,7 @@ echo "ARVADOS_DIR is ${ARVADOS_DIR}" ARVADOS_LOG=${ARVADOS_DIR}/arvados.log ARVADOS_CONF=${WB2_DIR}/tools/arvados_config.yml +VOCABULARY_CONF=${WB2_DIR}/tools/example-vocabulary.json if [ ! -f "${WB2_DIR}/src/index.tsx" ]; then echo "ERROR: '${WB2_DIR}' isn't workbench2's directory" @@ -104,6 +105,9 @@ echo "Installing dev dependencies..." ~/go/bin/arvados-server install -type test || exit 1 echo "Launching arvados in test mode..." +VOC_DIR=$(mktemp -d | cut -d \/ -f3) # Removes the /tmp/ part +cp ${VOCABULARY_CONF} /tmp/${VOC_DIR}/voc.json +sed -i "s/VocabularyPath: \".*\"/VocabularyPath: \"\/tmp\/${VOC_DIR}\/voc.json\"/" ${ARVADOS_CONF} coproc arvboot (~/go/bin/arvados-server boot \ -type test \ -config ${ARVADOS_CONF} \