Skip to content

Commit fe190ef

Browse files
Add Assistant helper for creating JQ and JSONata parser (#1302)
Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>
1 parent 43a59ff commit fe190ef

File tree

9 files changed

+92
-15
lines changed

9 files changed

+92
-15
lines changed

.changeset/chilled-walls-know.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'grafana-infinity-datasource': minor
3+
---
4+
5+
Add Assistant helper for creating JQ and JSONata parser

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ e2e-results/
1515
logs
1616
mage_output_file.go
1717
node_modules/
18+
.yarn/install-state.gz
1819
npm-debug.log*
1920
pids
2021
provisioning/

.yarn/install-state.gz

-1.37 MB
Binary file not shown.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
},
5353
"dependencies": {
5454
"@emotion/css": "11.10.6",
55+
"@grafana/assistant": "^0.0.17",
5556
"@grafana/data": "^12.1.0",
5657
"@grafana/runtime": "^12.1.0",
5758
"@grafana/schema": "^12.1.0",

src/editors/query.editor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ export const QueryEditor = (props: QueryEditorProps<Datasource, InfinityQuery>)
1313
...DefaultInfinityQuery,
1414
global_query_id: getDefaultGlobalQueryID(datasource.instanceSettings),
1515
});
16-
return <InfinityQueryEditor onChange={onChange} onRunQuery={onRunQuery} query={query} mode={'standard'} instanceSettings={datasource.instanceSettings} datasource={datasource} />;
16+
return <InfinityQueryEditor onChange={onChange} onRunQuery={onRunQuery} query={query} mode={'standard'} instanceSettings={datasource.instanceSettings} datasource={datasource} data={props.data} />;
1717
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import { OpenAssistantButton, createAssistantContextItem } from '@grafana/assistant';
3+
import type { PanelData } from '@grafana/data';
4+
import type { InfinityParserType } from '@/types';
5+
6+
type RootSelectorAssistantProps = { parser?: InfinityParserType; datasourceUid?: string; panelData?: PanelData };
7+
8+
export const RootSelectorAssistant = (props: RootSelectorAssistantProps) => {
9+
const { panelData, datasourceUid, parser } = props;
10+
if (!datasourceUid) {
11+
return <></>;
12+
}
13+
if (parser !== 'backend' && parser !== 'jq-backend') {
14+
return <></>;
15+
}
16+
const parserLang = parser === 'jq-backend' ? 'JQ' : 'JSONata';
17+
return (
18+
<OpenAssistantButton
19+
title="Use Assistant to parse data"
20+
origin="grafana-datasources/yesoreyeram-infinity-datasource/query-builder-parser"
21+
size="sm"
22+
prompt={`Create a ${parserLang} parser expression that extracts rows from provided data. The expression should work with the sample data provided in the context.`}
23+
context={getChatContext(datasourceUid, parserLang, panelData)}
24+
/>
25+
);
26+
};
27+
28+
const getChatContext = (datasourceUid: string, parserLang: 'JQ' | 'JSONata', panelData?: PanelData) => {
29+
const data = panelData?.series?.[0]?.meta?.custom?.data || [];
30+
const stringifiedData = Array.isArray(data) ? JSON.stringify(data.slice(0, 5)) : JSON.stringify(data ?? '').slice(0, 1500); // We take first 5 items if it is array, or the first 1500 character
31+
const basicRootSelector = parserLang === 'JQ' ? '.' : '$';
32+
const instructions = `
33+
- The data is provided in string format as a stringifiedData
34+
- Analyze the data structure first: identify if it's an array of objects, nested objects, or mixed structure
35+
- Use proper ${parserLang} syntax
36+
- Use ${basicRootSelector} as a root selector
37+
- If data has null/undefined values, handle them gracefully with null coalescing or default values
38+
- Provide 3 different examples:
39+
1. Basic extraction
40+
2. Nested extraction
41+
3. Filtered extraction
42+
- Explain what each expression does`;
43+
return [
44+
createAssistantContextItem('datasource', { datasourceUid }),
45+
createAssistantContextItem('structured', { title: 'Data', data: { stringifiedData } }),
46+
createAssistantContextItem('structured', { hidden: true, title: 'Page-specific instructions', data: { instructions } }),
47+
];
48+
};

