Skip to content

Commit 4f1a59b

Browse files
authored
[O2B-1391] Allow to create verified QC flag (#1983)
* cherry-pick * mv logic to serice * add test * fix * test * test * fix
1 parent f2a477a commit 4f1a59b

15 files changed

Lines changed: 203 additions & 29 deletions

File tree

lib/public/components/common/form/createButton.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ import { h } from '/js/src/index.js';
1818
*
1919
* @param {function} onclick function called when button is clicked (if null, button will be disabled)
2020
* @param {string} [text='Create'] the button's text
21+
* @param {string} [selector='button#submit.btn.btn-success'] the button selector
2122
* @return {vnode} the button component
2223
*/
23-
export const createButton = (onclick, text = 'Create') => h(
24-
'button#submit.btn.btn-success',
24+
export const createButton = (onclick, text = 'Create', selector = 'button#submit.btn.btn-success') => h(
25+
selector,
2526
onclick ? { onclick } : { disabled: true },
2627
text,
2728
);

lib/public/components/common/form/creationFormComponent.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,18 @@ import { h } from '/js/src/index.js';
1919
*
2020
* @param {CreationModel} creationFormModel the form model
2121
* @param {Component} formInputsComponent the component containing all the form inputs
22+
* @param {{renderSubmitSection?: function(CreationModel): Component}} [options] optional configuration
2223
* @return {Component} the form component
2324
*/
24-
export const creationFormComponent = (creationFormModel, formInputsComponent) => [
25+
export const creationFormComponent = (creationFormModel, formInputsComponent, { renderSubmitSection } = {}) => [
2526
creationFormModel.creationResult.isFailure() && errorAlert(creationFormModel.creationResult.payload),
2627
formInputsComponent,
27-
h('.pv3', creationFormModel.creationResult.match({
28-
Loading: () => createButton(null, 'Sending...'),
29-
Success: () => createButton(null, 'Sent!'),
30-
Failure: () => createButton(creationFormModel.isValid() ? () => creationFormModel.submit() : null),
31-
NotAsked: () => createButton(creationFormModel.isValid() ? () => creationFormModel.submit() : null),
32-
})),
28+
renderSubmitSection
29+
? renderSubmitSection(creationFormModel)
30+
: h('.pv3', creationFormModel.creationResult.match({
31+
Loading: () => createButton(null, 'Sending...'),
32+
Success: () => createButton(null, 'Sent!'),
33+
Failure: () => createButton(creationFormModel.isValid() ? () => creationFormModel.submit() : null),
34+
NotAsked: () => createButton(creationFormModel.isValid() ? () => creationFormModel.submit() : null),
35+
})),
3336
];

lib/public/models/CreationModel.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,15 @@ export class CreationModel extends Observable {
4848

4949
/**
5050
* Submit the current form data, handling errors appropriately
51+
* @param {*} [options] optional submission options forwarded to serialization
5152
* @returns {void}
5253
*/
53-
async submit() {
54+
async submit(options) {
5455
this._creationResult = RemoteData.loading();
5556
this.notify();
5657

5758
try {
58-
const { data } = await jsonPost(this._apiEndpoint, this._getSerializableData());
59+
const { data } = await jsonPost(this._apiEndpoint, this._getSerializableData(options));
5960

6061
this._creationResult = RemoteData.success(null);
6162
this._onCreationSuccess(data);

lib/public/views/QcFlags/Create/forDataPass/QcFlagCreationForDataPassModel.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,20 @@ export class QcFlagCreationForDataPassModel extends CreationModel {
5757
/**
5858
* @inheritDoc
5959
*/
60-
_getSerializableData() {
61-
return {
60+
_getSerializableData(options = {}) {
61+
const serialized = {
6262
dataPassId: this._dataPassId,
6363
...this._qcFlagCreationModel.match({
6464
Success: ({ normalized }) => normalized,
6565
Other: {},
6666
}),
6767
};
68+
69+
if (options?.verify) {
70+
serialized.verify = true;
71+
}
72+
73+
return serialized;
6874
}
6975

7076
/**

lib/public/views/QcFlags/Create/forDataPass/QcFlagCreationForDataPassPage.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
*/
1414

1515
import { h } from '/js/src/index.js';
16-
import { qcFlagCreationComponent } from '../qcFlagCreationComponent.js';
16+
import { qcFlagCreationComponent, qcFlagCreationSubmitSection } from '../qcFlagCreationComponent.js';
1717
import { frontLink } from '../../../../components/common/navigation/frontLink.js';
1818
import { tooltip } from '../../../../components/common/popover/tooltip.js';
1919
import { iconWarning } from '/js/src/icons.js';
@@ -48,6 +48,7 @@ export const QcFlagCreationForDataPassPage = ({ qcFlags: { creationForDataPassMo
4848
Failure: (errors) => errorAlert(errors),
4949
NotAsked: () => null,
5050
}),
51+
{ renderSubmitSection: qcFlagCreationSubmitSection },
5152
),
5253
),
5354
],

lib/public/views/QcFlags/Create/forSimulationPass/QcFlagCreationForSimulationPassModel.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,20 @@ export class QcFlagCreationForSimulationPassModel extends CreationModel {
5858
/**
5959
* @inheritDoc
6060
*/
61-
_getSerializableData() {
62-
return {
61+
_getSerializableData(options = {}) {
62+
const serialized = {
6363
simulationPassId: this._simulationPassId,
6464
...this._qcFlagCreationModel.match({
6565
Success: ({ normalized }) => normalized,
6666
Other: {},
6767
}),
6868
};
69+
70+
if (options?.verify) {
71+
serialized.verify = true;
72+
}
73+
74+
return serialized;
6975
}
7076

7177
/**

lib/public/views/QcFlags/Create/forSimulationPass/QcFlagCreationForSimulationPassPage.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { h, iconWarning } from '/js/src/index.js';
1616
import { creationFormComponent } from '../../../../components/common/form/creationFormComponent.js';
1717
import errorAlert from '../../../../components/common/errorAlert.js';
1818
import spinner from '../../../../components/common/spinner.js';
19-
import { qcFlagCreationComponent } from '../qcFlagCreationComponent.js';
19+
import { qcFlagCreationComponent, qcFlagCreationSubmitSection } from '../qcFlagCreationComponent.js';
2020
import { frontLink } from '../../../../components/common/navigation/frontLink.js';
2121
import { tooltip } from '../../../../components/common/popover/tooltip.js';
2222

@@ -51,6 +51,7 @@ export const QcFlagCreationForSimulationPassPage = ({ qcFlags: { creationForSimu
5151
Failure: (errors) => errorAlert(errors),
5252
NotAsked: () => null,
5353
}),
54+
{ renderSubmitSection: qcFlagCreationSubmitSection },
5455
),
5556
),
5657
];

lib/public/views/QcFlags/Create/qcFlagCreationComponent.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import errorAlert from '../../../components/common/errorAlert.js';
2424
import { isRunNotSubjectToQc } from '../../../components/qcFlags/isRunNotSubjectToQc.js';
2525
import { frontLink } from '../../../components/common/navigation/frontLink.js';
2626
import { getRunQcExclusionReason } from '../../../components/qcFlags/getRunQcExclusionReason.js';
27+
import { createButton } from '../../../components/common/form/createButton.js';
2728

2829
/**
2930
* Create table with selected detectors and runs information
@@ -160,3 +161,44 @@ export const qcFlagCreationComponent = (qcFlagCreationModel) => {
160161
? qcFlagCreationForm(qcFlagCreationModel)
161162
: errorAlert(errors);
162163
};
164+
165+
/**
166+
* Render submit section for QC flag creation form
167+
* @param {CreationModel} creationFormModel creation form model
168+
* @return {Component} submit section component
169+
*/
170+
export const qcFlagCreationSubmitSection = (creationFormModel) => {
171+
const interactiveButtons = () => {
172+
const submitHandler = creationFormModel.isValid()
173+
? () => creationFormModel.submit()
174+
: null;
175+
const submitWithVerificationHandler = creationFormModel.isValid()
176+
? () => creationFormModel.submit({ verify: true })
177+
: null;
178+
179+
return [
180+
createButton(submitHandler),
181+
createButton(
182+
submitWithVerificationHandler,
183+
'Create (with verification)',
184+
'button#submit-with-verification.btn.btn-success',
185+
),
186+
];
187+
};
188+
189+
return h(
190+
'.pv3.flex-row.g3',
191+
creationFormModel.creationResult.match({
192+
Loading: () => [
193+
createButton(null, 'Sending...'),
194+
createButton(null, 'Sending...', 'button#submit-with-verification.btn.btn-success'),
195+
],
196+
Success: () => [
197+
createButton(null, 'Sent!'),
198+
createButton(null, 'Sent!', 'button#submit-with-verification.btn.btn-success'),
199+
],
200+
Failure: interactiveButtons,
201+
NotAsked: interactiveButtons,
202+
}),
203+
);
204+
};

lib/server/controllers/qcFlag.controller.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ const createQcFlagsHandler = async (req, res) => {
185185
}),
186186
dataPassId: Joi.number().allow(null),
187187
simulationPassId: Joi.number().allow(null),
188+
verify: Joi.boolean().optional(),
188189
runDetectors: Joi.array().items(Joi.object({
189190
runNumber: Joi.number().required(),
190191
detectorIds: Joi.array().items(Joi.number().required()).min(1).required(),

lib/server/services/qualityControlFlag/QcFlagService.js

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class QcFlagService {
135135
throw new BadParameterError('Cannot create QC flag for data pass and simulation pass simultaneously');
136136
}
137137

138-
return dataSource.transaction(async () => {
138+
return dataSource.transaction(async (transaction) => {
139139
const user = await getUserOrFail({ userId, externalUserId });
140140
const detector = await getQcDetectorOrFail(detectorIdentifier);
141141

@@ -156,7 +156,7 @@ class QcFlagService {
156156

157157
const createdFlags = [];
158158
for (const qcFlag of qcFlags) {
159-
const { comment, flagTypeId, origin } = qcFlag;
159+
const { comment, flagTypeId, origin, verify } = qcFlag;
160160
// Each flag should be treated separately and the for loop should not fail on the first error
161161
try {
162162
const { from, to } = this._prepareQcFlagPeriod({ from: qcFlag.from, to: qcFlag.to }, targetRun);
@@ -179,14 +179,7 @@ class QcFlagService {
179179
}
180180

181181
/** @var {SequelizeQcFlag} createdFlag */
182-
const createdFlag = await QcFlagRepository.findOne({
183-
where: { id: newInstance.id },
184-
include: [
185-
{ association: 'dataPasses' },
186-
{ association: 'simulationPasses' },
187-
{ association: 'createdBy' },
188-
],
189-
});
182+
let createdFlag = await QcFlagRepository.findOne({ where: { id: newInstance.id } });
190183

191184
// Update effective periods
192185
const effectivePeriodsToBeUpdated = await QcFlagEffectivePeriodRepository.findOverlappingPeriodsCreatedBeforeLimit(
@@ -202,6 +195,20 @@ class QcFlagService {
202195
to: newInstance.to,
203196
});
204197

198+
if (verify) {
199+
await this.verifyFlag({ flagId: newInstance.id }, relations, { transaction });
200+
}
201+
202+
createdFlag = await QcFlagRepository.findOne({
203+
where: { id: newInstance.id },
204+
include: [
205+
{ association: 'dataPasses' },
206+
{ association: 'simulationPasses' },
207+
{ association: 'createdBy' },
208+
{ association: 'verifications' },
209+
],
210+
});
211+
205212
createdFlags.push(qcFlagAdapter.toEntity(createdFlag));
206213
} catch (error) {
207214
this._logger.warnMessage(`Failed to create QC flag with properties: ${JSON.stringify(qcFlag)}. Error: ${error}`);
@@ -359,9 +366,11 @@ class QcFlagService {
359366
* @param {object} relations QC Flag entity relations
360367
* @param {UserWithRoles} relations.user user identifier with roles
361368
* @return {Promise<QcFlag>} promise
369+
* @param {object} [options] options
370+
* @param {sequelize.Transaction} [options.transaction] sequelize transaction
362371
* @throws {NotFoundError|AccessDeniedError}
363372
*/
364-
async verifyFlag({ flagId, comment }, relations) {
373+
async verifyFlag({ flagId, comment }, relations, { transaction } = {}) {
365374
return dataSource.transaction(async () => {
366375
const qcFlag = await this.getOneOrFail(flagId);
367376
if (qcFlag.deleted) {
@@ -381,7 +390,7 @@ class QcFlagService {
381390
});
382391

383392
return await this.getOneOrFail(flagId);
384-
});
393+
}, { transaction });
385394
}
386395

387396
/**

0 commit comments

Comments
 (0)