Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BIOMAGE-1804] Implement load & save processing settings for api v2 #331

Merged
merged 23 commits into from
Apr 21, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/api.v2/controllers/experimentController.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,28 @@ const updateSamplePosition = async (req, res) => {
res.json(OK());
};

const getProcessingConfig = async (req, res) => {
const { params: { experimentId } } = req;
logger.log('Getting processing config for experiment ', experimentId);

const result = await new Experiment().getProcessingConfig(experimentId);
res.json(result);
};

const updateProcessingConfig = async (req, res) => {
const { params: { experimentId }, body } = req;
logger.log('Updating processing config for experiment ', experimentId);

await new Experiment().updateProcessingConfig(experimentId, body);
res.json(OK());
};

module.exports = {
getAllExperiments,
getExperiment,
createExperiment,
updateProcessingConfig,
patchExperiment,
updateSamplePosition,
getProcessingConfig,
};
2 changes: 2 additions & 0 deletions src/api.v2/model/__mocks__/Experiment.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const stub = {
getAllExperiments: jest.fn(),
getExperimentData: jest.fn(),
updateSamplePosition: jest.fn(),
updateProcessingConfig: jest.fn(),
getProcessingConfig: jest.fn(),
addSample: jest.fn(),
deleteSample: jest.fn(),
...BasicModel,
Expand Down
183 changes: 183 additions & 0 deletions src/api.v2/model/experiment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
const _ = require('lodash');
StefanBabukov marked this conversation as resolved.
Show resolved Hide resolved

const BasicModel = require('./BasicModel');
const sqlClient = require('../../sql/sqlClient');
const { collapseKeyIntoArray } = require('../../sql/helpers');

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

const tableNames = require('../helpers/tableNames');


const getLogger = require('../../utils/getLogger');

const logger = getLogger('[ExperimentModel] - ');


const experimentFields = [
'id',
'name',
'description',
'samples_order',
'processing_config',
'notify_by_email',
'created_at',
'updated_at',
];

class Experiment extends BasicModel {
constructor(sql = sqlClient.get()) {
super(sql, tableNames.EXPERIMENT, experimentFields);
}

async getAllExperiments(userId) {
const fields = [
'id',
'name',
'description',
'samples_order',
'notify_by_email',
'created_at',
'updated_at',
];

const aliasedExperimentFields = fields.map((field) => `e.${field}`);
function mainQuery() {
this.select([...aliasedExperimentFields, 'm.key'])
.from(tableNames.USER_ACCESS)
.where('user_id', userId)
.join(`${tableNames.EXPERIMENT} as e`, 'e.id', `${tableNames.USER_ACCESS}.experiment_id`)
.leftJoin(`${tableNames.METADATA_TRACK} as m`, 'e.id', 'm.experiment_id')
.as('mainQuery');
}

const result = await collapseKeyIntoArray(mainQuery, [...fields], 'key', 'metadataKeys', this.sql);

return result;
}

async getExperimentData(experimentId) {
function mainQuery() {
this.select('*')
.from(tableNames.EXPERIMENT)
.leftJoin(tableNames.EXPERIMENT_EXECUTION, `${tableNames.EXPERIMENT}.id`, `${tableNames.EXPERIMENT_EXECUTION}.experiment_id`)
.where('id', experimentId)
.as('mainQuery');
}

const experimentExecutionFields = [
'params_hash', 'state_machine_arn', 'execution_arn',
];

const pipelineExecutionKeys = experimentExecutionFields.reduce((acum, current) => {
acum.push(`'${current}'`);
acum.push(current);

return acum;
}, []);

const replaceNullsWithObject = (object, nullableKey) => (
`COALESCE(
${object}
FILTER(
WHERE ${nullableKey} IS NOT NULL
),
'{}'::jsonb
)`
);

const result = await this.sql
.select([
...experimentFields,
this.sql.raw(
`${replaceNullsWithObject(
`jsonb_object_agg(pipeline_type, jsonb_build_object(${pipelineExecutionKeys.join(', ')}))`,
'pipeline_type',
)} as pipelines`,
),
])
.from(mainQuery)
.groupBy(experimentFields)
.first();

if (_.isEmpty(result)) {
throw new NotFoundError('Experiment not found');
}

return result;
}

// Sets samples_order as an array that has the sample in oldPosition moved to newPosition
async updateSamplePosition(
experimentId, oldPosition, newPosition,
) {
const trx = await this.sql.transaction();

try {
const result = await trx(tableNames.EXPERIMENT)
.update({
samples_order: trx.raw(`(
SELECT jsonb_insert(samples_order - ${oldPosition}, '{${newPosition}}', samples_order -> ${oldPosition}, false)
FROM (
SELECT (samples_order)
FROM experiment e
WHERE e.id = '${experimentId}'
) samples_order
)`),
}).where('id', experimentId)
.returning(['samples_order']);

const { samplesOrder = null } = result[0] || {};

if (_.isNil(samplesOrder)
|| !_.inRange(oldPosition, 0, samplesOrder.length)
|| !_.inRange(newPosition, 0, samplesOrder.length)
) {
logger.log('Invalid positions or samples_order was broken, rolling back transaction');
throw new Error('Invalid update parameters');
}

trx.commit();
} catch (e) {
trx.rollback();
throw e;
}
}

async getProcessingConfig(experimentId) {
const result = await this.find({ id: experimentId });
if (_.isEmpty(result)) {
throw new NotFoundError('Experiment not found');
}
console.log('RESULT IS ', result[0].processingConfig);

return result[0].processingConfig;
}

async updateProcessingConfig(experimentId, body) {
const { name: stepName, body: change } = body[0];

const { processingConfig } = await this.getProcessingConfig(experimentId);
processingConfig[stepName] = change;

await this.update(experimentId, { processingConfig });
}

async addSample(experimentId, sampleId) {
await this.sql(tableNames.EXPERIMENT)
.update({
samples_order: this.sql.raw(`samples_order || '["${sampleId}"]'::jsonb`),
})
.where('id', experimentId);
}

async deleteSample(experimentId, sampleId) {
await this.sql(tableNames.EXPERIMENT)
.update({
samples_order: this.sql.raw(`samples_order - '${sampleId}'`),
})
.where('id', experimentId);
}
}

module.exports = Experiment;
11 changes: 10 additions & 1 deletion src/api.v2/routes/experiment.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const {
createExperiment, getExperiment, patchExperiment, updateSamplePosition, getAllExperiments,
createExperiment, getExperiment, patchExperiment, updateSamplePosition,
getAllExperiments, getProcessingConfig, updateProcessingConfig,
} = require('../controllers/experimentController');

const { expressAuthenticationOnlyMiddleware, expressAuthorizationMiddleware } = require('../middlewares/authMiddlewares');
Expand All @@ -25,4 +26,12 @@ module.exports = {
expressAuthorizationMiddleware,
(req, res, next) => updateSamplePosition(req, res).catch(next),
],
'experiment#getProcessingConfig': [
// expressAuthorizationMiddleware,
(req, res, next) => getProcessingConfig(req, res).catch(next),
],
'experiment#updateProcessingConfig': [
expressAuthorizationMiddleware,
(req, res, next) => updateProcessingConfig(req, res).catch(next),
],
};
66 changes: 66 additions & 0 deletions src/specs/api.v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,69 @@ paths:
- env
- clusterEnv
description: Returns a status on the health of the API.
'/experiments/{experimentId}/processingConfig':
get:
summary: Get processing configuration for an experiment
description: Get processing configuration for an experiment
operationId: getProcessingConfig
x-eov-operation-id: experiment#getProcessingConfig
x-eov-operation-handler: routes/experiment
responses:
'200':
description: get processing configuration for an experiment
content:
application/json:
schema:
$ref: '#/components/schemas/ProcessingConfig'
'404':
StefanBabukov marked this conversation as resolved.
Show resolved Hide resolved
description: Not found error.
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPError'
'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'
put:
summary: Update processing configuration for an experiment
description: Update processing configuration for an experiment
operationId: updateProcessingConfig
x-eov-operation-id: experiment#updateProcessingConfig
x-eov-operation-handler: routes/experiment
responses:
'200':
description: Processing configuration for an experiment
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPSuccess'
'404':
StefanBabukov marked this conversation as resolved.
Show resolved Hide resolved
description: Not found error.
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPError'
'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'
'/experiments':
get:
summary: Get all experiments
Expand Down Expand Up @@ -330,6 +393,7 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/HTTPError'

