Skip to content

Commit d3b2a3c

Browse files
author
Tony Hignett
committed
Fix plotting series when the first data point is empty.
- 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.
1 parent 994dfac commit d3b2a3c

9 files changed

Lines changed: 171 additions & 3226 deletions

File tree

dist/module.js

Lines changed: 2 additions & 3199 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/module.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
{ "name": "GitHub Security Advisories", "path": "img/github_security_advisories.png"}
2626
],
2727
"version": "1.3.0",
28-
"updated": "2021-03-16"
28+
"updated": "2021-05-25"
2929
},
3030

3131
"dependencies": {

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"moment": "*"
1919
},
2020
"dependencies": {
21-
"@types/lodash": "^4.14.144"
21+
"@types/lodash": "^4.14.144",
22+
"graphql": "^15.5.0"
2223
}
2324
}

src/DataSource.ts

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,31 @@ import {
1919
MyVariableQuery,
2020
MultiValueVariable,
2121
TextValuePair,
22+
RequestFactory,
2223
} from './types';
2324
import { dateTime, MutableDataFrame, FieldType, DataFrame } from '@grafana/data';
2425
import { getTemplateSrv } from '@grafana/runtime';
25-
import _ from 'lodash';
2626
import { isEqual } from 'lodash';
2727
import { flatten, isRFC3339_ISO6801 } from './util';
28+
import { GraphQLObjectType, isObjectType } from 'graphql';
29+
import { Schema } from './schema';
2830

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

31-
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
32-
basicAuth: string | undefined;
33-
withCredentials: boolean | undefined;
34-
url: string | undefined;
35-
36-
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>, private backendSrv: any) {
37-
super(instanceSettings);
38-
this.basicAuth = instanceSettings.basicAuth;
39-
this.withCredentials = instanceSettings.withCredentials;
40-
this.url = instanceSettings.url;
33+
class _RequestFactory implements RequestFactory {
34+
constructor(
35+
private basicAuth: string | undefined,
36+
private withCredentials: boolean | undefined,
37+
private url: string,
38+
private backendSrv: any
39+
) {
40+
this.basicAuth = basicAuth;
41+
this.withCredentials = withCredentials;
42+
this.url = url;
43+
this.backendSrv = backendSrv;
4144
}
4245

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

6164
return this.backendSrv.datasourceRequest(options);
6265
}
66+
}
67+
68+
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
69+
private schema: Schema;
70+
private requestFactory: RequestFactory;
71+
72+
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>, backendSrv: any) {
73+
super(instanceSettings);
74+
this.requestFactory = new _RequestFactory(
75+
instanceSettings.basicAuth,
76+
instanceSettings.withCredentials,
77+
instanceSettings.url as string,
78+
backendSrv
79+
);
80+
this.schema = new Schema(this.requestFactory);
81+
}
6382

6483
private postQuery(query: Partial<MyQuery>, payload: string) {
65-
return this.request(payload)
84+
return this.requestFactory
85+
.request(payload)
6686
.then((results: any) => {
6787
return { query, results };
6888
})
@@ -138,12 +158,15 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
138158
}
139159

