Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ export class TimeRangeInputModel extends FilterModel {
constructor(value, periodLabel, configuration) {
super();

const { required = false, seconds } = configuration || {};
const { required = false, seconds, milliseconds } = configuration || {};
this._required = required;

this._fromTimeInputModel = new DateTimeInputModel({ seconds });
this._toTimeInputModel = new DateTimeInputModel({ seconds });
this._fromTimeInputModel = new DateTimeInputModel({ seconds, milliseconds });
this._toTimeInputModel = new DateTimeInputModel({ seconds, milliseconds });

// eslint-disable-next-line no-return-assign
this._fromTimeInputModel.observe(() => this._periodLabel = null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { formatTimestampForDateTimeInput } from '../../../../utilities/formattin
* @property {function} onChange function called with the new value when it changes
* @property {Partial<DateTimeInputRawData>} [defaults] the default raw values to use when partially updating inputs
* @property {boolean} [seconds=false] states if the input has granularity up to seconds (if not, granularity is minutes)
* @property {boolean} [milliseconds=false] states if the input has granularity up to milliseconds
* @property {boolean} [required] states if the inputs can be omitted
* @property {number} [min] the timestamp of the minimum date allowed by the input (included)
* @property {number} [max] the timestamp of the maximum date allowed by the input (excluded)
Expand Down Expand Up @@ -94,7 +95,7 @@ export class DateTimeInputComponent extends StatefulComponent {
type: 'time',
required: this._required,
value: this._value.time,
step: this._seconds ? 1 : undefined,
step: this._milliseconds ? 0.001 : this._seconds ? 1 : undefined,
onchange: (e) => this._patchValue({ time: e.target.value }),
// Mithril do not remove min/max if previously set...
min: inputsMin?.time ?? '',
Expand All @@ -117,13 +118,14 @@ export class DateTimeInputComponent extends StatefulComponent {
* @private
*/
_updateAttrs(attrs) {
const { value, onChange, seconds, required = false, defaults = {}, min = null, max = null } = attrs;
const { value, onChange, seconds, milliseconds, required = false, defaults = {}, min = null, max = null } = attrs;
const { date: defaultDate = '', time: defaultTime = '' } = defaults;

this._value = value;
this._onChange = onChange;

this._seconds = seconds;
this._milliseconds = milliseconds;

this._required = required;

Expand Down Expand Up @@ -179,15 +181,19 @@ export class DateTimeInputComponent extends StatefulComponent {
const rawDate = raw.date || null;
const rawTime = raw.time || null;

const minDateAndTime = formatTimestampForDateTimeInput(minTimestamp, this._seconds);
const minDateAndTime = formatTimestampForDateTimeInput(minTimestamp, this._seconds || this._milliseconds, this._milliseconds);
const inputsMin = {};

if (rawDate !== null && rawDate === minDateAndTime.date) {
inputsMin.time = minDateAndTime.time;
}

if (rawTime !== null && rawTime < minDateAndTime.time) {
inputsMin.date = formatTimestampForDateTimeInput(minTimestamp + MILLISECONDS_IN_ONE_DAY, this._seconds).date;
inputsMin.date = formatTimestampForDateTimeInput(
minTimestamp + MILLISECONDS_IN_ONE_DAY,
this._seconds || this._milliseconds,
this._milliseconds,
).date;
} else {
inputsMin.date = minDateAndTime.date;
}
Expand All @@ -210,14 +216,14 @@ export class DateTimeInputComponent extends StatefulComponent {
const rawDate = raw.date || null;
const rawTime = raw.time || null;

const maxDateAndTime = formatTimestampForDateTimeInput(maxTimestamp, this._seconds);
const maxDateAndTime = formatTimestampForDateTimeInput(maxTimestamp, this._seconds, this._milliseconds);
const inputsMax = {};

if (rawDate !== null && rawDate === maxDateAndTime.date) {
inputsMax.time = maxDateAndTime.time;
}
if (rawTime !== null && rawTime > maxDateAndTime.time) {
inputsMax.date = formatTimestampForDateTimeInput(maxTimestamp - MILLISECONDS_IN_ONE_DAY, this._seconds).date;
inputsMax.date = formatTimestampForDateTimeInput(maxTimestamp - MILLISECONDS_IN_ONE_DAY, this._seconds, this._milliseconds).date;
} else {
inputsMax.date = maxDateAndTime.date;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ export class DateTimeInputModel extends Observable {
* Constructor
* @param {object} [configuration] the model configuration
* @param {boolean} [configuration.seconds=false] states if the date time input granularity is seconds (else, it is minutes)
* @param {boolean} [configuration.milliseconds=false] states if the date time input granularity is milliseconds
*/
constructor(configuration) {
super();

const { seconds } = configuration || {};
const { seconds = false, milliseconds = false } = configuration || {};
this._seconds = seconds;
this._milliseconds = milliseconds;

/**
* @type {DateTimeInputRawData}
Expand Down Expand Up @@ -129,7 +131,7 @@ export class DateTimeInputModel extends Observable {

this._value = value;
this._raw = value !== null
? formatTimestampForDateTimeInput(value, this._seconds)
? formatTimestampForDateTimeInput(value, this._seconds || this._milliseconds, this._milliseconds)
: { date: '', time: '' };

!silent && this.notify();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ import { DateTimeInputComponent } from '../../../common/form/inputs/DateTimeInpu
* @param {TimeRangeInputComponentOptions} options inputs options
* @return {Component} time range input component
*/
export const timeRangeInput = (timeRangeInputModel, { seconds, min, max, fromInputConfiguration, toInputConfiguration } = {}) => {
export const timeRangeInput = (
timeRangeInputModel,
{ milliseconds, seconds, min, max, fromInputConfiguration, toInputConfiguration } = {},
) => {
const { label: fromLabel = h('', 'From') } = fromInputConfiguration || {};
const { label: toLabel = h('', 'To') } = toInputConfiguration || {};

Expand All @@ -55,7 +58,7 @@ export const timeRangeInput = (timeRangeInputModel, { seconds, min, max, fromInp
toMin = fromValue;
}

const timeStep = seconds ? 1000 : 60 * 1000;
const timeStep = milliseconds ? 1 : seconds ? 1000 : 60 * 1000;
return h('.flex-column.g3', [
h('.flex-column.g1', [
h('label.flex-row.g2', fromLabel),
Expand All @@ -64,6 +67,7 @@ export const timeRangeInput = (timeRangeInputModel, { seconds, min, max, fromInp
onChange: (value) => timeRangeInputModel.fromTimeInputModel.update(value),
required: timeRangeInputModel.isRequired(),
seconds,
milliseconds,
min,
max: fromMax ? fromMax - timeStep : null,
}),
Expand All @@ -75,6 +79,7 @@ export const timeRangeInput = (timeRangeInputModel, { seconds, min, max, fromInp
onChange: (value) => timeRangeInputModel.toTimeInputModel.update(value),
required: timeRangeInputModel.isRequired(),
seconds,
milliseconds,
min: toMin ? toMin + timeStep : null,
max,
}),
Expand Down
16 changes: 12 additions & 4 deletions lib/public/utilities/formatting/dateTimeInputFormatters.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
*
* @param {number} timestamp the timestamp (ms) to format
* @param {boolean} enableSeconds states if the time input granularity is seconds (else, it is minutes)
* @param {boolean} enableMilliseconds states if the time input granularity is milliseconds
* @return {DateTimeInputRawData} the date expression to use as HTML input values
*/
export const formatTimestampForDateTimeInput = (timestamp, enableSeconds) => {
export const formatTimestampForDateTimeInput = (timestamp, enableSeconds, enableMilliseconds) => {
const date = new Date(timestamp);

/**
Expand All @@ -40,6 +41,10 @@ export const formatTimestampForDateTimeInput = (timestamp, enableSeconds) => {
const seconds = pad(date.getSeconds());
timeExpression = `${timeExpression}:${seconds}`;
}
if (enableMilliseconds) {
const milliseconds = `${date.getMilliseconds()}`.padStart(3, '0');
timeExpression = `${timeExpression}.${milliseconds}`;
}

return { date: dateExpression, time: timeExpression };
};
Expand All @@ -51,10 +56,13 @@ export const formatTimestampForDateTimeInput = (timestamp, enableSeconds) => {
* @return {number} the resulting timestamp
*/
export const extractTimestampFromDateTimeInput = ({ date, time }) => {
// Add seconds if they are not already here
if (time.length === 5) {
time = `${time}:00`;
// HH:MM -> HH:MM:SS.mmm
time = `${time}:00.000`;
} else if (time.length === 8) {
// HH:MM:SS -> HH:MM:SS.mmm
time = `${time}.000`;
}

return Date.parse(`${date}T${time}.000`);
return Date.parse(`${date}T${time}`);
};
6 changes: 3 additions & 3 deletions lib/public/views/QcFlags/Create/QcFlagCreationModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class QcFlagCreationModel extends Observable {
this._startTime = startTime;
this._endTime = endTime;

this._timeRangeModel = new TimeRangeInputModel(null, null, { required: true, seconds: true });
this._timeRangeModel = new TimeRangeInputModel(null, null, { required: true, seconds: true, milliseconds: true });
this._timeRangeModel.fromTimeInputModel.setValue(startTime);
this._timeRangeModel.toTimeInputModel.setValue(endTime);
this._timeRangeModel.bubbleTo(this);
Expand Down Expand Up @@ -140,9 +140,9 @@ export class QcFlagCreationModel extends Observable {
for (const run of runs) {
const { firstTfTimestamp, timeTrgStart, timeO2Start, lastTfTimestamp, timeTrgEnd, timeO2End } = run;

const startTime = Math.ceil((firstTfTimestamp ?? timeTrgStart ?? timeO2Start ?? null) / 1000) * 1000; // Round to next second
const startTime = firstTfTimestamp ?? timeTrgStart ?? timeO2Start ?? null;
// Do not use run's endTime because it's automatically coalesced to `now`
const endTime = Math.floor(lastTfTimestamp ?? timeTrgEnd ?? timeO2End ?? null); // Round to previous second
const endTime = lastTfTimestamp ?? timeTrgEnd ?? timeO2End ?? null; // Round to previous second

if (!startTime) {
timings.startTime = null;
Expand Down
6 changes: 3 additions & 3 deletions lib/public/views/QcFlags/Create/qcFlagCreationComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ const selectedRunsAndDetectorsTable = (runsWithDetectors) => h(
h('tbody', runsWithDetectors.map(({ run, detectors }) => h('tr', [
h('td', frontLink(run.runNumber, 'run-detail', { runNumber: run.runNumber })),
h('td', detectors.map((detector) => detector.name).join(', ')),
h('td', formatRunStart(run, { qc: true })),
h('td', formatRunEnd(run, { qc: true })),
h('td', formatRunStart(run, { qc: true, displayMilliseconds: true })),
h('td', formatRunEnd(run, { qc: true, displayMilliseconds: true })),
]))),
]),
],
Expand All @@ -74,7 +74,7 @@ const qcFlagCreationForm = (creationModel) => {
} else if (startTime > endTime) {
content = h('em', 'The selected runs don\'t have overlapping start/stop times');
} else if (isTimeBasedQcFlag) {
content = timeRangeInput(timeRangeModel, { seconds: true, min: startTime, max: endTime });
content = timeRangeInput(timeRangeModel, { seconds: true, milliseconds: true, min: startTime, max: endTime });
} else {
content = h('em', 'The flag will be applied on the full run');
}
Expand Down
13 changes: 8 additions & 5 deletions lib/public/views/Runs/format/formatRunEnd.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js';
import { h, iconWarning } from '/js/src/index.js';
import { getLocaleDateAndTime } from '../../../utilities/dateUtils.mjs';
import { getLocaleDateAndTime, getLocaleDateAndTimeWithMilliseconds } from '../../../utilities/dateUtils.mjs';
import { tooltip } from '../../../components/common/popover/tooltip.js';
import { TriggerValue } from '../../../domain/enums/TriggerValue.js';

Expand All @@ -26,10 +26,11 @@ const MISSING_TRIGGER_STOP_WARNING = 'O2 stop is displayed because trigger stop
* @param {object} [configuration] eventual display configuration
* @param {boolean} [configuration.inline] true if the date must be inlined
* @param {boolean} [configuration.qc] true if QC run end must be displayed, i.e. if last TF timestamp must be used if available
* @param {boolean} [configuration.milliseconds] true if the date must be displayed with milliseconds
* @return {Component} the formatted end date
*/
export const formatRunEnd = (run, configuration) => {
const { inline = false, qc = false } = configuration || {};
const { inline = false, qc = false, displayMilliseconds = false } = configuration || {};
const { timeTrgEnd, timeO2End, lastTfTimestamp, triggerValue } = run;

let runEnd = timeTrgEnd || timeO2End;
Expand All @@ -38,19 +39,21 @@ export const formatRunEnd = (run, configuration) => {
}

if (timeTrgEnd || triggerValue === TriggerValue.Off) {
return formatTimestamp(runEnd, inline);
return formatTimestamp(runEnd, inline, displayMilliseconds);
}

if (timeO2End) {
if (inline) {
return h('span', [
h('.flex-row.items-center.g2', [
formatTimestamp(runEnd, inline),
formatTimestamp(runEnd, inline, displayMilliseconds),
tooltip(iconWarning(), MISSING_TRIGGER_STOP_WARNING),
]),
]);
} else {
const { date, time } = getLocaleDateAndTime(runEnd);
const { date, time } = displayMilliseconds
? getLocaleDateAndTimeWithMilliseconds(runEnd)
: getLocaleDateAndTime(runEnd);
return h('', [
h('', date),
h('.flex-row.g2.items-center', [h('', time), tooltip(iconWarning(), MISSING_TRIGGER_STOP_WARNING)]),
Expand Down
14 changes: 8 additions & 6 deletions lib/public/views/Runs/format/formatRunStart.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js';
import { h, iconWarning } from '/js/src/index.js';
import { getLocaleDateAndTime } from '../../../utilities/dateUtils.mjs';
import { getLocaleDateAndTime, getLocaleDateAndTimeWithMilliseconds } from '../../../utilities/dateUtils.mjs';
import { tooltip } from '../../../components/common/popover/tooltip.js';
import { TriggerValue } from '../../../domain/enums/TriggerValue.js';

Expand All @@ -26,31 +26,33 @@ const MISSING_TRIGGER_START_WARNING = 'O2 start is displayed because trigger sto
* @param {object} [configuration] eventual display configuration
* @param {boolean} [configuration.inline] true if the date must be inlined
* @param {boolean} [configuration.qc] true if QC run start must be displayed, i.e. if first TF timestamp must be used if available
* @param {boolean} [configuration.displayMilliseconds] true if the date must be displayed with milliseconds
* @return {Component} the formatted start date
*/
export const formatRunStart = (run, configuration) => {
const { inline = false, qc = false } = configuration || {};
const { inline = false, qc = false, displayMilliseconds = false } = configuration || {};
const { timeO2Start, timeTrgStart, firstTfTimestamp, triggerValue } = run;

let runStart = timeTrgStart || timeO2Start;
if (qc) {
runStart = firstTfTimestamp || runStart;
}

if (timeTrgStart || triggerValue === TriggerValue.Off) {
return formatTimestamp(runStart, inline);
return formatTimestamp(runStart, inline, displayMilliseconds);
}

if (timeO2Start) {
if (inline) {
return h('span', [
h('.flex-row.items-center.g2', [
formatTimestamp(runStart, inline),
formatTimestamp(runStart, inline, displayMilliseconds),
tooltip(iconWarning(), MISSING_TRIGGER_START_WARNING),
]),
]);
} else {
const { date, time } = getLocaleDateAndTime(runStart);
const { date, time } = displayMilliseconds
? getLocaleDateAndTimeWithMilliseconds(runStart)
: getLocaleDateAndTime(runStart);
return h('', [
h('', date),
h('.flex-row.g2.items-center', [h('', time), tooltip(iconWarning(), MISSING_TRIGGER_START_WARNING)]),
Expand Down
16 changes: 8 additions & 8 deletions test/public/qcFlags/forDataPassCreation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ module.exports = () => {
});

await page.waitForSelector('button#submit[disabled]');
await expectInnerText(page, 'table > tbody > tr > td:nth-child(3) > div', '08/08/2019\n13:00:00');
await expectInnerText(page, 'table > tbody > tr > td:nth-child(4) > div', '09/08/2019\n14:00:00');
await expectInnerText(page, 'table > tbody > tr > td:nth-child(3) > div', '08/08/2019\n13:00:00.000');
await expectInnerText(page, 'table > tbody > tr > td:nth-child(4) > div', '09/08/2019\n14:00:00.000');
await page.waitForSelector('input[type="time"]', { hidden: true, timeout: 250 });

await pressElement(page, '#flag-type-panel .popover-trigger');
Expand Down Expand Up @@ -134,8 +134,8 @@ module.exports = () => {
});

await page.waitForSelector('button#submit[disabled]');
await expectInnerText(page, 'table > tbody > tr > td:nth-child(3) > div', '08/08/2019\n13:00:00');
await expectInnerText(page, 'table > tbody > tr > td:nth-child(4) > div', '09/08/2019\n14:00:00');
await expectInnerText(page, 'table > tbody > tr > td:nth-child(3) > div', '08/08/2019\n13:00:00.000');
await expectInnerText(page, 'table > tbody > tr > td:nth-child(4) > div', '09/08/2019\n14:00:00.000');
await page.waitForSelector('input[type="time"]', { hidden: true });

await pressElement(page, '#flag-type-panel .popover-trigger');
Expand All @@ -144,8 +144,8 @@ module.exports = () => {
await page.waitForSelector('button#submit[disabled]', { hidden: true });
await pressElement(page, '#time-based-toggle', true);

await fillInput(page, 'div:nth-child(1) > div > input:nth-child(2)', '13:01:01', ['change']);
await fillInput(page, 'div:nth-child(2) > div > input:nth-child(2)', '13:50:59', ['change']);
await fillInput(page, 'div:nth-child(1) > div > input:nth-child(2)', '13:01:01.123', ['change']);
await fillInput(page, 'div:nth-child(2) > div > input:nth-child(2)', '13:50:59.456', ['change']);

await waitForNavigation(page, () => pressElement(page, 'button#submit'));
expectUrlParams(page, {
Expand All @@ -158,8 +158,8 @@ module.exports = () => {

await expectRowValues(page, 1, {
flagType: 'Limited acceptance',
from: '08/08/2019,\n13:01:01.000',
to: '09/08/2019,\n13:50:59.000',
from: '08/08/2019,\n13:01:01.123',
to: '09/08/2019,\n13:50:59.456',
});
});

Expand Down
Loading
Loading