Skip to content

Commit

Permalink
Add a Profile Selector Query Editor
Browse files Browse the repository at this point in the history
  • Loading branch information
cyriltovena committed Aug 1, 2022
1 parent 9d82692 commit f401a7c
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 55 deletions.
36 changes: 33 additions & 3 deletions grafana/fire-datasource/pkg/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import (
"time"

"github.com/bufbuild/connect-go"
querierv1 "github.com/grafana/fire/pkg/gen/querier/v1"
"github.com/grafana/fire/pkg/gen/querier/v1/querierv1connect"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana-plugin-sdk-go/live"

querierv1 "github.com/grafana/fire/pkg/gen/querier/v1"
)

// Make sure FireDatasource implements required interfaces. This is important to do
Expand All @@ -27,6 +28,7 @@ import (
// instance created upon datasource settings changed.
var (
_ backend.QueryDataHandler = (*FireDatasource)(nil)
_ backend.CallResourceHandler = (*FireDatasource)(nil)
_ backend.CheckHealthHandler = (*FireDatasource)(nil)
_ backend.StreamHandler = (*FireDatasource)(nil)
_ instancemgmt.InstanceDisposer = (*FireDatasource)(nil)
Expand Down Expand Up @@ -62,6 +64,32 @@ func (d *FireDatasource) Dispose() {
// Clean up datasource instance resources.
}

func (d *FireDatasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
log.DefaultLogger.Info("CallResource", "req", req)
if req.Path == "profileTypes" {
return d.callProfileTypes(ctx, req, sender)
}
return sender.Send(&backend.CallResourceResponse{
Status: 404,
})
}

func (d *FireDatasource) callProfileTypes(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
res, err := d.client.ProfileTypes(ctx, connect.NewRequest(&querierv1.ProfileTypesRequest{}))
if err != nil {
return err
}
data, err := json.Marshal(res.Msg.ProfileTypes)
if err != nil {
return err
}
err = sender.Send(&backend.CallResourceResponse{Body: data, Headers: req.Headers, Status: 200})
if err != nil {
return err
}
return nil
}

// QueryData handles multiple queries and returns multiple responses.
// req contains the queries []DataQuery (where each query contains RefID as a unique identifier).
// The QueryDataResponse contains a map of RefID to the response for each query, and each response
Expand Down Expand Up @@ -104,9 +132,11 @@ func (d *FireDatasource) query(_ context.Context, pCtx backend.PluginContext, qu

// add fields.
frame.Fields = append(frame.Fields,
data.NewField("time", nil, []time.Time{query.TimeRange.From, query.TimeRange.To}),
data.NewField("values", nil, []int64{10, 20}),
data.NewField("levels.0", nil, []string{`[0, 4862950000000, 0, 0]`}),
data.NewField("levels.1", nil, []string{`[0, 75210000000, 70000000, 6112, 0, 884550000000, 490000000, 5601]`}),
)
// new frame for names
// data.NewField("names", nil, []string{"func1", "func2", "func3", "func4", "func5", "func6", "func7", "func8"}),

// If query called with streaming on then return a channel
// to subscribe on a client-side and consume updates from a plugin.
Expand Down
96 changes: 56 additions & 40 deletions grafana/fire-datasource/src/QueryEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,73 @@
import { defaults } from 'lodash';

import React, { ChangeEvent, PureComponent, SyntheticEvent } from 'react';
import { LegacyForms } from '@grafana/ui';
import React, { PureComponent, FormEvent } from 'react';
import { Input, ButtonCascader, CascaderOption } from '@grafana/ui';
import { QueryEditorProps } from '@grafana/data';
import { DataSource } from './datasource';
import { defaultQuery, MyDataSourceOptions, MyQuery } from './types';
import { defaultQuery, MyDataSourceOptions, ProfileType, Query } from './types';

const { FormField, Switch } = LegacyForms;

type Props = QueryEditorProps<DataSource, MyQuery, MyDataSourceOptions>;
type Props = QueryEditorProps<DataSource, Query, MyDataSourceOptions>

export class QueryEditor extends PureComponent<Props> {
onQueryTextChange = (event: ChangeEvent<HTMLInputElement>) => {
const { onChange, query } = this.props;
onChange({ ...query, queryText: event.target.value });
};
interface State {
profileTypes: Array<CascaderOption>
}

onConstantChange = (event: ChangeEvent<HTMLInputElement>) => {
const { onChange, query, onRunQuery } = this.props;
onChange({ ...query, constant: parseFloat(event.target.value) });
// executes the query
onRunQuery();
export class QueryEditor extends PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
profileTypes: [],
};
}

onProfileTypeChange = (value: string[], selectedOptions: CascaderOption[]) => {
if (selectedOptions.length == 0) {
return
}
let type = selectedOptions[selectedOptions.length - 1].value as ProfileType;
this.props.onChange({ ...this.props.query, ProfileType: type });
};
onLabelSelectorChange = (value: FormEvent<HTMLInputElement>) => {
this.props.onChange({ ...this.props.query, LabelSelector: value.currentTarget.value });
};
componentDidMount() {
this.props.datasource.getProfileTypes().then(profileTypes => {
let mainTypes = new Map<string, CascaderOption>();

onWithStreamingChange = (event: SyntheticEvent<HTMLInputElement>) => {
const { onChange, query, onRunQuery } = this.props;
onChange({ ...query, withStreaming: event.currentTarget.checked });
// executes the query
onRunQuery();
// Classify profile types by name then sample type.
for (let profileType of profileTypes) {
if (!mainTypes.has(profileType.name)) {
mainTypes.set(profileType.name, {
label: profileType.name,
value: profileType,
children: [],
});
}
mainTypes.get(profileType.name)?.children?.push({
label: profileType.sampleType,
value: profileType,
})
}
let types = new Array<CascaderOption>();
for (let [_, value] of mainTypes) {
types.push(value);
}
this.setState({
profileTypes: types,
});
});
};

render() {
const query = defaults(this.props.query, defaultQuery);
const { queryText, constant, withStreaming } = query;

let query = defaults(this.props.query, defaultQuery);
return (
<div className="gf-form">
<FormField
width={4}
value={constant}
onChange={this.onConstantChange}
label="Constant"
type="number"
step="0.1"
/>
<FormField
labelWidth={8}
value={queryText || ''}
onChange={this.onQueryTextChange}
label="Query Text"
tooltip="Not used yet"
/>
<Switch checked={withStreaming || false} label="Enable streaming (v8+)" onChange={this.onWithStreamingChange} />
<ButtonCascader
onChange={this.onProfileTypeChange}
options={this.state.profileTypes}
icon='process'
>{query.ProfileType?.Label() || 'Select a profile type'}</ButtonCascader>
<Input onChange={this.onLabelSelectorChange} value={query.LabelSelector} />
</div>
);
}
Expand Down
12 changes: 10 additions & 2 deletions grafana/fire-datasource/src/datasource.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { DataSourceInstanceSettings } from '@grafana/data';
import { DataSourceWithBackend } from '@grafana/runtime';
import { MyDataSourceOptions, MyQuery } from './types';
import { MyDataSourceOptions, Query, ProfileType } from './types';

export class DataSource extends DataSourceWithBackend<MyQuery, MyDataSourceOptions> {
export class DataSource extends DataSourceWithBackend<Query, MyDataSourceOptions> {
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
super(instanceSettings);
}

getProfileTypes(): Promise<ProfileType[]> {
return super.getResource("profileTypes").then((response): ProfileType[] => {
return response.map((profileType: any) => {
return new ProfileType(profileType.ID, profileType.name, profileType.periodType, profileType.periodUnit, profileType.sampleType, profileType.sampleUnit);
});
});
}
}
4 changes: 2 additions & 2 deletions grafana/fire-datasource/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { DataSourcePlugin } from '@grafana/data';
import { DataSource } from './datasource';
import { ConfigEditor } from './ConfigEditor';
import { QueryEditor } from './QueryEditor';
import { MyQuery, MyDataSourceOptions } from './types';
import { Query, MyDataSourceOptions } from './types';

export const plugin = new DataSourcePlugin<DataSource, MyQuery, MyDataSourceOptions>(DataSource)
export const plugin = new DataSourcePlugin<DataSource, Query, MyDataSourceOptions>(DataSource)
.setConfigEditor(ConfigEditor)
.setQueryEditor(QueryEditor);
35 changes: 27 additions & 8 deletions grafana/fire-datasource/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
import { DataQuery, DataSourceJsonData } from '@grafana/data';

export interface MyQuery extends DataQuery {
queryText?: string;
constant: number;
withStreaming: boolean;
export interface Query extends DataQuery {
LabelSelector: string;
ProfileType?: ProfileType;
}

export const defaultQuery: Partial<MyQuery> = {
constant: 6.5,
withStreaming: false,
};
export class ProfileType {
ID: string;
name: string;
periodType: string;
periodUnit: string;
sampleType: string;
sampleUnit: string;
constructor(ID: string, name: string, periodType: string, periodUnit: string, sampleType: string, sampleUnit: string) {
this.ID = ID;
this.name = name;
this.periodType = periodType;
this.periodUnit = periodUnit;
this.sampleType = sampleType;
this.sampleUnit = sampleUnit;
}
Label(): string {
return this.name + " - " + this.sampleType;
}
}

export const defaultQuery: Partial<Query> = {
LabelSelector: "{}",
ProfileType: undefined,
}

/**
* These are options configured for each DataSource instance.
Expand Down

0 comments on commit f401a7c

Please sign in to comment.