From f90909737a8538495a72dc8175c9e35b9280b9c3 Mon Sep 17 00:00:00 2001 From: MichaelBuessemeyer <39529669+MichaelBuessemeyer@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:33:57 +0100 Subject: [PATCH] Allow dollar in layer name (#8241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * allow dollar in layer and dataset name * only allow $ layer names, not dataset names * add changelog entry * allow $ in annotation layer names * add schema version reversion to db reversion & rename var * fix dataset setting form validation errors * set timeout fixing form validation error to 0 ms --------- Co-authored-by: Michael Büßemeyer --- CHANGELOG.unreleased.md | 1 + .../125-allow-dollar-in-layer-names.sql | 11 +++++++++++ .../125-allow-dollar-in-layer-names.sql | 13 +++++++++++++ .../admin/dataset/dataset_components.tsx | 19 +++++++++++++------ .../dataset/dataset_settings_data_tab.tsx | 5 +++-- .../dataset/dataset_settings_view.tsx | 16 +++++++++++----- .../modals/add_volume_layer_modal.tsx | 4 ++-- tools/postgres/schema.sql | 4 ++-- 8 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 conf/evolutions/125-allow-dollar-in-layer-names.sql create mode 100644 conf/evolutions/reversions/125-allow-dollar-in-layer-names.sql diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index e443b286771..42b34c92b86 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -15,6 +15,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released ### Changed - Renamed "resolution" to "magnification" in more places within the codebase, including local variables. [#8168](https://github.com/scalableminds/webknossos/pull/8168) +- Layer names are now allowed to contain `$` as special characters. [#8241](https://github.com/scalableminds/webknossos/pull/8241) - Datasets can now be renamed and can have duplicate names. [#8075](https://github.com/scalableminds/webknossos/pull/8075) - Improved the default colors for skeleton trees. [#8228](https://github.com/scalableminds/webknossos/pull/8228) - Allowed to train an AI model using differently sized bounding boxes. We recommend all bounding boxes to have equal dimensions or to have dimensions which are multiples of the smallest bounding box. [#8222](https://github.com/scalableminds/webknossos/pull/8222) diff --git a/conf/evolutions/125-allow-dollar-in-layer-names.sql b/conf/evolutions/125-allow-dollar-in-layer-names.sql new file mode 100644 index 00000000000..5600a13d853 --- /dev/null +++ b/conf/evolutions/125-allow-dollar-in-layer-names.sql @@ -0,0 +1,11 @@ +START TRANSACTION; + +do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 124, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql; + + +ALTER TABLE webknossos.annotation_layers DROP CONSTRAINT IF EXISTS annotation_layers_name_check; +ALTER TABLE webknossos.annotation_layers ADD CONSTRAINT annotation_layers_name_check CHECK (name ~* '^[A-Za-z0-9\-_\.\$]+$'); + +UPDATE webknossos.releaseInformation SET schemaVersion = 125; + +COMMIT TRANSACTION; diff --git a/conf/evolutions/reversions/125-allow-dollar-in-layer-names.sql b/conf/evolutions/reversions/125-allow-dollar-in-layer-names.sql new file mode 100644 index 00000000000..cff9aa20a7c --- /dev/null +++ b/conf/evolutions/reversions/125-allow-dollar-in-layer-names.sql @@ -0,0 +1,13 @@ +START TRANSACTION; + +-- This reversion might take a while because it needs to search in all annotation layer names for '$' and replace it with '' +do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 125, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql; + +UPDATE webknossos.annotation_layers SET name = regexp_replace(name, '\$', '', 'g') WHERE name ~* '\$'; + +ALTER TABLE webknossos.annotation_layers DROP CONSTRAINT IF EXISTS annotation_layers_name_check; +ALTER TABLE webknossos.annotation_layers ADD CONSTRAINT annotation_layers_name_check CHECK (name ~* '^[A-Za-z0-9\-_\.]+$'); + +UPDATE webknossos.releaseInformation SET schemaVersion = 124; + +COMMIT TRANSACTION; diff --git a/frontend/javascripts/admin/dataset/dataset_components.tsx b/frontend/javascripts/admin/dataset/dataset_components.tsx index ccbe40d7a30..d6348b46942 100644 --- a/frontend/javascripts/admin/dataset/dataset_components.tsx +++ b/frontend/javascripts/admin/dataset/dataset_components.tsx @@ -35,15 +35,10 @@ export function CardContainer({ ); } } -export const layerNameRules = [ +const sharedRules = [ { min: 1, }, - // Note that these rules are also checked by the backend - { - pattern: /^[0-9a-zA-Z_.-]+$/, - message: "Only letters, digits and the following characters are allowed: . _ -", - }, { validator: syncValidator( (value: string | null) => !value || !value.startsWith("."), @@ -52,6 +47,14 @@ export const layerNameRules = [ }, ]; +export const layerNameRules = [ + ...sharedRules, + { + pattern: /^[0-9a-zA-Z_.\-$.]+$/, + message: "Only letters, digits and the following characters are allowed: . _ - $", + }, +]; + export const getDatasetNameRules = (activeUser: APIUser | null | undefined) => [ { required: true, @@ -59,6 +62,10 @@ export const getDatasetNameRules = (activeUser: APIUser | null | undefined) => [ }, { min: 3, message: messages["dataset.name_length"] }, ...layerNameRules, + { + pattern: /^[0-9a-zA-Z_.-]+$/, + message: "Only letters, digits and the following characters are allowed: . _ -", + }, { validator: async () => { if (!activeUser) throw new Error("Can't do operation if no user is logged in."); diff --git a/frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx b/frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx index 2650c4e1367..e8bbe2aee2a 100644 --- a/frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx +++ b/frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx @@ -398,8 +398,9 @@ function SimpleLayerForm({ { validator: syncValidator( (value: string) => - dataLayers.filter((someLayer: APIDataLayer) => someLayer.name === value) - .length <= 1, + form + .getFieldValue(["dataSource", "dataLayers"]) + .filter((someLayer: APIDataLayer) => someLayer.name === value).length <= 1, "Layer names must be unique.", ), }, diff --git a/frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx b/frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx index 4962055491d..da35dcef146 100644 --- a/frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx +++ b/frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx @@ -339,12 +339,18 @@ class DatasetSettingsView extends React.PureComponent + const afterForceUpdateCallback = () => { // Trigger validation manually, because fields may have been updated - form - .validateFields() - .then((formValues) => this.submit(formValues)) - .catch((errorInfo) => this.handleValidationFailed(errorInfo)); + // and defer the validation as it is done asynchronously by antd or so. + setTimeout( + () => + form + .validateFields() + .then((formValues) => this.submit(formValues)) + .catch((errorInfo) => this.handleValidationFailed(errorInfo)), + 0, + ); + }; // Need to force update of the SimpleAdvancedDataForm as removing a layer in the advanced tab does not update // the form items in the simple tab (only the values are updated). The form items automatically update once diff --git a/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx b/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx index 29cf3d02ad3..fad6a364a53 100644 --- a/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx +++ b/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx @@ -48,9 +48,9 @@ export function checkLayerNameForInvalidCharacters(readableLayerName: string): V message: messages["tracing.volume_layer_name_starts_with_dot"], }; } - const uriSafeCharactersRegex = /[0-9a-zA-Z-._]+/g; + const validLayerNameCharactersRegex = /[0-9a-zA-Z-._$]+/g; // Removing all URISaveCharacters from readableLayerName. The leftover chars are all invalid. - const allInvalidChars = readableLayerName.replace(uriSafeCharactersRegex, ""); + const allInvalidChars = readableLayerName.replace(validLayerNameCharactersRegex, ""); const allUniqueInvalidCharsAsSet = new Set(allInvalidChars); const allUniqueInvalidCharsAsString = "".concat(...allUniqueInvalidCharsAsSet.values()); const isValid = allUniqueInvalidCharsAsString.length === 0; diff --git a/tools/postgres/schema.sql b/tools/postgres/schema.sql index 1cceca2f1ba..ed5c00bf4d7 100644 --- a/tools/postgres/schema.sql +++ b/tools/postgres/schema.sql @@ -20,7 +20,7 @@ CREATE TABLE webknossos.releaseInformation ( schemaVersion BIGINT NOT NULL ); -INSERT INTO webknossos.releaseInformation(schemaVersion) values(124); +INSERT INTO webknossos.releaseInformation(schemaVersion) values(125); COMMIT TRANSACTION; @@ -56,7 +56,7 @@ CREATE TABLE webknossos.annotation_layers( _annotation CHAR(24) NOT NULL, tracingId CHAR(36) NOT NULL UNIQUE, typ webknossos.ANNOTATION_LAYER_TYPE NOT NULL, - name VARCHAR(256) NOT NULL CHECK (name ~* '^[A-Za-z0-9\-_\.]+$'), + name VARCHAR(256) NOT NULL CHECK (name ~* '^[A-Za-z0-9\-_\.\$]+$'), statistics JSONB NOT NULL, UNIQUE (name, _annotation), PRIMARY KEY (_annotation, tracingId),