Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions src/HaystackVariableSupport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {
CustomVariableSupport,
DataQueryRequest,
DataQueryResponse,
DataFrame,
Field,
FieldType,
MetricFindValue,
} from '@grafana/data';

import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { HaystackVariableQueryEditor } from './components/HaystackVariableQueryEditor';
import { DataSource } from './datasource';
import { isRef, parseRef } from 'haystack';
import { HaystackVariableQuery } from './types';

export class HaystackVariableSupport extends CustomVariableSupport<DataSource, HaystackVariableQuery> {
editor = HaystackVariableQueryEditor;

constructor(private datasource: DataSource) {
super();
}

query(request: DataQueryRequest<HaystackVariableQuery>): Observable<DataQueryResponse> {
let variableQuery = request.targets[0];
let observable = this.datasource.query(request);
return observable.pipe(
map((response) => {
if (response === undefined || response.errors !== undefined || response.data === undefined) {
return response;
}

let variableValues = response.data.reduce((acc: MetricFindValue[], frame: DataFrame) => {
// Default to the first field
let column = frame.fields[0];
if (variableQuery.column !== undefined && variableQuery.column !== '') {
// If a column was input, match the column name
column = frame.fields.find((field: Field) => field.name === variableQuery.column) ?? column;
} else if (frame.fields.some((field: Field) => field.name === 'id')) {
// If there is an id column, use that
column = frame.fields.find((field: Field) => field.name === 'id') ?? column;
}

// Default to the selected column
let displayColumn = column;
if (variableQuery.displayColumn !== undefined && variableQuery.displayColumn !== '') {
// If a column was input, match the column name
displayColumn =
frame.fields.find((field: Field) => {
return field.name === variableQuery.displayColumn;
}) ?? displayColumn;
}

let variableValues = column.values.map((value, index) => {
let variableValue = variableValueFromCell(value, column.type);

let displayValue = displayColumn.values[index];
let variableText = variableTextFromCell(displayValue, displayColumn.type);

return { text: variableText, value: variableValue };
});
return acc.concat(variableValues);
}, []);
return { ...response, data: variableValues };
})
);
}
}

function variableValueFromCell(value: string, columnType: FieldType): string {
switch (columnType) {
case FieldType.string:
if (isRef(value)) {
return parseRef(value).id;
}
}
return value;
}

