mirror of
https://github.com/grafana/grafana.git
synced 2025-01-13 01:22:05 -06:00
StateTimeline: Treat second time field as state endings (#84130)
This commit is contained in:
parent
0b71354c8d
commit
57df3b84dc
271
devenv/dev-dashboards/panel-timeline/timeline-align-endtime.json
Normal file
271
devenv/dev-dashboards/panel-timeline/timeline-align-endtime.json
Normal file
@ -0,0 +1,271 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": 988,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 70,
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineWidth": 0,
|
||||
"spanNulls": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 15,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 1,
|
||||
"options": {
|
||||
"alignValue": "left",
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"mergeValues": true,
|
||||
"rowHeight": 0.9,
|
||||
"showValue": "auto",
|
||||
"tooltip": {
|
||||
"maxHeight": 600,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ]\n },\n \"name\": \"A\",\n \"fields\": [\n {\n \"name\": \"channel\",\n \"config\": {\n \"selector\": \"channel\"\n },\n \"type\": \"string\"\n },\n {\n \"name\": \"name\",\n \"config\": {\n \"selector\": \"name\"\n },\n \"type\": \"string\"\n },\n {\n \"name\": \"starttime\",\n \"config\": {\n \"selector\": \"starttime\"\n },\n \"type\": \"string\"\n },\n {\n \"name\": \"endtime\",\n \"config\": {\n \"selector\": \"endtime\"\n },\n \"type\": \"string\"\n },\n {\n \"name\": \"duration_minutes\",\n \"config\": {\n \"selector\": \"duration_minutes\"\n },\n \"type\": \"number\"\n },\n {\n \"name\": \"state\",\n \"config\": {\n \"selector\": \"state\"\n },\n \"type\": \"string\"\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n \"Channel 1\",\n \"Channel 2\",\n \"Channel 1\",\n \"Channel 2\"\n ],\n [\n \"Event 1\",\n \"Event 2\",\n \"Event 3\",\n \"Event 4\"\n ],\n [\n \"2024-02-28T08:00:00Z\",\n \"2024-02-28T09:00:00Z\",\n \"2024-02-28T11:00:00Z\",\n \"2024-02-28T12:30:00Z\"\n ],\n [\n \"2024-02-28T10:00:00Z\",\n \"2024-02-28T10:30:00Z\",\n \"2024-02-28T14:00:00Z\",\n \"2024-02-28T13:30:00Z\"\n ],\n [\n 120,\n 90,\n 180,\n 60\n ],\n [\n \"OK\",\n \"ERROR\",\n \"NO_DATA\",\n \"WARNING\"\n ]\n ]\n }\n }\n]",
|
||||
"refId": "A",
|
||||
"scenarioId": "raw_frame"
|
||||
}
|
||||
],
|
||||
"title": "Raw frames w/enums",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"channel": false,
|
||||
"duration_minutes": true,
|
||||
"name": true
|
||||
},
|
||||
"includeByName": {},
|
||||
"indexByName": {},
|
||||
"renameByName": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "convertFieldType",
|
||||
"options": {
|
||||
"conversions": [
|
||||
{
|
||||
"destinationType": "time",
|
||||
"enumConfig": {
|
||||
"text": [
|
||||
"2024-02-28T08:00:00Z",
|
||||
"2024-02-28T09:00:00Z",
|
||||
"2024-02-28T11:00:00Z",
|
||||
"2024-02-28T12:30:00Z"
|
||||
]
|
||||
},
|
||||
"targetField": "starttime"
|
||||
},
|
||||
{
|
||||
"destinationType": "time",
|
||||
"targetField": "endtime"
|
||||
},
|
||||
{
|
||||
"destinationType": "enum",
|
||||
"enumConfig": {
|
||||
"text": [
|
||||
"OK",
|
||||
"ERROR",
|
||||
"NO_DATA",
|
||||
"WARNING"
|
||||
]
|
||||
},
|
||||
"targetField": "state"
|
||||
}
|
||||
],
|
||||
"fields": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "partitionByValues",
|
||||
"options": {
|
||||
"fields": [
|
||||
"channel"
|
||||
],
|
||||
"keepFields": false,
|
||||
"naming": {
|
||||
"asLabels": false
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "state-timeline"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 70,
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineWidth": 0,
|
||||
"spanNulls": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 12,
|
||||
"w": 15,
|
||||
"x": 0,
|
||||
"y": 13
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"alignValue": "left",
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"mergeValues": true,
|
||||
"rowHeight": 0.9,
|
||||
"showValue": "auto",
|
||||
"tooltip": {
|
||||
"maxHeight": 600,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"alias": "Channel 1",
|
||||
"csvContent": "starttime,endtime,state\n1709107200000,1709114400000,OK\n1709118000000,1709128800000,NO_DATA",
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_content"
|
||||
},
|
||||
{
|
||||
"alias": "Channel 2",
|
||||
"csvContent": "starttime,endtime,state\n1709110800000,1709116200000,ERROR\n1709123400000,1709127000000,WARNING",
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "CSV content",
|
||||
"type": "state-timeline"
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 39,
|
||||
"tags": [
|
||||
"gdev",
|
||||
"panel-tests",
|
||||
"graph-ng",
|
||||
"demo"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "2024-02-28T07:47:21.428Z",
|
||||
"to": "2024-02-28T14:12:43.391Z"
|
||||
},
|
||||
"timeRangeUpdatedDuringEditOrView": false,
|
||||
"timepicker": {},
|
||||
"timezone": "browser",
|
||||
"title": "Panel Tests - StateTimeline - multiple frames with endTime",
|
||||
"uid": "cdf3gkge5reo0f",
|
||||
"version": 4,
|
||||
"weekStart": ""
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -105,6 +105,8 @@
|
||||
"testdata_alerts": (import '../dev-dashboards/alerting/testdata_alerts.json'),
|
||||
"text-options": (import '../dev-dashboards/panel-text/text-options.json'),
|
||||
"time_zone_support": (import '../dev-dashboards/scenarios/time_zone_support.json'),
|
||||
"timeline-align-endtime": (import '../dev-dashboards/panel-timeline/timeline-align-endtime.json'),
|
||||
"timeline-align-nulls-retain": (import '../dev-dashboards/panel-timeline/timeline-align-nulls-retain.json'),
|
||||
"timeline-demo": (import '../dev-dashboards/panel-timeline/timeline-demo.json'),
|
||||
"timeline-modes": (import '../dev-dashboards/panel-timeline/timeline-modes.json'),
|
||||
"timeline-thresholds-mappings": (import '../dev-dashboards/panel-timeline/timeline-thresholds-mappings.json'),
|
||||
|
@ -101,7 +101,11 @@ export function joinDataFrames(options: JoinOptions): DataFrame | undefined {
|
||||
}
|
||||
|
||||
const nullMode =
|
||||
options.nullMode ?? ((field: Field) => (field.config.custom?.spanNulls === true ? NULL_REMOVE : NULL_EXPAND));
|
||||
options.nullMode ??
|
||||
((field: Field) => {
|
||||
let spanNulls = field.config.custom?.spanNulls;
|
||||
return spanNulls === true ? NULL_REMOVE : spanNulls === -1 ? NULL_RETAIN : NULL_EXPAND;
|
||||
});
|
||||
|
||||
if (options.frames.length === 1) {
|
||||
let frame = options.frames[0];
|
||||
|
@ -107,8 +107,14 @@ export function preparePlotFrame(frames: DataFrame[], dimFields: XYFieldMatchers
|
||||
// prevent minesweeper-expansion of nulls (gaps) when joining bars
|
||||
// since bar width is determined from the minimum distance between non-undefined values
|
||||
// (this strategy will still retain any original pre-join nulls, though)
|
||||
nullMode: (field) =>
|
||||
isVisibleBarField(field) ? NULL_RETAIN : field.config.custom?.spanNulls === true ? NULL_REMOVE : NULL_EXPAND,
|
||||
nullMode: (field) => {
|
||||
if (isVisibleBarField(field)) {
|
||||
return NULL_RETAIN;
|
||||
}
|
||||
|
||||
let spanNulls = field.config.custom?.spanNulls;
|
||||
return spanNulls === true ? NULL_REMOVE : spanNulls === -1 ? NULL_RETAIN : NULL_EXPAND;
|
||||
},
|
||||
});
|
||||
|
||||
if (alignedFrame) {
|
||||
|
@ -1,6 +1,18 @@
|
||||
import { createTheme, FieldType, ThresholdsMode, TimeRange, toDataFrame, dateTime, DataFrame } from '@grafana/data';
|
||||
import {
|
||||
createTheme,
|
||||
FieldType,
|
||||
ThresholdsMode,
|
||||
TimeRange,
|
||||
toDataFrame,
|
||||
dateTime,
|
||||
DataFrame,
|
||||
fieldMatchers,
|
||||
FieldMatcherID,
|
||||
} from '@grafana/data';
|
||||
import { LegendDisplayMode, VizLegendOptions } from '@grafana/schema';
|
||||
|
||||
import { preparePlotFrame } from '../GraphNG/utils';
|
||||
|
||||
import {
|
||||
findNextStateIndex,
|
||||
fmtDuration,
|
||||
@ -87,6 +99,173 @@ describe('prepare timeline graph', () => {
|
||||
const result = prepareTimelineFields(frames, true, timeRange, theme);
|
||||
expect(result.frames?.[0].fields[0].values).toEqual([1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
it('join multiple frames with NULL_RETAIN rather than NULL_EXPAND', () => {
|
||||
const timeRange2: TimeRange = {
|
||||
from: dateTime('2023-10-20T05:04:00.000Z'),
|
||||
to: dateTime('2023-10-20T07:22:00.000Z'),
|
||||
raw: {
|
||||
from: dateTime('2023-10-20T05:04:00.000Z'),
|
||||
to: dateTime('2023-10-20T07:22:00.000Z'),
|
||||
},
|
||||
};
|
||||
|
||||
const frames = [
|
||||
toDataFrame({
|
||||
name: 'Mix',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1697778291972, 1697778393992, 1697778986994, 1697786485890] },
|
||||
{ name: 'state', type: FieldType.string, values: ['RUN', null, 'RUN', null] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
name: 'Cook',
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
values: [
|
||||
1697779163986, 1697779921045, 1697780221094, 1697780521111, 1697781186192, 1697781786291, 1697783332361,
|
||||
1697783784395, 1697783790397, 1697784146478, 1697784517471, 1697784523487, 1697784949480, 1697785369505,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
type: FieldType.string,
|
||||
values: [
|
||||
'Heat',
|
||||
'Stage',
|
||||
null,
|
||||
'Heat',
|
||||
'Stage',
|
||||
null,
|
||||
'Heat',
|
||||
'Stage',
|
||||
null,
|
||||
'Heat',
|
||||
'Stage',
|
||||
null,
|
||||
'CCP',
|
||||
null,
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const info = prepareTimelineFields(frames, true, timeRange2, theme);
|
||||
|
||||
let joined = preparePlotFrame(
|
||||
info.frames!,
|
||||
{
|
||||
x: fieldMatchers.get(FieldMatcherID.firstTimeField).get({}),
|
||||
y: fieldMatchers.get(FieldMatcherID.byType).get('string'),
|
||||
},
|
||||
timeRange2
|
||||
);
|
||||
|
||||
let vals = joined!.fields.map((f) => f.values);
|
||||
|
||||
expect(vals).toEqual([
|
||||
[
|
||||
1697778291972, 1697778393992, 1697778986994, 1697779163986, 1697779921045, 1697780221094, 1697780521111,
|
||||
1697781186192, 1697781786291, 1697783332361, 1697783784395, 1697783790397, 1697784146478, 1697784517471,
|
||||
1697784523487, 1697784949480, 1697785369505, 1697786485890,
|
||||
],
|
||||
[
|
||||
'RUN',
|
||||
null,
|
||||
'RUN',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
null,
|
||||
],
|
||||
[
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
'Heat',
|
||||
'Stage',
|
||||
null,
|
||||
'Heat',
|
||||
'Stage',
|
||||
null,
|
||||
'Heat',
|
||||
'Stage',
|
||||
null,
|
||||
'Heat',
|
||||
'Stage',
|
||||
null,
|
||||
'CCP',
|
||||
null,
|
||||
undefined,
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('join multiple frames with start and end time fields', () => {
|
||||
const timeRange2: TimeRange = {
|
||||
from: dateTime('2024-02-28T07:47:21.428Z'),
|
||||
to: dateTime('2024-02-28T14:12:43.391Z'),
|
||||
raw: {
|
||||
from: dateTime('2024-02-28T07:47:21.428Z'),
|
||||
to: dateTime('2024-02-28T14:12:43.391Z'),
|
||||
},
|
||||
};
|
||||
|
||||
const frames = [
|
||||
toDataFrame({
|
||||
name: 'Channel 1',
|
||||
fields: [
|
||||
{ name: 'starttime', type: FieldType.time, values: [1709107200000, 1709118000000] },
|
||||
{ name: 'endtime', type: FieldType.time, values: [1709114400000, 1709128800000] },
|
||||
{ name: 'state', type: FieldType.string, values: ['OK', 'NO_DATA'] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
name: 'Channel 2',
|
||||
fields: [
|
||||
{ name: 'starttime', type: FieldType.time, values: [1709110800000, 1709123400000] },
|
||||
{ name: 'endtime', type: FieldType.time, values: [1709116200000, 1709127000000] },
|
||||
{ name: 'state', type: FieldType.string, values: ['ERROR', 'WARNING'] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const info = prepareTimelineFields(frames, true, timeRange2, theme);
|
||||
|
||||
let joined = preparePlotFrame(
|
||||
info.frames!,
|
||||
{
|
||||
x: fieldMatchers.get(FieldMatcherID.firstTimeField).get({}),
|
||||
y: fieldMatchers.get(FieldMatcherID.byType).get('string'),
|
||||
},
|
||||
timeRange2
|
||||
);
|
||||
|
||||
let vals = joined!.fields.map((f) => f.values);
|
||||
|
||||
expect(vals).toEqual([
|
||||
[
|
||||
1709107200000, 1709110800000, 1709114400000, 1709116200000, 1709118000000, 1709123400000, 1709127000000,
|
||||
1709128800000,
|
||||
],
|
||||
['OK', undefined, null, undefined, 'NO_DATA', undefined, undefined, null],
|
||||
[undefined, 'ERROR', undefined, null, undefined, 'WARNING', null, undefined],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findNextStateIndex', () => {
|
||||
|
@ -22,8 +22,9 @@ import {
|
||||
ThresholdsMode,
|
||||
TimeRange,
|
||||
cacheFieldDisplayNames,
|
||||
outerJoinDataFrames,
|
||||
} from '@grafana/data';
|
||||
import { maybeSortFrame } from '@grafana/data/src/transformations/transformers/joinDataFrames';
|
||||
import { maybeSortFrame, NULL_RETAIN } from '@grafana/data/src/transformations/transformers/joinDataFrames';
|
||||
import { applyNullInsertThreshold } from '@grafana/data/src/transformations/transformers/nulls/nullInsertThreshold';
|
||||
import { nullToValue } from '@grafana/data/src/transformations/transformers/nulls/nullToValue';
|
||||
import {
|
||||
@ -445,15 +446,61 @@ export function prepareTimelineFields(
|
||||
const frames: DataFrame[] = [];
|
||||
|
||||
for (let frame of series) {
|
||||
let isTimeseries = false;
|
||||
let startFieldIdx = -1;
|
||||
let endFieldIdx = -1;
|
||||
|
||||
for (let i = 0; i < frame.fields.length; i++) {
|
||||
let f = frame.fields[i];
|
||||
|
||||
if (f.type === FieldType.time) {
|
||||
if (startFieldIdx === -1) {
|
||||
startFieldIdx = i;
|
||||
} else if (endFieldIdx === -1) {
|
||||
endFieldIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let isTimeseries = startFieldIdx !== -1;
|
||||
let changed = false;
|
||||
let maybeSortedFrame = maybeSortFrame(
|
||||
frame,
|
||||
frame.fields.findIndex((f) => f.type === FieldType.time)
|
||||
);
|
||||
frame = maybeSortFrame(frame, startFieldIdx);
|
||||
|
||||
// if we have a second time field, assume it is state end timestamps
|
||||
// and insert nulls into the data at the end timestamps
|
||||
if (endFieldIdx !== -1) {
|
||||
let startFrame: DataFrame = {
|
||||
...frame,
|
||||
fields: frame.fields.filter((f, i) => i !== endFieldIdx),
|
||||
};
|
||||
|
||||
let endFrame: DataFrame = {
|
||||
length: frame.length,
|
||||
fields: [frame.fields[endFieldIdx]],
|
||||
};
|
||||
|
||||
frame = outerJoinDataFrames({
|
||||
frames: [startFrame, endFrame],
|
||||
keepDisplayNames: true,
|
||||
nullMode: () => NULL_RETAIN,
|
||||
})!;
|
||||
|
||||
frame.fields.forEach((f, i) => {
|
||||
if (i > 0) {
|
||||
let vals = f.values;
|
||||
for (let i = 0; i < vals.length; i++) {
|
||||
if (vals[i] == null) {
|
||||
vals[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
changed = true;
|
||||
}
|
||||
|
||||
let nulledFrame = applyNullInsertThreshold({
|
||||
frame: maybeSortedFrame,
|
||||
frame,
|
||||
refFieldPseudoMin: timeRange.from.valueOf(),
|
||||
refFieldPseudoMax: timeRange.to.valueOf(),
|
||||
});
|
||||
@ -462,8 +509,10 @@ export function prepareTimelineFields(
|
||||
changed = true;
|
||||
}
|
||||
|
||||
frame = nullToValue(nulledFrame);
|
||||
|
||||
const fields: Field[] = [];
|
||||
for (let field of nullToValue(nulledFrame).fields) {
|
||||
for (let field of frame.fields) {
|
||||
if (field.config.custom?.hideFrom?.viz) {
|
||||
continue;
|
||||
}
|
||||
@ -496,6 +545,7 @@ export function prepareTimelineFields(
|
||||
},
|
||||
},
|
||||
};
|
||||
changed = true;
|
||||
fields.push(field);
|
||||
break;
|
||||
default:
|
||||
@ -506,11 +556,11 @@ export function prepareTimelineFields(
|
||||
hasTimeseries = true;
|
||||
if (changed) {
|
||||
frames.push({
|
||||
...maybeSortedFrame,
|
||||
...frame,
|
||||
fields,
|
||||
});
|
||||
} else {
|
||||
frames.push(maybeSortedFrame);
|
||||
frames.push(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -521,6 +571,7 @@ export function prepareTimelineFields(
|
||||
if (!frames.length) {
|
||||
return { warn: 'No graphable fields' };
|
||||
}
|
||||
|
||||
return { frames };
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user