Skip to content

Commit

Permalink
feat: add search by filter (#35)
Browse files Browse the repository at this point in the history
* fix: fix return type

* feat: add method to get value by multiple filters

* chore: update changelog

* Update src/utils/getMetricValueWithFilters/getMetricValueWithFilters.ts

Co-authored-by: Ole Kristian (Zee) <[email protected]>

* Update src/utils/getMetricValueWithFilters/getMetricValueWithFilters.ts

Co-authored-by: Ole Kristian (Zee) <[email protected]>

* Update CHANGELOG.md

Co-authored-by: Ole Kristian (Zee) <[email protected]>

* Update src/utils/getMetricValueWithFilters/getMetricValueWithFilters.ts

Co-authored-by: Ole Kristian (Zee) <[email protected]>

* Update src/utils/getMetricValueWithFilters/getMetricValueWithFilters.ts

Co-authored-by: Ole Kristian (Zee) <[email protected]>

* refac: add noDataValue & remove excessive type check

* feat: add reducerId to getMetricValueWithFilter

* fix: change import to correct reducerid

---------

Co-authored-by: Ole Kristian (Zee) <[email protected]>
  • Loading branch information
flesa and ZuperZee authored Apr 27, 2023
1 parent c08830e commit a0137c6
Show file tree
Hide file tree
Showing 6 changed files with 359 additions and 1 deletion.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog for grafana-metric

## Unreleased

### Features / enhancements

- Add method to filter by series name, field name and multiple labels [#35](https://github.com/gapitio/grafana-metric/pull/35)

## v1.1.1 (2022/01/07)

### Features / enhancements
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from "./utils/getDataFieldsFromName";
export * from "./utils/getFieldFromName";
export * from "./utils/getSeriesFromName";
export * from "./utils/evaluateString";
export * from "./utils/getMetricValueWithFilters";
2 changes: 1 addition & 1 deletion src/utils/getDataFieldsFromName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function getSeriesAndValueField(
export function getDataFieldsFromName(
name: string,
{ searchLabels = true, getTime = true }: DataFieldOptions = {}
): DataFields | Record<string, never> {
): DataFields {
const { series, valueField } = getSeriesAndValueField(name, { searchLabels });
if (series && valueField)
return {
Expand Down
227 changes: 227 additions & 0 deletions src/utils/getMetricValueWithFilters/getMetricValueWithFilters.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import { FieldType, LoadingState, PanelData, dateTime } from "@grafana/data";

import { TIME_FIELD, field } from "../field";

import { getMetricValueWithFilters } from "./getMetricValueWithFilters";

declare global {
interface Window {
data?: PanelData;
}
}

window.data = {
state: LoadingState.Done,
series: [
{
name: "series-1",
fields: [
TIME_FIELD,
field({
name: "Value",
type: FieldType.number,
values: [47, 100],
labels: { label1: "label1" },
}),
],
length: 1,
},
{
name: "series-2",
fields: [
TIME_FIELD,
field({
name: "Value",
type: FieldType.number,
values: [200],
labels: { label2: "label2" },
}),
],
length: 1,
},
{
name: "series-3",
fields: [
TIME_FIELD,
field({
name: "FieldName",
type: FieldType.number,
values: [300],
labels: { label3: "label3" },
}),
],
length: 1,
},
{
name: "series-3",
fields: [
TIME_FIELD,
field({
name: "FieldName",
type: FieldType.number,
values: [400],
labels: { label3: "label3", label4: "first" },
}),
],
length: 1,
},
{
name: "series-3",
fields: [
TIME_FIELD,
field({
name: "FieldName",
type: FieldType.number,
values: [500],
labels: { label3: "label3", label4: "second" },
}),
],
length: 1,
},
{
name: "series-12",
fields: [
TIME_FIELD,
field({
name: "FieldName",
type: FieldType.number,
values: [100, 200, 300, 400, 500],
labels: { label3: "label3", label4: "second" },
}),
],
length: 5,
},
],
timeRange: {
from: dateTime(0),
to: dateTime(0),
raw: {
from: dateTime(0),
to: dateTime(0),
},
},
};

describe("getMetricValue", () => {
beforeEach(() => {
jest.spyOn(global.Math, "random").mockReturnValue(0.5);
});

afterEach(() => {
jest.spyOn(global.Math, "random").mockRestore();
});

it("retrieves random value", () => {
expect(
getMetricValueWithFilters({ seriesName: "series-1", showcase: true })
).toEqual(500);
expect(
getMetricValueWithFilters({
seriesName: "series-1",
showcase: true,
range: { min: 0, max: 10 },
decimals: 2,
})
).toEqual(5);
});

it("retrieves metric value w/o set fieldName & labels", () => {
expect(getMetricValueWithFilters({ seriesName: "series-1" })).toEqual(100);
});

it("retrieves metric value w/o set fieldName", () => {
expect(
getMetricValueWithFilters({
seriesName: "series-1",
labels: { label1: "label1" },
})
).toEqual(100);
});

it("retrieves metric value with all variables", () => {
expect(
getMetricValueWithFilters({
seriesName: "series-3",
fieldName: "FieldName",
labels: { label3: "label3" },
})
).toEqual(300);
});

it("retrieves first object when multiple is available", () => {
expect(
getMetricValueWithFilters({
seriesName: "series-3",
fieldName: "FieldName",
labels: { label3: "label3" },
})
).toEqual(300);
});

it("retrieves null when no match is found", () => {
expect(
getMetricValueWithFilters({
seriesName: "series-4",
fieldName: "FieldName",
labels: { label3: "label3" },
})
).toEqual(null);
});

it("retrieves null when including existing and nonexisting labels", () => {
expect(
getMetricValueWithFilters({
seriesName: "series-3",
fieldName: "FieldName",
labels: { label3: "label3", nonExistent: "nonExistent" },
})
).toEqual(null);
});

it("retrieves 'No data' when including noDataValue = 'No data'", () => {
expect(
getMetricValueWithFilters({
seriesName: "series-3",
fieldName: "FieldName",
labels: { label3: "label3", nonExistent: "nonExistent" },
noDataValue: "No data",
})
).toEqual("No data");
});

it("retrieves correct metric value when multiple labels", () => {
expect(
getMetricValueWithFilters({
seriesName: "series-3",
fieldName: "FieldName",
labels: { label3: "label3", label4: "first" },
})
).toEqual(400);
expect(
getMetricValueWithFilters({
seriesName: "series-3",
fieldName: "FieldName",
labels: { label3: "label3", label4: "second" },
})
).toEqual(500);
});

it("retrieves correct metric value with reducerId", () => {
expect(
getMetricValueWithFilters({
seriesName: "series-12",
fieldName: "FieldName",
labels: { label3: "label3" },
reducerID: "first",
})
).toEqual(100);
expect(
getMetricValueWithFilters({
seriesName: "series-12",
fieldName: "FieldName",
labels: { label3: "label3" },
reducerID: "last",
})
).toEqual(500);
});
});
123 changes: 123 additions & 0 deletions src/utils/getMetricValueWithFilters/getMetricValueWithFilters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { DataFrame, Field, PanelData } from "@grafana/data";

import { ReducerID } from "../field";
import { getValue } from "../metricValue";
import { getShowcaseMetricValue } from "../metricValue/getShowcaseMetricValue";

declare const data: PanelData;

function getMultipleSeriesFromName(seriesName: string): DataFrame[] {
return data.series.filter((series) => series.name === seriesName);
}

function getValueFieldsFromSeries(series: DataFrame[], fieldName: string) {
return series
.map(({ fields }) =>
fields.find((field) =>
[field.name, field.state?.displayName].includes(fieldName)
)
)
.filter((x): x is Exclude<typeof x, undefined> => x !== undefined);
}

function getValueFieldsFromName(metricName: string, fieldName: string) {
const series = getMultipleSeriesFromName(metricName);
return getValueFieldsFromSeries(series, fieldName);
}

function getValueFieldFromLabels(
valueFields: Field[],
labels: Record<string, string>
) {
return valueFields.find(({ labels: fieldLabels }) =>
Object.entries(labels).every(
([k, v]) => fieldLabels && fieldLabels[k] === v
)
);
}

function getMetricSeries({
seriesName,
fieldName,
labels,
noDataValue,
reducerID,
}: {
seriesName: string;
fieldName: string;
labels: Record<string, string>;
noDataValue: unknown;
reducerID: string;
}) {
const valueFields = getValueFieldsFromName(seriesName, fieldName);
const valueField = getValueFieldFromLabels(valueFields, labels);
const value =
valueField?.state?.calcs?.[reducerID] ?? getValue(valueField, reducerID);
return value ?? noDataValue;
}

/**
* Function provides value from grafana queries based on given filters.
*
* @example
*
* getMetricValueWithFilters({seriesName: "series-1"}); // Returns first series with name "series-1".
*
* getMetricValueWithFilters({
seriesName: "series-1",
fieldName: "field-1",
}); // Returns first series with name "series-1" and fieldName "field-1".
*
* getMetricValueWithFilters({
seriesName: "series-1",
fieldName: "field-1",
labels: { someLabelKey: "label-1", anotherLabelKey: "label-2" },
}) // Returns first series with name "series-1", fieldName "field-1" and field contains matching labels.
*
* // Showcase
* getMetricValueWithFilters({ seriesName: "series-1", showcase: true }) // Returns a random value between 0 and 1000.
*
* getMetricValueWithFilters({
seriesName: "series-1",
showcase: true,
range: { min: 0, max: 10 },
decimals: 2,
}) // Returns random value between 1-10.
*
* @param seriesName - String for identifying correct series in Grafana's data object.
* @param fieldName - String for identifying correct field in series(Defaults to "Value").
* @param labels - Object for filtering a specific series when multiple series have the same seriesName.
* @param showcase - Boolean for returning random showcase value.
* @param range - Object for setting minimum(min:) or maximum(max:) range for showcase value.
* @param decimals - Number for setting static amount of decimals for showcase value.
*/
export function getMetricValueWithFilters({
seriesName,
fieldName = "Value",
labels = {},
showcase,
range,
decimals,
noDataValue = null,
reducerID = ReducerID.last,
}: {
seriesName: string;
fieldName?: string;
labels?: Record<string, string>;
showcase?: boolean;
range?: { min: number; max: number };
decimals?: number;
noDataValue?: unknown;
reducerID?: string;
}): unknown {
if (showcase) {
return getShowcaseMetricValue({ range, decimals });
}
return getMetricSeries({
seriesName,
fieldName,
labels,
noDataValue,
reducerID,
});
}
1 change: 1 addition & 0 deletions src/utils/getMetricValueWithFilters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./getMetricValueWithFilters";

0 comments on commit a0137c6

Please sign in to comment.