diff --git a/package.json b/package.json index fca80498..cc9f14aa 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,8 @@ "dependencies": { "lodash": "^4.17.21", "react-virtualized-auto-sizer": "^1.0.7", - "rxjs": "7.5.7" + "rxjs": "7.5.7", + "ts-pattern": "^4.1.3" }, "homepage": "https://github.com/simPod/GrafanaJsonDatasource", "engineStrict": true, diff --git a/src/Component/QueryBuilder.tsx b/src/Component/QueryBuilder.tsx index f5c5fde2..8d8a9eeb 100644 --- a/src/Component/QueryBuilder.tsx +++ b/src/Component/QueryBuilder.tsx @@ -14,10 +14,12 @@ interface LastQuery { metric: string; } +type UnknownPayload = Array<{ name: string; value: unknown }>; + type EditorProps = QueryEditorProps; interface Props extends EditorProps { - payload: { [key: string]: any }; + payload: { [key: string]: unknown }; } export const QueryBuilder: ComponentType = (props) => { @@ -25,7 +27,7 @@ export const QueryBuilder: ComponentType = (props) => { const [metric, setMetric] = React.useState>(); const [payload, setPayload] = React.useState(props.payload ?? ''); - const [unknownPayload, setUnknownPayload] = React.useState>([]); + const [unknownPayload, setUnknownPayload] = React.useState([]); const [lastQuery, setLastQuery] = React.useState(null); const [payloadConfig, setPayloadConfig] = React.useState([]); @@ -82,12 +84,13 @@ export const QueryBuilder: ComponentType = (props) => { }, [payload, metric]); React.useEffect(() => { - const newUnknownPayload: Array<{ name: string; value: any }> = []; + const newUnknownPayload: UnknownPayload = []; for (const key in payload) { const foundConfig = payloadConfig.find((item) => item.name === key); if (foundConfig === undefined) { newUnknownPayload.push({ name: key, value: payload[key] }); } + setUnknownPayload(newUnknownPayload); } }, [payload, payloadConfig]); @@ -166,7 +169,8 @@ export const QueryBuilder: ComponentType = (props) => { { changePayload(opt.name, opt.reloadMetric, v); @@ -181,6 +185,7 @@ export const QueryBuilder: ComponentType = (props) => { { changePayload(opt.name, opt.reloadMetric, { value: v }); diff --git a/src/Component/QueryBuilderTag.jest.tsx b/src/Component/QueryBuilderTag.jest.tsx new file mode 100644 index 00000000..ef5ce199 --- /dev/null +++ b/src/Component/QueryBuilderTag.jest.tsx @@ -0,0 +1,19 @@ +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { QueryBuilderTag } from './QueryBuilderTag'; +import React from 'react'; + +test.each([ + ['key="text"', 'text'], + ['key=true', true], + ['key=1', 1], + ['key=null', null], + ['key="{"some":"object"}"', { some: 'object' }], +])('displays key-value as %s for %s', async (expectedContent, value) => { + render( {}} />); + + const element: HTMLSpanElement = screen.getByText(/key/); + + expect(element).toBeInTheDocument(); + expect(element).toHaveTextContent(expectedContent); +}); diff --git a/src/Component/QueryBuilderTag.tsx b/src/Component/QueryBuilderTag.tsx index 5ed567d2..85f9934b 100644 --- a/src/Component/QueryBuilderTag.tsx +++ b/src/Component/QueryBuilderTag.tsx @@ -2,10 +2,11 @@ import { css } from '@emotion/css'; import { GrafanaTheme } from '@grafana/data'; import { getTagColorsFromName, IconButton, stylesFactory, useTheme } from '@grafana/ui'; import React, { ComponentType } from 'react'; +import { match } from 'ts-pattern'; type QueryBuilderTagProps = { name: string; - value: string; + value: unknown; onRemove: (name: string) => void; }; @@ -48,10 +49,17 @@ export const QueryBuilderTag: ComponentType = ({ name, val const theme = useTheme(); const styles = getStyles({ theme, name }); + const formattedValue = match(typeof value) + .with('string', () => `"${value}"`) + .with('number', () => value) + .with('boolean', () => (value as boolean).toString()) + .with('object', () => (value === null ? 'null' : `"${JSON.stringify(value)}"`)) + .otherwise(() => `"${JSON.stringify(value)}"`); + return (
- {name}={value} + {name}={formattedValue} ; -function convertPayloadToObject(payload: string | { [key: string]: any }): { [key: string]: any } { +function convertPayloadToObject(payload: string | { [key: string]: unknown }): { [key: string]: unknown } { if (payload) { if (typeof payload === 'string') { try { @@ -29,7 +29,7 @@ function convertPayloadToObject(payload: string | { [key: string]: any }): { [ke } } -const convertPayloadToString = (payload: string | { [key: string]: any }): string => { +const convertPayloadToString = (payload: string | { [key: string]: unknown }): string => { if (typeof payload === 'string') { return payload; } else if (payload) { diff --git a/src/types.d.ts b/src/types.d.ts index c14746cf..e5bfb53c 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -22,7 +22,7 @@ export interface GrafanaQuery extends DataQuery { editorMode?: QueryEditorMode; alias?: string; target?: string; - payload: string | { [key: string]: any }; + payload: string | { [key: string]: unknown }; } export interface GenericOptions extends DataSourceJsonData { diff --git a/yarn.lock b/yarn.lock index 0c666ae0..f4622031 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8069,6 +8069,7 @@ __metadata: react-dom: ^18.2.0 react-virtualized-auto-sizer: ^1.0.7 rxjs: 7.5.7 + ts-pattern: ^4.1.3 languageName: unknown linkType: soft @@ -14085,6 +14086,13 @@ __metadata: languageName: node linkType: hard +"ts-pattern@npm:^4.1.3": + version: 4.1.3 + resolution: "ts-pattern@npm:4.1.3" + checksum: a9462aa0e503dcf8fd1a8d648b02315276f644711bf1c7564a1185cd8b78a7b84fa9bc4f9ecdbb0fd88692c74a28f03e5af1898066d5f3b8744a87508d6a457b + languageName: node + linkType: hard + "tslib@npm:2.4.0": version: 2.4.0 resolution: "tslib@npm:2.4.0"