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; }