src/editors/query/infinityQuery.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { Datasource } from '@/datasource';
1818
import { PaginationEditor } from '@/editors/query/query.pagination';
1919
import { TransformationsEditor } from '@/editors/query/query.transformations';
2020
import { QueryWarning } from '@/editors/query/query.warning';
21+
import type { PanelData } from '@grafana/data';
2122
import type { EditorMode, InfinityQuery } from '@/types';
2223

2324
export type InfinityEditorProps = {
@@ -27,10 +28,11 @@ export type InfinityEditorProps = {
2728
instanceSettings: any;
2829
mode: EditorMode;
2930
datasource: Datasource;
31+
data?: PanelData;
3032
};
3133

3234
export const InfinityQueryEditor = (props: InfinityEditorProps) => {
33-
const { onChange, mode, instanceSettings, onRunQuery, datasource } = props;
35+
const { onChange, mode, instanceSettings, onRunQuery, datasource, data } = props;
3436
const [showUrlOptions, setShowUrlOptions] = useState(false);
3537
const [showHelp, setShowHelp] = useState(false);
3638
let query: InfinityQuery = defaultsDeep(cloneDeep(props.query), DefaultInfinityQuery) as InfinityQuery;
@@ -76,7 +78,7 @@ export const InfinityQueryEditor = (props: InfinityEditorProps) => {
7678
{query.type === 'series' && <SeriesEditor {...{ query, onChange }} />}
7779
{isDataQuery(query) && query.source !== 'inline' && showUrlOptions && <URLEditor {...{ mode, query, onChange, onRunQuery }} />}
7880
{isDataQuery(query) && query.source === 'azure-blob' && <AzureBlobEditor query={query} onChange={onChange} />}
79-
{canShowColumnsEditor && <QueryColumnsEditor {...{ mode, query, onChange, onRunQuery }} />}
81+
{canShowColumnsEditor && <QueryColumnsEditor {...{ mode, query, onChange, onRunQuery, datasourceUid: datasource?.uid, data }} />}
8082
{canShowFilterEditor && <TableFilter {...{ query, onChange, onRunQuery }} />}
8183
{query.type === 'uql' && (
8284
<>

src/editors/query/query.columns.editor.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import React, { useState } from 'react';
22
import { Button, TextArea, Stack } from '@grafana/ui';
3+
import { PanelData } from '@grafana/data';
34
import { EditorRow } from '@/components/extended/EditorRow';
45
import { EditorField } from '@/components/extended/EditorField';
56
import { isBackendQuery, isDataQuery } from '@/app/utils';
67
import { QueryColumnItem } from '@/components/QueryColumnItem';
78
import { JSONOptionsEditor } from '@/components/JSONOptionsEditor';
89
import { CSVOptionsEditor } from '@/components/CSVOptionsEditor';
10+
import { RootSelectorAssistant } from '@/editors/query/components/RootSelectorAssistant';
911
import { UQLEditor } from '@/editors/query/query.uql';
1012
import { GROQEditor } from '@/editors/query/query.groq';
1113
import type { InfinityColumn, InfinityQuery } from '@/types';
1214

13-
export const QueryColumnsEditor = (props: { query: InfinityQuery; onChange: (value: any) => void; onRunQuery: () => void }) => {
15+
export const QueryColumnsEditor = (props: { query: InfinityQuery; onChange: (value: any) => void; onRunQuery: () => void; datasourceUid?: string; data?: PanelData }) => {
1416
const { query, onChange, onRunQuery } = props;
1517
if (!isDataQuery(query) && query.type !== 'google-sheets') {
1618
return <></>;
@@ -113,8 +115,8 @@ export const QueryColumnsEditor = (props: { query: InfinityQuery; onChange: (val
113115
);
114116
};
115117

116-
const RootSelector = (props: { query: InfinityQuery; onChange: (value: any) => void; onRunQuery: () => void }) => {
117-
const { query, onChange, onRunQuery } = props;
118+
const RootSelector = (props: { query: InfinityQuery; onChange: (value: any) => void; onRunQuery: () => void; datasourceUid?: string; data?: PanelData }) => {
119+
const { query, onChange, onRunQuery, datasourceUid, data: panelData } = props;
118120
const [root_selector, setRootSelector] = useState(isDataQuery(query) ? query.root_selector || '' : '');
119121
if (!isDataQuery(query)) {
120122
return <></>;
@@ -125,15 +127,18 @@ const RootSelector = (props: { query: InfinityQuery; onChange: (value: any) => v
125127
};
126128
return ['html', 'json', 'xml', 'graphql'].indexOf(props.query.type) > -1 ? (
127129
<EditorField label="Rows/Root" optional={true}>
128-
<TextArea
129-
width={'300px'}
130-
cols={50}
131-
rows={isBackendQuery(query) ? 7 : 2}
132-
value={root_selector}
133-
placeholder={isBackendQuery(query) ? (query.parser === 'jq-backend' ? 'JQ / rows selector' : 'JSONata / rows selector') : 'rows / root selector (optional)'}
134-
onChange={(e) => setRootSelector(e.currentTarget.value)}
135-
onBlur={onRootSelectorChange}
136-
/>
130+
<Stack direction="column" gap={2} alignItems="flex-start">
131+
<TextArea
132+
width={'300px'}
133+
cols={50}
134+
rows={isBackendQuery(query) ? 7 : 2}
135+
value={root_selector}
136+
placeholder={isBackendQuery(query) ? (query.parser === 'jq-backend' ? 'JQ / rows selector' : 'JSONata / rows selector') : 'rows / root selector (optional)'}
137+
onChange={(e) => setRootSelector(e.currentTarget.value)}
138+
onBlur={onRootSelectorChange}
139+
/>
140+
<RootSelectorAssistant datasourceUid={datasourceUid} parser={query.parser} panelData={panelData} />
141+
</Stack>
137142
</EditorField>
138143
) : (
139144
<></>

yarn.lock

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,20 @@ __metadata:
13441344
languageName: node
13451345
linkType: hard
13461346

1347+
"@grafana/assistant@npm:^0.0.17":
1348+
version: 0.0.17
1349+
resolution: "@grafana/assistant@npm:0.0.17"
1350+
peerDependencies:
1351+
"@grafana/data": ">=12.1.0"
1352+
"@grafana/runtime": ">=12.1.0"
1353+
"@grafana/scenes": ">=5.41.0"
1354+
"@grafana/ui": ">=12.1.0"
1355+
react: ">=18.0.0"
1356+
rxjs: ">=7.0.0"
1357+
checksum: 10c0/60cdf83b8a5ce9d1acb4e9df0917d2d87cc32a6faa9186e77c27e9fb7527c0f44b0d3917ee650263dcf7a417af493a2d2c9b96f97ef660dde1fc0d9095402c33
1358+
languageName: node
1359+
linkType: hard
1360+
13471361
"@grafana/data@npm:12.1.0, @grafana/data@npm:^12.1.0":
13481362
version: 12.1.0
13491363
resolution: "@grafana/data@npm:12.1.0"
@@ -7280,6 +7294,7 @@ __metadata:
72807294
"@changesets/cli": "npm:^2.26.1"
72817295
"@changesets/types": "npm:^5.2.1"
72827296
"@emotion/css": "npm:11.10.6"
7297+
"@grafana/assistant": "npm:^0.0.17"
72837298
"@grafana/data": "npm:^12.1.0"
72847299
"@grafana/eslint-config": "npm:^8.0.0"
72857300
"@grafana/plugin-e2e": "npm:^2.1.7"

0 commit comments

Comments
 (0)