Skip to content

Commit

Permalink
Merge pull request #360 from hms-dbmi-cellenics/plots-and-tables-v2
Browse files Browse the repository at this point in the history
[BIOMAGE-1880] - Plots and tables v2
  • Loading branch information
aerlaut authored Jun 7, 2022
2 parents cdbfa22 + e4fd20a commit c69c0c5
Show file tree
Hide file tree
Showing 14 changed files with 472 additions and 5 deletions.
7 changes: 7 additions & 0 deletions src/api.v2/controllers/__mocks__/plotController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const mockGetPlotConfig = jest.fn();
const mockUpdatePlotConfig = jest.fn();

module.exports = {
getPlotConfig: mockGetPlotConfig,
updatePlotConfig: mockUpdatePlotConfig,
};
34 changes: 34 additions & 0 deletions src/api.v2/controllers/plotController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const getLogger = require('../../utils/getLogger');
const { OK } = require('../../utils/responses');

const Plot = require('../model/Plot');

const logger = getLogger('[Plot Controller] - ');

const getPlotConfig = async (req, res) => {
const { experimentId, plotUuid } = req.params;
logger.log(`Getting plot config for plot ${plotUuid}`);

const result = await new Plot().getConfig(experimentId, plotUuid);

logger.log(`Finished getting config for plot ${plotUuid}`);
res.json(result);
};

const updatePlotConfig = async (req, res) => {
const { experimentId, plotUuid } = req.params;
const { config } = req.body;

logger.log(`Updating config for plot ${plotUuid}`);

await new Plot().updateConfig(experimentId, plotUuid, config);

logger.log(`Finished updating config for plot ${plotUuid}`);

res.send(OK());
};

module.exports = {
getPlotConfig,
updatePlotConfig,
};
1 change: 1 addition & 0 deletions src/api.v2/helpers/s3/bucketNames.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const bucketNames = {
PROCESSED_MATRIX: `processed-matrix-${config.clusterEnv}`,
RAW_SEURAT: `biomage-source-${config.clusterEnv}`,
CELL_SETS: `cell-sets-${config.clusterEnv}`,
PLOTS: `plots-tables-${config.clusterEnv}`,
};

module.exports = bucketNames;
35 changes: 35 additions & 0 deletions src/api.v2/model/Plot.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
const BasicModel = require('./BasicModel');
const sqlClient = require('../../sql/sqlClient');

const validateRequest = require('../../utils/schema-validator');

const tableNames = require('./tableNames');
const bucketNames = require('../helpers/s3/bucketNames');
const getObject = require('../helpers/s3/getObject');

