From 481b4fa41ddb024a1cabd2b6ec9a0f085eb0479a Mon Sep 17 00:00:00 2001 From: "Ole Kristian (Zee)" Date: Tue, 3 Aug 2021 13:12:22 +0200 Subject: [PATCH] fix: fix wrong time returned (#27) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ♻️ add type condtition to get time field * ✅ add tests to time field conditions * fix: fix wrong time returned The time field is not retrieved when there are multiple series and the data is retrieved based on the field name instead of the series name. Co-authored-by: Flesaker --- src/index.ts | 1 + src/utils/getDataFieldsFromName.ts | 87 +++++++ .../metricData/getMetricDataFromName.test.ts | 217 ++++++++++++++++++ src/utils/metricData/getMetricDataFromName.ts | 28 +-- .../metricValue/getMetricValueFromName.ts | 27 +-- 5 files changed, 316 insertions(+), 44 deletions(-) create mode 100644 src/utils/getDataFieldsFromName.ts diff --git a/src/index.ts b/src/index.ts index a120b77..e580b43 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export { ReducerID } from "./utils/field"; export * from "./utils/metricValue"; export * from "./utils/metricData"; +export * from "./utils/getDataFieldsFromName"; export * from "./utils/getFieldFromName"; export * from "./utils/getSeriesFromName"; export * from "./utils/evaluateString"; diff --git a/src/utils/getDataFieldsFromName.ts b/src/utils/getDataFieldsFromName.ts new file mode 100644 index 0000000..9daf563 --- /dev/null +++ b/src/utils/getDataFieldsFromName.ts @@ -0,0 +1,87 @@ +import { DataFrame, Field, PanelData } from "@grafana/data"; + +import { SearchOptions } from "./getFieldFromName"; +import { getSeriesFromName } from "./getSeriesFromName"; + +declare const data: PanelData; + +export interface DataFields { + valueField?: Field; + timeField?: Field; +} + +export interface DataFieldOptions extends SearchOptions { + /** + * Get time field + * + * @default true + */ + getTime?: boolean; +} + +function getValueField( + series: DataFrame, + name: string, + { searchLabels = true }: SearchOptions = {} +) { + return series.fields.find((field) => + [ + field.name, + ...(searchLabels && field.labels ? [field.labels.name] : []), + ].includes(name) + ); +} + +function getTimeField(series: DataFrame) { + return ( + series.fields.find((field) => field.type == "time") ?? + series.fields.find((field) => field.name == "Time" || field.name == "time") + ); +} + +function getSeriesAndValueField( + name: string, + { searchLabels = true }: SearchOptions = {} +) { + const series = getSeriesFromName(name); + if (series) { + const valueField = + getValueField(series, "Value", { searchLabels: false }) ?? + getValueField(series, name, { searchLabels }); + + return { series, valueField }; + } + + for (const series of data.series) { + const valueField = getValueField(series, name, { searchLabels }); + if (valueField) return { series, valueField }; + } + + return {}; +} + +/** + * Gets the series that contains the name (searches through series, fields and labels) + * + * @example + * ```ts + * getDataFieldsFromName("series-name"); + * ``` + * + * @param name + * @param DataFieldOptions + * + * @returns value and time field + */ +export function getDataFieldsFromName( + name: string, + { searchLabels = true, getTime = true }: DataFieldOptions = {} +): DataFields | Record { + const { series, valueField } = getSeriesAndValueField(name, { searchLabels }); + if (series && valueField) + return { + valueField, + timeField: getTime ? getTimeField(series) : undefined, + }; + return {}; +} diff --git a/src/utils/metricData/getMetricDataFromName.test.ts b/src/utils/metricData/getMetricDataFromName.test.ts index 1f007ba..9605880 100644 --- a/src/utils/metricData/getMetricDataFromName.test.ts +++ b/src/utils/metricData/getMetricDataFromName.test.ts @@ -99,6 +99,22 @@ describe("getMetricDataFromName", () => { window.data = { state: LoadingState.Done, series: [ + { + fields: [ + field({ + name: "Time", + type: FieldType.time, + calcs: {}, + values: [1577736800, 1577936800], + }), + field({ + name: "field-0", + type: FieldType.number, + calcs: { [ReducerID.last]: 100 }, + }), + ], + length: 1, + }, { name: "series-1", fields: [ @@ -145,6 +161,22 @@ describe("getMetricDataFromName", () => { window.data = { state: LoadingState.Done, series: [ + { + fields: [ + field({ + name: "Time", + type: FieldType.time, + calcs: {}, + values: [1577736800, 1577936800], + }), + field({ + name: "field-0", + type: FieldType.number, + calcs: { [ReducerID.last]: 100 }, + }), + ], + length: 1, + }, { fields: [ TIME_FIELD, @@ -156,6 +188,22 @@ describe("getMetricDataFromName", () => { ], length: 1, }, + { + fields: [ + field({ + name: "Time", + type: FieldType.time, + calcs: {}, + values: [1577736800, 1577936800], + }), + field({ + name: "field-2", + type: FieldType.number, + calcs: { [ReducerID.last]: 100 }, + }), + ], + length: 1, + }, ], timeRange: minimalTimeRange, }; @@ -182,6 +230,22 @@ describe("getMetricDataFromName", () => { window.data = { state: LoadingState.Done, series: [ + { + fields: [ + field({ + name: "Time", + type: FieldType.time, + calcs: {}, + values: [1577736800, 1577936800], + }), + field({ + name: "field-0", + type: FieldType.number, + calcs: { [ReducerID.last]: 100 }, + }), + ], + length: 1, + }, { fields: [ TIME_FIELD, @@ -263,4 +327,157 @@ describe("getMetricDataFromName", () => { }); }); }); + + describe("timeField name is time", () => { + beforeEach(() => { + window.data = { + state: LoadingState.Done, + series: [ + { + name: "series-1", + fields: [ + field({ + name: "time", + type: FieldType.time, + calcs: {}, + values: TIME_VALUES, + }), + field({ + name: "Value", + type: FieldType.number, + calcs: { + [ReducerID.last]: 1000, + [ReducerID.first]: 100, + [ReducerID.max]: 1000, + }, + }), + ], + length: 1, + }, + ], + timeRange: minimalTimeRange, + }; + }); + + afterEach(() => { + delete window.data; + }); + + it("get correct time", () => { + expect(getMetricDataFromName("series-1")).toStrictEqual({ + calcs: { + [ReducerID.last]: 1000, + [ReducerID.first]: 100, + [ReducerID.max]: 1000, + }, + time: { + [ReducerID.first]: TIME_VALUES[0], + [ReducerID.last]: TIME_VALUES[TIME_VALUES.length - 1], + }, + hasData: true, + }); + }); + }); + + describe("timeField relies on type", () => { + beforeEach(() => { + window.data = { + state: LoadingState.Done, + series: [ + { + name: "series-1", + fields: [ + field({ + name: "Random", + type: FieldType.time, + calcs: {}, + values: TIME_VALUES, + }), + field({ + name: "Value", + type: FieldType.number, + calcs: { + [ReducerID.last]: 1000, + [ReducerID.first]: 100, + [ReducerID.max]: 1000, + }, + }), + ], + length: 1, + }, + ], + timeRange: minimalTimeRange, + }; + }); + + afterEach(() => { + delete window.data; + }); + + it("get correct time", () => { + expect(getMetricDataFromName("series-1")).toStrictEqual({ + calcs: { + [ReducerID.last]: 1000, + [ReducerID.first]: 100, + [ReducerID.max]: 1000, + }, + time: { + [ReducerID.first]: TIME_VALUES[0], + [ReducerID.last]: TIME_VALUES[TIME_VALUES.length - 1], + }, + hasData: true, + }); + }); + }); + + describe("timeField with wrong type but correct name", () => { + beforeEach(() => { + window.data = { + state: LoadingState.Done, + series: [ + { + name: "series-1", + fields: [ + field({ + name: "Time", + type: FieldType.number, + calcs: {}, + values: TIME_VALUES, + }), + field({ + name: "Value", + type: FieldType.number, + calcs: { + [ReducerID.last]: 1000, + [ReducerID.first]: 100, + [ReducerID.max]: 1000, + }, + }), + ], + length: 1, + }, + ], + timeRange: minimalTimeRange, + }; + }); + + afterEach(() => { + delete window.data; + }); + + it("get correct time", () => { + expect(getMetricDataFromName("series-1")).toStrictEqual({ + calcs: { + [ReducerID.last]: 1000, + [ReducerID.first]: 100, + [ReducerID.max]: 1000, + }, + time: { + [ReducerID.first]: TIME_VALUES[0], + [ReducerID.last]: TIME_VALUES[TIME_VALUES.length - 1], + }, + hasData: true, + }); + }); + }); }); diff --git a/src/utils/metricData/getMetricDataFromName.ts b/src/utils/metricData/getMetricDataFromName.ts index fdc4b5c..0905f88 100644 --- a/src/utils/metricData/getMetricDataFromName.ts +++ b/src/utils/metricData/getMetricDataFromName.ts @@ -1,13 +1,8 @@ -import { DataFrame } from "@grafana/data"; +import { Field } from "@grafana/data"; -import { getFieldFromName } from "../getFieldFromName"; -import { getSeriesFromName } from "../getSeriesFromName"; - -function getTime(series?: DataFrame) { - const timeField = - series?.fields.find((field) => field.name == "Time") ?? - getFieldFromName("Time"); +import { getDataFieldsFromName } from "../getDataFieldsFromName"; +function getTime(timeField?: Field) { const timeValues = timeField?.values; const time = timeValues ? { @@ -20,18 +15,12 @@ function getTime(series?: DataFrame) { } function getCalcs({ - series, - metricName, + valueField, reducerIDs, }: { - series?: DataFrame; - metricName?: string; + valueField?: Field; reducerIDs?: string[]; }) { - const valueField = - series?.fields.find((field) => field.name == "Value") ?? - (metricName ? getFieldFromName(metricName) : undefined); - const calcs = valueField?.state?.calcs ?? {}; const enquiredCalcs = reducerIDs?.reduce( @@ -74,10 +63,11 @@ export function getMetricDataFromName( metricName: string, { reducerIDs }: MetricDataFromNameOptions = {} ): MetricData { - const series = getSeriesFromName(metricName); - const calcs = getCalcs({ series, metricName, reducerIDs }); + const { valueField, timeField } = getDataFieldsFromName(metricName); + + const calcs = getCalcs({ valueField, reducerIDs }); const hasData = Object.keys(calcs).length > 0; - const time = hasData ? getTime(series) : {}; + const time = hasData ? getTime(timeField) : {}; return { calcs, time, hasData }; } diff --git a/src/utils/metricValue/getMetricValueFromName.ts b/src/utils/metricValue/getMetricValueFromName.ts index f7b7a1e..9928ab0 100644 --- a/src/utils/metricValue/getMetricValueFromName.ts +++ b/src/utils/metricValue/getMetricValueFromName.ts @@ -1,24 +1,5 @@ -import { Field } from "@grafana/data"; - import { ReducerID } from "../field"; -import { getFieldFromName } from "../getFieldFromName"; -import { getSeriesFromName } from "../getSeriesFromName"; - -/** - * Finds the value field - * - * @example - * ```ts - * getValueField(series.fields); - * ``` - * - * @param fields - Fields object - * - * @returns Value field - */ -export function getValueField(fields: Field[]): Field | undefined { - return fields.find((field) => field.name == "Value"); -} +import { getDataFieldsFromName } from "../getDataFieldsFromName"; export interface MetricValueFromNameOptions { /** @@ -60,10 +41,6 @@ export function getMetricValueFromName( reducerID = ReducerID.last, }: MetricValueFromNameOptions = {} ): unknown { - const series = getSeriesFromName(metricName); - const valueField = series - ? getValueField(series.fields) - : getFieldFromName(metricName); - + const { valueField } = getDataFieldsFromName(metricName, { getTime: false }); return valueField?.state?.calcs?.[reducerID] ?? noDataValue; }