const assert = require("assert"); const config = ({ timeformat }) => `
`; const scenarios = { "Works with data sorted ascending by time column": { timeseries: { time: [-1, 0, 1, 2, 3], one: [13.11794020069087, 95.2190411666738, 37.03620982977559, 16.8961637786484, 49.2981075645916], two: [-5.653544080516028, 10.07458649100271, 0, -20.140046051127193, 32.40194378594322], }, assert(I) { I.waitForVisible(".htx-timeseries", 5); I.dontSeeElement(locate(".lsf-errors")); }, }, "Errors with data sorted descending by time column": { timeseries: { time: [3, 2, 1], one: [37.03620982977559, 16.8961637786484, 49.2981075645916], two: [0, -20.140046051127193, 32.40194378594322], }, assert(I) { I.seeElement(locate(".lsf-errors")); }, }, "Errors with data not sorted by time column": { timeseries: { time: [1, 3, 2], one: [37.03620982977559, 16.8961637786484, 49.2981075645916], two: [0, -20.140046051127193, 32.40194378594322], }, assert(I) { I.seeElement(locate(".lsf-errors")); }, }, "Works with data formatted and sorted ascending by time column": { timeformat: "%Y-%m-%d %H:%M:%S", timeseries: { time: ["2022-02-07 00:50:00", "2022-02-07 00:51:00", "2022-02-07 00:52:00"], one: [37.03620982977559, 16.8961637786484, 49.2981075645916], two: [0, -20.140046051127193, 32.40194378594322], }, assert(I) { I.waitForVisible(".htx-timeseries", 5); I.dontSeeElement(locate(".lsf-errors")); }, }, "Errors with data formatted and sorted descending by time column": { timeformat: "%Y-%m-%d %H:%M:%S", timeseries: { time: ["2022-02-07 00:52:00", "2022-02-07 00:51:00", "2022-02-07 00:50:00"], one: [37.03620982977559, 16.8961637786484, 49.2981075645916], two: [0, -20.140046051127193, 32.40194378594322], }, assert(I) { I.seeElement(locate(".lsf-errors")); }, }, "Errors with data formatted and not sorted by time column": { timeformat: "%Y-%m-%d %H:%M:%S", timeseries: { time: ["2022-02-07 00:50:00", "2022-02-07 00:52:00", "2022-02-07 00:51:00"], one: [37.03620982977559, 16.8961637786484, 49.2981075645916], two: [0, -20.140046051127193, 32.40194378594322], }, assert(I) { I.seeElement(locate(".lsf-errors")); }, }, "Works with microseconds in time column and %f timeFormat": { timeformat: "%Y-%m-%d %H:%M:%S.%f", timeseries: { time: ["2024-07-15 12:34:56.123456", "2024-07-15 12:34:57.654321", "2024-07-15 12:34:58.000123"], one: [1, 2, 3], two: [4, 5, 6], }, assert(I) { I.waitForVisible(".htx-timeseries", 5); I.dontSeeElement(locate(".lsf-errors")); }, }, }; function generateData(stepsNumber) { const timeseries = { time: [], one: [], two: [], }; for (let i = 0; i < stepsNumber; i++) { timeseries.time[i] = i; timeseries.one[i] = Math.sin(Math.sqrt(i)); timeseries.two[i] = Math.cos(Math.sqrt(i)); } return timeseries; } Feature("TimeSeries datasets"); Object.entries(scenarios).forEach(([title, scenario]) => Scenario(title, async ({ I, LabelStudio }) => { const cfg = config(scenario); const params = { annotations: [{ id: "test", result: [] }], config: cfg, data: { timeseries: scenario.timeseries }, }; // const configTree = Utils.parseXml(config); await I.amOnPage("/"); LabelStudio.init(params); scenario.assert(I); }), ); Scenario("TimeSeries with optimized data", async ({ I, LabelStudio, AtTimeSeries }) => { async function doNotSeeProblems() { await I.wait(2); I.seeElement(".htx-timeseries"); // The potential errors should be caught by `errorsCollector` plugin const counters = await I.executeScript(() => { return { NaN: document.querySelectorAll("[d*='NaN']").length + document.querySelectorAll("[cy*='NaN']").length + document.querySelectorAll("[transform*='NaN']").length, Infinity: document.querySelectorAll("[transform*='Infinity']").length, }; }); if (counters.NaN) { assert.fail("Found element with NaN in attribute"); } if (counters.Infinity) { assert.fail("Found element with Infinity in attribute"); } } I.amOnPage("/"); const SLICES_COUNT = 10; const BAD_MULTIPLIER = 1.9; const screenWidth = await I.executeScript(() => { return window.screen.width * window.devicePixelRatio; }); const stepsToGenerate = screenWidth * SLICES_COUNT * BAD_MULTIPLIER; const params = { annotations: [ { id: "test", result: [], }, ], config: config({}), data: { timeseries: generateData(stepsToGenerate), }, }; LabelStudio.init(params); I.waitForVisible(".htx-timeseries"); I.say("try to get errors by selecting overview range"); await AtTimeSeries.selectOverviewRange(0.98, 1); await doNotSeeProblems(); I.say("try to get errors by zooming in by mouse wheel"); I.pressKeyDown("CommandOrControl"); for (let i = 0; i < 10; i++) { await AtTimeSeries.zoomByMouse(-100, { x: 0.98 }); } I.pressKeyUp("CommandOrControl"); await doNotSeeProblems(); I.say("try to get errors by moving handle to the extreme position"); await AtTimeSeries.moveHandle(1.1); await AtTimeSeries.moveHandle(1.1); await doNotSeeProblems(); I.say("try to get errors by moving overview range by click"); await AtTimeSeries.clickOverview(0.15); await doNotSeeProblems(); I.say("try to get errors by creating micro ranges at overview"); await AtTimeSeries.selectOverviewRange(0.9, 0.9001); await doNotSeeProblems(); await AtTimeSeries.selectOverviewRange(0.9, 0.8999); await doNotSeeProblems(); I.say("check that every timestamps from timeseries data is available to display"); await AtTimeSeries.selectOverviewRange(0.001, 0.0); for (let i = 0; i < 20; i++) { await AtTimeSeries.zoomByMouse(-100, { x: 0.001 }); } let lastTimestamp; for (let i = 0; i < 15; i++) { AtTimeSeries.moveMouseOverChannel({ x: 0.01 + 0.025 * i }); const timestamp = await AtTimeSeries.grabStickTime(); if (lastTimestamp !== undefined) { I.say(`I see ${timestamp}`); assert( timestamp === lastTimestamp || timestamp - lastTimestamp === 1, `Timestamps should not be skipped. Got ${lastTimestamp} and ${timestamp} but ${timestamp - 1} is missed`, ); } lastTimestamp = timestamp; } });