const selectableProps = [
'id',
Expand All @@ -14,6 +18,37 @@ class Plot extends BasicModel {
constructor(sql = sqlClient.get()) {
super(sql, tableNames.PLOT, selectableProps);
}

async getConfig(experimentId, plotUuid) {
const {
s3DataKey,
config: plotConfig,
} = await this.findOne({ id: plotUuid, experiment_id: experimentId });

const result = { config: plotConfig };


if (s3DataKey) {
const plotDataObject = await getObject({
Bucket: bucketNames.PLOTS,
Key: s3DataKey,
});

const output = JSON.parse(plotDataObject);

if (output.plotData) {
await validateRequest(output, 'plots-bodies/PlotData.v2.yaml');
}

result.plotData = output.plotData || [];
}

return result;
}

async updateConfig(experimentId, plotUuid, plotConfig) {
return await this.update({ id: plotUuid, experiment_id: experimentId }, { config: plotConfig });
}
}

module.exports = Plot;
11 changes: 11 additions & 0 deletions src/api.v2/model/__mocks__/Plot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const BasicModel = require('./BasicModel')();

const stub = {
getConfig: jest.fn(),
updateConfig: jest.fn(),
...BasicModel,
};

const Plot = jest.fn().mockImplementation(() => stub);

module.exports = Plot;
17 changes: 17 additions & 0 deletions src/api.v2/routes/plots.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const {
getPlotConfig,
updatePlotConfig,
} = require('../controllers/plotController');

const { expressAuthorizationMiddleware } = require('../middlewares/authMiddlewares');

module.exports = {
'plots#get': [
expressAuthorizationMiddleware,
(req, res, next) => getPlotConfig(req, res).catch(next),
],
'plots#update': [
expressAuthorizationMiddleware,
(req, res, next) => updatePlotConfig(req, res).catch(next),
],
};
100 changes: 99 additions & 1 deletion src/specs/api.v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,101 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/HTTPError'
'/experiments/{experimentId}/plots/{plotUuid}':
parameters:
- schema:
type: string
name: plotUuid
in: path
required: true
- schema:
type: string
name: experimentId
in: path
required: true
get:
summary: Get a plot or table config
operationId: getPlotTable
x-eov-operation-id: plots#get
x-eov-operation-handler: routes/plots
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/PlotConfig'
'400':
description: Bad request
content:
application/json:
schema:
$ref: ./models/HTTPError.v1.yaml
'401':
description: The request lacks authentication credentials.
content:
application/json:
schema:
$ref: ./models/HTTPError.v1.yaml
'403':
description: The authenticated user is not authorized to view this resource.
content:
application/json:
schema:
$ref: ./models/HTTPError.v1.yaml
'404':
description: Experiment/plot not found.
content:
application/json:
schema:
$ref: ./models/HTTPError.v1.yaml
description: Reads a plot and table for a given experiment with the data specified.
put:
summary: Update a plot or table config
operationId: updatePlotTable
x-eov-operation-id: plots#update
x-eov-operation-handler: routes/plots
responses:
'200':
description: Update to object in response successful.
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPSuccess'
'201':
description: New resource created.
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPSuccess'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPError'
'401':
description: The request lacks authentication credentials.
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPError'
'403':
description: The authenticated user is not authorized to view this resource.
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPError'
'404':
description: Invalid experiment ID specified.
description: Updates a plot and table for a given experiment with the data specified.
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PlotConfig'
description: The new configuration to update the old one by.

'/experiments/{experimentId}/qc':
parameters:
- schema:
Expand Down Expand Up @@ -1190,7 +1285,8 @@ paths:
content:
application/json:
schema:
$ref: ./models/HTTPError.v1.yaml
$ref: '#/components/schemas/HTTPError'

/gem2sResults:
post:
summary: Retrieve results from pipeline step functions
Expand Down Expand Up @@ -1345,6 +1441,8 @@ components:
$ref: './models/samples-bodies/PatchSampleFile.v2.yaml'
CellSets:
$ref: ./models/cell-sets-bodies/CellSets.v2.yaml
PlotConfig:
$ref: ./models/plots-bodies/PlotConfig.v2.yaml
HTTPSuccess:
$ref: './models/HTTPSuccess.v1.yaml'
HTTPError:
Expand Down
8 changes: 8 additions & 0 deletions src/specs/models/plots-bodies/PlotConfig.v2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
title: Plots and tables configuration
type: object
description: A file specifying the configuration for a given plot/table.
properties:
config:
type: object
required:
- config
10 changes: 10 additions & 0 deletions src/specs/models/plots-bodies/PlotData.v2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
title: Plot Data
type: object
properties:
plotData:
anyOf:
- type: array
- type: object
required:
- plotData
description: Object storing the data for one particular plot.
69 changes: 69 additions & 0 deletions tests/api.v2/controllers/plotController.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Disable semantic check because linter doesn't recognize jest mocks
// @ts-nocheck
const plotController = require('../../../src/api.v2/controllers/plotController');
const Plot = require('../../../src/api.v2/model/Plot');

const { OK } = require('../../../src/utils/responses');

jest.mock('../../../src/api.v2/model/Plot');

const mockRes = {
json: jest.fn(),
send: jest.fn(),
};

const mockExperimentId = 'experimentId';
const mockPlotUuid = 'plotUuid';

const plotInstance = new Plot();

describe('plotController', () => {
beforeEach(async () => {
jest.clearAllMocks();
});

it('getting plot works correctly', async () => {
const mockConfig = {
experimentId: mockExperimentId,
plotUuid: mockPlotUuid,
plotType: 'SomePlotType',
config: {},
};

plotInstance.getConfig.mockReturnValue(mockConfig);

const mockReq = {
params: { experimentId: mockExperimentId, plotUuid: mockPlotUuid },
};

await plotController.getPlotConfig(mockReq, mockRes);

expect(plotInstance.getConfig).toHaveBeenCalledWith(
mockExperimentId, mockPlotUuid,
);

expect(mockRes.json).toHaveBeenCalledWith(mockConfig);
});

it('updating plot works correctly', async () => {
const mockConfig = {
experimentId: mockExperimentId,
plotUuid: mockPlotUuid,
plotType: 'SomePlotType',
config: {},
};

const mockReq = {
params: { experimentId: mockExperimentId, plotUuid: mockPlotUuid },
body: { config: mockConfig },
};

await plotController.updatePlotConfig(mockReq, mockRes);

expect(plotInstance.updateConfig).toHaveBeenCalledWith(
mockExperimentId, mockPlotUuid, mockConfig,
);

expect(mockRes.send).toHaveBeenCalledWith(OK());
});
});
2 changes: 0 additions & 2 deletions tests/api.v2/helpers/access/createUserInvite.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ describe('creatUserInvite', () => {

expect(sendEmail).toHaveBeenCalledTimes(1);

console.log('*** calls', sendEmail.mock.calls);

const emailBody = sendEmail.mock.calls[0][0];

expect(emailBody).toMatchSnapshot();
Expand Down
Loading

0 comments on commit c69c0c5

Please sign in to comment.