140160
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
141-
return Promise.all(
142-
options.targets.map((target) => {
143-
return this.createQuery(defaults(target, defaultQuery), options.range, options.scopedVars);
144-
})
145-
).then((results: any) => {
161+
let promises: Array<Promise<any>> = options.targets.map((target) => {
162+
return this.createQuery(defaults(target, defaultQuery), options.range, options.scopedVars);
163+
});
164+
promises.push(this.schema.getQuery());
165+
166+
return Promise.all(promises).then((results: any[]) => {
146167
const dataFrameArray: DataFrame[] = [];
168+
let queryType: GraphQLObjectType = results.pop();
169+
147170
for (let res of results) {
148171
const dataPathArray: string[] = DataSource.getDataPathArray(res.query.dataPath);
149172
const { timePath, timeFormat, groupBy, aliasBy } = res.query;
@@ -157,6 +180,10 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
157180
}
158181
for (const dataPath of dataPathArray) {
159182
const docs: any[] = DataSource.getDocs(res.results.data, dataPath);
183+
let dataType = Schema.getTypeOfDescendant(queryType, dataPath);
184+
if (!isObjectType(dataType)) {
185+
throw `Data path ${dataPath} has type ${dataType.name}, expected object type`;
186+
}
160187

161188
const dataFrameMap = new Map<string, MutableDataFrame>();
162189
for (const doc of docs) {
@@ -180,9 +207,13 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
180207
let t: FieldType = FieldType.string;
181208
if (fieldName === timePath || isRFC3339_ISO6801(String(doc[fieldName]))) {
182209
t = FieldType.time;
183-
} else if (_.isNumber(doc[fieldName])) {
184-
t = FieldType.number;
210+
} else {
211+
let fieldType = Schema.getTypeOfDescendant(dataType, fieldName);
212+
if (Schema.isNumericType(fieldType)) {
213+
t = FieldType.number;
214+
}
185215
}
216+
186217
let title;
187218
if (identifiers.length !== 0) {
188219
// if we have any identifiers

src/schema.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { GraphQLFloat, GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql';
2+
import { Schema } from './schema';
3+
4+
test('getTypeOfDescendant', () => {
5+
const childType = new GraphQLObjectType({
6+
name: 'child-type',
7+
fields: {
8+
grandchild1: { type: GraphQLInt },
9+
grandchild2: { type: GraphQLFloat },
10+
},
11+
});
12+
const parentType = new GraphQLObjectType({
13+
name: 'parent-type',
14+
fields: {
15+
child1: { type: GraphQLString },
16+
child2: {
17+
type: childType,
18+
},
19+
},
20+
});
21+
22+
expect(Schema.getTypeOfDescendant(parentType, 'child1')).toBe(GraphQLString);
23+
expect(Schema.getTypeOfDescendant(parentType, 'child2')).toBe(childType);
24+
expect(Schema.getTypeOfDescendant(parentType, 'child2.grandchild1')).toBe(GraphQLInt);
25+
expect(Schema.getTypeOfDescendant(parentType, 'child2.grandchild2')).toBe(GraphQLFloat);
26+
expect(Schema.getTypeOfDescendant(childType, 'grandchild1')).toBe(GraphQLInt);
27+
});
28+
29+
describe('isNumericType', () => {
30+
test('object', () => {
31+
const type = new GraphQLObjectType({ name: 'Address', fields: { street: { type: GraphQLString } } });
32+
expect(Schema.isNumericType(type)).not.toBeTruthy();
33+
});
34+
35+
test('string', () => {
36+
expect(Schema.isNumericType(GraphQLString)).not.toBeTruthy();
37+
});
38+
39+
test('int', () => {
40+
expect(Schema.isNumericType(GraphQLInt)).toBeTruthy();
41+
});
42+
43+
test('float', () => {
44+
expect(Schema.isNumericType(GraphQLFloat)).toBeTruthy();
45+
});
46+
});

src/schema.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
buildClientSchema,
3+
getIntrospectionQuery,
4+
getNamedType,
5+
GraphQLNamedType,
6+
GraphQLObjectType,
7+
isObjectType,
8+
isScalarType,
9+
printSchema,
10+
} from 'graphql';
11+
import { RequestFactory } from './types';
12+
13+
export class Schema {
14+
private query: Promise<GraphQLObjectType> | undefined;
15+
16+
constructor(private requestFactory: RequestFactory) {
17+
this.requestFactory = requestFactory;
18+
}
19+
20+
getQuery(): Promise<GraphQLObjectType> {
21+
if (!this.query) {
22+
this.query = this.requestFactory.request(getIntrospectionQuery()).then((results: any) => {
23+
let schema = buildClientSchema(results.data.data);
24+
let queryType = schema.getQueryType();
25+
if (!queryType) {
26+
throw `No query type in schema: ${printSchema(schema)}`;
27+
}
28+
return queryType;
29+
});
30+
}
31+
// @ts-ignore (it's defined now)
32+
return this.query;
33+
}
34+
35+
static getTypeOfDescendant(nodeType: GraphQLObjectType, path: string): GraphQLNamedType {
36+
let descendantType = nodeType;
37+
let pathComponents = path.split('.');
38+
for (let i = 0; i < pathComponents.length; i++) {
39+
let type = getNamedType(descendantType.getFields()[pathComponents[i]].type);
40+
if (i === pathComponents.length - 1) {
41+
return type;
42+
} else {
43+
if (!isObjectType(type)) {
44+
throw `Found type ${type.name} for component ${pathComponents[i]} of ${path}, expected object type`;
45+
}
46+
descendantType = type as GraphQLObjectType;
47+
}
48+
}
49+
return descendantType;
50+
}
51+
52+
static isNumericType(fieldType: GraphQLNamedType): boolean {
53+
return isScalarType(fieldType) && (fieldType.name === 'Int' || fieldType.name === 'Float');
54+
}
55+
}

src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,7 @@ export interface MultiValueVariable extends VariableModel {
5656
current: TextValuePair;
5757
options: TextValuePair[];
5858
}
59+
60+
export interface RequestFactory {
61+
request(data: string): Promise<any>;
62+
}

yarn.lock

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,10 +1795,10 @@
17951795
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
17961796
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
17971797

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

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

5789+
graphql@^15.5.0:
5790+
version "15.5.0"
5791+
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5"
5792+
integrity sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA==
5793+
57895794
growly@^1.3.0:
57905795
version "1.3.0"
57915796
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"

0 commit comments

Comments
 (0)