From f8db60cb0c724a4a0fb04511970248becc45b312 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Fri, 4 Oct 2024 11:08:11 -0700 Subject: [PATCH 01/33] Start work on MMS endpoint --- packages/core/entity/Annotation/index.ts | 5 ++ .../core/services/HttpServiceBase/index.ts | 24 +++++++++ .../MetadataManagementService/index.ts | 54 +++++++++++++++++++ .../test/MetadataManagementService.test.ts | 34 ++++++++++++ packages/desktop/src/util/constants.ts | 6 +++ 5 files changed, 123 insertions(+) create mode 100644 packages/core/services/MetadataManagementService/index.ts create mode 100644 packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts diff --git a/packages/core/entity/Annotation/index.ts b/packages/core/entity/Annotation/index.ts index c5ed195c5..56d5aa613 100644 --- a/packages/core/entity/Annotation/index.ts +++ b/packages/core/entity/Annotation/index.ts @@ -15,6 +15,7 @@ export interface AnnotationResponse { type: string; isOpenFileLink?: boolean; units?: string; + annotationId?: string; } /** @@ -70,6 +71,10 @@ export default class Annotation { return this.annotation.units; } + public get id(): string | undefined { + return this.annotation.annotationId; + } + /** * Get the annotation this instance represents from a given FmsFile. An annotation on an FmsFile * can either be at the "top-level" of the document or it can be within it's "annotations" list. diff --git a/packages/core/services/HttpServiceBase/index.ts b/packages/core/services/HttpServiceBase/index.ts index 1c89ae508..324fcd428 100644 --- a/packages/core/services/HttpServiceBase/index.ts +++ b/packages/core/services/HttpServiceBase/index.ts @@ -210,6 +210,30 @@ export default class HttpServiceBase { return new RestServiceResponse(response.data); } + public async put(url: string, body: string): Promise> { + const encodedUrl = HttpServiceBase.encodeURI(url); + const config = { headers: { "Content-Type": "application/json" } }; + + let response; + try { + // if this fails, bubble up exception + response = await retry.execute(() => this.httpClient.put(encodedUrl, body, config)); + } catch (err) { + // Specific errors about the failure from services will be in this path + if (axios.isAxiosError(err) && err?.response?.data?.message) { + throw new Error(JSON.stringify(err.response.data.message)); + } + throw err; + } + + if (response.status >= 400 || response.data === undefined) { + // by default axios will reject if does not satisfy: status >= 200 && status < 300 + throw new Error(`Request for ${encodedUrl} failed`); + } + + return new RestServiceResponse(response.data); + } + public async patch(url: string, body: string): Promise> { const encodedUrl = HttpServiceBase.encodeURI(url); const config = { headers: { "Content-Type": "application/json" } }; diff --git a/packages/core/services/MetadataManagementService/index.ts b/packages/core/services/MetadataManagementService/index.ts new file mode 100644 index 000000000..79d56e030 --- /dev/null +++ b/packages/core/services/MetadataManagementService/index.ts @@ -0,0 +1,54 @@ +import { MetadataManagementServiceBaseUrl } from "../../../desktop/src/util/constants"; +import HttpServiceBase, { ConnectionConfig } from "../HttpServiceBase"; + +interface Config extends ConnectionConfig { + baseUrl: string | keyof typeof MetadataManagementServiceBaseUrl; +} + +// Interfaces borrowed from aics-file-upload-app +// Used for the POST request to MMS for creating file metadata +export interface MMSFileAnnotation { + annotationId: number; + values: string[]; +} + +export interface MMSFile { + fileId: string; + annotations?: MMSFileAnnotation[]; + templateId?: number; +} + +export interface MMSUploadRequest { + customMetadata?: MMSFile; + fileType?: string; + file: FileMetadataBlock; + [id: string]: any; +} + +interface FileMetadataBlock { + originalPath: string; + fileName?: string; + fileType: string; + jobId?: string; + [id: string]: any; +} + +export default class MetadataManagementService extends HttpServiceBase { + private static readonly FILEMETADATA_ENDPOINT_VERSION = "1.0"; + + constructor(config: Config) { + super(config); + } + + public async getFileMetadata(fileId: string): Promise { + const url = `${this.baseUrl}/${MetadataManagementService.FILEMETADATA_ENDPOINT_VERSION}/filemetadata/${fileId}`; + const response = await this.get(url); + return response.data[0]; + } + + public async editFileMetadata(fileId: string, request: MMSUploadRequest): Promise { + const url = `${this.baseUrl}/${MetadataManagementService.FILEMETADATA_ENDPOINT_VERSION}/filemetadata/${fileId}`; + const requestBody = JSON.stringify(request); + await this.put(url, requestBody); + } +} diff --git a/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts b/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts new file mode 100644 index 000000000..7b23cc4df --- /dev/null +++ b/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts @@ -0,0 +1,34 @@ +import { createMockHttpClient } from "@aics/redux-utils"; +import { expect } from "chai"; + +import MetadataManagementService from ".."; + +describe("MetadataManagementService", () => { + const baseUrl = "test"; + const fileIds = ["abc123", "def456", "ghi789", "jkl012"]; + const files = fileIds.map((fileId) => ({ + fileId, + })); + + describe("getFiles", () => { + const httpClient = createMockHttpClient([ + { + when: () => true, + respondWith: { + data: { + data: files.slice(0, 1), + }, + }, + }, + ]); + + it("issues request for files that match given parameters", async () => { + const mmsService = new MetadataManagementService({ + baseUrl, + httpClient, + }); + const response = await mmsService.getFileMetadata(files[0]["fileId"]); + expect(response.fileId).to.equal(files[0]["fileId"]); + }); + }); +}); diff --git a/packages/desktop/src/util/constants.ts b/packages/desktop/src/util/constants.ts index 2b950921b..c4c95ed0c 100644 --- a/packages/desktop/src/util/constants.ts +++ b/packages/desktop/src/util/constants.ts @@ -15,6 +15,12 @@ export enum FileExplorerServiceBaseUrl { PRODUCTION = "https://production.int.allencell.org", } +export enum MetadataManagementServiceBaseUrl { + LOCALHOST = "http://localhost:9060/metadata-management-service", + STAGING = "http://stg-aics-api/metadata-management-service", + PRODUCTION = "http://aics-api/metadata-management-service", +} + // Channels global variables can be modified on / listen to export enum GlobalVariableChannels { BaseUrl = "data-source-base-url", From 3a56a6af017a5531e6a5c6de3e1d3637bc4eab68 Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Fri, 4 Oct 2024 14:57:03 -0700 Subject: [PATCH 02/33] Add edit files logic and support in fileservices --- packages/core/entity/Annotation/index.ts | 5 - .../core/services/DatabaseService/index.ts | 2 +- .../FileService/DatabaseFileService/index.ts | 46 ++++- .../test/DatabaseFileService.test.ts | 32 ++++ .../services/FileService/FileServiceNoop.ts | 14 +- .../FileService/HttpFileService/index.ts | 89 +++++++++- .../test/HttpFileService.test.ts | 124 ++++++++++++++ packages/core/services/FileService/index.ts | 11 +- packages/core/state/interaction/actions.ts | 27 +++ packages/core/state/interaction/logics.ts | 97 ++++++++++- .../state/interaction/test/logics.test.ts | 158 ++++++++++++++++++ packages/core/state/metadata/logics.ts | 8 + .../src/services/DatabaseServiceElectron.ts | 2 +- packages/desktop/src/util/constants.ts | 1 + .../web/src/services/DatabaseServiceWeb.ts | 2 +- 15 files changed, 599 insertions(+), 19 deletions(-) diff --git a/packages/core/entity/Annotation/index.ts b/packages/core/entity/Annotation/index.ts index 56d5aa613..c5ed195c5 100644 --- a/packages/core/entity/Annotation/index.ts +++ b/packages/core/entity/Annotation/index.ts @@ -15,7 +15,6 @@ export interface AnnotationResponse { type: string; isOpenFileLink?: boolean; units?: string; - annotationId?: string; } /** @@ -71,10 +70,6 @@ export default class Annotation { return this.annotation.units; } - public get id(): string | undefined { - return this.annotation.annotationId; - } - /** * Get the annotation this instance represents from a given FmsFile. An annotation on an FmsFile * can either be at the "top-level" of the document or it can be within it's "annotations" list. diff --git a/packages/core/services/DatabaseService/index.ts b/packages/core/services/DatabaseService/index.ts index c7d29697b..8fc8b886c 100644 --- a/packages/core/services/DatabaseService/index.ts +++ b/packages/core/services/DatabaseService/index.ts @@ -53,7 +53,7 @@ export default abstract class DatabaseService { _uri: string | File ): Promise; - protected abstract execute(_sql: string): Promise; + public abstract execute(_sql: string): Promise; private static columnTypeToAnnotationType(columnType: string): string { switch (columnType) { diff --git a/packages/core/services/FileService/DatabaseFileService/index.ts b/packages/core/services/FileService/DatabaseFileService/index.ts index 4ec842bcd..d5df644ed 100644 --- a/packages/core/services/FileService/DatabaseFileService/index.ts +++ b/packages/core/services/FileService/DatabaseFileService/index.ts @@ -1,6 +1,11 @@ import { isEmpty, isNil, uniqueId } from "lodash"; -import FileService, { GetFilesRequest, SelectionAggregationResult, Selection } from ".."; +import FileService, { + GetFilesRequest, + SelectionAggregationResult, + Selection, + AnnotationNameToValuesMap, +} from ".."; import DatabaseService from "../../DatabaseService"; import DatabaseServiceNoop from "../../DatabaseService/DatabaseServiceNoop"; import FileDownloadService, { DownloadResolution, DownloadResult } from "../../FileDownloadService"; @@ -184,4 +189,43 @@ export default class DatabaseFileService implements FileService { uniqueId() ); } + + public editFile(fileId: string, annotations: AnnotationNameToValuesMap): Promise { + const tableName = this.dataSourceNames.sort().join(", "); + const columnAssignments = Object.entries(annotations).map( + ([name, values]) => `${name} = ${values.join(DatabaseService.LIST_DELIMITER)}` + ); + const sql = `\ + UPDATE ${tableName} \ + SET ${columnAssignments.join(", ")} \ + WHERE FileId = ${fileId}; \ + `; + return this.databaseService.execute(sql); + } + + public async getEdittableFileMetadata( + fileIds: string[] + ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { + const sql = new SQLBuilder() + .from(this.dataSourceNames) + .where(`"File ID" IN (${fileIds.join(", ")})`) + .toSQL(); + + const rows = await this.databaseService.query(sql); + return rows + .map((row) => DatabaseFileService.convertDatabaseRowToFileDetail(row, 0)) + .reduce( + (acc, file) => ({ + ...acc, + [file.id]: file.annotations.reduce( + (annoAcc, annotation) => ({ + ...annoAcc, + [annotation.name]: annotation.values, + }), + {} as AnnotationNameToValuesMap + ), + }), + {} as { [fileId: string]: AnnotationNameToValuesMap } + ); + } } diff --git a/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts b/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts index 3269add37..25e2c2485 100644 --- a/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts +++ b/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts @@ -65,6 +65,38 @@ describe("DatabaseFileService", () => { }); }); + describe("getEdittableFileMetadata", () => { + it("converts response into a map of file_id to metadata", async () => { + // Arrange + const databaseFileService = new DatabaseFileService({ + dataSourceNames: ["whatever", "and another"], + databaseService, + downloadService: new FileDownloadServiceNoop(), + }); + + // Act + const response = await databaseFileService.getEdittableFileMetadata(["abc123"]); + + // Assert + expect(response).to.deep.equal({ + abc123: { + "File ID": ["abc123"], + "File Name": ["file"], + "File Path": ["path/to/file"], + "File Size": ["432226"], + num_files: ["6"], + }, + def456: { + "File ID": ["def456"], + "File Name": ["file"], + "File Path": ["path/to/file"], + "File Size": ["432226"], + num_files: ["6"], + }, + }); + }); + }); + describe("getAggregateInformation", () => { it("issues request for aggregated information about given files", async () => { // Arrange diff --git a/packages/core/services/FileService/FileServiceNoop.ts b/packages/core/services/FileService/FileServiceNoop.ts index 7063fa214..e74491170 100644 --- a/packages/core/services/FileService/FileServiceNoop.ts +++ b/packages/core/services/FileService/FileServiceNoop.ts @@ -1,4 +1,4 @@ -import FileService, { SelectionAggregationResult } from "."; +import FileService, { AnnotationNameToValuesMap, SelectionAggregationResult } from "."; import { DownloadResolution, DownloadResult } from "../FileDownloadService"; import FileDetail from "../../entity/FileDetail"; @@ -11,15 +11,19 @@ export default class FileServiceNoop implements FileService { return Promise.resolve({ count: 0, size: 0 }); } - public getFiles(): Promise { - return Promise.resolve([]); + public getEdittableFileMetadata(): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { + return Promise.resolve({}); } - public getFilesAsBuffer(): Promise { - return Promise.resolve(new Uint8Array()); + public getFiles(): Promise { + return Promise.resolve([]); } public download(): Promise { return Promise.resolve({ downloadRequestId: "", resolution: DownloadResolution.CANCELLED }); } + + public editFile(): Promise { + return Promise.resolve(); + } } diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index f352cfe2e..5e2c4985a 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -1,6 +1,12 @@ -import { compact, join, uniqueId } from "lodash"; - -import FileService, { GetFilesRequest, SelectionAggregationResult, Selection } from ".."; +import { chunk, compact, invert, join, uniqueId } from "lodash"; + +import FileService, { + GetFilesRequest, + SelectionAggregationResult, + Selection, + AnnotationNameToValuesMap, +} from ".."; +import { AnnotationValue } from "../../AnnotationService"; import FileDownloadService, { DownloadResult } from "../../FileDownloadService"; import FileDownloadServiceNoop from "../../FileDownloadService/FileDownloadServiceNoop"; import HttpServiceBase, { ConnectionConfig } from "../../HttpServiceBase"; @@ -16,6 +22,16 @@ interface Config extends ConnectionConfig { downloadService: FileDownloadService; } +// Used for the GET request to MMS for file metadata +interface EdittableFileMetadata { + fileId: string; + annotations?: { + annotationId: number; + values: string[]; + }[]; + templateId?: number; +} + /** * Service responsible for fetching file related metadata. */ @@ -23,10 +39,13 @@ export default class HttpFileService extends HttpServiceBase implements FileServ private static readonly ENDPOINT_VERSION = "3.0"; public static readonly BASE_FILES_URL = `file-explorer-service/${HttpFileService.ENDPOINT_VERSION}/files`; public static readonly BASE_FILE_COUNT_URL = `${HttpFileService.BASE_FILES_URL}/count`; + public static readonly BASE_EDIT_FILES_URL = `metadata-management-service/1.0/filemetadata`; + public static readonly BASE_ANNOTATION_ID_URL = `metadata-management-service/1.0/annotation`; public static readonly SELECTION_AGGREGATE_URL = `${HttpFileService.BASE_FILES_URL}/selection/aggregate`; private static readonly CSV_ENDPOINT_VERSION = "2.0"; public static readonly BASE_CSV_DOWNLOAD_URL = `file-explorer-service/${HttpFileService.CSV_ENDPOINT_VERSION}/files/selection/manifest`; private readonly downloadService: FileDownloadService; + private readonly edittableAnnotationIdToNameCache: { [name: string]: number } = {}; constructor(config: Config = { downloadService: new FileDownloadServiceNoop() }) { super(config); @@ -127,4 +146,68 @@ export default class HttpFileService extends HttpServiceBase implements FileServ uniqueId() ); } + + public async editFile(fileId: string, annotations: AnnotationNameToValuesMap): Promise { + const url = `${this.baseUrl}/${HttpFileService.BASE_EDIT_FILES_URL}/${fileId}`; + const mmsAnnotations: { annotationId: number; values: AnnotationValue[] }[] = []; + for (const [name, values] of Object.entries(annotations)) { + const annotationId = await this.getEdittableAnnotationIdByName(name); + mmsAnnotations.push({ annotationId, values }); + } + const requestBody = JSON.stringify({ + annotations: mmsAnnotations, + }); + await this.put(url, requestBody); + } + + public async getEdittableFileMetadata( + fileIds: string[] + ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { + const url = `${this.baseUrl}/${HttpFileService.BASE_EDIT_FILES_URL}/${fileIds.join(",")}`; + const response = await this.get(url); + + const fileIdToAnnotations: { [fileId: string]: AnnotationNameToValuesMap } = {}; + for (const file of response.data) { + fileIdToAnnotations[file.fileId] = {}; + for (const annotation of file.annotations || []) { + const name = await this.getEdittableAnnotationNameById(annotation.annotationId); + fileIdToAnnotations[file.fileId][name] = annotation.values; + } + } + return fileIdToAnnotations; + } + + /** + * For every annotation name given, fetch the annotation id from MMS and cache it + * for future in edit scenarios, necessary because we don't have an endpoint in MMS + * that can grab the annotation name from the annotation id + */ + public async prepareAnnotationIdCache(annotationNames: string[]): Promise { + const batches = chunk(annotationNames, 25); + for (const batch of batches) { + await Promise.all(batch.map((name) => this.getEdittableAnnotationIdByName(name))); + } + } + + private async getEdittableAnnotationIdByName(name: string): Promise { + if (!this.edittableAnnotationIdToNameCache[name]) { + const url = `${this.baseUrl}/${HttpFileService.BASE_ANNOTATION_ID_URL}/${name}`; + const response = await this.get<{ annotationId: number }>(url); + + this.edittableAnnotationIdToNameCache[name] = response.data[0].annotationId; + } + + return this.edittableAnnotationIdToNameCache[name]; + } + + private async getEdittableAnnotationNameById(id: number): Promise { + const edittableAnnotationNameToIdCache = invert(this.edittableAnnotationIdToNameCache); + if (!edittableAnnotationNameToIdCache[id]) { + throw new Error( + `Unable to find annotation name for id ${id}. This should have been cached on app initialization.` + ); + } + + return edittableAnnotationNameToIdCache[id]; + } } diff --git a/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts b/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts index 4f5691804..e8a4f32c0 100644 --- a/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts +++ b/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts @@ -44,6 +44,130 @@ describe("HttpFileService", () => { }); }); + describe("editFile", () => { + const httpClient = createMockHttpClient([ + { + when: () => true, + respondWith: {}, + }, + ]); + + it("fails if unable to find id of annotation", async () => { + // Arrange + const httpFileService = new HttpFileService({ + baseUrl, + httpClient, + downloadService: new FileDownloadServiceNoop(), + }); + + // Act / Assert + try { + await httpFileService.editFile("file_id", { ["Color"]: ["red"] }); + expect(false, "Expected to throw").to.be.true; + } catch (e) { + /* noop */ + } + }); + }); + + describe("getEdittableFileMetadata", () => { + const colorAnnotation = "Color"; + const colorAnnotationId = 3; + const fileIdWithColorAnnotation = "abc123"; + const fileIdWithoutColorAnnotation = "def456"; + const httpClient = createMockHttpClient([ + { + when: (config) => + !!config.url?.includes(`filemetadata/${fileIdWithColorAnnotation}`), + respondWith: { + data: { + data: [ + { + fileId: "abc123", + annotations: [ + { + annotationId: colorAnnotationId, + values: ["red"], + }, + ], + }, + ], + }, + }, + }, + { + when: (config) => + !!config.url?.includes(`filemetadata/${fileIdWithoutColorAnnotation}`), + respondWith: { + data: { + data: [ + { + fileId: "abc123", + annotations: [ + { + annotationId: 4, + values: ["AICS-0"], + }, + ], + }, + ], + }, + }, + }, + { + when: (config) => !!config.url?.includes(`annotation/${colorAnnotation}`), + respondWith: { + data: { + data: [ + { + annotationId: colorAnnotationId, + }, + ], + }, + }, + }, + ]); + + it("converts response into a map of file_id to metadata", async () => { + const httpFileService = new HttpFileService({ + baseUrl, + httpClient, + downloadService: new FileDownloadServiceNoop(), + }); + + // fails if cache is not prepared beforehand + await httpFileService.prepareAnnotationIdCache([colorAnnotation]); + + // Act + const fileMetadata = await httpFileService.getEdittableFileMetadata([ + fileIdWithColorAnnotation, + ]); + + // Assert + expect(fileMetadata).to.deep.equal({ + [fileIdWithColorAnnotation]: { + [colorAnnotation]: ["red"], + }, + }); + }); + + it("fails if unable to find id of annotation", async () => { + const httpFileService = new HttpFileService({ + baseUrl, + httpClient, + downloadService: new FileDownloadServiceNoop(), + }); + + // Act / Assert + try { + await httpFileService.getEdittableFileMetadata([fileIdWithoutColorAnnotation]); + expect(false, "Expected to throw").to.be.true; + } catch (e) { + /* noop */ + } + }); + }); + describe("getAggregateInformation", () => { const totalFileSize = 12424114; const totalFileCount = 7; diff --git a/packages/core/services/FileService/index.ts b/packages/core/services/FileService/index.ts index a540f2da4..0847e509f 100644 --- a/packages/core/services/FileService/index.ts +++ b/packages/core/services/FileService/index.ts @@ -1,3 +1,4 @@ +import { AnnotationValue } from "../AnnotationService"; import { DownloadResult } from "../FileDownloadService"; import FileDetail from "../../entity/FileDetail"; import FileSelection from "../../entity/FileSelection"; @@ -38,6 +39,10 @@ export interface Selection { include?: string[]; } +export interface AnnotationNameToValuesMap { + [name: string]: AnnotationValue[]; +} + export default interface FileService { baseUrl?: string; download( @@ -45,7 +50,11 @@ export default interface FileService { selections: Selection[], format: "csv" | "json" | "parquet" ): Promise; - getCountOfMatchingFiles(fileSet: FileSet): Promise; + editFile(fileId: string, annotations: AnnotationNameToValuesMap): Promise; getAggregateInformation(fileSelection: FileSelection): Promise; + getCountOfMatchingFiles(fileSet: FileSet): Promise; + getEdittableFileMetadata( + fileIds: string[] + ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }>; getFiles(request: GetFilesRequest): Promise; } diff --git a/packages/core/state/interaction/actions.ts b/packages/core/state/interaction/actions.ts index 441dca1eb..e9cc38727 100644 --- a/packages/core/state/interaction/actions.ts +++ b/packages/core/state/interaction/actions.ts @@ -4,6 +4,7 @@ import { uniqueId } from "lodash"; import { ContextMenuItem, PositionReference } from "../../components/ContextMenu"; import FileFilter from "../../entity/FileFilter"; import { ModalType } from "../../components/Modal"; +import { AnnotationValue } from "../../services/AnnotationService"; import { UserSelectedApplication } from "../../services/PersistentConfigService"; import FileDetail from "../../entity/FileDetail"; import { Source } from "../../entity/FileExplorerURL"; @@ -267,6 +268,32 @@ export function initializeApp(baseUrl: string): InitializeApp { }; } +/** + * Edit the currently selected files with the given metadata + */ +export const EDIT_FILES = makeConstant(STATE_BRANCH_NAME, "edit-files"); + +export interface EditFiles { + type: string; + payload: { + annotations: { [name: string]: AnnotationValue[] }; + filters?: FileFilter[]; + }; +} + +export function editFiles( + annotations: { [name: string]: AnnotationValue[] }, + filters?: FileFilter[] +): EditFiles { + return { + type: EDIT_FILES, + payload: { + annotations, + filters, + }, + }; +} + /** * PROCESS AND STATUS RELATED ENUMS, INTERFACES, ETC. */ diff --git a/packages/core/state/interaction/logics.ts b/packages/core/state/interaction/logics.ts index 94ef5e225..fdb0be34f 100644 --- a/packages/core/state/interaction/logics.ts +++ b/packages/core/state/interaction/logics.ts @@ -1,4 +1,4 @@ -import { isEmpty, sumBy, throttle, uniq, uniqueId } from "lodash"; +import { chunk, isEmpty, noop, sumBy, throttle, uniq, uniqueId } from "lodash"; import { AnyAction } from "redux"; import { createLogic } from "redux-logic"; @@ -31,6 +31,8 @@ import { SetIsSmallScreenAction, setVisibleModal, hideVisibleModal, + EDIT_FILES, + EditFiles, } from "./actions"; import * as interactionSelectors from "./selectors"; import { DownloadResolution, FileInfo } from "../../services/FileDownloadService"; @@ -499,6 +501,98 @@ const openWithLogic = createLogic({ type: OPEN_WITH, }); +/** + * Interceptor responsible for translating an EDIT_FILES action into a progress tracked + * series of edits on the files currently selected. + */ +const editFilesLogic = createLogic({ + async process(deps: ReduxLogicDeps, dispatch, done) { + const fileService = interactionSelectors.getFileService(deps.getState()); + const fileSelection = selection.selectors.getFileSelection(deps.getState()); + const sortColumn = selection.selectors.getSortColumn(deps.getState()); + const { + payload: { annotations, filters }, + } = deps.action as EditFiles; + + // Gather up the files for the files selected currently + // if filters is present then actual "selected" files + // are the ones that match the filters, this happens when + // editing a whole folder for example + let filesSelected; + if (filters) { + const fileSet = new FileSet({ + filters, + fileService, + sort: sortColumn, + }); + const totalFileCount = await fileSet.fetchTotalCount(); + filesSelected = await fileSet.fetchFileRange(0, totalFileCount); + } else { + filesSelected = await fileSelection.fetchAllDetails(); + } + + // Break files into batches of 10 File IDs + const fileIds = filesSelected.map((file) => file.id); + const batches = chunk(fileIds, 10); + + // Dispatch an event to alert the user of the start of the process + const editRequestId = uniqueId(); + const editProcessMsg = "Editing files in progress."; + // TODO: Lyndsay's design did not include a progress bar for editing files + // nor a way to cancel the process. This is a placeholder for now. + dispatch(processStart(editRequestId, editProcessMsg, noop)); + + // Track the total number of files edited + let totalFileEdited = 0; + + // Throttled progress dispatcher + const onProgress = throttle(() => { + dispatch( + processProgress( + editRequestId, + totalFileEdited / fileIds.length, + editProcessMsg, + noop + ) + ); + }, 1000); + + try { + // Begin editing files in batches + for (const batch of batches) { + // Asynchronously begin the edit for each file in the batch + const promises = batch.map( + (fileId) => + new Promise(async (resolve, reject) => { + try { + await fileService.editFile(fileId, annotations); + totalFileEdited += 1; + onProgress(); + resolve(); + } catch (err) { + reject(err); + } + }) + ); + + // Await the results of this batch + await Promise.all(promises); + } + + dispatch(processSuccess(editRequestId, "Successfully edited files.")); + } catch (err) { + // Dispatch an event to alert the user of the failure + const errorMsg = `Failed to finish editing files, some may have been edited. Details:
${ + err instanceof Error ? err.message : err + }`; + dispatch(processFailure(editRequestId, errorMsg)); + } finally { + done(); + } + }, + type: EDIT_FILES, +}); + /** * Interceptor responsible for responding to a SHOW_CONTEXT_MENU action and ensuring the previous * context menu is dismissed gracefully. @@ -578,6 +672,7 @@ const setIsSmallScreen = createLogic({ export default [ initializeApp, downloadManifest, + editFilesLogic, cancelFileDownloadLogic, promptForNewExecutable, openWithDefault, diff --git a/packages/core/state/interaction/test/logics.test.ts b/packages/core/state/interaction/test/logics.test.ts index 688c55321..abf6558b7 100644 --- a/packages/core/state/interaction/test/logics.test.ts +++ b/packages/core/state/interaction/test/logics.test.ts @@ -17,6 +17,7 @@ import { promptForNewExecutable, openWithDefault, downloadFiles, + editFiles, } from "../actions"; import { ExecutableEnvCancellationToken, @@ -650,6 +651,163 @@ describe("Interaction logics", () => { }); }); + describe("editFilesLogic", () => { + const files = []; + const fileKinds = ["PNG", "TIFF"]; + for (let i = 0; i <= 100; i++) { + files.push({ + file_path: `/allen/file_${i}.ext`, + annotations: [ + { + name: AnnotationName.KIND, + values: fileKinds, + }, + { + name: "Cell Line", + values: ["AICS-10", "AICS-12"], + }, + ], + }); + } + const baseUrl = "test"; + const responseStub = { + when: () => true, + respondWith: { + data: { data: files }, + }, + }; + const mockHttpClient = createMockHttpClient(responseStub); + const fileService = new HttpFileService({ + baseUrl, + httpClient: mockHttpClient, + downloadService: new FileDownloadServiceNoop(), + }); + const fakeSelection = new FileSelection().select({ + fileSet: new FileSet({ fileService }), + index: new NumericRange(0, 100), + sortOrder: 0, + }); + + it("edits 'folder' when filter specified'", async () => { + // Arrange + const state = mergeState(initialState, {}); + const { store, logicMiddleware, actions } = configureMockStore({ + state, + logics: interactionLogics, + }); + + // Act + store.dispatch(editFiles({ "Cell Line": ["AICS-12"] }, [])); + await logicMiddleware.whenComplete(); + + // Assert + expect( + actions.includesMatch({ + type: SET_STATUS, + payload: { + data: { + status: ProcessStatus.SUCCEEDED, + }, + }, + }) + ).to.be.true; + + // sanity-check: make certain this isn't evergreen + expect( + actions.includesMatch({ + type: SET_STATUS, + payload: { + data: { + status: ProcessStatus.FAILED, + }, + }, + }) + ).to.be.false; + }); + + it("edits selected files when no filter sepecified", async () => { + // Arrange + const state = mergeState(initialState, { + selection: { + fileSelection: fakeSelection, + }, + }); + const { store, logicMiddleware, actions } = configureMockStore({ + state, + logics: interactionLogics, + }); + + // Act + store.dispatch(editFiles({ "Cell Line": ["AICS-12"] })); + await logicMiddleware.whenComplete(); + + // Assert + expect( + actions.includesMatch({ + type: SET_STATUS, + payload: { + data: { + status: ProcessStatus.SUCCEEDED, + }, + }, + }) + ).to.be.true; + + // sanity-check: make certain this isn't evergreen + expect( + actions.includesMatch({ + type: SET_STATUS, + payload: { + data: { + status: ProcessStatus.FAILED, + }, + }, + }) + ).to.be.false; + }); + + it("alerts user to failure editing", async () => { + // Arrange + const state = mergeState(initialState, { + selection: { + fileSelection: fakeSelection, + }, + }); + const { store, logicMiddleware, actions } = configureMockStore({ + state, + logics: interactionLogics, + }); + + // Act + store.dispatch(editFiles({ "Cell Line": ["AICS-12"] })); + await logicMiddleware.whenComplete(); + + // Assert + expect( + actions.includesMatch({ + type: SET_STATUS, + payload: { + data: { + status: ProcessStatus.FAILED, + }, + }, + }) + ).to.be.true; + + // sanity-check: make certain this isn't evergreen + expect( + actions.includesMatch({ + type: SET_STATUS, + payload: { + data: { + status: ProcessStatus.SUCCEEDED, + }, + }, + }) + ).to.be.false; + }); + }); + describe("cancelFileDownloadLogic", () => { it("marks the failure of a download cancellation (on error)", async () => { // arrange diff --git a/packages/core/state/metadata/logics.ts b/packages/core/state/metadata/logics.ts index b4c0bb1ba..ebdeb612d 100644 --- a/packages/core/state/metadata/logics.ts +++ b/packages/core/state/metadata/logics.ts @@ -17,6 +17,7 @@ import * as metadataSelectors from "./selectors"; import AnnotationName from "../../entity/Annotation/AnnotationName"; import FileSort, { SortOrder } from "../../entity/FileSort"; import HttpAnnotationService from "../../services/AnnotationService/HttpAnnotationService"; +import HttpFileService from "../../services/FileService/HttpFileService"; /** * Interceptor responsible for turning REQUEST_ANNOTATIONS action into a network call for available annotations. Outputs @@ -26,7 +27,9 @@ const requestAnnotations = createLogic({ async process(deps: ReduxLogicDeps, dispatch, done) { const { getState, httpClient } = deps; const annotationService = interaction.selectors.getAnnotationService(getState()); + const fileService = interaction.selectors.getFileService(getState()); const applicationVersion = interaction.selectors.getApplicationVersion(getState()); + const isAicsEmployee = interaction.selectors.isAicsEmployee(getState()); if (annotationService instanceof HttpAnnotationService) { if (applicationVersion) { annotationService.setApplicationVersion(applicationVersion); @@ -37,6 +40,11 @@ const requestAnnotations = createLogic({ try { const annotations = await annotationService.fetchAnnotations(); dispatch(receiveAnnotations(annotations)); + + if (isAicsEmployee && fileService instanceof HttpFileService) { + const annotationNames = annotations.map((annotation) => annotation.name); + await fileService.prepareAnnotationIdCache(annotationNames); + } } catch (err) { console.error("Failed to fetch annotations", err); } finally { diff --git a/packages/desktop/src/services/DatabaseServiceElectron.ts b/packages/desktop/src/services/DatabaseServiceElectron.ts index 87efd70eb..2b0d84246 100644 --- a/packages/desktop/src/services/DatabaseServiceElectron.ts +++ b/packages/desktop/src/services/DatabaseServiceElectron.ts @@ -135,7 +135,7 @@ export default class DatabaseServiceElectron extends DatabaseService { } } - protected async execute(sql: string): Promise { + public async execute(sql: string): Promise { return new Promise((resolve, reject) => { try { this.database.exec(sql, (err: any) => { diff --git a/packages/desktop/src/util/constants.ts b/packages/desktop/src/util/constants.ts index c4c95ed0c..3fa335854 100644 --- a/packages/desktop/src/util/constants.ts +++ b/packages/desktop/src/util/constants.ts @@ -15,6 +15,7 @@ export enum FileExplorerServiceBaseUrl { PRODUCTION = "https://production.int.allencell.org", } +// TODO: Do we need this? export enum MetadataManagementServiceBaseUrl { LOCALHOST = "http://localhost:9060/metadata-management-service", STAGING = "http://stg-aics-api/metadata-management-service", diff --git a/packages/web/src/services/DatabaseServiceWeb.ts b/packages/web/src/services/DatabaseServiceWeb.ts index 4e437be3b..f453f5293 100644 --- a/packages/web/src/services/DatabaseServiceWeb.ts +++ b/packages/web/src/services/DatabaseServiceWeb.ts @@ -111,7 +111,7 @@ export default class DatabaseServiceWeb extends DatabaseService { } } - protected async execute(sql: string): Promise { + public async execute(sql: string): Promise { if (!this.database) { throw new Error("Database failed to initialize"); } From a669973d4771a60628382de54b2aaf9de614ec57 Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Mon, 7 Oct 2024 14:44:07 -0700 Subject: [PATCH 03/33] Update to use FES stored annotationID --- packages/core/entity/Annotation/index.ts | 6 ++ .../FileService/HttpFileService/index.ts | 94 ++++++++----------- .../test/HttpFileService.test.ts | 31 +++--- packages/core/services/FileService/index.ts | 10 +- packages/core/state/interaction/logics.ts | 9 +- packages/core/state/metadata/logics.ts | 8 -- 6 files changed, 73 insertions(+), 85 deletions(-) diff --git a/packages/core/entity/Annotation/index.ts b/packages/core/entity/Annotation/index.ts index c5ed195c5..ba5839aa6 100644 --- a/packages/core/entity/Annotation/index.ts +++ b/packages/core/entity/Annotation/index.ts @@ -9,6 +9,8 @@ import { AnnotationValue } from "../../services/AnnotationService"; * Expected JSON structure of an annotation returned from the query service. */ export interface AnnotationResponse { + // Undefined when pulled from a non-AICS FMS data source + annotationId?: number; annotationDisplayName: string; annotationName: string; description: string; @@ -70,6 +72,10 @@ export default class Annotation { return this.annotation.units; } + public get id(): number | undefined { + return this.annotation.annotationId; + } + /** * Get the annotation this instance represents from a given FmsFile. An annotation on an FmsFile * can either be at the "top-level" of the document or it can be within it's "annotations" list. diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index 5e2c4985a..fa39eb45f 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -1,4 +1,4 @@ -import { chunk, compact, invert, join, uniqueId } from "lodash"; +import { compact, join, uniqueId } from "lodash"; import FileService, { GetFilesRequest, @@ -6,10 +6,10 @@ import FileService, { Selection, AnnotationNameToValuesMap, } from ".."; -import { AnnotationValue } from "../../AnnotationService"; import FileDownloadService, { DownloadResult } from "../../FileDownloadService"; import FileDownloadServiceNoop from "../../FileDownloadService/FileDownloadServiceNoop"; import HttpServiceBase, { ConnectionConfig } from "../../HttpServiceBase"; +import Annotation from "../../../entity/Annotation"; import FileSelection from "../../../entity/FileSelection"; import FileSet from "../../../entity/FileSet"; import FileDetail, { FmsFile } from "../../../entity/FileDetail"; @@ -147,67 +147,53 @@ export default class HttpFileService extends HttpServiceBase implements FileServ ); } - public async editFile(fileId: string, annotations: AnnotationNameToValuesMap): Promise { + public async editFile( + fileId: string, + annotationNameToValuesMap: AnnotationNameToValuesMap, + annotationNameToAnnotationMap?: Record + ): Promise { const url = `${this.baseUrl}/${HttpFileService.BASE_EDIT_FILES_URL}/${fileId}`; - const mmsAnnotations: { annotationId: number; values: AnnotationValue[] }[] = []; - for (const [name, values] of Object.entries(annotations)) { - const annotationId = await this.getEdittableAnnotationIdByName(name); - mmsAnnotations.push({ annotationId, values }); - } - const requestBody = JSON.stringify({ - annotations: mmsAnnotations, + const annotations = Object.entries(annotationNameToValuesMap).map(([name, values]) => { + const annotationId = annotationNameToAnnotationMap?.[name].id; + if (!annotationId) { + throw new Error( + `Unable to edit file. Failed to find annotation id for annotation ${name}` + ); + } + return { annotationId, values }; }); + const requestBody = JSON.stringify({ annotations }); await this.put(url, requestBody); } public async getEdittableFileMetadata( - fileIds: string[] + fileIds: string[], + annotationIdToAnnotationMap?: Record ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { const url = `${this.baseUrl}/${HttpFileService.BASE_EDIT_FILES_URL}/${fileIds.join(",")}`; const response = await this.get(url); - const fileIdToAnnotations: { [fileId: string]: AnnotationNameToValuesMap } = {}; - for (const file of response.data) { - fileIdToAnnotations[file.fileId] = {}; - for (const annotation of file.annotations || []) { - const name = await this.getEdittableAnnotationNameById(annotation.annotationId); - fileIdToAnnotations[file.fileId][name] = annotation.values; - } - } - return fileIdToAnnotations; - } - - /** - * For every annotation name given, fetch the annotation id from MMS and cache it - * for future in edit scenarios, necessary because we don't have an endpoint in MMS - * that can grab the annotation name from the annotation id - */ - public async prepareAnnotationIdCache(annotationNames: string[]): Promise { - const batches = chunk(annotationNames, 25); - for (const batch of batches) { - await Promise.all(batch.map((name) => this.getEdittableAnnotationIdByName(name))); - } - } - - private async getEdittableAnnotationIdByName(name: string): Promise { - if (!this.edittableAnnotationIdToNameCache[name]) { - const url = `${this.baseUrl}/${HttpFileService.BASE_ANNOTATION_ID_URL}/${name}`; - const response = await this.get<{ annotationId: number }>(url); - - this.edittableAnnotationIdToNameCache[name] = response.data[0].annotationId; - } - - return this.edittableAnnotationIdToNameCache[name]; - } - - private async getEdittableAnnotationNameById(id: number): Promise { - const edittableAnnotationNameToIdCache = invert(this.edittableAnnotationIdToNameCache); - if (!edittableAnnotationNameToIdCache[id]) { - throw new Error( - `Unable to find annotation name for id ${id}. This should have been cached on app initialization.` - ); - } - - return edittableAnnotationNameToIdCache[id]; + // Group files by fileId + return response.data.reduce( + (fileAcc, file) => ({ + ...fileAcc, + // Group annotations by annotationId + [file.fileId]: + file.annotations?.reduce((annoAcc, annotation) => { + const name = annotationIdToAnnotationMap?.[annotation.annotationId]?.name; + if (!name) { + throw new Error( + "Failure mapping editable metadata response. " + + `Failed to find annotation name for annotation id ${annotation.annotationId}` + ); + } + return { + ...annoAcc, + [name]: annotation.values, + }; + }, {} as AnnotationNameToValuesMap) || {}, + }), + {} as { [fileId: string]: AnnotationNameToValuesMap } + ); } } diff --git a/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts b/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts index e8a4f32c0..f4298b28d 100644 --- a/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts +++ b/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts @@ -2,6 +2,7 @@ import { createMockHttpClient } from "@aics/redux-utils"; import { expect } from "chai"; import HttpFileService from ".."; +import Annotation from "../../../../entity/Annotation"; import FileSelection from "../../../../entity/FileSelection"; import FileSet from "../../../../entity/FileSet"; import NumericRange from "../../../../entity/NumericRange"; @@ -65,7 +66,9 @@ describe("HttpFileService", () => { await httpFileService.editFile("file_id", { ["Color"]: ["red"] }); expect(false, "Expected to throw").to.be.true; } catch (e) { - /* noop */ + expect((e as Error).message).to.equal( + "Unable to edit file. Failed to find annotation id for annotation Color" + ); } }); }); @@ -114,18 +117,6 @@ describe("HttpFileService", () => { }, }, }, - { - when: (config) => !!config.url?.includes(`annotation/${colorAnnotation}`), - respondWith: { - data: { - data: [ - { - annotationId: colorAnnotationId, - }, - ], - }, - }, - }, ]); it("converts response into a map of file_id to metadata", async () => { @@ -135,13 +126,11 @@ describe("HttpFileService", () => { downloadService: new FileDownloadServiceNoop(), }); - // fails if cache is not prepared beforehand - await httpFileService.prepareAnnotationIdCache([colorAnnotation]); - // Act - const fileMetadata = await httpFileService.getEdittableFileMetadata([ - fileIdWithColorAnnotation, - ]); + const fileMetadata = await httpFileService.getEdittableFileMetadata( + [fileIdWithColorAnnotation], + { [colorAnnotationId]: new Annotation({ annotationName: colorAnnotation } as any) } + ); // Assert expect(fileMetadata).to.deep.equal({ @@ -163,7 +152,9 @@ describe("HttpFileService", () => { await httpFileService.getEdittableFileMetadata([fileIdWithoutColorAnnotation]); expect(false, "Expected to throw").to.be.true; } catch (e) { - /* noop */ + expect((e as Error).message).to.equal( + "Failure mapping editable metadata response. Failed to find annotation name for annotation id 4" + ); } }); }); diff --git a/packages/core/services/FileService/index.ts b/packages/core/services/FileService/index.ts index 0847e509f..fda2b2063 100644 --- a/packages/core/services/FileService/index.ts +++ b/packages/core/services/FileService/index.ts @@ -1,5 +1,6 @@ import { AnnotationValue } from "../AnnotationService"; import { DownloadResult } from "../FileDownloadService"; +import Annotation from "../../entity/Annotation"; import FileDetail from "../../entity/FileDetail"; import FileSelection from "../../entity/FileSelection"; import FileSet from "../../entity/FileSet"; @@ -50,11 +51,16 @@ export default interface FileService { selections: Selection[], format: "csv" | "json" | "parquet" ): Promise; - editFile(fileId: string, annotations: AnnotationNameToValuesMap): Promise; + editFile( + fileId: string, + annotations: AnnotationNameToValuesMap, + annotationNameToAnnotationMap?: Record + ): Promise; getAggregateInformation(fileSelection: FileSelection): Promise; getCountOfMatchingFiles(fileSet: FileSet): Promise; getEdittableFileMetadata( - fileIds: string[] + fileIds: string[], + annotationIdToAnnotationMap?: Record ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }>; getFiles(request: GetFilesRequest): Promise; } diff --git a/packages/core/state/interaction/logics.ts b/packages/core/state/interaction/logics.ts index fdb0be34f..5823391a9 100644 --- a/packages/core/state/interaction/logics.ts +++ b/packages/core/state/interaction/logics.ts @@ -510,6 +510,9 @@ const editFilesLogic = createLogic({ const fileService = interactionSelectors.getFileService(deps.getState()); const fileSelection = selection.selectors.getFileSelection(deps.getState()); const sortColumn = selection.selectors.getSortColumn(deps.getState()); + const annotationNameToAnnotationMap = metadata.selectors.getAnnotationNameToAnnotationMap( + deps.getState() + ); const { payload: { annotations, filters }, } = deps.action as EditFiles; @@ -565,7 +568,11 @@ const editFilesLogic = createLogic({ (fileId) => new Promise(async (resolve, reject) => { try { - await fileService.editFile(fileId, annotations); + await fileService.editFile( + fileId, + annotations, + annotationNameToAnnotationMap + ); totalFileEdited += 1; onProgress(); resolve(); diff --git a/packages/core/state/metadata/logics.ts b/packages/core/state/metadata/logics.ts index ebdeb612d..b4c0bb1ba 100644 --- a/packages/core/state/metadata/logics.ts +++ b/packages/core/state/metadata/logics.ts @@ -17,7 +17,6 @@ import * as metadataSelectors from "./selectors"; import AnnotationName from "../../entity/Annotation/AnnotationName"; import FileSort, { SortOrder } from "../../entity/FileSort"; import HttpAnnotationService from "../../services/AnnotationService/HttpAnnotationService"; -import HttpFileService from "../../services/FileService/HttpFileService"; /** * Interceptor responsible for turning REQUEST_ANNOTATIONS action into a network call for available annotations. Outputs @@ -27,9 +26,7 @@ const requestAnnotations = createLogic({ async process(deps: ReduxLogicDeps, dispatch, done) { const { getState, httpClient } = deps; const annotationService = interaction.selectors.getAnnotationService(getState()); - const fileService = interaction.selectors.getFileService(getState()); const applicationVersion = interaction.selectors.getApplicationVersion(getState()); - const isAicsEmployee = interaction.selectors.isAicsEmployee(getState()); if (annotationService instanceof HttpAnnotationService) { if (applicationVersion) { annotationService.setApplicationVersion(applicationVersion); @@ -40,11 +37,6 @@ const requestAnnotations = createLogic({ try { const annotations = await annotationService.fetchAnnotations(); dispatch(receiveAnnotations(annotations)); - - if (isAicsEmployee && fileService instanceof HttpFileService) { - const annotationNames = annotations.map((annotation) => annotation.name); - await fileService.prepareAnnotationIdCache(annotationNames); - } } catch (err) { console.error("Failed to fetch annotations", err); } finally { From d405170793284a12dd54a538c6d8f5cd4a5bf693 Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Mon, 7 Oct 2024 14:45:49 -0700 Subject: [PATCH 04/33] Update comment --- packages/core/services/FileService/HttpFileService/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index fa39eb45f..ccfdb76cf 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -177,7 +177,7 @@ export default class HttpFileService extends HttpServiceBase implements FileServ return response.data.reduce( (fileAcc, file) => ({ ...fileAcc, - // Group annotations by annotationId + // Group annotations by name [file.fileId]: file.annotations?.reduce((annoAcc, annotation) => { const name = annotationIdToAnnotationMap?.[annotation.annotationId]?.name; From 8eafa0f872ce980c3cc121fc6a2ac89b91eede17 Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Mon, 7 Oct 2024 14:46:43 -0700 Subject: [PATCH 05/33] Remove MMS --- .../MetadataManagementService/index.ts | 54 ------------------- .../test/MetadataManagementService.test.ts | 34 ------------ 2 files changed, 88 deletions(-) delete mode 100644 packages/core/services/MetadataManagementService/index.ts delete mode 100644 packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts diff --git a/packages/core/services/MetadataManagementService/index.ts b/packages/core/services/MetadataManagementService/index.ts deleted file mode 100644 index 79d56e030..000000000 --- a/packages/core/services/MetadataManagementService/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { MetadataManagementServiceBaseUrl } from "../../../desktop/src/util/constants"; -import HttpServiceBase, { ConnectionConfig } from "../HttpServiceBase"; - -interface Config extends ConnectionConfig { - baseUrl: string | keyof typeof MetadataManagementServiceBaseUrl; -} - -// Interfaces borrowed from aics-file-upload-app -// Used for the POST request to MMS for creating file metadata -export interface MMSFileAnnotation { - annotationId: number; - values: string[]; -} - -export interface MMSFile { - fileId: string; - annotations?: MMSFileAnnotation[]; - templateId?: number; -} - -export interface MMSUploadRequest { - customMetadata?: MMSFile; - fileType?: string; - file: FileMetadataBlock; - [id: string]: any; -} - -interface FileMetadataBlock { - originalPath: string; - fileName?: string; - fileType: string; - jobId?: string; - [id: string]: any; -} - -export default class MetadataManagementService extends HttpServiceBase { - private static readonly FILEMETADATA_ENDPOINT_VERSION = "1.0"; - - constructor(config: Config) { - super(config); - } - - public async getFileMetadata(fileId: string): Promise { - const url = `${this.baseUrl}/${MetadataManagementService.FILEMETADATA_ENDPOINT_VERSION}/filemetadata/${fileId}`; - const response = await this.get(url); - return response.data[0]; - } - - public async editFileMetadata(fileId: string, request: MMSUploadRequest): Promise { - const url = `${this.baseUrl}/${MetadataManagementService.FILEMETADATA_ENDPOINT_VERSION}/filemetadata/${fileId}`; - const requestBody = JSON.stringify(request); - await this.put(url, requestBody); - } -} diff --git a/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts b/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts deleted file mode 100644 index 7b23cc4df..000000000 --- a/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createMockHttpClient } from "@aics/redux-utils"; -import { expect } from "chai"; - -import MetadataManagementService from ".."; - -describe("MetadataManagementService", () => { - const baseUrl = "test"; - const fileIds = ["abc123", "def456", "ghi789", "jkl012"]; - const files = fileIds.map((fileId) => ({ - fileId, - })); - - describe("getFiles", () => { - const httpClient = createMockHttpClient([ - { - when: () => true, - respondWith: { - data: { - data: files.slice(0, 1), - }, - }, - }, - ]); - - it("issues request for files that match given parameters", async () => { - const mmsService = new MetadataManagementService({ - baseUrl, - httpClient, - }); - const response = await mmsService.getFileMetadata(files[0]["fileId"]); - expect(response.fileId).to.equal(files[0]["fileId"]); - }); - }); -}); From f4a1eb9f1ab1090c90621c063912566f923ae07d Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Mon, 7 Oct 2024 15:19:35 -0700 Subject: [PATCH 06/33] Map FES base URL to MMS base url --- .../services/FileService/HttpFileService/index.ts | 14 +++++++++++--- packages/desktop/src/util/constants.ts | 7 ------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index ccfdb76cf..e530745e2 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -9,6 +9,7 @@ import FileService, { import FileDownloadService, { DownloadResult } from "../../FileDownloadService"; import FileDownloadServiceNoop from "../../FileDownloadService/FileDownloadServiceNoop"; import HttpServiceBase, { ConnectionConfig } from "../../HttpServiceBase"; +import { FileExplorerServiceBaseUrl } from "../../../constants"; import Annotation from "../../../entity/Annotation"; import FileSelection from "../../../entity/FileSelection"; import FileSet from "../../../entity/FileSet"; @@ -32,6 +33,12 @@ interface EdittableFileMetadata { templateId?: number; } +const FESBaseUrlToMMSBaseUrlMap = { + [FileExplorerServiceBaseUrl.LOCALHOST]: "http://localhost:9060", + [FileExplorerServiceBaseUrl.STAGING]: "http://stg-aics-api", + [FileExplorerServiceBaseUrl.PRODUCTION]: "http://stg-aics-api", +}; + /** * Service responsible for fetching file related metadata. */ @@ -40,7 +47,6 @@ export default class HttpFileService extends HttpServiceBase implements FileServ public static readonly BASE_FILES_URL = `file-explorer-service/${HttpFileService.ENDPOINT_VERSION}/files`; public static readonly BASE_FILE_COUNT_URL = `${HttpFileService.BASE_FILES_URL}/count`; public static readonly BASE_EDIT_FILES_URL = `metadata-management-service/1.0/filemetadata`; - public static readonly BASE_ANNOTATION_ID_URL = `metadata-management-service/1.0/annotation`; public static readonly SELECTION_AGGREGATE_URL = `${HttpFileService.BASE_FILES_URL}/selection/aggregate`; private static readonly CSV_ENDPOINT_VERSION = "2.0"; public static readonly BASE_CSV_DOWNLOAD_URL = `file-explorer-service/${HttpFileService.CSV_ENDPOINT_VERSION}/files/selection/manifest`; @@ -152,7 +158,8 @@ export default class HttpFileService extends HttpServiceBase implements FileServ annotationNameToValuesMap: AnnotationNameToValuesMap, annotationNameToAnnotationMap?: Record ): Promise { - const url = `${this.baseUrl}/${HttpFileService.BASE_EDIT_FILES_URL}/${fileId}`; + const mmsBaseUrl = FESBaseUrlToMMSBaseUrlMap[this.baseUrl as FileExplorerServiceBaseUrl]; + const url = `${mmsBaseUrl}/${HttpFileService.BASE_EDIT_FILES_URL}/${fileId}`; const annotations = Object.entries(annotationNameToValuesMap).map(([name, values]) => { const annotationId = annotationNameToAnnotationMap?.[name].id; if (!annotationId) { @@ -170,7 +177,8 @@ export default class HttpFileService extends HttpServiceBase implements FileServ fileIds: string[], annotationIdToAnnotationMap?: Record ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { - const url = `${this.baseUrl}/${HttpFileService.BASE_EDIT_FILES_URL}/${fileIds.join(",")}`; + const mmsBaseUrl = FESBaseUrlToMMSBaseUrlMap[this.baseUrl as FileExplorerServiceBaseUrl]; + const url = `${mmsBaseUrl}/${HttpFileService.BASE_EDIT_FILES_URL}/${fileIds.join(",")}`; const response = await this.get(url); // Group files by fileId diff --git a/packages/desktop/src/util/constants.ts b/packages/desktop/src/util/constants.ts index 3fa335854..2b950921b 100644 --- a/packages/desktop/src/util/constants.ts +++ b/packages/desktop/src/util/constants.ts @@ -15,13 +15,6 @@ export enum FileExplorerServiceBaseUrl { PRODUCTION = "https://production.int.allencell.org", } -// TODO: Do we need this? -export enum MetadataManagementServiceBaseUrl { - LOCALHOST = "http://localhost:9060/metadata-management-service", - STAGING = "http://stg-aics-api/metadata-management-service", - PRODUCTION = "http://aics-api/metadata-management-service", -} - // Channels global variables can be modified on / listen to export enum GlobalVariableChannels { BaseUrl = "data-source-base-url", From d39f51483627294079ced44b672c034c80374cfc Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Tue, 15 Oct 2024 16:33:00 -0700 Subject: [PATCH 07/33] Change minor spelling --- .../core/services/FileService/DatabaseFileService/index.ts | 2 +- .../DatabaseFileService/test/DatabaseFileService.test.ts | 7 +++++-- packages/core/services/FileService/FileServiceNoop.ts | 2 +- .../core/services/FileService/HttpFileService/index.ts | 7 +++---- .../HttpFileService/test/HttpFileService.test.ts | 6 +++--- packages/core/services/FileService/index.ts | 2 +- packages/core/state/interaction/logics.ts | 2 +- packages/core/state/interaction/test/logics.test.ts | 6 +++--- 8 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/core/services/FileService/DatabaseFileService/index.ts b/packages/core/services/FileService/DatabaseFileService/index.ts index d5df644ed..1cbe46f76 100644 --- a/packages/core/services/FileService/DatabaseFileService/index.ts +++ b/packages/core/services/FileService/DatabaseFileService/index.ts @@ -203,7 +203,7 @@ export default class DatabaseFileService implements FileService { return this.databaseService.execute(sql); } - public async getEdittableFileMetadata( + public async getEditableFileMetadata( fileIds: string[] ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { const sql = new SQLBuilder() diff --git a/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts b/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts index 25e2c2485..a006a212d 100644 --- a/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts +++ b/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts @@ -65,7 +65,7 @@ describe("DatabaseFileService", () => { }); }); - describe("getEdittableFileMetadata", () => { + describe("getEditableFileMetadata", () => { it("converts response into a map of file_id to metadata", async () => { // Arrange const databaseFileService = new DatabaseFileService({ @@ -75,7 +75,10 @@ describe("DatabaseFileService", () => { }); // Act - const response = await databaseFileService.getEdittableFileMetadata(["abc123"]); + const response = await databaseFileService.getEditableFileMetadata([ + "abc123", + "def456", + ]); // Assert expect(response).to.deep.equal({ diff --git a/packages/core/services/FileService/FileServiceNoop.ts b/packages/core/services/FileService/FileServiceNoop.ts index e74491170..569bd2d90 100644 --- a/packages/core/services/FileService/FileServiceNoop.ts +++ b/packages/core/services/FileService/FileServiceNoop.ts @@ -11,7 +11,7 @@ export default class FileServiceNoop implements FileService { return Promise.resolve({ count: 0, size: 0 }); } - public getEdittableFileMetadata(): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { + public getEditableFileMetadata(): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { return Promise.resolve({}); } diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index e530745e2..7c3aecc4a 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -24,7 +24,7 @@ interface Config extends ConnectionConfig { } // Used for the GET request to MMS for file metadata -interface EdittableFileMetadata { +interface EditableFileMetadata { fileId: string; annotations?: { annotationId: number; @@ -51,7 +51,6 @@ export default class HttpFileService extends HttpServiceBase implements FileServ private static readonly CSV_ENDPOINT_VERSION = "2.0"; public static readonly BASE_CSV_DOWNLOAD_URL = `file-explorer-service/${HttpFileService.CSV_ENDPOINT_VERSION}/files/selection/manifest`; private readonly downloadService: FileDownloadService; - private readonly edittableAnnotationIdToNameCache: { [name: string]: number } = {}; constructor(config: Config = { downloadService: new FileDownloadServiceNoop() }) { super(config); @@ -173,13 +172,13 @@ export default class HttpFileService extends HttpServiceBase implements FileServ await this.put(url, requestBody); } - public async getEdittableFileMetadata( + public async getEditableFileMetadata( fileIds: string[], annotationIdToAnnotationMap?: Record ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { const mmsBaseUrl = FESBaseUrlToMMSBaseUrlMap[this.baseUrl as FileExplorerServiceBaseUrl]; const url = `${mmsBaseUrl}/${HttpFileService.BASE_EDIT_FILES_URL}/${fileIds.join(",")}`; - const response = await this.get(url); + const response = await this.get(url); // Group files by fileId return response.data.reduce( diff --git a/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts b/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts index f4298b28d..78e28573a 100644 --- a/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts +++ b/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts @@ -73,7 +73,7 @@ describe("HttpFileService", () => { }); }); - describe("getEdittableFileMetadata", () => { + describe("getEditableFileMetadata", () => { const colorAnnotation = "Color"; const colorAnnotationId = 3; const fileIdWithColorAnnotation = "abc123"; @@ -127,7 +127,7 @@ describe("HttpFileService", () => { }); // Act - const fileMetadata = await httpFileService.getEdittableFileMetadata( + const fileMetadata = await httpFileService.getEditableFileMetadata( [fileIdWithColorAnnotation], { [colorAnnotationId]: new Annotation({ annotationName: colorAnnotation } as any) } ); @@ -149,7 +149,7 @@ describe("HttpFileService", () => { // Act / Assert try { - await httpFileService.getEdittableFileMetadata([fileIdWithoutColorAnnotation]); + await httpFileService.getEditableFileMetadata([fileIdWithoutColorAnnotation]); expect(false, "Expected to throw").to.be.true; } catch (e) { expect((e as Error).message).to.equal( diff --git a/packages/core/services/FileService/index.ts b/packages/core/services/FileService/index.ts index fda2b2063..2b2b110fd 100644 --- a/packages/core/services/FileService/index.ts +++ b/packages/core/services/FileService/index.ts @@ -58,7 +58,7 @@ export default interface FileService { ): Promise; getAggregateInformation(fileSelection: FileSelection): Promise; getCountOfMatchingFiles(fileSet: FileSet): Promise; - getEdittableFileMetadata( + getEditableFileMetadata( fileIds: string[], annotationIdToAnnotationMap?: Record ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }>; diff --git a/packages/core/state/interaction/logics.ts b/packages/core/state/interaction/logics.ts index 5823391a9..f87e06010 100644 --- a/packages/core/state/interaction/logics.ts +++ b/packages/core/state/interaction/logics.ts @@ -592,7 +592,7 @@ const editFilesLogic = createLogic({ const errorMsg = `Failed to finish editing files, some may have been edited. Details:
${ err instanceof Error ? err.message : err }`; - dispatch(processFailure(editRequestId, errorMsg)); + dispatch(processError(editRequestId, errorMsg)); } finally { done(); } diff --git a/packages/core/state/interaction/test/logics.test.ts b/packages/core/state/interaction/test/logics.test.ts index abf6558b7..938b7d048 100644 --- a/packages/core/state/interaction/test/logics.test.ts +++ b/packages/core/state/interaction/test/logics.test.ts @@ -718,7 +718,7 @@ describe("Interaction logics", () => { type: SET_STATUS, payload: { data: { - status: ProcessStatus.FAILED, + status: ProcessStatus.ERROR, }, }, }) @@ -759,7 +759,7 @@ describe("Interaction logics", () => { type: SET_STATUS, payload: { data: { - status: ProcessStatus.FAILED, + status: ProcessStatus.ERROR, }, }, }) @@ -788,7 +788,7 @@ describe("Interaction logics", () => { type: SET_STATUS, payload: { data: { - status: ProcessStatus.FAILED, + status: ProcessStatus.ERROR, }, }, }) From a1392ba4a53439e677d6cbdd5787827b7400ce53 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Wed, 16 Oct 2024 15:30:14 -0700 Subject: [PATCH 08/33] Fix editFileLogic tests --- .../state/interaction/test/logics.test.ts | 77 ++++++++++++++++--- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/packages/core/state/interaction/test/logics.test.ts b/packages/core/state/interaction/test/logics.test.ts index 938b7d048..055fc3ca8 100644 --- a/packages/core/state/interaction/test/logics.test.ts +++ b/packages/core/state/interaction/test/logics.test.ts @@ -1,4 +1,9 @@ -import { configureMockStore, mergeState, createMockHttpClient } from "@aics/redux-utils"; +import { + configureMockStore, + mergeState, + createMockHttpClient, + ResponseStub, +} from "@aics/redux-utils"; import { expect } from "chai"; import { get as _get } from "lodash"; import { createSandbox } from "sinon"; @@ -652,11 +657,23 @@ describe("Interaction logics", () => { }); describe("editFilesLogic", () => { + const sandbox = createSandbox(); + before(() => { + sandbox.stub(interaction.selectors, "getFileService").returns(fileService); + }); + afterEach(() => { + sandbox.resetHistory(); + }); + after(() => { + sandbox.restore(); + }); + const files = []; const fileKinds = ["PNG", "TIFF"]; for (let i = 0; i <= 100; i++) { files.push({ file_path: `/allen/file_${i}.ext`, + file_id: `file_${i}`, annotations: [ { name: AnnotationName.KIND, @@ -669,14 +686,40 @@ describe("Interaction logics", () => { ], }); } + const mockAnnotations = [ + new Annotation({ + annotationDisplayName: AnnotationName.KIND, + annotationName: AnnotationName.KIND, + description: "", + type: "Text", + annotationId: 0, + }), + new Annotation({ + annotationDisplayName: "Cell Line", + annotationName: "Cell Line", + description: "", + type: "Text", + annotationId: 1, + }), + ]; + const baseUrl = "test"; - const responseStub = { - when: () => true, - respondWith: { - data: { data: files }, + const responseStubs: ResponseStub[] = [ + { + when: (config) => + _get(config, "url", "").includes(HttpFileService.BASE_FILE_COUNT_URL), + respondWith: { + data: { data: [files.length] }, + }, }, - }; - const mockHttpClient = createMockHttpClient(responseStub); + { + when: (config) => _get(config, "url", "").includes(HttpFileService.BASE_FILES_URL), + respondWith: { + data: { data: files }, + }, + }, + ]; + const mockHttpClient = createMockHttpClient(responseStubs); const fileService = new HttpFileService({ baseUrl, httpClient: mockHttpClient, @@ -690,14 +733,22 @@ describe("Interaction logics", () => { it("edits 'folder' when filter specified'", async () => { // Arrange - const state = mergeState(initialState, {}); + const state = mergeState(initialState, { + metadata: { + annotations: mockAnnotations, + }, + }); const { store, logicMiddleware, actions } = configureMockStore({ state, logics: interactionLogics, }); // Act - store.dispatch(editFiles({ "Cell Line": ["AICS-12"] }, [])); + store.dispatch( + editFiles({ "Cell Line": ["AICS-12"] }, [ + new FileFilter(AnnotationName.KIND, "PNG"), + ]) + ); await logicMiddleware.whenComplete(); // Assert @@ -725,12 +776,15 @@ describe("Interaction logics", () => { ).to.be.false; }); - it("edits selected files when no filter sepecified", async () => { + it("edits selected files when no filter specified", async () => { // Arrange const state = mergeState(initialState, { selection: { fileSelection: fakeSelection, }, + metadata: { + annotations: mockAnnotations, + }, }); const { store, logicMiddleware, actions } = configureMockStore({ state, @@ -779,7 +833,8 @@ describe("Interaction logics", () => { }); // Act - store.dispatch(editFiles({ "Cell Line": ["AICS-12"] })); + // Try to edit an annotation we don't recognize + store.dispatch(editFiles({ "Nonexistent Annotation": ["AICS-12"] })); await logicMiddleware.whenComplete(); // Assert From 64d8bada87f5c4ce2dee0348d5c02c255e4429c9 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Fri, 4 Oct 2024 11:08:11 -0700 Subject: [PATCH 09/33] Start work on MMS endpoint --- .../MetadataManagementService/index.ts | 54 +++++++++++++++++++ .../test/MetadataManagementService.test.ts | 34 ++++++++++++ packages/desktop/src/util/constants.ts | 6 +++ 3 files changed, 94 insertions(+) create mode 100644 packages/core/services/MetadataManagementService/index.ts create mode 100644 packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts diff --git a/packages/core/services/MetadataManagementService/index.ts b/packages/core/services/MetadataManagementService/index.ts new file mode 100644 index 000000000..79d56e030 --- /dev/null +++ b/packages/core/services/MetadataManagementService/index.ts @@ -0,0 +1,54 @@ +import { MetadataManagementServiceBaseUrl } from "../../../desktop/src/util/constants"; +import HttpServiceBase, { ConnectionConfig } from "../HttpServiceBase"; + +interface Config extends ConnectionConfig { + baseUrl: string | keyof typeof MetadataManagementServiceBaseUrl; +} + +// Interfaces borrowed from aics-file-upload-app +// Used for the POST request to MMS for creating file metadata +export interface MMSFileAnnotation { + annotationId: number; + values: string[]; +} + +export interface MMSFile { + fileId: string; + annotations?: MMSFileAnnotation[]; + templateId?: number; +} + +export interface MMSUploadRequest { + customMetadata?: MMSFile; + fileType?: string; + file: FileMetadataBlock; + [id: string]: any; +} + +interface FileMetadataBlock { + originalPath: string; + fileName?: string; + fileType: string; + jobId?: string; + [id: string]: any; +} + +export default class MetadataManagementService extends HttpServiceBase { + private static readonly FILEMETADATA_ENDPOINT_VERSION = "1.0"; + + constructor(config: Config) { + super(config); + } + + public async getFileMetadata(fileId: string): Promise { + const url = `${this.baseUrl}/${MetadataManagementService.FILEMETADATA_ENDPOINT_VERSION}/filemetadata/${fileId}`; + const response = await this.get(url); + return response.data[0]; + } + + public async editFileMetadata(fileId: string, request: MMSUploadRequest): Promise { + const url = `${this.baseUrl}/${MetadataManagementService.FILEMETADATA_ENDPOINT_VERSION}/filemetadata/${fileId}`; + const requestBody = JSON.stringify(request); + await this.put(url, requestBody); + } +} diff --git a/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts b/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts new file mode 100644 index 000000000..7b23cc4df --- /dev/null +++ b/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts @@ -0,0 +1,34 @@ +import { createMockHttpClient } from "@aics/redux-utils"; +import { expect } from "chai"; + +import MetadataManagementService from ".."; + +describe("MetadataManagementService", () => { + const baseUrl = "test"; + const fileIds = ["abc123", "def456", "ghi789", "jkl012"]; + const files = fileIds.map((fileId) => ({ + fileId, + })); + + describe("getFiles", () => { + const httpClient = createMockHttpClient([ + { + when: () => true, + respondWith: { + data: { + data: files.slice(0, 1), + }, + }, + }, + ]); + + it("issues request for files that match given parameters", async () => { + const mmsService = new MetadataManagementService({ + baseUrl, + httpClient, + }); + const response = await mmsService.getFileMetadata(files[0]["fileId"]); + expect(response.fileId).to.equal(files[0]["fileId"]); + }); + }); +}); diff --git a/packages/desktop/src/util/constants.ts b/packages/desktop/src/util/constants.ts index 2b950921b..c4c95ed0c 100644 --- a/packages/desktop/src/util/constants.ts +++ b/packages/desktop/src/util/constants.ts @@ -15,6 +15,12 @@ export enum FileExplorerServiceBaseUrl { PRODUCTION = "https://production.int.allencell.org", } +export enum MetadataManagementServiceBaseUrl { + LOCALHOST = "http://localhost:9060/metadata-management-service", + STAGING = "http://stg-aics-api/metadata-management-service", + PRODUCTION = "http://aics-api/metadata-management-service", +} + // Channels global variables can be modified on / listen to export enum GlobalVariableChannels { BaseUrl = "data-source-base-url", From 47cbad78d179ecdfd831dc792683376c1a6c4934 Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Fri, 4 Oct 2024 14:57:03 -0700 Subject: [PATCH 10/33] Add edit files logic and support in fileservices --- .../core/services/FileService/HttpFileService/index.ts | 4 +++- packages/core/state/metadata/logics.ts | 8 ++++++++ packages/desktop/src/util/constants.ts | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index 7c3aecc4a..9005170ea 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -1,4 +1,4 @@ -import { compact, join, uniqueId } from "lodash"; +import { chunk, compact, invert, join, uniqueId } from "lodash"; import FileService, { GetFilesRequest, @@ -47,10 +47,12 @@ export default class HttpFileService extends HttpServiceBase implements FileServ public static readonly BASE_FILES_URL = `file-explorer-service/${HttpFileService.ENDPOINT_VERSION}/files`; public static readonly BASE_FILE_COUNT_URL = `${HttpFileService.BASE_FILES_URL}/count`; public static readonly BASE_EDIT_FILES_URL = `metadata-management-service/1.0/filemetadata`; + public static readonly BASE_ANNOTATION_ID_URL = `metadata-management-service/1.0/annotation`; public static readonly SELECTION_AGGREGATE_URL = `${HttpFileService.BASE_FILES_URL}/selection/aggregate`; private static readonly CSV_ENDPOINT_VERSION = "2.0"; public static readonly BASE_CSV_DOWNLOAD_URL = `file-explorer-service/${HttpFileService.CSV_ENDPOINT_VERSION}/files/selection/manifest`; private readonly downloadService: FileDownloadService; + private readonly edittableAnnotationIdToNameCache: { [name: string]: number } = {}; constructor(config: Config = { downloadService: new FileDownloadServiceNoop() }) { super(config); diff --git a/packages/core/state/metadata/logics.ts b/packages/core/state/metadata/logics.ts index b4c0bb1ba..ebdeb612d 100644 --- a/packages/core/state/metadata/logics.ts +++ b/packages/core/state/metadata/logics.ts @@ -17,6 +17,7 @@ import * as metadataSelectors from "./selectors"; import AnnotationName from "../../entity/Annotation/AnnotationName"; import FileSort, { SortOrder } from "../../entity/FileSort"; import HttpAnnotationService from "../../services/AnnotationService/HttpAnnotationService"; +import HttpFileService from "../../services/FileService/HttpFileService"; /** * Interceptor responsible for turning REQUEST_ANNOTATIONS action into a network call for available annotations. Outputs @@ -26,7 +27,9 @@ const requestAnnotations = createLogic({ async process(deps: ReduxLogicDeps, dispatch, done) { const { getState, httpClient } = deps; const annotationService = interaction.selectors.getAnnotationService(getState()); + const fileService = interaction.selectors.getFileService(getState()); const applicationVersion = interaction.selectors.getApplicationVersion(getState()); + const isAicsEmployee = interaction.selectors.isAicsEmployee(getState()); if (annotationService instanceof HttpAnnotationService) { if (applicationVersion) { annotationService.setApplicationVersion(applicationVersion); @@ -37,6 +40,11 @@ const requestAnnotations = createLogic({ try { const annotations = await annotationService.fetchAnnotations(); dispatch(receiveAnnotations(annotations)); + + if (isAicsEmployee && fileService instanceof HttpFileService) { + const annotationNames = annotations.map((annotation) => annotation.name); + await fileService.prepareAnnotationIdCache(annotationNames); + } } catch (err) { console.error("Failed to fetch annotations", err); } finally { diff --git a/packages/desktop/src/util/constants.ts b/packages/desktop/src/util/constants.ts index c4c95ed0c..3fa335854 100644 --- a/packages/desktop/src/util/constants.ts +++ b/packages/desktop/src/util/constants.ts @@ -15,6 +15,7 @@ export enum FileExplorerServiceBaseUrl { PRODUCTION = "https://production.int.allencell.org", } +// TODO: Do we need this? export enum MetadataManagementServiceBaseUrl { LOCALHOST = "http://localhost:9060/metadata-management-service", STAGING = "http://stg-aics-api/metadata-management-service", From d773efb3ce9b6a49491ba2d263b27253cd5ff00c Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Mon, 7 Oct 2024 14:44:07 -0700 Subject: [PATCH 11/33] Update to use FES stored annotationID --- .../core/services/FileService/HttpFileService/index.ts | 2 +- packages/core/state/metadata/logics.ts | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index 9005170ea..c0023d3e7 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -1,4 +1,4 @@ -import { chunk, compact, invert, join, uniqueId } from "lodash"; +import { compact, join, uniqueId } from "lodash"; import FileService, { GetFilesRequest, diff --git a/packages/core/state/metadata/logics.ts b/packages/core/state/metadata/logics.ts index ebdeb612d..b4c0bb1ba 100644 --- a/packages/core/state/metadata/logics.ts +++ b/packages/core/state/metadata/logics.ts @@ -17,7 +17,6 @@ import * as metadataSelectors from "./selectors"; import AnnotationName from "../../entity/Annotation/AnnotationName"; import FileSort, { SortOrder } from "../../entity/FileSort"; import HttpAnnotationService from "../../services/AnnotationService/HttpAnnotationService"; -import HttpFileService from "../../services/FileService/HttpFileService"; /** * Interceptor responsible for turning REQUEST_ANNOTATIONS action into a network call for available annotations. Outputs @@ -27,9 +26,7 @@ const requestAnnotations = createLogic({ async process(deps: ReduxLogicDeps, dispatch, done) { const { getState, httpClient } = deps; const annotationService = interaction.selectors.getAnnotationService(getState()); - const fileService = interaction.selectors.getFileService(getState()); const applicationVersion = interaction.selectors.getApplicationVersion(getState()); - const isAicsEmployee = interaction.selectors.isAicsEmployee(getState()); if (annotationService instanceof HttpAnnotationService) { if (applicationVersion) { annotationService.setApplicationVersion(applicationVersion); @@ -40,11 +37,6 @@ const requestAnnotations = createLogic({ try { const annotations = await annotationService.fetchAnnotations(); dispatch(receiveAnnotations(annotations)); - - if (isAicsEmployee && fileService instanceof HttpFileService) { - const annotationNames = annotations.map((annotation) => annotation.name); - await fileService.prepareAnnotationIdCache(annotationNames); - } } catch (err) { console.error("Failed to fetch annotations", err); } finally { From d249446f568602a13f6527428d2a956e9b66c7ac Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Mon, 7 Oct 2024 14:46:43 -0700 Subject: [PATCH 12/33] Remove MMS --- .../MetadataManagementService/index.ts | 54 ------------------- .../test/MetadataManagementService.test.ts | 34 ------------ 2 files changed, 88 deletions(-) delete mode 100644 packages/core/services/MetadataManagementService/index.ts delete mode 100644 packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts diff --git a/packages/core/services/MetadataManagementService/index.ts b/packages/core/services/MetadataManagementService/index.ts deleted file mode 100644 index 79d56e030..000000000 --- a/packages/core/services/MetadataManagementService/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { MetadataManagementServiceBaseUrl } from "../../../desktop/src/util/constants"; -import HttpServiceBase, { ConnectionConfig } from "../HttpServiceBase"; - -interface Config extends ConnectionConfig { - baseUrl: string | keyof typeof MetadataManagementServiceBaseUrl; -} - -// Interfaces borrowed from aics-file-upload-app -// Used for the POST request to MMS for creating file metadata -export interface MMSFileAnnotation { - annotationId: number; - values: string[]; -} - -export interface MMSFile { - fileId: string; - annotations?: MMSFileAnnotation[]; - templateId?: number; -} - -export interface MMSUploadRequest { - customMetadata?: MMSFile; - fileType?: string; - file: FileMetadataBlock; - [id: string]: any; -} - -interface FileMetadataBlock { - originalPath: string; - fileName?: string; - fileType: string; - jobId?: string; - [id: string]: any; -} - -export default class MetadataManagementService extends HttpServiceBase { - private static readonly FILEMETADATA_ENDPOINT_VERSION = "1.0"; - - constructor(config: Config) { - super(config); - } - - public async getFileMetadata(fileId: string): Promise { - const url = `${this.baseUrl}/${MetadataManagementService.FILEMETADATA_ENDPOINT_VERSION}/filemetadata/${fileId}`; - const response = await this.get(url); - return response.data[0]; - } - - public async editFileMetadata(fileId: string, request: MMSUploadRequest): Promise { - const url = `${this.baseUrl}/${MetadataManagementService.FILEMETADATA_ENDPOINT_VERSION}/filemetadata/${fileId}`; - const requestBody = JSON.stringify(request); - await this.put(url, requestBody); - } -} diff --git a/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts b/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts deleted file mode 100644 index 7b23cc4df..000000000 --- a/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createMockHttpClient } from "@aics/redux-utils"; -import { expect } from "chai"; - -import MetadataManagementService from ".."; - -describe("MetadataManagementService", () => { - const baseUrl = "test"; - const fileIds = ["abc123", "def456", "ghi789", "jkl012"]; - const files = fileIds.map((fileId) => ({ - fileId, - })); - - describe("getFiles", () => { - const httpClient = createMockHttpClient([ - { - when: () => true, - respondWith: { - data: { - data: files.slice(0, 1), - }, - }, - }, - ]); - - it("issues request for files that match given parameters", async () => { - const mmsService = new MetadataManagementService({ - baseUrl, - httpClient, - }); - const response = await mmsService.getFileMetadata(files[0]["fileId"]); - expect(response.fileId).to.equal(files[0]["fileId"]); - }); - }); -}); From 90dcd75eee8ab1704aabeea1f553fcc65e20b3bd Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Mon, 7 Oct 2024 15:19:35 -0700 Subject: [PATCH 13/33] Map FES base URL to MMS base url --- .../core/services/FileService/HttpFileService/index.ts | 1 - packages/desktop/src/util/constants.ts | 7 ------- 2 files changed, 8 deletions(-) diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index c0023d3e7..e35607f9f 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -47,7 +47,6 @@ export default class HttpFileService extends HttpServiceBase implements FileServ public static readonly BASE_FILES_URL = `file-explorer-service/${HttpFileService.ENDPOINT_VERSION}/files`; public static readonly BASE_FILE_COUNT_URL = `${HttpFileService.BASE_FILES_URL}/count`; public static readonly BASE_EDIT_FILES_URL = `metadata-management-service/1.0/filemetadata`; - public static readonly BASE_ANNOTATION_ID_URL = `metadata-management-service/1.0/annotation`; public static readonly SELECTION_AGGREGATE_URL = `${HttpFileService.BASE_FILES_URL}/selection/aggregate`; private static readonly CSV_ENDPOINT_VERSION = "2.0"; public static readonly BASE_CSV_DOWNLOAD_URL = `file-explorer-service/${HttpFileService.CSV_ENDPOINT_VERSION}/files/selection/manifest`; diff --git a/packages/desktop/src/util/constants.ts b/packages/desktop/src/util/constants.ts index 3fa335854..2b950921b 100644 --- a/packages/desktop/src/util/constants.ts +++ b/packages/desktop/src/util/constants.ts @@ -15,13 +15,6 @@ export enum FileExplorerServiceBaseUrl { PRODUCTION = "https://production.int.allencell.org", } -// TODO: Do we need this? -export enum MetadataManagementServiceBaseUrl { - LOCALHOST = "http://localhost:9060/metadata-management-service", - STAGING = "http://stg-aics-api/metadata-management-service", - PRODUCTION = "http://aics-api/metadata-management-service", -} - // Channels global variables can be modified on / listen to export enum GlobalVariableChannels { BaseUrl = "data-source-base-url", From a528766254638f57fe102c5d60e65782373c9d05 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Tue, 15 Oct 2024 16:33:00 -0700 Subject: [PATCH 14/33] Change minor spelling --- packages/core/services/FileService/HttpFileService/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index e35607f9f..7c3aecc4a 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -51,7 +51,6 @@ export default class HttpFileService extends HttpServiceBase implements FileServ private static readonly CSV_ENDPOINT_VERSION = "2.0"; public static readonly BASE_CSV_DOWNLOAD_URL = `file-explorer-service/${HttpFileService.CSV_ENDPOINT_VERSION}/files/selection/manifest`; private readonly downloadService: FileDownloadService; - private readonly edittableAnnotationIdToNameCache: { [name: string]: number } = {}; constructor(config: Config = { downloadService: new FileDownloadServiceNoop() }) { super(config); From 18ce0822bb0d72b7932e46bdaf66c7f8df75ced3 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Fri, 4 Oct 2024 11:08:11 -0700 Subject: [PATCH 15/33] Start work on MMS endpoint --- packages/core/entity/Annotation/index.ts | 1 + .../MetadataManagementService/index.ts | 54 +++++++++++++++++++ .../test/MetadataManagementService.test.ts | 34 ++++++++++++ packages/desktop/src/util/constants.ts | 6 +++ 4 files changed, 95 insertions(+) create mode 100644 packages/core/services/MetadataManagementService/index.ts create mode 100644 packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts diff --git a/packages/core/entity/Annotation/index.ts b/packages/core/entity/Annotation/index.ts index ba5839aa6..d925f0326 100644 --- a/packages/core/entity/Annotation/index.ts +++ b/packages/core/entity/Annotation/index.ts @@ -17,6 +17,7 @@ export interface AnnotationResponse { type: string; isOpenFileLink?: boolean; units?: string; + annotationId?: string; } /** diff --git a/packages/core/services/MetadataManagementService/index.ts b/packages/core/services/MetadataManagementService/index.ts new file mode 100644 index 000000000..79d56e030 --- /dev/null +++ b/packages/core/services/MetadataManagementService/index.ts @@ -0,0 +1,54 @@ +import { MetadataManagementServiceBaseUrl } from "../../../desktop/src/util/constants"; +import HttpServiceBase, { ConnectionConfig } from "../HttpServiceBase"; + +interface Config extends ConnectionConfig { + baseUrl: string | keyof typeof MetadataManagementServiceBaseUrl; +} + +// Interfaces borrowed from aics-file-upload-app +// Used for the POST request to MMS for creating file metadata +export interface MMSFileAnnotation { + annotationId: number; + values: string[]; +} + +export interface MMSFile { + fileId: string; + annotations?: MMSFileAnnotation[]; + templateId?: number; +} + +export interface MMSUploadRequest { + customMetadata?: MMSFile; + fileType?: string; + file: FileMetadataBlock; + [id: string]: any; +} + +interface FileMetadataBlock { + originalPath: string; + fileName?: string; + fileType: string; + jobId?: string; + [id: string]: any; +} + +export default class MetadataManagementService extends HttpServiceBase { + private static readonly FILEMETADATA_ENDPOINT_VERSION = "1.0"; + + constructor(config: Config) { + super(config); + } + + public async getFileMetadata(fileId: string): Promise { + const url = `${this.baseUrl}/${MetadataManagementService.FILEMETADATA_ENDPOINT_VERSION}/filemetadata/${fileId}`; + const response = await this.get(url); + return response.data[0]; + } + + public async editFileMetadata(fileId: string, request: MMSUploadRequest): Promise { + const url = `${this.baseUrl}/${MetadataManagementService.FILEMETADATA_ENDPOINT_VERSION}/filemetadata/${fileId}`; + const requestBody = JSON.stringify(request); + await this.put(url, requestBody); + } +} diff --git a/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts b/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts new file mode 100644 index 000000000..7b23cc4df --- /dev/null +++ b/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts @@ -0,0 +1,34 @@ +import { createMockHttpClient } from "@aics/redux-utils"; +import { expect } from "chai"; + +import MetadataManagementService from ".."; + +describe("MetadataManagementService", () => { + const baseUrl = "test"; + const fileIds = ["abc123", "def456", "ghi789", "jkl012"]; + const files = fileIds.map((fileId) => ({ + fileId, + })); + + describe("getFiles", () => { + const httpClient = createMockHttpClient([ + { + when: () => true, + respondWith: { + data: { + data: files.slice(0, 1), + }, + }, + }, + ]); + + it("issues request for files that match given parameters", async () => { + const mmsService = new MetadataManagementService({ + baseUrl, + httpClient, + }); + const response = await mmsService.getFileMetadata(files[0]["fileId"]); + expect(response.fileId).to.equal(files[0]["fileId"]); + }); + }); +}); diff --git a/packages/desktop/src/util/constants.ts b/packages/desktop/src/util/constants.ts index 2b950921b..c4c95ed0c 100644 --- a/packages/desktop/src/util/constants.ts +++ b/packages/desktop/src/util/constants.ts @@ -15,6 +15,12 @@ export enum FileExplorerServiceBaseUrl { PRODUCTION = "https://production.int.allencell.org", } +export enum MetadataManagementServiceBaseUrl { + LOCALHOST = "http://localhost:9060/metadata-management-service", + STAGING = "http://stg-aics-api/metadata-management-service", + PRODUCTION = "http://aics-api/metadata-management-service", +} + // Channels global variables can be modified on / listen to export enum GlobalVariableChannels { BaseUrl = "data-source-base-url", From 97d671002d78f6053efed0abacd7f0fffdd48643 Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Fri, 4 Oct 2024 14:57:03 -0700 Subject: [PATCH 16/33] Add edit files logic and support in fileservices --- packages/core/entity/Annotation/index.ts | 1 - .../core/services/FileService/HttpFileService/index.ts | 3 ++- packages/core/state/metadata/logics.ts | 8 ++++++++ packages/desktop/src/util/constants.ts | 1 + 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/core/entity/Annotation/index.ts b/packages/core/entity/Annotation/index.ts index d925f0326..ba5839aa6 100644 --- a/packages/core/entity/Annotation/index.ts +++ b/packages/core/entity/Annotation/index.ts @@ -17,7 +17,6 @@ export interface AnnotationResponse { type: string; isOpenFileLink?: boolean; units?: string; - annotationId?: string; } /** diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index 7c3aecc4a..21027680e 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -1,4 +1,4 @@ -import { compact, join, uniqueId } from "lodash"; +import { chunk, compact, invert, join, uniqueId } from "lodash"; import FileService, { GetFilesRequest, @@ -51,6 +51,7 @@ export default class HttpFileService extends HttpServiceBase implements FileServ private static readonly CSV_ENDPOINT_VERSION = "2.0"; public static readonly BASE_CSV_DOWNLOAD_URL = `file-explorer-service/${HttpFileService.CSV_ENDPOINT_VERSION}/files/selection/manifest`; private readonly downloadService: FileDownloadService; + private readonly edittableAnnotationIdToNameCache: { [name: string]: number } = {}; constructor(config: Config = { downloadService: new FileDownloadServiceNoop() }) { super(config); diff --git a/packages/core/state/metadata/logics.ts b/packages/core/state/metadata/logics.ts index b4c0bb1ba..ebdeb612d 100644 --- a/packages/core/state/metadata/logics.ts +++ b/packages/core/state/metadata/logics.ts @@ -17,6 +17,7 @@ import * as metadataSelectors from "./selectors"; import AnnotationName from "../../entity/Annotation/AnnotationName"; import FileSort, { SortOrder } from "../../entity/FileSort"; import HttpAnnotationService from "../../services/AnnotationService/HttpAnnotationService"; +import HttpFileService from "../../services/FileService/HttpFileService"; /** * Interceptor responsible for turning REQUEST_ANNOTATIONS action into a network call for available annotations. Outputs @@ -26,7 +27,9 @@ const requestAnnotations = createLogic({ async process(deps: ReduxLogicDeps, dispatch, done) { const { getState, httpClient } = deps; const annotationService = interaction.selectors.getAnnotationService(getState()); + const fileService = interaction.selectors.getFileService(getState()); const applicationVersion = interaction.selectors.getApplicationVersion(getState()); + const isAicsEmployee = interaction.selectors.isAicsEmployee(getState()); if (annotationService instanceof HttpAnnotationService) { if (applicationVersion) { annotationService.setApplicationVersion(applicationVersion); @@ -37,6 +40,11 @@ const requestAnnotations = createLogic({ try { const annotations = await annotationService.fetchAnnotations(); dispatch(receiveAnnotations(annotations)); + + if (isAicsEmployee && fileService instanceof HttpFileService) { + const annotationNames = annotations.map((annotation) => annotation.name); + await fileService.prepareAnnotationIdCache(annotationNames); + } } catch (err) { console.error("Failed to fetch annotations", err); } finally { diff --git a/packages/desktop/src/util/constants.ts b/packages/desktop/src/util/constants.ts index c4c95ed0c..3fa335854 100644 --- a/packages/desktop/src/util/constants.ts +++ b/packages/desktop/src/util/constants.ts @@ -15,6 +15,7 @@ export enum FileExplorerServiceBaseUrl { PRODUCTION = "https://production.int.allencell.org", } +// TODO: Do we need this? export enum MetadataManagementServiceBaseUrl { LOCALHOST = "http://localhost:9060/metadata-management-service", STAGING = "http://stg-aics-api/metadata-management-service", From 4bb3c3352c8d15aee2f7b1f5bdc6361d9d16a37e Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Mon, 7 Oct 2024 14:44:07 -0700 Subject: [PATCH 17/33] Update to use FES stored annotationID --- .../core/services/FileService/HttpFileService/index.ts | 2 +- packages/core/state/metadata/logics.ts | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index 21027680e..e35607f9f 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -1,4 +1,4 @@ -import { chunk, compact, invert, join, uniqueId } from "lodash"; +import { compact, join, uniqueId } from "lodash"; import FileService, { GetFilesRequest, diff --git a/packages/core/state/metadata/logics.ts b/packages/core/state/metadata/logics.ts index ebdeb612d..b4c0bb1ba 100644 --- a/packages/core/state/metadata/logics.ts +++ b/packages/core/state/metadata/logics.ts @@ -17,7 +17,6 @@ import * as metadataSelectors from "./selectors"; import AnnotationName from "../../entity/Annotation/AnnotationName"; import FileSort, { SortOrder } from "../../entity/FileSort"; import HttpAnnotationService from "../../services/AnnotationService/HttpAnnotationService"; -import HttpFileService from "../../services/FileService/HttpFileService"; /** * Interceptor responsible for turning REQUEST_ANNOTATIONS action into a network call for available annotations. Outputs @@ -27,9 +26,7 @@ const requestAnnotations = createLogic({ async process(deps: ReduxLogicDeps, dispatch, done) { const { getState, httpClient } = deps; const annotationService = interaction.selectors.getAnnotationService(getState()); - const fileService = interaction.selectors.getFileService(getState()); const applicationVersion = interaction.selectors.getApplicationVersion(getState()); - const isAicsEmployee = interaction.selectors.isAicsEmployee(getState()); if (annotationService instanceof HttpAnnotationService) { if (applicationVersion) { annotationService.setApplicationVersion(applicationVersion); @@ -40,11 +37,6 @@ const requestAnnotations = createLogic({ try { const annotations = await annotationService.fetchAnnotations(); dispatch(receiveAnnotations(annotations)); - - if (isAicsEmployee && fileService instanceof HttpFileService) { - const annotationNames = annotations.map((annotation) => annotation.name); - await fileService.prepareAnnotationIdCache(annotationNames); - } } catch (err) { console.error("Failed to fetch annotations", err); } finally { From 6f66c590398921cd3a714767b6942fa844a73d33 Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Mon, 7 Oct 2024 14:46:43 -0700 Subject: [PATCH 18/33] Remove MMS --- .../MetadataManagementService/index.ts | 54 ------------------- .../test/MetadataManagementService.test.ts | 34 ------------ 2 files changed, 88 deletions(-) delete mode 100644 packages/core/services/MetadataManagementService/index.ts delete mode 100644 packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts diff --git a/packages/core/services/MetadataManagementService/index.ts b/packages/core/services/MetadataManagementService/index.ts deleted file mode 100644 index 79d56e030..000000000 --- a/packages/core/services/MetadataManagementService/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { MetadataManagementServiceBaseUrl } from "../../../desktop/src/util/constants"; -import HttpServiceBase, { ConnectionConfig } from "../HttpServiceBase"; - -interface Config extends ConnectionConfig { - baseUrl: string | keyof typeof MetadataManagementServiceBaseUrl; -} - -// Interfaces borrowed from aics-file-upload-app -// Used for the POST request to MMS for creating file metadata -export interface MMSFileAnnotation { - annotationId: number; - values: string[]; -} - -export interface MMSFile { - fileId: string; - annotations?: MMSFileAnnotation[]; - templateId?: number; -} - -export interface MMSUploadRequest { - customMetadata?: MMSFile; - fileType?: string; - file: FileMetadataBlock; - [id: string]: any; -} - -interface FileMetadataBlock { - originalPath: string; - fileName?: string; - fileType: string; - jobId?: string; - [id: string]: any; -} - -export default class MetadataManagementService extends HttpServiceBase { - private static readonly FILEMETADATA_ENDPOINT_VERSION = "1.0"; - - constructor(config: Config) { - super(config); - } - - public async getFileMetadata(fileId: string): Promise { - const url = `${this.baseUrl}/${MetadataManagementService.FILEMETADATA_ENDPOINT_VERSION}/filemetadata/${fileId}`; - const response = await this.get(url); - return response.data[0]; - } - - public async editFileMetadata(fileId: string, request: MMSUploadRequest): Promise { - const url = `${this.baseUrl}/${MetadataManagementService.FILEMETADATA_ENDPOINT_VERSION}/filemetadata/${fileId}`; - const requestBody = JSON.stringify(request); - await this.put(url, requestBody); - } -} diff --git a/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts b/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts deleted file mode 100644 index 7b23cc4df..000000000 --- a/packages/core/services/MetadataManagementService/test/MetadataManagementService.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createMockHttpClient } from "@aics/redux-utils"; -import { expect } from "chai"; - -import MetadataManagementService from ".."; - -describe("MetadataManagementService", () => { - const baseUrl = "test"; - const fileIds = ["abc123", "def456", "ghi789", "jkl012"]; - const files = fileIds.map((fileId) => ({ - fileId, - })); - - describe("getFiles", () => { - const httpClient = createMockHttpClient([ - { - when: () => true, - respondWith: { - data: { - data: files.slice(0, 1), - }, - }, - }, - ]); - - it("issues request for files that match given parameters", async () => { - const mmsService = new MetadataManagementService({ - baseUrl, - httpClient, - }); - const response = await mmsService.getFileMetadata(files[0]["fileId"]); - expect(response.fileId).to.equal(files[0]["fileId"]); - }); - }); -}); From 85ecfe226858bca1f5a46af1828e5e00fbae7e89 Mon Sep 17 00:00:00 2001 From: SeanLeRoy Date: Mon, 7 Oct 2024 15:19:35 -0700 Subject: [PATCH 19/33] Map FES base URL to MMS base url --- packages/desktop/src/util/constants.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/desktop/src/util/constants.ts b/packages/desktop/src/util/constants.ts index 3fa335854..2b950921b 100644 --- a/packages/desktop/src/util/constants.ts +++ b/packages/desktop/src/util/constants.ts @@ -15,13 +15,6 @@ export enum FileExplorerServiceBaseUrl { PRODUCTION = "https://production.int.allencell.org", } -// TODO: Do we need this? -export enum MetadataManagementServiceBaseUrl { - LOCALHOST = "http://localhost:9060/metadata-management-service", - STAGING = "http://stg-aics-api/metadata-management-service", - PRODUCTION = "http://aics-api/metadata-management-service", -} - // Channels global variables can be modified on / listen to export enum GlobalVariableChannels { BaseUrl = "data-source-base-url", From 55649ba4ae2d002ba25f35348b31f81e49318d9f Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Tue, 15 Oct 2024 16:33:00 -0700 Subject: [PATCH 20/33] Change minor spelling --- packages/core/services/FileService/HttpFileService/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index e35607f9f..7c3aecc4a 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -51,7 +51,6 @@ export default class HttpFileService extends HttpServiceBase implements FileServ private static readonly CSV_ENDPOINT_VERSION = "2.0"; public static readonly BASE_CSV_DOWNLOAD_URL = `file-explorer-service/${HttpFileService.CSV_ENDPOINT_VERSION}/files/selection/manifest`; private readonly downloadService: FileDownloadService; - private readonly edittableAnnotationIdToNameCache: { [name: string]: number } = {}; constructor(config: Config = { downloadService: new FileDownloadServiceNoop() }) { super(config); From b0e65440323987b765e11fb421a301a52d512972 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Tue, 10 Dec 2024 14:23:08 -0800 Subject: [PATCH 21/33] temporarily skip out-of-date tests --- .../core/services/FileService/DatabaseFileService/index.ts | 2 +- .../DatabaseFileService/test/DatabaseFileService.test.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/services/FileService/DatabaseFileService/index.ts b/packages/core/services/FileService/DatabaseFileService/index.ts index 1cbe46f76..4f08f9744 100644 --- a/packages/core/services/FileService/DatabaseFileService/index.ts +++ b/packages/core/services/FileService/DatabaseFileService/index.ts @@ -213,7 +213,7 @@ export default class DatabaseFileService implements FileService { const rows = await this.databaseService.query(sql); return rows - .map((row) => DatabaseFileService.convertDatabaseRowToFileDetail(row, 0)) + .map((row) => DatabaseFileService.convertDatabaseRowToFileDetail(row)) .reduce( (acc, file) => ({ ...acc, diff --git a/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts b/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts index a006a212d..f30dfcfe0 100644 --- a/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts +++ b/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts @@ -66,7 +66,8 @@ describe("DatabaseFileService", () => { }); describe("getEditableFileMetadata", () => { - it("converts response into a map of file_id to metadata", async () => { + // TO DO: Fix tests post rebase + it.skip("converts response into a map of file_id to metadata", async () => { // Arrange const databaseFileService = new DatabaseFileService({ dataSourceNames: ["whatever", "and another"], From 2d4217930e25edccc24bf88e6d0bc36a2ec15a46 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Mon, 16 Dec 2024 17:27:17 -0800 Subject: [PATCH 22/33] add refresh for edits and account for annotations with spaces --- .../ExistingAnnotationPathway.tsx | 7 ++++- .../StatusMessage/StatusMessage.module.css | 1 + .../core/hooks/useFileAccessContextMenu.ts | 30 ++++++++----------- .../FileService/DatabaseFileService/index.ts | 6 ++-- packages/core/state/interaction/logics.ts | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/core/components/EditMetadata/ExistingAnnotationPathway.tsx b/packages/core/components/EditMetadata/ExistingAnnotationPathway.tsx index fe663851a..3aeb579ca 100644 --- a/packages/core/components/EditMetadata/ExistingAnnotationPathway.tsx +++ b/packages/core/components/EditMetadata/ExistingAnnotationPathway.tsx @@ -1,10 +1,12 @@ import { IComboBoxOption } from "@fluentui/react"; import classNames from "classnames"; import * as React from "react"; +import { useDispatch } from "react-redux"; import MetadataDetails, { ValueCountItem } from "./MetadataDetails"; import { PrimaryButton, SecondaryButton } from "../Buttons"; import ComboBox from "../ComboBox"; +import { interaction } from "../../state"; import styles from "./EditMetadata.module.css"; @@ -20,6 +22,7 @@ interface ExistingAnnotationProps { * and then entering values for the selected files */ export default function ExistingAnnotationPathway(props: ExistingAnnotationProps) { + const dispatch = useDispatch(); const [newValues, setNewValues] = React.useState(); const [valueCount, setValueCount] = React.useState(); const [selectedAnnotation, setSelectedAnnotation] = React.useState(); @@ -65,7 +68,9 @@ export default function ExistingAnnotationPathway(props: ExistingAnnotationProps }; function onSubmit() { - // TO DO: endpoint logic is in progress on a different branch + if (selectedAnnotation && newValues) { + dispatch(interaction.actions.editFiles({ [selectedAnnotation]: [newValues] })); + } props.onDismiss(); } diff --git a/packages/core/components/StatusMessage/StatusMessage.module.css b/packages/core/components/StatusMessage/StatusMessage.module.css index 2fb75ed8b..d2b40d644 100644 --- a/packages/core/components/StatusMessage/StatusMessage.module.css +++ b/packages/core/components/StatusMessage/StatusMessage.module.css @@ -4,6 +4,7 @@ width: 40vw; height: 100%; left: 30px; + /* FluentUI modal z-index is set to 1000000 */ z-index: 1000; pointer-events: none; } diff --git a/packages/core/hooks/useFileAccessContextMenu.ts b/packages/core/hooks/useFileAccessContextMenu.ts index 73c72c341..f5d29e322 100644 --- a/packages/core/hooks/useFileAccessContextMenu.ts +++ b/packages/core/hooks/useFileAccessContextMenu.ts @@ -136,24 +136,18 @@ export default (filters?: FileFilter[], onDismiss?: () => void) => { ], }, }, - ...(isQueryingAicsFms - ? [ - { - key: "edit", - text: "Edit metadata", - title: "Edit metadata of selected files", - disabled: !filters && fileSelection.count() === 0, - iconProps: { - iconName: "Edit", - }, - onClick() { - dispatch( - interaction.actions.setVisibleModal(ModalType.EditMetadata) - ); - }, - }, - ] - : []), + { + key: "edit", + text: "Edit metadata", + title: "Edit metadata for selected files", + disabled: !filters && fileSelection.count() === 0, + iconProps: { + iconName: "Edit", + }, + onClick() { + dispatch(interaction.actions.setVisibleModal(ModalType.EditMetadata)); + }, + }, { key: "download", text: "Download", diff --git a/packages/core/services/FileService/DatabaseFileService/index.ts b/packages/core/services/FileService/DatabaseFileService/index.ts index 4f08f9744..f67930213 100644 --- a/packages/core/services/FileService/DatabaseFileService/index.ts +++ b/packages/core/services/FileService/DatabaseFileService/index.ts @@ -193,12 +193,12 @@ export default class DatabaseFileService implements FileService { public editFile(fileId: string, annotations: AnnotationNameToValuesMap): Promise { const tableName = this.dataSourceNames.sort().join(", "); const columnAssignments = Object.entries(annotations).map( - ([name, values]) => `${name} = ${values.join(DatabaseService.LIST_DELIMITER)}` + ([name, values]) => `"${name}" = '${values.join(DatabaseService.LIST_DELIMITER)}'` ); const sql = `\ - UPDATE ${tableName} \ + UPDATE '${tableName}' \ SET ${columnAssignments.join(", ")} \ - WHERE FileId = ${fileId}; \ + WHERE "File ID" = '${fileId}'; \ `; return this.databaseService.execute(sql); } diff --git a/packages/core/state/interaction/logics.ts b/packages/core/state/interaction/logics.ts index f87e06010..17fec97a3 100644 --- a/packages/core/state/interaction/logics.ts +++ b/packages/core/state/interaction/logics.ts @@ -585,7 +585,7 @@ const editFilesLogic = createLogic({ // Await the results of this batch await Promise.all(promises); } - + dispatch(refresh); // Sync state to pull updated files dispatch(processSuccess(editRequestId, "Successfully edited files.")); } catch (err) { // Dispatch an event to alert the user of the failure From 4b8da5e69bde721f6c24abf0f99c1be97366b6b1 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Wed, 18 Dec 2024 14:54:30 -0800 Subject: [PATCH 23/33] update file id references to match UID changes --- .../FileService/DatabaseFileService/index.ts | 6 +++--- .../test/DatabaseFileService.test.ts | 7 ++----- packages/core/state/interaction/logics.ts | 2 +- .../state/interaction/test/logics.test.ts | 20 +++++++++---------- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/packages/core/services/FileService/DatabaseFileService/index.ts b/packages/core/services/FileService/DatabaseFileService/index.ts index f67930213..27011b998 100644 --- a/packages/core/services/FileService/DatabaseFileService/index.ts +++ b/packages/core/services/FileService/DatabaseFileService/index.ts @@ -198,7 +198,7 @@ export default class DatabaseFileService implements FileService { const sql = `\ UPDATE '${tableName}' \ SET ${columnAssignments.join(", ")} \ - WHERE "File ID" = '${fileId}'; \ + WHERE ${DatabaseService.HIDDEN_UID_ANNOTATION} = '${fileId}'; \ `; return this.databaseService.execute(sql); } @@ -208,7 +208,7 @@ export default class DatabaseFileService implements FileService { ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { const sql = new SQLBuilder() .from(this.dataSourceNames) - .where(`"File ID" IN (${fileIds.join(", ")})`) + .where(`${DatabaseService.HIDDEN_UID_ANNOTATION} IN (${fileIds.join(", ")})`) .toSQL(); const rows = await this.databaseService.query(sql); @@ -217,7 +217,7 @@ export default class DatabaseFileService implements FileService { .reduce( (acc, file) => ({ ...acc, - [file.id]: file.annotations.reduce( + [file.uid]: file.annotations.reduce( (annoAcc, annotation) => ({ ...annoAcc, [annotation.name]: annotation.values, diff --git a/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts b/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts index f30dfcfe0..81d1b0c9f 100644 --- a/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts +++ b/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts @@ -66,11 +66,10 @@ describe("DatabaseFileService", () => { }); describe("getEditableFileMetadata", () => { - // TO DO: Fix tests post rebase - it.skip("converts response into a map of file_id to metadata", async () => { + it("converts response into a map of file uids to metadata", async () => { // Arrange const databaseFileService = new DatabaseFileService({ - dataSourceNames: ["whatever", "and another"], + dataSourceNames: ["mock source name", "another mock source name"], databaseService, downloadService: new FileDownloadServiceNoop(), }); @@ -84,14 +83,12 @@ describe("DatabaseFileService", () => { // Assert expect(response).to.deep.equal({ abc123: { - "File ID": ["abc123"], "File Name": ["file"], "File Path": ["path/to/file"], "File Size": ["432226"], num_files: ["6"], }, def456: { - "File ID": ["def456"], "File Name": ["file"], "File Path": ["path/to/file"], "File Size": ["432226"], diff --git a/packages/core/state/interaction/logics.ts b/packages/core/state/interaction/logics.ts index 17fec97a3..affe73f6d 100644 --- a/packages/core/state/interaction/logics.ts +++ b/packages/core/state/interaction/logics.ts @@ -535,7 +535,7 @@ const editFilesLogic = createLogic({ } // Break files into batches of 10 File IDs - const fileIds = filesSelected.map((file) => file.id); + const fileIds = filesSelected.map((file) => file.uid); const batches = chunk(fileIds, 10); // Dispatch an event to alert the user of the start of the process diff --git a/packages/core/state/interaction/test/logics.test.ts b/packages/core/state/interaction/test/logics.test.ts index 055fc3ca8..1703966f8 100644 --- a/packages/core/state/interaction/test/logics.test.ts +++ b/packages/core/state/interaction/test/logics.test.ts @@ -658,16 +658,6 @@ describe("Interaction logics", () => { describe("editFilesLogic", () => { const sandbox = createSandbox(); - before(() => { - sandbox.stub(interaction.selectors, "getFileService").returns(fileService); - }); - afterEach(() => { - sandbox.resetHistory(); - }); - after(() => { - sandbox.restore(); - }); - const files = []; const fileKinds = ["PNG", "TIFF"]; for (let i = 0; i <= 100; i++) { @@ -731,6 +721,16 @@ describe("Interaction logics", () => { sortOrder: 0, }); + before(() => { + sandbox.stub(interaction.selectors, "getFileService").returns(fileService); + }); + afterEach(() => { + sandbox.resetHistory(); + }); + after(() => { + sandbox.restore(); + }); + it("edits 'folder' when filter specified'", async () => { // Arrange const state = mergeState(initialState, { From 80d953618afc8226eefbd1e4dd575682a0a48b34 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Wed, 18 Dec 2024 16:03:12 -0800 Subject: [PATCH 24/33] fix MMS endpoint base urls and request body --- .../core/services/FileService/HttpFileService/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index 7c3aecc4a..02acef160 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -34,9 +34,9 @@ interface EditableFileMetadata { } const FESBaseUrlToMMSBaseUrlMap = { - [FileExplorerServiceBaseUrl.LOCALHOST]: "http://localhost:9060", - [FileExplorerServiceBaseUrl.STAGING]: "http://stg-aics-api", - [FileExplorerServiceBaseUrl.PRODUCTION]: "http://stg-aics-api", + [FileExplorerServiceBaseUrl.LOCALHOST]: "https://localhost:9060", + [FileExplorerServiceBaseUrl.STAGING]: "https://stg-aics.corp.alleninstitute.org", + [FileExplorerServiceBaseUrl.PRODUCTION]: "https://stg-aics.corp.alleninstitute.org", // TO DO: unsure if using stg for prod was intentional }; /** @@ -168,7 +168,7 @@ export default class HttpFileService extends HttpServiceBase implements FileServ } return { annotationId, values }; }); - const requestBody = JSON.stringify({ annotations }); + const requestBody = JSON.stringify({ customMetadata: { annotations } }); await this.put(url, requestBody); } From e885706c8a2a107d4fd473cacbeb720e0c5c5088 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Thu, 19 Dec 2024 13:27:42 -0800 Subject: [PATCH 25/33] remove logic for getting metadata from MMS --- .../FileService/DatabaseFileService/index.ts | 26 ------ .../test/DatabaseFileService.test.ts | 33 ------- .../services/FileService/FileServiceNoop.ts | 6 +- .../FileService/HttpFileService/index.ts | 44 +--------- .../test/HttpFileService.test.ts | 87 ------------------- packages/core/services/FileService/index.ts | 4 - packages/core/state/interaction/logics.ts | 2 - 7 files changed, 2 insertions(+), 200 deletions(-) diff --git a/packages/core/services/FileService/DatabaseFileService/index.ts b/packages/core/services/FileService/DatabaseFileService/index.ts index 27011b998..b2fe55b5b 100644 --- a/packages/core/services/FileService/DatabaseFileService/index.ts +++ b/packages/core/services/FileService/DatabaseFileService/index.ts @@ -202,30 +202,4 @@ export default class DatabaseFileService implements FileService { `; return this.databaseService.execute(sql); } - - public async getEditableFileMetadata( - fileIds: string[] - ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { - const sql = new SQLBuilder() - .from(this.dataSourceNames) - .where(`${DatabaseService.HIDDEN_UID_ANNOTATION} IN (${fileIds.join(", ")})`) - .toSQL(); - - const rows = await this.databaseService.query(sql); - return rows - .map((row) => DatabaseFileService.convertDatabaseRowToFileDetail(row)) - .reduce( - (acc, file) => ({ - ...acc, - [file.uid]: file.annotations.reduce( - (annoAcc, annotation) => ({ - ...annoAcc, - [annotation.name]: annotation.values, - }), - {} as AnnotationNameToValuesMap - ), - }), - {} as { [fileId: string]: AnnotationNameToValuesMap } - ); - } } diff --git a/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts b/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts index 81d1b0c9f..3269add37 100644 --- a/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts +++ b/packages/core/services/FileService/DatabaseFileService/test/DatabaseFileService.test.ts @@ -65,39 +65,6 @@ describe("DatabaseFileService", () => { }); }); - describe("getEditableFileMetadata", () => { - it("converts response into a map of file uids to metadata", async () => { - // Arrange - const databaseFileService = new DatabaseFileService({ - dataSourceNames: ["mock source name", "another mock source name"], - databaseService, - downloadService: new FileDownloadServiceNoop(), - }); - - // Act - const response = await databaseFileService.getEditableFileMetadata([ - "abc123", - "def456", - ]); - - // Assert - expect(response).to.deep.equal({ - abc123: { - "File Name": ["file"], - "File Path": ["path/to/file"], - "File Size": ["432226"], - num_files: ["6"], - }, - def456: { - "File Name": ["file"], - "File Path": ["path/to/file"], - "File Size": ["432226"], - num_files: ["6"], - }, - }); - }); - }); - describe("getAggregateInformation", () => { it("issues request for aggregated information about given files", async () => { // Arrange diff --git a/packages/core/services/FileService/FileServiceNoop.ts b/packages/core/services/FileService/FileServiceNoop.ts index 569bd2d90..bf2684270 100644 --- a/packages/core/services/FileService/FileServiceNoop.ts +++ b/packages/core/services/FileService/FileServiceNoop.ts @@ -1,4 +1,4 @@ -import FileService, { AnnotationNameToValuesMap, SelectionAggregationResult } from "."; +import FileService, { SelectionAggregationResult } from "."; import { DownloadResolution, DownloadResult } from "../FileDownloadService"; import FileDetail from "../../entity/FileDetail"; @@ -11,10 +11,6 @@ export default class FileServiceNoop implements FileService { return Promise.resolve({ count: 0, size: 0 }); } - public getEditableFileMetadata(): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { - return Promise.resolve({}); - } - public getFiles(): Promise { return Promise.resolve([]); } diff --git a/packages/core/services/FileService/HttpFileService/index.ts b/packages/core/services/FileService/HttpFileService/index.ts index 02acef160..f27dc3497 100644 --- a/packages/core/services/FileService/HttpFileService/index.ts +++ b/packages/core/services/FileService/HttpFileService/index.ts @@ -23,20 +23,10 @@ interface Config extends ConnectionConfig { downloadService: FileDownloadService; } -// Used for the GET request to MMS for file metadata -interface EditableFileMetadata { - fileId: string; - annotations?: { - annotationId: number; - values: string[]; - }[]; - templateId?: number; -} - const FESBaseUrlToMMSBaseUrlMap = { [FileExplorerServiceBaseUrl.LOCALHOST]: "https://localhost:9060", [FileExplorerServiceBaseUrl.STAGING]: "https://stg-aics.corp.alleninstitute.org", - [FileExplorerServiceBaseUrl.PRODUCTION]: "https://stg-aics.corp.alleninstitute.org", // TO DO: unsure if using stg for prod was intentional + [FileExplorerServiceBaseUrl.PRODUCTION]: "https://aics.corp.alleninstitute.org", }; /** @@ -171,36 +161,4 @@ export default class HttpFileService extends HttpServiceBase implements FileServ const requestBody = JSON.stringify({ customMetadata: { annotations } }); await this.put(url, requestBody); } - - public async getEditableFileMetadata( - fileIds: string[], - annotationIdToAnnotationMap?: Record - ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }> { - const mmsBaseUrl = FESBaseUrlToMMSBaseUrlMap[this.baseUrl as FileExplorerServiceBaseUrl]; - const url = `${mmsBaseUrl}/${HttpFileService.BASE_EDIT_FILES_URL}/${fileIds.join(",")}`; - const response = await this.get(url); - - // Group files by fileId - return response.data.reduce( - (fileAcc, file) => ({ - ...fileAcc, - // Group annotations by name - [file.fileId]: - file.annotations?.reduce((annoAcc, annotation) => { - const name = annotationIdToAnnotationMap?.[annotation.annotationId]?.name; - if (!name) { - throw new Error( - "Failure mapping editable metadata response. " + - `Failed to find annotation name for annotation id ${annotation.annotationId}` - ); - } - return { - ...annoAcc, - [name]: annotation.values, - }; - }, {} as AnnotationNameToValuesMap) || {}, - }), - {} as { [fileId: string]: AnnotationNameToValuesMap } - ); - } } diff --git a/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts b/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts index 78e28573a..548ef40c8 100644 --- a/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts +++ b/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts @@ -2,7 +2,6 @@ import { createMockHttpClient } from "@aics/redux-utils"; import { expect } from "chai"; import HttpFileService from ".."; -import Annotation from "../../../../entity/Annotation"; import FileSelection from "../../../../entity/FileSelection"; import FileSet from "../../../../entity/FileSet"; import NumericRange from "../../../../entity/NumericRange"; @@ -73,92 +72,6 @@ describe("HttpFileService", () => { }); }); - describe("getEditableFileMetadata", () => { - const colorAnnotation = "Color"; - const colorAnnotationId = 3; - const fileIdWithColorAnnotation = "abc123"; - const fileIdWithoutColorAnnotation = "def456"; - const httpClient = createMockHttpClient([ - { - when: (config) => - !!config.url?.includes(`filemetadata/${fileIdWithColorAnnotation}`), - respondWith: { - data: { - data: [ - { - fileId: "abc123", - annotations: [ - { - annotationId: colorAnnotationId, - values: ["red"], - }, - ], - }, - ], - }, - }, - }, - { - when: (config) => - !!config.url?.includes(`filemetadata/${fileIdWithoutColorAnnotation}`), - respondWith: { - data: { - data: [ - { - fileId: "abc123", - annotations: [ - { - annotationId: 4, - values: ["AICS-0"], - }, - ], - }, - ], - }, - }, - }, - ]); - - it("converts response into a map of file_id to metadata", async () => { - const httpFileService = new HttpFileService({ - baseUrl, - httpClient, - downloadService: new FileDownloadServiceNoop(), - }); - - // Act - const fileMetadata = await httpFileService.getEditableFileMetadata( - [fileIdWithColorAnnotation], - { [colorAnnotationId]: new Annotation({ annotationName: colorAnnotation } as any) } - ); - - // Assert - expect(fileMetadata).to.deep.equal({ - [fileIdWithColorAnnotation]: { - [colorAnnotation]: ["red"], - }, - }); - }); - - it("fails if unable to find id of annotation", async () => { - const httpFileService = new HttpFileService({ - baseUrl, - httpClient, - downloadService: new FileDownloadServiceNoop(), - }); - - // Act / Assert - try { - await httpFileService.getEditableFileMetadata([fileIdWithoutColorAnnotation]); - expect(false, "Expected to throw").to.be.true; - } catch (e) { - expect((e as Error).message).to.equal( - "Failure mapping editable metadata response. Failed to find annotation name for annotation id 4" - ); - } - }); - }); - describe("getAggregateInformation", () => { const totalFileSize = 12424114; const totalFileCount = 7; diff --git a/packages/core/services/FileService/index.ts b/packages/core/services/FileService/index.ts index 2b2b110fd..c95b969d2 100644 --- a/packages/core/services/FileService/index.ts +++ b/packages/core/services/FileService/index.ts @@ -58,9 +58,5 @@ export default interface FileService { ): Promise; getAggregateInformation(fileSelection: FileSelection): Promise; getCountOfMatchingFiles(fileSet: FileSet): Promise; - getEditableFileMetadata( - fileIds: string[], - annotationIdToAnnotationMap?: Record - ): Promise<{ [fileId: string]: AnnotationNameToValuesMap }>; getFiles(request: GetFilesRequest): Promise; } diff --git a/packages/core/state/interaction/logics.ts b/packages/core/state/interaction/logics.ts index affe73f6d..32d340596 100644 --- a/packages/core/state/interaction/logics.ts +++ b/packages/core/state/interaction/logics.ts @@ -541,8 +541,6 @@ const editFilesLogic = createLogic({ // Dispatch an event to alert the user of the start of the process const editRequestId = uniqueId(); const editProcessMsg = "Editing files in progress."; - // TODO: Lyndsay's design did not include a progress bar for editing files - // nor a way to cancel the process. This is a placeholder for now. dispatch(processStart(editRequestId, editProcessMsg, noop)); // Track the total number of files edited From e5114f8f53cbdbf8dabd277119631608140cb428 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Fri, 20 Dec 2024 12:43:20 -0800 Subject: [PATCH 26/33] trim value strings to prevent empty submits --- .../components/EditMetadata/ExistingAnnotationPathway.tsx | 6 +++--- .../core/components/StatusMessage/StatusMessage.module.css | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core/components/EditMetadata/ExistingAnnotationPathway.tsx b/packages/core/components/EditMetadata/ExistingAnnotationPathway.tsx index 3aeb579ca..1391b122b 100644 --- a/packages/core/components/EditMetadata/ExistingAnnotationPathway.tsx +++ b/packages/core/components/EditMetadata/ExistingAnnotationPathway.tsx @@ -68,8 +68,8 @@ export default function ExistingAnnotationPathway(props: ExistingAnnotationProps }; function onSubmit() { - if (selectedAnnotation && newValues) { - dispatch(interaction.actions.editFiles({ [selectedAnnotation]: [newValues] })); + if (selectedAnnotation && newValues?.trim()) { + dispatch(interaction.actions.editFiles({ [selectedAnnotation]: [newValues.trim()] })); } props.onDismiss(); } @@ -96,7 +96,7 @@ export default function ExistingAnnotationPathway(props: ExistingAnnotationProps {valueCount && ( Date: Tue, 7 Jan 2025 09:59:40 -0800 Subject: [PATCH 27/33] fix references to MMS endpoint url --- packages/core/constants/index.ts | 8 ++++---- .../HttpFileService/test/HttpFileService.test.ts | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/core/constants/index.ts b/packages/core/constants/index.ts index ff53eb69d..f3611aaa8 100644 --- a/packages/core/constants/index.ts +++ b/packages/core/constants/index.ts @@ -64,10 +64,10 @@ export enum FESBaseUrl { } export enum MMSBaseUrl { - LOCALHOST = "http://localhost:9060", - STAGING = "http://stg-aics-api", - PRODUCTION = "http://prod-aics-api", - TEST = "http://test-aics-api", + LOCALHOST = "https://localhost:9060", + STAGING = "https://stg-aics.corp.alleninstitute.org", + PRODUCTION = "https://aics.corp.alleninstitute.org", + TEST = "http://test-aics-mms-api.corp.alleninstitute.org", } export enum LoadBalancerBaseUrl { diff --git a/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts b/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts index d285d6ae4..202339f73 100644 --- a/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts +++ b/packages/core/services/FileService/HttpFileService/test/HttpFileService.test.ts @@ -2,7 +2,7 @@ import { createMockHttpClient } from "@aics/redux-utils"; import { expect } from "chai"; import HttpFileService from ".."; -import { FESBaseUrl, LoadBalancerBaseUrl } from "../../../../constants"; +import { FESBaseUrl, LoadBalancerBaseUrl, MMSBaseUrl } from "../../../../constants"; import FileSelection from "../../../../entity/FileSelection"; import FileSet from "../../../../entity/FileSet"; import NumericRange from "../../../../entity/NumericRange"; @@ -11,6 +11,7 @@ import FileDownloadServiceNoop from "../../../FileDownloadService/FileDownloadSe describe("HttpFileService", () => { const fileExplorerServiceBaseUrl = FESBaseUrl.TEST; const loadBalancerBaseUrl = LoadBalancerBaseUrl.TEST; + const metadataManagementServiceBaseURl = MMSBaseUrl.TEST; const fileIds = ["abc123", "def456", "ghi789", "jkl012"]; const files = fileIds.map((file_id) => ({ file_id, @@ -57,7 +58,7 @@ describe("HttpFileService", () => { it("fails if unable to find id of annotation", async () => { // Arrange const httpFileService = new HttpFileService({ - baseUrl, + metadataManagementServiceBaseURl, httpClient, downloadService: new FileDownloadServiceNoop(), }); From 4e91442d66ec913c4c7bc07e19bb1c53300bed2f Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Tue, 7 Jan 2025 11:23:38 -0800 Subject: [PATCH 28/33] fix edit logic test --- packages/core/state/interaction/test/logics.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/state/interaction/test/logics.test.ts b/packages/core/state/interaction/test/logics.test.ts index 6b4b5e697..7087cded5 100644 --- a/packages/core/state/interaction/test/logics.test.ts +++ b/packages/core/state/interaction/test/logics.test.ts @@ -30,7 +30,7 @@ import { } from "../../../services/ExecutionEnvService"; import ExecutionEnvServiceNoop from "../../../services/ExecutionEnvService/ExecutionEnvServiceNoop"; import interactionLogics from "../logics"; -import { FESBaseUrl } from "../../../constants"; +import { FESBaseUrl, MMSBaseUrl } from "../../../constants"; import Annotation from "../../../entity/Annotation"; import AnnotationName from "../../../entity/Annotation/AnnotationName"; import { AnnotationType } from "../../../entity/AnnotationFormatter"; @@ -660,6 +660,7 @@ describe("Interaction logics", () => { const sandbox = createSandbox(); const files = []; const fileKinds = ["PNG", "TIFF"]; + const metadataManagementServiceBaseURl = MMSBaseUrl.TEST; for (let i = 0; i <= 100; i++) { files.push({ file_path: `/allen/file_${i}.ext`, @@ -693,7 +694,6 @@ describe("Interaction logics", () => { }), ]; - const baseUrl = "test"; const responseStubs: ResponseStub[] = [ { when: (config) => @@ -711,7 +711,7 @@ describe("Interaction logics", () => { ]; const mockHttpClient = createMockHttpClient(responseStubs); const fileService = new HttpFileService({ - baseUrl, + metadataManagementServiceBaseURl, httpClient: mockHttpClient, downloadService: new FileDownloadServiceNoop(), }); From fcfa8f993b5146e925401d4f4f57aa4e926fcd9d Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Tue, 7 Jan 2025 14:00:40 -0800 Subject: [PATCH 29/33] try addressing edit test timouts by awaiting all logic branches --- packages/core/state/interaction/test/logics.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/state/interaction/test/logics.test.ts b/packages/core/state/interaction/test/logics.test.ts index 7087cded5..94a99c585 100644 --- a/packages/core/state/interaction/test/logics.test.ts +++ b/packages/core/state/interaction/test/logics.test.ts @@ -8,7 +8,7 @@ import { expect } from "chai"; import { get as _get } from "lodash"; import { createSandbox } from "sinon"; -import { initialState, interaction } from "../.."; +import { initialState, interaction, reduxLogics } from "../.."; import { downloadManifest, ProcessStatus, @@ -740,7 +740,7 @@ describe("Interaction logics", () => { }); const { store, logicMiddleware, actions } = configureMockStore({ state, - logics: interactionLogics, + logics: reduxLogics, }); // Act @@ -788,7 +788,7 @@ describe("Interaction logics", () => { }); const { store, logicMiddleware, actions } = configureMockStore({ state, - logics: interactionLogics, + logics: reduxLogics, }); // Act From 404f5c98a25ac5456560d0caca6e4de464971c37 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Wed, 8 Jan 2025 15:29:03 -0800 Subject: [PATCH 30/33] test modifications to async edit logic --- packages/core/state/interaction/logics.ts | 33 ++++++++++++------- .../state/interaction/test/logics.test.ts | 2 +- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/core/state/interaction/logics.ts b/packages/core/state/interaction/logics.ts index 91c9f6023..f4d83e1d5 100644 --- a/packages/core/state/interaction/logics.ts +++ b/packages/core/state/interaction/logics.ts @@ -567,18 +567,26 @@ const editFilesLogic = createLogic({ const promises = batch.map( (fileId) => new Promise(async (resolve, reject) => { - try { - await fileService.editFile( - fileId, - annotations, - annotationNameToAnnotationMap - ); - totalFileEdited += 1; - onProgress(); - resolve(); - } catch (err) { - reject(err); - } + fileService + .editFile(fileId, annotations, annotationNameToAnnotationMap) + .then((_) => { + totalFileEdited += 1; + onProgress(); + resolve(); + }) + .catch((err) => reject(err)); + // try { + // await fileService.editFile( + // fileId, + // annotations, + // annotationNameToAnnotationMap + // ); + // totalFileEdited += 1; + // onProgress(); + // resolve(); + // } catch (err) { + // reject(err); + // } }) ); @@ -594,6 +602,7 @@ const editFilesLogic = createLogic({ }`; dispatch(processError(editRequestId, errorMsg)); } finally { + console.log("reached finally in editFiles logic"); done(); } }, diff --git a/packages/core/state/interaction/test/logics.test.ts b/packages/core/state/interaction/test/logics.test.ts index 94a99c585..2a7bc5d34 100644 --- a/packages/core/state/interaction/test/logics.test.ts +++ b/packages/core/state/interaction/test/logics.test.ts @@ -794,7 +794,7 @@ describe("Interaction logics", () => { // Act store.dispatch(editFiles({ "Cell Line": ["AICS-12"] })); await logicMiddleware.whenComplete(); - + console.log("finished awaiting"); // Assert expect( actions.includesMatch({ From a1517c5a186f69546d4d40fbf222d1d7587aa3d3 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Wed, 8 Jan 2025 15:38:03 -0800 Subject: [PATCH 31/33] add FES test endpoint to mock state --- packages/core/state/interaction/test/logics.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/state/interaction/test/logics.test.ts b/packages/core/state/interaction/test/logics.test.ts index 2a7bc5d34..9d05238ea 100644 --- a/packages/core/state/interaction/test/logics.test.ts +++ b/packages/core/state/interaction/test/logics.test.ts @@ -656,11 +656,12 @@ describe("Interaction logics", () => { }); }); - describe("editFilesLogic", () => { + describe.only("editFilesLogic", () => { const sandbox = createSandbox(); const files = []; const fileKinds = ["PNG", "TIFF"]; const metadataManagementServiceBaseURl = MMSBaseUrl.TEST; + const fileExplorerServiceBaseUrl = FESBaseUrl.TEST; for (let i = 0; i <= 100; i++) { files.push({ file_path: `/allen/file_${i}.ext`, @@ -712,6 +713,7 @@ describe("Interaction logics", () => { const mockHttpClient = createMockHttpClient(responseStubs); const fileService = new HttpFileService({ metadataManagementServiceBaseURl, + fileExplorerServiceBaseUrl, httpClient: mockHttpClient, downloadService: new FileDownloadServiceNoop(), }); From ec5277b59e2f294d4978fca9327be5c0101f3b90 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Wed, 8 Jan 2025 16:32:47 -0800 Subject: [PATCH 32/33] verify that mocked download service is not necessary --- .../HttpAnnotationService/index.ts | 12 +++++ .../state/interaction/test/logics.test.ts | 44 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/core/services/AnnotationService/HttpAnnotationService/index.ts b/packages/core/services/AnnotationService/HttpAnnotationService/index.ts index 8fbbde906..5a5f59ba3 100644 --- a/packages/core/services/AnnotationService/HttpAnnotationService/index.ts +++ b/packages/core/services/AnnotationService/HttpAnnotationService/index.ts @@ -96,6 +96,12 @@ export default class HttpAnnotationService extends HttpServiceBase implements An .filter((param) => !!param) .join("&"); const requestUrl = `${this.fileExplorerServiceBaseUrl}/${HttpAnnotationService.BASE_ANNOTATION_HIERARCHY_UNDER_PATH_URL}${this.pathSuffix}?${queryParams}`; + console.log( + "fes base url", + this.fileExplorerServiceBaseUrl, + "mms base url", + this.metadataManagementServiceBaseURl + ); const response = await this.get(requestUrl); return response.data; @@ -109,6 +115,12 @@ export default class HttpAnnotationService extends HttpServiceBase implements An const queryParams = this.buildQueryParams(QueryParam.HIERARCHY, [...annotations].sort()); const requestUrl = `${this.fileExplorerServiceBaseUrl}/${HttpAnnotationService.BASE_AVAILABLE_ANNOTATIONS_UNDER_HIERARCHY}${this.pathSuffix}?${queryParams}`; + console.log( + "fes base url", + this.fileExplorerServiceBaseUrl, + "mms base url", + this.metadataManagementServiceBaseURl + ); const response = await this.get(requestUrl); if (!response.data) { return annotations; diff --git a/packages/core/state/interaction/test/logics.test.ts b/packages/core/state/interaction/test/logics.test.ts index 9d05238ea..f442a34fd 100644 --- a/packages/core/state/interaction/test/logics.test.ts +++ b/packages/core/state/interaction/test/logics.test.ts @@ -5,7 +5,7 @@ import { ResponseStub, } from "@aics/redux-utils"; import { expect } from "chai"; -import { get as _get } from "lodash"; +import { get as _get, noop } from "lodash"; import { createSandbox } from "sinon"; import { initialState, interaction, reduxLogics } from "../.."; @@ -711,11 +711,35 @@ describe("Interaction logics", () => { }, ]; const mockHttpClient = createMockHttpClient(responseStubs); + class TestDownloadService extends FileDownloadService { + isFileSystemAccessible = false; + getDefaultDownloadDirectory() { + return Promise.reject(); + } + prepareHttpResourceForDownload() { + return Promise.reject(); + } + download(_fileInfo: FileInfo) { + return Promise.reject(); + } + cancelActiveRequest() { + noop(); + } + } + + const annotationService = new HttpAnnotationService({ + fileExplorerServiceBaseUrl, + httpClient: mockHttpClient, + }); + const downloadService = new TestDownloadService({ + fileExplorerServiceBaseUrl, + metadataManagementServiceBaseURl, + }); const fileService = new HttpFileService({ metadataManagementServiceBaseURl, fileExplorerServiceBaseUrl, httpClient: mockHttpClient, - downloadService: new FileDownloadServiceNoop(), + downloadService, }); const fakeSelection = new FileSelection().select({ fileSet: new FileSet({ fileService }), @@ -725,6 +749,7 @@ describe("Interaction logics", () => { before(() => { sandbox.stub(interaction.selectors, "getFileService").returns(fileService); + sandbox.stub(interaction.selectors, "getAnnotationService").returns(annotationService); }); afterEach(() => { sandbox.resetHistory(); @@ -736,6 +761,11 @@ describe("Interaction logics", () => { it("edits 'folder' when filter specified'", async () => { // Arrange const state = mergeState(initialState, { + interaction: { + platformDependentServices: { + fileDownloadService: downloadService, + }, + }, metadata: { annotations: mockAnnotations, }, @@ -787,6 +817,11 @@ describe("Interaction logics", () => { metadata: { annotations: mockAnnotations, }, + interaction: { + platformDependentServices: { + fileDownloadService: downloadService, + }, + }, }); const { store, logicMiddleware, actions } = configureMockStore({ state, @@ -828,6 +863,11 @@ describe("Interaction logics", () => { selection: { fileSelection: fakeSelection, }, + interaction: { + platformDependentServices: { + fileDownloadService: downloadService, + }, + }, }); const { store, logicMiddleware, actions } = configureMockStore({ state, From 03fd374dbfbe4eb5f2a75453b6feed5b45c85bf4 Mon Sep 17 00:00:00 2001 From: Anya Wallace Date: Wed, 8 Jan 2025 16:38:11 -0800 Subject: [PATCH 33/33] clean up debugging code --- .../AnnotationService/HttpAnnotationService/index.ts | 12 ------------ packages/core/state/interaction/logics.ts | 1 - packages/core/state/interaction/test/logics.test.ts | 3 +-- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/core/services/AnnotationService/HttpAnnotationService/index.ts b/packages/core/services/AnnotationService/HttpAnnotationService/index.ts index 5a5f59ba3..8fbbde906 100644 --- a/packages/core/services/AnnotationService/HttpAnnotationService/index.ts +++ b/packages/core/services/AnnotationService/HttpAnnotationService/index.ts @@ -96,12 +96,6 @@ export default class HttpAnnotationService extends HttpServiceBase implements An .filter((param) => !!param) .join("&"); const requestUrl = `${this.fileExplorerServiceBaseUrl}/${HttpAnnotationService.BASE_ANNOTATION_HIERARCHY_UNDER_PATH_URL}${this.pathSuffix}?${queryParams}`; - console.log( - "fes base url", - this.fileExplorerServiceBaseUrl, - "mms base url", - this.metadataManagementServiceBaseURl - ); const response = await this.get(requestUrl); return response.data; @@ -115,12 +109,6 @@ export default class HttpAnnotationService extends HttpServiceBase implements An const queryParams = this.buildQueryParams(QueryParam.HIERARCHY, [...annotations].sort()); const requestUrl = `${this.fileExplorerServiceBaseUrl}/${HttpAnnotationService.BASE_AVAILABLE_ANNOTATIONS_UNDER_HIERARCHY}${this.pathSuffix}?${queryParams}`; - console.log( - "fes base url", - this.fileExplorerServiceBaseUrl, - "mms base url", - this.metadataManagementServiceBaseURl - ); const response = await this.get(requestUrl); if (!response.data) { return annotations; diff --git a/packages/core/state/interaction/logics.ts b/packages/core/state/interaction/logics.ts index f4d83e1d5..c63062769 100644 --- a/packages/core/state/interaction/logics.ts +++ b/packages/core/state/interaction/logics.ts @@ -602,7 +602,6 @@ const editFilesLogic = createLogic({ }`; dispatch(processError(editRequestId, errorMsg)); } finally { - console.log("reached finally in editFiles logic"); done(); } }, diff --git a/packages/core/state/interaction/test/logics.test.ts b/packages/core/state/interaction/test/logics.test.ts index f442a34fd..29f26e52e 100644 --- a/packages/core/state/interaction/test/logics.test.ts +++ b/packages/core/state/interaction/test/logics.test.ts @@ -656,7 +656,7 @@ describe("Interaction logics", () => { }); }); - describe.only("editFilesLogic", () => { + describe("editFilesLogic", () => { const sandbox = createSandbox(); const files = []; const fileKinds = ["PNG", "TIFF"]; @@ -831,7 +831,6 @@ describe("Interaction logics", () => { // Act store.dispatch(editFiles({ "Cell Line": ["AICS-12"] })); await logicMiddleware.whenComplete(); - console.log("finished awaiting"); // Assert expect( actions.includesMatch({