diff --git a/packages/grafana-ui/src/components/Cascader/Cascader.tsx b/packages/grafana-ui/src/components/Cascader/Cascader.tsx
index 978d48e71c7..5e5ab5ee94e 100644
--- a/packages/grafana-ui/src/components/Cascader/Cascader.tsx
+++ b/packages/grafana-ui/src/components/Cascader/Cascader.tsx
@@ -29,9 +29,11 @@ interface CascaderState {
export interface CascaderOption {
value: any;
label: string;
+ // Items will be just flattened into the main list of items recursively.
items?: CascaderOption[];
disabled?: boolean;
title?: string;
+ // Children will be shown in a submenu.
children?: CascaderOption[];
}
diff --git a/public/app/plugins/datasource/influxdb/components/InfluxLogsQueryField.test.tsx b/public/app/plugins/datasource/influxdb/components/InfluxLogsQueryField.test.tsx
index 9f31f5b1286..10987bb52ea 100644
--- a/public/app/plugins/datasource/influxdb/components/InfluxLogsQueryField.test.tsx
+++ b/public/app/plugins/datasource/influxdb/components/InfluxLogsQueryField.test.tsx
@@ -1,4 +1,10 @@
-import { pairsAreValid } from './InfluxLogsQueryField';
+import React from 'react';
+import { mount } from 'enzyme';
+import { InfluxLogsQueryField, pairsAreValid } from './InfluxLogsQueryField';
+import { InfluxDatasourceMock } from '../datasource.mock';
+import InfluxDatasource from '../datasource';
+import { InfluxQuery } from '../types';
+import { ButtonCascader } from '@grafana/ui';
describe('pairsAreValid()', () => {
describe('when all pairs are fully defined', () => {
@@ -51,3 +57,43 @@ describe('pairsAreValid()', () => {
});
});
});
+
+describe('InfluxLogsQueryField', () => {
+ it('should load and show correct measurements and fields in cascader', async () => {
+ const wrapper = getInfluxLogsQueryField();
+ // Looks strange but we do async stuff in didMount and this will push the stack at the end of eval loop, effectively
+ // waiting for the didMount to finish.
+ await Promise.resolve();
+ wrapper.update();
+ const cascader = wrapper.find(ButtonCascader);
+ expect(cascader.prop('options')).toEqual([
+ { label: 'logs', value: 'logs', children: [{ label: 'description', value: 'description', children: [] }] },
+ ]);
+ });
+});
+
+function getInfluxLogsQueryField(props?: any) {
+ const datasource: InfluxDatasource = new InfluxDatasourceMock(
+ props?.measurements || {
+ logs: [{ name: 'description', type: 'string' }],
+ }
+ ) as any;
+
+ const defaultProps = {
+ datasource,
+ history: [] as any[],
+ onRunQuery: () => {},
+ onChange: (query: InfluxQuery) => {},
+ query: {
+ refId: '',
+ } as InfluxQuery,
+ };
+ return mount(
+
+ );
+}
diff --git a/public/app/plugins/datasource/influxdb/components/InfluxLogsQueryField.tsx b/public/app/plugins/datasource/influxdb/components/InfluxLogsQueryField.tsx
index c3e4b579492..8dc886b31f7 100644
--- a/public/app/plugins/datasource/influxdb/components/InfluxLogsQueryField.tsx
+++ b/public/app/plugins/datasource/influxdb/components/InfluxLogsQueryField.tsx
@@ -75,7 +75,7 @@ export class InfluxLogsQueryField extends React.PureComponent {
measurements.push({
label: measurementObj.text,
value: measurementObj.text,
- items: fields,
+ children: fields,
});
}
this.setState({ measurements });
diff --git a/public/app/plugins/datasource/influxdb/datasource.mock.ts b/public/app/plugins/datasource/influxdb/datasource.mock.ts
new file mode 100644
index 00000000000..fb9c9e1ecf4
--- /dev/null
+++ b/public/app/plugins/datasource/influxdb/datasource.mock.ts
@@ -0,0 +1,54 @@
+type FieldsDefinition = {
+ name: string;
+ // String type, usually something like 'string' or 'float'.
+ type: string;
+};
+type Measurements = { [measurement: string]: FieldsDefinition[] };
+type FieldReturnValue = { text: string };
+
+/**
+ * Datasource mock for influx. At the moment this only works for queries that should return measurements or their
+ * fields and no other functionality is implemented.
+ */
+export class InfluxDatasourceMock {
+ constructor(private measurements: Measurements) {}
+ metricFindQuery(query: string) {
+ if (isMeasurementsQuery(query)) {
+ return this.getMeasurements();
+ } else {
+ return this.getMeasurementFields(query);
+ }
+ }
+
+ private getMeasurements(): FieldReturnValue[] {
+ return Object.keys(this.measurements).map(key => ({ text: key }));
+ }
+
+ private getMeasurementFields(query: string): FieldReturnValue[] {
+ const match = query.match(/SHOW FIELD KEYS FROM \"(.+)\"/);
+ if (!match) {
+ throw new Error(`Failed to match query="${query}"`);
+ }
+ const measurementName = match[1];
+ if (!measurementName) {
+ throw new Error(`Failed to match measurement name from query="${query}"`);
+ }
+
+ const fields = this.measurements[measurementName];
+ if (!fields) {
+ throw new Error(
+ `Failed to find measurement with name="${measurementName}" in measurements="[${Object.keys(
+ this.measurements
+ ).join(', ')}]"`
+ );
+ }
+
+ return fields.map(field => ({
+ text: field.name,
+ }));
+ }
+}
+
+function isMeasurementsQuery(query: string) {
+ return /SHOW MEASUREMENTS/.test(query);
+}