((acc, frame) => {
+ if (frame.refId && !acc.includes(frame.refId)) {
+ return [...acc, frame.refId];
+ }
+ return acc;
+ }, []);
-export function TimeSeriesTableTransformEditor({ input, options, onChange }: Props) {
- if (input.length === 0) {
- return null;
- }
+ const onSelectStat = useCallback(
+ (refId: string, stats: string[]) => {
+ const reducerID = stats[0];
+ if (reducerID && isReducerID(reducerID)) {
+ onChange({
+ refIdToStat: {
+ ...options.refIdToStat,
+ [refId]: reducerID,
+ },
+ });
+ }
+ },
+ [onChange, options]
+ );
- return ;
+ return (
+ <>
+ {refIds.map((refId) => {
+ return (
+
+
+ 1 ? ` #${refId}` : ''} value`}>
+ ext.id !== ReducerID.allValues && ext.id !== ReducerID.uniqueValues}
+ />
+
+
+
+ );
+ })}
+ >
+ );
}
export const timeSeriesTableTransformRegistryItem: TransformerRegistryItem = {
diff --git a/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.test.ts b/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.test.ts
index 76a8898bff2..a281c4d5d2b 100644
--- a/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.test.ts
+++ b/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.test.ts
@@ -1,4 +1,4 @@
-import { toDataFrame, FieldType, Labels, DataFrame, Field } from '@grafana/data';
+import { toDataFrame, FieldType, Labels, DataFrame, Field, ReducerID } from '@grafana/data';
import { timeSeriesToTableTransform } from './timeSeriesTableTransformer';
@@ -61,6 +61,35 @@ describe('timeSeriesTableTransformer', () => {
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', () => {
+ const series = [
+ getTimeSeries('A', { instance: 'A', pod: 'B' }, [4, 2, 3]),
+ getTimeSeries('A', { instance: 'A', pod: 'C' }, [3, 4, 5]),
+ ];
+
+ const results = timeSeriesToTableTransform({}, series);
+ 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', () => {
+ const series = [
+ getTimeSeries('A', { instance: 'A', pod: 'B' }, [4, 2, 3]),
+ getTimeSeries('B', { instance: 'A', pod: 'C' }, [3, 4, 5]),
+ ];
+
+ const results = timeSeriesToTableTransform(
+ {
+ refIdToStat: {
+ B: ReducerID.mean,
+ },
+ },
+ series
+ );
+ expect(results[0].fields[2].values[0].value).toEqual(3);
+ expect(results[1].fields[2].values[0].value).toEqual(4);
+ });
});
function assertFieldsEqual(field1: Field, field2: Field) {
@@ -80,7 +109,7 @@ function assertDataFrameField(field: Field, matchesFrames: DataFrame[]) {
});
}
-function getTimeSeries(refId: string, labels: Labels) {
+function getTimeSeries(refId: string, labels: Labels, values: number[] = [10]) {
return toDataFrame({
refId,
fields: [
@@ -88,7 +117,7 @@ function getTimeSeries(refId: string, labels: Labels) {
{
name: 'Value',
type: FieldType.number,
- values: [10],
+ values,
labels,
},
],
diff --git a/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.ts b/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.ts
index a2ddb2da6fb..5137b15f050 100644
--- a/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.ts
+++ b/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.ts
@@ -2,15 +2,20 @@ import { map } from 'rxjs/operators';
import {
DataFrame,
+ DataFrameWithValue,
DataTransformerID,
DataTransformerInfo,
Field,
FieldType,
MutableDataFrame,
isTimeSeriesFrame,
+ ReducerID,
+ reduceField,
} from '@grafana/data';
-export interface TimeSeriesTableTransformerOptions {}
+export interface TimeSeriesTableTransformerOptions {
+ refIdToStat?: Record;
+}
export const timeSeriesTableTransformer: DataTransformerInfo = {
id: DataTransformerID.timeSeriesTable,
@@ -44,7 +49,7 @@ export function timeSeriesToTableTransform(options: TimeSeriesTableTransformerOp
// initialize fields from labels for each refId
const refId2LabelFields = getLabelFields(data);
- const refId2frameField: Record> = {};
+ const refId2frameField: Record> = {};
const result: DataFrame[] = [];
@@ -83,8 +88,13 @@ export function timeSeriesToTableTransform(options: TimeSeriesTableTransformerOp
const labelValue = labels?.[labelKey] ?? null;
labelFields[labelKey].values.push(labelValue!);
}
-
- frameField.values.push(frame);
+ const reducerId = options.refIdToStat?.[refId] ?? ReducerID.lastNotNull;
+ const valueField = frame.fields.find((f) => f.type === FieldType.number);
+ const value = (valueField && reduceField({ field: valueField, reducers: [reducerId] })[reducerId]) || null;
+ frameField.values.push({
+ ...frame,
+ value,
+ });
}
return result;
}
diff --git a/public/app/plugins/panel/table/TableCellOptionEditor.tsx b/public/app/plugins/panel/table/TableCellOptionEditor.tsx
index b90fb577279..5eb8b698b36 100644
--- a/public/app/plugins/panel/table/TableCellOptionEditor.tsx
+++ b/public/app/plugins/panel/table/TableCellOptionEditor.tsx
@@ -3,7 +3,7 @@ import { merge } from 'lodash';
import React, { useState } from 'react';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
-import { config, reportInteraction } from '@grafana/runtime';
+import { reportInteraction } from '@grafana/runtime';
import { TableCellOptions } from '@grafana/schema';
import { Field, Select, TableCellDisplayMode, useStyles2 } from '@grafana/ui';
@@ -77,14 +77,9 @@ export const TableCellOptionEditor = ({ value, onChange }: Props) => {
);
};
-const SparklineDisplayModeOption: SelectableValue = {
- value: { type: TableCellDisplayMode.Sparkline },
- label: 'Sparkline',
-};
-
const cellDisplayModeOptions: Array> = [
{ value: { type: TableCellDisplayMode.Auto }, label: 'Auto' },
- ...(config.featureToggles.timeSeriesTable ? [SparklineDisplayModeOption] : []),
+ { value: { type: TableCellDisplayMode.Sparkline }, label: 'Sparkline' },
{ value: { type: TableCellDisplayMode.ColorText }, label: 'Colored text' },
{ value: { type: TableCellDisplayMode.ColorBackground }, label: 'Colored background' },
{ value: { type: TableCellDisplayMode.Gauge }, label: 'Gauge' },
diff --git a/public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx b/public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx
index 2e7ade9d591..61799c51c18 100644
--- a/public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx
+++ b/public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx
@@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import React, { useMemo } from 'react';
-import { createFieldConfigRegistry } from '@grafana/data';
+import { createFieldConfigRegistry, SetFieldConfigOptionsArgs } from '@grafana/data';
import { GraphFieldConfig, TableSparklineCellOptions } from '@grafana/schema';
import { VerticalGroup, Field, useStyles2 } from '@grafana/ui';
import { defaultSparklineCellConfig } from '@grafana/ui/src/components/Table/SparklineCell';
@@ -11,7 +11,8 @@ import { TableCellEditorProps } from '../TableCellOptionEditor';
type OptionKey = keyof TableSparklineCellOptions;
-const optionIds: Array = [
+const optionIds: Array = [
+ 'hideValue',
'drawStyle',
'lineInterpolation',
'barAlignment',
@@ -24,11 +25,25 @@ const optionIds: Array = [
'pointSize',
];
+function getChartCellConfig(cfg: GraphFieldConfig): SetFieldConfigOptionsArgs {
+ const graphFieldConfig = getGraphFieldConfig(cfg);
+ return {
+ ...graphFieldConfig,
+ useCustomConfig: (builder) => {
+ graphFieldConfig.useCustomConfig?.(builder);
+ builder.addBooleanSwitch({
+ path: 'hideValue',
+ name: 'Hide value',
+ });
+ },
+ };
+}
+
export const SparklineCellOptionsEditor = (props: TableCellEditorProps) => {
const { cellOptions, onChange } = props;
const registry = useMemo(() => {
- const config = getGraphFieldConfig(defaultSparklineCellConfig);
+ const config = getChartCellConfig(defaultSparklineCellConfig);
return createFieldConfigRegistry(config, 'ChartCell');
}, []);