diff --git a/src/api.v2/model/Experiment.js b/src/api.v2/model/Experiment.js index 7ed7f75b6..a7df9c7eb 100644 --- a/src/api.v2/model/Experiment.js +++ b/src/api.v2/model/Experiment.js @@ -67,9 +67,32 @@ class Experiment extends BasicModel { return result; } - async getExampleExperiments() { - return this.getAllExperiments(constants.PUBLIC_ACCESS_ID); + const fields = [ + 'id', + 'name', + 'description', + 'publication_title', + 'publication_url', + 'data_source_title', + 'data_source_url', + 'species', + 'cell_count', + ]; + + const aliasedExperimentFields = fields.map((column) => `e.${column}`); + + const result = await this.sql + .select(aliasedExperimentFields) + .min('s.sample_technology as sample_technology') + .count('s.id as sample_count') // Returns a BigInt type which is represented as string (parse?) + .from(tableNames.USER_ACCESS) + .join(`${tableNames.EXPERIMENT} as e`, 'e.id', `${tableNames.USER_ACCESS}.experiment_id`) + .join(`${tableNames.SAMPLE} as s`, 'e.id', 's.experiment_id') + .where('user_id', constants.PUBLIC_ACCESS_ID) + .groupBy('e.id'); + + return result; } async getExperimentData(experimentId) { diff --git a/src/specs/api.v2.yaml b/src/specs/api.v2.yaml index 4dd419ee8..251306d6f 100644 --- a/src/specs/api.v2.yaml +++ b/src/specs/api.v2.yaml @@ -301,7 +301,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/ExperimentsList" + $ref: "#/components/schemas/ExampleExperimentsList" "400": description: Bad Request content: @@ -681,7 +681,6 @@ paths: application/json: schema: $ref: "#/components/schemas/HTTPError" - "/experiments/{experimentId}/samples/{sampleId}": patch: summary: Patch sample @@ -2150,6 +2149,8 @@ components: $ref: ./models/experiment-bodies/ProcessingConfig.v2.yaml ExperimentsList: $ref: ./models/experiment-bodies/ExperimentsList.v2.yaml + ExampleExperimentsList: + $ref: ./models/experiment-bodies/ExampleExperimentsList.v2.yaml CreateSamples: $ref: ./models/samples-bodies/CreateSamples.v2.yaml GetSamples: diff --git a/src/specs/models/experiment-bodies/ExampleExperimentsList.v2.yaml b/src/specs/models/experiment-bodies/ExampleExperimentsList.v2.yaml new file mode 100644 index 000000000..6de521354 --- /dev/null +++ b/src/specs/models/experiment-bodies/ExampleExperimentsList.v2.yaml @@ -0,0 +1,52 @@ +title: Get all example experiments +type: array +items: + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + nullable: true + publicationTitle: + type: string + nullable: true + publicationUrl: + type: string + nullable: true + dataSourceTitle: + type: string + nullable: true + dataSourceUrl: + type: string + nullable: true + species: + type: string + nullable: true + cellCount: + type: string + nullable: true + sampleCount: + type: string + sampleTechnology: + type: string + nullable: true + + required: + - id + - name + - description + - publicationTitle + - publicationUrl + - dataSourceTitle + - dataSourceUrl + - species + - cellCount + - sampleCount + - sampleTechnology + + additionalProperties: false + +additionalProperties: false diff --git a/src/sql/migrations/20230207141717_add_example_experiment_fields.js b/src/sql/migrations/20230207141717_add_example_experiment_fields.js new file mode 100644 index 000000000..c716de980 --- /dev/null +++ b/src/sql/migrations/20230207141717_add_example_experiment_fields.js @@ -0,0 +1,29 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = async (knex) => { + await knex.schema.alterTable('experiment', (table) => { + table.string('publication_title').nullable(); + table.string('publication_url').nullable(); + table.string('data_source_title').nullable(); + table.string('data_source_url').nullable(); + table.string('species').nullable(); + table.integer('cell_count').nullable(); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = async (knex) => { + await knex.schema.alterTable('experiment', (table) => { + table.dropColumn('publication_title'); + table.dropColumn('publication_url'); + table.dropColumn('data_source_title'); + table.dropColumn('data_source_url'); + table.dropColumn('species'); + table.dropColumn('cell_count'); + }); +}; diff --git a/tests/api.v2/mocks/getMockSqlClient.js b/tests/api.v2/mocks/getMockSqlClient.js index e1c6ecf51..d3be24fef 100644 --- a/tests/api.v2/mocks/getMockSqlClient.js +++ b/tests/api.v2/mocks/getMockSqlClient.js @@ -27,6 +27,8 @@ module.exports = () => { onConflict: jest.fn().mockReturnThis(), merge: jest.fn().mockReturnThis(), andWhereLike: jest.fn().mockReturnThis(), + min: jest.fn().mockReturnThis(), + count: jest.fn().mockReturnThis(), ref: jest.fn(), }; diff --git a/tests/api.v2/model/Experiment.test.js b/tests/api.v2/model/Experiment.test.js index fcc231966..5753afbea 100644 --- a/tests/api.v2/model/Experiment.test.js +++ b/tests/api.v2/model/Experiment.test.js @@ -32,6 +32,7 @@ jest.mock('../../../src/sql/helpers', () => ({ const Experiment = require('../../../src/api.v2/model/Experiment'); const constants = require('../../../src/utils/constants'); +const tableNames = require('../../../src/api.v2/model/tableNames'); const mockExperimentId = 'mockExperimentId'; const mockSampleId = 'mockSampleId'; @@ -77,15 +78,35 @@ describe('model/Experiment', () => { }); it('getExampleExperiments works correctly', async () => { - const expectedResult = { isMockResult: true }; + const queryResult = 'result'; + + mockSqlClient.groupBy.mockReturnValueOnce(queryResult); - const getAllExperimentsSpy = jest.spyOn(Experiment.prototype, 'getAllExperiments') - .mockImplementationOnce(() => Promise.resolve(expectedResult)); + const expectedResult = await new Experiment().getExampleExperiments(); - const result = await new Experiment().getExampleExperiments('mockUserId'); + expect(queryResult).toEqual(expectedResult); - expect(result).toBe(expectedResult); - expect(getAllExperimentsSpy).toHaveBeenCalledWith(constants.PUBLIC_ACCESS_ID); + expect(sqlClient.get).toHaveBeenCalled(); + expect(mockSqlClient.select).toHaveBeenCalledWith( + [ + 'e.id', + 'e.name', + 'e.description', + 'e.publication_title', + 'e.publication_url', + 'e.data_source_title', + 'e.data_source_url', + 'e.species', + 'e.cell_count', + ], + ); + expect(mockSqlClient.min).toHaveBeenCalledWith('s.sample_technology as sample_technology'); + expect(mockSqlClient.count).toHaveBeenCalledWith('s.id as sample_count'); + expect(mockSqlClient.from).toHaveBeenCalledWith(tableNames.USER_ACCESS); + expect(mockSqlClient.join).toHaveBeenCalledWith(`${tableNames.EXPERIMENT} as e`, 'e.id', `${tableNames.USER_ACCESS}.experiment_id`); + expect(mockSqlClient.join).toHaveBeenCalledWith(`${tableNames.SAMPLE} as s`, 'e.id', 's.experiment_id'); + expect(mockSqlClient.where).toHaveBeenCalledWith('user_id', constants.PUBLIC_ACCESS_ID); + expect(mockSqlClient.groupBy).toHaveBeenCalledWith('e.id'); }); it('getExperimentData works correctly', async () => {