Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timeseries to table transformation: Update Output Changes #77415

Merged
merged 12 commits into from
Nov 2, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {
ReducerID,
isReducerID,
SelectableValue,
getFieldDisplayName,
Field,
FieldType,
} from '@grafana/data';
import { InlineFieldRow, InlineField, StatsPicker, InlineSwitch, Select } from '@grafana/ui';
import { InlineFieldRow, InlineField, StatsPicker, Select, InlineLabel } from '@grafana/ui';

import {
timeSeriesTableTransformer,
Expand All @@ -22,19 +23,8 @@ export function TimeSeriesTableTransformEditor({
options,
onChange,
}: TransformerUIProps<TimeSeriesTableTransformerOptions>) {
const timeFields: Array<SelectableValue<string>> = [];
const refIdMap = getRefData(input);

// Retrieve time fields
for (const frame of input) {
for (const field of frame.fields) {
if (field.type === 'time') {
const name = getFieldDisplayName(field, frame, input);
timeFields.push({ label: name, value: name });
}
}
}

const onSelectTimefield = useCallback(
(refId: string, value: SelectableValue<string>) => {
const val = value?.value !== undefined ? value.value : '';
Expand Down Expand Up @@ -65,32 +55,45 @@ export function TimeSeriesTableTransformEditor({
[onChange, options]
);

const onMergeSeriesToggle = useCallback(
(refId: string) => {
const mergeSeries = options[refId]?.mergeSeries !== undefined ? !options[refId].mergeSeries : false;
onChange({
...options,
[refId]: {
...options[refId],
mergeSeries,
},
});
},
[onChange, options]
);

let configRows = [];
for (const refId of Object.keys(refIdMap)) {
// Get time fields for the current refId
const timeFields: Record<string, Field<FieldType.time>> = {};
const timeValues: Array<SelectableValue<string>> = [];

// Get a map of time fields, we map
// by field name and assume that time fields
// in the same query with the same name
// are the same
for (const frame of input) {
if (frame.refId === refId) {
for (const field of frame.fields) {
if (field.type === 'time') {
timeFields[field.name] = field;
}
}
}
}

for (const timeField of Object.values(timeFields)) {
const { name } = timeField;
timeValues.push({ label: name, value: name });
}

configRows.push(
<InlineFieldRow key={refId}>
<InlineField>
<InlineLabel>{`Trend #${refId}`}</InlineLabel>
</InlineField>
<InlineField
label="Time field"
tooltip="The time field that will be used for the time series. If not selected the first found will be used."
>
<Select
onChange={onSelectTimefield.bind(null, refId)}
options={timeFields}
options={timeValues}
value={options[refId]?.timeField}
isClearable={true}
/>
</InlineField>
<InlineField label="Stat" tooltip="The statistic that should be calculated for this time series.">
Expand All @@ -100,15 +103,6 @@ export function TimeSeriesTableTransformEditor({
filterOptions={(ext) => ext.id !== ReducerID.allValues && ext.id !== ReducerID.uniqueValues}
/>
</InlineField>
<InlineField
label="Merge series"
tooltip="If selected, multiple series from a single datasource will be merged into one series."
>
<InlineSwitch
value={options[refId]?.mergeSeries !== undefined ? options[refId]?.mergeSeries : true}
onChange={onMergeSeriesToggle.bind(null, refId)}
/>
</InlineField>
</InlineFieldRow>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@ describe('timeSeriesTableTransformer', () => {
const result = results[0];
expect(result.refId).toBe('A');
expect(result.fields).toHaveLength(3);
expect(result.fields[0].values).toEqual([
'Value : instance=A : pod=B',
'Value : instance=A : pod=C',
'Value : instance=A : pod=D',
]);
assertDataFrameField(result.fields[1], series);
expect(result.fields[0].values).toEqual(['A', 'A', 'A']);
expect(result.fields[1].values).toEqual(['B', 'C', 'D']);
assertDataFrameField(result.fields[2], series);
});

it('Will pass through non time series frames', () => {
Expand All @@ -34,9 +31,11 @@ describe('timeSeriesTableTransformer', () => {
const results = timeSeriesToTableTransform({}, series);
expect(results).toHaveLength(3);
expect(results[0]).toEqual(series[0]);
expect(results[1].refId).toBe('A');
expect(results[1].fields).toHaveLength(3);
expect(results[2]).toEqual(series[3]);
expect(results[2].refId).toBe('A');
expect(results[2].fields).toHaveLength(3);
expect(results[2].fields[0].values).toEqual(['A', 'A']);
expect(results[2].fields[1].values).toEqual(['B', 'C']);
expect(results[1]).toEqual(series[3]);
});

it('Will group by refId', () => {
Expand All @@ -49,26 +48,19 @@ describe('timeSeriesTableTransformer', () => {
];

const results = timeSeriesToTableTransform({}, series);

expect(results).toHaveLength(2);
expect(results[0].refId).toBe('A');
expect(results[0].fields).toHaveLength(3);
expect(results[0].fields[0].values).toEqual([
'Value : instance=A : pod=B',
'Value : instance=A : pod=C',
'Value : instance=A : pod=D',
]);
assertDataFrameField(results[0].fields[1], series.slice(0, 3));
expect(results[0].fields[0].values).toEqual(['A', 'A', 'A']);
expect(results[0].fields[1].values).toEqual(['B', 'C', 'D']);
assertDataFrameField(results[0].fields[2], series.slice(0, 3));
expect(results[1].refId).toBe('B');
expect(results[1].fields).toHaveLength(3);
expect(results[1].fields[0].values).toEqual([
'Value : instance=B : pod=F : cluster=A',
'Value : instance=B : pod=G : cluster=B',
]);
expect(results[1].fields[0].values).toEqual([
'Value : instance=B : pod=F : cluster=A',
'Value : instance=B : pod=G : cluster=B',
]);
assertDataFrameField(results[1].fields[1], series.slice(3, 5));
expect(results[1].fields).toHaveLength(4);
expect(results[1].fields[0].values).toEqual(['B', 'B']);
expect(results[1].fields[1].values).toEqual(['F', 'G']);
expect(results[1].fields[2].values).toEqual(['A', 'B']);
assertDataFrameField(results[1].fields[3], series.slice(3, 5));
});

it('Will include last value by deault', () => {
Expand All @@ -78,8 +70,8 @@ describe('timeSeriesTableTransformer', () => {
];

const results = timeSeriesToTableTransform({}, series);
expect(results[0].fields[1].values[0].fields[1].values[2]).toEqual(3);
expect(results[0].fields[1].values[1].fields[1].values[2]).toEqual(5);
expect(results[0].fields[2].values[0].value).toEqual(3);
expect(results[0].fields[2].values[1].value).toEqual(5);
});

it('Will calculate average value if configured', () => {
Expand All @@ -88,12 +80,45 @@ describe('timeSeriesTableTransformer', () => {
getTimeSeries('B', { instance: 'A', pod: 'C' }, [3, 4, 5]),
];

const results = timeSeriesToTableTransform({ B: { stat: ReducerID.mean } }, series);
expect(results[0].fields[2].values[0]).toEqual(3);
expect(results[1].fields[2].values[0]).toEqual(4);
const results = timeSeriesToTableTransform(
{
B: {
stat: ReducerID.mean,
},
},
series
);

expect(results[0].fields[2].values[0].value).toEqual(3);
expect(results[1].fields[2].values[0].value).toEqual(4);
});
});

it('Will transform multiple data series with the same label', () => {
const series = [
getTimeSeries('A', { instance: 'A', pod: 'B' }, [4, 2, 3]),
getTimeSeries('B', { instance: 'A', pod: 'B' }, [3, 4, 5]),
getTimeSeries('C', { instance: 'A', pod: 'B' }, [3, 4, 5]),
];

const results = timeSeriesToTableTransform({}, series);

// Check series A
expect(results[0].fields).toHaveLength(3);
expect(results[0].fields[0].values[0]).toBe('A');
expect(results[0].fields[1].values[0]).toBe('B');

// Check series B
expect(results[1].fields).toHaveLength(3);
expect(results[1].fields[0].values[0]).toBe('A');
expect(results[1].fields[1].values[0]).toBe('B');

// Check series C
expect(results[2].fields).toHaveLength(3);
expect(results[2].fields[0].values[0]).toBe('A');
expect(results[2].fields[1].values[0]).toBe('B');
});

function assertFieldsEqual(field1: Field, field2: Field) {
expect(field1.type).toEqual(field2.type);
expect(field1.name).toEqual(field2.name);
Expand Down
Loading
Loading