components:
schemas:
CreateExperiment:
Expand All @@ -338,6 +402,8 @@ components:
$ref: './models/experiment-bodies/ExperimentInfo.v2.yaml'
ExperimentPatch:
$ref: './models/experiment-bodies/ExperimentPatch.v2.yaml'
ProcessingConfig:
$ref: './models/experiment-bodies/ProcessingConfig.v2.yaml'
GetAllExperiments:
$ref: './models/experiment-bodies/GetAllExperiments.v2.yaml'
CreateSample:
Expand Down
48 changes: 24 additions & 24 deletions src/specs/models/api-body-schemas/ProcessingConfig.v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,28 @@ title: Processing Config schema
description: Processing Config schema
type: object
properties:
cellSizeDistribution:
$ref: '../processing-config-bodies/ProcessingConfigCellSizeDistribution.v1.yaml'
mitochondrialContent:
$ref: '../processing-config-bodies/ProcessingConfigMitochondrialContent.v1.yaml'
readAlignment:
$ref: '../processing-config-bodies/ProcessingConfigReadAlignment.v1.yaml'
classifier:
$ref: '../processing-config-bodies/ProcessingConfigClassifier.v1.yaml'
numGenesVsNumUmis:
$ref: '../processing-config-bodies/ProcessingConfigNumGenesVsNumUMIs.v1.yaml'
doubletScores:
$ref: '../processing-config-bodies/ProcessingConfigDoubletScores.v1.yaml'
dataIntegration:
$ref: '../processing-config-bodies/ProcessingConfigDataIntegration.v1.yaml'
configureEmbedding:
$ref: '../processing-config-bodies/ProcessingConfigConfigureEmbedding.v1.yaml'
processingConfig:
type: object
properties:
cellSizeDistribution:
StefanBabukov marked this conversation as resolved.
Show resolved Hide resolved
$ref: '../processing-config-bodies/ProcessingConfigCellSizeDistribution.v1.yaml'
mitochondrialContent:
$ref: '../processing-config-bodies/ProcessingConfigMitochondrialContent.v1.yaml'
readAlignment:
$ref: '../processing-config-bodies/ProcessingConfigReadAlignment.v1.yaml'
classifier:
$ref: '../processing-config-bodies/ProcessingConfigClassifier.v1.yaml'
numGenesVsNumUmis:
$ref: '../processing-config-bodies/ProcessingConfigNumGenesVsNumUMIs.v1.yaml'
doubletScores:
$ref: '../processing-config-bodies/ProcessingConfigDoubletScores.v1.yaml'
dataIntegration:
$ref: '../processing-config-bodies/ProcessingConfigDataIntegration.v1.yaml'
configureEmbedding:
$ref: '../processing-config-bodies/ProcessingConfigConfigureEmbedding.v1.yaml'

required:
- cellSizeDistribution
- mitochondrialContent
- readAlignment
- classifier
- numGenesVsNumUmis
- doubletScores
- dataIntegration
- configureEmbedding
- processingConfig: [
'cellSizeDistribution', 'mitochondrialContent', 'readAlignment',
'classifier', 'numGenesVsNumUmis', 'doubletScores', 'dataIntegration', 'configureEmbedding']
additionalProperties: false
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ required:
- numGenesVsNumUmis
- doubletScores
- dataIntegration
- configureEmbedding
- configureEmbedding
Loading