Skip to content

Commit d375a8c

Browse files
committed
cherry-pick
1 parent be02074 commit d375a8c

5 files changed

Lines changed: 116 additions & 11 deletions

File tree

lib/domain/entities/QcSummary.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1+
/**
2+
* @typedef QcFlagComment
3+
*
4+
* @property {number} id - QC flag identifier
5+
* @property {string} comment - QC flag comment
6+
*/
7+
18
/**
29
* @typedef RunDetectorQcSummary
310
*
411
* @property {number} badEffectiveRunCoverage - fraction of run's data, marked explicitly with bad QC flag
512
* @property {number} explicitlyNotBadEffectiveRunCoverage - fraction of run's data, marked explicitly with good QC flag
613
* @property {boolean} mcReproducible - if true states that some Limited Acceptance MC Reproducible flag was assigned
714
* @property {number} missingVerificationsCount - number of QC flags that are unverified and have not been discarded
15+
* @property {QcFlagComment[]} flagComments - comments associated with QC flags contributing to the summary
816
*/
917

1018
/**

lib/domain/enums/QcSummaryProperties.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ exports.QcSummarProperties = {
1616
EXPLICITELY_NOT_BAD_EFFECTIVE_RUN_COVERAGE: 'explicitlyNotBadEffectiveRunCoverage',
1717
MISSING_VERIFICATIONS: 'missingVerificationsCount',
1818
MC_REPRODUCIBLE: 'mcReproducible',
19+
FLAG_COMMENTS: 'flagComments',
1920
UNDEFINED_QUALITY_PERIODS_COUNT: 'undefinedQualityPeriodsCount',
2021
};

lib/public/components/common/popover/overflowBalloon.js

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,22 @@ const doesElementOverflow = (element) => {
3131
/**
3232
* Return a component containing the given content, with a popover displayed on hover if the content overflow from its parent
3333
*
34-
* @param {Component} content the content of the component to create
35-
* @param {Object|null} [options=null] a restricted set of popover options
34+
* @param {Component} trigger baloon trigger
35+
* @param {Component} content baloon content
36+
* @param {object} [options] a restricted set of popover options
3637
* @param {boolean} [options.stretch=false] if true, the balloon will be displayed when hovering the element in which this component is displayed
37-
* @return {vnode} the component
38+
* @param {function<triggerNode, boolean>} [options.showCondition=(()=>true)] content show condition
39+
* @param {number} [optionsanchor=PopoverAnchors.TOP_MIDDLE] popover anchor
40+
* @return {Component} the component
3841
*/
39-
export const overflowBalloon = (content, options = null) => {
40-
const { stretch = false } = options || {};
42+
export const balloon = (trigger, content, options = null) => {
43+
const { stretch = false, showCondition = null, anchor = PopoverAnchors.TOP_MIDDLE } = options || {};
4144

4245
return popover(
43-
h('.w-wrapped', content),
46+
trigger,
4447
content,
4548
{
46-
anchor: PopoverAnchors.TOP_MIDDLE,
49+
anchor,
4750
popoverClass: ['p2'],
4851

4952
/**
@@ -96,8 +99,27 @@ export const overflowBalloon = (content, options = null) => {
9699
* @returns {boolean} true if visible, false otherwise
97100
*/
98101
showCondition: function () {
99-
return doesElementOverflow(this.triggerNode.querySelector('.w-wrapped'));
102+
return showCondition
103+
? showCondition(this.triggerNode)
104+
: true;
100105
},
101106
},
102107
);
103108
};
109+
110+
/**
111+
* Return a component containing the given content, with a popover displayed on hover if the content overflow from its parent
112+
*
113+
* @param {Component} content the content of the component to create
114+
* @param {object} [options] a restricted set of popover options
115+
* @param {boolean} [options.stretch=false] if true, the balloon will be displayed when hovering the element in which this component is displayed
116+
* @return {Component} the component
117+
*/
118+
export const overflowBalloon = (content, options = null) => balloon(
119+
h('.w-wrapped', content),
120+
content,
121+
{
122+
showCondition: (triggerNode) => doesElementOverflow(triggerNode.querySelector('.w-wrapped')),
123+
...options,
124+
},
125+
);

lib/public/views/Runs/ActiveColumns/getQcSummaryDisplay.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,19 @@
1111
* or submit itself to any jurisdiction.
1212
*/
1313

14-
import { h, iconWarning, iconBolt } from '/js/src/index.js';
14+
import {
15+
h,
16+
iconWarning,
17+
iconBolt,
18+
PopoverAnchors,
19+
PopoverTriggerPreConfiguration,
20+
} from '/js/src/index.js';
1521
import { formatFloat } from '../../../utilities/formatting/formatFloat.js';
1622
import { QcSummaryColors } from '../../../components/qcFlags/qcSummaryColors.js';
1723
import { tooltip } from '../../../components/common/popover/tooltip.js';
1824
import { getContrastColor } from '../../../components/common/colors.js';
25+
import { balloon } from '../../../components/common/popover/overflowBalloon.js';
26+
import { qcFlagTypeColoredBadge } from '../../../components/qcFlags/qcFlagTypeColoredBadge.js';
1927

2028
const FULL_COVERAGE = 1;
2129

@@ -31,6 +39,7 @@ export const getQcSummaryDisplay = (summary) => {
3139
explicitlyNotBadEffectiveRunCoverage,
3240
missingVerificationsCount,
3341
mcReproducible,
42+
flagComments = [],
3443
} = summary;
3544

3645
const missingVerificationDisplay = missingVerificationsCount
@@ -102,5 +111,25 @@ export const getQcSummaryDisplay = (summary) => {
102111
);
103112
}
104113

