import { map } from 'rxjs/operators'; import { ArrayVector, DataFrame, DataTransformerID, DataTransformerInfo, Field, FieldType, MutableDataFrame, isTimeSeriesFrame, } from '@grafana/data'; export interface TimeSeriesTableTransformerOptions {} export const timeSeriesTableTransformer: DataTransformerInfo = { id: DataTransformerID.timeSeriesTable, name: 'Time series to table transform', description: 'Time series to table rows', defaultOptions: {}, operator: (options) => (source) => source.pipe( map((data) => { return timeSeriesToTableTransform(options, data); }) ), }; /** * Converts time series frames to table frames for use with sparkline chart type. * * @remarks * For each refId (queryName) convert all time series frames into a single table frame, adding each series * as values of a "Trend" frame field. This allows "Trend" to be rendered as area chart type. * Any non time series frames are returned as is. * * @param options - Transform options, currently not used * @param data - Array of data frames to transform * @returns Array of transformed data frames * * @alpha */ export function timeSeriesToTableTransform(options: TimeSeriesTableTransformerOptions, data: DataFrame[]): DataFrame[] { // initialize fields from labels for each refId const refId2LabelFields = getLabelFields(data); const refId2frameField: Record> = {}; const result: DataFrame[] = []; for (const frame of data) { if (!isTimeSeriesFrame(frame)) { result.push(frame); continue; } const refId = frame.refId ?? ''; const labelFields = refId2LabelFields[refId] ?? {}; // initialize a new frame for this refId with fields per label and a Trend frame field, if it doesn't exist yet let frameField = refId2frameField[refId]; if (!frameField) { frameField = { name: 'Trend' + (refId && Object.keys(refId2LabelFields).length > 1 ? ` #${refId}` : ''), type: FieldType.frame, config: {}, values: new ArrayVector(), }; refId2frameField[refId] = frameField; const table = new MutableDataFrame(); for (const label of Object.values(labelFields)) { table.addField(label); } table.addField(frameField); table.refId = refId; result.push(table); } // add values to each label based field of this frame const labels = frame.fields[1].labels; for (const labelKey of Object.keys(labelFields)) { const labelValue = labels?.[labelKey] ?? null; labelFields[labelKey].values.add(labelValue); } frameField.values.add(frame); } return result; } // For each refId, initialize a field for each label name function getLabelFields(frames: DataFrame[]): Record>> { // refId -> label name -> field const labelFields: Record>> = {}; for (const frame of frames) { if (!isTimeSeriesFrame(frame)) { continue; } const refId = frame.refId ?? ''; if (!labelFields[refId]) { labelFields[refId] = {}; } for (const field of frame.fields) { if (!field.labels) { continue; } for (const labelName of Object.keys(field.labels)) { if (!labelFields[refId][labelName]) { labelFields[refId][labelName] = { name: labelName, type: FieldType.string, config: {}, values: new ArrayVector(), }; } } } } return labelFields; }