From ba901f80645bc0eaefecd540b16d8a59ca3aa3c9 Mon Sep 17 00:00:00 2001 From: Florian M Date: Tue, 11 Nov 2025 15:33:05 +0100 Subject: [PATCH 01/16] WIP: Improve job list view --- frontend/javascripts/admin/job/job_list_view.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/javascripts/admin/job/job_list_view.tsx b/frontend/javascripts/admin/job/job_list_view.tsx index 9da60d3f07c..63b4cccbd1f 100644 --- a/frontend/javascripts/admin/job/job_list_view.tsx +++ b/frontend/javascripts/admin/job/job_list_view.tsx @@ -392,7 +392,8 @@ function JobListView() { job.type === APIJobType.MATERIALIZE_VOLUME_ANNOTATION || job.type === APIJobType.COMPUTE_MESH_FILE || job.type === APIJobType.DEPRECATED_INFER_WITH_MODEL || - job.type === APIJobType.INFER_MITOCHONDRIA + job.type === APIJobType.INFER_MITOCHONDRIA || + job.type === APIJobType.INFER_INSTANCES ) { return ( From 90ff6d5e3437b717983d1e500c2601a30a88ecb1 Mon Sep 17 00:00:00 2001 From: Florian M Date: Tue, 11 Nov 2025 15:55:55 +0100 Subject: [PATCH 02/16] fix loading spinner, add workflow report link --- .../javascripts/admin/job/job_list_view.tsx | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/frontend/javascripts/admin/job/job_list_view.tsx b/frontend/javascripts/admin/job/job_list_view.tsx index 63b4cccbd1f..2517c98ce67 100644 --- a/frontend/javascripts/admin/job/job_list_view.tsx +++ b/frontend/javascripts/admin/job/job_list_view.tsx @@ -139,11 +139,15 @@ function JobListView() { const isCurrentUserSuperUser = useWkSelector((state) => state.activeUser?.isSuperUser); useEffect(() => { - fetchData(); + initialize(); + }, []); + + async function initialize() { + await fetchData(); const { searchQuery } = persistence.load(); setSearchQuery(searchQuery || ""); setIsLoading(false); - }, []); + } async function fetchData() { setJobs(await getJobs()); @@ -305,6 +309,12 @@ function JobListView() { } } + function renderWorkflowLink(__: any, job: APIJob) { + return job.voxelyticsWorkflowHash != null ? ( + Voxelytics Report + ) : null; + } + function renderActions(__: any, job: APIJob) { if (job.state === "PENDING" || job.state === "STARTED") { return ( @@ -506,17 +516,20 @@ function JobListView() { )} /> + (job.creditCost ? formatCreditsString(job.creditCost) : "-")} + /> + {isCurrentUserSuperUser ? ( + + ) : null} ((job) => job.state)} /> - (job.creditCost ? formatCreditsString(job.creditCost) : "-")} - /> From 698fcd0a558b494e98f61528024bc08e0760822d Mon Sep 17 00:00:00 2001 From: Florian M Date: Wed, 12 Nov 2025 17:18:16 +0100 Subject: [PATCH 03/16] wip FormattedId --- .../javascripts/admin/job/job_list_view.tsx | 16 +++++---- .../admin/scripts/script_list_view.tsx | 3 +- .../javascripts/admin/task/task_list_view.tsx | 5 +-- .../admin/tasktype/task_type_list_view.tsx | 3 +- .../admin/voxelytics/workflow_list_view.tsx | 9 ++++- .../javascripts/components/formatted_id.tsx | 27 +++++++++++++++ .../explorative_annotations_view.tsx | 34 +++---------------- .../dashboard/folders/details_sidebar.tsx | 13 ++----- frontend/javascripts/libs/format_utils.ts | 4 --- .../util/requestlogging/RequestLogging.scala | 2 +- 10 files changed, 59 insertions(+), 57 deletions(-) create mode 100644 frontend/javascripts/components/formatted_id.tsx diff --git a/frontend/javascripts/admin/job/job_list_view.tsx b/frontend/javascripts/admin/job/job_list_view.tsx index 2517c98ce67..98da18c8f64 100644 --- a/frontend/javascripts/admin/job/job_list_view.tsx +++ b/frontend/javascripts/admin/job/job_list_view.tsx @@ -15,6 +15,7 @@ import { cancelJob, getJobs, retryJob } from "admin/rest_api"; import { Input, Modal, Spin, Table, Tooltip, Typography } from "antd"; import { AsyncLink } from "components/async_clickables"; import FormattedDate from "components/formatted_date"; +import FormattedId from "components/formatted_id"; import { confirmAsync } from "dashboard/dataset/helper_components"; import { formatCreditsString, formatWkLibsNdBBox } from "libs/format_utils"; import Persistence from "libs/persistence"; @@ -494,16 +495,10 @@ function JobListView() { title="Job Id" dataIndex="id" key="id" + render={(id) => } sorter={Utils.localeCompareBy((job) => job.id)} /> - } - sorter={Utils.compareBy((job) => job.createdAt)} - defaultSortOrder="descend" - /> (job.creditCost ? formatCreditsString(job.creditCost) : "-")} /> + } + sorter={Utils.compareBy((job) => job.createdAt)} + defaultSortOrder="descend" + /> {isCurrentUserSuperUser ? ( ) : null} diff --git a/frontend/javascripts/admin/scripts/script_list_view.tsx b/frontend/javascripts/admin/scripts/script_list_view.tsx index 6e65eeb8ff1..e1ff8377d52 100644 --- a/frontend/javascripts/admin/scripts/script_list_view.tsx +++ b/frontend/javascripts/admin/scripts/script_list_view.tsx @@ -2,6 +2,7 @@ import { DeleteOutlined, EditOutlined, PlusOutlined } from "@ant-design/icons"; import { PropTypes } from "@scalableminds/prop-types"; import { deleteScript as deleteScriptAPI, getScripts } from "admin/rest_api"; import { App, Button, Input, Spin, Table } from "antd"; +import FormattedId from "components/formatted_id"; import LinkButton from "components/link_button"; import { handleGenericError } from "libs/error_handling"; import Persistence from "libs/persistence"; @@ -139,8 +140,8 @@ function ScriptListView() { } key="id" - className="monospace-id" sorter={Utils.localeCompareBy((script) => script.id)} width={150} /> diff --git a/frontend/javascripts/admin/task/task_list_view.tsx b/frontend/javascripts/admin/task/task_list_view.tsx index bfbbcda32ea..06fa04da35d 100644 --- a/frontend/javascripts/admin/task/task_list_view.tsx +++ b/frontend/javascripts/admin/task/task_list_view.tsx @@ -27,6 +27,7 @@ import type { ColumnType } from "antd/lib/table/interface"; import { AsyncLink } from "components/async_clickables"; import FixedExpandableTable from "components/fixed_expandable_table"; import FormattedDate from "components/formatted_date"; +import FormattedId from "components/formatted_id"; import LinkButton from "components/link_button"; import features from "features"; import { handleGenericError } from "libs/error_handling"; @@ -253,8 +254,8 @@ function TaskListView({ initialFieldValues }: Props) { dataIndex: "id", key: "id", sorter: Utils.localeCompareBy((task) => task.id), - className: "monospace-id", - width: 100, + render: (id: string) => , + width: 120, }, { title: "Project", diff --git a/frontend/javascripts/admin/tasktype/task_type_list_view.tsx b/frontend/javascripts/admin/tasktype/task_type_list_view.tsx index 73a63157914..8e0331be51d 100644 --- a/frontend/javascripts/admin/tasktype/task_type_list_view.tsx +++ b/frontend/javascripts/admin/tasktype/task_type_list_view.tsx @@ -14,6 +14,7 @@ import { } from "admin/rest_api"; import { App, Button, Input, Spin, Table, Tag } from "antd"; import { AsyncLink } from "components/async_clickables"; +import FormattedId from "components/formatted_id"; import LinkButton from "components/link_button"; import { handleGenericError } from "libs/error_handling"; import Markdown from "libs/markdown_adapter"; @@ -153,7 +154,7 @@ function TaskTypeListView() { key="id" width={120} sorter={Utils.localeCompareBy((taskType) => taskType.id)} - className="monospace-id" + render={(id) => } /> `${run.id}-${run.workflowHash}`} pagination={{ pageSize: 100 }} + locale={{ + emptyText: isLoading ? ( + + ) : ( + "There are no voxelytics workflow reports yet." + ), + }} columns={[ { title: "Workflow", diff --git a/frontend/javascripts/components/formatted_id.tsx b/frontend/javascripts/components/formatted_id.tsx new file mode 100644 index 00000000000..d751f4dfc79 --- /dev/null +++ b/frontend/javascripts/components/formatted_id.tsx @@ -0,0 +1,27 @@ +import FastTooltip from "./fast_tooltip"; + +import Toast from "libs/toast"; + +import { CopyOutlined } from "@ant-design/icons"; + +export default function FormattedId({ + id, +}: { + id: string; +}) { + const _shortId = id.slice(-6); + + return ( + +
{ + navigator.clipboard.writeText(id); + Toast.success("Copied ID to clipboard."); + }} + style={{ cursor: "pointer" }} + > + {`…${_shortId}`} +
+
+ ); +} diff --git a/frontend/javascripts/dashboard/explorative_annotations_view.tsx b/frontend/javascripts/dashboard/explorative_annotations_view.tsx index 01893039a11..34d863e1be3 100644 --- a/frontend/javascripts/dashboard/explorative_annotations_view.tsx +++ b/frontend/javascripts/dashboard/explorative_annotations_view.tsx @@ -1,5 +1,4 @@ import { - CopyOutlined, DownloadOutlined, FolderOpenOutlined, InboxOutlined, @@ -22,15 +21,16 @@ import { getReadableAnnotations, reOpenAnnotation, } from "admin/rest_api"; -import { Button, Card, Col, Input, Modal, Row, Spin, Table, Tag, Tooltip } from "antd"; +import { Button, Card, Col, Input, Modal, Row, Spin, Table, Tag } from "antd"; import type { SearchProps } from "antd/lib/input"; import type { ColumnType } from "antd/lib/table/interface"; import { AsyncLink } from "components/async_clickables"; import FormattedDate from "components/formatted_date"; +import FormattedId from "components/formatted_id"; import TextWithDescription from "components/text_with_description"; import update from "immutability-helper"; import { handleGenericError } from "libs/error_handling"; -import { formatHash, stringToColor } from "libs/format_utils"; +import { stringToColor } from "libs/format_utils"; import Persistence from "libs/persistence"; import Toast from "libs/toast"; import * as Utils from "libs/utils"; @@ -547,30 +547,6 @@ class ExplorativeAnnotationsView extends React.PureComponent { return filteredAnnotations.filter((el) => _.intersection(this.state.tags, el.tags).length > 0); } - renderIdAndCopyButton(annotation: APIAnnotationInfo) { - const copyIdToClipboard = async () => { - await navigator.clipboard.writeText(annotation.id); - Toast.success("ID copied to clipboard"); - }; - - return ( -
- -
- ); - } - renderNameWithDescription(annotation: APIAnnotationInfo) { return (
@@ -646,10 +622,10 @@ class ExplorativeAnnotationsView extends React.PureComponent { { title: "ID", dataIndex: "id", - width: 100, + width: 120, render: (__: any, annotation: APIAnnotationInfo) => ( <> -
{this.renderIdAndCopyButton(annotation)}
+ {!this.isAnnotationEditable(annotation) ? (
{READ_ONLY_ICON} read-only
diff --git a/frontend/javascripts/dashboard/folders/details_sidebar.tsx b/frontend/javascripts/dashboard/folders/details_sidebar.tsx index 5efa0fa32a9..bbde86f81ef 100644 --- a/frontend/javascripts/dashboard/folders/details_sidebar.tsx +++ b/frontend/javascripts/dashboard/folders/details_sidebar.tsx @@ -1,5 +1,4 @@ import { - CopyOutlined, EditOutlined, FileOutlined, FolderOpenOutlined, @@ -9,10 +8,10 @@ import { import { useQuery } from "@tanstack/react-query"; import { getOrganization } from "admin/rest_api"; import { Result, Spin, Tag, Tooltip } from "antd"; +import FormattedId from "components/formatted_id"; import { formatCountToDataAmountUnit, stringToColor } from "libs/format_utils"; import Markdown from "libs/markdown_adapter"; import { useWkSelector } from "libs/react_hooks"; -import Toast from "libs/toast"; import { pluralize } from "libs/utils"; import _ from "lodash"; import { useEffect } from "react"; @@ -176,15 +175,7 @@ function DatasetDetails({ selectedDataset }: { selectedDataset: APIDatasetCompac
ID
{fullDataset && ( - {fullDataset.id.substring(0, 10)}...{" "} - - { - navigator.clipboard.writeText(fullDataset.id); - Toast.success("Dataset ID copied."); - }} - /> - + )}
diff --git a/frontend/javascripts/libs/format_utils.ts b/frontend/javascripts/libs/format_utils.ts index eeb7c0908b6..75faa33f04e 100644 --- a/frontend/javascripts/libs/format_utils.ts +++ b/frontend/javascripts/libs/format_utils.ts @@ -444,10 +444,6 @@ export function formatDurationToSeconds(durationInMillisecons: number) { return duration.format("s"); } -export function formatHash(id: string): string { - return id.slice(-6); -} - export function formatDateMedium(date: Date | number): string { return dayjs(date).format("lll"); } diff --git a/util/src/main/scala/com/scalableminds/util/requestlogging/RequestLogging.scala b/util/src/main/scala/com/scalableminds/util/requestlogging/RequestLogging.scala index 8d3b902d57d..977e68f830f 100644 --- a/util/src/main/scala/com/scalableminds/util/requestlogging/RequestLogging.scala +++ b/util/src/main/scala/com/scalableminds/util/requestlogging/RequestLogging.scala @@ -17,7 +17,7 @@ trait AbstractRequestLogging extends LazyLogging with Formatter { requesterId: Option[String] = None): Unit = if (!Status.isSuccessful(result.header.status)) { val userIdMsg = requesterId.map(id => s" for user $id").getOrElse("") - val resultMsg = s": ${resultBody(result)}" + val resultMsg = s": `${resultBody(result)}`" val msg = s"Answering ${result.header.status} at ${request.uri}$userIdMsg$resultMsg" logger.warn(msg) notifier.foreach(_(msg)) From 2b6d1e1dfc42e26fd6d1893b69d46ba5fb9b0a30 Mon Sep 17 00:00:00 2001 From: Florian M Date: Wed, 12 Nov 2025 18:10:10 +0100 Subject: [PATCH 04/16] remove unused css entry --- frontend/stylesheets/main.less | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/frontend/stylesheets/main.less b/frontend/stylesheets/main.less index e0dd47431b8..5748bd02c3b 100644 --- a/frontend/stylesheets/main.less +++ b/frontend/stylesheets/main.less @@ -178,15 +178,6 @@ button.narrow { margin-right: 0; } -.monospace-id { - // break long IDs - word-wrap: break-word; - max-width: 154px; - font-family: monospace; - font-size: 14px; - width: 135px; -} - .hide-on-small-screen { @media (max-width: 1200px) { display: none !important; @@ -722,4 +713,4 @@ button.narrow { .ant-notification-notice-btn{ margin-top: 0 !important; } -} \ No newline at end of file +} From 7fc613ec0943ddb285ae05a5a4d6f60c9d66d72a Mon Sep 17 00:00:00 2001 From: Florian M Date: Wed, 12 Nov 2025 18:11:46 +0100 Subject: [PATCH 05/16] wording --- frontend/javascripts/components/formatted_id.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/javascripts/components/formatted_id.tsx b/frontend/javascripts/components/formatted_id.tsx index d751f4dfc79..55bf719e294 100644 --- a/frontend/javascripts/components/formatted_id.tsx +++ b/frontend/javascripts/components/formatted_id.tsx @@ -12,7 +12,7 @@ export default function FormattedId({ const _shortId = id.slice(-6); return ( - +
{ navigator.clipboard.writeText(id); From 449640285f95164225a97b6eb95ae712ecaa1c10 Mon Sep 17 00:00:00 2001 From: Florian M Date: Sun, 23 Nov 2025 18:43:31 +0100 Subject: [PATCH 06/16] move schema and evolutions --- {conf => schema}/evolutions/000-initial-schema.sql | 0 {conf => schema}/evolutions/001-add-organizations.sql | 0 .../evolutions/002-add-dataset-urlsharing-token.sql | 0 {conf => schema}/evolutions/003-add-dataset-displayname.sql | 0 .../evolutions/004-add-initializing-annotation-state.sql | 0 .../evolutions/005-add-openinstances-trigger.sql | 0 .../evolutions/007-unify-type-datalayer-name.sql | 0 {conf => schema}/evolutions/008-task-instances-triggers.sql | 0 .../evolutions/009-remove-team-assignment-from-task.sql | 0 {conf => schema}/evolutions/010-add-organization-data.sql | 0 {conf => schema}/evolutions/011-add-isOrganizationTeam.sql | 0 {conf => schema}/evolutions/012-add-foreign-keys.sql | 0 {conf => schema}/evolutions/013-add-logoUrl.sql | 0 .../evolutions/014-equalize-schema-and-evolutions.sql | 0 .../evolutions/015-add-organization-displayname.sql | 0 {conf => schema}/evolutions/016-add-schema-version.sql | 0 {conf => schema}/evolutions/017-add-organization-email.sql | 0 {conf => schema}/evolutions/018-hybrid-annotations.sql | 0 {conf => schema}/evolutions/019-dataset-lastusedtime.sql | 0 {conf => schema}/evolutions/021-list-experiences.sql | 0 {conf => schema}/evolutions/022-add-foreign-datastore.sql | 0 {conf => schema}/evolutions/023-drop-datastore-type.sql | 0 {conf => schema}/evolutions/024-drop-md5hash.sql | 0 {conf => schema}/evolutions/025-add-dataset-sortingKey.sql | 0 {conf => schema}/evolutions/026-decrease-total-instance.sql | 0 .../evolutions/027-drop-dataset-name-unique-constraint.sql | 0 .../evolutions/028-add-isBlacklistedFromReport.sql | 0 {conf => schema}/evolutions/029-foreign-keys-deferrable.sql | 0 {conf => schema}/evolutions/030-tracingstore.sql | 0 {conf => schema}/evolutions/031-maintenance.sql | 0 {conf => schema}/evolutions/032-scratch-datastores.sql | 0 .../evolutions/033-tasktype-recommendedConfiguration.sql | 0 {conf => schema}/evolutions/034-meshes.sql | 0 .../evolutions/035-add-annotation-dataset-foreign-key.sql | 0 .../evolutions/036-add-lastTaskTypeId-to-user.sql | 0 {conf => schema}/evolutions/037-add-publications.sql | 0 {conf => schema}/evolutions/038-more-voxel-types.sql | 0 {conf => schema}/evolutions/039-add-tasktype-tracingtype.sql | 0 .../evolutions/040-add-auto-activate-per-orga.sql | 0 .../evolutions/041-add-datastore-isconnector.sql | 0 .../evolutions/042-add-json-object-constraints.sql | 0 .../043-annotationsettings-allowedMagnifications.sql | 0 {conf => schema}/evolutions/044-datasource-hash.sql | 0 .../evolutions/045-annotationsettings-mergerMode.sql | 0 {conf => schema}/evolutions/046-fix-missing-voxel-type.sql | 0 {conf => schema}/evolutions/047-add-datastore-publicUrl.sql | 0 .../evolutions/048-add-tracingstore-publicUrl.sql | 0 {conf => schema}/evolutions/049-annotation-listed-teams.sql | 0 .../evolutions/050-add-annotation-visibility-enum.sql | 0 .../evolutions/051-add-source-view-configuration.sql | 0 .../evolutions/052-replace-segmentation-opacity.sql | 0 {conf => schema}/evolutions/053-add-allowsUpload.sql | 0 {conf => schema}/evolutions/054-add-isDatasetManager.sql | 0 .../evolutions/055-make-organization-name-unique.sql | 0 {conf => schema}/evolutions/056-add-jobs.sql | 0 .../evolutions/057-add-layer-specific-view-configs.sql | 0 .../evolutions/058-add-onlyAllowedOrganization.sql | 0 {conf => schema}/evolutions/059-resolution-restrictions.sql | 0 {conf => schema}/evolutions/060-multiusers.sql | 0 {conf => schema}/evolutions/061-userinfos-view.sql | 0 {conf => schema}/evolutions/062-dataset-uploader.sql | 0 {conf => schema}/evolutions/063-novelUserExperienceinfos.sql | 0 .../evolutions/064-experienceDomains-per-orga.sql | 0 {conf => schema}/evolutions/065-unlisted-superusers.sql | 0 {conf => schema}/evolutions/066-publications-foreign-key.sql | 0 {conf => schema}/evolutions/067-drop-analytics.sql | 0 {conf => schema}/evolutions/068-pricing-plan.sql | 0 .../evolutions/069-tasktype-project-unique-per-orga.sql | 0 {conf => schema}/evolutions/070-dark-theme.sql | 0 .../evolutions/071-adapt-td-view-display-planes.sql | 0 {conf => schema}/evolutions/072-jobs-manually-repaired.sql | 0 .../evolutions/073-modern-controls-user-conf.sql | 0 {conf => schema}/evolutions/074-jobs-owner-foreign-key.sql | 0 .../evolutions/075-tasktype-remove-hovered-cell-id.sql | 0 .../evolutions/076-jobs-enabled-per-datastore.sql | 0 {conf => schema}/evolutions/077-workers.sql | 0 {conf => schema}/evolutions/078-annotation-layers.sql | 0 {conf => schema}/evolutions/079-add-dataset-tags.sql | 0 {conf => schema}/evolutions/080-job-add-cancelled.sql | 0 .../evolutions/081-annotation-viewconfiguration.sql | 0 .../082-annotationsettings-volumeInterpolationAllowed.sql | 0 {conf => schema}/evolutions/083-unique-layer-names.sql | 0 {conf => schema}/evolutions/084-annotation-contributors.sql | 0 {conf => schema}/evolutions/085-annotation-publication.sql | 0 {conf => schema}/evolutions/086-drop-foreign-datastores.sql | 0 {conf => schema}/evolutions/087-zarr-private-links.sql | 0 {conf => schema}/evolutions/088-shortlinks.sql | 0 {conf => schema}/evolutions/089-voxelytics.sql | 0 {conf => schema}/evolutions/090-cleanup.sql | 0 {conf => schema}/evolutions/091-folders.sql | 0 {conf => schema}/evolutions/092-oidc.sql | 0 {conf => schema}/evolutions/093-terms-of-service.sql | 0 {conf => schema}/evolutions/094-pricing-plans.sql | 0 {conf => schema}/evolutions/095-constraint-naming.sql | 0 {conf => schema}/evolutions/096-storage.sql | 0 {conf => schema}/evolutions/097-credentials.sql | 0 {conf => schema}/evolutions/098-voxelytics-states.sql | 0 {conf => schema}/evolutions/099-rename-credential-types.sql | 0 {conf => schema}/evolutions/100-annotation-mutexes.sql | 0 .../evolutions/101-coordinate-transformations.sql | 0 {conf => schema}/evolutions/102-no-more-wkconnect.sql | 0 {conf => schema}/evolutions/103-thin-plane-splines.sql | 0 {conf => schema}/evolutions/104-thumbnails.sql | 0 {conf => schema}/evolutions/105-verify-email.sql | 0 {conf => schema}/evolutions/106-folder-no-slashes.sql | 0 {conf => schema}/evolutions/107-task-terminology.sql | 0 {conf => schema}/evolutions/108-additional-coordinates.sql | 0 {conf => schema}/evolutions/109-scheduled-maintenances.sql | 0 {conf => schema}/evolutions/110-worker-config.sql | 0 .../evolutions/111-stats-per-annotation-layer.sql | 0 {conf => schema}/evolutions/112-reuse-deleted.sql | 0 {conf => schema}/evolutions/113-analytics-events.sql | 0 {conf => schema}/evolutions/114-ai-models.sql | 0 .../evolutions/115-annotation-locked-by-user.sql | 0 {conf => schema}/evolutions/116-drop-overtimemailinglist.sql | 0 {conf => schema}/evolutions/117-voxel-size-unit.sql | 0 .../evolutions/118-voxelytics-artifacts-index.sql | 0 .../evolutions/119-add-metadata-to-folders-and-datasets.sql | 0 .../evolutions/120-remove-old-organization-id.sql | 0 {conf => schema}/evolutions/121-worker-name.sql | 0 {conf => schema}/evolutions/122-resolution-to-mag.sql | 0 {conf => schema}/evolutions/123-more-model-categories.sql | 0 .../evolutions/124-decouple-dataset-directory-from-name.sql | 0 .../evolutions/125-allow-dollar-in-layer-names.sql | 0 {conf => schema}/evolutions/126-mag-real-paths.sql | 0 .../evolutions/127-job-retried-by-super-user.sql | 0 {conf => schema}/evolutions/128-allow-ai-model-sharing.sql | 0 {conf => schema}/evolutions/129-credit-transactions.sql | 0 {conf => schema}/evolutions/130-replace-text-types.sql | 0 {conf => schema}/evolutions/131-more-indices-on-users.sql | 0 {conf => schema}/evolutions/132-remove-stored-meshes.sql | 0 .../evolutions/133-datasource-properties-in-db.sql | 0 .../evolutions/134-dataset-layer-attachments.sql | 0 {conf => schema}/evolutions/135-neuroglancer-attachment.sql | 0 .../evolutions/136-extra-column-for-email-changed.sql | 0 {conf => schema}/evolutions/137-virtual-datasets.sql | 0 {conf => schema}/evolutions/138-add-webauthn-credentials.sql | 0 {conf => schema}/evolutions/139-logout-everywhere.sql | 0 .../140-annotation-layer-name-check-deferrable.sql | 0 {conf => schema}/evolutions/141-allows-upload-to-paths.sql | 0 {conf => schema}/evolutions/142-personal-plan.sql | 0 {conf => schema}/evolutions/143-remote-storage-analysis.sql | 0 {conf => schema}/evolutions/144-improve-storage-scan.sql | 0 {conf => schema}/evolutions/145-attachment-realpaths.sql | 0 .../evolutions/reversions/001-add-organizations.sql | 0 .../reversions/002-add-dataset-urlsharing-token.sql | 0 .../evolutions/reversions/003-add-dataset-displayname.sql | 0 .../reversions/004-add-initializing-annotation-state.sql | 0 .../evolutions/reversions/005-add-openinstances-trigger.sql | 0 .../evolutions/reversions/007-unify-type-datalayer-name.sql | 0 .../evolutions/reversions/008-task-instances-triggers.sql | 0 .../reversions/009-remove-team-assignment-from-task.sql | 0 .../evolutions/reversions/010-add-organization-data.sql | 0 .../evolutions/reversions/011-add-isOrganizationTeam.sql | 0 .../evolutions/reversions/012-add-foreign-keys.sql | 0 {conf => schema}/evolutions/reversions/013-add-logoUrl.sql | 0 .../reversions/014-equalize-schema-and-evolutions.sql | 0 .../reversions/015-add-organization-displayname.sql | 0 .../evolutions/reversions/016-add-schema-version.sql | 0 .../evolutions/reversions/017-add-organization-email.sql | 0 .../evolutions/reversions/018-hybrid-annotations.sql | 0 .../evolutions/reversions/019-dataset-lastusedtime.sql | 0 .../evolutions/reversions/021-list-experiences.sql | 0 .../evolutions/reversions/022-add-foreign-datastore.sql | 0 .../evolutions/reversions/023-drop-datastore-type.sql | 0 {conf => schema}/evolutions/reversions/024-drop-md5hash.sql | 0 .../evolutions/reversions/025-add-dataset-sortingKey.sql | 0 .../evolutions/reversions/026-decrease-total-instance.sql | 0 .../reversions/027-drop-dataset-name-unique-constraint.sql | 0 .../reversions/028-add-isBlacklistedFromReport.sql | 0 .../evolutions/reversions/029-foreign-keys-deferrable.sql | 0 {conf => schema}/evolutions/reversions/030-tracingstore.sql | 0 {conf => schema}/evolutions/reversions/031-maintenance.sql | 0 .../evolutions/reversions/032-scratch-datastores.sql | 0 .../reversions/033-tasktype-recommendedConfiguration.sql | 0 {conf => schema}/evolutions/reversions/034-meshes.sql | 0 .../reversions/035-add-annotation-dataset-foreign-key.sql | 0 .../evolutions/reversions/036-add-lastTaskTypeId-to-user.sql | 0 .../evolutions/reversions/037-add-publications.sql | 0 .../evolutions/reversions/038-more-voxel-types.sql | 0 .../evolutions/reversions/039-add-tasktype-tracingtype.sql | 0 .../evolutions/reversions/040-add-auto-activate-per-orga.sql | 0 .../evolutions/reversions/041-add-datastore-isconnector.sql | 0 .../reversions/042-add-json-object-constraints.sql | 0 .../043-annotationsettings-allowedMagnifications.sql | 0 .../evolutions/reversions/044-datasource-hash.sql | 0 .../reversions/045-annotationsettings-mergerMode.sql | 0 .../evolutions/reversions/046-fix-missing-voxel-type.sql | 0 .../evolutions/reversions/047-add-datastore-publicUrl.sql | 0 .../evolutions/reversions/048-add-tracingstore-publicUrl.sql | 0 .../evolutions/reversions/048-annotation-listed-teams.sql | 0 .../evolutions/reversions/049-annotation-listed-teams.sql | 0 .../reversions/050-add-annotation-visibility-enum.sql | 0 .../reversions/051-add-source-view-configuration.sql | 0 .../reversions/052-replace-segmentation-opacity.sql | 0 .../evolutions/reversions/053-add-allowsUpload.sql | 0 .../evolutions/reversions/054-add-isDatasetManager.sql | 0 .../reversions/055-make-organization-name-unique.sql | 0 {conf => schema}/evolutions/reversions/056-add-jobs.sql | 0 .../reversions/057-add-layer-specific-view-configs.sql | 0 .../reversions/058-add-onlyAllowedOrganization.sql | 0 .../evolutions/reversions/059-resolution-restrictions.sql | 0 {conf => schema}/evolutions/reversions/060-multiusers.sql | 0 .../evolutions/reversions/061-userinfos-view.sql | 0 .../evolutions/reversions/062-dataset-uploader.sql | 0 .../evolutions/reversions/063-novelUserExperienceinfos.sql | 0 .../evolutions/reversions/064-experienceDomains-per-orga.sql | 0 .../evolutions/reversions/065-unlisted-superusers.sql | 0 .../evolutions/reversions/066-publications-foreign-key.sql | 0 .../evolutions/reversions/067-drop-analytics.sql | 0 {conf => schema}/evolutions/reversions/068-pricing-plan.sql | 0 .../reversions/069-tasktype-project-unique-per-orga.sql | 0 {conf => schema}/evolutions/reversions/070-dark-theme.sql | 0 .../reversions/071-adapt-td-view-display-planes.sql | 0 .../evolutions/reversions/072-jobs-manually-repaired.sql | 0 .../evolutions/reversions/073-modern-controls-user-conf.sql | 0 .../evolutions/reversions/074-jobs-owner-foreign-key.sql | 0 .../reversions/075-tasktype-remove-hovered-cell-id.sql | 0 .../evolutions/reversions/076-jobs-enabled-per-datastore.sql | 0 {conf => schema}/evolutions/reversions/077-workers.sql | 0 .../evolutions/reversions/078-annotation-layers.sql | 0 .../evolutions/reversions/079-add-dataset-tags.sql | 0 .../evolutions/reversions/080-job-add-cancelled.sql | 0 .../reversions/081-annotation-viewconfiguration.sql | 0 .../082-annotationsettings-volumeInterpolationAllowed.sql | 0 .../evolutions/reversions/083-unique-layer-names.sql | 0 .../evolutions/reversions/084-annotation-contributors.sql | 0 .../evolutions/reversions/085-annotation-publication.sql | 0 .../evolutions/reversions/086-drop-foreign-datastores.sql | 0 .../evolutions/reversions/087-zarr-private-links.sql | 0 {conf => schema}/evolutions/reversions/088-shortlinks.sql | 0 {conf => schema}/evolutions/reversions/089-voxelytics.sql | 0 {conf => schema}/evolutions/reversions/090-cleanup.sql | 0 {conf => schema}/evolutions/reversions/091-folders.sql | 0 {conf => schema}/evolutions/reversions/092-oidc.sql | 0 .../evolutions/reversions/093-terms-of-service.sql | 0 {conf => schema}/evolutions/reversions/094-pricing-plans.sql | 0 .../evolutions/reversions/095-constraint-naming.sql | 0 {conf => schema}/evolutions/reversions/096-storage.sql | 0 {conf => schema}/evolutions/reversions/097-credentials.sql | 0 .../evolutions/reversions/098-voxelytics-states.sql | 0 .../evolutions/reversions/099-rename-credential-types.sql | 0 .../evolutions/reversions/100-annotation-mutexes.sql | 0 .../evolutions/reversions/101-coordinate-transformations.sql | 0 .../evolutions/reversions/102-no-more-wkconnect.sql | 0 .../evolutions/reversions/103-thin-plane-splines.sql | 0 {conf => schema}/evolutions/reversions/104-thumbnails.sql | 0 {conf => schema}/evolutions/reversions/105-verify-email.sql | 0 .../evolutions/reversions/106-folder-no-slashes.sql | 0 .../evolutions/reversions/107-task-terminology.sql | 0 .../evolutions/reversions/108-additional-coordinates.sql | 0 .../evolutions/reversions/109-scheduled-maintenances.sql | 0 {conf => schema}/evolutions/reversions/110-worker-config.sql | 0 .../evolutions/reversions/111-stats-per-annotation-layer.sql | 0 {conf => schema}/evolutions/reversions/112-reuse-deleted.sql | 0 .../evolutions/reversions/113-analytics-events.sql | 0 {conf => schema}/evolutions/reversions/114-ai-models.sql | 0 .../evolutions/reversions/115-annotation-locked-by-user.sql | 0 .../evolutions/reversions/116-drop-overtimemailinglist.sql | 0 .../evolutions/reversions/117-voxel-size-unit.sql | 0 .../evolutions/reversions/118-voxelytics-artifacts-index.sql | 0 .../reversions/119-add-metadata-to-folders-and-datasets.sql | 0 .../evolutions/reversions/120-remove-old-organization-id.sql | 0 {conf => schema}/evolutions/reversions/121-worker-name.sql | 0 .../evolutions/reversions/122-resolution-to-mag.sql | 0 .../evolutions/reversions/123-more-model-categories.sql | 0 .../reversions/124-decouple-dataset-directory-from-name.sql | 0 .../reversions/125-allow-dollar-in-layer-names.sql | 0 .../evolutions/reversions/126-mag-real-paths.sql | 0 .../evolutions/reversions/127-job-retried-by-super-user.sql | 0 .../evolutions/reversions/128-allow-ai-model-sharing.sql | 0 .../evolutions/reversions/129-credit-transactions.sql | 0 .../evolutions/reversions/130-replace-text-types.sql | 0 .../evolutions/reversions/131-more-indices-on-users.sql | 0 .../evolutions/reversions/132-remove-stored-meshes.sql | 0 .../reversions/133-datasource-properties-in-db.sql | 0 .../evolutions/reversions/134-dataset-layer-attachments.sql | 0 .../evolutions/reversions/135-neuroglancer-attachment.sql | 0 .../reversions/136-extra-column-for-email-changed.sql | 0 .../evolutions/reversions/137-virtual-datasets.sql | 0 .../evolutions/reversions/138-add-webauthn-credentials.sql | 0 .../evolutions/reversions/139-logout-everywhere.sql | 0 .../140-annotation-layer-name-check-deferrable.sql | 0 .../evolutions/reversions/141-allows-upload-to-paths.sql | 0 {conf => schema}/evolutions/reversions/142-personal-plan.sql | 0 .../evolutions/reversions/143-remote-storage-analysis.sql | 0 .../evolutions/reversions/144-improve-storage-scan.sql | 0 .../evolutions/reversions/145-attachment-realpaths.sql | 0 {tools/postgres => schema}/schema.sql | 0 tools/assert-complete-migrations.sh | 2 +- tools/postgres/dbtool.js | 5 +++-- 291 files changed, 4 insertions(+), 3 deletions(-) rename {conf => schema}/evolutions/000-initial-schema.sql (100%) rename {conf => schema}/evolutions/001-add-organizations.sql (100%) rename {conf => schema}/evolutions/002-add-dataset-urlsharing-token.sql (100%) rename {conf => schema}/evolutions/003-add-dataset-displayname.sql (100%) rename {conf => schema}/evolutions/004-add-initializing-annotation-state.sql (100%) rename {conf => schema}/evolutions/005-add-openinstances-trigger.sql (100%) rename {conf => schema}/evolutions/007-unify-type-datalayer-name.sql (100%) rename {conf => schema}/evolutions/008-task-instances-triggers.sql (100%) rename {conf => schema}/evolutions/009-remove-team-assignment-from-task.sql (100%) rename {conf => schema}/evolutions/010-add-organization-data.sql (100%) rename {conf => schema}/evolutions/011-add-isOrganizationTeam.sql (100%) rename {conf => schema}/evolutions/012-add-foreign-keys.sql (100%) rename {conf => schema}/evolutions/013-add-logoUrl.sql (100%) rename {conf => schema}/evolutions/014-equalize-schema-and-evolutions.sql (100%) rename {conf => schema}/evolutions/015-add-organization-displayname.sql (100%) rename {conf => schema}/evolutions/016-add-schema-version.sql (100%) rename {conf => schema}/evolutions/017-add-organization-email.sql (100%) rename {conf => schema}/evolutions/018-hybrid-annotations.sql (100%) rename {conf => schema}/evolutions/019-dataset-lastusedtime.sql (100%) rename {conf => schema}/evolutions/021-list-experiences.sql (100%) rename {conf => schema}/evolutions/022-add-foreign-datastore.sql (100%) rename {conf => schema}/evolutions/023-drop-datastore-type.sql (100%) rename {conf => schema}/evolutions/024-drop-md5hash.sql (100%) rename {conf => schema}/evolutions/025-add-dataset-sortingKey.sql (100%) rename {conf => schema}/evolutions/026-decrease-total-instance.sql (100%) rename {conf => schema}/evolutions/027-drop-dataset-name-unique-constraint.sql (100%) rename {conf => schema}/evolutions/028-add-isBlacklistedFromReport.sql (100%) rename {conf => schema}/evolutions/029-foreign-keys-deferrable.sql (100%) rename {conf => schema}/evolutions/030-tracingstore.sql (100%) rename {conf => schema}/evolutions/031-maintenance.sql (100%) rename {conf => schema}/evolutions/032-scratch-datastores.sql (100%) rename {conf => schema}/evolutions/033-tasktype-recommendedConfiguration.sql (100%) rename {conf => schema}/evolutions/034-meshes.sql (100%) rename {conf => schema}/evolutions/035-add-annotation-dataset-foreign-key.sql (100%) rename {conf => schema}/evolutions/036-add-lastTaskTypeId-to-user.sql (100%) rename {conf => schema}/evolutions/037-add-publications.sql (100%) rename {conf => schema}/evolutions/038-more-voxel-types.sql (100%) rename {conf => schema}/evolutions/039-add-tasktype-tracingtype.sql (100%) rename {conf => schema}/evolutions/040-add-auto-activate-per-orga.sql (100%) rename {conf => schema}/evolutions/041-add-datastore-isconnector.sql (100%) rename {conf => schema}/evolutions/042-add-json-object-constraints.sql (100%) rename {conf => schema}/evolutions/043-annotationsettings-allowedMagnifications.sql (100%) rename {conf => schema}/evolutions/044-datasource-hash.sql (100%) rename {conf => schema}/evolutions/045-annotationsettings-mergerMode.sql (100%) rename {conf => schema}/evolutions/046-fix-missing-voxel-type.sql (100%) rename {conf => schema}/evolutions/047-add-datastore-publicUrl.sql (100%) rename {conf => schema}/evolutions/048-add-tracingstore-publicUrl.sql (100%) rename {conf => schema}/evolutions/049-annotation-listed-teams.sql (100%) rename {conf => schema}/evolutions/050-add-annotation-visibility-enum.sql (100%) rename {conf => schema}/evolutions/051-add-source-view-configuration.sql (100%) rename {conf => schema}/evolutions/052-replace-segmentation-opacity.sql (100%) rename {conf => schema}/evolutions/053-add-allowsUpload.sql (100%) rename {conf => schema}/evolutions/054-add-isDatasetManager.sql (100%) rename {conf => schema}/evolutions/055-make-organization-name-unique.sql (100%) rename {conf => schema}/evolutions/056-add-jobs.sql (100%) rename {conf => schema}/evolutions/057-add-layer-specific-view-configs.sql (100%) rename {conf => schema}/evolutions/058-add-onlyAllowedOrganization.sql (100%) rename {conf => schema}/evolutions/059-resolution-restrictions.sql (100%) rename {conf => schema}/evolutions/060-multiusers.sql (100%) rename {conf => schema}/evolutions/061-userinfos-view.sql (100%) rename {conf => schema}/evolutions/062-dataset-uploader.sql (100%) rename {conf => schema}/evolutions/063-novelUserExperienceinfos.sql (100%) rename {conf => schema}/evolutions/064-experienceDomains-per-orga.sql (100%) rename {conf => schema}/evolutions/065-unlisted-superusers.sql (100%) rename {conf => schema}/evolutions/066-publications-foreign-key.sql (100%) rename {conf => schema}/evolutions/067-drop-analytics.sql (100%) rename {conf => schema}/evolutions/068-pricing-plan.sql (100%) rename {conf => schema}/evolutions/069-tasktype-project-unique-per-orga.sql (100%) rename {conf => schema}/evolutions/070-dark-theme.sql (100%) rename {conf => schema}/evolutions/071-adapt-td-view-display-planes.sql (100%) rename {conf => schema}/evolutions/072-jobs-manually-repaired.sql (100%) rename {conf => schema}/evolutions/073-modern-controls-user-conf.sql (100%) rename {conf => schema}/evolutions/074-jobs-owner-foreign-key.sql (100%) rename {conf => schema}/evolutions/075-tasktype-remove-hovered-cell-id.sql (100%) rename {conf => schema}/evolutions/076-jobs-enabled-per-datastore.sql (100%) rename {conf => schema}/evolutions/077-workers.sql (100%) rename {conf => schema}/evolutions/078-annotation-layers.sql (100%) rename {conf => schema}/evolutions/079-add-dataset-tags.sql (100%) rename {conf => schema}/evolutions/080-job-add-cancelled.sql (100%) rename {conf => schema}/evolutions/081-annotation-viewconfiguration.sql (100%) rename {conf => schema}/evolutions/082-annotationsettings-volumeInterpolationAllowed.sql (100%) rename {conf => schema}/evolutions/083-unique-layer-names.sql (100%) rename {conf => schema}/evolutions/084-annotation-contributors.sql (100%) rename {conf => schema}/evolutions/085-annotation-publication.sql (100%) rename {conf => schema}/evolutions/086-drop-foreign-datastores.sql (100%) rename {conf => schema}/evolutions/087-zarr-private-links.sql (100%) rename {conf => schema}/evolutions/088-shortlinks.sql (100%) rename {conf => schema}/evolutions/089-voxelytics.sql (100%) rename {conf => schema}/evolutions/090-cleanup.sql (100%) rename {conf => schema}/evolutions/091-folders.sql (100%) rename {conf => schema}/evolutions/092-oidc.sql (100%) rename {conf => schema}/evolutions/093-terms-of-service.sql (100%) rename {conf => schema}/evolutions/094-pricing-plans.sql (100%) rename {conf => schema}/evolutions/095-constraint-naming.sql (100%) rename {conf => schema}/evolutions/096-storage.sql (100%) rename {conf => schema}/evolutions/097-credentials.sql (100%) rename {conf => schema}/evolutions/098-voxelytics-states.sql (100%) rename {conf => schema}/evolutions/099-rename-credential-types.sql (100%) rename {conf => schema}/evolutions/100-annotation-mutexes.sql (100%) rename {conf => schema}/evolutions/101-coordinate-transformations.sql (100%) rename {conf => schema}/evolutions/102-no-more-wkconnect.sql (100%) rename {conf => schema}/evolutions/103-thin-plane-splines.sql (100%) rename {conf => schema}/evolutions/104-thumbnails.sql (100%) rename {conf => schema}/evolutions/105-verify-email.sql (100%) rename {conf => schema}/evolutions/106-folder-no-slashes.sql (100%) rename {conf => schema}/evolutions/107-task-terminology.sql (100%) rename {conf => schema}/evolutions/108-additional-coordinates.sql (100%) rename {conf => schema}/evolutions/109-scheduled-maintenances.sql (100%) rename {conf => schema}/evolutions/110-worker-config.sql (100%) rename {conf => schema}/evolutions/111-stats-per-annotation-layer.sql (100%) rename {conf => schema}/evolutions/112-reuse-deleted.sql (100%) rename {conf => schema}/evolutions/113-analytics-events.sql (100%) rename {conf => schema}/evolutions/114-ai-models.sql (100%) rename {conf => schema}/evolutions/115-annotation-locked-by-user.sql (100%) rename {conf => schema}/evolutions/116-drop-overtimemailinglist.sql (100%) rename {conf => schema}/evolutions/117-voxel-size-unit.sql (100%) rename {conf => schema}/evolutions/118-voxelytics-artifacts-index.sql (100%) rename {conf => schema}/evolutions/119-add-metadata-to-folders-and-datasets.sql (100%) rename {conf => schema}/evolutions/120-remove-old-organization-id.sql (100%) rename {conf => schema}/evolutions/121-worker-name.sql (100%) rename {conf => schema}/evolutions/122-resolution-to-mag.sql (100%) rename {conf => schema}/evolutions/123-more-model-categories.sql (100%) rename {conf => schema}/evolutions/124-decouple-dataset-directory-from-name.sql (100%) rename {conf => schema}/evolutions/125-allow-dollar-in-layer-names.sql (100%) rename {conf => schema}/evolutions/126-mag-real-paths.sql (100%) rename {conf => schema}/evolutions/127-job-retried-by-super-user.sql (100%) rename {conf => schema}/evolutions/128-allow-ai-model-sharing.sql (100%) rename {conf => schema}/evolutions/129-credit-transactions.sql (100%) rename {conf => schema}/evolutions/130-replace-text-types.sql (100%) rename {conf => schema}/evolutions/131-more-indices-on-users.sql (100%) rename {conf => schema}/evolutions/132-remove-stored-meshes.sql (100%) rename {conf => schema}/evolutions/133-datasource-properties-in-db.sql (100%) rename {conf => schema}/evolutions/134-dataset-layer-attachments.sql (100%) rename {conf => schema}/evolutions/135-neuroglancer-attachment.sql (100%) rename {conf => schema}/evolutions/136-extra-column-for-email-changed.sql (100%) rename {conf => schema}/evolutions/137-virtual-datasets.sql (100%) rename {conf => schema}/evolutions/138-add-webauthn-credentials.sql (100%) rename {conf => schema}/evolutions/139-logout-everywhere.sql (100%) rename {conf => schema}/evolutions/140-annotation-layer-name-check-deferrable.sql (100%) rename {conf => schema}/evolutions/141-allows-upload-to-paths.sql (100%) rename {conf => schema}/evolutions/142-personal-plan.sql (100%) rename {conf => schema}/evolutions/143-remote-storage-analysis.sql (100%) rename {conf => schema}/evolutions/144-improve-storage-scan.sql (100%) rename {conf => schema}/evolutions/145-attachment-realpaths.sql (100%) rename {conf => schema}/evolutions/reversions/001-add-organizations.sql (100%) rename {conf => schema}/evolutions/reversions/002-add-dataset-urlsharing-token.sql (100%) rename {conf => schema}/evolutions/reversions/003-add-dataset-displayname.sql (100%) rename {conf => schema}/evolutions/reversions/004-add-initializing-annotation-state.sql (100%) rename {conf => schema}/evolutions/reversions/005-add-openinstances-trigger.sql (100%) rename {conf => schema}/evolutions/reversions/007-unify-type-datalayer-name.sql (100%) rename {conf => schema}/evolutions/reversions/008-task-instances-triggers.sql (100%) rename {conf => schema}/evolutions/reversions/009-remove-team-assignment-from-task.sql (100%) rename {conf => schema}/evolutions/reversions/010-add-organization-data.sql (100%) rename {conf => schema}/evolutions/reversions/011-add-isOrganizationTeam.sql (100%) rename {conf => schema}/evolutions/reversions/012-add-foreign-keys.sql (100%) rename {conf => schema}/evolutions/reversions/013-add-logoUrl.sql (100%) rename {conf => schema}/evolutions/reversions/014-equalize-schema-and-evolutions.sql (100%) rename {conf => schema}/evolutions/reversions/015-add-organization-displayname.sql (100%) rename {conf => schema}/evolutions/reversions/016-add-schema-version.sql (100%) rename {conf => schema}/evolutions/reversions/017-add-organization-email.sql (100%) rename {conf => schema}/evolutions/reversions/018-hybrid-annotations.sql (100%) rename {conf => schema}/evolutions/reversions/019-dataset-lastusedtime.sql (100%) rename {conf => schema}/evolutions/reversions/021-list-experiences.sql (100%) rename {conf => schema}/evolutions/reversions/022-add-foreign-datastore.sql (100%) rename {conf => schema}/evolutions/reversions/023-drop-datastore-type.sql (100%) rename {conf => schema}/evolutions/reversions/024-drop-md5hash.sql (100%) rename {conf => schema}/evolutions/reversions/025-add-dataset-sortingKey.sql (100%) rename {conf => schema}/evolutions/reversions/026-decrease-total-instance.sql (100%) rename {conf => schema}/evolutions/reversions/027-drop-dataset-name-unique-constraint.sql (100%) rename {conf => schema}/evolutions/reversions/028-add-isBlacklistedFromReport.sql (100%) rename {conf => schema}/evolutions/reversions/029-foreign-keys-deferrable.sql (100%) rename {conf => schema}/evolutions/reversions/030-tracingstore.sql (100%) rename {conf => schema}/evolutions/reversions/031-maintenance.sql (100%) rename {conf => schema}/evolutions/reversions/032-scratch-datastores.sql (100%) rename {conf => schema}/evolutions/reversions/033-tasktype-recommendedConfiguration.sql (100%) rename {conf => schema}/evolutions/reversions/034-meshes.sql (100%) rename {conf => schema}/evolutions/reversions/035-add-annotation-dataset-foreign-key.sql (100%) rename {conf => schema}/evolutions/reversions/036-add-lastTaskTypeId-to-user.sql (100%) rename {conf => schema}/evolutions/reversions/037-add-publications.sql (100%) rename {conf => schema}/evolutions/reversions/038-more-voxel-types.sql (100%) rename {conf => schema}/evolutions/reversions/039-add-tasktype-tracingtype.sql (100%) rename {conf => schema}/evolutions/reversions/040-add-auto-activate-per-orga.sql (100%) rename {conf => schema}/evolutions/reversions/041-add-datastore-isconnector.sql (100%) rename {conf => schema}/evolutions/reversions/042-add-json-object-constraints.sql (100%) rename {conf => schema}/evolutions/reversions/043-annotationsettings-allowedMagnifications.sql (100%) rename {conf => schema}/evolutions/reversions/044-datasource-hash.sql (100%) rename {conf => schema}/evolutions/reversions/045-annotationsettings-mergerMode.sql (100%) rename {conf => schema}/evolutions/reversions/046-fix-missing-voxel-type.sql (100%) rename {conf => schema}/evolutions/reversions/047-add-datastore-publicUrl.sql (100%) rename {conf => schema}/evolutions/reversions/048-add-tracingstore-publicUrl.sql (100%) rename {conf => schema}/evolutions/reversions/048-annotation-listed-teams.sql (100%) rename {conf => schema}/evolutions/reversions/049-annotation-listed-teams.sql (100%) rename {conf => schema}/evolutions/reversions/050-add-annotation-visibility-enum.sql (100%) rename {conf => schema}/evolutions/reversions/051-add-source-view-configuration.sql (100%) rename {conf => schema}/evolutions/reversions/052-replace-segmentation-opacity.sql (100%) rename {conf => schema}/evolutions/reversions/053-add-allowsUpload.sql (100%) rename {conf => schema}/evolutions/reversions/054-add-isDatasetManager.sql (100%) rename {conf => schema}/evolutions/reversions/055-make-organization-name-unique.sql (100%) rename {conf => schema}/evolutions/reversions/056-add-jobs.sql (100%) rename {conf => schema}/evolutions/reversions/057-add-layer-specific-view-configs.sql (100%) rename {conf => schema}/evolutions/reversions/058-add-onlyAllowedOrganization.sql (100%) rename {conf => schema}/evolutions/reversions/059-resolution-restrictions.sql (100%) rename {conf => schema}/evolutions/reversions/060-multiusers.sql (100%) rename {conf => schema}/evolutions/reversions/061-userinfos-view.sql (100%) rename {conf => schema}/evolutions/reversions/062-dataset-uploader.sql (100%) rename {conf => schema}/evolutions/reversions/063-novelUserExperienceinfos.sql (100%) rename {conf => schema}/evolutions/reversions/064-experienceDomains-per-orga.sql (100%) rename {conf => schema}/evolutions/reversions/065-unlisted-superusers.sql (100%) rename {conf => schema}/evolutions/reversions/066-publications-foreign-key.sql (100%) rename {conf => schema}/evolutions/reversions/067-drop-analytics.sql (100%) rename {conf => schema}/evolutions/reversions/068-pricing-plan.sql (100%) rename {conf => schema}/evolutions/reversions/069-tasktype-project-unique-per-orga.sql (100%) rename {conf => schema}/evolutions/reversions/070-dark-theme.sql (100%) rename {conf => schema}/evolutions/reversions/071-adapt-td-view-display-planes.sql (100%) rename {conf => schema}/evolutions/reversions/072-jobs-manually-repaired.sql (100%) rename {conf => schema}/evolutions/reversions/073-modern-controls-user-conf.sql (100%) rename {conf => schema}/evolutions/reversions/074-jobs-owner-foreign-key.sql (100%) rename {conf => schema}/evolutions/reversions/075-tasktype-remove-hovered-cell-id.sql (100%) rename {conf => schema}/evolutions/reversions/076-jobs-enabled-per-datastore.sql (100%) rename {conf => schema}/evolutions/reversions/077-workers.sql (100%) rename {conf => schema}/evolutions/reversions/078-annotation-layers.sql (100%) rename {conf => schema}/evolutions/reversions/079-add-dataset-tags.sql (100%) rename {conf => schema}/evolutions/reversions/080-job-add-cancelled.sql (100%) rename {conf => schema}/evolutions/reversions/081-annotation-viewconfiguration.sql (100%) rename {conf => schema}/evolutions/reversions/082-annotationsettings-volumeInterpolationAllowed.sql (100%) rename {conf => schema}/evolutions/reversions/083-unique-layer-names.sql (100%) rename {conf => schema}/evolutions/reversions/084-annotation-contributors.sql (100%) rename {conf => schema}/evolutions/reversions/085-annotation-publication.sql (100%) rename {conf => schema}/evolutions/reversions/086-drop-foreign-datastores.sql (100%) rename {conf => schema}/evolutions/reversions/087-zarr-private-links.sql (100%) rename {conf => schema}/evolutions/reversions/088-shortlinks.sql (100%) rename {conf => schema}/evolutions/reversions/089-voxelytics.sql (100%) rename {conf => schema}/evolutions/reversions/090-cleanup.sql (100%) rename {conf => schema}/evolutions/reversions/091-folders.sql (100%) rename {conf => schema}/evolutions/reversions/092-oidc.sql (100%) rename {conf => schema}/evolutions/reversions/093-terms-of-service.sql (100%) rename {conf => schema}/evolutions/reversions/094-pricing-plans.sql (100%) rename {conf => schema}/evolutions/reversions/095-constraint-naming.sql (100%) rename {conf => schema}/evolutions/reversions/096-storage.sql (100%) rename {conf => schema}/evolutions/reversions/097-credentials.sql (100%) rename {conf => schema}/evolutions/reversions/098-voxelytics-states.sql (100%) rename {conf => schema}/evolutions/reversions/099-rename-credential-types.sql (100%) rename {conf => schema}/evolutions/reversions/100-annotation-mutexes.sql (100%) rename {conf => schema}/evolutions/reversions/101-coordinate-transformations.sql (100%) rename {conf => schema}/evolutions/reversions/102-no-more-wkconnect.sql (100%) rename {conf => schema}/evolutions/reversions/103-thin-plane-splines.sql (100%) rename {conf => schema}/evolutions/reversions/104-thumbnails.sql (100%) rename {conf => schema}/evolutions/reversions/105-verify-email.sql (100%) rename {conf => schema}/evolutions/reversions/106-folder-no-slashes.sql (100%) rename {conf => schema}/evolutions/reversions/107-task-terminology.sql (100%) rename {conf => schema}/evolutions/reversions/108-additional-coordinates.sql (100%) rename {conf => schema}/evolutions/reversions/109-scheduled-maintenances.sql (100%) rename {conf => schema}/evolutions/reversions/110-worker-config.sql (100%) rename {conf => schema}/evolutions/reversions/111-stats-per-annotation-layer.sql (100%) rename {conf => schema}/evolutions/reversions/112-reuse-deleted.sql (100%) rename {conf => schema}/evolutions/reversions/113-analytics-events.sql (100%) rename {conf => schema}/evolutions/reversions/114-ai-models.sql (100%) rename {conf => schema}/evolutions/reversions/115-annotation-locked-by-user.sql (100%) rename {conf => schema}/evolutions/reversions/116-drop-overtimemailinglist.sql (100%) rename {conf => schema}/evolutions/reversions/117-voxel-size-unit.sql (100%) rename {conf => schema}/evolutions/reversions/118-voxelytics-artifacts-index.sql (100%) rename {conf => schema}/evolutions/reversions/119-add-metadata-to-folders-and-datasets.sql (100%) rename {conf => schema}/evolutions/reversions/120-remove-old-organization-id.sql (100%) rename {conf => schema}/evolutions/reversions/121-worker-name.sql (100%) rename {conf => schema}/evolutions/reversions/122-resolution-to-mag.sql (100%) rename {conf => schema}/evolutions/reversions/123-more-model-categories.sql (100%) rename {conf => schema}/evolutions/reversions/124-decouple-dataset-directory-from-name.sql (100%) rename {conf => schema}/evolutions/reversions/125-allow-dollar-in-layer-names.sql (100%) rename {conf => schema}/evolutions/reversions/126-mag-real-paths.sql (100%) rename {conf => schema}/evolutions/reversions/127-job-retried-by-super-user.sql (100%) rename {conf => schema}/evolutions/reversions/128-allow-ai-model-sharing.sql (100%) rename {conf => schema}/evolutions/reversions/129-credit-transactions.sql (100%) rename {conf => schema}/evolutions/reversions/130-replace-text-types.sql (100%) rename {conf => schema}/evolutions/reversions/131-more-indices-on-users.sql (100%) rename {conf => schema}/evolutions/reversions/132-remove-stored-meshes.sql (100%) rename {conf => schema}/evolutions/reversions/133-datasource-properties-in-db.sql (100%) rename {conf => schema}/evolutions/reversions/134-dataset-layer-attachments.sql (100%) rename {conf => schema}/evolutions/reversions/135-neuroglancer-attachment.sql (100%) rename {conf => schema}/evolutions/reversions/136-extra-column-for-email-changed.sql (100%) rename {conf => schema}/evolutions/reversions/137-virtual-datasets.sql (100%) rename {conf => schema}/evolutions/reversions/138-add-webauthn-credentials.sql (100%) rename {conf => schema}/evolutions/reversions/139-logout-everywhere.sql (100%) rename {conf => schema}/evolutions/reversions/140-annotation-layer-name-check-deferrable.sql (100%) rename {conf => schema}/evolutions/reversions/141-allows-upload-to-paths.sql (100%) rename {conf => schema}/evolutions/reversions/142-personal-plan.sql (100%) rename {conf => schema}/evolutions/reversions/143-remote-storage-analysis.sql (100%) rename {conf => schema}/evolutions/reversions/144-improve-storage-scan.sql (100%) rename {conf => schema}/evolutions/reversions/145-attachment-realpaths.sql (100%) rename {tools/postgres => schema}/schema.sql (100%) diff --git a/conf/evolutions/000-initial-schema.sql b/schema/evolutions/000-initial-schema.sql similarity index 100% rename from conf/evolutions/000-initial-schema.sql rename to schema/evolutions/000-initial-schema.sql diff --git a/conf/evolutions/001-add-organizations.sql b/schema/evolutions/001-add-organizations.sql similarity index 100% rename from conf/evolutions/001-add-organizations.sql rename to schema/evolutions/001-add-organizations.sql diff --git a/conf/evolutions/002-add-dataset-urlsharing-token.sql b/schema/evolutions/002-add-dataset-urlsharing-token.sql similarity index 100% rename from conf/evolutions/002-add-dataset-urlsharing-token.sql rename to schema/evolutions/002-add-dataset-urlsharing-token.sql diff --git a/conf/evolutions/003-add-dataset-displayname.sql b/schema/evolutions/003-add-dataset-displayname.sql similarity index 100% rename from conf/evolutions/003-add-dataset-displayname.sql rename to schema/evolutions/003-add-dataset-displayname.sql diff --git a/conf/evolutions/004-add-initializing-annotation-state.sql b/schema/evolutions/004-add-initializing-annotation-state.sql similarity index 100% rename from conf/evolutions/004-add-initializing-annotation-state.sql rename to schema/evolutions/004-add-initializing-annotation-state.sql diff --git a/conf/evolutions/005-add-openinstances-trigger.sql b/schema/evolutions/005-add-openinstances-trigger.sql similarity index 100% rename from conf/evolutions/005-add-openinstances-trigger.sql rename to schema/evolutions/005-add-openinstances-trigger.sql diff --git a/conf/evolutions/007-unify-type-datalayer-name.sql b/schema/evolutions/007-unify-type-datalayer-name.sql similarity index 100% rename from conf/evolutions/007-unify-type-datalayer-name.sql rename to schema/evolutions/007-unify-type-datalayer-name.sql diff --git a/conf/evolutions/008-task-instances-triggers.sql b/schema/evolutions/008-task-instances-triggers.sql similarity index 100% rename from conf/evolutions/008-task-instances-triggers.sql rename to schema/evolutions/008-task-instances-triggers.sql diff --git a/conf/evolutions/009-remove-team-assignment-from-task.sql b/schema/evolutions/009-remove-team-assignment-from-task.sql similarity index 100% rename from conf/evolutions/009-remove-team-assignment-from-task.sql rename to schema/evolutions/009-remove-team-assignment-from-task.sql diff --git a/conf/evolutions/010-add-organization-data.sql b/schema/evolutions/010-add-organization-data.sql similarity index 100% rename from conf/evolutions/010-add-organization-data.sql rename to schema/evolutions/010-add-organization-data.sql diff --git a/conf/evolutions/011-add-isOrganizationTeam.sql b/schema/evolutions/011-add-isOrganizationTeam.sql similarity index 100% rename from conf/evolutions/011-add-isOrganizationTeam.sql rename to schema/evolutions/011-add-isOrganizationTeam.sql diff --git a/conf/evolutions/012-add-foreign-keys.sql b/schema/evolutions/012-add-foreign-keys.sql similarity index 100% rename from conf/evolutions/012-add-foreign-keys.sql rename to schema/evolutions/012-add-foreign-keys.sql diff --git a/conf/evolutions/013-add-logoUrl.sql b/schema/evolutions/013-add-logoUrl.sql similarity index 100% rename from conf/evolutions/013-add-logoUrl.sql rename to schema/evolutions/013-add-logoUrl.sql diff --git a/conf/evolutions/014-equalize-schema-and-evolutions.sql b/schema/evolutions/014-equalize-schema-and-evolutions.sql similarity index 100% rename from conf/evolutions/014-equalize-schema-and-evolutions.sql rename to schema/evolutions/014-equalize-schema-and-evolutions.sql diff --git a/conf/evolutions/015-add-organization-displayname.sql b/schema/evolutions/015-add-organization-displayname.sql similarity index 100% rename from conf/evolutions/015-add-organization-displayname.sql rename to schema/evolutions/015-add-organization-displayname.sql diff --git a/conf/evolutions/016-add-schema-version.sql b/schema/evolutions/016-add-schema-version.sql similarity index 100% rename from conf/evolutions/016-add-schema-version.sql rename to schema/evolutions/016-add-schema-version.sql diff --git a/conf/evolutions/017-add-organization-email.sql b/schema/evolutions/017-add-organization-email.sql similarity index 100% rename from conf/evolutions/017-add-organization-email.sql rename to schema/evolutions/017-add-organization-email.sql diff --git a/conf/evolutions/018-hybrid-annotations.sql b/schema/evolutions/018-hybrid-annotations.sql similarity index 100% rename from conf/evolutions/018-hybrid-annotations.sql rename to schema/evolutions/018-hybrid-annotations.sql diff --git a/conf/evolutions/019-dataset-lastusedtime.sql b/schema/evolutions/019-dataset-lastusedtime.sql similarity index 100% rename from conf/evolutions/019-dataset-lastusedtime.sql rename to schema/evolutions/019-dataset-lastusedtime.sql diff --git a/conf/evolutions/021-list-experiences.sql b/schema/evolutions/021-list-experiences.sql similarity index 100% rename from conf/evolutions/021-list-experiences.sql rename to schema/evolutions/021-list-experiences.sql diff --git a/conf/evolutions/022-add-foreign-datastore.sql b/schema/evolutions/022-add-foreign-datastore.sql similarity index 100% rename from conf/evolutions/022-add-foreign-datastore.sql rename to schema/evolutions/022-add-foreign-datastore.sql diff --git a/conf/evolutions/023-drop-datastore-type.sql b/schema/evolutions/023-drop-datastore-type.sql similarity index 100% rename from conf/evolutions/023-drop-datastore-type.sql rename to schema/evolutions/023-drop-datastore-type.sql diff --git a/conf/evolutions/024-drop-md5hash.sql b/schema/evolutions/024-drop-md5hash.sql similarity index 100% rename from conf/evolutions/024-drop-md5hash.sql rename to schema/evolutions/024-drop-md5hash.sql diff --git a/conf/evolutions/025-add-dataset-sortingKey.sql b/schema/evolutions/025-add-dataset-sortingKey.sql similarity index 100% rename from conf/evolutions/025-add-dataset-sortingKey.sql rename to schema/evolutions/025-add-dataset-sortingKey.sql diff --git a/conf/evolutions/026-decrease-total-instance.sql b/schema/evolutions/026-decrease-total-instance.sql similarity index 100% rename from conf/evolutions/026-decrease-total-instance.sql rename to schema/evolutions/026-decrease-total-instance.sql diff --git a/conf/evolutions/027-drop-dataset-name-unique-constraint.sql b/schema/evolutions/027-drop-dataset-name-unique-constraint.sql similarity index 100% rename from conf/evolutions/027-drop-dataset-name-unique-constraint.sql rename to schema/evolutions/027-drop-dataset-name-unique-constraint.sql diff --git a/conf/evolutions/028-add-isBlacklistedFromReport.sql b/schema/evolutions/028-add-isBlacklistedFromReport.sql similarity index 100% rename from conf/evolutions/028-add-isBlacklistedFromReport.sql rename to schema/evolutions/028-add-isBlacklistedFromReport.sql diff --git a/conf/evolutions/029-foreign-keys-deferrable.sql b/schema/evolutions/029-foreign-keys-deferrable.sql similarity index 100% rename from conf/evolutions/029-foreign-keys-deferrable.sql rename to schema/evolutions/029-foreign-keys-deferrable.sql diff --git a/conf/evolutions/030-tracingstore.sql b/schema/evolutions/030-tracingstore.sql similarity index 100% rename from conf/evolutions/030-tracingstore.sql rename to schema/evolutions/030-tracingstore.sql diff --git a/conf/evolutions/031-maintenance.sql b/schema/evolutions/031-maintenance.sql similarity index 100% rename from conf/evolutions/031-maintenance.sql rename to schema/evolutions/031-maintenance.sql diff --git a/conf/evolutions/032-scratch-datastores.sql b/schema/evolutions/032-scratch-datastores.sql similarity index 100% rename from conf/evolutions/032-scratch-datastores.sql rename to schema/evolutions/032-scratch-datastores.sql diff --git a/conf/evolutions/033-tasktype-recommendedConfiguration.sql b/schema/evolutions/033-tasktype-recommendedConfiguration.sql similarity index 100% rename from conf/evolutions/033-tasktype-recommendedConfiguration.sql rename to schema/evolutions/033-tasktype-recommendedConfiguration.sql diff --git a/conf/evolutions/034-meshes.sql b/schema/evolutions/034-meshes.sql similarity index 100% rename from conf/evolutions/034-meshes.sql rename to schema/evolutions/034-meshes.sql diff --git a/conf/evolutions/035-add-annotation-dataset-foreign-key.sql b/schema/evolutions/035-add-annotation-dataset-foreign-key.sql similarity index 100% rename from conf/evolutions/035-add-annotation-dataset-foreign-key.sql rename to schema/evolutions/035-add-annotation-dataset-foreign-key.sql diff --git a/conf/evolutions/036-add-lastTaskTypeId-to-user.sql b/schema/evolutions/036-add-lastTaskTypeId-to-user.sql similarity index 100% rename from conf/evolutions/036-add-lastTaskTypeId-to-user.sql rename to schema/evolutions/036-add-lastTaskTypeId-to-user.sql diff --git a/conf/evolutions/037-add-publications.sql b/schema/evolutions/037-add-publications.sql similarity index 100% rename from conf/evolutions/037-add-publications.sql rename to schema/evolutions/037-add-publications.sql diff --git a/conf/evolutions/038-more-voxel-types.sql b/schema/evolutions/038-more-voxel-types.sql similarity index 100% rename from conf/evolutions/038-more-voxel-types.sql rename to schema/evolutions/038-more-voxel-types.sql diff --git a/conf/evolutions/039-add-tasktype-tracingtype.sql b/schema/evolutions/039-add-tasktype-tracingtype.sql similarity index 100% rename from conf/evolutions/039-add-tasktype-tracingtype.sql rename to schema/evolutions/039-add-tasktype-tracingtype.sql diff --git a/conf/evolutions/040-add-auto-activate-per-orga.sql b/schema/evolutions/040-add-auto-activate-per-orga.sql similarity index 100% rename from conf/evolutions/040-add-auto-activate-per-orga.sql rename to schema/evolutions/040-add-auto-activate-per-orga.sql diff --git a/conf/evolutions/041-add-datastore-isconnector.sql b/schema/evolutions/041-add-datastore-isconnector.sql similarity index 100% rename from conf/evolutions/041-add-datastore-isconnector.sql rename to schema/evolutions/041-add-datastore-isconnector.sql diff --git a/conf/evolutions/042-add-json-object-constraints.sql b/schema/evolutions/042-add-json-object-constraints.sql similarity index 100% rename from conf/evolutions/042-add-json-object-constraints.sql rename to schema/evolutions/042-add-json-object-constraints.sql diff --git a/conf/evolutions/043-annotationsettings-allowedMagnifications.sql b/schema/evolutions/043-annotationsettings-allowedMagnifications.sql similarity index 100% rename from conf/evolutions/043-annotationsettings-allowedMagnifications.sql rename to schema/evolutions/043-annotationsettings-allowedMagnifications.sql diff --git a/conf/evolutions/044-datasource-hash.sql b/schema/evolutions/044-datasource-hash.sql similarity index 100% rename from conf/evolutions/044-datasource-hash.sql rename to schema/evolutions/044-datasource-hash.sql diff --git a/conf/evolutions/045-annotationsettings-mergerMode.sql b/schema/evolutions/045-annotationsettings-mergerMode.sql similarity index 100% rename from conf/evolutions/045-annotationsettings-mergerMode.sql rename to schema/evolutions/045-annotationsettings-mergerMode.sql diff --git a/conf/evolutions/046-fix-missing-voxel-type.sql b/schema/evolutions/046-fix-missing-voxel-type.sql similarity index 100% rename from conf/evolutions/046-fix-missing-voxel-type.sql rename to schema/evolutions/046-fix-missing-voxel-type.sql diff --git a/conf/evolutions/047-add-datastore-publicUrl.sql b/schema/evolutions/047-add-datastore-publicUrl.sql similarity index 100% rename from conf/evolutions/047-add-datastore-publicUrl.sql rename to schema/evolutions/047-add-datastore-publicUrl.sql diff --git a/conf/evolutions/048-add-tracingstore-publicUrl.sql b/schema/evolutions/048-add-tracingstore-publicUrl.sql similarity index 100% rename from conf/evolutions/048-add-tracingstore-publicUrl.sql rename to schema/evolutions/048-add-tracingstore-publicUrl.sql diff --git a/conf/evolutions/049-annotation-listed-teams.sql b/schema/evolutions/049-annotation-listed-teams.sql similarity index 100% rename from conf/evolutions/049-annotation-listed-teams.sql rename to schema/evolutions/049-annotation-listed-teams.sql diff --git a/conf/evolutions/050-add-annotation-visibility-enum.sql b/schema/evolutions/050-add-annotation-visibility-enum.sql similarity index 100% rename from conf/evolutions/050-add-annotation-visibility-enum.sql rename to schema/evolutions/050-add-annotation-visibility-enum.sql diff --git a/conf/evolutions/051-add-source-view-configuration.sql b/schema/evolutions/051-add-source-view-configuration.sql similarity index 100% rename from conf/evolutions/051-add-source-view-configuration.sql rename to schema/evolutions/051-add-source-view-configuration.sql diff --git a/conf/evolutions/052-replace-segmentation-opacity.sql b/schema/evolutions/052-replace-segmentation-opacity.sql similarity index 100% rename from conf/evolutions/052-replace-segmentation-opacity.sql rename to schema/evolutions/052-replace-segmentation-opacity.sql diff --git a/conf/evolutions/053-add-allowsUpload.sql b/schema/evolutions/053-add-allowsUpload.sql similarity index 100% rename from conf/evolutions/053-add-allowsUpload.sql rename to schema/evolutions/053-add-allowsUpload.sql diff --git a/conf/evolutions/054-add-isDatasetManager.sql b/schema/evolutions/054-add-isDatasetManager.sql similarity index 100% rename from conf/evolutions/054-add-isDatasetManager.sql rename to schema/evolutions/054-add-isDatasetManager.sql diff --git a/conf/evolutions/055-make-organization-name-unique.sql b/schema/evolutions/055-make-organization-name-unique.sql similarity index 100% rename from conf/evolutions/055-make-organization-name-unique.sql rename to schema/evolutions/055-make-organization-name-unique.sql diff --git a/conf/evolutions/056-add-jobs.sql b/schema/evolutions/056-add-jobs.sql similarity index 100% rename from conf/evolutions/056-add-jobs.sql rename to schema/evolutions/056-add-jobs.sql diff --git a/conf/evolutions/057-add-layer-specific-view-configs.sql b/schema/evolutions/057-add-layer-specific-view-configs.sql similarity index 100% rename from conf/evolutions/057-add-layer-specific-view-configs.sql rename to schema/evolutions/057-add-layer-specific-view-configs.sql diff --git a/conf/evolutions/058-add-onlyAllowedOrganization.sql b/schema/evolutions/058-add-onlyAllowedOrganization.sql similarity index 100% rename from conf/evolutions/058-add-onlyAllowedOrganization.sql rename to schema/evolutions/058-add-onlyAllowedOrganization.sql diff --git a/conf/evolutions/059-resolution-restrictions.sql b/schema/evolutions/059-resolution-restrictions.sql similarity index 100% rename from conf/evolutions/059-resolution-restrictions.sql rename to schema/evolutions/059-resolution-restrictions.sql diff --git a/conf/evolutions/060-multiusers.sql b/schema/evolutions/060-multiusers.sql similarity index 100% rename from conf/evolutions/060-multiusers.sql rename to schema/evolutions/060-multiusers.sql diff --git a/conf/evolutions/061-userinfos-view.sql b/schema/evolutions/061-userinfos-view.sql similarity index 100% rename from conf/evolutions/061-userinfos-view.sql rename to schema/evolutions/061-userinfos-view.sql diff --git a/conf/evolutions/062-dataset-uploader.sql b/schema/evolutions/062-dataset-uploader.sql similarity index 100% rename from conf/evolutions/062-dataset-uploader.sql rename to schema/evolutions/062-dataset-uploader.sql diff --git a/conf/evolutions/063-novelUserExperienceinfos.sql b/schema/evolutions/063-novelUserExperienceinfos.sql similarity index 100% rename from conf/evolutions/063-novelUserExperienceinfos.sql rename to schema/evolutions/063-novelUserExperienceinfos.sql diff --git a/conf/evolutions/064-experienceDomains-per-orga.sql b/schema/evolutions/064-experienceDomains-per-orga.sql similarity index 100% rename from conf/evolutions/064-experienceDomains-per-orga.sql rename to schema/evolutions/064-experienceDomains-per-orga.sql diff --git a/conf/evolutions/065-unlisted-superusers.sql b/schema/evolutions/065-unlisted-superusers.sql similarity index 100% rename from conf/evolutions/065-unlisted-superusers.sql rename to schema/evolutions/065-unlisted-superusers.sql diff --git a/conf/evolutions/066-publications-foreign-key.sql b/schema/evolutions/066-publications-foreign-key.sql similarity index 100% rename from conf/evolutions/066-publications-foreign-key.sql rename to schema/evolutions/066-publications-foreign-key.sql diff --git a/conf/evolutions/067-drop-analytics.sql b/schema/evolutions/067-drop-analytics.sql similarity index 100% rename from conf/evolutions/067-drop-analytics.sql rename to schema/evolutions/067-drop-analytics.sql diff --git a/conf/evolutions/068-pricing-plan.sql b/schema/evolutions/068-pricing-plan.sql similarity index 100% rename from conf/evolutions/068-pricing-plan.sql rename to schema/evolutions/068-pricing-plan.sql diff --git a/conf/evolutions/069-tasktype-project-unique-per-orga.sql b/schema/evolutions/069-tasktype-project-unique-per-orga.sql similarity index 100% rename from conf/evolutions/069-tasktype-project-unique-per-orga.sql rename to schema/evolutions/069-tasktype-project-unique-per-orga.sql diff --git a/conf/evolutions/070-dark-theme.sql b/schema/evolutions/070-dark-theme.sql similarity index 100% rename from conf/evolutions/070-dark-theme.sql rename to schema/evolutions/070-dark-theme.sql diff --git a/conf/evolutions/071-adapt-td-view-display-planes.sql b/schema/evolutions/071-adapt-td-view-display-planes.sql similarity index 100% rename from conf/evolutions/071-adapt-td-view-display-planes.sql rename to schema/evolutions/071-adapt-td-view-display-planes.sql diff --git a/conf/evolutions/072-jobs-manually-repaired.sql b/schema/evolutions/072-jobs-manually-repaired.sql similarity index 100% rename from conf/evolutions/072-jobs-manually-repaired.sql rename to schema/evolutions/072-jobs-manually-repaired.sql diff --git a/conf/evolutions/073-modern-controls-user-conf.sql b/schema/evolutions/073-modern-controls-user-conf.sql similarity index 100% rename from conf/evolutions/073-modern-controls-user-conf.sql rename to schema/evolutions/073-modern-controls-user-conf.sql diff --git a/conf/evolutions/074-jobs-owner-foreign-key.sql b/schema/evolutions/074-jobs-owner-foreign-key.sql similarity index 100% rename from conf/evolutions/074-jobs-owner-foreign-key.sql rename to schema/evolutions/074-jobs-owner-foreign-key.sql diff --git a/conf/evolutions/075-tasktype-remove-hovered-cell-id.sql b/schema/evolutions/075-tasktype-remove-hovered-cell-id.sql similarity index 100% rename from conf/evolutions/075-tasktype-remove-hovered-cell-id.sql rename to schema/evolutions/075-tasktype-remove-hovered-cell-id.sql diff --git a/conf/evolutions/076-jobs-enabled-per-datastore.sql b/schema/evolutions/076-jobs-enabled-per-datastore.sql similarity index 100% rename from conf/evolutions/076-jobs-enabled-per-datastore.sql rename to schema/evolutions/076-jobs-enabled-per-datastore.sql diff --git a/conf/evolutions/077-workers.sql b/schema/evolutions/077-workers.sql similarity index 100% rename from conf/evolutions/077-workers.sql rename to schema/evolutions/077-workers.sql diff --git a/conf/evolutions/078-annotation-layers.sql b/schema/evolutions/078-annotation-layers.sql similarity index 100% rename from conf/evolutions/078-annotation-layers.sql rename to schema/evolutions/078-annotation-layers.sql diff --git a/conf/evolutions/079-add-dataset-tags.sql b/schema/evolutions/079-add-dataset-tags.sql similarity index 100% rename from conf/evolutions/079-add-dataset-tags.sql rename to schema/evolutions/079-add-dataset-tags.sql diff --git a/conf/evolutions/080-job-add-cancelled.sql b/schema/evolutions/080-job-add-cancelled.sql similarity index 100% rename from conf/evolutions/080-job-add-cancelled.sql rename to schema/evolutions/080-job-add-cancelled.sql diff --git a/conf/evolutions/081-annotation-viewconfiguration.sql b/schema/evolutions/081-annotation-viewconfiguration.sql similarity index 100% rename from conf/evolutions/081-annotation-viewconfiguration.sql rename to schema/evolutions/081-annotation-viewconfiguration.sql diff --git a/conf/evolutions/082-annotationsettings-volumeInterpolationAllowed.sql b/schema/evolutions/082-annotationsettings-volumeInterpolationAllowed.sql similarity index 100% rename from conf/evolutions/082-annotationsettings-volumeInterpolationAllowed.sql rename to schema/evolutions/082-annotationsettings-volumeInterpolationAllowed.sql diff --git a/conf/evolutions/083-unique-layer-names.sql b/schema/evolutions/083-unique-layer-names.sql similarity index 100% rename from conf/evolutions/083-unique-layer-names.sql rename to schema/evolutions/083-unique-layer-names.sql diff --git a/conf/evolutions/084-annotation-contributors.sql b/schema/evolutions/084-annotation-contributors.sql similarity index 100% rename from conf/evolutions/084-annotation-contributors.sql rename to schema/evolutions/084-annotation-contributors.sql diff --git a/conf/evolutions/085-annotation-publication.sql b/schema/evolutions/085-annotation-publication.sql similarity index 100% rename from conf/evolutions/085-annotation-publication.sql rename to schema/evolutions/085-annotation-publication.sql diff --git a/conf/evolutions/086-drop-foreign-datastores.sql b/schema/evolutions/086-drop-foreign-datastores.sql similarity index 100% rename from conf/evolutions/086-drop-foreign-datastores.sql rename to schema/evolutions/086-drop-foreign-datastores.sql diff --git a/conf/evolutions/087-zarr-private-links.sql b/schema/evolutions/087-zarr-private-links.sql similarity index 100% rename from conf/evolutions/087-zarr-private-links.sql rename to schema/evolutions/087-zarr-private-links.sql diff --git a/conf/evolutions/088-shortlinks.sql b/schema/evolutions/088-shortlinks.sql similarity index 100% rename from conf/evolutions/088-shortlinks.sql rename to schema/evolutions/088-shortlinks.sql diff --git a/conf/evolutions/089-voxelytics.sql b/schema/evolutions/089-voxelytics.sql similarity index 100% rename from conf/evolutions/089-voxelytics.sql rename to schema/evolutions/089-voxelytics.sql diff --git a/conf/evolutions/090-cleanup.sql b/schema/evolutions/090-cleanup.sql similarity index 100% rename from conf/evolutions/090-cleanup.sql rename to schema/evolutions/090-cleanup.sql diff --git a/conf/evolutions/091-folders.sql b/schema/evolutions/091-folders.sql similarity index 100% rename from conf/evolutions/091-folders.sql rename to schema/evolutions/091-folders.sql diff --git a/conf/evolutions/092-oidc.sql b/schema/evolutions/092-oidc.sql similarity index 100% rename from conf/evolutions/092-oidc.sql rename to schema/evolutions/092-oidc.sql diff --git a/conf/evolutions/093-terms-of-service.sql b/schema/evolutions/093-terms-of-service.sql similarity index 100% rename from conf/evolutions/093-terms-of-service.sql rename to schema/evolutions/093-terms-of-service.sql diff --git a/conf/evolutions/094-pricing-plans.sql b/schema/evolutions/094-pricing-plans.sql similarity index 100% rename from conf/evolutions/094-pricing-plans.sql rename to schema/evolutions/094-pricing-plans.sql diff --git a/conf/evolutions/095-constraint-naming.sql b/schema/evolutions/095-constraint-naming.sql similarity index 100% rename from conf/evolutions/095-constraint-naming.sql rename to schema/evolutions/095-constraint-naming.sql diff --git a/conf/evolutions/096-storage.sql b/schema/evolutions/096-storage.sql similarity index 100% rename from conf/evolutions/096-storage.sql rename to schema/evolutions/096-storage.sql diff --git a/conf/evolutions/097-credentials.sql b/schema/evolutions/097-credentials.sql similarity index 100% rename from conf/evolutions/097-credentials.sql rename to schema/evolutions/097-credentials.sql diff --git a/conf/evolutions/098-voxelytics-states.sql b/schema/evolutions/098-voxelytics-states.sql similarity index 100% rename from conf/evolutions/098-voxelytics-states.sql rename to schema/evolutions/098-voxelytics-states.sql diff --git a/conf/evolutions/099-rename-credential-types.sql b/schema/evolutions/099-rename-credential-types.sql similarity index 100% rename from conf/evolutions/099-rename-credential-types.sql rename to schema/evolutions/099-rename-credential-types.sql diff --git a/conf/evolutions/100-annotation-mutexes.sql b/schema/evolutions/100-annotation-mutexes.sql similarity index 100% rename from conf/evolutions/100-annotation-mutexes.sql rename to schema/evolutions/100-annotation-mutexes.sql diff --git a/conf/evolutions/101-coordinate-transformations.sql b/schema/evolutions/101-coordinate-transformations.sql similarity index 100% rename from conf/evolutions/101-coordinate-transformations.sql rename to schema/evolutions/101-coordinate-transformations.sql diff --git a/conf/evolutions/102-no-more-wkconnect.sql b/schema/evolutions/102-no-more-wkconnect.sql similarity index 100% rename from conf/evolutions/102-no-more-wkconnect.sql rename to schema/evolutions/102-no-more-wkconnect.sql diff --git a/conf/evolutions/103-thin-plane-splines.sql b/schema/evolutions/103-thin-plane-splines.sql similarity index 100% rename from conf/evolutions/103-thin-plane-splines.sql rename to schema/evolutions/103-thin-plane-splines.sql diff --git a/conf/evolutions/104-thumbnails.sql b/schema/evolutions/104-thumbnails.sql similarity index 100% rename from conf/evolutions/104-thumbnails.sql rename to schema/evolutions/104-thumbnails.sql diff --git a/conf/evolutions/105-verify-email.sql b/schema/evolutions/105-verify-email.sql similarity index 100% rename from conf/evolutions/105-verify-email.sql rename to schema/evolutions/105-verify-email.sql diff --git a/conf/evolutions/106-folder-no-slashes.sql b/schema/evolutions/106-folder-no-slashes.sql similarity index 100% rename from conf/evolutions/106-folder-no-slashes.sql rename to schema/evolutions/106-folder-no-slashes.sql diff --git a/conf/evolutions/107-task-terminology.sql b/schema/evolutions/107-task-terminology.sql similarity index 100% rename from conf/evolutions/107-task-terminology.sql rename to schema/evolutions/107-task-terminology.sql diff --git a/conf/evolutions/108-additional-coordinates.sql b/schema/evolutions/108-additional-coordinates.sql similarity index 100% rename from conf/evolutions/108-additional-coordinates.sql rename to schema/evolutions/108-additional-coordinates.sql diff --git a/conf/evolutions/109-scheduled-maintenances.sql b/schema/evolutions/109-scheduled-maintenances.sql similarity index 100% rename from conf/evolutions/109-scheduled-maintenances.sql rename to schema/evolutions/109-scheduled-maintenances.sql diff --git a/conf/evolutions/110-worker-config.sql b/schema/evolutions/110-worker-config.sql similarity index 100% rename from conf/evolutions/110-worker-config.sql rename to schema/evolutions/110-worker-config.sql diff --git a/conf/evolutions/111-stats-per-annotation-layer.sql b/schema/evolutions/111-stats-per-annotation-layer.sql similarity index 100% rename from conf/evolutions/111-stats-per-annotation-layer.sql rename to schema/evolutions/111-stats-per-annotation-layer.sql diff --git a/conf/evolutions/112-reuse-deleted.sql b/schema/evolutions/112-reuse-deleted.sql similarity index 100% rename from conf/evolutions/112-reuse-deleted.sql rename to schema/evolutions/112-reuse-deleted.sql diff --git a/conf/evolutions/113-analytics-events.sql b/schema/evolutions/113-analytics-events.sql similarity index 100% rename from conf/evolutions/113-analytics-events.sql rename to schema/evolutions/113-analytics-events.sql diff --git a/conf/evolutions/114-ai-models.sql b/schema/evolutions/114-ai-models.sql similarity index 100% rename from conf/evolutions/114-ai-models.sql rename to schema/evolutions/114-ai-models.sql diff --git a/conf/evolutions/115-annotation-locked-by-user.sql b/schema/evolutions/115-annotation-locked-by-user.sql similarity index 100% rename from conf/evolutions/115-annotation-locked-by-user.sql rename to schema/evolutions/115-annotation-locked-by-user.sql diff --git a/conf/evolutions/116-drop-overtimemailinglist.sql b/schema/evolutions/116-drop-overtimemailinglist.sql similarity index 100% rename from conf/evolutions/116-drop-overtimemailinglist.sql rename to schema/evolutions/116-drop-overtimemailinglist.sql diff --git a/conf/evolutions/117-voxel-size-unit.sql b/schema/evolutions/117-voxel-size-unit.sql similarity index 100% rename from conf/evolutions/117-voxel-size-unit.sql rename to schema/evolutions/117-voxel-size-unit.sql diff --git a/conf/evolutions/118-voxelytics-artifacts-index.sql b/schema/evolutions/118-voxelytics-artifacts-index.sql similarity index 100% rename from conf/evolutions/118-voxelytics-artifacts-index.sql rename to schema/evolutions/118-voxelytics-artifacts-index.sql diff --git a/conf/evolutions/119-add-metadata-to-folders-and-datasets.sql b/schema/evolutions/119-add-metadata-to-folders-and-datasets.sql similarity index 100% rename from conf/evolutions/119-add-metadata-to-folders-and-datasets.sql rename to schema/evolutions/119-add-metadata-to-folders-and-datasets.sql diff --git a/conf/evolutions/120-remove-old-organization-id.sql b/schema/evolutions/120-remove-old-organization-id.sql similarity index 100% rename from conf/evolutions/120-remove-old-organization-id.sql rename to schema/evolutions/120-remove-old-organization-id.sql diff --git a/conf/evolutions/121-worker-name.sql b/schema/evolutions/121-worker-name.sql similarity index 100% rename from conf/evolutions/121-worker-name.sql rename to schema/evolutions/121-worker-name.sql diff --git a/conf/evolutions/122-resolution-to-mag.sql b/schema/evolutions/122-resolution-to-mag.sql similarity index 100% rename from conf/evolutions/122-resolution-to-mag.sql rename to schema/evolutions/122-resolution-to-mag.sql diff --git a/conf/evolutions/123-more-model-categories.sql b/schema/evolutions/123-more-model-categories.sql similarity index 100% rename from conf/evolutions/123-more-model-categories.sql rename to schema/evolutions/123-more-model-categories.sql diff --git a/conf/evolutions/124-decouple-dataset-directory-from-name.sql b/schema/evolutions/124-decouple-dataset-directory-from-name.sql similarity index 100% rename from conf/evolutions/124-decouple-dataset-directory-from-name.sql rename to schema/evolutions/124-decouple-dataset-directory-from-name.sql diff --git a/conf/evolutions/125-allow-dollar-in-layer-names.sql b/schema/evolutions/125-allow-dollar-in-layer-names.sql similarity index 100% rename from conf/evolutions/125-allow-dollar-in-layer-names.sql rename to schema/evolutions/125-allow-dollar-in-layer-names.sql diff --git a/conf/evolutions/126-mag-real-paths.sql b/schema/evolutions/126-mag-real-paths.sql similarity index 100% rename from conf/evolutions/126-mag-real-paths.sql rename to schema/evolutions/126-mag-real-paths.sql diff --git a/conf/evolutions/127-job-retried-by-super-user.sql b/schema/evolutions/127-job-retried-by-super-user.sql similarity index 100% rename from conf/evolutions/127-job-retried-by-super-user.sql rename to schema/evolutions/127-job-retried-by-super-user.sql diff --git a/conf/evolutions/128-allow-ai-model-sharing.sql b/schema/evolutions/128-allow-ai-model-sharing.sql similarity index 100% rename from conf/evolutions/128-allow-ai-model-sharing.sql rename to schema/evolutions/128-allow-ai-model-sharing.sql diff --git a/conf/evolutions/129-credit-transactions.sql b/schema/evolutions/129-credit-transactions.sql similarity index 100% rename from conf/evolutions/129-credit-transactions.sql rename to schema/evolutions/129-credit-transactions.sql diff --git a/conf/evolutions/130-replace-text-types.sql b/schema/evolutions/130-replace-text-types.sql similarity index 100% rename from conf/evolutions/130-replace-text-types.sql rename to schema/evolutions/130-replace-text-types.sql diff --git a/conf/evolutions/131-more-indices-on-users.sql b/schema/evolutions/131-more-indices-on-users.sql similarity index 100% rename from conf/evolutions/131-more-indices-on-users.sql rename to schema/evolutions/131-more-indices-on-users.sql diff --git a/conf/evolutions/132-remove-stored-meshes.sql b/schema/evolutions/132-remove-stored-meshes.sql similarity index 100% rename from conf/evolutions/132-remove-stored-meshes.sql rename to schema/evolutions/132-remove-stored-meshes.sql diff --git a/conf/evolutions/133-datasource-properties-in-db.sql b/schema/evolutions/133-datasource-properties-in-db.sql similarity index 100% rename from conf/evolutions/133-datasource-properties-in-db.sql rename to schema/evolutions/133-datasource-properties-in-db.sql diff --git a/conf/evolutions/134-dataset-layer-attachments.sql b/schema/evolutions/134-dataset-layer-attachments.sql similarity index 100% rename from conf/evolutions/134-dataset-layer-attachments.sql rename to schema/evolutions/134-dataset-layer-attachments.sql diff --git a/conf/evolutions/135-neuroglancer-attachment.sql b/schema/evolutions/135-neuroglancer-attachment.sql similarity index 100% rename from conf/evolutions/135-neuroglancer-attachment.sql rename to schema/evolutions/135-neuroglancer-attachment.sql diff --git a/conf/evolutions/136-extra-column-for-email-changed.sql b/schema/evolutions/136-extra-column-for-email-changed.sql similarity index 100% rename from conf/evolutions/136-extra-column-for-email-changed.sql rename to schema/evolutions/136-extra-column-for-email-changed.sql diff --git a/conf/evolutions/137-virtual-datasets.sql b/schema/evolutions/137-virtual-datasets.sql similarity index 100% rename from conf/evolutions/137-virtual-datasets.sql rename to schema/evolutions/137-virtual-datasets.sql diff --git a/conf/evolutions/138-add-webauthn-credentials.sql b/schema/evolutions/138-add-webauthn-credentials.sql similarity index 100% rename from conf/evolutions/138-add-webauthn-credentials.sql rename to schema/evolutions/138-add-webauthn-credentials.sql diff --git a/conf/evolutions/139-logout-everywhere.sql b/schema/evolutions/139-logout-everywhere.sql similarity index 100% rename from conf/evolutions/139-logout-everywhere.sql rename to schema/evolutions/139-logout-everywhere.sql diff --git a/conf/evolutions/140-annotation-layer-name-check-deferrable.sql b/schema/evolutions/140-annotation-layer-name-check-deferrable.sql similarity index 100% rename from conf/evolutions/140-annotation-layer-name-check-deferrable.sql rename to schema/evolutions/140-annotation-layer-name-check-deferrable.sql diff --git a/conf/evolutions/141-allows-upload-to-paths.sql b/schema/evolutions/141-allows-upload-to-paths.sql similarity index 100% rename from conf/evolutions/141-allows-upload-to-paths.sql rename to schema/evolutions/141-allows-upload-to-paths.sql diff --git a/conf/evolutions/142-personal-plan.sql b/schema/evolutions/142-personal-plan.sql similarity index 100% rename from conf/evolutions/142-personal-plan.sql rename to schema/evolutions/142-personal-plan.sql diff --git a/conf/evolutions/143-remote-storage-analysis.sql b/schema/evolutions/143-remote-storage-analysis.sql similarity index 100% rename from conf/evolutions/143-remote-storage-analysis.sql rename to schema/evolutions/143-remote-storage-analysis.sql diff --git a/conf/evolutions/144-improve-storage-scan.sql b/schema/evolutions/144-improve-storage-scan.sql similarity index 100% rename from conf/evolutions/144-improve-storage-scan.sql rename to schema/evolutions/144-improve-storage-scan.sql diff --git a/conf/evolutions/145-attachment-realpaths.sql b/schema/evolutions/145-attachment-realpaths.sql similarity index 100% rename from conf/evolutions/145-attachment-realpaths.sql rename to schema/evolutions/145-attachment-realpaths.sql diff --git a/conf/evolutions/reversions/001-add-organizations.sql b/schema/evolutions/reversions/001-add-organizations.sql similarity index 100% rename from conf/evolutions/reversions/001-add-organizations.sql rename to schema/evolutions/reversions/001-add-organizations.sql diff --git a/conf/evolutions/reversions/002-add-dataset-urlsharing-token.sql b/schema/evolutions/reversions/002-add-dataset-urlsharing-token.sql similarity index 100% rename from conf/evolutions/reversions/002-add-dataset-urlsharing-token.sql rename to schema/evolutions/reversions/002-add-dataset-urlsharing-token.sql diff --git a/conf/evolutions/reversions/003-add-dataset-displayname.sql b/schema/evolutions/reversions/003-add-dataset-displayname.sql similarity index 100% rename from conf/evolutions/reversions/003-add-dataset-displayname.sql rename to schema/evolutions/reversions/003-add-dataset-displayname.sql diff --git a/conf/evolutions/reversions/004-add-initializing-annotation-state.sql b/schema/evolutions/reversions/004-add-initializing-annotation-state.sql similarity index 100% rename from conf/evolutions/reversions/004-add-initializing-annotation-state.sql rename to schema/evolutions/reversions/004-add-initializing-annotation-state.sql diff --git a/conf/evolutions/reversions/005-add-openinstances-trigger.sql b/schema/evolutions/reversions/005-add-openinstances-trigger.sql similarity index 100% rename from conf/evolutions/reversions/005-add-openinstances-trigger.sql rename to schema/evolutions/reversions/005-add-openinstances-trigger.sql diff --git a/conf/evolutions/reversions/007-unify-type-datalayer-name.sql b/schema/evolutions/reversions/007-unify-type-datalayer-name.sql similarity index 100% rename from conf/evolutions/reversions/007-unify-type-datalayer-name.sql rename to schema/evolutions/reversions/007-unify-type-datalayer-name.sql diff --git a/conf/evolutions/reversions/008-task-instances-triggers.sql b/schema/evolutions/reversions/008-task-instances-triggers.sql similarity index 100% rename from conf/evolutions/reversions/008-task-instances-triggers.sql rename to schema/evolutions/reversions/008-task-instances-triggers.sql diff --git a/conf/evolutions/reversions/009-remove-team-assignment-from-task.sql b/schema/evolutions/reversions/009-remove-team-assignment-from-task.sql similarity index 100% rename from conf/evolutions/reversions/009-remove-team-assignment-from-task.sql rename to schema/evolutions/reversions/009-remove-team-assignment-from-task.sql diff --git a/conf/evolutions/reversions/010-add-organization-data.sql b/schema/evolutions/reversions/010-add-organization-data.sql similarity index 100% rename from conf/evolutions/reversions/010-add-organization-data.sql rename to schema/evolutions/reversions/010-add-organization-data.sql diff --git a/conf/evolutions/reversions/011-add-isOrganizationTeam.sql b/schema/evolutions/reversions/011-add-isOrganizationTeam.sql similarity index 100% rename from conf/evolutions/reversions/011-add-isOrganizationTeam.sql rename to schema/evolutions/reversions/011-add-isOrganizationTeam.sql diff --git a/conf/evolutions/reversions/012-add-foreign-keys.sql b/schema/evolutions/reversions/012-add-foreign-keys.sql similarity index 100% rename from conf/evolutions/reversions/012-add-foreign-keys.sql rename to schema/evolutions/reversions/012-add-foreign-keys.sql diff --git a/conf/evolutions/reversions/013-add-logoUrl.sql b/schema/evolutions/reversions/013-add-logoUrl.sql similarity index 100% rename from conf/evolutions/reversions/013-add-logoUrl.sql rename to schema/evolutions/reversions/013-add-logoUrl.sql diff --git a/conf/evolutions/reversions/014-equalize-schema-and-evolutions.sql b/schema/evolutions/reversions/014-equalize-schema-and-evolutions.sql similarity index 100% rename from conf/evolutions/reversions/014-equalize-schema-and-evolutions.sql rename to schema/evolutions/reversions/014-equalize-schema-and-evolutions.sql diff --git a/conf/evolutions/reversions/015-add-organization-displayname.sql b/schema/evolutions/reversions/015-add-organization-displayname.sql similarity index 100% rename from conf/evolutions/reversions/015-add-organization-displayname.sql rename to schema/evolutions/reversions/015-add-organization-displayname.sql diff --git a/conf/evolutions/reversions/016-add-schema-version.sql b/schema/evolutions/reversions/016-add-schema-version.sql similarity index 100% rename from conf/evolutions/reversions/016-add-schema-version.sql rename to schema/evolutions/reversions/016-add-schema-version.sql diff --git a/conf/evolutions/reversions/017-add-organization-email.sql b/schema/evolutions/reversions/017-add-organization-email.sql similarity index 100% rename from conf/evolutions/reversions/017-add-organization-email.sql rename to schema/evolutions/reversions/017-add-organization-email.sql diff --git a/conf/evolutions/reversions/018-hybrid-annotations.sql b/schema/evolutions/reversions/018-hybrid-annotations.sql similarity index 100% rename from conf/evolutions/reversions/018-hybrid-annotations.sql rename to schema/evolutions/reversions/018-hybrid-annotations.sql diff --git a/conf/evolutions/reversions/019-dataset-lastusedtime.sql b/schema/evolutions/reversions/019-dataset-lastusedtime.sql similarity index 100% rename from conf/evolutions/reversions/019-dataset-lastusedtime.sql rename to schema/evolutions/reversions/019-dataset-lastusedtime.sql diff --git a/conf/evolutions/reversions/021-list-experiences.sql b/schema/evolutions/reversions/021-list-experiences.sql similarity index 100% rename from conf/evolutions/reversions/021-list-experiences.sql rename to schema/evolutions/reversions/021-list-experiences.sql diff --git a/conf/evolutions/reversions/022-add-foreign-datastore.sql b/schema/evolutions/reversions/022-add-foreign-datastore.sql similarity index 100% rename from conf/evolutions/reversions/022-add-foreign-datastore.sql rename to schema/evolutions/reversions/022-add-foreign-datastore.sql diff --git a/conf/evolutions/reversions/023-drop-datastore-type.sql b/schema/evolutions/reversions/023-drop-datastore-type.sql similarity index 100% rename from conf/evolutions/reversions/023-drop-datastore-type.sql rename to schema/evolutions/reversions/023-drop-datastore-type.sql diff --git a/conf/evolutions/reversions/024-drop-md5hash.sql b/schema/evolutions/reversions/024-drop-md5hash.sql similarity index 100% rename from conf/evolutions/reversions/024-drop-md5hash.sql rename to schema/evolutions/reversions/024-drop-md5hash.sql diff --git a/conf/evolutions/reversions/025-add-dataset-sortingKey.sql b/schema/evolutions/reversions/025-add-dataset-sortingKey.sql similarity index 100% rename from conf/evolutions/reversions/025-add-dataset-sortingKey.sql rename to schema/evolutions/reversions/025-add-dataset-sortingKey.sql diff --git a/conf/evolutions/reversions/026-decrease-total-instance.sql b/schema/evolutions/reversions/026-decrease-total-instance.sql similarity index 100% rename from conf/evolutions/reversions/026-decrease-total-instance.sql rename to schema/evolutions/reversions/026-decrease-total-instance.sql diff --git a/conf/evolutions/reversions/027-drop-dataset-name-unique-constraint.sql b/schema/evolutions/reversions/027-drop-dataset-name-unique-constraint.sql similarity index 100% rename from conf/evolutions/reversions/027-drop-dataset-name-unique-constraint.sql rename to schema/evolutions/reversions/027-drop-dataset-name-unique-constraint.sql diff --git a/conf/evolutions/reversions/028-add-isBlacklistedFromReport.sql b/schema/evolutions/reversions/028-add-isBlacklistedFromReport.sql similarity index 100% rename from conf/evolutions/reversions/028-add-isBlacklistedFromReport.sql rename to schema/evolutions/reversions/028-add-isBlacklistedFromReport.sql diff --git a/conf/evolutions/reversions/029-foreign-keys-deferrable.sql b/schema/evolutions/reversions/029-foreign-keys-deferrable.sql similarity index 100% rename from conf/evolutions/reversions/029-foreign-keys-deferrable.sql rename to schema/evolutions/reversions/029-foreign-keys-deferrable.sql diff --git a/conf/evolutions/reversions/030-tracingstore.sql b/schema/evolutions/reversions/030-tracingstore.sql similarity index 100% rename from conf/evolutions/reversions/030-tracingstore.sql rename to schema/evolutions/reversions/030-tracingstore.sql diff --git a/conf/evolutions/reversions/031-maintenance.sql b/schema/evolutions/reversions/031-maintenance.sql similarity index 100% rename from conf/evolutions/reversions/031-maintenance.sql rename to schema/evolutions/reversions/031-maintenance.sql diff --git a/conf/evolutions/reversions/032-scratch-datastores.sql b/schema/evolutions/reversions/032-scratch-datastores.sql similarity index 100% rename from conf/evolutions/reversions/032-scratch-datastores.sql rename to schema/evolutions/reversions/032-scratch-datastores.sql diff --git a/conf/evolutions/reversions/033-tasktype-recommendedConfiguration.sql b/schema/evolutions/reversions/033-tasktype-recommendedConfiguration.sql similarity index 100% rename from conf/evolutions/reversions/033-tasktype-recommendedConfiguration.sql rename to schema/evolutions/reversions/033-tasktype-recommendedConfiguration.sql diff --git a/conf/evolutions/reversions/034-meshes.sql b/schema/evolutions/reversions/034-meshes.sql similarity index 100% rename from conf/evolutions/reversions/034-meshes.sql rename to schema/evolutions/reversions/034-meshes.sql diff --git a/conf/evolutions/reversions/035-add-annotation-dataset-foreign-key.sql b/schema/evolutions/reversions/035-add-annotation-dataset-foreign-key.sql similarity index 100% rename from conf/evolutions/reversions/035-add-annotation-dataset-foreign-key.sql rename to schema/evolutions/reversions/035-add-annotation-dataset-foreign-key.sql diff --git a/conf/evolutions/reversions/036-add-lastTaskTypeId-to-user.sql b/schema/evolutions/reversions/036-add-lastTaskTypeId-to-user.sql similarity index 100% rename from conf/evolutions/reversions/036-add-lastTaskTypeId-to-user.sql rename to schema/evolutions/reversions/036-add-lastTaskTypeId-to-user.sql diff --git a/conf/evolutions/reversions/037-add-publications.sql b/schema/evolutions/reversions/037-add-publications.sql similarity index 100% rename from conf/evolutions/reversions/037-add-publications.sql rename to schema/evolutions/reversions/037-add-publications.sql diff --git a/conf/evolutions/reversions/038-more-voxel-types.sql b/schema/evolutions/reversions/038-more-voxel-types.sql similarity index 100% rename from conf/evolutions/reversions/038-more-voxel-types.sql rename to schema/evolutions/reversions/038-more-voxel-types.sql diff --git a/conf/evolutions/reversions/039-add-tasktype-tracingtype.sql b/schema/evolutions/reversions/039-add-tasktype-tracingtype.sql similarity index 100% rename from conf/evolutions/reversions/039-add-tasktype-tracingtype.sql rename to schema/evolutions/reversions/039-add-tasktype-tracingtype.sql diff --git a/conf/evolutions/reversions/040-add-auto-activate-per-orga.sql b/schema/evolutions/reversions/040-add-auto-activate-per-orga.sql similarity index 100% rename from conf/evolutions/reversions/040-add-auto-activate-per-orga.sql rename to schema/evolutions/reversions/040-add-auto-activate-per-orga.sql diff --git a/conf/evolutions/reversions/041-add-datastore-isconnector.sql b/schema/evolutions/reversions/041-add-datastore-isconnector.sql similarity index 100% rename from conf/evolutions/reversions/041-add-datastore-isconnector.sql rename to schema/evolutions/reversions/041-add-datastore-isconnector.sql diff --git a/conf/evolutions/reversions/042-add-json-object-constraints.sql b/schema/evolutions/reversions/042-add-json-object-constraints.sql similarity index 100% rename from conf/evolutions/reversions/042-add-json-object-constraints.sql rename to schema/evolutions/reversions/042-add-json-object-constraints.sql diff --git a/conf/evolutions/reversions/043-annotationsettings-allowedMagnifications.sql b/schema/evolutions/reversions/043-annotationsettings-allowedMagnifications.sql similarity index 100% rename from conf/evolutions/reversions/043-annotationsettings-allowedMagnifications.sql rename to schema/evolutions/reversions/043-annotationsettings-allowedMagnifications.sql diff --git a/conf/evolutions/reversions/044-datasource-hash.sql b/schema/evolutions/reversions/044-datasource-hash.sql similarity index 100% rename from conf/evolutions/reversions/044-datasource-hash.sql rename to schema/evolutions/reversions/044-datasource-hash.sql diff --git a/conf/evolutions/reversions/045-annotationsettings-mergerMode.sql b/schema/evolutions/reversions/045-annotationsettings-mergerMode.sql similarity index 100% rename from conf/evolutions/reversions/045-annotationsettings-mergerMode.sql rename to schema/evolutions/reversions/045-annotationsettings-mergerMode.sql diff --git a/conf/evolutions/reversions/046-fix-missing-voxel-type.sql b/schema/evolutions/reversions/046-fix-missing-voxel-type.sql similarity index 100% rename from conf/evolutions/reversions/046-fix-missing-voxel-type.sql rename to schema/evolutions/reversions/046-fix-missing-voxel-type.sql diff --git a/conf/evolutions/reversions/047-add-datastore-publicUrl.sql b/schema/evolutions/reversions/047-add-datastore-publicUrl.sql similarity index 100% rename from conf/evolutions/reversions/047-add-datastore-publicUrl.sql rename to schema/evolutions/reversions/047-add-datastore-publicUrl.sql diff --git a/conf/evolutions/reversions/048-add-tracingstore-publicUrl.sql b/schema/evolutions/reversions/048-add-tracingstore-publicUrl.sql similarity index 100% rename from conf/evolutions/reversions/048-add-tracingstore-publicUrl.sql rename to schema/evolutions/reversions/048-add-tracingstore-publicUrl.sql diff --git a/conf/evolutions/reversions/048-annotation-listed-teams.sql b/schema/evolutions/reversions/048-annotation-listed-teams.sql similarity index 100% rename from conf/evolutions/reversions/048-annotation-listed-teams.sql rename to schema/evolutions/reversions/048-annotation-listed-teams.sql diff --git a/conf/evolutions/reversions/049-annotation-listed-teams.sql b/schema/evolutions/reversions/049-annotation-listed-teams.sql similarity index 100% rename from conf/evolutions/reversions/049-annotation-listed-teams.sql rename to schema/evolutions/reversions/049-annotation-listed-teams.sql diff --git a/conf/evolutions/reversions/050-add-annotation-visibility-enum.sql b/schema/evolutions/reversions/050-add-annotation-visibility-enum.sql similarity index 100% rename from conf/evolutions/reversions/050-add-annotation-visibility-enum.sql rename to schema/evolutions/reversions/050-add-annotation-visibility-enum.sql diff --git a/conf/evolutions/reversions/051-add-source-view-configuration.sql b/schema/evolutions/reversions/051-add-source-view-configuration.sql similarity index 100% rename from conf/evolutions/reversions/051-add-source-view-configuration.sql rename to schema/evolutions/reversions/051-add-source-view-configuration.sql diff --git a/conf/evolutions/reversions/052-replace-segmentation-opacity.sql b/schema/evolutions/reversions/052-replace-segmentation-opacity.sql similarity index 100% rename from conf/evolutions/reversions/052-replace-segmentation-opacity.sql rename to schema/evolutions/reversions/052-replace-segmentation-opacity.sql diff --git a/conf/evolutions/reversions/053-add-allowsUpload.sql b/schema/evolutions/reversions/053-add-allowsUpload.sql similarity index 100% rename from conf/evolutions/reversions/053-add-allowsUpload.sql rename to schema/evolutions/reversions/053-add-allowsUpload.sql diff --git a/conf/evolutions/reversions/054-add-isDatasetManager.sql b/schema/evolutions/reversions/054-add-isDatasetManager.sql similarity index 100% rename from conf/evolutions/reversions/054-add-isDatasetManager.sql rename to schema/evolutions/reversions/054-add-isDatasetManager.sql diff --git a/conf/evolutions/reversions/055-make-organization-name-unique.sql b/schema/evolutions/reversions/055-make-organization-name-unique.sql similarity index 100% rename from conf/evolutions/reversions/055-make-organization-name-unique.sql rename to schema/evolutions/reversions/055-make-organization-name-unique.sql diff --git a/conf/evolutions/reversions/056-add-jobs.sql b/schema/evolutions/reversions/056-add-jobs.sql similarity index 100% rename from conf/evolutions/reversions/056-add-jobs.sql rename to schema/evolutions/reversions/056-add-jobs.sql diff --git a/conf/evolutions/reversions/057-add-layer-specific-view-configs.sql b/schema/evolutions/reversions/057-add-layer-specific-view-configs.sql similarity index 100% rename from conf/evolutions/reversions/057-add-layer-specific-view-configs.sql rename to schema/evolutions/reversions/057-add-layer-specific-view-configs.sql diff --git a/conf/evolutions/reversions/058-add-onlyAllowedOrganization.sql b/schema/evolutions/reversions/058-add-onlyAllowedOrganization.sql similarity index 100% rename from conf/evolutions/reversions/058-add-onlyAllowedOrganization.sql rename to schema/evolutions/reversions/058-add-onlyAllowedOrganization.sql diff --git a/conf/evolutions/reversions/059-resolution-restrictions.sql b/schema/evolutions/reversions/059-resolution-restrictions.sql similarity index 100% rename from conf/evolutions/reversions/059-resolution-restrictions.sql rename to schema/evolutions/reversions/059-resolution-restrictions.sql diff --git a/conf/evolutions/reversions/060-multiusers.sql b/schema/evolutions/reversions/060-multiusers.sql similarity index 100% rename from conf/evolutions/reversions/060-multiusers.sql rename to schema/evolutions/reversions/060-multiusers.sql diff --git a/conf/evolutions/reversions/061-userinfos-view.sql b/schema/evolutions/reversions/061-userinfos-view.sql similarity index 100% rename from conf/evolutions/reversions/061-userinfos-view.sql rename to schema/evolutions/reversions/061-userinfos-view.sql diff --git a/conf/evolutions/reversions/062-dataset-uploader.sql b/schema/evolutions/reversions/062-dataset-uploader.sql similarity index 100% rename from conf/evolutions/reversions/062-dataset-uploader.sql rename to schema/evolutions/reversions/062-dataset-uploader.sql diff --git a/conf/evolutions/reversions/063-novelUserExperienceinfos.sql b/schema/evolutions/reversions/063-novelUserExperienceinfos.sql similarity index 100% rename from conf/evolutions/reversions/063-novelUserExperienceinfos.sql rename to schema/evolutions/reversions/063-novelUserExperienceinfos.sql diff --git a/conf/evolutions/reversions/064-experienceDomains-per-orga.sql b/schema/evolutions/reversions/064-experienceDomains-per-orga.sql similarity index 100% rename from conf/evolutions/reversions/064-experienceDomains-per-orga.sql rename to schema/evolutions/reversions/064-experienceDomains-per-orga.sql diff --git a/conf/evolutions/reversions/065-unlisted-superusers.sql b/schema/evolutions/reversions/065-unlisted-superusers.sql similarity index 100% rename from conf/evolutions/reversions/065-unlisted-superusers.sql rename to schema/evolutions/reversions/065-unlisted-superusers.sql diff --git a/conf/evolutions/reversions/066-publications-foreign-key.sql b/schema/evolutions/reversions/066-publications-foreign-key.sql similarity index 100% rename from conf/evolutions/reversions/066-publications-foreign-key.sql rename to schema/evolutions/reversions/066-publications-foreign-key.sql diff --git a/conf/evolutions/reversions/067-drop-analytics.sql b/schema/evolutions/reversions/067-drop-analytics.sql similarity index 100% rename from conf/evolutions/reversions/067-drop-analytics.sql rename to schema/evolutions/reversions/067-drop-analytics.sql diff --git a/conf/evolutions/reversions/068-pricing-plan.sql b/schema/evolutions/reversions/068-pricing-plan.sql similarity index 100% rename from conf/evolutions/reversions/068-pricing-plan.sql rename to schema/evolutions/reversions/068-pricing-plan.sql diff --git a/conf/evolutions/reversions/069-tasktype-project-unique-per-orga.sql b/schema/evolutions/reversions/069-tasktype-project-unique-per-orga.sql similarity index 100% rename from conf/evolutions/reversions/069-tasktype-project-unique-per-orga.sql rename to schema/evolutions/reversions/069-tasktype-project-unique-per-orga.sql diff --git a/conf/evolutions/reversions/070-dark-theme.sql b/schema/evolutions/reversions/070-dark-theme.sql similarity index 100% rename from conf/evolutions/reversions/070-dark-theme.sql rename to schema/evolutions/reversions/070-dark-theme.sql diff --git a/conf/evolutions/reversions/071-adapt-td-view-display-planes.sql b/schema/evolutions/reversions/071-adapt-td-view-display-planes.sql similarity index 100% rename from conf/evolutions/reversions/071-adapt-td-view-display-planes.sql rename to schema/evolutions/reversions/071-adapt-td-view-display-planes.sql diff --git a/conf/evolutions/reversions/072-jobs-manually-repaired.sql b/schema/evolutions/reversions/072-jobs-manually-repaired.sql similarity index 100% rename from conf/evolutions/reversions/072-jobs-manually-repaired.sql rename to schema/evolutions/reversions/072-jobs-manually-repaired.sql diff --git a/conf/evolutions/reversions/073-modern-controls-user-conf.sql b/schema/evolutions/reversions/073-modern-controls-user-conf.sql similarity index 100% rename from conf/evolutions/reversions/073-modern-controls-user-conf.sql rename to schema/evolutions/reversions/073-modern-controls-user-conf.sql diff --git a/conf/evolutions/reversions/074-jobs-owner-foreign-key.sql b/schema/evolutions/reversions/074-jobs-owner-foreign-key.sql similarity index 100% rename from conf/evolutions/reversions/074-jobs-owner-foreign-key.sql rename to schema/evolutions/reversions/074-jobs-owner-foreign-key.sql diff --git a/conf/evolutions/reversions/075-tasktype-remove-hovered-cell-id.sql b/schema/evolutions/reversions/075-tasktype-remove-hovered-cell-id.sql similarity index 100% rename from conf/evolutions/reversions/075-tasktype-remove-hovered-cell-id.sql rename to schema/evolutions/reversions/075-tasktype-remove-hovered-cell-id.sql diff --git a/conf/evolutions/reversions/076-jobs-enabled-per-datastore.sql b/schema/evolutions/reversions/076-jobs-enabled-per-datastore.sql similarity index 100% rename from conf/evolutions/reversions/076-jobs-enabled-per-datastore.sql rename to schema/evolutions/reversions/076-jobs-enabled-per-datastore.sql diff --git a/conf/evolutions/reversions/077-workers.sql b/schema/evolutions/reversions/077-workers.sql similarity index 100% rename from conf/evolutions/reversions/077-workers.sql rename to schema/evolutions/reversions/077-workers.sql diff --git a/conf/evolutions/reversions/078-annotation-layers.sql b/schema/evolutions/reversions/078-annotation-layers.sql similarity index 100% rename from conf/evolutions/reversions/078-annotation-layers.sql rename to schema/evolutions/reversions/078-annotation-layers.sql diff --git a/conf/evolutions/reversions/079-add-dataset-tags.sql b/schema/evolutions/reversions/079-add-dataset-tags.sql similarity index 100% rename from conf/evolutions/reversions/079-add-dataset-tags.sql rename to schema/evolutions/reversions/079-add-dataset-tags.sql diff --git a/conf/evolutions/reversions/080-job-add-cancelled.sql b/schema/evolutions/reversions/080-job-add-cancelled.sql similarity index 100% rename from conf/evolutions/reversions/080-job-add-cancelled.sql rename to schema/evolutions/reversions/080-job-add-cancelled.sql diff --git a/conf/evolutions/reversions/081-annotation-viewconfiguration.sql b/schema/evolutions/reversions/081-annotation-viewconfiguration.sql similarity index 100% rename from conf/evolutions/reversions/081-annotation-viewconfiguration.sql rename to schema/evolutions/reversions/081-annotation-viewconfiguration.sql diff --git a/conf/evolutions/reversions/082-annotationsettings-volumeInterpolationAllowed.sql b/schema/evolutions/reversions/082-annotationsettings-volumeInterpolationAllowed.sql similarity index 100% rename from conf/evolutions/reversions/082-annotationsettings-volumeInterpolationAllowed.sql rename to schema/evolutions/reversions/082-annotationsettings-volumeInterpolationAllowed.sql diff --git a/conf/evolutions/reversions/083-unique-layer-names.sql b/schema/evolutions/reversions/083-unique-layer-names.sql similarity index 100% rename from conf/evolutions/reversions/083-unique-layer-names.sql rename to schema/evolutions/reversions/083-unique-layer-names.sql diff --git a/conf/evolutions/reversions/084-annotation-contributors.sql b/schema/evolutions/reversions/084-annotation-contributors.sql similarity index 100% rename from conf/evolutions/reversions/084-annotation-contributors.sql rename to schema/evolutions/reversions/084-annotation-contributors.sql diff --git a/conf/evolutions/reversions/085-annotation-publication.sql b/schema/evolutions/reversions/085-annotation-publication.sql similarity index 100% rename from conf/evolutions/reversions/085-annotation-publication.sql rename to schema/evolutions/reversions/085-annotation-publication.sql diff --git a/conf/evolutions/reversions/086-drop-foreign-datastores.sql b/schema/evolutions/reversions/086-drop-foreign-datastores.sql similarity index 100% rename from conf/evolutions/reversions/086-drop-foreign-datastores.sql rename to schema/evolutions/reversions/086-drop-foreign-datastores.sql diff --git a/conf/evolutions/reversions/087-zarr-private-links.sql b/schema/evolutions/reversions/087-zarr-private-links.sql similarity index 100% rename from conf/evolutions/reversions/087-zarr-private-links.sql rename to schema/evolutions/reversions/087-zarr-private-links.sql diff --git a/conf/evolutions/reversions/088-shortlinks.sql b/schema/evolutions/reversions/088-shortlinks.sql similarity index 100% rename from conf/evolutions/reversions/088-shortlinks.sql rename to schema/evolutions/reversions/088-shortlinks.sql diff --git a/conf/evolutions/reversions/089-voxelytics.sql b/schema/evolutions/reversions/089-voxelytics.sql similarity index 100% rename from conf/evolutions/reversions/089-voxelytics.sql rename to schema/evolutions/reversions/089-voxelytics.sql diff --git a/conf/evolutions/reversions/090-cleanup.sql b/schema/evolutions/reversions/090-cleanup.sql similarity index 100% rename from conf/evolutions/reversions/090-cleanup.sql rename to schema/evolutions/reversions/090-cleanup.sql diff --git a/conf/evolutions/reversions/091-folders.sql b/schema/evolutions/reversions/091-folders.sql similarity index 100% rename from conf/evolutions/reversions/091-folders.sql rename to schema/evolutions/reversions/091-folders.sql diff --git a/conf/evolutions/reversions/092-oidc.sql b/schema/evolutions/reversions/092-oidc.sql similarity index 100% rename from conf/evolutions/reversions/092-oidc.sql rename to schema/evolutions/reversions/092-oidc.sql diff --git a/conf/evolutions/reversions/093-terms-of-service.sql b/schema/evolutions/reversions/093-terms-of-service.sql similarity index 100% rename from conf/evolutions/reversions/093-terms-of-service.sql rename to schema/evolutions/reversions/093-terms-of-service.sql diff --git a/conf/evolutions/reversions/094-pricing-plans.sql b/schema/evolutions/reversions/094-pricing-plans.sql similarity index 100% rename from conf/evolutions/reversions/094-pricing-plans.sql rename to schema/evolutions/reversions/094-pricing-plans.sql diff --git a/conf/evolutions/reversions/095-constraint-naming.sql b/schema/evolutions/reversions/095-constraint-naming.sql similarity index 100% rename from conf/evolutions/reversions/095-constraint-naming.sql rename to schema/evolutions/reversions/095-constraint-naming.sql diff --git a/conf/evolutions/reversions/096-storage.sql b/schema/evolutions/reversions/096-storage.sql similarity index 100% rename from conf/evolutions/reversions/096-storage.sql rename to schema/evolutions/reversions/096-storage.sql diff --git a/conf/evolutions/reversions/097-credentials.sql b/schema/evolutions/reversions/097-credentials.sql similarity index 100% rename from conf/evolutions/reversions/097-credentials.sql rename to schema/evolutions/reversions/097-credentials.sql diff --git a/conf/evolutions/reversions/098-voxelytics-states.sql b/schema/evolutions/reversions/098-voxelytics-states.sql similarity index 100% rename from conf/evolutions/reversions/098-voxelytics-states.sql rename to schema/evolutions/reversions/098-voxelytics-states.sql diff --git a/conf/evolutions/reversions/099-rename-credential-types.sql b/schema/evolutions/reversions/099-rename-credential-types.sql similarity index 100% rename from conf/evolutions/reversions/099-rename-credential-types.sql rename to schema/evolutions/reversions/099-rename-credential-types.sql diff --git a/conf/evolutions/reversions/100-annotation-mutexes.sql b/schema/evolutions/reversions/100-annotation-mutexes.sql similarity index 100% rename from conf/evolutions/reversions/100-annotation-mutexes.sql rename to schema/evolutions/reversions/100-annotation-mutexes.sql diff --git a/conf/evolutions/reversions/101-coordinate-transformations.sql b/schema/evolutions/reversions/101-coordinate-transformations.sql similarity index 100% rename from conf/evolutions/reversions/101-coordinate-transformations.sql rename to schema/evolutions/reversions/101-coordinate-transformations.sql diff --git a/conf/evolutions/reversions/102-no-more-wkconnect.sql b/schema/evolutions/reversions/102-no-more-wkconnect.sql similarity index 100% rename from conf/evolutions/reversions/102-no-more-wkconnect.sql rename to schema/evolutions/reversions/102-no-more-wkconnect.sql diff --git a/conf/evolutions/reversions/103-thin-plane-splines.sql b/schema/evolutions/reversions/103-thin-plane-splines.sql similarity index 100% rename from conf/evolutions/reversions/103-thin-plane-splines.sql rename to schema/evolutions/reversions/103-thin-plane-splines.sql diff --git a/conf/evolutions/reversions/104-thumbnails.sql b/schema/evolutions/reversions/104-thumbnails.sql similarity index 100% rename from conf/evolutions/reversions/104-thumbnails.sql rename to schema/evolutions/reversions/104-thumbnails.sql diff --git a/conf/evolutions/reversions/105-verify-email.sql b/schema/evolutions/reversions/105-verify-email.sql similarity index 100% rename from conf/evolutions/reversions/105-verify-email.sql rename to schema/evolutions/reversions/105-verify-email.sql diff --git a/conf/evolutions/reversions/106-folder-no-slashes.sql b/schema/evolutions/reversions/106-folder-no-slashes.sql similarity index 100% rename from conf/evolutions/reversions/106-folder-no-slashes.sql rename to schema/evolutions/reversions/106-folder-no-slashes.sql diff --git a/conf/evolutions/reversions/107-task-terminology.sql b/schema/evolutions/reversions/107-task-terminology.sql similarity index 100% rename from conf/evolutions/reversions/107-task-terminology.sql rename to schema/evolutions/reversions/107-task-terminology.sql diff --git a/conf/evolutions/reversions/108-additional-coordinates.sql b/schema/evolutions/reversions/108-additional-coordinates.sql similarity index 100% rename from conf/evolutions/reversions/108-additional-coordinates.sql rename to schema/evolutions/reversions/108-additional-coordinates.sql diff --git a/conf/evolutions/reversions/109-scheduled-maintenances.sql b/schema/evolutions/reversions/109-scheduled-maintenances.sql similarity index 100% rename from conf/evolutions/reversions/109-scheduled-maintenances.sql rename to schema/evolutions/reversions/109-scheduled-maintenances.sql diff --git a/conf/evolutions/reversions/110-worker-config.sql b/schema/evolutions/reversions/110-worker-config.sql similarity index 100% rename from conf/evolutions/reversions/110-worker-config.sql rename to schema/evolutions/reversions/110-worker-config.sql diff --git a/conf/evolutions/reversions/111-stats-per-annotation-layer.sql b/schema/evolutions/reversions/111-stats-per-annotation-layer.sql similarity index 100% rename from conf/evolutions/reversions/111-stats-per-annotation-layer.sql rename to schema/evolutions/reversions/111-stats-per-annotation-layer.sql diff --git a/conf/evolutions/reversions/112-reuse-deleted.sql b/schema/evolutions/reversions/112-reuse-deleted.sql similarity index 100% rename from conf/evolutions/reversions/112-reuse-deleted.sql rename to schema/evolutions/reversions/112-reuse-deleted.sql diff --git a/conf/evolutions/reversions/113-analytics-events.sql b/schema/evolutions/reversions/113-analytics-events.sql similarity index 100% rename from conf/evolutions/reversions/113-analytics-events.sql rename to schema/evolutions/reversions/113-analytics-events.sql diff --git a/conf/evolutions/reversions/114-ai-models.sql b/schema/evolutions/reversions/114-ai-models.sql similarity index 100% rename from conf/evolutions/reversions/114-ai-models.sql rename to schema/evolutions/reversions/114-ai-models.sql diff --git a/conf/evolutions/reversions/115-annotation-locked-by-user.sql b/schema/evolutions/reversions/115-annotation-locked-by-user.sql similarity index 100% rename from conf/evolutions/reversions/115-annotation-locked-by-user.sql rename to schema/evolutions/reversions/115-annotation-locked-by-user.sql diff --git a/conf/evolutions/reversions/116-drop-overtimemailinglist.sql b/schema/evolutions/reversions/116-drop-overtimemailinglist.sql similarity index 100% rename from conf/evolutions/reversions/116-drop-overtimemailinglist.sql rename to schema/evolutions/reversions/116-drop-overtimemailinglist.sql diff --git a/conf/evolutions/reversions/117-voxel-size-unit.sql b/schema/evolutions/reversions/117-voxel-size-unit.sql similarity index 100% rename from conf/evolutions/reversions/117-voxel-size-unit.sql rename to schema/evolutions/reversions/117-voxel-size-unit.sql diff --git a/conf/evolutions/reversions/118-voxelytics-artifacts-index.sql b/schema/evolutions/reversions/118-voxelytics-artifacts-index.sql similarity index 100% rename from conf/evolutions/reversions/118-voxelytics-artifacts-index.sql rename to schema/evolutions/reversions/118-voxelytics-artifacts-index.sql diff --git a/conf/evolutions/reversions/119-add-metadata-to-folders-and-datasets.sql b/schema/evolutions/reversions/119-add-metadata-to-folders-and-datasets.sql similarity index 100% rename from conf/evolutions/reversions/119-add-metadata-to-folders-and-datasets.sql rename to schema/evolutions/reversions/119-add-metadata-to-folders-and-datasets.sql diff --git a/conf/evolutions/reversions/120-remove-old-organization-id.sql b/schema/evolutions/reversions/120-remove-old-organization-id.sql similarity index 100% rename from conf/evolutions/reversions/120-remove-old-organization-id.sql rename to schema/evolutions/reversions/120-remove-old-organization-id.sql diff --git a/conf/evolutions/reversions/121-worker-name.sql b/schema/evolutions/reversions/121-worker-name.sql similarity index 100% rename from conf/evolutions/reversions/121-worker-name.sql rename to schema/evolutions/reversions/121-worker-name.sql diff --git a/conf/evolutions/reversions/122-resolution-to-mag.sql b/schema/evolutions/reversions/122-resolution-to-mag.sql similarity index 100% rename from conf/evolutions/reversions/122-resolution-to-mag.sql rename to schema/evolutions/reversions/122-resolution-to-mag.sql diff --git a/conf/evolutions/reversions/123-more-model-categories.sql b/schema/evolutions/reversions/123-more-model-categories.sql similarity index 100% rename from conf/evolutions/reversions/123-more-model-categories.sql rename to schema/evolutions/reversions/123-more-model-categories.sql diff --git a/conf/evolutions/reversions/124-decouple-dataset-directory-from-name.sql b/schema/evolutions/reversions/124-decouple-dataset-directory-from-name.sql similarity index 100% rename from conf/evolutions/reversions/124-decouple-dataset-directory-from-name.sql rename to schema/evolutions/reversions/124-decouple-dataset-directory-from-name.sql diff --git a/conf/evolutions/reversions/125-allow-dollar-in-layer-names.sql b/schema/evolutions/reversions/125-allow-dollar-in-layer-names.sql similarity index 100% rename from conf/evolutions/reversions/125-allow-dollar-in-layer-names.sql rename to schema/evolutions/reversions/125-allow-dollar-in-layer-names.sql diff --git a/conf/evolutions/reversions/126-mag-real-paths.sql b/schema/evolutions/reversions/126-mag-real-paths.sql similarity index 100% rename from conf/evolutions/reversions/126-mag-real-paths.sql rename to schema/evolutions/reversions/126-mag-real-paths.sql diff --git a/conf/evolutions/reversions/127-job-retried-by-super-user.sql b/schema/evolutions/reversions/127-job-retried-by-super-user.sql similarity index 100% rename from conf/evolutions/reversions/127-job-retried-by-super-user.sql rename to schema/evolutions/reversions/127-job-retried-by-super-user.sql diff --git a/conf/evolutions/reversions/128-allow-ai-model-sharing.sql b/schema/evolutions/reversions/128-allow-ai-model-sharing.sql similarity index 100% rename from conf/evolutions/reversions/128-allow-ai-model-sharing.sql rename to schema/evolutions/reversions/128-allow-ai-model-sharing.sql diff --git a/conf/evolutions/reversions/129-credit-transactions.sql b/schema/evolutions/reversions/129-credit-transactions.sql similarity index 100% rename from conf/evolutions/reversions/129-credit-transactions.sql rename to schema/evolutions/reversions/129-credit-transactions.sql diff --git a/conf/evolutions/reversions/130-replace-text-types.sql b/schema/evolutions/reversions/130-replace-text-types.sql similarity index 100% rename from conf/evolutions/reversions/130-replace-text-types.sql rename to schema/evolutions/reversions/130-replace-text-types.sql diff --git a/conf/evolutions/reversions/131-more-indices-on-users.sql b/schema/evolutions/reversions/131-more-indices-on-users.sql similarity index 100% rename from conf/evolutions/reversions/131-more-indices-on-users.sql rename to schema/evolutions/reversions/131-more-indices-on-users.sql diff --git a/conf/evolutions/reversions/132-remove-stored-meshes.sql b/schema/evolutions/reversions/132-remove-stored-meshes.sql similarity index 100% rename from conf/evolutions/reversions/132-remove-stored-meshes.sql rename to schema/evolutions/reversions/132-remove-stored-meshes.sql diff --git a/conf/evolutions/reversions/133-datasource-properties-in-db.sql b/schema/evolutions/reversions/133-datasource-properties-in-db.sql similarity index 100% rename from conf/evolutions/reversions/133-datasource-properties-in-db.sql rename to schema/evolutions/reversions/133-datasource-properties-in-db.sql diff --git a/conf/evolutions/reversions/134-dataset-layer-attachments.sql b/schema/evolutions/reversions/134-dataset-layer-attachments.sql similarity index 100% rename from conf/evolutions/reversions/134-dataset-layer-attachments.sql rename to schema/evolutions/reversions/134-dataset-layer-attachments.sql diff --git a/conf/evolutions/reversions/135-neuroglancer-attachment.sql b/schema/evolutions/reversions/135-neuroglancer-attachment.sql similarity index 100% rename from conf/evolutions/reversions/135-neuroglancer-attachment.sql rename to schema/evolutions/reversions/135-neuroglancer-attachment.sql diff --git a/conf/evolutions/reversions/136-extra-column-for-email-changed.sql b/schema/evolutions/reversions/136-extra-column-for-email-changed.sql similarity index 100% rename from conf/evolutions/reversions/136-extra-column-for-email-changed.sql rename to schema/evolutions/reversions/136-extra-column-for-email-changed.sql diff --git a/conf/evolutions/reversions/137-virtual-datasets.sql b/schema/evolutions/reversions/137-virtual-datasets.sql similarity index 100% rename from conf/evolutions/reversions/137-virtual-datasets.sql rename to schema/evolutions/reversions/137-virtual-datasets.sql diff --git a/conf/evolutions/reversions/138-add-webauthn-credentials.sql b/schema/evolutions/reversions/138-add-webauthn-credentials.sql similarity index 100% rename from conf/evolutions/reversions/138-add-webauthn-credentials.sql rename to schema/evolutions/reversions/138-add-webauthn-credentials.sql diff --git a/conf/evolutions/reversions/139-logout-everywhere.sql b/schema/evolutions/reversions/139-logout-everywhere.sql similarity index 100% rename from conf/evolutions/reversions/139-logout-everywhere.sql rename to schema/evolutions/reversions/139-logout-everywhere.sql diff --git a/conf/evolutions/reversions/140-annotation-layer-name-check-deferrable.sql b/schema/evolutions/reversions/140-annotation-layer-name-check-deferrable.sql similarity index 100% rename from conf/evolutions/reversions/140-annotation-layer-name-check-deferrable.sql rename to schema/evolutions/reversions/140-annotation-layer-name-check-deferrable.sql diff --git a/conf/evolutions/reversions/141-allows-upload-to-paths.sql b/schema/evolutions/reversions/141-allows-upload-to-paths.sql similarity index 100% rename from conf/evolutions/reversions/141-allows-upload-to-paths.sql rename to schema/evolutions/reversions/141-allows-upload-to-paths.sql diff --git a/conf/evolutions/reversions/142-personal-plan.sql b/schema/evolutions/reversions/142-personal-plan.sql similarity index 100% rename from conf/evolutions/reversions/142-personal-plan.sql rename to schema/evolutions/reversions/142-personal-plan.sql diff --git a/conf/evolutions/reversions/143-remote-storage-analysis.sql b/schema/evolutions/reversions/143-remote-storage-analysis.sql similarity index 100% rename from conf/evolutions/reversions/143-remote-storage-analysis.sql rename to schema/evolutions/reversions/143-remote-storage-analysis.sql diff --git a/conf/evolutions/reversions/144-improve-storage-scan.sql b/schema/evolutions/reversions/144-improve-storage-scan.sql similarity index 100% rename from conf/evolutions/reversions/144-improve-storage-scan.sql rename to schema/evolutions/reversions/144-improve-storage-scan.sql diff --git a/conf/evolutions/reversions/145-attachment-realpaths.sql b/schema/evolutions/reversions/145-attachment-realpaths.sql similarity index 100% rename from conf/evolutions/reversions/145-attachment-realpaths.sql rename to schema/evolutions/reversions/145-attachment-realpaths.sql diff --git a/tools/postgres/schema.sql b/schema/schema.sql similarity index 100% rename from tools/postgres/schema.sql rename to schema/schema.sql diff --git a/tools/assert-complete-migrations.sh b/tools/assert-complete-migrations.sh index 709fb52cd54..a80b846f831 100755 --- a/tools/assert-complete-migrations.sh +++ b/tools/assert-complete-migrations.sh @@ -5,7 +5,7 @@ set -euo pipefail # - is mentioned in either MIGRATIONS.released.md or in unreleased_changes/*.md (but not both) # - has a reversion sibling in conf/evolutions/reversions -EVOLUTIONS_FOLDER="conf/evolutions" +EVOLUTIONS_FOLDER="schema/evolutions" RELEASED_FILE="MIGRATIONS.released.md" UNRELEASED_DIR="unreleased_changes" diff --git a/tools/postgres/dbtool.js b/tools/postgres/dbtool.js index cb1616fbfb1..7e581ffdb9f 100755 --- a/tools/postgres/dbtool.js +++ b/tools/postgres/dbtool.js @@ -4,8 +4,9 @@ const path = require("node:path"); const fs = require("node:fs"); const { Command } = require("commander"); -const schemaPath = path.join(__dirname, "schema.sql"); -const evolutionsPath = path.resolve(path.join(__dirname, "..", "..", "conf", "evolutions")); +const repoRootPath = path.resolve(path.join(__dirname, "..", "..")); +const schemaPath = path.join(repoRootPath, "schema", "schema.sql"); +const evolutionsPath = path.join(repoRootPath, "schema", "evolutions"); const PG_CONFIG = (() => { let rawUrl = process.env.POSTGRES_URL || "postgres://postgres:postgres@127.0.0.1:5432/webknossos"; From f13d4b75e5e96ab1412f0072f4449ea7aa939dab Mon Sep 17 00:00:00 2001 From: Florian M Date: Mon, 24 Nov 2025 12:03:41 +0100 Subject: [PATCH 07/16] WIP speed up job list request --- app/controllers/JobController.scala | 5 +- app/models/job/Job.scala | 188 ++++++++++++++++++++++------ app/models/job/JobService.scala | 2 +- 3 files changed, 152 insertions(+), 43 deletions(-) diff --git a/app/controllers/JobController.scala b/app/controllers/JobController.scala index b6fc9b58741..a3f248cc24f 100644 --- a/app/controllers/JobController.scala +++ b/app/controllers/JobController.scala @@ -87,9 +87,8 @@ class JobController @Inject()(jobDAO: JobDAO, def list: Action[AnyContent] = sil.SecuredAction.async { implicit request => for { _ <- Fox.fromBool(wkconf.Features.jobsEnabled) ?~> "job.disabled" - jobs <- jobDAO.findAll - jobsJsonList <- Fox.serialCombined(jobs.sortBy(_.created).reverse)(jobService.publicWrites) - } yield Ok(Json.toJson(jobsJsonList)) + jobsCompact <- jobDAO.findAllCompact + } yield Ok(Json.toJson(jobsCompact.map(_.enrich))) } def get(id: ObjectId): Action[AnyContent] = sil.SecuredAction.async { implicit request => diff --git a/app/models/job/Job.scala b/app/models/job/Job.scala index 0155dc9d344..4a9946662e7 100644 --- a/app/models/job/Job.scala +++ b/app/models/job/Job.scala @@ -12,52 +12,29 @@ import slick.jdbc.TransactionIsolation.Serializable import slick.lifted.Rep import utils.sql.{SQLDAO, SqlClient, SqlToken} import com.scalableminds.util.objectid.ObjectId +import spire.std.map import javax.inject.Inject import scala.concurrent.ExecutionContext import scala.concurrent.duration._ -case class Job( - _id: ObjectId, - _owner: ObjectId, - _dataStore: String, - command: JobCommand, - commandArgs: JsObject = Json.obj(), - state: JobState = JobState.PENDING, - manualState: Option[JobState] = None, - _worker: Option[ObjectId] = None, - _voxelyticsWorkflowHash: Option[String] = None, - latestRunId: Option[String] = None, - returnValue: Option[String] = None, - retriedBySuperUser: Boolean = false, - started: Option[Long] = None, - ended: Option[Long] = None, - created: Instant = Instant.now, - isDeleted: Boolean = false -) { - def isEnded: Boolean = { - val relevantState = manualState.getOrElse(state) - relevantState == JobState.SUCCESS || state == JobState.FAILURE - } - - def duration: Option[FiniteDuration] = - for { - e <- ended - s <- started - } yield (e - s).millis +trait JobResultLinks { + def commandArgs: JsObject + def command: JobCommand + def returnValue: Option[String] + protected def id: ObjectId - private def effectiveState: JobState = manualState.getOrElse(state) - - def exportFileName: Option[String] = argAsStringOpt("export_file_name") + protected def effectiveState: JobState def datasetName: Option[String] = argAsStringOpt("dataset_name") def datasetId: Option[String] = argAsStringOpt("dataset_id") - private def argAsStringOpt(key: String) = (commandArgs \ key).toOption.flatMap(_.asOpt[String]) - private def argAsBooleanOpt(key: String) = (commandArgs \ key).toOption.flatMap(_.asOpt[Boolean]) + protected def argAsStringOpt(key: String): Option[String] = (commandArgs \ key).toOption.flatMap(_.asOpt[String]) - def resultLink(organizationId: String): Option[String] = + protected def argAsBooleanOpt(key: String): Option[Boolean] = (commandArgs \ key).toOption.flatMap(_.asOpt[Boolean]) + + def constructResultLink(organizationId: String): Option[String] = if (effectiveState != JobState.SUCCESS) None else { command match { @@ -67,7 +44,7 @@ case class Job( Some(s"/datasets/$datasetNameMaybe$datasetId/view") }.getOrElse(datasetName.map(name => s"datasets/$organizationId/$name/view")) case JobCommand.export_tiff | JobCommand.render_animation => - Some(s"/api/jobs/${this._id}/export") + Some(s"/api/jobs/${this.id}/export") case JobCommand.infer_neurons if this.argAsBooleanOpt("do_evaluation").getOrElse(false) => returnValue.map { resultAnnotationLink => resultAnnotationLink @@ -91,10 +68,46 @@ case class Job( def resultLinkPublic(organizationId: String, webknossosPublicUrl: String): Option[String] = for { - resultLink <- resultLink(organizationId) + resultLink <- constructResultLink(organizationId) resultLinkPublic = if (resultLink.startsWith("/")) s"$webknossosPublicUrl$resultLink" else s"$resultLink" } yield resultLinkPublic +} + +case class Job( + _id: ObjectId, + _owner: ObjectId, + _dataStore: String, + command: JobCommand, + commandArgs: JsObject = Json.obj(), + state: JobState = JobState.PENDING, + manualState: Option[JobState] = None, + _worker: Option[ObjectId] = None, + _voxelyticsWorkflowHash: Option[String] = None, + latestRunId: Option[String] = None, + returnValue: Option[String] = None, + retriedBySuperUser: Boolean = false, + started: Option[Long] = None, + ended: Option[Long] = None, + created: Instant = Instant.now, + isDeleted: Boolean = false +) extends JobResultLinks { + protected def id: ObjectId = _id + + def isEnded: Boolean = { + val relevantState = manualState.getOrElse(state) + relevantState == JobState.SUCCESS || state == JobState.FAILURE + } + + def duration: Option[FiniteDuration] = + for { + e <- ended + s <- started + } yield (e - s).millis + + protected def effectiveState: JobState = manualState.getOrElse(state) + + def exportFileName: Option[String] = argAsStringOpt("export_file_name") def resultLinkSlackFormatted(organizationId: String, webknossosPublicUrl: String): String = (for { @@ -108,6 +121,48 @@ case class Job( }.getOrElse("") } +case class JobCompactInfo( + id: ObjectId, + command: JobCommand, + organizationId: String, + ownerFirstName: String, + ownerLastName: String, + ownerEmail: String, + commandArgs: JsObject, + state: JobState, + returnValue: Option[String], + resultLink: Option[String], + voxelyticsWorkflowHash: Option[String], + created: Instant, + started: Option[Instant], + ended: Option[Instant], + creditCost: Option[scala.math.BigDecimal] +) extends JobResultLinks { + protected def effectiveState: JobState = state + + def enrich: JobCompactInfo = this.copy( + resultLink = this.constructResultLink(organizationId) + ) +} + +/* + +"id" -> job._id.id, +"owner" -> ownerJson, +"command" -> job.command, +"commandArgs" -> (job.commandArgs - "webknossos_token" - "user_auth_token"), +"state" -> job.state, +"manualState" -> job.manualState, +"latestRunId" -> job.latestRunId, +"returnValue" -> job.returnValue, +"resultLink" -> resultLink, +"voxelyticsWorkflowHash" -> job._voxelyticsWorkflowHash, +"created" -> job.created, +"started" -> job.started, +"ended" -> job.ended, +"creditCost" -> creditTransactionBox.toOption.map(t => (t.creditDelta * -1).toString) + */ + class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) extends SQLDAO[Job, JobsRow, Jobs](sqlClient) { protected val collection = Jobs @@ -160,9 +215,12 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) """ private def listAccessQ(requestingUserId: ObjectId) = - q"""_owner = $requestingUserId OR - ((SELECT u._organization FROM webknossos.users_ u WHERE u._id = _owner) IN (SELECT _organization FROM webknossos.users_ WHERE _id = $requestingUserId AND isAdmin)) - """ + listAccessQWithPrefix(requestingUserId, q"") + + private def listAccessQWithPrefix(requestingUserId: ObjectId, prefix: SqlToken) = + q"""${prefix}_owner = $requestingUserId OR + ((SELECT u._organization FROM webknossos.users_ u WHERE u._id = _owner) IN (SELECT _organization FROM webknossos.users_ WHERE _id = $requestingUserId AND isAdmin)) + """ override def findAll(implicit ctx: DBAccessContext): Fox[List[Job]] = for { @@ -171,6 +229,58 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) parsed <- parseAll(r) } yield parsed + def findAllCompact(implicit ctx: DBAccessContext): Fox[Seq[JobCompactInfo]] = + for { + accessQuery <- accessQueryFromAccessQWithPrefix(listAccessQWithPrefix, q"j.") + rows <- run( + q""" + SELECT j._id, j.command, u._organization, u.firstName, u.lastName, mu.email, j.commandArgs, j.state, + j.returnValue, j._voxelytics_workflowHash, j.created, j.started, j.ended, ct.cost + FROM webknossos.jobs_ j + JOIN webknossos.users_ u on j._owner = u._id + JOIN webknossos.multiusers_ mu on u._multiUser = mu._id + LEFT JOIN webknossos.credit_transactions_ ct ON j._id = ct._paid_job + WHERE $accessQuery + """.as[(ObjectId, + String, + String, + String, + String, + String, + String, + String, + Option[String], + Option[String], + Instant, + Option[Instant], + Option[Instant], + Option[scala.math.BigDecimal])]) + parsed <- Fox.serialCombined(rows) { row => + for { + command <- JobCommand.fromString(row._2).toFox + state <- JobState.fromString(row._8).toFox + commandArgs <- JsonHelper.parseAs[JsObject](row._7).toFox + } yield + JobCompactInfo( + id = row._1, + command = command, + organizationId = row._3, + ownerFirstName = row._4, + ownerLastName = row._5, + ownerEmail = row._6, + commandArgs = commandArgs, + state = state, + returnValue = row._9, + resultLink = None, // To be filled by calling “enrich” + voxelyticsWorkflowHash = row._10, + created = row._11, + started = row._12, + ended = row._13, + creditCost = row._14 + ) + } + } yield parsed + override def findOne(jobId: ObjectId)(implicit ctx: DBAccessContext): Fox[Job] = for { accessQuery <- readAccessQuery diff --git a/app/models/job/JobService.scala b/app/models/job/JobService.scala index 4f24cda0e87..7f7e83cb76f 100644 --- a/app/models/job/JobService.scala +++ b/app/models/job/JobService.scala @@ -172,7 +172,7 @@ class JobService @Inject()(wkConf: WkConf, for { owner <- userDAO.findOne(job._owner) ?~> "user.notFound" organization <- organizationDAO.findOne(owner._organization) ?~> "organization.notFound" - resultLink = job.resultLink(organization._id) + resultLink = job.constructResultLink(organization._id) ownerJson <- userService.compactWrites(owner) creditTransactionBox <- creditTransactionService.findTransactionOfJob(job._id).shiftBox } yield { From fff1fe6270838e5e5447527b4d459fc7ad0c855c Mon Sep 17 00:00:00 2001 From: Florian M Date: Mon, 24 Nov 2025 14:53:53 +0100 Subject: [PATCH 08/16] unify full and compact job json --- app/models/job/Job.scala | 103 ++-------- app/models/job/JobResultLinks.scala | 62 ++++++ app/models/job/JobService.scala | 7 +- frontend/javascripts/admin/api/jobs.ts | 50 +---- .../admin/dataset/dataset_upload_view.tsx | 4 +- frontend/javascripts/admin/job/job_hooks.ts | 4 +- .../javascripts/admin/job/job_list_view.tsx | 192 ++++++++++-------- .../admin/voxelytics/ai_model_list_view.tsx | 7 +- .../dataset/dataset_settings_data_tab.tsx | 12 +- .../javascripts/dashboard/dataset_view.tsx | 12 +- frontend/javascripts/types/api_types.ts | 63 ++++-- .../action-bar/ai_job_modals/constants.ts | 13 +- .../forms/align_sections_form.tsx | 4 +- .../forms/custom_ai_model_inference_form.tsx | 4 +- .../forms/mitochondria_segmentation_form.tsx | 8 +- .../forms/neuron_segmentation_form.tsx | 8 +- .../forms/nuclei_detection_form.tsx | 8 +- .../ai_job_modals/forms/start_job_form.tsx | 4 +- .../materialize_volume_annotation_modal.tsx | 4 +- .../ai_job_modals/tabs/run_ai_model_tab.tsx | 22 +- .../view/action-bar/ai_job_modals/utils.tsx | 15 +- .../action-bar/create_animation_modal.tsx | 4 +- .../view/action-bar/download_modal_view.tsx | 4 +- .../action-bar/tools/skeleton_specific_ui.tsx | 4 +- .../viewer/view/action_bar_view.tsx | 14 +- .../left-border-tabs/layer_settings_tab.tsx | 4 +- .../right-border-tabs/bounding_box_tab.tsx | 4 +- .../segments_tab/segments_view.tsx | 10 +- 28 files changed, 327 insertions(+), 323 deletions(-) create mode 100644 app/models/job/JobResultLinks.scala diff --git a/app/models/job/Job.scala b/app/models/job/Job.scala index 4a9946662e7..825e8513362 100644 --- a/app/models/job/Job.scala +++ b/app/models/job/Job.scala @@ -6,80 +6,23 @@ import com.scalableminds.util.tools.{Fox, JsonHelper} import com.scalableminds.webknossos.schema.Tables._ import models.job.JobState.JobState import models.job.JobCommand.JobCommand -import play.api.libs.json.{JsObject, Json} +import play.api.libs.json.{JsObject, Json, OFormat} import slick.jdbc.PostgresProfile.api._ import slick.jdbc.TransactionIsolation.Serializable import slick.lifted.Rep import utils.sql.{SQLDAO, SqlClient, SqlToken} import com.scalableminds.util.objectid.ObjectId -import spire.std.map import javax.inject.Inject import scala.concurrent.ExecutionContext import scala.concurrent.duration._ -trait JobResultLinks { - def commandArgs: JsObject - def command: JobCommand - def returnValue: Option[String] - protected def id: ObjectId - - protected def effectiveState: JobState - - def datasetName: Option[String] = argAsStringOpt("dataset_name") - - def datasetId: Option[String] = argAsStringOpt("dataset_id") - - protected def argAsStringOpt(key: String): Option[String] = (commandArgs \ key).toOption.flatMap(_.asOpt[String]) - - protected def argAsBooleanOpt(key: String): Option[Boolean] = (commandArgs \ key).toOption.flatMap(_.asOpt[Boolean]) - - def constructResultLink(organizationId: String): Option[String] = - if (effectiveState != JobState.SUCCESS) None - else { - command match { - case JobCommand.convert_to_wkw | JobCommand.compute_mesh_file => - datasetId.map { datasetId => - val datasetNameMaybe = datasetName.map(name => s"$name-").getOrElse("") - Some(s"/datasets/$datasetNameMaybe$datasetId/view") - }.getOrElse(datasetName.map(name => s"datasets/$organizationId/$name/view")) - case JobCommand.export_tiff | JobCommand.render_animation => - Some(s"/api/jobs/${this.id}/export") - case JobCommand.infer_neurons if this.argAsBooleanOpt("do_evaluation").getOrElse(false) => - returnValue.map { resultAnnotationLink => - resultAnnotationLink - } - case JobCommand.infer_nuclei | JobCommand.infer_neurons | JobCommand.materialize_volume_annotation | - JobCommand.infer_with_model | JobCommand.infer_mitochondria | JobCommand.align_sections | - JobCommand.infer_instances => - // There exist jobs with dataset name as return value, some with directoryName, and newest with datasetId - // Construct links that work in either case. - returnValue.map { datasetIdentifier => - ObjectId - .fromStringSync(datasetIdentifier) - .map { asObjectId => - s"/datasets/$asObjectId/view" - } - .getOrElse(s"/datasets/$organizationId/$datasetIdentifier/view") - } - case _ => None - } - } - - def resultLinkPublic(organizationId: String, webknossosPublicUrl: String): Option[String] = - for { - resultLink <- constructResultLink(organizationId) - resultLinkPublic = if (resultLink.startsWith("/")) s"$webknossosPublicUrl$resultLink" - else s"$resultLink" - } yield resultLinkPublic -} - case class Job( _id: ObjectId, _owner: ObjectId, _dataStore: String, command: JobCommand, - commandArgs: JsObject = Json.obj(), + args: JsObject = Json.obj(), state: JobState = JobState.PENDING, manualState: Option[JobState] = None, _worker: Option[ObjectId] = None, @@ -128,8 +71,8 @@ case class JobCompactInfo( ownerFirstName: String, ownerLastName: String, ownerEmail: String, - commandArgs: JsObject, - state: JobState, + args: JsObject, + effectiveState: JobState, returnValue: Option[String], resultLink: Option[String], voxelyticsWorkflowHash: Option[String], @@ -138,30 +81,15 @@ case class JobCompactInfo( ended: Option[Instant], creditCost: Option[scala.math.BigDecimal] ) extends JobResultLinks { - protected def effectiveState: JobState = state - def enrich: JobCompactInfo = this.copy( - resultLink = this.constructResultLink(organizationId) + resultLink = constructResultLink(organizationId), + args = args - "webknossos_token" - "user_auth_token" ) } -/* - -"id" -> job._id.id, -"owner" -> ownerJson, -"command" -> job.command, -"commandArgs" -> (job.commandArgs - "webknossos_token" - "user_auth_token"), -"state" -> job.state, -"manualState" -> job.manualState, -"latestRunId" -> job.latestRunId, -"returnValue" -> job.returnValue, -"resultLink" -> resultLink, -"voxelyticsWorkflowHash" -> job._voxelyticsWorkflowHash, -"created" -> job.created, -"started" -> job.started, -"ended" -> job.ended, -"creditCost" -> creditTransactionBox.toOption.map(t => (t.creditDelta * -1).toString) - */ +object JobCompactInfo { + implicit val jsonFormat: OFormat[JobCompactInfo] = Json.format[JobCompactInfo] +} class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) extends SQLDAO[Job, JobsRow, Jobs](sqlClient) { @@ -234,13 +162,14 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) accessQuery <- accessQueryFromAccessQWithPrefix(listAccessQWithPrefix, q"j.") rows <- run( q""" - SELECT j._id, j.command, u._organization, u.firstName, u.lastName, mu.email, j.commandArgs, j.state, - j.returnValue, j._voxelytics_workflowHash, j.created, j.started, j.ended, ct.cost + SELECT j._id, j.command, u._organization, u.firstName, u.lastName, mu.email, j.commandArgs, COALESCE(j.manualState, j.state), + j.returnValue, j._voxelytics_workflowHash, j.created, j.started, j.ended, ct.credit_delta FROM webknossos.jobs_ j JOIN webknossos.users_ u on j._owner = u._id JOIN webknossos.multiusers_ mu on u._multiUser = mu._id LEFT JOIN webknossos.credit_transactions_ ct ON j._id = ct._paid_job WHERE $accessQuery + ORDER BY j.created """.as[(ObjectId, String, String, @@ -258,7 +187,7 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) parsed <- Fox.serialCombined(rows) { row => for { command <- JobCommand.fromString(row._2).toFox - state <- JobState.fromString(row._8).toFox + effectiveState <- JobState.fromString(row._8).toFox commandArgs <- JsonHelper.parseAs[JsObject](row._7).toFox } yield JobCompactInfo( @@ -268,8 +197,8 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) ownerFirstName = row._4, ownerLastName = row._5, ownerEmail = row._6, - commandArgs = commandArgs, - state = state, + args = commandArgs, + effectiveState = effectiveState, returnValue = row._9, resultLink = None, // To be filled by calling “enrich” voxelyticsWorkflowHash = row._10, @@ -360,7 +289,7 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) created, isDeleted ) VALUES( - ${j._id}, ${j._owner}, ${j._dataStore}, ${j.command}, ${j.commandArgs}, + ${j._id}, ${j._owner}, ${j._dataStore}, ${j.command}, ${j.args}, ${j.state}, ${j.manualState}, ${j._worker}, ${j.latestRunId}, ${j.returnValue}, ${j.started}, ${j.ended}, ${j.created}, ${j.isDeleted})""".asUpdate) diff --git a/app/models/job/JobResultLinks.scala b/app/models/job/JobResultLinks.scala new file mode 100644 index 00000000000..b9f0a6b916b --- /dev/null +++ b/app/models/job/JobResultLinks.scala @@ -0,0 +1,62 @@ +package models.job + +import com.scalableminds.util.objectid.ObjectId +import models.job.JobCommand.JobCommand +import models.job.JobState.JobState +import play.api.libs.json.JsObject + +trait JobResultLinks { + def args: JsObject + def command: JobCommand + def returnValue: Option[String] + protected def id: ObjectId + + protected def effectiveState: JobState + + def datasetName: Option[String] = argAsStringOpt("dataset_name") + + def datasetId: Option[String] = argAsStringOpt("dataset_id") + + protected def argAsStringOpt(key: String): Option[String] = (args \ key).toOption.flatMap(_.asOpt[String]) + + protected def argAsBooleanOpt(key: String): Option[Boolean] = (args \ key).toOption.flatMap(_.asOpt[Boolean]) + + def constructResultLink(organizationId: String): Option[String] = + if (effectiveState != JobState.SUCCESS) None + else { + command match { + case JobCommand.convert_to_wkw | JobCommand.compute_mesh_file => + datasetId.map { datasetId => + val datasetNameMaybe = datasetName.map(name => s"$name-").getOrElse("") + Some(s"/datasets/$datasetNameMaybe$datasetId/view") + }.getOrElse(datasetName.map(name => s"datasets/$organizationId/$name/view")) + case JobCommand.export_tiff | JobCommand.render_animation => + Some(s"/api/jobs/${this.id}/export") + case JobCommand.infer_neurons if this.argAsBooleanOpt("do_evaluation").getOrElse(false) => + returnValue.map { resultAnnotationLink => + resultAnnotationLink + } + case JobCommand.infer_nuclei | JobCommand.infer_neurons | JobCommand.materialize_volume_annotation | + JobCommand.infer_with_model | JobCommand.infer_mitochondria | JobCommand.align_sections | + JobCommand.infer_instances => + // There exist jobs with dataset name as return value, some with directoryName, and newest with datasetId + // Construct links that work in either case. + returnValue.map { datasetIdentifier => + ObjectId + .fromStringSync(datasetIdentifier) + .map { asObjectId => + s"/datasets/$asObjectId/view" + } + .getOrElse(s"/datasets/$organizationId/$datasetIdentifier/view") + } + case _ => None + } + } + + def resultLinkPublic(organizationId: String, webknossosPublicUrl: String): Option[String] = + for { + resultLink <- constructResultLink(organizationId) + resultLinkPublic = if (resultLink.startsWith("/")) s"$webknossosPublicUrl$resultLink" + else s"$resultLink" + } yield resultLinkPublic +} diff --git a/app/models/job/JobService.scala b/app/models/job/JobService.scala index 7f7e83cb76f..dd6f17f3600 100644 --- a/app/models/job/JobService.scala +++ b/app/models/job/JobService.scala @@ -158,7 +158,7 @@ class JobService @Inject()(wkConf: WkConf, if (job.state == JobState.FAILURE && job.command == JobCommand.convert_to_wkw) { logger.info( s"WKW conversion job ${job._id} failed. Deleting dataset from the database, freeing the directoryName...") - val commandArgs = job.commandArgs.value + val commandArgs = job.args.value for { datasetDirectoryName <- commandArgs.get("dataset_directory_name").map(_.as[String]).toFox organizationId <- commandArgs.get("organization_id").map(_.as[String]).toFox @@ -180,9 +180,10 @@ class JobService @Inject()(wkConf: WkConf, "id" -> job._id.id, "owner" -> ownerJson, "command" -> job.command, - "commandArgs" -> (job.commandArgs - "webknossos_token" - "user_auth_token"), + "commandArgs" -> (job.args - "webknossos_token" - "user_auth_token"), "state" -> job.state, "manualState" -> job.manualState, + "effectiveState" -> Json.toJson(job.manualState.getOrElse(job.state)), "latestRunId" -> job.latestRunId, "returnValue" -> job.returnValue, "resultLink" -> resultLink, @@ -204,7 +205,7 @@ class JobService @Inject()(wkConf: WkConf, Json.obj( "job_id" -> job._id.id, "command" -> job.command, - "job_kwargs" -> (job.commandArgs ++ Json.obj("user_auth_token" -> userAuthToken.id)) + "job_kwargs" -> (job.args ++ Json.obj("user_auth_token" -> userAuthToken.id)) ) } diff --git a/frontend/javascripts/admin/api/jobs.ts b/frontend/javascripts/admin/api/jobs.ts index 05b62541f41..f4c295c1e3f 100644 --- a/frontend/javascripts/admin/api/jobs.ts +++ b/frontend/javascripts/admin/api/jobs.ts @@ -1,11 +1,9 @@ import Request from "libs/request"; import { location } from "libs/window"; +import _ from "lodash"; import type { APIAnnotationType, - APIEffectiveJobState, APIJob, - APIJobManualState, - APIJobState, AdditionalCoordinate, AiModel, RenderAnimationOptions, @@ -16,43 +14,16 @@ import { assertResponseLimit } from "./api_utils"; function transformBackendJobToAPIJob(job: any): APIJob { return { - id: job.id, - datasetId: job.commandArgs.datasetId, - owner: job.owner, - type: job.command, - datasetName: job.commandArgs.dataset_name, - datasetDirectoryName: job.commandArgs.dataset_directory_name, - organizationId: job.commandArgs.organization_id || job.commandArgs.organization_name, - layerName: job.commandArgs.layer_name || job.commandArgs.volume_layer_name, - annotationLayerName: job.commandArgs.annotation_layer_name, - boundingBox: job.commandArgs.bbox, - ndBoundingBox: job.commandArgs.nd_bbox, - exportFileName: job.commandArgs.export_file_name, - tracingId: job.commandArgs.volume_tracing_id, - annotationId: job.commandArgs.annotation_id, - annotationType: job.commandArgs.annotation_type, - mergeSegments: job.commandArgs.merge_segments, - trainingAnnotations: job.commandArgs.training_annotations, - state: adaptJobState(job.state, job.manualState), - manualState: job.manualState, - result: job.returnValue, - resultLink: job.resultLink, - createdAt: job.created, - voxelyticsWorkflowHash: job.voxelyticsWorkflowHash, - creditCost: job.creditCost, - modelId: job.commandArgs.model_id, + ...job, + args: _.mapKeys(job.args, (_, key) => _.camelCase(key)), + state: job.effectiveState, }; } export async function getJobs(): Promise { const jobs = await Request.receiveJSON("/api/jobs"); assertResponseLimit(jobs); - return ( - jobs - .map(transformBackendJobToAPIJob) - // Newest jobs should be first - .sort((a: APIJob, b: APIJob) => a.createdAt > b.createdAt) - ); + return jobs.map(transformBackendJobToAPIJob); } export async function getJob(jobId: string): Promise { @@ -60,17 +31,6 @@ export async function getJob(jobId: string): Promise { return transformBackendJobToAPIJob(job); } -function adaptJobState( - celeryState: APIJobState, - manualState: APIJobManualState, -): APIEffectiveJobState { - if (manualState) { - return manualState; - } - - return celeryState || "UNKNOWN"; -} - export async function cancelJob(jobId: string): Promise { return Request.receiveJSON(`/api/jobs/${jobId}/cancel`, { method: "PATCH", diff --git a/frontend/javascripts/admin/dataset/dataset_upload_view.tsx b/frontend/javascripts/admin/dataset/dataset_upload_view.tsx index b026ec00be6..8bf342fb285 100644 --- a/frontend/javascripts/admin/dataset/dataset_upload_view.tsx +++ b/frontend/javascripts/admin/dataset/dataset_upload_view.tsx @@ -64,7 +64,7 @@ import { type FileWithPath, useDropzone } from "react-dropzone"; import { type BlockerFunction, Link } from "react-router-dom"; import { type APIDataStore, - APIJobType, + APIJobCommand, type APIOrganization, type APITeam, type APIUser, @@ -685,7 +685,7 @@ class DatasetUploadView extends React.Component { : this.getDatastoreForUrl(this.state.datastoreUrl); return ( - selectedDatastore?.jobsSupportedByAvailableWorkers.includes(APIJobType.CONVERT_TO_WKW) || + selectedDatastore?.jobsSupportedByAvailableWorkers.includes(APIJobCommand.CONVERT_TO_WKW) || false ); }; diff --git a/frontend/javascripts/admin/job/job_hooks.ts b/frontend/javascripts/admin/job/job_hooks.ts index c9eaa8a10fc..91e58d934a4 100644 --- a/frontend/javascripts/admin/job/job_hooks.ts +++ b/frontend/javascripts/admin/job/job_hooks.ts @@ -31,7 +31,7 @@ export function useStartAndPollJob({ if (initialJobKeyExtractor != null && areJobsEnabled) { (async () => { const jobs = await getJobs(); - jobs.sort((a, b) => b.createdAt - a.createdAt); // sort in descending order + jobs.sort((a, b) => b.created - a.created); // sort in descending order for (const job of jobs) { const key = initialJobKeyExtractor(job); if (key != null && job.state === "SUCCESS") { @@ -49,7 +49,7 @@ export function useStartAndPollJob({ if (job.state === "SUCCESS") { onSuccess(job); setRunningJobs((previous) => previous.filter(([, j]) => j !== jobId)); - if (mostRecentSuccessfulJob == null || job.createdAt > mostRecentSuccessfulJob.createdAt) { + if (mostRecentSuccessfulJob == null || job.created > mostRecentSuccessfulJob.created) { setMostRecentSuccessfulJob(job); } } else if (job.state === "FAILURE") { diff --git a/frontend/javascripts/admin/job/job_list_view.tsx b/frontend/javascripts/admin/job/job_list_view.tsx index 98da18c8f64..6f126c5c85c 100644 --- a/frontend/javascripts/admin/job/job_list_view.tsx +++ b/frontend/javascripts/admin/job/job_list_view.tsx @@ -27,7 +27,7 @@ import _ from "lodash"; import type * as React from "react"; import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; -import { type APIJob, APIJobType, type APIUserBase } from "types/api_types"; +import { type APIJob, APIJobCommand } from "types/api_types"; import { getReadableURLPart } from "viewer/model/accessors/dataset_accessor"; // Unfortunately, the twoToneColor (nor the style) prop don't support @@ -168,145 +168,170 @@ function JobListView() { function getLinkToDataset(job: APIJob) { // prefer updated link over legacy link. - if (job.datasetId != null) - return `/datasets/${getReadableURLPart({ name: job.datasetName || "unknown_name", id: job.datasetId })}/view`; - if (job.organizationId != null && (job.datasetName != null || job.datasetDirectoryName != null)) - return `/datasets/${job.organizationId}/${job.datasetDirectoryName || job.datasetName}/view`; + if (job.args.datasetId != null) + return `/datasets/${getReadableURLPart({ name: job.args.datasetName || "unknown_name", id: job.args.datasetId })}/view`; + if ( + job.organizationId != null && + (job.args.datasetName != null || job.args.datasetDirectoryName != null) + ) + return `/datasets/${job.organizationId}/${job.args.datasetDirectoryName || job.args.datasetName}/view`; return null; } function renderDescription(__: any, job: APIJob) { const linkToDataset = getLinkToDataset(job); - if (job.type === APIJobType.CONVERT_TO_WKW && job.datasetName) { - return {`Conversion to WKW of ${job.datasetName}`}; - } else if (job.type === APIJobType.EXPORT_TIFF && linkToDataset != null) { + if (job.command === APIJobCommand.CONVERT_TO_WKW && job.args.datasetName) { + return {`Conversion to WKW of ${job.args.datasetName}`}; + } else if (job.command === APIJobCommand.EXPORT_TIFF && linkToDataset != null) { const labelToAnnotationOrDataset = - job.annotationId != null ? ( - - annotation of dataset {job.datasetName} + job.args.annotationId != null ? ( + + annotation of dataset {job.args.datasetName} ) : ( - dataset {job.datasetName} + dataset {job.args.datasetName} ); - const layerLabel = job.annotationLayerName || job.layerName; + const layerLabel = job.args.annotationLayerName || job.args.layerName; return ( Tiff export of layer {layerLabel} from {labelToAnnotationOrDataset} (Bounding Box{" "} - {job.ndBoundingBox ? formatWkLibsNdBBox(job.ndBoundingBox) : job.boundingBox}) + {job.args.ndBoundingBox + ? formatWkLibsNdBBox(job.args.ndBoundingBox) + : job.args.boundingBox} + ) ); - } else if (job.type === APIJobType.RENDER_ANIMATION && linkToDataset != null) { + } else if (job.command === APIJobCommand.RENDER_ANIMATION && linkToDataset != null) { return ( - Animation rendering for layer {job.layerName} of dataset{" "} - {job.datasetName} + Animation rendering for layer {job.args.layerName} of dataset{" "} + {job.args.datasetName} ); - } else if (job.type === APIJobType.COMPUTE_MESH_FILE && linkToDataset != null) { + } else if (job.command === APIJobCommand.COMPUTE_MESH_FILE && linkToDataset != null) { return ( - Mesh file computation for {job.datasetName}{" "} + Mesh file computation for {job.args.datasetName}{" "} ); - } else if (job.type === APIJobType.COMPUTE_SEGMENT_INDEX_FILE && linkToDataset != null) { + } else if (job.command === APIJobCommand.COMPUTE_SEGMENT_INDEX_FILE && linkToDataset != null) { return ( - Segment index file computation for {job.datasetName}{" "} + Segment index file computation for + {job.args.datasetName} + {" "} ); } else if ( - job.type === APIJobType.FIND_LARGEST_SEGMENT_ID && + job.command === APIJobCommand.FIND_LARGEST_SEGMENT_ID && linkToDataset != null && - job.layerName + job.args.layerName ) { return ( - Largest segment id detection for layer {job.layerName} of{" "} - {job.datasetName}{" "} + Largest segment id detection for layer {job.args.layerName} of{" "} + {job.args.datasetName}{" "} ); - } else if (job.type === APIJobType.INFER_NUCLEI && linkToDataset != null && job.layerName) { + } else if ( + job.command === APIJobCommand.INFER_NUCLEI && + linkToDataset != null && + job.args.layerName + ) { return ( - Nuclei inferral for layer {job.layerName} of{" "} - {job.datasetName}{" "} + Nuclei inferral for layer {job.args.layerName} of{" "} + {job.args.datasetName}{" "} ); } else if ( - job.type === APIJobType.INFER_NEURONS && + job.command === APIJobCommand.INFER_NEURONS && linkToDataset != null && - job.layerName && - job.modelId == null + job.args.layerName && + job.args.modelId == null ) { return ( - AI Neuron inferral for layer {job.layerName} of{" "} - {job.datasetName}{" "} + AI Neuron inferral for layer {job.args.layerName} of{" "} + {job.args.datasetName}{" "} ); } else if ( - (job.type === APIJobType.DEPRECATED_INFER_WITH_MODEL || - job.type === APIJobType.INFER_NEURONS) && + (job.command === APIJobCommand.DEPRECATED_INFER_WITH_MODEL || + job.command === APIJobCommand.INFER_NEURONS) && linkToDataset != null ) { return ( - Run AI segmentation with custom model on {job.datasetName} + Run AI segmentation with custom model on{" "} + {job.args.datasetName} ); } else if ( - job.type === APIJobType.INFER_MITOCHONDRIA && + job.command === APIJobCommand.INFER_MITOCHONDRIA && linkToDataset != null && - job.layerName + job.args.layerName ) { return ( - AI Mitochondria inferral for layer {job.layerName} of{" "} - {job.datasetName}{" "} + AI Mitochondria inferral for layer {job.args.layerName} of{" "} + {job.args.datasetName}{" "} ); - } else if (job.type === APIJobType.INFER_INSTANCES && linkToDataset != null && job.layerName) { + } else if ( + job.command === APIJobCommand.INFER_INSTANCES && + linkToDataset != null && + job.args.layerName + ) { return ( - AI instance segmentation for layer {job.layerName} of{" "} - {job.datasetName}{" "} + AI instance segmentation for layer {job.args.layerName} of{" "} + {job.args.datasetName}{" "} ); - } else if (job.type === APIJobType.ALIGN_SECTIONS && linkToDataset != null && job.layerName) { + } else if ( + job.command === APIJobCommand.ALIGN_SECTIONS && + linkToDataset != null && + job.args.layerName + ) { return ( - Align sections for layer {job.layerName} of{" "} - {job.datasetName}{" "} + Align sections for layer {job.args.layerName} of{" "} + {job.args.datasetName}{" "} ); - } else if (job.type === APIJobType.MATERIALIZE_VOLUME_ANNOTATION && linkToDataset != null) { + } else if ( + job.command === APIJobCommand.MATERIALIZE_VOLUME_ANNOTATION && + linkToDataset != null + ) { return ( - Materialize annotation for {job.layerName ? ` layer ${job.layerName} of ` : " "} - {job.datasetName} - {job.mergeSegments + Materialize annotation for {job.args.layerName ? ` layer ${job.args.layerName} of ` : " "} + {job.args.datasetName} + {job.args.mergeSegments ? ". This includes merging the segments that were merged via merger mode." : null} ); } else if ( - job.type === APIJobType.TRAIN_NEURON_MODEL || - job.type === APIJobType.TRAIN_INSTANCE_MODEL || - job.type === APIJobType.DEPRECATED_TRAIN_MODEL + job.command === APIJobCommand.TRAIN_NEURON_MODEL || + job.command === APIJobCommand.TRAIN_INSTANCE_MODEL || + job.command === APIJobCommand.DEPRECATED_TRAIN_MODEL ) { - const numberOfTrainingAnnotations = job.trainingAnnotations?.length || 0; + const numberOfTrainingAnnotations = job.args.trainingAnnotations?.length || 0; const modelName = - job.type === APIJobType.TRAIN_NEURON_MODEL || job.type === APIJobType.DEPRECATED_TRAIN_MODEL + job.command === APIJobCommand.TRAIN_NEURON_MODEL || + job.command === APIJobCommand.DEPRECATED_TRAIN_MODEL ? "neuron model" : "instance model"; return ( {`Train ${modelName} on ${numberOfTrainingAnnotations} ${Utils.pluralize("annotation", numberOfTrainingAnnotations)}. `} - {getShowTrainingDataLink(job.trainingAnnotations)} + {getShowTrainingDataLink(job.args.trainingAnnotations)} ); } else { - return {job.type}; + return {job.command}; } } @@ -359,9 +384,9 @@ function JobListView() { ); } else if ( - job.type === APIJobType.CONVERT_TO_WKW || - job.type === APIJobType.COMPUTE_SEGMENT_INDEX_FILE || - job.type === APIJobType.ALIGN_SECTIONS + job.command === APIJobCommand.CONVERT_TO_WKW || + job.command === APIJobCommand.COMPUTE_SEGMENT_INDEX_FILE || + job.command === APIJobCommand.ALIGN_SECTIONS ) { return ( @@ -373,7 +398,7 @@ function JobListView() { )} ); - } else if (job.type === APIJobType.EXPORT_TIFF) { + } else if (job.command === APIJobCommand.EXPORT_TIFF) { return ( {job.resultLink && ( @@ -384,7 +409,7 @@ function JobListView() { )} ); - } else if (job.type === APIJobType.RENDER_ANIMATION) { + } else if (job.command === APIJobCommand.RENDER_ANIMATION) { return ( {job.resultLink && ( @@ -395,16 +420,16 @@ function JobListView() { )} ); - } else if (job.type === "find_largest_segment_id") { - return {job.result}; + } else if (job.command === "find_largest_segment_id") { + return {job.returnValue}; } else if ( - job.type === APIJobType.INFER_NUCLEI || - job.type === APIJobType.INFER_NEURONS || - job.type === APIJobType.MATERIALIZE_VOLUME_ANNOTATION || - job.type === APIJobType.COMPUTE_MESH_FILE || - job.type === APIJobType.DEPRECATED_INFER_WITH_MODEL || - job.type === APIJobType.INFER_MITOCHONDRIA || - job.type === APIJobType.INFER_INSTANCES + job.command === APIJobCommand.INFER_NUCLEI || + job.command === APIJobCommand.INFER_NEURONS || + job.command === APIJobCommand.MATERIALIZE_VOLUME_ANNOTATION || + job.command === APIJobCommand.COMPUTE_MESH_FILE || + job.command === APIJobCommand.DEPRECATED_INFER_WITH_MODEL || + job.command === APIJobCommand.INFER_MITOCHONDRIA || + job.command === APIJobCommand.INFER_INSTANCES ) { return ( @@ -417,8 +442,8 @@ function JobListView() { ); } else if ( - job.type === APIJobType.TRAIN_NEURON_MODEL || - job.type === APIJobType.DEPRECATED_TRAIN_MODEL + job.command === APIJobCommand.TRAIN_NEURON_MODEL || + job.command === APIJobCommand.DEPRECATED_TRAIN_MODEL ) { return ( @@ -435,7 +460,7 @@ function JobListView() { Result )} - {job.result &&

{job.result}

} + {job.returnValue &&

{job.returnValue}

}
); } @@ -481,7 +506,11 @@ function JobListView() { /> job.args.datasetName || ""], + searchQuery, + )} rowKey="id" pagination={{ defaultPageSize: 50, @@ -501,13 +530,12 @@ function JobListView() { ((job) => job.owner.lastName)} - render={(owner: APIUserBase) => ( + sorter={Utils.localeCompareBy((job) => job.ownerLastName)} + render={(job: APIJob) => ( <> -
{owner.email ? `${owner.lastName}, ${owner.firstName}` : "-"}
-
{owner.email ? `(${owner.email})` : "-"}
+
{`${job.ownerLastName}, ${job.ownerFirstName}`}
+
{`(${job.ownerEmail})`}
)} /> @@ -519,8 +547,8 @@ function JobListView() { } - sorter={Utils.compareBy((job) => job.createdAt)} + render={(job) => } + sorter={Utils.compareBy((job) => job.created)} defaultSortOrder="descend" /> {isCurrentUserSuperUser ? ( diff --git a/frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx b/frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx index dafd7fc4a35..6f5d75ab9c7 100644 --- a/frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx +++ b/frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx @@ -193,11 +193,8 @@ const renderActionsForModel = (model: AiModel, onChangeSharedOrganizations: () = if (model.trainingJob == null) { return organizationSharingButton; } - const { - voxelyticsWorkflowHash, - trainingAnnotations, - state: trainingJobState, - } = model.trainingJob; + const { voxelyticsWorkflowHash, state: trainingJobState } = model.trainingJob; + const trainingAnnotations = model.trainingJob.args.trainingAnnotations; return (
diff --git a/frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx b/frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx index c044d9dda16..09947c27a83 100644 --- a/frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx +++ b/frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx @@ -22,7 +22,7 @@ import Toast from "libs/toast"; import { BoundingBoxInput, Vector3Input } from "libs/vector_input"; import type React from "react"; import { cloneElement, useEffect } from "react"; -import { type APIDataLayer, type APIDataset, APIJobType } from "types/api_types"; +import { type APIDataLayer, type APIDataset, APIJobCommand } from "types/api_types"; import type { DataLayer } from "types/schemas/datasource.types"; import { syncValidator } from "types/validation"; import { AllUnits, LongUnitToShortUnitMap, type Vector3 } from "viewer/constants"; @@ -274,8 +274,8 @@ function SimpleLayerForm({ ); }, initialJobKeyExtractor: (job) => - job.type === "find_largest_segment_id" && job.datasetName === dataset?.name - ? (job.datasetName ?? "largest_segment_id") + job.command === "find_largest_segment_id" && job.args.datasetName === dataset?.name + ? (job.args.datasetName ?? "largest_segment_id") : null, }); const activeJob = runningJobs[0]; @@ -287,7 +287,7 @@ function SimpleLayerForm({ Toast.info( "A job was scheduled to compute the largest segment ID. It will be automatically updated for the dataset. You may close this tab now.", ); - return [job.datasetName ?? "largest_segment_id", job.id] as [string, string]; + return [job.args.datasetName ?? "largest_segment_id", job.id] as [string, string]; } : null; @@ -515,7 +515,7 @@ function SimpleLayerForm({ }} /> {dataset?.dataStore.jobsSupportedByAvailableWorkers.includes( - APIJobType.FIND_LARGEST_SEGMENT_ID, + APIJobCommand.FIND_LARGEST_SEGMENT_ID, ) ? ( {mostRecentSuccessfulJob && (
- Output of most recent job: {mostRecentSuccessfulJob.result} + Output of most recent job: {mostRecentSuccessfulJob.returnValue}
)} diff --git a/frontend/javascripts/dashboard/dataset_view.tsx b/frontend/javascripts/dashboard/dataset_view.tsx index 4692a7c76c7..a76c4d89c40 100644 --- a/frontend/javascripts/dashboard/dataset_view.tsx +++ b/frontend/javascripts/dashboard/dataset_view.tsx @@ -430,10 +430,10 @@ function NewJobsAlert({ jobs }: { jobs: APIJob[] }) { const newJobs = jobs .filter( (job) => - job.type === "convert_to_wkw" && - dayjs.duration(now.diff(job.createdAt)).asDays() <= RECENT_DATASET_DAY_THRESHOLD, + job.command === "convert_to_wkw" && + dayjs.duration(now.diff(job.created)).asDays() <= RECENT_DATASET_DAY_THRESHOLD, ) - .sort((a, b) => b.createdAt - a.createdAt); + .sort((a, b) => b.created - a.created); if (newJobs.length === 0) { return null; @@ -463,12 +463,12 @@ function NewJobsAlert({ jobs }: { jobs: APIJob[] }) {
{icon}{" "} {job.state === "SUCCESS" && job.resultLink ? ( - {job.datasetName} + {job.args.datasetName} ) : ( - job.datasetName || "UNKNOWN" + job.args.datasetName || "UNKNOWN" )} {Unicode.NonBreakingSpace}(started at{Unicode.NonBreakingSpace} - + ) diff --git a/frontend/javascripts/types/api_types.ts b/frontend/javascripts/types/api_types.ts index b6a3c6cdb71..64e3a7dff18 100644 --- a/frontend/javascripts/types/api_types.ts +++ b/frontend/javascripts/types/api_types.ts @@ -155,7 +155,7 @@ export type APIDataStore = { readonly url: string; readonly allowsUpload: boolean; readonly jobsEnabled: boolean; - readonly jobsSupportedByAvailableWorkers: APIJobType[]; + readonly jobsSupportedByAvailableWorkers: APIJobCommand[]; }; export type APITracingStore = { readonly name: string; @@ -774,16 +774,10 @@ export type APIFeatureToggles = { readonly passkeysEnabled: boolean; readonly registerToDefaultOrgaEnabled?: boolean; }; -export type APIJobState = "SUCCESS" | "PENDING" | "STARTED" | "FAILURE" | "CANCELLED" | null; -export type APIJobManualState = "SUCCESS" | "FAILURE" | null; -export type APIEffectiveJobState = - | "UNKNOWN" - | "SUCCESS" - | "PENDING" - | "STARTED" - | "FAILURE" - | "CANCELLED"; -export enum APIJobType { + +export type APIJobState = "PENDING" | "STARTED" | "SUCCESS" | "FAILURE" | "CANCELLED"; + +export enum APIJobCommand { ALIGN_SECTIONS = "align_sections", CONVERT_TO_WKW = "convert_to_wkw", EXPORT_TIFF = "export_tiff", @@ -808,32 +802,57 @@ export type WkLibsNdBoundingBox = BoundingBoxObject & { additionalAxes: Array; }; +// Different job types have different argument sets. +// To simplify the frontend code, this is just the union, +// listing all possible frontend-relevant arguments. +export type ApiJobArgs = { + readonly datasetId: string | null | undefined; + readonly datasetName: string | null | undefined; + readonly mergeSegments: boolean | null | undefined; + readonly trainingAnnotations: Array<{ annotationId: string }>; + readonly datasetDirectoryName: string | null | undefined; + readonly annotationId: string | null | undefined; + readonly annotationLayerName: string | null | undefined; + readonly layerName: string | null | undefined; + readonly modelId: string | null | undefined; + readonly boundingBox: string | null | undefined; + readonly ndBoundingBox: WkLibsNdBoundingBox | null | undefined; +}; + export type APIJob = { + readonly id: string; + readonly command: APIJobCommand; + readonly organizationId: string; + readonly ownerFirstName: string; + readonly ownerLastName: string; + readonly ownerEmail: string; + readonly args: ApiJobArgs; + readonly state: APIJobState; + readonly resultLink: string | null | undefined; + readonly returnValue: string | null | undefined; + readonly voxelyticsWorkflowHash: string | null | undefined; + readonly created: number; + readonly started: number | null | undefined; + readonly ended: number | null | undefined; + readonly creditCost: string | null | undefined; +}; + +export type APIJobOLD = { readonly id: string; readonly datasetId: string | null | undefined; readonly owner: APIUserBase; readonly datasetName: string | null | undefined; - readonly datasetDirectoryName: string | null | undefined; readonly exportFileName: string | null | undefined; - readonly layerName: string | null | undefined; - readonly annotationLayerName: string | null | undefined; readonly tracingId: string | null | undefined; - readonly annotationId: string | null | undefined; readonly annotationType: string | null | undefined; readonly organizationId: string | null | undefined; - readonly boundingBox: string | null | undefined; - readonly ndBoundingBox: WkLibsNdBoundingBox | null | undefined; readonly mergeSegments: boolean | null | undefined; - readonly type: APIJobType; - readonly state: APIEffectiveJobState; - readonly manualState: APIJobManualState; + readonly command: APIJobCommand; readonly result: string | null | undefined; readonly resultLink: string | null | undefined; readonly createdAt: number; readonly voxelyticsWorkflowHash: string | null; - readonly trainingAnnotations: Array<{ annotationId: string }>; readonly creditCost: string | null | undefined; - readonly modelId: string | null | undefined; }; export type AiModel = { diff --git a/frontend/javascripts/viewer/view/action-bar/ai_job_modals/constants.ts b/frontend/javascripts/viewer/view/action-bar/ai_job_modals/constants.ts index 11d8edd40e3..fa534df6a8a 100644 --- a/frontend/javascripts/viewer/view/action-bar/ai_job_modals/constants.ts +++ b/frontend/javascripts/viewer/view/action-bar/ai_job_modals/constants.ts @@ -1,10 +1,10 @@ -import type { APIJobType } from "types/api_types"; +import type { APIJobCommand } from "types/api_types"; import type { Vector3 } from "viewer/constants"; export type ModalJobTypes = - | APIJobType.INFER_NEURONS - | APIJobType.INFER_NUCLEI - | APIJobType.INFER_MITOCHONDRIA; + | APIJobCommand.INFER_NEURONS + | APIJobCommand.INFER_NUCLEI + | APIJobCommand.INFER_MITOCHONDRIA; export type StartAIJobModalState = ModalJobTypes | "invisible"; @@ -28,7 +28,10 @@ export const MIN_BBOX_EXTENT: Record = { infer_mitochondria: [4, 4, 4], }; -export const MEAN_VX_SIZE: Record = { +export const MEAN_VX_SIZE: Record< + APIJobCommand.INFER_NEURONS | APIJobCommand.INFER_NUCLEI, + Vector3 +> = { infer_neurons: [7.96, 7.96, 31.2], infer_nuclei: [179.84, 179.84, 224.0], // "infer_mitochondria" infers on finest available mag diff --git a/frontend/javascripts/viewer/view/action-bar/ai_job_modals/forms/align_sections_form.tsx b/frontend/javascripts/viewer/view/action-bar/ai_job_modals/forms/align_sections_form.tsx index 9ee71cf16ea..324e6ba98a7 100644 --- a/frontend/javascripts/viewer/view/action-bar/ai_job_modals/forms/align_sections_form.tsx +++ b/frontend/javascripts/viewer/view/action-bar/ai_job_modals/forms/align_sections_form.tsx @@ -4,7 +4,7 @@ import features from "features"; import { useWkSelector } from "libs/react_hooks"; import { useCallback } from "react"; import { useDispatch } from "react-redux"; -import { APIJobType } from "types/api_types"; +import { APIJobCommand } from "types/api_types"; import { setAIJobModalStateAction } from "viewer/model/actions/ui_actions"; import { type JobApiCallArgsType, StartJobForm } from "./start_job_form"; @@ -26,7 +26,7 @@ export function AlignSectionsForm() { return ( ) => Promise; - jobName: APIJobType; + jobName: APIJobCommand; description: React.ReactNode; jobSpecificInputFields?: React.ReactNode | undefined; isBoundingBoxConfigurable?: boolean; diff --git a/frontend/javascripts/viewer/view/action-bar/ai_job_modals/materialize_volume_annotation_modal.tsx b/frontend/javascripts/viewer/view/action-bar/ai_job_modals/materialize_volume_annotation_modal.tsx index ee8167dc553..40d23dde15c 100644 --- a/frontend/javascripts/viewer/view/action-bar/ai_job_modals/materialize_volume_annotation_modal.tsx +++ b/frontend/javascripts/viewer/view/action-bar/ai_job_modals/materialize_volume_annotation_modal.tsx @@ -4,7 +4,7 @@ import { useWkSelector } from "libs/react_hooks"; import { computeArrayFromBoundingBox } from "libs/utils"; import { useCallback } from "react"; import type { APIDataLayer } from "types/api_types"; -import { APIJobType } from "types/api_types"; +import { APIJobCommand } from "types/api_types"; import { getActiveSegmentationTracingLayer, getReadableNameOfVolumeLayer, @@ -128,7 +128,7 @@ export function MaterializeVolumeAnnotationModal({ dispatch(setAIJobModalStateAction(APIJobType.INFER_NEURONS))} + checked={aIJobModalState === APIJobCommand.INFER_NEURONS} + onClick={() => dispatch(setAIJobModalStateAction(APIJobCommand.INFER_NEURONS))} > @@ -85,8 +85,8 @@ export function RunAiModelTab({ aIJobModalState }: { aIJobModalState: string }) dispatch(setAIJobModalStateAction(APIJobType.INFER_MITOCHONDRIA))} + checked={aIJobModalState === APIJobCommand.INFER_MITOCHONDRIA} + onClick={() => dispatch(setAIJobModalStateAction(APIJobCommand.INFER_MITOCHONDRIA))} > @@ -106,8 +106,8 @@ export function RunAiModelTab({ aIJobModalState }: { aIJobModalState: string }) dispatch(setAIJobModalStateAction(APIJobType.INFER_NUCLEI))} + checked={aIJobModalState === APIJobCommand.INFER_NUCLEI} + onClick={() => dispatch(setAIJobModalStateAction(APIJobCommand.INFER_NUCLEI))} > @@ -124,12 +124,12 @@ export function RunAiModelTab({ aIJobModalState }: { aIJobModalState: string }) - {aIJobModalState === APIJobType.INFER_NEURONS ? : null} - {aIJobModalState === APIJobType.INFER_NUCLEI ? : null} - {aIJobModalState === APIJobType.INFER_MITOCHONDRIA ? ( + {aIJobModalState === APIJobCommand.INFER_NEURONS ? : null} + {aIJobModalState === APIJobCommand.INFER_NUCLEI ? : null} + {aIJobModalState === APIJobCommand.INFER_MITOCHONDRIA ? ( ) : null} - {aIJobModalState === APIJobType.ALIGN_SECTIONS ? : null} + {aIJobModalState === APIJobCommand.ALIGN_SECTIONS ? : null} )} diff --git a/frontend/javascripts/viewer/view/action-bar/ai_job_modals/utils.tsx b/frontend/javascripts/viewer/view/action-bar/ai_job_modals/utils.tsx index 9ce5e7842af..46c016c23a9 100644 --- a/frontend/javascripts/viewer/view/action-bar/ai_job_modals/utils.tsx +++ b/frontend/javascripts/viewer/view/action-bar/ai_job_modals/utils.tsx @@ -2,7 +2,7 @@ import Toast from "libs/toast"; import { computeArrayFromBoundingBox, computeBoundingBoxFromBoundingBoxObject } from "libs/utils"; import _ from "lodash"; import type { APIAnnotation, APIDataLayer, APIDataset, VoxelSize } from "types/api_types"; -import { APIJobType } from "types/api_types"; +import { APIJobCommand } from "types/api_types"; import type { Vector3, Vector6 } from "viewer/constants"; import { UnitShort } from "viewer/constants"; import { getMagInfo } from "viewer/model/accessors/dataset_accessor"; @@ -13,10 +13,10 @@ import { MEAN_VX_SIZE, MIN_BBOX_EXTENT, type ModalJobTypes } from "./constants"; export const getMinimumDSSize = (jobType: ModalJobTypes) => { switch (jobType) { - case APIJobType.INFER_NEURONS: - case APIJobType.INFER_NUCLEI: + case APIJobCommand.INFER_NEURONS: + case APIJobCommand.INFER_NUCLEI: return MIN_BBOX_EXTENT[jobType].map((dim) => dim * 2); - case APIJobType.INFER_MITOCHONDRIA: + case APIJobCommand.INFER_MITOCHONDRIA: return MIN_BBOX_EXTENT[jobType].map((dim) => dim + 80); } }; @@ -39,9 +39,12 @@ export function getBoundingBoxesForLayers(layers: APIDataLayer[]): UserBoundingB export const getBestFittingMagComparedToTrainingDS = ( colorLayer: APIDataLayer, datasetScaleMag1: VoxelSize, - jobType: APIJobType.INFER_MITOCHONDRIA | APIJobType.INFER_NEURONS | APIJobType.INFER_NUCLEI, + jobType: + | APIJobCommand.INFER_MITOCHONDRIA + | APIJobCommand.INFER_NEURONS + | APIJobCommand.INFER_NUCLEI, ) => { - if (jobType === APIJobType.INFER_MITOCHONDRIA) { + if (jobType === APIJobCommand.INFER_MITOCHONDRIA) { // infer_mitochondria_model always infers on the finest mag of the current dataset const magInfo = getMagInfo(colorLayer.mags); return magInfo.getFinestMag(); diff --git a/frontend/javascripts/viewer/view/action-bar/create_animation_modal.tsx b/frontend/javascripts/viewer/view/action-bar/create_animation_modal.tsx index d1797a5d354..6f08b74613b 100644 --- a/frontend/javascripts/viewer/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/viewer/view/action-bar/create_animation_modal.tsx @@ -20,7 +20,7 @@ import { import type { Mesh } from "three"; import { type APIDataLayer, - APIJobType, + APIJobCommand, type APISegmentationLayer, CAMERA_POSITIONS, MOVIE_RESOLUTIONS, @@ -330,7 +330,7 @@ function CreateAnimationModal(props: Props) { const isFeatureDisabled = !( dataset.dataStore.jobsEnabled && - dataset.dataStore.jobsSupportedByAvailableWorkers.includes(APIJobType.RENDER_ANIMATION) + dataset.dataStore.jobsSupportedByAvailableWorkers.includes(APIJobCommand.RENDER_ANIMATION) ); return ( diff --git a/frontend/javascripts/viewer/view/action-bar/download_modal_view.tsx b/frontend/javascripts/viewer/view/action-bar/download_modal_view.tsx index 23d13d01539..4048cb9cfb4 100644 --- a/frontend/javascripts/viewer/view/action-bar/download_modal_view.tsx +++ b/frontend/javascripts/viewer/view/action-bar/download_modal_view.tsx @@ -38,7 +38,7 @@ import { useState } from "react"; import { type APIDataLayer, type APIDataset, - APIJobType, + APIJobCommand, type AdditionalAxis, type VoxelSize, } from "types/api_types"; @@ -591,7 +591,7 @@ function _DownloadModalView({ {activeTabKey === "export" && - !dataset.dataStore.jobsSupportedByAvailableWorkers.includes(APIJobType.EXPORT_TIFF) ? ( + !dataset.dataStore.jobsSupportedByAvailableWorkers.includes(APIJobCommand.EXPORT_TIFF) ? ( workerInfo ) : (
diff --git a/frontend/javascripts/viewer/view/action-bar/tools/skeleton_specific_ui.tsx b/frontend/javascripts/viewer/view/action-bar/tools/skeleton_specific_ui.tsx index d3cf3ceca97..40a52aacf4c 100644 --- a/frontend/javascripts/viewer/view/action-bar/tools/skeleton_specific_ui.tsx +++ b/frontend/javascripts/viewer/view/action-bar/tools/skeleton_specific_ui.tsx @@ -16,7 +16,7 @@ import { MaterializeVolumeAnnotationModal } from "viewer/view/action-bar/ai_job_ import ButtonComponent, { ToggleButton } from "viewer/view/components/button_component"; import { useIsActiveUserAdminOrManager } from "libs/react_helpers"; -import { APIJobType } from "types/api_types"; +import { APIJobCommand } from "types/api_types"; import { IMG_STYLE_FOR_SPACEY_ICONS, NARROW_BUTTON_STYLE } from "./tool_helpers"; export function SkeletonSpecificButtons() { @@ -60,7 +60,7 @@ export function SkeletonSpecificButtons() { const isMaterializeVolumeAnnotationEnabled = dataset.dataStore.jobsSupportedByAvailableWorkers.includes( - APIJobType.MATERIALIZE_VOLUME_ANNOTATION, + APIJobCommand.MATERIALIZE_VOLUME_ANNOTATION, ); return ( diff --git a/frontend/javascripts/viewer/view/action_bar_view.tsx b/frontend/javascripts/viewer/view/action_bar_view.tsx index 46e046824a2..9db1f48e83a 100644 --- a/frontend/javascripts/viewer/view/action_bar_view.tsx +++ b/frontend/javascripts/viewer/view/action_bar_view.tsx @@ -10,7 +10,7 @@ import * as React from "react"; import { connect, useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import type { APIDataset, APISegmentationLayer, APIUser } from "types/api_types"; -import { APIJobType, type AdditionalCoordinate } from "types/api_types"; +import { APIJobCommand, type AdditionalCoordinate } from "types/api_types"; import { type ControlMode, MappingStatusEnum, type ViewMode } from "viewer/constants"; import constants, { ControlModeEnum } from "viewer/constants"; import { @@ -303,7 +303,7 @@ class ActionBarView extends React.PureComponent { return ( Store.dispatch(setAIJobModalStateAction(APIJobType.INFER_NEURONS))} + onClick={() => Store.dispatch(setAIJobModalStateAction(APIJobCommand.INFER_NEURONS))} style={{ marginLeft: 12, pointerEvents: "auto" }} disabled={disabled} title={tooltipText} @@ -325,10 +325,12 @@ class ActionBarView extends React.PureComponent { const isViewMode = controlMode === ControlModeEnum.VIEW; const getIsAIAnalysisEnabled = () => { const jobsEnabled = - dataset.dataStore.jobsSupportedByAvailableWorkers.includes(APIJobType.INFER_NEURONS) || - dataset.dataStore.jobsSupportedByAvailableWorkers.includes(APIJobType.INFER_MITOCHONDRIA) || - dataset.dataStore.jobsSupportedByAvailableWorkers.includes(APIJobType.INFER_NUCLEI) || - dataset.dataStore.jobsSupportedByAvailableWorkers.includes(APIJobType.ALIGN_SECTIONS); + dataset.dataStore.jobsSupportedByAvailableWorkers.includes(APIJobCommand.INFER_NEURONS) || + dataset.dataStore.jobsSupportedByAvailableWorkers.includes( + APIJobCommand.INFER_MITOCHONDRIA, + ) || + dataset.dataStore.jobsSupportedByAvailableWorkers.includes(APIJobCommand.INFER_NUCLEI) || + dataset.dataStore.jobsSupportedByAvailableWorkers.includes(APIJobCommand.ALIGN_SECTIONS); return jobsEnabled; }; diff --git a/frontend/javascripts/viewer/view/left-border-tabs/layer_settings_tab.tsx b/frontend/javascripts/viewer/view/left-border-tabs/layer_settings_tab.tsx index 0b2f85dc1fd..9ab21dd7131 100644 --- a/frontend/javascripts/viewer/view/left-border-tabs/layer_settings_tab.tsx +++ b/frontend/javascripts/viewer/view/left-border-tabs/layer_settings_tab.tsx @@ -48,7 +48,7 @@ import { APIAnnotationTypeEnum, type APIDataLayer, type APIDataset, - APIJobType, + APIJobCommand, type APISkeletonLayer, AnnotationLayerEnum, type AnnotationLayerType, @@ -709,7 +709,7 @@ class DatasetSettings extends React.PureComponent { : null, this.props.dataset.dataStore.jobsEnabled && this.props.dataset.dataStore.jobsSupportedByAvailableWorkers.includes( - APIJobType.COMPUTE_SEGMENT_INDEX_FILE, + APIJobCommand.COMPUTE_SEGMENT_INDEX_FILE, ) ? { label: this.getComputeSegmentIndexFileButton(layerName, isSegmentation), diff --git a/frontend/javascripts/viewer/view/right-border-tabs/bounding_box_tab.tsx b/frontend/javascripts/viewer/view/right-border-tabs/bounding_box_tab.tsx index c36fe73bd4e..3cfc990dec6 100644 --- a/frontend/javascripts/viewer/view/right-border-tabs/bounding_box_tab.tsx +++ b/frontend/javascripts/viewer/view/right-border-tabs/bounding_box_tab.tsx @@ -7,7 +7,7 @@ import type React from "react"; import { useCallback, useEffect, useRef, useState } from "react"; import { useDispatch } from "react-redux"; import AutoSizer from "react-virtualized-auto-sizer"; -import { APIJobType } from "types/api_types"; +import { APIJobCommand } from "types/api_types"; import type { BoundingBoxMinMaxType } from "types/bounding_box"; import { ControlModeEnum, type Vector3, type Vector6 } from "viewer/constants"; import { isAnnotationOwner } from "viewer/model/accessors/annotation_accessor"; @@ -119,7 +119,7 @@ export default function BoundingBoxTab() { } const isExportEnabled = dataset.dataStore.jobsSupportedByAvailableWorkers.includes( - APIJobType.EXPORT_TIFF, + APIJobCommand.EXPORT_TIFF, ); useEffect(() => { diff --git a/frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx index 70b122a0676..2942c7ccc53 100644 --- a/frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx @@ -49,7 +49,7 @@ import { connect } from "react-redux"; import AutoSizer from "react-virtualized-auto-sizer"; import type { Dispatch } from "redux"; import type { APIMeshFileInfo, MetadataEntryProto } from "types/api_types"; -import { APIJobType, type AdditionalCoordinate } from "types/api_types"; +import { APIJobCommand, type AdditionalCoordinate } from "types/api_types"; import type { Vector3 } from "viewer/constants"; import { EMPTY_OBJECT, MappingStatusEnum } from "viewer/constants"; import { @@ -434,7 +434,7 @@ class SegmentsView extends React.Component { if ( this.props.dataset.dataStore.jobsEnabled && this.props.dataset.dataStore.jobsSupportedByAvailableWorkers.includes( - APIJobType.COMPUTE_MESH_FILE, + APIJobCommand.COMPUTE_MESH_FILE, ) ) { this.pollJobData(); @@ -679,7 +679,8 @@ class SegmentsView extends React.Component { const jobs = this.props.activeUser != null ? await getJobs() : []; const oldActiveJobId = this.state.activeMeshJobId; const meshJobsForDataset = jobs.filter( - (job) => job.type === "compute_mesh_file" && job.datasetName === this.props.datasetName, + (job) => + job.command === "compute_mesh_file" && job.args.datasetName === this.props.datasetName, ); const activeJob = oldActiveJobId != null ? meshJobsForDataset.find((job) => job.id === oldActiveJobId) : null; @@ -707,7 +708,6 @@ class SegmentsView extends React.Component { } case "STARTED": - case "UNKNOWN": case "PENDING": { break; } @@ -759,7 +759,7 @@ class SegmentsView extends React.Component { if ( !this.props.dataset.dataStore.jobsSupportedByAvailableWorkers.includes( - APIJobType.COMPUTE_MESH_FILE, + APIJobCommand.COMPUTE_MESH_FILE, ) ) { title = "Mesh computation jobs are not enabled for this WEBKNOSSOS instance."; From 5be2ef443340c1333d5a431bc83f91d28a494daa Mon Sep 17 00:00:00 2001 From: Florian M Date: Mon, 24 Nov 2025 21:40:49 +0100 Subject: [PATCH 09/16] cleanup, make single job writes superset of list writes --- app/models/job/JobService.scala | 18 ++++++--- frontend/javascripts/admin/api/jobs.ts | 2 +- .../javascripts/admin/job/job_list_view.tsx | 40 ++++++++----------- frontend/javascripts/types/api_types.ts | 20 ---------- 4 files changed, 29 insertions(+), 51 deletions(-) diff --git a/app/models/job/JobService.scala b/app/models/job/JobService.scala index dd6f17f3600..0bde812ae78 100644 --- a/app/models/job/JobService.scala +++ b/app/models/job/JobService.scala @@ -173,25 +173,31 @@ class JobService @Inject()(wkConf: WkConf, owner <- userDAO.findOne(job._owner) ?~> "user.notFound" organization <- organizationDAO.findOne(owner._organization) ?~> "organization.notFound" resultLink = job.constructResultLink(organization._id) + ownerEmail <- userService.emailFor(owner) ownerJson <- userService.compactWrites(owner) creditTransactionBox <- creditTransactionService.findTransactionOfJob(job._id).shiftBox } yield { Json.obj( "id" -> job._id.id, - "owner" -> ownerJson, "command" -> job.command, - "commandArgs" -> (job.args - "webknossos_token" - "user_auth_token"), - "state" -> job.state, - "manualState" -> job.manualState, + "organizationId" -> organization._id, + "ownerFirstName" -> owner.firstName, + "ownerLastName" -> owner.lastName, + "ownerEmail" -> ownerEmail, + "args" -> (job.args - "webknossos_token" - "user_auth_token"), "effectiveState" -> Json.toJson(job.manualState.getOrElse(job.state)), - "latestRunId" -> job.latestRunId, "returnValue" -> job.returnValue, "resultLink" -> resultLink, "voxelyticsWorkflowHash" -> job._voxelyticsWorkflowHash, "created" -> job.created, "started" -> job.started, "ended" -> job.ended, - "creditCost" -> creditTransactionBox.toOption.map(t => (t.creditDelta * -1).toString) + "creditCost" -> creditTransactionBox.toOption.map(t => (t.creditDelta * -1).toString), + // Additional properties not present in JobCompactInfo: + "owner" -> ownerJson, + "state" -> job.state, + "manualState" -> job.manualState, + "latestRunId" -> job.latestRunId, ) } diff --git a/frontend/javascripts/admin/api/jobs.ts b/frontend/javascripts/admin/api/jobs.ts index f4c295c1e3f..7357ba265c6 100644 --- a/frontend/javascripts/admin/api/jobs.ts +++ b/frontend/javascripts/admin/api/jobs.ts @@ -15,7 +15,7 @@ import { assertResponseLimit } from "./api_utils"; function transformBackendJobToAPIJob(job: any): APIJob { return { ...job, - args: _.mapKeys(job.args, (_, key) => _.camelCase(key)), + args: _.mapKeys(job.args, (_value, key) => _.camelCase(key)), state: job.effectiveState, }; } diff --git a/frontend/javascripts/admin/job/job_list_view.tsx b/frontend/javascripts/admin/job/job_list_view.tsx index 6f126c5c85c..4a46d6c5a44 100644 --- a/frontend/javascripts/admin/job/job_list_view.tsx +++ b/frontend/javascripts/admin/job/job_list_view.tsx @@ -180,6 +180,7 @@ function JobListView() { function renderDescription(__: any, job: APIJob) { const linkToDataset = getLinkToDataset(job); + const layerName = job.args.annotationLayerName || job.args.layerName; if (job.command === APIJobCommand.CONVERT_TO_WKW && job.args.datasetName) { return {`Conversion to WKW of ${job.args.datasetName}`}; } else if (job.command === APIJobCommand.EXPORT_TIFF && linkToDataset != null) { @@ -191,10 +192,9 @@ function JobListView() { ) : ( dataset {job.args.datasetName} ); - const layerLabel = job.args.annotationLayerName || job.args.layerName; return ( - Tiff export of layer {layerLabel} from {labelToAnnotationOrDataset} (Bounding Box{" "} + Tiff export of layer {layerName} from {labelToAnnotationOrDataset} (Bounding Box{" "} {job.args.ndBoundingBox ? formatWkLibsNdBBox(job.args.ndBoundingBox) : job.args.boundingBox} @@ -204,7 +204,7 @@ function JobListView() { } else if (job.command === APIJobCommand.RENDER_ANIMATION && linkToDataset != null) { return ( - Animation rendering for layer {job.args.layerName} of dataset{" "} + Animation rendering for layer {layerName} of dataset{" "} {job.args.datasetName} ); @@ -225,34 +225,30 @@ function JobListView() { } else if ( job.command === APIJobCommand.FIND_LARGEST_SEGMENT_ID && linkToDataset != null && - job.args.layerName + layerName ) { return ( - Largest segment id detection for layer {job.args.layerName} of{" "} + Largest segment id detection for layer {layerName} of{" "} {job.args.datasetName}{" "} ); - } else if ( - job.command === APIJobCommand.INFER_NUCLEI && - linkToDataset != null && - job.args.layerName - ) { + } else if (job.command === APIJobCommand.INFER_NUCLEI && linkToDataset != null && layerName) { return ( - Nuclei inferral for layer {job.args.layerName} of{" "} + Nuclei inferral for layer {layerName} of{" "} {job.args.datasetName}{" "} ); } else if ( job.command === APIJobCommand.INFER_NEURONS && linkToDataset != null && - job.args.layerName && + layerName && job.args.modelId == null ) { return ( - AI Neuron inferral for layer {job.args.layerName} of{" "} + AI Neuron inferral for layer {layerName} of{" "} {job.args.datasetName}{" "} ); @@ -270,33 +266,29 @@ function JobListView() { } else if ( job.command === APIJobCommand.INFER_MITOCHONDRIA && linkToDataset != null && - job.args.layerName + layerName ) { return ( - AI Mitochondria inferral for layer {job.args.layerName} of{" "} + AI Mitochondria inferral for layer {layerName} of{" "} {job.args.datasetName}{" "} ); } else if ( job.command === APIJobCommand.INFER_INSTANCES && linkToDataset != null && - job.args.layerName + layerName ) { return ( - AI instance segmentation for layer {job.args.layerName} of{" "} + AI instance segmentation for layer {layerName} of{" "} {job.args.datasetName}{" "} ); - } else if ( - job.command === APIJobCommand.ALIGN_SECTIONS && - linkToDataset != null && - job.args.layerName - ) { + } else if (job.command === APIJobCommand.ALIGN_SECTIONS && linkToDataset != null && layerName) { return ( - Align sections for layer {job.args.layerName} of{" "} + Align sections for layer {layerName} of{" "} {job.args.datasetName}{" "} ); @@ -306,7 +298,7 @@ function JobListView() { ) { return ( - Materialize annotation for {job.args.layerName ? ` layer ${job.args.layerName} of ` : " "} + Materialize annotation for {layerName ? ` layer ${layerName} of ` : " "} {job.args.datasetName} {job.args.mergeSegments ? ". This includes merging the segments that were merged via merger mode." diff --git a/frontend/javascripts/types/api_types.ts b/frontend/javascripts/types/api_types.ts index 64e3a7dff18..fe70fae4b5f 100644 --- a/frontend/javascripts/types/api_types.ts +++ b/frontend/javascripts/types/api_types.ts @@ -832,26 +832,6 @@ export type APIJob = { readonly returnValue: string | null | undefined; readonly voxelyticsWorkflowHash: string | null | undefined; readonly created: number; - readonly started: number | null | undefined; - readonly ended: number | null | undefined; - readonly creditCost: string | null | undefined; -}; - -export type APIJobOLD = { - readonly id: string; - readonly datasetId: string | null | undefined; - readonly owner: APIUserBase; - readonly datasetName: string | null | undefined; - readonly exportFileName: string | null | undefined; - readonly tracingId: string | null | undefined; - readonly annotationType: string | null | undefined; - readonly organizationId: string | null | undefined; - readonly mergeSegments: boolean | null | undefined; - readonly command: APIJobCommand; - readonly result: string | null | undefined; - readonly resultLink: string | null | undefined; - readonly createdAt: number; - readonly voxelyticsWorkflowHash: string | null; readonly creditCost: string | null | undefined; }; From cf6af3affb45f777154711fbe4271cfa6a651d8f Mon Sep 17 00:00:00 2001 From: Florian M Date: Mon, 24 Nov 2025 22:02:27 +0100 Subject: [PATCH 10/16] cleanup --- app/models/job/Job.scala | 20 +++++--- app/models/job/JobService.scala | 50 ++++++++----------- frontend/javascripts/admin/api/jobs.ts | 1 - .../javascripts/admin/job/job_list_view.tsx | 4 +- 4 files changed, 36 insertions(+), 39 deletions(-) diff --git a/app/models/job/Job.scala b/app/models/job/Job.scala index 825e8513362..aa8867890d3 100644 --- a/app/models/job/Job.scala +++ b/app/models/job/Job.scala @@ -30,8 +30,8 @@ case class Job( latestRunId: Option[String] = None, returnValue: Option[String] = None, retriedBySuperUser: Boolean = false, - started: Option[Long] = None, - ended: Option[Long] = None, + started: Option[Instant] = None, + ended: Option[Instant] = None, created: Instant = Instant.now, isDeleted: Boolean = false ) extends JobResultLinks { @@ -46,7 +46,7 @@ case class Job( for { e <- ended s <- started - } yield (e - s).millis + } yield e - s protected def effectiveState: JobState = manualState.getOrElse(state) @@ -72,7 +72,7 @@ case class JobCompactInfo( ownerLastName: String, ownerEmail: String, args: JsObject, - effectiveState: JobState, + state: JobState, returnValue: Option[String], resultLink: Option[String], voxelyticsWorkflowHash: Option[String], @@ -81,10 +81,14 @@ case class JobCompactInfo( ended: Option[Instant], creditCost: Option[scala.math.BigDecimal] ) extends JobResultLinks { + + protected def effectiveState: JobState = state + def enrich: JobCompactInfo = this.copy( resultLink = constructResultLink(organizationId), args = args - "webknossos_token" - "user_auth_token" ) + } object JobCompactInfo { @@ -118,8 +122,8 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) r.latestrunid, r.returnvalue, r.retriedbysuperuser, - r.started.map(_.getTime), - r.ended.map(_.getTime), + r.started.map(Instant.fromSql), + r.ended.map(Instant.fromSql), Instant.fromSql(r.created), r.isdeleted ) @@ -198,14 +202,14 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) ownerLastName = row._5, ownerEmail = row._6, args = commandArgs, - effectiveState = effectiveState, + state = effectiveState, returnValue = row._9, resultLink = None, // To be filled by calling “enrich” voxelyticsWorkflowHash = row._10, created = row._11, started = row._12, ended = row._13, - creditCost = row._14 + creditCost = row._14.map(_ * -1) ) } } yield parsed diff --git a/app/models/job/JobService.scala b/app/models/job/JobService.scala index 0bde812ae78..0d4f3c98b59 100644 --- a/app/models/job/JobService.scala +++ b/app/models/job/JobService.scala @@ -14,7 +14,7 @@ import models.organization.{CreditTransactionService, OrganizationDAO} import models.user.{MultiUserDAO, User, UserDAO, UserService} import com.scalableminds.util.tools.Full import org.apache.pekko.actor.ActorSystem -import play.api.libs.json.{JsObject, Json} +import play.api.libs.json.{JsObject, JsValue, Json} import security.WkSilhouetteEnvironment import telemetry.SlackNotificationService import utils.WkConf @@ -168,38 +168,32 @@ class JobService @Inject()(wkConf: WkConf, } yield () } else Fox.successful(()) - def publicWrites(job: Job)(implicit ctx: DBAccessContext): Fox[JsObject] = + def publicWrites(job: Job)(implicit ctx: DBAccessContext): Fox[JsValue] = for { owner <- userDAO.findOne(job._owner) ?~> "user.notFound" organization <- organizationDAO.findOne(owner._organization) ?~> "organization.notFound" - resultLink = job.constructResultLink(organization._id) ownerEmail <- userService.emailFor(owner) - ownerJson <- userService.compactWrites(owner) creditTransactionBox <- creditTransactionService.findTransactionOfJob(job._id).shiftBox - } yield { - Json.obj( - "id" -> job._id.id, - "command" -> job.command, - "organizationId" -> organization._id, - "ownerFirstName" -> owner.firstName, - "ownerLastName" -> owner.lastName, - "ownerEmail" -> ownerEmail, - "args" -> (job.args - "webknossos_token" - "user_auth_token"), - "effectiveState" -> Json.toJson(job.manualState.getOrElse(job.state)), - "returnValue" -> job.returnValue, - "resultLink" -> resultLink, - "voxelyticsWorkflowHash" -> job._voxelyticsWorkflowHash, - "created" -> job.created, - "started" -> job.started, - "ended" -> job.ended, - "creditCost" -> creditTransactionBox.toOption.map(t => (t.creditDelta * -1).toString), - // Additional properties not present in JobCompactInfo: - "owner" -> ownerJson, - "state" -> job.state, - "manualState" -> job.manualState, - "latestRunId" -> job.latestRunId, + } yield + Json.toJson( + JobCompactInfo( + id = job._id, + command = job.command, + organizationId = organization._id, + ownerFirstName = owner.firstName, + ownerLastName = owner.lastName, + ownerEmail = ownerEmail, + args = job.args - "webknossos_token" - "user_auth_token", + state = job.manualState.getOrElse(job.state), + returnValue = job.returnValue, + resultLink = job.constructResultLink(organization._id), + voxelyticsWorkflowHash = job._voxelyticsWorkflowHash, + created = job.created, + started = job.started, + ended = job.ended, + creditCost = creditTransactionBox.toOption.map(t => t.creditDelta * -1) + ) ) - } // Only seen by the workers def parameterWrites(job: Job)(implicit ctx: DBAccessContext): Fox[JsObject] = @@ -229,7 +223,7 @@ class JobService @Inject()(wkConf: WkConf, jobBoundingBox: BoundingBox, creditTransactionComment: String, user: User, - datastoreName: String)(implicit ctx: DBAccessContext): Fox[JsObject] = + datastoreName: String)(implicit ctx: DBAccessContext): Fox[JsValue] = for { costsInCredits <- if (SHOULD_DEDUCE_CREDITS) calculateJobCostInCredits(jobBoundingBox, command) else Fox.successful(BigDecimal(0)) diff --git a/frontend/javascripts/admin/api/jobs.ts b/frontend/javascripts/admin/api/jobs.ts index 7357ba265c6..7fe50f71349 100644 --- a/frontend/javascripts/admin/api/jobs.ts +++ b/frontend/javascripts/admin/api/jobs.ts @@ -16,7 +16,6 @@ function transformBackendJobToAPIJob(job: any): APIJob { return { ...job, args: _.mapKeys(job.args, (_value, key) => _.camelCase(key)), - state: job.effectiveState, }; } diff --git a/frontend/javascripts/admin/job/job_list_view.tsx b/frontend/javascripts/admin/job/job_list_view.tsx index 4a46d6c5a44..8d6ff37a49c 100644 --- a/frontend/javascripts/admin/job/job_list_view.tsx +++ b/frontend/javascripts/admin/job/job_list_view.tsx @@ -329,7 +329,7 @@ function JobListView() { function renderWorkflowLink(__: any, job: APIJob) { return job.voxelyticsWorkflowHash != null ? ( - Voxelytics Report + Workflow ) : null; } @@ -544,7 +544,7 @@ function JobListView() { defaultSortOrder="descend" /> {isCurrentUserSuperUser ? ( - + ) : null} Date: Mon, 24 Nov 2025 22:04:40 +0100 Subject: [PATCH 11/16] changelog --- unreleased_changes/9068.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 unreleased_changes/9068.md diff --git a/unreleased_changes/9068.md b/unreleased_changes/9068.md new file mode 100644 index 00000000000..a7fdb212d01 --- /dev/null +++ b/unreleased_changes/9068.md @@ -0,0 +1,7 @@ +### Added +- Unified display of ids, allowing single-click copy. +- Added link to workflow report to job list (superusers only for now). +- Speed up loading of job list. + +### Fixed +- Fixed loading indicators for job and workflow lists. From ab68dc2c9b6731d0167cc5bfe1cae989091ecc60 Mon Sep 17 00:00:00 2001 From: Florian M Date: Tue, 25 Nov 2025 09:15:30 +0100 Subject: [PATCH 12/16] implement coderabbit feedback --- app/models/job/Job.scala | 2 +- app/models/job/JobResultLinks.scala | 2 +- app/models/job/JobService.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/job/Job.scala b/app/models/job/Job.scala index aa8867890d3..abfdf2631e4 100644 --- a/app/models/job/Job.scala +++ b/app/models/job/Job.scala @@ -209,7 +209,7 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) created = row._11, started = row._12, ended = row._13, - creditCost = row._14.map(_ * -1) + creditCost = row._14.map(_ * -1) // delta is negative, so cost should be positive. ) } } yield parsed diff --git a/app/models/job/JobResultLinks.scala b/app/models/job/JobResultLinks.scala index b9f0a6b916b..bdd45541206 100644 --- a/app/models/job/JobResultLinks.scala +++ b/app/models/job/JobResultLinks.scala @@ -29,7 +29,7 @@ trait JobResultLinks { datasetId.map { datasetId => val datasetNameMaybe = datasetName.map(name => s"$name-").getOrElse("") Some(s"/datasets/$datasetNameMaybe$datasetId/view") - }.getOrElse(datasetName.map(name => s"datasets/$organizationId/$name/view")) + }.getOrElse(datasetName.map(name => s"/datasets/$organizationId/$name/view")) case JobCommand.export_tiff | JobCommand.render_animation => Some(s"/api/jobs/${this.id}/export") case JobCommand.infer_neurons if this.argAsBooleanOpt("do_evaluation").getOrElse(false) => diff --git a/app/models/job/JobService.scala b/app/models/job/JobService.scala index 0d4f3c98b59..03d902301e6 100644 --- a/app/models/job/JobService.scala +++ b/app/models/job/JobService.scala @@ -191,7 +191,7 @@ class JobService @Inject()(wkConf: WkConf, created = job.created, started = job.started, ended = job.ended, - creditCost = creditTransactionBox.toOption.map(t => t.creditDelta * -1) + creditCost = creditTransactionBox.toOption.map(t => t.creditDelta * -1) // delta is negative, so cost should be positive. ) ) From 8edfac8a5a8728009f975d2765ccc8fe45840819 Mon Sep 17 00:00:00 2001 From: Florian M Date: Wed, 26 Nov 2025 11:17:48 +0100 Subject: [PATCH 13/16] Implement backend PR feedback --- app/models/job/Job.scala | 16 +++------------- app/models/job/JobService.scala | 2 +- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/models/job/Job.scala b/app/models/job/Job.scala index abfdf2631e4..c6f5610f8f8 100644 --- a/app/models/job/Job.scala +++ b/app/models/job/Job.scala @@ -48,7 +48,7 @@ case class Job( s <- started } yield e - s - protected def effectiveState: JobState = manualState.getOrElse(state) + def effectiveState: JobState = manualState.getOrElse(state) def exportFileName: Option[String] = argAsStringOpt("export_file_name") @@ -146,24 +146,14 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) ) """ - private def listAccessQ(requestingUserId: ObjectId) = - listAccessQWithPrefix(requestingUserId, q"") - - private def listAccessQWithPrefix(requestingUserId: ObjectId, prefix: SqlToken) = + private def listAccessQ(requestingUserId: ObjectId, prefix: SqlToken) = q"""${prefix}_owner = $requestingUserId OR ((SELECT u._organization FROM webknossos.users_ u WHERE u._id = _owner) IN (SELECT _organization FROM webknossos.users_ WHERE _id = $requestingUserId AND isAdmin)) """ - override def findAll(implicit ctx: DBAccessContext): Fox[List[Job]] = - for { - accessQuery <- accessQueryFromAccessQ(listAccessQ) - r <- run(q"SELECT $columns FROM $existingCollectionName WHERE $accessQuery ORDER BY created".as[JobsRow]) - parsed <- parseAll(r) - } yield parsed - def findAllCompact(implicit ctx: DBAccessContext): Fox[Seq[JobCompactInfo]] = for { - accessQuery <- accessQueryFromAccessQWithPrefix(listAccessQWithPrefix, q"j.") + accessQuery <- accessQueryFromAccessQWithPrefix(listAccessQ, q"j.") rows <- run( q""" SELECT j._id, j.command, u._organization, u.firstName, u.lastName, mu.email, j.commandArgs, COALESCE(j.manualState, j.state), diff --git a/app/models/job/JobService.scala b/app/models/job/JobService.scala index 03d902301e6..d4206c690f4 100644 --- a/app/models/job/JobService.scala +++ b/app/models/job/JobService.scala @@ -184,7 +184,7 @@ class JobService @Inject()(wkConf: WkConf, ownerLastName = owner.lastName, ownerEmail = ownerEmail, args = job.args - "webknossos_token" - "user_auth_token", - state = job.manualState.getOrElse(job.state), + state = job.effectiveState, returnValue = job.returnValue, resultLink = job.constructResultLink(organization._id), voxelyticsWorkflowHash = job._voxelyticsWorkflowHash, From 64b4cbb2976a577df0285e505e5990f0e9704716 Mon Sep 17 00:00:00 2001 From: Florian M Date: Wed, 26 Nov 2025 11:47:57 +0100 Subject: [PATCH 14/16] implement more coderabbit feedback --- app/models/job/Job.scala | 2 +- frontend/javascripts/admin/job/job_list_view.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/job/Job.scala b/app/models/job/Job.scala index c6f5610f8f8..d99e306d768 100644 --- a/app/models/job/Job.scala +++ b/app/models/job/Job.scala @@ -148,7 +148,7 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) private def listAccessQ(requestingUserId: ObjectId, prefix: SqlToken) = q"""${prefix}_owner = $requestingUserId OR - ((SELECT u._organization FROM webknossos.users_ u WHERE u._id = _owner) IN (SELECT _organization FROM webknossos.users_ WHERE _id = $requestingUserId AND isAdmin)) + ((SELECT u._organization FROM webknossos.users_ u WHERE u._id = ${prefix}_owner) IN (SELECT _organization FROM webknossos.users_ WHERE _id = $requestingUserId AND isAdmin)) """ def findAllCompact(implicit ctx: DBAccessContext): Fox[Seq[JobCompactInfo]] = diff --git a/frontend/javascripts/admin/job/job_list_view.tsx b/frontend/javascripts/admin/job/job_list_view.tsx index a635fa3df5d..77c7be63723 100644 --- a/frontend/javascripts/admin/job/job_list_view.tsx +++ b/frontend/javascripts/admin/job/job_list_view.tsx @@ -409,7 +409,7 @@ function JobListView() { )} ); - } else if (job.command === "find_largest_segment_id") { + } else if (job.command === APIJobCommand.FIND_LARGEST_SEGMENT_ID) { return {job.returnValue}; } else if ( job.command === APIJobCommand.INFER_NUCLEI || From eb941c098101996c86d9afce0648f3783e84e871 Mon Sep 17 00:00:00 2001 From: Florian M Date: Wed, 26 Nov 2025 15:42:42 +0100 Subject: [PATCH 15/16] undo redundant changes --- frontend/javascripts/admin/job/job_list_view.tsx | 9 +-------- .../javascripts/admin/voxelytics/workflow_list_view.tsx | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/frontend/javascripts/admin/job/job_list_view.tsx b/frontend/javascripts/admin/job/job_list_view.tsx index 77c7be63723..28f82e1e1fa 100644 --- a/frontend/javascripts/admin/job/job_list_view.tsx +++ b/frontend/javascripts/admin/job/job_list_view.tsx @@ -139,16 +139,9 @@ function JobListView() { const isCurrentUserSuperUser = useWkSelector((state) => state.activeUser?.isSuperUser); useEffect(() => { - initialize(); + fetchData(); }, []); - async function initialize() { - await fetchData(); - const { searchQuery } = persistence.load(); - setSearchQuery(searchQuery || ""); - setIsLoading(false); - } - async function fetchData() { setJobs(await getJobs()); } diff --git a/frontend/javascripts/admin/voxelytics/workflow_list_view.tsx b/frontend/javascripts/admin/voxelytics/workflow_list_view.tsx index 0732bf76971..978ef9351e8 100644 --- a/frontend/javascripts/admin/voxelytics/workflow_list_view.tsx +++ b/frontend/javascripts/admin/voxelytics/workflow_list_view.tsx @@ -1,7 +1,7 @@ import { SyncOutlined } from "@ant-design/icons"; import { PropTypes } from "@scalableminds/prop-types"; import { getVoxelyticsWorkflows } from "admin/rest_api"; -import { Button, Input, Progress, Spin, Table, Tooltip } from "antd"; +import { Button, Input, Progress, Table, Tooltip } from "antd"; import { formatCountToDataAmountUnit, formatDateMedium, formatNumber } from "libs/format_utils"; import Persistence from "libs/persistence"; import { usePolling } from "libs/react_hooks"; @@ -188,13 +188,6 @@ export default function WorkflowListView() { bordered rowKey={(run: RenderRunInfo) => `${run.id}-${run.workflowHash}`} pagination={{ pageSize: 100 }} - locale={{ - emptyText: isLoading ? ( - - ) : ( - "There are no voxelytics workflow reports yet." - ), - }} columns={[ { title: "Workflow", From f6716e9c453e33a67651f156d24307038372d97b Mon Sep 17 00:00:00 2001 From: Florian M Date: Wed, 26 Nov 2025 16:03:29 +0100 Subject: [PATCH 16/16] fix sorting in list query (newest first) --- app/models/job/Job.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/job/Job.scala b/app/models/job/Job.scala index d99e306d768..9b0abfd44f1 100644 --- a/app/models/job/Job.scala +++ b/app/models/job/Job.scala @@ -163,7 +163,7 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) JOIN webknossos.multiusers_ mu on u._multiUser = mu._id LEFT JOIN webknossos.credit_transactions_ ct ON j._id = ct._paid_job WHERE $accessQuery - ORDER BY j.created + ORDER BY j.created DESC -- list newest first """.as[(ObjectId, String, String,