diff --git a/migrations/20241202105905-multiple-principal-investigators.js b/migrations/20241202105905-multiple-principal-investigators.js new file mode 100644 index 000000000..4ae53f70c --- /dev/null +++ b/migrations/20241202105905-multiple-principal-investigators.js @@ -0,0 +1,31 @@ +module.exports = { + async up(db, client) { + await db.collection("Dataset").updateMany({}, [ + { + $set: { + principalInvestigators: ["$principalInvestigator"], + }, + }, + ]); + + await db + .collection("Dataset") + .updateMany({}, { $unset: { principalInvestigator: "" } }); + }, + + async down(db, client) { + await db.collection("Dataset").updateMany({}, [ + { + $set: { + principalInvestigator: { + $arrayElemAt: ["$principalInvestigators", 0], + }, + }, + }, + ]); + + await db + .collection("Dataset") + .updateMany({}, { $unset: { principalInvestigators: "" } }); + }, +}; diff --git a/src/datasets/datasets.controller.ts b/src/datasets/datasets.controller.ts index bcee3bdb1..bfadfe301 100644 --- a/src/datasets/datasets.controller.ts +++ b/src/datasets/datasets.controller.ts @@ -421,10 +421,28 @@ export class DatasetsController { whereFilter.instrumentIds = whereFilter.instrumentId; delete whereFilter.instrumentId; } + if ("investigator" in whereFilter) { + if (typeof whereFilter.investigator === "string") { + whereFilter.principalInvestigators = { + $in: [whereFilter.investigator], + }; + } else { + whereFilter.principalInvestigators = whereFilter.investigator; + } + + delete whereFilter.investigator; + } if ("principalInvestigator" in whereFilter) { - whereFilter.investigator = whereFilter.principalInvestigator; + if (typeof whereFilter.investigator === "string") { + whereFilter.principalInvestigators = { + $in: [whereFilter.principalInvestigator], + }; + } else { + whereFilter.principalInvestigators = whereFilter.principalInvestigator; + } delete whereFilter.principalInvestigator; } + return whereFilter; } convertObsoleteToCurrentSchema( @@ -463,15 +481,22 @@ export class DatasetsController { (inputObsoleteDataset as CreateRawDatasetObsoleteDto).instrumentId, ]; } + if ("principalInvestigator" in inputObsoleteDataset) { + propertiesModifier.principalInvestigators = [ + (inputObsoleteDataset as CreateRawDatasetObsoleteDto) + .principalInvestigator, + ]; + } } else if ( inputObsoleteDataset instanceof CreateDerivedDatasetObsoleteDto || inputObsoleteDataset instanceof UpdateDerivedDatasetObsoleteDto || inputObsoleteDataset instanceof PartialUpdateDerivedDatasetObsoleteDto ) { if ("investigator" in inputObsoleteDataset) { - propertiesModifier.principalInvestigator = ( - inputObsoleteDataset as CreateDerivedDatasetObsoleteDto - ).investigator; + propertiesModifier.principalInvestigators = [ + (inputObsoleteDataset as CreateDerivedDatasetObsoleteDto) + .investigator, + ]; } } @@ -516,18 +541,34 @@ export class DatasetsController { ): OutputDatasetObsoleteDto { const propertiesModifier: Record = {}; if (inputDataset) { - if ("proposalIds" in inputDataset) { - propertiesModifier.proposalId = inputDataset.proposalIds![0]; + if ("proposalIds" in inputDataset && inputDataset.proposalIds?.length) { + propertiesModifier.proposalId = inputDataset.proposalIds[0]; + } + if ("sampleIds" in inputDataset && inputDataset.sampleIds?.length) { + propertiesModifier.sampleId = inputDataset.sampleIds[0]; } - if ("sampleIds" in inputDataset) { - propertiesModifier.sampleId = inputDataset.sampleIds![0]; + if ( + "instrumentIds" in inputDataset && + inputDataset.instrumentIds?.length + ) { + propertiesModifier.instrumentId = inputDataset.instrumentIds[0]; } - if ("instrumentIds" in inputDataset) { - propertiesModifier.instrumentId = inputDataset.instrumentIds![0]; + + if ( + "principalInvestigators" in inputDataset && + inputDataset.principalInvestigators?.length + ) { + propertiesModifier.principalInvestigator = + inputDataset.principalInvestigators[0]; } + if (inputDataset.type == "derived") { - if ("investigator" in inputDataset) { - propertiesModifier.investigator = inputDataset.principalInvestigator; + if ( + "investigator" in inputDataset && + inputDataset.principalInvestigators?.length + ) { + propertiesModifier.investigator = + inputDataset.principalInvestigators[0]; } } } diff --git a/src/datasets/datasets.service.spec.ts b/src/datasets/datasets.service.spec.ts index 9cea8700e..cd14bbd4a 100644 --- a/src/datasets/datasets.service.spec.ts +++ b/src/datasets/datasets.service.spec.ts @@ -19,7 +19,7 @@ const mockDataset: DatasetClass = { pid: "testPid", owner: "testOwner", ownerEmail: "testOwner@email.com", - instrumentId: "testInstrumentId", + instrumentIds: ["testInstrumentId"], orcidOfOwner: "https://0000.0000.0000.0001", contactEmail: "testContact@email.com", sourceFolder: "/nfs/groups/beamlines/test/123456", @@ -59,24 +59,20 @@ const mockDataset: DatasetClass = { createdAt: new Date("2021-11-11T12:29:02.083Z"), updatedAt: new Date("2021-11-11T12:29:02.083Z"), techniques: [], - principalInvestigator: "testInvestigator", + principalInvestigators: ["testInvestigator"], endTime: new Date("2021-12-11T12:29:02.083Z"), creationLocation: "test", dataFormat: "Test Format", scientificMetadata: {}, - proposalId: "ABCDEF", - sampleId: "testSampleId", - attachments: [], + proposalIds: ["ABCDEF"], + sampleIds: ["testSampleId"], accessGroups: [], createdBy: "test user", - datablocks: [], - origdatablocks: [], ownerGroup: "test", relationships: [], sharedWith: [], updatedBy: "test", instrumentGroup: "test", - investigator: "test", inputDatasets: [], usedSoftware: [], jobParameters: {}, diff --git a/src/datasets/dto/update-dataset.dto.ts b/src/datasets/dto/update-dataset.dto.ts index 127eb7f2b..1dc67f0e7 100644 --- a/src/datasets/dto/update-dataset.dto.ts +++ b/src/datasets/dto/update-dataset.dto.ts @@ -292,12 +292,14 @@ export class UpdateDatasetDto extends OwnableDto { @ApiProperty({ type: String, - required: true, + required: false, + isArray: true, description: - "First name and last name of principal investigator(s). If multiple PIs are present, use a semicolon separated list. This field is required if the dataset is a Raw dataset.", + "First and last name of principal investigator(s). Multiple PIs can be provided as separate strings in the array. This field is required if the dataset is a Raw dataset.", }) - @IsString() - readonly principalInvestigator: string; + @IsOptional() + @IsString({ each: true }) + readonly principalInvestigators?: string[]; @ApiProperty({ type: Date, diff --git a/src/datasets/schemas/dataset.schema.ts b/src/datasets/schemas/dataset.schema.ts index 5419d0fd4..723816a56 100644 --- a/src/datasets/schemas/dataset.schema.ts +++ b/src/datasets/schemas/dataset.schema.ts @@ -357,11 +357,12 @@ export class DatasetClass extends OwnableClass { @ApiProperty({ type: String, required: false, + isArray: true, description: - "First name and last name of principal investigator(s). If multiple PIs are present, use a semicolon separated list. This field is required if the dataset is a Raw dataset.", + "First and last name of principal investigator(s). Multiple PIs can be provided as separate strings in the array. This field is required if the dataset is a Raw dataset.", }) - @Prop({ type: String, required: false }) - principalInvestigator?: string; + @Prop({ type: [String], required: false }) + principalInvestigators?: string[]; @ApiProperty({ type: Date, diff --git a/test/DatasetCustom.js b/test/DatasetCustom.js index 9670c1adf..f23c76229 100644 --- a/test/DatasetCustom.js +++ b/test/DatasetCustom.js @@ -17,7 +17,7 @@ describe("2400: CustomDataset: Custom Type Datasets", () => { before(() => { db.collection("Dataset").deleteMany({}); }); - beforeEach(async() => { + beforeEach(async () => { accessTokenAdminIngestor = await utils.getToken(appUrl, { username: "adminIngestor", password: TestData.Accounts["adminIngestor"]["password"], diff --git a/test/TestData.js b/test/TestData.js index afbadf1b1..f16142a96 100644 --- a/test/TestData.js +++ b/test/TestData.js @@ -481,7 +481,7 @@ const TestData = { }, CustomDatasetCorrectMin: { - principalInvestigator: faker.internet.email(), + principalInvestigators: [faker.internet.email()], owner: faker.internet.username(), contactEmail: faker.internet.email(), sourceFolder: faker.system.directoryPath(), @@ -492,7 +492,7 @@ const TestData = { }, CustomDatasetCorrect: { - principalInvestigator: "egon.meier@web.de", + principalInvestigators: ["egon.meier@web.de"], inputDatasets: ["/data/input/file1.dat"], usedSoftware: [ "https://gitlab.psi.ch/ANALYSIS/csaxs/commit/7d5888bfffc440bb613bc7fa50adc0097853446c", @@ -522,7 +522,7 @@ const TestData = { }, CustomDatasetWrongType: { - principalInvestigator: "egon.meier@web.de", + principalInvestigators: ["egon.meier@web.de"], jobParameters: { nscans: 10, }, @@ -540,7 +540,7 @@ const TestData = { }, CustomDatasetWrongData: { - principalInvestigator: "egon.meier@web.de", + principalInvestigators: ["egon.meier@web.de"], jobParameters: { nscans: 10, }, @@ -559,7 +559,7 @@ const TestData = { }, CustomDatasetIncompleteData: { - principalInvestigator: "egon.meier@web.de", + principalInvestigators: ["egon.meier@web.de"], jobParameters: { nscans: 10, },