Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AI-5] FDM v3 support #228

Merged
merged 13 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from 11 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
1 change: 0 additions & 1 deletion .eslintcache

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ yarn-error.log*

.idea
.ionide/
.eslintcache
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@

![Select Timeseries](https://raw.githubusercontent.com/cognitedata/cognite-grafana-datasource/release-v2/images/readme/image2.png)


## Installation

The easiest way to install this plugin is to follow the installation instructions on [this page](https://grafana.com/grafana/plugins/cognitedata-datasource/?tab=installation).

## Documentation

To learn more about the connector please visit our [documentation](https://docs.cognite.com/cdf/dashboards/guides/grafana/getting_started.html)
Expand All @@ -43,7 +42,7 @@ ln -s /path/to/cognite-grafana-datasource cognitedata-datasource

`yarn` followed by `yarn build` should work on systems with a shell.

For debugging and development, use `yarn dev:watch`, and for testing use `yarn test`.
For debugging and development, use `yarn watch`, and for testing use `yarn test`.
polomani marked this conversation as resolved.
Show resolved Hide resolved

## Docker

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"identity-obj-proxy": "3.0.0",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"lint-staged": "^13.0.3",
"lint-staged": "^13.2.2",
"prettier": "^2.5.0",
"replace-in-file-webpack-plugin": "^1.0.6",
"sass": "1.56.1",
Expand All @@ -74,7 +74,7 @@
"tsconfig-paths": "^4.1.0",
"typescript": "^4.4.0",
"webpack": "^5.69.1",
"webpack-cli": "^4.9.2",
"webpack-cli": "^5.0.2",
"webpack-livereload-plugin": "^3.0.2"
},
"husky": {
Expand Down Expand Up @@ -106,6 +106,7 @@
"deepdash": "^4.5.4",
"eslint-plugin-jsdoc": "^40.1.1",
"graphql": "^16.6.0",
"graphql-language-service": "^5.1.2",
"graphql-tag": "^2.12.6",
"jsonlint-mod": "^1.7.6",
"lodash": "^4.17.21",
Expand Down
4 changes: 2 additions & 2 deletions src/components/configEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const enableTemplatesTooltip = `Enable the templates tab for use with the Cognit

const enableEventsAdvancedFilteringTooltip = `Enable the Events advanced filtering (preview)`;

const enableFlexibleDataModellingTooltip = 'Enable Flexible Data Modelling (preview)';
const enableFlexibleDataModellingTooltip = 'Enable Data Models (preview)';
polomani marked this conversation as resolved.
Show resolved Hide resolved

const enableExtractionPipelinesTooltip = 'Enable Extraction Pipelines (preview)';

Expand Down Expand Up @@ -251,7 +251,7 @@ export function ConfigEditor(props: ConfigEditorProps) {
</div>
<div className="gf-form-inline">
<Switch
label="Flexible Data Modelling"
label="Data Models"
labelClass="width-12"
checked={enableFlexibleDataModelling}
onChange={onJsonBoolValueChange('enableFlexibleDataModelling')}
Expand Down
171 changes: 130 additions & 41 deletions src/components/flexibleDataModellingTab.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { CodeEditor, Field, HorizontalGroup, Select } from '@grafana/ui';
import {
CodeEditor,
CodeEditorSuggestionItem,
CodeEditorSuggestionItemKind,
Field,
HorizontalGroup,
MonacoEditor,
Select,
} from '@grafana/ui';
import React, { useState, useEffect, useMemo } from 'react';
import { SelectableValue } from '@grafana/data';
import { buildClientSchema, GraphQLSchema } from 'graphql';
import {
FDMResponseToDropdown,
getFirstSelection,
isValidQuery,
reverseSortGet,
typeNameList,
} from '../utils';
CompletionItem,
getAutocompleteSuggestions,
Position,
Range,
} from 'graphql-language-service';
import { getFirstSelection, isValidQuery, typeNameList } from '../utils';
import { FlexibleDataModellingQuery, SelectedProps, EditorProps } from '../types';
import CogniteDatasource from '../datasource';
import { CommonEditors } from './commonEditors';
Expand All @@ -15,15 +25,19 @@ export const FlexibleDataModellingTab = (
props: SelectedProps & Pick<EditorProps, 'onRunQuery'> & { datasource: CogniteDatasource }
) => {
const { query, onQueryChange, datasource } = props;
const [editor, setEditor] = useState<MonacoEditor>();
const { flexibleDataModellingQuery } = query;
const [allOptions, setAllOptions] = useState({});
const [dataModelOptions, setDataModelOptions] = useState([]);
const [names, setNames] = useState({});
const { externalId, space, version, graphQlQuery } = flexibleDataModellingQuery;
const [options, setOptions] = useState<
Array<SelectableValue<{ space: string; externalId: string; version: string; dml: string }>>
>([]);
const [versions, setVersions] = useState<
Array<SelectableValue<{ version: string; dml: string }>>
>([]);
const [dml, setDML] = useState<string>('');
const firstSelection = useMemo(
(graphQlQuery = flexibleDataModellingQuery.graphQlQuery) =>
getFirstSelection(graphQlQuery, query.refId),
// eslint-disable-next-line react-hooks/exhaustive-deps
[flexibleDataModellingQuery.graphQlQuery]
() => getFirstSelection(graphQlQuery, query.refId),
[graphQlQuery, query.refId]
);
const patchFlexibleDataModellingQuery = (
flexibleDataModellingQueryPatch: Partial<FlexibleDataModellingQuery>
Expand All @@ -35,10 +49,10 @@ export const FlexibleDataModellingTab = (
},
});
};
const updateGraphQuery = (graphQlQuery) => {
if (isValidQuery(graphQlQuery, query.refId)) {
const updateGraphQuery = (newQuery) => {
if (isValidQuery(newQuery, query.refId)) {
patchFlexibleDataModellingQuery({
graphQlQuery,
graphQlQuery: newQuery,
});
}
};
Expand All @@ -47,59 +61,105 @@ export const FlexibleDataModellingTab = (
tsKeys: firstSelection.length ? typeNameList(firstSelection) : [],
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [flexibleDataModellingQuery.graphQlQuery, firstSelection]);
}, [graphQlQuery, firstSelection]);

useEffect(() => {
datasource.flexibleDataModellingDatasource
.listFlexibleDataModelling(query.refId)
.then((items) => {
const {
listApis: { edges },
} = items;
const { all, names, dataModelOptions } = FDMResponseToDropdown(edges);
setAllOptions(all);
setNames(names);
setDataModelOptions(dataModelOptions);
.then(({ listGraphQlDmlVersions: { items } }) => {
setOptions(
items.map((el) => ({
label: `${el.name} (${el.externalId}) <${el.space}>`,
value: {
space: el.space,
externalId: el.externalId,
version: el.version,
dml: el.graphQlDml,
},
}))
);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
setVersions([]);
datasource.flexibleDataModellingDatasource
.listVersionByExternalIdAndSpace(query.refId, space, externalId)
.then(({ graphQlDmlVersionsById: { items } }) => {
setVersions(
items.map((el) => ({
label: el.version,
value: { version: el.version, dml: el.graphQlDml },
}))
);
});
}, [space, externalId, datasource.flexibleDataModellingDatasource, query.refId]);

const [schema, setSchema] = useState<GraphQLSchema>();

useEffect(() => {
(async () => {
const data = await datasource.flexibleDataModellingDatasource.runIntrospectionQuery(
{ externalId, space, version },
query
);
setSchema(buildClientSchema(data));
})();
}, [externalId, space, version, datasource.flexibleDataModellingDatasource, query]);

return (
<>
<HorizontalGroup>
<Field label="Data Model">
<Select
options={dataModelOptions}
value={{
label:
names[flexibleDataModellingQuery.externalId] ||
flexibleDataModellingQuery.externalId,
value: flexibleDataModellingQuery.externalId,
}}
onChange={(externalId) => {
patchFlexibleDataModellingQuery({ externalId: externalId.value, version: undefined });
options={options}
value={options.find(
(el) =>
el.value.space === flexibleDataModellingQuery.space &&
el.value.externalId === flexibleDataModellingQuery.externalId
)}
onChange={(data) => {
patchFlexibleDataModellingQuery({
externalId: data.value.externalId,
space: data.value.space,
version: data.value.version,
});
setDML(data.value.dml);
}}
width={24}
/>
</Field>
<Field label="Version">
<Select
options={reverseSortGet(allOptions, flexibleDataModellingQuery.externalId)}
value={{
label: flexibleDataModellingQuery.version?.toString(),
value: flexibleDataModellingQuery.version,
options={versions}
value={versions.find((el) => el.value.version === flexibleDataModellingQuery.version)}
onChange={(update) => {
patchFlexibleDataModellingQuery({ version: update.value.version });
setDML(update.value.dml);
}}
onChange={(version) => patchFlexibleDataModellingQuery({ version: version.value })}
/>
</Field>
</HorizontalGroup>
<Field label="Query" description="GraphQL query">
<CodeEditor
onEditorDidMount={(newEditor) => setEditor(newEditor)}
value={flexibleDataModellingQuery.graphQlQuery ?? ''}
language="graphql"
height={400}
height={200}
onBlur={updateGraphQuery}
onSave={updateGraphQuery}
showMiniMap={false}
showLineNumbers
getSuggestions={() => {
if (schema && editor) {
return getAutocompleteSuggestions(
schema,
editor.getModel().getValue(),
new Position(editor.getPosition().lineNumber - 1, editor.getPosition().column - 1)
).map((el) => toCompletionItem(el));
}
return [];
}}
/>
</Field>
{flexibleDataModellingQuery.tsKeys?.length > 0 && (
Expand All @@ -108,3 +168,32 @@ export const FlexibleDataModellingTab = (
</>
);
};

/** Format the text, adds icon and returns in format that monaco editor expects */
const toCompletionItem = (entry: CompletionItem, range?: Range): CodeEditorSuggestionItem => {
const results = {
label: entry.label,
insertText: entry.insertText || entry.label,
insertTextFormat: entry.insertTextFormat,
sortText: entry.sortText,
filterText: entry.filterText,
documentation: entry.documentation,
detail: entry.detail,
range: range ? toMonacoRange(range) : undefined,
kind: CodeEditorSuggestionItemKind.Property,
};
if (entry.insertTextFormat) {
results.insertTextFormat = entry.insertTextFormat;
}

return results;
};

const toMonacoRange = (range: Range) => {
return {
startLineNumber: range.start.line + 1,
startColumn: range.start.character + 1,
endLineNumber: range.end.line + 1,
endColumn: range.end.character + 1,
};
};
27 changes: 6 additions & 21 deletions src/components/queryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -315,16 +315,6 @@ export function QueryEditor(props: EditorProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tab]);

const tabId = (t) => {
if (
t === Tabs.Templates ||
t === Tabs.FlexibleDataModelling ||
t === Tabs.ExtractionPipelines
) {
return 'preview-tab-label';
}
return '';
};
const hiddenTab = (t) => {
if (t === Tabs.Templates) {
return !datasource.connector.isTemplatesEnabled();
Expand All @@ -337,15 +327,6 @@ export function QueryEditor(props: EditorProps) {
}
return false;
};
const tabClass = (t) => {
if (t === Tabs.FlexibleDataModelling || t === Tabs.ExtractionPipelines) {
return { minWidth: '14em' };
}
if (t === Tabs.Templates) {
return { minWidth: '10em' };
}
return {};
};
return (
<div>
<TabsBar>
Expand All @@ -356,8 +337,12 @@ export function QueryEditor(props: EditorProps) {
key={t}
active={tab === t}
onChangeTab={onSelectTab(t)}
id={tabId(t)}
style={tabClass(t)}
style={{ display: 'flex' }}
suffix={
t === Tabs.Templates || t === Tabs.ExtractionPipelines
? () => <p className="preview-label">Preview</p>
: undefined
}
/>
))}
</TabsBar>
Expand Down
4 changes: 2 additions & 2 deletions src/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ export class Connector {
return data.items;
}

async fetchQuery<T>(params: RequestParams): Promise<FDMResponse> {
const { data } = await this.fetchData<DataResponse<FDMResponse>>(params);
async fetchQuery<T>(params: RequestParams): Promise<FDMResponse<T>> {
const { data } = await this.fetchData<DataResponse<FDMResponse<T>>>(params);
return data;
}

Expand Down
7 changes: 3 additions & 4 deletions src/css/query_editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,18 @@ pre code,
.gf-field-switch > div:last-child {
margin: 8px 0 0 16px;
}
#preview-tab-label:after {
.preview-label {
align-self: center;
background: #ffdc7f;
border-radius: 3px;
color: #262626;
content: 'Preview';
margin-top: 0px;
margin-left: 5px;
min-width: 20px;
margin-bottom: 0;
padding-left: 5px;
padding-bottom: 1px;
padding-top: 1px;
padding-right: 5px;
position: absolute;
text-align: center;
font-size: 11px;
font-weight: 500;
Expand Down
Loading