Skip to content

Commit

Permalink
Fix plotting series when the first data point is empty.
Browse files Browse the repository at this point in the history
- Add `graphql` to the dependencies, to help interacting with the schema.
- Get the schema in parallel with the first data query and remember it.
- Use it to find the numeric fields under the data paths, instead of inspecting the first data point.
  • Loading branch information
Tony Hignett committed May 25, 2021
1 parent 994dfac commit 481bbd7
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 3,225 deletions.
3,201 changes: 2 additions & 3,199 deletions dist/module.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/module.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
{ "name": "GitHub Security Advisories", "path": "img/github_security_advisories.png"}
],
"version": "1.3.0",
"updated": "2021-03-16"
"updated": "2021-05-25"
},

"dependencies": {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"moment": "*"
},
"dependencies": {
"@types/lodash": "^4.14.144"
"@types/lodash": "^4.14.144",
"graphql": "^15.5.0"
}
}
71 changes: 51 additions & 20 deletions src/DataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,31 @@ import {
MyVariableQuery,
MultiValueVariable,
TextValuePair,
RequestFactory,
} from './types';
import { dateTime, MutableDataFrame, FieldType, DataFrame } from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime';
import _ from 'lodash';
import { isEqual } from 'lodash';
import { flatten, isRFC3339_ISO6801 } from './util';
import { GraphQLObjectType, isObjectType } from 'graphql';
import { Schema } from './schema';

const supportedVariableTypes = ['constant', 'custom', 'query', 'textbox'];

export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
basicAuth: string | undefined;
withCredentials: boolean | undefined;
url: string | undefined;

constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>, private backendSrv: any) {
super(instanceSettings);
this.basicAuth = instanceSettings.basicAuth;
this.withCredentials = instanceSettings.withCredentials;
this.url = instanceSettings.url;
class _RequestFactory implements RequestFactory {
constructor(
private basicAuth: string | undefined,
private withCredentials: boolean | undefined,
private url: string,
private backendSrv: any
) {
this.basicAuth = basicAuth;
this.withCredentials = withCredentials;
this.url = url;
this.backendSrv = backendSrv;
}

private request(data: string) {
request(data: string): Promise<any> {
const options: any = {
url: this.url,
method: 'POST',
Expand All @@ -60,9 +63,26 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {

return this.backendSrv.datasourceRequest(options);
}
}

export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
private schema: Schema;
private requestFactory: RequestFactory;

constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>, backendSrv: any) {
super(instanceSettings);
this.requestFactory = new _RequestFactory(
instanceSettings.basicAuth,
instanceSettings.withCredentials,
instanceSettings.url as string,
backendSrv
);
this.schema = new Schema(this.requestFactory);
}

private postQuery(query: Partial<MyQuery>, payload: string) {
return this.request(payload)
return this.requestFactory
.request(payload)
.then((results: any) => {
return { query, results };
})
Expand Down Expand Up @@ -138,12 +158,15 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
}

async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
return Promise.all(
options.targets.map((target) => {
return this.createQuery(defaults(target, defaultQuery), options.range, options.scopedVars);
})
).then((results: any) => {
let promises: Array<Promise<any>> = options.targets.map((target) => {
return this.createQuery(defaults(target, defaultQuery), options.range, options.scopedVars);
});
promises.push(this.schema.getQuery());

return Promise.all(promises).then((results: any[]) => {
const dataFrameArray: DataFrame[] = [];
let queryType: GraphQLObjectType = results.pop();

for (let res of results) {
const dataPathArray: string[] = DataSource.getDataPathArray(res.query.dataPath);
const { timePath, timeFormat, groupBy, aliasBy } = res.query;
Expand All @@ -157,6 +180,10 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
}
for (const dataPath of dataPathArray) {
const docs: any[] = DataSource.getDocs(res.results.data, dataPath);
let dataType = Schema.getTypeOfDescendant(queryType, dataPath);
if (!isObjectType(dataType)) {
throw `Data path ${dataPath} has type ${dataType.name}, expected object type`;
}

const dataFrameMap = new Map<string, MutableDataFrame>();
for (const doc of docs) {
Expand All @@ -180,9 +207,13 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
let t: FieldType = FieldType.string;
if (fieldName === timePath || isRFC3339_ISO6801(String(doc[fieldName]))) {
t = FieldType.time;
} else if (_.isNumber(doc[fieldName])) {
t = FieldType.number;
} else {
let fieldType = Schema.getTypeOfDescendant(dataType, fieldName);
if (Schema.isNumericType(fieldType)) {
t = FieldType.number;
}
}

let title;
if (identifiers.length !== 0) {
// if we have any identifiers
Expand Down
46 changes: 46 additions & 0 deletions src/schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { GraphQLFloat, GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql';
import { Schema } from './schema';

test('getTypeOfDescendant', () => {
const childType = new GraphQLObjectType({
name: 'child-type',
fields: {
grandchild1: { type: GraphQLInt },
grandchild2: { type: GraphQLFloat },
},
});
const parentType = new GraphQLObjectType({
name: 'parent-type',
fields: {
child1: { type: GraphQLString },
child2: {
type: childType,
},
},
});

expect(Schema.getTypeOfDescendant(parentType, 'child1')).toBe(GraphQLString);
expect(Schema.getTypeOfDescendant(parentType, 'child2')).toBe(childType);
expect(Schema.getTypeOfDescendant(parentType, 'child2.grandchild1')).toBe(GraphQLInt);
expect(Schema.getTypeOfDescendant(parentType, 'child2.grandchild2')).toBe(GraphQLFloat);
expect(Schema.getTypeOfDescendant(childType, 'grandchild1')).toBe(GraphQLInt);
});

describe('isNumericType', () => {
test('object', () => {
const type = new GraphQLObjectType({ name: 'Address', fields: { street: { type: GraphQLString } } });
expect(Schema.isNumericType(type)).not.toBeTruthy();
});

test('string', () => {
expect(Schema.isNumericType(GraphQLString)).not.toBeTruthy();
});

test('int', () => {
expect(Schema.isNumericType(GraphQLInt)).toBeTruthy();
});

test('float', () => {
expect(Schema.isNumericType(GraphQLFloat)).toBeTruthy();
});
});
55 changes: 55 additions & 0 deletions src/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
buildClientSchema,
getIntrospectionQuery,
getNamedType,
GraphQLNamedType,
GraphQLObjectType,
isObjectType,
isScalarType,
printSchema,
} from 'graphql';
import { RequestFactory } from './types';

export class Schema {
private query: Promise<GraphQLObjectType> | undefined;

constructor(private requestFactory: RequestFactory) {
this.requestFactory = requestFactory;
}

getQuery(): Promise<GraphQLObjectType> {
if (!this.query) {
this.query = this.requestFactory.request(getIntrospectionQuery()).then((results: any) => {
let schema = buildClientSchema(results.data.data);
let queryType = schema.getQueryType();
if (!queryType) {
throw `No query type in schema: ${printSchema(schema)}`;
}
return queryType;
});
}
// @ts-ignore (it's defined now)
return this.query;
}

static getTypeOfDescendant(nodeType: GraphQLObjectType, path: string): GraphQLNamedType {
let descendantType = nodeType;
let pathComponents = path.split('.');
for (let i = 0; i < pathComponents.length; i++) {
let type = getNamedType(descendantType.getFields()[pathComponents[i]].type);
if (i === pathComponents.length - 1) {
return type;
} else {
if (!isObjectType(type)) {
throw `Found type ${type.name} for component ${pathComponents[i]} of ${path}, expected object type`;
}
descendantType = type as GraphQLObjectType;
}
}
return descendantType;
}

static isNumericType(fieldType: GraphQLNamedType): boolean {
return isScalarType(fieldType) && (fieldType.name === 'Int' || fieldType.name === 'Float');
}
}
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ export interface MultiValueVariable extends VariableModel {
current: TextValuePair;
options: TextValuePair[];
}

export interface RequestFactory {
request(data: string): Promise<any>;
}
11 changes: 8 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1796,9 +1796,9 @@
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==

"@types/lodash@^4.14.144":
version "4.14.167"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.167.tgz#ce7d78553e3c886d4ea643c37ec7edc20f16765e"
integrity sha512-w7tQPjARrvdeBkX/Rwg95S592JwxqOjmms3zWQ0XZgSyxSLdzWaYH3vErBhdVS/lRBX7F8aBYcYJYTr5TMGOzw==
version "4.14.170"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6"
integrity sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==

"@types/mime@*":
version "2.0.3"
Expand Down Expand Up @@ -5786,6 +5786,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==

graphql@^15.5.0:
version "15.5.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5"
integrity sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA==

growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
Expand Down

0 comments on commit 481bbd7

Please sign in to comment.