function variableTextFromCell(value: string, columnType: FieldType): string {
switch (columnType) {
case FieldType.string:
if (isRef(value)) {
let ref = parseRef(value);
return ref.dis ?? ref.id;
}
}
return value;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { QueryEditorProps } from '@grafana/data';
import { InlineField, Input, Stack } from '@grafana/ui';
import { DataSource } from 'datasource';

type Props = QueryEditorProps<DataSource, HaystackQuery, HaystackDataSourceOptions, HaystackVariableQuery>;
export type Props = QueryEditorProps<DataSource, HaystackQuery, HaystackDataSourceOptions, HaystackVariableQuery>;

export const VariableQueryEditor = ({ onChange, query }: Props) => {
const refId = 'HaystackVariableQueryEditor-VariableQuery';

export const HaystackVariableQueryEditor = ({ onChange, query }: Props) => {
let variableInputWidth = 30;

// Computes the query string and calls the onChange function. Should be used instead of onChange for all mutating functions.
Expand Down Expand Up @@ -42,22 +44,28 @@ export const VariableQueryEditor = ({ onChange, query }: Props) => {

const onQueryChange = (newQuery: string) => {
if (query.type === "eval") {
onChangeAndSave({ ...query, eval: newQuery });
onChangeAndSave({ ...query, refId: refId, eval: newQuery });
} else if (query.type === "hisRead") {
onChangeAndSave({ ...query, hisRead: newQuery });
onChangeAndSave({ ...query, refId: refId, hisRead: newQuery });
} else if (query.type === "hisReadFilter") {
onChangeAndSave({ ...query, hisReadFilter: newQuery });
onChangeAndSave({ ...query, refId: refId, hisReadFilter: newQuery });
} else if (query.type === "read") {
onChangeAndSave({ ...query, read: newQuery });
onChangeAndSave({ ...query, refId: refId, read: newQuery });
}
};

const onColumnChange = (event: React.FormEvent<HTMLInputElement>) => {
onChangeAndSave({...query, column: event.currentTarget.value,});
onChangeAndSave({...query, column: event.currentTarget.value });
};

const onDisplayColumnChange = (event: React.FormEvent<HTMLInputElement>) => {
onChangeAndSave({...query, displayColumn: event.currentTarget.value,});
onChangeAndSave({...query, displayColumn: event.currentTarget.value });
};

const handleBlur = () => {
if (query.query !== undefined && query.query !== "") {
onChange({ ...query, refId: refId });
}
};

return (
Expand All @@ -78,6 +86,7 @@ export const VariableQueryEditor = ({ onChange, query }: Props) => {
<InlineField label="Column">
<Input
width={variableInputWidth}
onBlur={handleBlur}
onChange={onColumnChange}
value={query.column}
placeholder="Defaults to 'id' or first column"
Expand All @@ -86,6 +95,7 @@ export const VariableQueryEditor = ({ onChange, query }: Props) => {
<InlineField label="Display Column">
<Input
width={variableInputWidth}
onBlur={handleBlur}
onChange={onDisplayColumnChange}
value={query.displayColumn}
placeholder="Defaults to 'Column'"
Expand Down
96 changes: 4 additions & 92 deletions src/datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,13 @@ import {
DataQueryRequest,
DataFrame,
Field,
MetricFindValue,
getDefaultTimeRange,
FieldType,
CustomVariableSupport,
DataQueryResponse,
QueryEditorProps,
} from '@grafana/data';
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';

import { HaystackQuery, OpsQuery, HaystackDataSourceOptions, HaystackVariableQuery, QueryType } from './types';
import { firstValueFrom, map, Observable } from 'rxjs';
import { isRef, parseRef } from 'haystack';
import { ComponentType } from 'react';
import { VariableQueryEditor } from 'components/VariableQueryEditor';
import { HaystackQuery, OpsQuery, HaystackDataSourceOptions, QueryType } from './types';
import { firstValueFrom } from 'rxjs';
import { HaystackVariableSupport } from 'HaystackVariableSupport';

export const queryTypes: QueryType[] = [
{ label: 'Eval', value: 'eval', apiRequirements: ['eval'], description: 'Evaluate an Axon expression' },
Expand All @@ -34,9 +27,7 @@ export const queryTypes: QueryType[] = [
export class DataSource extends DataSourceWithBackend<HaystackQuery, HaystackDataSourceOptions> {
constructor(instanceSettings: DataSourceInstanceSettings<HaystackDataSourceOptions>) {
super(instanceSettings);
this.variables = new HaystackVariableSupport((request) => {
return this.query(request)
});
this.variables = new HaystackVariableSupport(this);
}

// Queries the available ops from the datasource and returns the queryTypes that are supported.
Expand Down Expand Up @@ -115,82 +106,3 @@ export class DataSource extends DataSourceWithBackend<HaystackQuery, HaystackDat
};
}
}

export class HaystackVariableSupport extends CustomVariableSupport<DataSource, HaystackVariableQuery, HaystackQuery, HaystackDataSourceOptions> {
editor: ComponentType<QueryEditorProps<DataSource, HaystackQuery, HaystackDataSourceOptions, HaystackVariableQuery>>;

// Requests data from the backend. This allows this class to reuse the DataSource.query method to get data.
onQuery: (request: DataQueryRequest<HaystackVariableQuery>) => Observable<DataQueryResponse>;

constructor(onQuery: (request: DataQueryRequest<HaystackVariableQuery>) => Observable<DataQueryResponse>) {
super();
this.editor = VariableQueryEditor;
this.onQuery = onQuery;
}

query(request: DataQueryRequest<HaystackVariableQuery>): Observable<DataQueryResponse> {
let variableQuery = request.targets[0];
let observable = this.onQuery(request);
return observable.pipe(
map((response) => {
if (response === undefined || response.errors !== undefined || response.data === undefined) {
return response
}

let variableValues = response.data.reduce((acc: MetricFindValue[], frame: DataFrame) => {
// Default to the first field
let column = frame.fields[0];
if (variableQuery.column !== undefined && variableQuery.column !== '') {
// If a column was input, match the column name
column = frame.fields.find((field: Field) => field.name === variableQuery.column) ?? column;
} else if (frame.fields.some((field: Field) => field.name === 'id')) {
// If there is an id column, use that
column = frame.fields.find((field: Field) => field.name === 'id') ?? column;
}

// Default to the selected column
let displayColumn = column;
if (variableQuery.displayColumn !== undefined && variableQuery.displayColumn !== '') {
// If a column was input, match the column name
displayColumn = frame.fields.find((field: Field) => {
return field.name === variableQuery.displayColumn
}) ?? displayColumn;
}

let variableValues = column.values.map((value, index) => {
let variableValue = variableValueFromCell(value, column.type);

let displayValue = displayColumn.values[index];
let variableText = variableTextFromCell(displayValue, displayColumn.type);

return { text: variableText, value: variableValue };
});
return acc.concat(variableValues);
}, []);
return { ...response, data: variableValues };
})
);
}
}


function variableValueFromCell(value: string, columnType: FieldType): string {
switch (columnType) {
case FieldType.string:
if (isRef(value)) {
return parseRef(value).id;
}
}
return value;
}

function variableTextFromCell(value: string, columnType: FieldType): string {
switch (columnType) {
case FieldType.string:
if (isRef(value)) {
let ref = parseRef(value);
return ref.dis ?? ref.id;
}
}
return value;
}