114+
if (flagComments.length > 0) {
115+
const commentsContent = h(
116+
'.p2.flex-column.g2',
117+
flagComments.map(({ id, comment, flagType }) => h('.flex-column.g1', { key: id }, [
118+
h('.f7.gray.flex-row.g1', [`Flag ${id}`, qcFlagTypeColoredBadge(flagType)]),
119+
h('.f6', { style: { whiteSpace: 'pre-wrap' } }, comment),
120+
h('.section-divider'),
121+
])),
122+
);
123+
124+
qcSummaryDisplay = balloon(
125+
qcSummaryDisplay,
126+
commentsContent,
127+
{
128+
...PopoverTriggerPreConfiguration.hover,
129+
anchor: PopoverAnchors.BOTTOM_MIDDLE,
130+
},
131+
);
132+
}
133+
105134
return qcSummaryDisplay;
106135
};

lib/server/services/qualityControlFlag/QcFlagSummaryService.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,17 +159,44 @@ class QcFlagSummaryService {
159159
? Math.min(1, Math.max(0, parseFloat(effectiveRunCoverageString)))
160160
: null;
161161

162+
const flagIds = (summaryDb.get('flagIds')?.split(',') ?? [])
163+
.map((id) => parseInt(id, 10))
164+
.filter(Number.isFinite)
165+
.sort((a, b) => a - b);
166+
162167
return {
163168
runNumber: summaryDb.runNumber,
164169
detectorId: summaryDb.detectorId,
165170
effectiveRunCoverage,
166171
bad: Boolean(summaryDb.get('bad')),
167-
flagIds: (summaryDb.get('flagIds')?.split(',') ?? []).map((id) => parseInt(id, 10)),
172+
flagIds,
168173
mcReproducible: Boolean(summaryDb.get('mcReproducible')),
169174
};
170175
});
171176

172177
const allFlagsIds = new Set(runDetectorSummaryList.flatMap(({ flagIds }) => flagIds));
178+
179+
const miniCommentedFlagByFlagId = new Map();
180+
if (allFlagsIds.size > 0) {
181+
const flagsWithComments = await QcFlagRepository.findAll({
182+
attributes: ['id', 'comment'],
183+
where: { id: { [Op.in]: [...allFlagsIds] } },
184+
include: [{ association: 'flagType' }],
185+
});
186+
187+
for (const { id, comment, flagType: { name, color } } of flagsWithComments) {
188+
if (comment?.trim()) {
189+
miniCommentedFlagByFlagId.set(id, { comment, flagType: { name, color } });
190+
}
191+
}
192+
}
193+
194+
for (const summaryUnit of runDetectorSummaryList) {
195+
summaryUnit.flagComments = summaryUnit.flagIds
196+
.map((id) => ({ id, ...miniCommentedFlagByFlagId.get(id) }))
197+
.filter(({ comment }) => Boolean(comment));
198+
}
199+
173200
const notVerifiedFlagsIds = new Set((await QcFlagRepository.findAll({
174201
attributes: ['id'],
175202
include: [{ association: 'verifications', required: false, attributes: [] }],
@@ -187,21 +214,39 @@ class QcFlagSummaryService {
187214
runNumber,
188215
detectorId,
189216
flagIds,
217+
flagComments,
190218
} = runDetectorSummaryForFlagTypesClass;
191219
const missingVerificationsCount = flagIds.filter((id) => notVerifiedFlagsIds.has(id)).length;
192220

193221
if (!summary[runNumber]) {
194222
summary[runNumber] = {};
195223
}
196224
if (!summary[runNumber][detectorId]) {
197-
summary[runNumber][detectorId] = { [QcSummarProperties.MC_REPRODUCIBLE]: false };
225+
summary[runNumber][detectorId] = {
226+
[QcSummarProperties.MC_REPRODUCIBLE]: false,
227+
[QcSummarProperties.FLAG_COMMENTS]: [],
228+
};
198229
}
199230

200231
const runDetectorSummary = summary[runNumber][detectorId];
201232

202233
runDetectorSummary[QcSummarProperties.MISSING_VERIFICATIONS] =
203234
(runDetectorSummary[QcSummarProperties.MISSING_VERIFICATIONS] ?? 0) + missingVerificationsCount;
204235

236+
if (!runDetectorSummary[QcSummarProperties.FLAG_COMMENTS]) {
237+
runDetectorSummary[QcSummarProperties.FLAG_COMMENTS] = [];
238+
}
239+
if (flagComments.length > 0) {
240+
const existingFlagComments = runDetectorSummary[QcSummarProperties.FLAG_COMMENTS];
241+
const existingIds = new Set(existingFlagComments.map(({ id }) => id));
242+
for (const miniCommentedFlagByFlagId of flagComments) {
243+
if (!existingIds.has(miniCommentedFlagByFlagId.id)) {
244+
existingFlagComments.push(miniCommentedFlagByFlagId);
245+
existingIds.add(miniCommentedFlagByFlagId.id);
246+
}
247+
}
248+
}
249+
205250
QcFlagSummaryService.mergeIntoSummaryUnit(runDetectorSummary, runDetectorSummaryForFlagTypesClass);
206251
}
207252

0 commit comments

Comments
 (0)