From 0f0c0f78336630b4cc3f676b76ffbbf6c5aa71a0 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Thu, 31 Aug 2023 22:46:41 -0400 Subject: [PATCH 01/95] checkpoint for v4 restructure + layout changes may contain bugs. definitely contains bugs. --- pkg/plugin/driver.go | 1 + src/__mocks__/datasource.ts | 12 +- ...ypeSwitcher.tsx => QueryTypeSwitcher._tsx} | 0 ...r.test.tsx => QueryTypeSwitcher.test._tsx} | 0 .../{SQLEditor.tsx => SQLEditor._tsx} | 0 ...SQLEditor.test.tsx => SQLEditor.test._tsx} | 0 src/components/queryBuilder/utils.spec.ts | 6 +- src/components/queryBuilder/utils.ts | 10 +- src/components/v4/DatabaseTableSelect.tsx | 118 ++++++++ src/components/v4/EditorTypeSwitcher.tsx | 110 ++++++++ src/components/v4/QueryTypeSwitcher.tsx | 45 +++ src/components/v4/SqlEditor.tsx | 140 ++++++++++ .../v4/queryBuilder/QueryBuilder.tsx | 264 ++++++++++++++++++ src/data/CHDatasource.test.ts | 30 +- src/data/CHDatasource.ts | 35 ++- src/module.ts | 5 +- src/tracking.test.ts | 51 ++-- src/tracking.ts | 25 +- src/types.ts | 37 +-- src/types/config.ts | 35 +++ src/types/queryBuilder.ts | 178 ++++++++++++ src/types/sql.ts | 77 +++++ src/v4/selectors.ts | 42 +++ src/views/CHConfigEditor.test.tsx | 2 +- src/views/CHConfigEditor.tsx | 6 +- .../{CHQueryEditor.tsx => CHQueryEditor._tsx} | 0 ...ditor.test.tsx => CHQueryEditor.test._tsx} | 0 src/views/v4/CHQueryEditor.tsx | 134 +++++++++ 28 files changed, 1238 insertions(+), 125 deletions(-) rename src/components/{QueryTypeSwitcher.tsx => QueryTypeSwitcher._tsx} (100%) rename src/components/{QueryTypeSwitcher.test.tsx => QueryTypeSwitcher.test._tsx} (100%) rename src/components/{SQLEditor.tsx => SQLEditor._tsx} (100%) rename src/components/{SQLEditor.test.tsx => SQLEditor.test._tsx} (100%) create mode 100644 src/components/v4/DatabaseTableSelect.tsx create mode 100644 src/components/v4/EditorTypeSwitcher.tsx create mode 100644 src/components/v4/QueryTypeSwitcher.tsx create mode 100644 src/components/v4/SqlEditor.tsx create mode 100644 src/components/v4/queryBuilder/QueryBuilder.tsx create mode 100644 src/types/config.ts create mode 100644 src/types/queryBuilder.ts create mode 100644 src/types/sql.ts create mode 100644 src/v4/selectors.ts rename src/views/{CHQueryEditor.tsx => CHQueryEditor._tsx} (100%) rename src/views/{CHQueryEditor.test.tsx => CHQueryEditor.test._tsx} (100%) create mode 100644 src/views/v4/CHQueryEditor.tsx diff --git a/pkg/plugin/driver.go b/pkg/plugin/driver.go index 43b41319..4acfc38b 100644 --- a/pkg/plugin/driver.go +++ b/pkg/plugin/driver.go @@ -290,6 +290,7 @@ func (h *Clickhouse) MutateResponse(ctx context.Context, res data.Frames) (data. } } frame.Fields = fields + frame.Meta.PreferredVisualization = data.VisTypeTable // TODO: Temporary fix for Explorer view. } } return res, nil diff --git a/src/__mocks__/datasource.ts b/src/__mocks__/datasource.ts index 83d97bdc..f23220c2 100644 --- a/src/__mocks__/datasource.ts +++ b/src/__mocks__/datasource.ts @@ -1,5 +1,6 @@ import { PluginType } from '@grafana/data'; -import { CHQuery, Protocol, QueryType } from '../types'; +import { Protocol } from 'types/config'; +import { CHQuery, EditorType, QueryType } from 'types/sql'; import { Datasource } from '../data/CHDatasource'; export const mockDatasource = new Datasource({ @@ -12,7 +13,7 @@ export const mockDatasource = new Datasource({ port: 443, username: 'user', defaultDatabase: 'foo', - protocol: Protocol.NATIVE, + protocol: Protocol.Native, }, readOnly: true, access: 'direct', @@ -42,7 +43,8 @@ mockDatasource.adHocFiltersStatus = 1; // most tests should skip checking the CH export const mockQuery: CHQuery = { rawSql: 'select * from foo', refId: '', - format: 1, - queryType: QueryType.SQL, - selectedFormat: 4, + database: '', + table: 'foo', + editorType: EditorType.SQL, + queryType: QueryType.Table }; diff --git a/src/components/QueryTypeSwitcher.tsx b/src/components/QueryTypeSwitcher._tsx similarity index 100% rename from src/components/QueryTypeSwitcher.tsx rename to src/components/QueryTypeSwitcher._tsx diff --git a/src/components/QueryTypeSwitcher.test.tsx b/src/components/QueryTypeSwitcher.test._tsx similarity index 100% rename from src/components/QueryTypeSwitcher.test.tsx rename to src/components/QueryTypeSwitcher.test._tsx diff --git a/src/components/SQLEditor.tsx b/src/components/SQLEditor._tsx similarity index 100% rename from src/components/SQLEditor.tsx rename to src/components/SQLEditor._tsx diff --git a/src/components/SQLEditor.test.tsx b/src/components/SQLEditor.test._tsx similarity index 100% rename from src/components/SQLEditor.test.tsx rename to src/components/SQLEditor.test._tsx diff --git a/src/components/queryBuilder/utils.spec.ts b/src/components/queryBuilder/utils.spec.ts index 9d7e1bca..f1dc804b 100644 --- a/src/components/queryBuilder/utils.spec.ts +++ b/src/components/queryBuilder/utils.spec.ts @@ -414,10 +414,8 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { it('timeseries function throws if "timeFieldType" not a DateType', () => { expect(() => - getSQLFromQueryOptions({ + getSQLFromQueryOptions('db', 'foo', { mode: BuilderMode.Trend, - database: 'db', - table: 'foo', fields: [], timeField: 'time', timeFieldType: 'boolean', @@ -430,7 +428,7 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { function testCondition(name: string, sql: string, builder: any, testQueryOptionsFromSql = true) { it(name, () => { - expect(getSQLFromQueryOptions(builder)).toBe(sql); + expect(getSQLFromQueryOptions('db', 'foo', builder)).toBe(sql); if (testQueryOptionsFromSql) { expect(getQueryOptionsFromSql(sql)).toEqual(builder); } diff --git a/src/components/queryBuilder/utils.ts b/src/components/queryBuilder/utils.ts index 46daac0e..f3fe6a93 100644 --- a/src/components/queryBuilder/utils.ts +++ b/src/components/queryBuilder/utils.ts @@ -29,7 +29,7 @@ import { SqlBuilderOptionsAggregate, SqlBuilderOptionsTrend, StringFilter, -} from 'types'; +} from 'types/queryBuilder'; import { sqlToStatement } from 'data/ast'; export const isBooleanType = (type: string): boolean => { @@ -227,12 +227,12 @@ const getLimit = (limit?: number): string => { return ` LIMIT ` + (limit || 100); }; -export const getSQLFromQueryOptions = (options: SqlBuilderOptions): string => { +export const getSQLFromQueryOptions = (database: string, table: string, options: SqlBuilderOptions): string => { const limit = options.limit ? getLimit(options.limit) : ''; let query = ``; switch (options.mode) { case BuilderMode.Aggregate: - query += getAggregationQuery(options.database, options.table, options.fields, options.metrics, options.groupBy); + query += getAggregationQuery(database, table, options.fields, options.metrics, options.groupBy); let aggregateFilters = getFilters(options.filters || []); if (aggregateFilters) { query += ` WHERE ${aggregateFilters}`; @@ -244,8 +244,8 @@ export const getSQLFromQueryOptions = (options: SqlBuilderOptions): string => { throw new Error('timeFieldType is expected to be valid Date type.'); } query += getTrendByQuery( - options.database, - options.table, + database, + table, options.metrics, options.groupBy, options.timeField, diff --git a/src/components/v4/DatabaseTableSelect.tsx b/src/components/v4/DatabaseTableSelect.tsx new file mode 100644 index 00000000..47b8f4c2 --- /dev/null +++ b/src/components/v4/DatabaseTableSelect.tsx @@ -0,0 +1,118 @@ +import React, { useState, useEffect } from 'react'; +import { InlineFormLabel, Select } from '@grafana/ui'; +import { SelectableValue } from '@grafana/data'; +import { Datasource } from '../../data/CHDatasource'; +import selectors from 'v4/selectors'; +import { styles } from '../../styles'; + +export type DatabaseSelectProps = { + datasource: Datasource; + database?: string; + onDatabaseChange: (value: string) => void +}; + +export const DatabaseSelect = (props: DatabaseSelectProps) => { + const { datasource, onDatabaseChange, database } = props; + const [list, setList] = useState>>([]); + const { label, tooltip } = selectors.components.DatabaseSelect; + + useEffect(() => { + async function fetchList() { + const list = await datasource.fetchDatabases(); + const values = list.map(t => ({ label: t, value: t })); + + // Add selected value to the list if it does not exist. + if (database && !list.includes(database)) { + values.push({ label: database, value: database }); + } + + setList(values); + } + fetchList(); + }, [datasource, database]); + + const defaultDatabase = datasource.settings.jsonData.defaultDatabase; + const db = database ?? defaultDatabase; + + return ( + <> + + {label} + + + + ); +}; + +export type TableSelectProps = { + datasource: Datasource; + database?: string; + table?: string; + onTableChange: (value: string) => void; +}; + + +export const TableSelect = (props: TableSelectProps) => { + const { datasource, onTableChange, database, table } = props; + const [list, setList] = useState>>([]); + const { label, tooltip } = selectors.components.TableSelect; + + useEffect(() => { + async function fetchTables() { + const tables = await datasource.fetchTables(database); + const values = tables.map(t => ({ label: t, value: t })); + + // Add selected value to the list if it does not exist. + if (table && !tables.includes(table)) { + values.push({ label: table, value: table }); + } + + // TODO: Can't seem to reset the select to unselected + values.push({ label: '-- Choose --', value: '' }); + setList(values); + } + fetchTables(); + }, [datasource, database, table]); + + return ( + <> + + {label} + + + + ); +}; + +export type DatabaseTableSelectProps = { + datasource: Datasource; + database?: string; + onDatabaseChange: (value: string) => void + table?: string; + onTableChange: (value: string) => void; +}; + +export const DatabaseTableSelect = (props: DatabaseTableSelectProps) => { + const { datasource, database, onDatabaseChange, table, onTableChange } = props; + + return ( +
+ + +
+ ); +} diff --git a/src/components/v4/EditorTypeSwitcher.tsx b/src/components/v4/EditorTypeSwitcher.tsx new file mode 100644 index 00000000..fa83dfdc --- /dev/null +++ b/src/components/v4/EditorTypeSwitcher.tsx @@ -0,0 +1,110 @@ +import React, { useState } from 'react'; +import { SelectableValue } from '@grafana/data'; +import { RadioButtonGroup, ConfirmModal, InlineFormLabel } from '@grafana/ui'; +import { getQueryOptionsFromSql, getSQLFromQueryOptions } from '../queryBuilder/utils'; +import selectors from '../../v4/selectors'; +import { EditorType, CHQuery, defaultCHBuilderQuery, CHSqlQuery } from 'types/sql'; +import { SqlBuilderOptions } from 'types/queryBuilder'; +import isString from 'lodash/isString'; + +interface CHEditorTypeSwitcherProps { + query: CHQuery; + onChange: (query: CHQuery) => void; + onRunQuery: () => void; +} + +/** + * Component for switching between the SQL and Query Builder editors. + */ +export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { + const { query, onChange } = props; + const { label, tooltip, switcher, cannotConvert } = selectors.components.EditorTypeSwitcher; + let editorType: EditorType = + query.editorType || + ((query as CHSqlQuery).rawSql && !(query as CHQuery).editorType ? EditorType.SQL : EditorType.Builder); + const [editor, setEditor] = useState(editorType); + const [confirmModalState, setConfirmModalState] = useState(false); + const [cannotConvertModalState, setCannotConvertModalState] = useState(false); + const options: Array> = [ + { label: selectors.types.EditorType.sql, value: EditorType.SQL }, + { label: selectors.types.EditorType.builder, value: EditorType.Builder }, + ]; + const [errorMessage, setErrorMessage] = useState(''); + const onEditorTypeChange = (editorType: EditorType, confirm = false) => { + if (query.editorType === EditorType.SQL && editorType === EditorType.Builder && !confirm) { + const queryOptionsFromSql = getQueryOptionsFromSql(query.rawSql); + if (isString(queryOptionsFromSql)) { + setCannotConvertModalState(true); + setErrorMessage(queryOptionsFromSql); + } else { + setConfirmModalState(true); + } + } else { + setEditor(editorType); + let builderOptions: SqlBuilderOptions; + switch (query.editorType) { + case EditorType.Builder: + builderOptions = query.builderOptions; + break; + case EditorType.SQL: + builderOptions = + (getQueryOptionsFromSql(query.rawSql) as SqlBuilderOptions) || defaultCHBuilderQuery.builderOptions; + break; + default: + builderOptions = defaultCHBuilderQuery.builderOptions; + break; + } + if (editorType === EditorType.SQL) { + onChange({ + ...query, + editorType: EditorType.SQL, + // queryType: QueryType.SQL, + rawSql: getSQLFromQueryOptions(query.database, query.table, builderOptions), + meta: { builderOptions }, + // format: query.format, + // selectedFormat: query.selectedFormat, + }); + } else if (editorType === EditorType.Builder) { + onChange({ + ...query, + editorType: EditorType.Builder, + // queryType: QueryType.Builder, + rawSql: getSQLFromQueryOptions(query.database, query.table, builderOptions), + builderOptions + }); + } + } + }; + const onConfirmEditorTypeChange = () => { + onEditorTypeChange(EditorType.Builder, true); + setConfirmModalState(false); + setCannotConvertModalState(false); + }; + return ( + + + {label} + + onEditorTypeChange(e!)} /> + setConfirmModalState(false)} + /> + setCannotConvertModalState(false)} + /> + + ); +}; diff --git a/src/components/v4/QueryTypeSwitcher.tsx b/src/components/v4/QueryTypeSwitcher.tsx new file mode 100644 index 00000000..27a61c75 --- /dev/null +++ b/src/components/v4/QueryTypeSwitcher.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { RadioButtonGroup, InlineFormLabel } from '@grafana/ui'; +import selectors from '../../v4/selectors'; +import { QueryType } from 'types/sql'; + +export interface QueryTypeSwitcherProps { + queryType: QueryType; + onChange: (queryType: QueryType) => void; +}; + +const options = [ + { + label: selectors.types.QueryType.table, + value: QueryType.Table, + }, + { + label: selectors.types.QueryType.logs, + value: QueryType.Logs, + }, + { + label: selectors.types.QueryType.timeSeries, + value: QueryType.TimeSeries, + }, + { + label: selectors.types.QueryType.traces, + value: QueryType.Traces, + }, +]; + +/** + * Component for switching between the different query builder interfaces + */ +export const QueryTypeSwitcher = (props: QueryTypeSwitcherProps) => { + const { queryType, onChange } = props; + const { label, tooltip } = selectors.components.QueryTypeSwitcher; + + return ( + + + {label} + + onChange(v)} /> + + ); +}; diff --git a/src/components/v4/SqlEditor.tsx b/src/components/v4/SqlEditor.tsx new file mode 100644 index 00000000..71e78c90 --- /dev/null +++ b/src/components/v4/SqlEditor.tsx @@ -0,0 +1,140 @@ +import React, { useState } from 'react'; +import { QueryEditorProps } from '@grafana/data'; +import { CodeEditor } from '@grafana/ui'; +import { Datasource } from 'data/CHDatasource'; +import { registerSQL, Range, Fetcher } from '../sqlProvider'; +import { CHConfig } from 'types/config'; +import { CHQuery, EditorType, QueryType, CHSqlQuery } from 'types/sql'; +import { styles } from 'styles'; +import { fetchSuggestions as sugg, Schema } from '../suggestions'; +import { selectors } from 'selectors'; +// import { getFormat } from '../editor'; +import { validate } from 'data/validate'; + +type SqlEditorProps = QueryEditorProps; + +interface Expand { + height: string; + icon: 'plus' | 'minus'; + on: boolean; +} + +export const SqlEditor = (props: SqlEditorProps) => { + const defaultHeight = '150px'; + const { query, onRunQuery, onChange, datasource } = props; + const [codeEditor, setCodeEditor] = useState(); + const [expand, setExpand] = useState({ + height: defaultHeight, + icon: 'plus', + on: (query as CHSqlQuery).expand || false, + }); + + const onSqlChange = (sql: string) => { + // const format = getFormat(sql, query.selectedFormat); + onChange({ ...query, rawSql: sql, editorType: EditorType.SQL, queryType: QueryType.Table }); + onRunQuery(); + }; + + const onToggleExpand = () => { + const sqlQuery = query as CHSqlQuery; + const on = !expand.on; + const icon = on ? 'minus' : 'plus'; + onChange({ ...sqlQuery, expand: on }); + + if (!codeEditor) { + return; + } + if (on) { + codeEditor.expanded = true; + const height = getEditorHeight(codeEditor); + setExpand({ height: `${height}px`, on, icon }); + return; + } + + codeEditor.expanded = false; + setExpand({ height: defaultHeight, icon, on }); + }; + + const schema: Schema = { + databases: () => datasource.fetchDatabases(), + tables: (db?: string) => datasource.fetchTables(db), + fields: (db: string, table: string) => datasource.fetchFields(db, table), + defaultDatabase: datasource.getDefaultDatabase(), + }; + + const fetchSuggestions: Fetcher = async (text: string, range: Range) => { + const suggestions = await sugg(text, schema, range); + return Promise.resolve({ suggestions }); + }; + + const validateSql = (sql: string, model: any, me: any) => { + const v = validate(sql); + const errorSeverity = 8; + if (v.valid) { + me.setModelMarkers(model, 'clickhouse', []); + } else { + const err = v.error!; + me.setModelMarkers(model, 'clickhouse', [ + { + startLineNumber: err.startLine, + startColumn: err.startCol, + endLineNumber: err.endLine, + endColumn: err.endCol, + message: err.expected, + severity: errorSeverity, + }, + ]); + } + }; + + const handleMount = (editor: any) => { + const me = registerSQL('chSql', editor, fetchSuggestions); + editor.expanded = (query as CHSqlQuery).expand; + editor.onDidChangeModelDecorations((a: any) => { + if (editor.expanded) { + const height = getEditorHeight(editor); + setExpand({ height: `${height}px`, on: true, icon: 'minus' }); + } + }); + editor.onKeyUp((e: any) => { + if (datasource.settings.jsonData.validate) { + const sql = editor.getValue(); + validateSql(sql, editor.getModel(), me); + } + }); + setCodeEditor(editor); + }; + + return ( +
+ onToggleExpand()} + className={styles.Common.expand} + data-testid={selectors.components.QueryEditor.CodeEditor.Expand} + > + + + onChange({ ...query, rawSql: text })} + onEditorDidMount={(editor: any) => handleMount(editor)} + /> +
+ ); +}; + +const getEditorHeight = (editor: any): number | undefined => { + const editorElement = editor.getDomNode(); + if (!editorElement) { + return; + } + + const lineCount = editor.getModel()?.getLineCount() || 1; + return editor.getTopForLineNumber(lineCount + 1) + 40; +}; diff --git a/src/components/v4/queryBuilder/QueryBuilder.tsx b/src/components/v4/queryBuilder/QueryBuilder.tsx new file mode 100644 index 00000000..af520dd1 --- /dev/null +++ b/src/components/v4/queryBuilder/QueryBuilder.tsx @@ -0,0 +1,264 @@ +import React, { useEffect, useState } from 'react'; +import defaultsDeep from 'lodash/defaultsDeep'; +import { Datasource } from '../../../data/CHDatasource'; +import { FieldsEditor } from '../../queryBuilder/Fields'; +import { MetricsEditor } from '../../queryBuilder/Metrics'; +import { TimeFieldEditor } from '../../queryBuilder/TimeField'; +import { FiltersEditor, PredefinedFilter } from '../../queryBuilder/Filters'; +import { GroupByEditor } from '../../queryBuilder/GroupBy'; +import { getOrderByFields, OrderByEditor } from '../../queryBuilder/OrderBy'; +import { LimitEditor } from '../../queryBuilder/Limit'; +import { QueryType, defaultCHBuilderQuery } from 'types/sql'; +import { + BuilderMetricField, + BuilderMode, + Filter, + FilterOperator, + FullField, + OrderBy, + SqlBuilderOptions, +} from 'types/queryBuilder'; +import { isDateTimeType, isDateType } from '../../queryBuilder/utils'; +import { selectors } from 'selectors'; +import { LogLevelFieldEditor } from '../../queryBuilder/LogLevelField'; +import { CoreApp } from '@grafana/data'; + +interface QueryBuilderProps { + builderOptions: SqlBuilderOptions; + onBuilderOptionsChange: (builderOptions: SqlBuilderOptions) => void; + datasource: Datasource; + queryType: QueryType; + database: string; + table: string; + app: CoreApp | undefined; +} + +export const QueryBuilder = (props: QueryBuilderProps) => { + const [baseFieldsList, setBaseFieldsList] = useState([]); + const [timeField, setTimeField] = useState(null); + const [logLevelField, setLogLevelField] = useState(null); + const builder = defaultsDeep(props.builderOptions, defaultCHBuilderQuery.builderOptions); + useEffect(() => { + const fetchBaseFields = async (database: string, table: string) => { + props.datasource + .fetchFieldsFull(database, table) + .then(async (fields) => { + fields.push({ name: '*', label: 'ALL', type: 'string', picklistValues: [] }); + setBaseFieldsList(fields); + + // if no filters are set, we add a default one for the time range + if (builder.filters?.length === 0) { + const dateTimeFields = fields.filter((f) => isDateTimeType(f.type)); + if (dateTimeFields.length > 0) { + const filter: Filter & PredefinedFilter = { + operator: FilterOperator.WithInGrafanaTimeRange, + filterType: 'custom', + key: dateTimeFields[0].name, + type: 'datetime', + condition: 'AND', + restrictToFields: dateTimeFields, + }; + onFiltersChange([filter]); + } + } + + // When changing from SQL Editor to Query Builder, we need to find out if the + // first value is a datetime or date, so we can change the mode to Time Series + if (builder.fields?.length > 0) { + const fieldName = builder.fields[0]; + const timeFields = fields.filter((f) => isDateType(f.type)); + const timeField = timeFields.find((x) => x.name === fieldName); + if (timeField) { + const queryOptions: SqlBuilderOptions = { + ...builder, + timeField: timeField.name, + timeFieldType: timeField.type, + mode: BuilderMode.Trend, + fields: builder.fields.slice(1, builder.fields.length), + }; + props.onBuilderOptionsChange(queryOptions); + } + } + }) + .catch((ex: any) => { + console.error(ex); + throw ex; + }); + }; + + if (props.table) { + fetchBaseFields(props.database, props.table); + } + // We want to run this only when the table changes or first time load. + // If we add 'builder.fields' / 'builder.groupBy' / 'builder.metrics' / 'builder.filters' to the deps array, this will be called every time query editor changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [props.datasource, props.table]); + + const onDatabaseChange = (database = '') => { + setBaseFieldsList([]); + setTimeField(null); + setLogLevelField(null); + const queryOptions: SqlBuilderOptions = { + ...builder, + database, + table: '', + fields: [], + filters: [], + orderBy: [], + timeField: undefined, + logLevelField: undefined, + }; + props.onBuilderOptionsChange(queryOptions); + }; + + const onTableChange = (table = '') => { + setTimeField(null); + setLogLevelField(null); + const queryOptions: SqlBuilderOptions = { + ...builder, + table, + fields: [], + filters: [], + orderBy: [], + timeField: undefined, + logLevelField: undefined, + }; + props.onBuilderOptionsChange(queryOptions); + }; + + useEffect(() => { + onDatabaseChange(props.database); + }, [props.database]); + useEffect(() => { + onTableChange(props.table); + }, [props.table]); + + // const onModeChange = (mode: BuilderMode) => { + // if (mode === BuilderMode.List) { + // const queryOptions: SqlBuilderOptions = { ...builder, mode, fields: builder.fields || [], orderBy: [] }; + // props.onBuilderOptionsChange(queryOptions); + // } else if (mode === BuilderMode.Aggregate) { + // const queryOptions: SqlBuilderOptions = { + // ...builder, + // mode, + // orderBy: [], + // metrics: builder.metrics || [], + // }; + // props.onBuilderOptionsChange(queryOptions); + // } else if (mode === BuilderMode.Trend) { + // const queryOptions: SqlBuilderOptionsTrend = { + // ...builder, + // mode: BuilderMode.Trend, + // timeField: builder.timeField || '', + // timeFieldType: builder.timeFieldType || 'datetime', + // metrics: builder.metrics || [], + // }; + // props.onBuilderOptionsChange(queryOptions); + // } + // }; + + const onFieldsChange = (fields: string[] = []) => { + const queryOptions: SqlBuilderOptions = { ...builder, fields }; + props.onBuilderOptionsChange(queryOptions); + }; + + const onMetricsChange = (metrics: BuilderMetricField[] = []) => { + const queryOptions: SqlBuilderOptions = { ...builder, metrics }; + props.onBuilderOptionsChange(queryOptions); + }; + + const onFiltersChange = (filters: Filter[] = []) => { + const queryOptions: SqlBuilderOptions = { ...builder, filters }; + props.onBuilderOptionsChange(queryOptions); + }; + + const onGroupByChange = (groupBy: string[] = []) => { + const queryOptions: SqlBuilderOptions = { ...builder, groupBy }; + props.onBuilderOptionsChange(queryOptions); + }; + + const onTimeFieldChange = (timeField = '', timeFieldType = '') => { + setTimeField(timeField); + const queryOptions: SqlBuilderOptions = { ...builder, timeField, timeFieldType }; + props.onBuilderOptionsChange(queryOptions); + }; + + const onLogLevelFieldChange = (logLevelField = '') => { + setLogLevelField(logLevelField); + const queryOptions: SqlBuilderOptions = { ...builder, logLevelField }; + props.onBuilderOptionsChange(queryOptions); + }; + + const onOrderByChange = (orderBy: OrderBy[] = []) => { + const queryOptions: SqlBuilderOptions = { ...builder, orderBy }; + props.onBuilderOptionsChange(queryOptions); + }; + + const onLimitChange = (limit = 20) => { + const queryOptions: SqlBuilderOptions = { ...builder, limit }; + props.onBuilderOptionsChange(queryOptions); + }; + + const getFieldList = (): FullField[] => { + const newArray: FullField[] = []; + baseFieldsList.forEach((bf) => { + newArray.push(bf); + }); + return newArray; + }; + const fieldsList = getFieldList(); + return builder ? ( + <> + {/* */} + {builder.mode === BuilderMode.Trend && ( + + )} + { + // Time and LogLevel fields selection for Logs Volume histogram in the Explore mode + builder.mode === BuilderMode.List && props.queryType === QueryType.Logs && props.app === CoreApp.Explore && ( + <> + + + + ) + } + {builder.mode !== BuilderMode.Trend && ( + + )} + + {(builder.mode === BuilderMode.Aggregate || builder.mode === BuilderMode.Trend) && ( + + )} + + {(builder.mode === BuilderMode.Aggregate || builder.mode === BuilderMode.Trend) && ( + + )} + <> + + + + + ) : null; +}; diff --git a/src/data/CHDatasource.test.ts b/src/data/CHDatasource.test.ts index b037b255..62258e5d 100644 --- a/src/data/CHDatasource.test.ts +++ b/src/data/CHDatasource.test.ts @@ -12,7 +12,8 @@ import { DataQuery } from '@grafana/schema'; import { Observable, of } from 'rxjs'; import { DataSourceWithBackend } from '@grafana/runtime'; import { mockDatasource } from '__mocks__/datasource'; -import { BuilderMode, CHBuilderQuery, CHQuery, CHSQLQuery, Format, QueryType, SqlBuilderOptionsList } from 'types'; +import { CHBuilderQuery, CHQuery, CHSqlQuery, EditorType, QueryType } from 'types/sql'; +import { BuilderMode, SqlBuilderOptionsList } from 'types/queryBuilder'; import { cloneDeep } from 'lodash'; import { Datasource } from './CHDatasource'; import * as logs from './logs'; @@ -74,30 +75,30 @@ describe('ClickHouseDatasource', () => { it('interpolates', async () => { const rawSql = 'foo'; const spyOnReplace = jest.spyOn(templateSrvMock, 'replace').mockImplementation(() => rawSql); - const query = { rawSql: 'select', queryType: QueryType.SQL } as CHQuery; + const query = { rawSql: 'select', editorType: EditorType.SQL } as CHQuery; const val = createInstance({}).applyTemplateVariables(query, {}); expect(spyOnReplace).toHaveBeenCalled(); - expect(val).toEqual({ rawSql, queryType: QueryType.SQL }); + expect(val).toEqual({ rawSql, editorType: EditorType.SQL }); }); it('should handle $__conditionalAll and not replace', async () => { - const query = { rawSql: '$__conditionalAll(foo, $fieldVal)', queryType: QueryType.SQL } as CHQuery; + const query = { rawSql: '$__conditionalAll(foo, $fieldVal)', editorType: EditorType.SQL } as CHQuery; const vars = [{ current: { value: `'val1', 'val2'` }, name: 'fieldVal' }] as TypedVariableModel[]; const spyOnReplace = jest.spyOn(templateSrvMock, 'replace').mockImplementation((x) => x); const spyOnGetVars = jest.spyOn(templateSrvMock, 'getVariables').mockImplementation(() => vars); const val = createInstance({}).applyTemplateVariables(query, {}); expect(spyOnReplace).toHaveBeenCalled(); expect(spyOnGetVars).toHaveBeenCalled(); - expect(val).toEqual({ rawSql: `foo`, queryType: QueryType.SQL }); + expect(val).toEqual({ rawSql: `foo`, editorType: EditorType.SQL }); }); it('should handle $__conditionalAll and replace', async () => { - const query = { rawSql: '$__conditionalAll(foo, $fieldVal)', queryType: QueryType.SQL } as CHQuery; + const query = { rawSql: '$__conditionalAll(foo, $fieldVal)', editorType: EditorType.SQL } as CHQuery; const vars = [{ current: { value: '$__all' }, name: 'fieldVal' }] as TypedVariableModel[]; const spyOnReplace = jest.spyOn(templateSrvMock, 'replace').mockImplementation((x) => x); const spyOnGetVars = jest.spyOn(templateSrvMock, 'getVariables').mockImplementation(() => vars); const val = createInstance({}).applyTemplateVariables(query, {}); expect(spyOnReplace).toHaveBeenCalled(); expect(spyOnGetVars).toHaveBeenCalled(); - expect(val).toEqual({ rawSql: `1=1`, queryType: QueryType.SQL }); + expect(val).toEqual({ rawSql: `1=1`, editorType: EditorType.SQL }); }); }); @@ -312,7 +313,10 @@ describe('ClickHouseDatasource', () => { describe('SupplementaryQueriesSupport', () => { const query: CHBuilderQuery = { refId: '42', - queryType: QueryType.Builder, + editorType: EditorType.Builder, + queryType: QueryType.Logs, + database: 'system', + table: 'numbers', rawSql: 'SELECT * FROM system.numbers LIMIT 1', builderOptions: { mode: BuilderMode.List, @@ -321,8 +325,6 @@ describe('ClickHouseDatasource', () => { timeField: 'created_at', logLevelField: 'level', }, - format: Format.LOGS, - selectedFormat: Format.LOGS, }; const request: DataQueryRequest = { app: CoreApp.Explore, @@ -348,11 +350,11 @@ describe('ClickHouseDatasource', () => { describe('getSupplementaryLogsVolumeQuery', () => { it('should return undefined if any of the conditions are not met', async () => { - [Format.AUTO, Format.TIMESERIES, Format.TABLE, Format.TRACE].forEach((format) => { + [QueryType.Table, QueryType.Logs, QueryType.TimeSeries, QueryType.Traces].forEach(queryType => { expect( datasource.getSupplementaryLogsVolumeQuery(request, { ...query, - format, + queryType, }) ).toBeUndefined(); }); @@ -367,7 +369,7 @@ describe('ClickHouseDatasource', () => { expect( datasource.getSupplementaryLogsVolumeQuery(request, { ...query, - queryType: QueryType.SQL, + editorType: EditorType.SQL, }) ).toBeUndefined(); expect( @@ -461,7 +463,7 @@ describe('ClickHouseDatasource', () => { const range = ['from', 'to']; const supplementaryQuery = { rawSql: 'supplementaryQuery', - } as CHSQLQuery; + } as CHSqlQuery; jest.spyOn(Datasource.prototype, 'getSupplementaryLogsVolumeQuery').mockReturnValue(supplementaryQuery); jest.spyOn(logs, 'getIntervalInfo').mockReturnValue({ interval: '1d' }); const queryLogsVolumeSpy = jest diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index 23b3e3e4..47817c75 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -16,20 +16,18 @@ import { } from '@grafana/data'; import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime'; import { Observable } from 'rxjs'; +import { CHConfig } from 'types/config'; +import { EditorType, QueryType, CHQuery } from 'types/sql'; import { BuilderMetricField, BuilderMetricFieldAggregation, BuilderMode, - CHConfig, - CHQuery, Filter, FilterOperator, - Format, FullField, OrderByDirection, - QueryType, SqlBuilderOptionsAggregate, -} from '../types'; +} from 'types/queryBuilder'; import { AdHocFilter } from './adHocFilter'; import { cloneDeep, isEmpty, isString } from 'lodash'; import { @@ -114,8 +112,8 @@ export class Datasource getSupplementaryLogsVolumeQuery(logsVolumeRequest: DataQueryRequest, query: CHQuery): CHQuery | undefined { if ( - query.format !== Format.LOGS || - query.queryType !== QueryType.Builder || + query.editorType !== EditorType.Builder || + query.queryType !== QueryType.Logs || query.builderOptions.mode !== BuilderMode.List || query.builderOptions.timeField === undefined || query.builderOptions.database === undefined || @@ -149,8 +147,6 @@ export class Datasource const logVolumeSqlBuilderOptions: SqlBuilderOptionsAggregate = { mode: BuilderMode.Aggregate, - database: query.builderOptions.database, - table: query.builderOptions.table, filters: query.builderOptions.filters, fields, metrics, @@ -163,13 +159,16 @@ export class Datasource ], }; - const logVolumeSupplementaryQuery = getSQLFromQueryOptions(logVolumeSqlBuilderOptions); + const logVolumeSupplementaryQuery = getSQLFromQueryOptions(query.database, query.table, logVolumeSqlBuilderOptions); return { - format: Format.AUTO, - queryType: QueryType.SQL, + // format: Format.AUTO, + // selectedFormat: Format.AUTO, + editorType: EditorType.SQL, + queryType: QueryType.Table, + database: query.database, + table: query.table, rawSql: logVolumeSupplementaryQuery, refId: '', - selectedFormat: Format.AUTO, }; } @@ -181,16 +180,16 @@ export class Datasource if (this.adHocFiltersStatus === AdHocFilterStatus.none) { this.adHocFiltersStatus = await this.canUseAdhocFilters(); } - const chQuery = isString(query) ? { rawSql: query, queryType: QueryType.SQL } : query; + const chQuery = isString(query) ? { rawSql: query, editorType: EditorType.SQL } : query; - if (!(chQuery.queryType === QueryType.SQL || chQuery.queryType === QueryType.Builder || !chQuery.queryType)) { + if (!(chQuery.editorType === EditorType.SQL || chQuery.editorType === EditorType.Builder || !chQuery.editorType)) { return []; } if (!chQuery.rawSql) { return []; } - const q = { ...chQuery, queryType: chQuery.queryType || QueryType.SQL }; + const q = { ...chQuery, editorType: chQuery.editorType || EditorType.SQL }; const frame = await this.runQuery(q, options); if (frame.fields?.length === 0) { return []; @@ -256,7 +255,7 @@ export class Datasource modifyQuery(query: CHQuery, action: QueryFixAction): CHQuery { // support filtering by field value in Explore if ( - query.queryType === QueryType.Builder && + query.editorType === EditorType.Builder && action.options !== undefined && 'key' in action.options && 'value' in action.options @@ -309,7 +308,7 @@ export class Datasource return { ...query, // the query is updated to trigger the URL update and propagation to the panels - rawSql: getSQLFromQueryOptions(updatedBuilder), + rawSql: getSQLFromQueryOptions(query.database, query.table, updatedBuilder), builderOptions: updatedBuilder, }; } diff --git a/src/module.ts b/src/module.ts index 223e156f..571262ec 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,8 +1,9 @@ import { DataSourcePlugin, DashboardLoadedEvent } from '@grafana/data'; import { Datasource } from './data/CHDatasource'; import { ConfigEditor } from './views/CHConfigEditor'; -import { CHQueryEditor } from './views/CHQueryEditor'; -import { CHQuery, CHConfig } from './types'; +import { CHQueryEditor } from './views/v4/CHQueryEditor'; +import { CHConfig } from './types/config'; +import { CHQuery } from './types/sql'; import { getAppEvents } from '@grafana/runtime'; import { analyzeQueries, trackClickhouseDashboardLoaded } from 'tracking'; import pluginJson from './plugin.json'; diff --git a/src/tracking.test.ts b/src/tracking.test.ts index 84027717..e126d417 100644 --- a/src/tracking.test.ts +++ b/src/tracking.test.ts @@ -1,27 +1,28 @@ import { analyzeQueries } from 'tracking'; -import { BuilderMode, QueryType, CHQuery, Format } from 'types'; +import { CHQuery, EditorType, QueryType } from 'types/sql'; +import { BuilderMode } from 'types/queryBuilder'; describe('analyzeQueries', () => { [ - { - description: 'should count 1 sql query (with a default mode of auto)', - queries: [{ queryType: QueryType.SQL, selectedFormat: Format.AUTO }], - expectedCounters: { - sql_queries: 1, - sql_query_format_auto: 1, - sql_query_format_table: 0, - sql_query_format_logs: 0, - sql_query_format_time_series: 0, - sql_query_format_trace: 0, - builder_queries: 0, - builder_table_queries: 0, - builder_aggregate_queries: 0, - builder_time_series_queries: 0, - }, - }, + // { + // description: 'should count 1 sql query (with a default mode of auto)', + // queries: [{ editorType: EditorType.SQL, QueryType: QueryType.Table }], + // expectedCounters: { + // sql_queries: 1, + // sql_query_format_auto: 1, + // sql_query_format_table: 0, + // sql_query_format_logs: 0, + // sql_query_format_time_series: 0, + // sql_query_format_trace: 0, + // builder_queries: 0, + // builder_table_queries: 0, + // builder_aggregate_queries: 0, + // builder_time_series_queries: 0, + // }, + // }, { description: 'should count 1 sql query with a mode of Table', - queries: [{ queryType: QueryType.SQL, selectedFormat: Format.TABLE }], + queries: [{ editorType: EditorType.SQL, queryType: QueryType.Table }], expectedCounters: { sql_queries: 1, sql_query_format_auto: 0, @@ -37,7 +38,7 @@ describe('analyzeQueries', () => { }, { description: 'should count 1 sql query with a mode of Logs', - queries: [{ queryType: QueryType.SQL, selectedFormat: Format.LOGS }], + queries: [{ editorType: EditorType.SQL, queryType: QueryType.Logs }], expectedCounters: { sql_queries: 1, sql_query_format_auto: 0, @@ -53,7 +54,7 @@ describe('analyzeQueries', () => { }, { description: 'should count 1 sql query with a mode of Time Series', - queries: [{ queryType: QueryType.SQL, selectedFormat: Format.TIMESERIES }], + queries: [{ editorType: EditorType.SQL, queryType: QueryType.TimeSeries }], expectedCounters: { sql_queries: 1, sql_query_format_auto: 0, @@ -68,8 +69,8 @@ describe('analyzeQueries', () => { }, }, { - description: 'should count 1 sql query with a mode of Trace', - queries: [{ queryType: QueryType.SQL, selectedFormat: Format.TRACE }], + description: 'should count 1 sql query with a mode of Traces', + queries: [{ editorType: EditorType.SQL, queryType: QueryType.Traces }], expectedCounters: { sql_queries: 1, sql_query_format_auto: 0, @@ -85,7 +86,7 @@ describe('analyzeQueries', () => { }, { description: 'should count 1 builder query (with a default mode of Table)', - queries: [{ queryType: QueryType.Builder, builderOptions: { mode: BuilderMode.List } }], + queries: [{ editorType: EditorType.Builder, builderOptions: { mode: BuilderMode.List } }], expectedCounters: { sql_queries: 0, sql_query_format_auto: 0, @@ -101,7 +102,7 @@ describe('analyzeQueries', () => { }, { description: 'should count 1 builder query with a mode of Aggregate', - queries: [{ queryType: QueryType.Builder, builderOptions: { mode: BuilderMode.Aggregate } }], + queries: [{ editorType: EditorType.Builder, builderOptions: { mode: BuilderMode.Aggregate } }], expectedCounters: { sql_queries: 0, sql_query_format_auto: 0, @@ -117,7 +118,7 @@ describe('analyzeQueries', () => { }, { description: 'should count 1 builder query with a mode of Time Series', - queries: [{ queryType: QueryType.Builder, builderOptions: { mode: BuilderMode.Trend } }], + queries: [{ editorType: EditorType.Builder, builderOptions: { mode: BuilderMode.Trend } }], expectedCounters: { sql_queries: 0, sql_query_format_auto: 0, diff --git a/src/tracking.ts b/src/tracking.ts index aa4083a3..c5552ddf 100644 --- a/src/tracking.ts +++ b/src/tracking.ts @@ -1,5 +1,8 @@ import { reportInteraction } from '@grafana/runtime'; -import { BuilderMode, CHQuery, Format, QueryType } from 'types'; +import { CHQuery, EditorType, QueryType } from 'types/sql'; +import { BuilderMode } from 'types/queryBuilder'; + +// TODO: v4, determine new/updated fields to track export const trackClickhouseDashboardLoaded = (props: ClickhouseDashboardLoadedProps) => { reportInteraction('grafana_ds_clickhouse_dashboard_loaded', props); @@ -7,7 +10,6 @@ export const trackClickhouseDashboardLoaded = (props: ClickhouseDashboardLoadedP export type ClickhouseCounters = { sql_queries: number; - sql_query_format_auto: number; sql_query_format_table: number; sql_query_format_logs: number; sql_query_format_time_series: number; @@ -29,7 +31,6 @@ export interface ClickhouseDashboardLoadedProps extends ClickhouseCounters { export const analyzeQueries = (queries: CHQuery[]): ClickhouseCounters => { const counters = { sql_queries: 0, - sql_query_format_auto: 0, sql_query_format_table: 0, sql_query_format_logs: 0, sql_query_format_time_series: 0, @@ -40,23 +41,21 @@ export const analyzeQueries = (queries: CHQuery[]): ClickhouseCounters => { builder_time_series_queries: 0, }; - queries.forEach((query) => { - switch (query.queryType) { - case QueryType.SQL: + queries.forEach(query => { + switch (query.editorType) { + case EditorType.SQL: counters.sql_queries++; - if (query.selectedFormat === Format.AUTO) { - counters.sql_query_format_auto++; - } else if (query.selectedFormat === Format.TABLE) { + if (query.queryType === QueryType.Table) { counters.sql_query_format_table++; - } else if (query.selectedFormat === Format.LOGS) { + } else if (query.queryType === QueryType.Logs) { counters.sql_query_format_logs++; - } else if (query.selectedFormat === Format.TIMESERIES) { + } else if (query.queryType === QueryType.TimeSeries) { counters.sql_query_format_time_series++; - } else if (query.selectedFormat === Format.TRACE) { + } else if (query.queryType === QueryType.Traces) { counters.sql_query_format_trace++; } break; - case QueryType.Builder: + case EditorType.Builder: counters.builder_queries++; if (query.builderOptions.mode === BuilderMode.Aggregate) { counters.builder_aggregate_queries++; diff --git a/src/types.ts b/src/types.ts index a35fffd8..a61292a6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,40 +1,7 @@ -import { DataQuery, DataSourceJsonData } from '@grafana/data'; -export const defaultQuery: Partial = {}; - -export interface CHConfig extends DataSourceJsonData { - username: string; - server: string; - protocol: Protocol; - port: number; - defaultDatabase?: string; - tlsSkipVerify?: boolean; - tlsAuth?: boolean; - tlsAuthWithCACert?: boolean; - secure?: boolean; - validate?: boolean; - timeout?: string; - queryTimeout?: string; - customSettings?: CHCustomSetting[]; - enableSecureSocksProxy?: boolean; -} - -export interface CHCustomSetting { - setting: string; - value: string; -} - -export interface CHSecureConfig { - password: string; - tlsCACert?: string; - tlsClientCert?: string; - tlsClientKey?: string; -} +import { DataQuery } from '@grafana/schema'; -export enum Protocol { - NATIVE = 'native', - HTTP = 'http', -} +export const defaultQuery: Partial = {}; export enum Format { TIMESERIES = 0, diff --git a/src/types/config.ts b/src/types/config.ts new file mode 100644 index 00000000..adbb22d0 --- /dev/null +++ b/src/types/config.ts @@ -0,0 +1,35 @@ +import { DataSourceJsonData } from '@grafana/data'; + +export interface CHConfig extends DataSourceJsonData { + username: string; + server: string; + protocol: Protocol; + port: number; + defaultDatabase?: string; + tlsSkipVerify?: boolean; + tlsAuth?: boolean; + tlsAuthWithCACert?: boolean; + secure?: boolean; + validate?: boolean; + timeout?: string; + queryTimeout?: string; + customSettings?: CHCustomSetting[]; + enableSecureSocksProxy?: boolean; +} + +export interface CHCustomSetting { + setting: string; + value: string; +} + +export interface CHSecureConfig { + password: string; + tlsCACert?: string; + tlsClientCert?: string; + tlsClientKey?: string; +} + +export enum Protocol { + Native = 'native', + HTTP = 'http', +} \ No newline at end of file diff --git a/src/types/queryBuilder.ts b/src/types/queryBuilder.ts new file mode 100644 index 00000000..0afbdb08 --- /dev/null +++ b/src/types/queryBuilder.ts @@ -0,0 +1,178 @@ +export enum BuilderMode { + List = 'list', + Aggregate = 'aggregate', + Trend = 'trend', +} + +/** + * @property {string} timeField Explore only: used for Logs Volume histogram + * @property {string} logLevelField Explore only: used for Logs Volume histogram + */ +export interface SqlBuilderOptionsList { + mode: BuilderMode.List; + database?: string; + table?: string; + fields?: string[]; + filters?: Filter[]; + orderBy?: OrderBy[]; + limit?: number; + timeField?: string; + logLevelField?: string; +} + +export enum BuilderMetricFieldAggregation { + Sum = 'sum', + Average = 'avg', + Min = 'min', + Max = 'max', + Count = 'count', + Any = 'any', + // Count_Distinct = 'count_distinct', +} + +export type BuilderMetricField = { + field: string; + aggregation: BuilderMetricFieldAggregation; + alias?: string; +} + +export interface SqlBuilderOptionsAggregate { + mode: BuilderMode.Aggregate; + fields: string[]; + metrics: BuilderMetricField[]; + groupBy?: string[]; + filters?: Filter[]; + orderBy?: OrderBy[]; + limit?: number; +} + +export interface SqlBuilderOptionsTrend { + mode: BuilderMode.Trend; + fields: string[]; + metrics: BuilderMetricField[]; + filters?: Filter[]; + groupBy?: string[]; + timeField: string; + timeFieldType: string; + orderBy?: OrderBy[]; + limit?: number; +} + +export type SqlBuilderOptions = SqlBuilderOptionsList | SqlBuilderOptionsAggregate | SqlBuilderOptionsTrend; + +export interface Field { + name: string; + type: string; + rel: string; + label: string; + ref: string[]; +} + +export interface FullEntity { + name: string; + label: string; + custom: boolean; + queryable: boolean; +} + +interface FullFieldPickListItem { + value: string; + label: string; +} + +export interface FullField { + name: string; + label: string; + type: string; + picklistValues: FullFieldPickListItem[]; + filterable?: boolean; + sortable?: boolean; + groupable?: boolean; + aggregatable?: boolean; +} + +export enum OrderByDirection { + ASC = 'ASC', + DESC = 'DESC', +} + +export interface OrderBy { + name: string; + dir: OrderByDirection; +} + +export enum FilterOperator { + IsNull = 'IS NULL', + IsNotNull = 'IS NOT NULL', + Equals = '=', + NotEquals = '!=', + LessThan = '<', + LessThanOrEqual = '<=', + GreaterThan = '>', + GreaterThanOrEqual = '>=', + Like = 'LIKE', + NotLike = 'NOT LIKE', + In = 'IN', + NotIn = 'NOT IN', + WithInGrafanaTimeRange = 'WITH IN DASHBOARD TIME RANGE', + OutsideGrafanaTimeRange = 'OUTSIDE DASHBOARD TIME RANGE', +} + +export interface CommonFilterProps { + filterType: 'custom'; + key: string; + type: string; + condition: 'AND' | 'OR'; +} + +export interface NullFilter extends CommonFilterProps { + operator: FilterOperator.IsNull | FilterOperator.IsNotNull; +} + +export interface BooleanFilter extends CommonFilterProps { + type: 'boolean'; + operator: FilterOperator.Equals | FilterOperator.NotEquals; + value: boolean; +} + +export interface StringFilter extends CommonFilterProps { + operator: FilterOperator.Equals | FilterOperator.NotEquals | FilterOperator.Like | FilterOperator.NotLike; + value: string; +} + +export interface NumberFilter extends CommonFilterProps { + operator: + | FilterOperator.Equals + | FilterOperator.NotEquals + | FilterOperator.LessThan + | FilterOperator.LessThanOrEqual + | FilterOperator.GreaterThan + | FilterOperator.GreaterThanOrEqual; + value: number; +} + +export interface DateFilterWithValue extends CommonFilterProps { + type: 'datetime' | 'date'; + operator: + | FilterOperator.Equals + | FilterOperator.NotEquals + | FilterOperator.LessThan + | FilterOperator.LessThanOrEqual + | FilterOperator.GreaterThan + | FilterOperator.GreaterThanOrEqual; + value: string; +} + +export interface DateFilterWithoutValue extends CommonFilterProps { + type: 'datetime' | 'date'; + operator: FilterOperator.WithInGrafanaTimeRange | FilterOperator.OutsideGrafanaTimeRange; +} + +export type DateFilter = DateFilterWithValue | DateFilterWithoutValue; + +export interface MultiFilter extends CommonFilterProps { + operator: FilterOperator.In | FilterOperator.NotIn; + value: string[]; +} + +export type Filter = NullFilter | BooleanFilter | NumberFilter | DateFilter | StringFilter | MultiFilter; diff --git a/src/types/sql.ts b/src/types/sql.ts new file mode 100644 index 00000000..2518115b --- /dev/null +++ b/src/types/sql.ts @@ -0,0 +1,77 @@ +import { DataQuery } from '@grafana/schema'; +import { BuilderMode, SqlBuilderOptions } from './queryBuilder'; + +/** + * EditorType determines the query editor type. + */ +export enum EditorType { + SQL = 'sql', + Builder = 'builder', +} + +/** + * QueryType determines the display/query format. + */ +export enum QueryType { + Table = 'table', + Logs = 'logs', + TimeSeries = 'timeSeries', + Traces = 'traces', +} + +export interface CHQueryBase extends DataQuery { + editorType: EditorType; + queryType: QueryType; + database: string; + table: string; + selectedQueryType?: QueryType; + + rawSql: string; +} + +export interface CHSqlQuery extends CHQueryBase { + editorType: EditorType.SQL; + meta?: { + timezone?: string; + // meta fields to be used just for building builder options when migrating back to EditorType.Builder + builderOptions?: SqlBuilderOptions; + }; + expand?: boolean; +} + +export interface CHBuilderQuery extends CHQueryBase { + editorType: EditorType.Builder; + builderOptions: SqlBuilderOptions; + meta?: { + timezone?: string; + }; +} + +export type CHQuery = CHSqlQuery | CHBuilderQuery; + +// TODO: these aren't really types +export const defaultEditorType: EditorType = EditorType.Builder; +export const defaultCHBuilderQuery: Omit = { + editorType: EditorType.Builder, + queryType: QueryType.Table, + database: '', + table: '', + rawSql: '', + builderOptions: { + mode: BuilderMode.List, + fields: [], + limit: 100, + }, + // format: Format.TABLE, + // selectedFormat: Format.AUTO, +}; +export const defaultCHSqlQuery: Omit = { + editorType: EditorType.SQL, + queryType: QueryType.Table, + database: '', + table: '', + rawSql: '', + // format: Format.TABLE, + // selectedFormat: Format.AUTO, + expand: false, +}; diff --git a/src/v4/selectors.ts b/src/v4/selectors.ts new file mode 100644 index 00000000..2d407125 --- /dev/null +++ b/src/v4/selectors.ts @@ -0,0 +1,42 @@ +export default { + components: { + EditorTypeSwitcher: { + label: 'Editor Type', + tooltip: 'Switches between the raw SQL Editor and the Query Builder.', + switcher: { + title: 'Are you sure?', + body: 'Queries that are too complex for the Query Builder will be altered.', + confirmText: 'Continue', + dismissText: 'Cancel', + }, + cannotConvert: { + title: 'Cannot convert', + confirmText: 'Yes', + }, + }, + QueryTypeSwitcher: { + label: 'Query Type', + tooltip: 'Sets the layout for the query builder' + }, + DatabaseSelect: { + label: 'Database', + tooltip: 'ClickHouse database to query from', + }, + TableSelect: { + label: 'Table', + tooltip: 'ClickHouse table to query from', + } + }, + types: { + EditorType: { + sql: 'SQL Editor', + builder: 'Query Builder', + }, + QueryType: { + table: 'Table', + logs: 'Logs', + timeSeries: 'Time Series', + traces: 'Traces', + } + } +} diff --git a/src/views/CHConfigEditor.test.tsx b/src/views/CHConfigEditor.test.tsx index e48b902f..98bd5998 100644 --- a/src/views/CHConfigEditor.test.tsx +++ b/src/views/CHConfigEditor.test.tsx @@ -4,7 +4,7 @@ import { ConfigEditor } from './CHConfigEditor'; import { mockConfigEditorProps } from '../__mocks__/ConfigEditor'; import { Components } from './../selectors'; import '@testing-library/jest-dom'; -import { Protocol } from '../types'; +import { Protocol } from 'types/config'; describe('ConfigEditor', () => { it('new editor', () => { diff --git a/src/views/CHConfigEditor.tsx b/src/views/CHConfigEditor.tsx index 10a7de06..5d52c21e 100644 --- a/src/views/CHConfigEditor.tsx +++ b/src/views/CHConfigEditor.tsx @@ -18,7 +18,7 @@ import { import { CertificationKey } from '../components/ui/CertificationKey'; import { Components } from './../selectors'; import { config } from '@grafana/runtime'; -import { CHConfig, CHCustomSetting, CHSecureConfig, Protocol } from './../types'; +import { CHConfig, CHCustomSetting, CHSecureConfig, Protocol } from 'types/config'; import { gte } from 'semver'; export interface Props extends DataSourcePluginOptionsEditorProps {} @@ -33,7 +33,7 @@ export const ConfigEditor: React.FC = (props) => { const hasTLSClientCert = secureJsonFields && secureJsonFields.tlsClientCert; const hasTLSClientKey = secureJsonFields && secureJsonFields.tlsClientKey; const protocolOptions = [ - { label: 'Native', value: Protocol.NATIVE }, + { label: 'Native', value: Protocol.Native }, { label: 'HTTP', value: Protocol.HTTP }, ]; const switchContainerStyle: React.CSSProperties = { @@ -169,7 +169,7 @@ export const ConfigEditor: React.FC = (props) => { options={protocolOptions} disabledOptions={[]} - value={jsonData.protocol || Protocol.NATIVE} + value={jsonData.protocol || Protocol.Native} onChange={(e) => onProtocolToggle(e!)} /> diff --git a/src/views/CHQueryEditor.tsx b/src/views/CHQueryEditor._tsx similarity index 100% rename from src/views/CHQueryEditor.tsx rename to src/views/CHQueryEditor._tsx diff --git a/src/views/CHQueryEditor.test.tsx b/src/views/CHQueryEditor.test._tsx similarity index 100% rename from src/views/CHQueryEditor.test.tsx rename to src/views/CHQueryEditor.test._tsx diff --git a/src/views/v4/CHQueryEditor.tsx b/src/views/v4/CHQueryEditor.tsx new file mode 100644 index 00000000..435be02d --- /dev/null +++ b/src/views/v4/CHQueryEditor.tsx @@ -0,0 +1,134 @@ +import React from 'react'; +import { QueryEditorProps } from '@grafana/data'; +import { Datasource } from '../../data/CHDatasource'; +import { Preview } from 'components/queryBuilder/Preview'; +import { EditorTypeSwitcher } from 'components/v4/EditorTypeSwitcher'; +import { QueryTypeSwitcher } from 'components/v4/QueryTypeSwitcher'; +import { styles } from 'styles'; +import { Button } from '@grafana/ui'; +import { EditorType, QueryType, CHBuilderQuery, defaultCHBuilderQuery } from 'types/sql'; +import { CHConfig } from 'types/config'; +import { CHQuery } from 'types/sql'; +import { SqlBuilderOptions } from 'types/queryBuilder'; +import { SqlEditor } from 'components/v4/SqlEditor'; +import { QueryBuilder } from 'components/v4/queryBuilder/QueryBuilder'; +import { getSQLFromQueryOptions } from 'components/queryBuilder/utils'; +import { DatabaseTableSelect } from 'components/v4/DatabaseTableSelect'; + +export type CHQueryEditorProps = QueryEditorProps; + +/** + * Top level query editor component + */ +export const CHQueryEditor = (props: CHQueryEditorProps) => { + const { query, onChange, onRunQuery } = props; + + React.useEffect(() => { + if (!query.queryType) { + onChange({ ...query, queryType: QueryType.Table }); + } + if (!query.database) { + onChange({ ...query, database: props.datasource.getDefaultDatabase() || 'default' }); + } + }, [query, onChange]); + + const runQuery = () => { + if (query.editorType === EditorType.SQL) { + // const format = getFormat(query.rawSql, query.selectedFormat); + // if (format !== query.format) { + // onChange({ ...query, format }); + onChange({ ...query }); + // } + } + onRunQuery(); + }; + + const onDatabaseChange = (db: string) => onChange({ ...query, database: db }); + const onTableChange = (t: string) => onChange({ ...query, table: t }); + + // const onFormatChange = (selectedFormat: Format) => { + // switch (query.queryType) { + // case QueryType.SQL: + // onChange({ ...query, format: getFormat(query.rawSql, selectedFormat), selectedFormat }); + // case QueryType.Builder: + // default: + // if (selectedFormat === Format.AUTO) { + // let builderOptions = (query as CHBuilderQuery).builderOptions; + // const format = builderOptions && builderOptions.mode === BuilderMode.Trend ? Format.TIMESERIES : Format.TABLE; + // onChange({ ...query, format, selectedFormat }); + // } else { + // onChange({ ...query, format: selectedFormat, selectedFormat }); + // } + // } + // }; + + return ( + <> +
+ + +
+
+ +
+
+ onChange({ ...query, queryType: t })} /> +
+ + + ); +}; + +const CHEditorByType = (props: CHQueryEditorProps) => { + const { query, onChange, app } = props; + const onBuilderOptionsChange = (builderOptions: SqlBuilderOptions) => { + const sql = getSQLFromQueryOptions(query.database, query.table, builderOptions); + onChange({ ...query, editorType: EditorType.Builder, rawSql: sql, builderOptions }); + }; + + switch (query.editorType) { + case EditorType.SQL: + return ( +
+ +
+ ); + case EditorType.Builder: + default: + let newQuery: CHBuilderQuery = { ...query }; + if (query.rawSql && !query.builderOptions) { + return ( +
+ +
+ ); + } + if (!query.rawSql || !query.builderOptions) { + newQuery = { + ...newQuery, + rawSql: defaultCHBuilderQuery.rawSql, + builderOptions: { + ...defaultCHBuilderQuery.builderOptions, + }, + }; + } + return ( +
+ + +
+ ); + } +}; From cf224ec0358b433f13e6ce7b89db2477979bfbe4 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Sep 2023 17:20:19 -0400 Subject: [PATCH 02/95] checkpoint for simplified logic and layout changes --- src/components/v4/DatabaseTableSelect.tsx | 55 +-- .../v4/queryBuilder/AggregateEditor.tsx | 147 ++++++ .../v4/queryBuilder/ColumnSelect.tsx | 37 ++ .../v4/queryBuilder/ColumnsEditor.tsx | 83 ++++ .../v4/queryBuilder/FilterEditor.tsx | 444 ++++++++++++++++++ .../v4/queryBuilder/GroupByEditor.tsx | 45 ++ .../v4/queryBuilder/LimitEditor.tsx | 29 ++ src/components/v4/queryBuilder/ModeSwitch.tsx | 38 ++ .../v4/queryBuilder/OrderByEditor.tsx | 191 ++++++++ .../v4/queryBuilder/OtelVersionSelect.tsx | 60 +++ .../v4/queryBuilder/QueryBuilder.tsx | 80 +++- src/components/v4/queryBuilder/Switch.tsx | 36 ++ .../queryBuilder/views/LogsQueryBuilder.tsx | 95 ++++ .../views/TimeSeriesQueryBuilder.tsx | 102 ++++ src/hooks/useDatabaseTables.tsx | 24 + src/hooks/useDatabases.tsx | 24 + src/hooks/useTableColumns.tsx | 29 ++ src/types/config.ts | 2 +- src/types/queryBuilder.ts | 5 + src/v4/otel.tsx | 11 + src/v4/selectors.ts | 36 ++ src/views/v4/CHQueryEditor.tsx | 11 +- 22 files changed, 1531 insertions(+), 53 deletions(-) create mode 100644 src/components/v4/queryBuilder/AggregateEditor.tsx create mode 100644 src/components/v4/queryBuilder/ColumnSelect.tsx create mode 100644 src/components/v4/queryBuilder/ColumnsEditor.tsx create mode 100644 src/components/v4/queryBuilder/FilterEditor.tsx create mode 100644 src/components/v4/queryBuilder/GroupByEditor.tsx create mode 100644 src/components/v4/queryBuilder/LimitEditor.tsx create mode 100644 src/components/v4/queryBuilder/ModeSwitch.tsx create mode 100644 src/components/v4/queryBuilder/OrderByEditor.tsx create mode 100644 src/components/v4/queryBuilder/OtelVersionSelect.tsx create mode 100644 src/components/v4/queryBuilder/Switch.tsx create mode 100644 src/components/v4/queryBuilder/views/LogsQueryBuilder.tsx create mode 100644 src/components/v4/queryBuilder/views/TimeSeriesQueryBuilder.tsx create mode 100644 src/hooks/useDatabaseTables.tsx create mode 100644 src/hooks/useDatabases.tsx create mode 100644 src/hooks/useTableColumns.tsx create mode 100644 src/v4/otel.tsx diff --git a/src/components/v4/DatabaseTableSelect.tsx b/src/components/v4/DatabaseTableSelect.tsx index 47b8f4c2..3cdf46e9 100644 --- a/src/components/v4/DatabaseTableSelect.tsx +++ b/src/components/v4/DatabaseTableSelect.tsx @@ -4,32 +4,31 @@ import { SelectableValue } from '@grafana/data'; import { Datasource } from '../../data/CHDatasource'; import selectors from 'v4/selectors'; import { styles } from '../../styles'; +import useDatabaseTables from 'hooks/useDatabaseTables'; +import useDatabases from 'hooks/useDatabases'; export type DatabaseSelectProps = { datasource: Datasource; - database?: string; + database: string; onDatabaseChange: (value: string) => void }; export const DatabaseSelect = (props: DatabaseSelectProps) => { const { datasource, onDatabaseChange, database } = props; + const databases = useDatabases(datasource); const [list, setList] = useState>>([]); const { label, tooltip } = selectors.components.DatabaseSelect; useEffect(() => { - async function fetchList() { - const list = await datasource.fetchDatabases(); - const values = list.map(t => ({ label: t, value: t })); + const values = databases.map(d => ({ label: d, value: d })); - // Add selected value to the list if it does not exist. - if (database && !list.includes(database)) { - values.push({ label: database, value: database }); - } - - setList(values); + // Add selected value to the list if it does not exist. + if (database && !databases.includes(database)) { + values.push({ label: database, value: database }); } - fetchList(); - }, [datasource, database]); + + setList(values); + }, [datasource, database, databases]); const defaultDatabase = datasource.settings.jsonData.defaultDatabase; const db = database ?? defaultDatabase; @@ -53,33 +52,29 @@ export const DatabaseSelect = (props: DatabaseSelectProps) => { export type TableSelectProps = { datasource: Datasource; - database?: string; - table?: string; + database: string; + table: string; onTableChange: (value: string) => void; }; export const TableSelect = (props: TableSelectProps) => { const { datasource, onTableChange, database, table } = props; + const tables = useDatabaseTables(datasource, database); const [list, setList] = useState>>([]); const { label, tooltip } = selectors.components.TableSelect; useEffect(() => { - async function fetchTables() { - const tables = await datasource.fetchTables(database); - const values = tables.map(t => ({ label: t, value: t })); - - // Add selected value to the list if it does not exist. - if (table && !tables.includes(table)) { - values.push({ label: table, value: table }); - } - - // TODO: Can't seem to reset the select to unselected - values.push({ label: '-- Choose --', value: '' }); - setList(values); + const values = tables.map(t => ({ label: t, value: t })); + // Add selected value to the list if it does not exist. + if (table && !tables.includes(table)) { + values.push({ label: table, value: table }); } - fetchTables(); - }, [datasource, database, table]); + + // TODO: Can't seem to reset the select to unselected + values.push({ label: '-- Choose --', value: '' }); + setList(values); + }, [tables, table]); return ( <> @@ -100,9 +95,9 @@ export const TableSelect = (props: TableSelectProps) => { export type DatabaseTableSelectProps = { datasource: Datasource; - database?: string; + database: string; onDatabaseChange: (value: string) => void - table?: string; + table: string; onTableChange: (value: string) => void; }; diff --git a/src/components/v4/queryBuilder/AggregateEditor.tsx b/src/components/v4/queryBuilder/AggregateEditor.tsx new file mode 100644 index 00000000..fa064673 --- /dev/null +++ b/src/components/v4/queryBuilder/AggregateEditor.tsx @@ -0,0 +1,147 @@ +import React, { useState } from 'react'; +import { SelectableValue } from '@grafana/data'; +import { InlineFormLabel, Select, Button, Input } from '@grafana/ui'; +import { BuilderMetricField, BuilderMetricFieldAggregation, FullField } from 'types/queryBuilder'; +import { selectors } from 'selectors'; +import { styles } from 'styles'; + +const AggregateEditor = (props: { + allColumns: FullField[]; + index: number; + metric: BuilderMetricField; + metrics: BuilderMetricField[]; + onMetricsChange: (metrics: BuilderMetricField[]) => void; +}) => { + const columns: SelectableValue[] = (props.allColumns || []).map((f) => ({ label: f.label, value: f.name })); + const [isOpen, setIsOpen] = useState(false); + const { metric, index, metrics, onMetricsChange } = props; + const [alias, setAlias] = useState(metric.alias || ''); + const { ALIAS } = selectors.components.QueryEditor.QueryBuilder.AGGREGATES; + const aggregationTypes: Array> = [ + { value: BuilderMetricFieldAggregation.Count, label: 'Count' }, + { value: BuilderMetricFieldAggregation.Sum, label: 'Sum' }, + { value: BuilderMetricFieldAggregation.Min, label: 'Min' }, + { value: BuilderMetricFieldAggregation.Max, label: 'Max' }, + { value: BuilderMetricFieldAggregation.Average, label: 'Average' }, + { value: BuilderMetricFieldAggregation.Any, label: 'Any' }, + // { value: BuilderMetricFieldAggregation.Count_Distinct, label: 'Distinct Count' }, + ]; + const onMetricFieldChange = (e: SelectableValue) => { + setIsOpen(false); + const newMetrics: BuilderMetricField[] = [...metrics].map((o, i) => { + return { ...o, field: i === index ? e.value! : o.field }; + }); + onMetricsChange(newMetrics); + }; + const onMetricAggregationChange = (aggregation: BuilderMetricFieldAggregation) => { + const newMetrics: BuilderMetricField[] = [...metrics].map((o, i) => { + return { ...o, aggregation: i === index ? aggregation : o.aggregation }; + }); + onMetricsChange(newMetrics); + }; + const onMetricAliasChange = () => { + const newMetrics: BuilderMetricField[] = [...metrics].map((o, i) => { + return { ...o, alias: i === index ? alias : o.alias }; + }); + onMetricsChange(newMetrics); + }; + return ( + <> + setAlias(e.currentTarget.value)} + onBlur={onMetricAliasChange} + placeholder="alias" + /> + + ); +}; + +interface AggregatesEditorProps { + allColumns: FullField[]; + aggregates: BuilderMetricField[]; + onAggregatesChange: (metrics: BuilderMetricField[]) => void; +} +export const AggregatesEditor = (props: AggregatesEditorProps) => { + const { aggregates, onAggregatesChange, allColumns = [] } = props; + const { label, tooltipAggregate, AddLabel, RemoveLabel } = selectors.components.QueryEditor.QueryBuilder.AGGREGATES; + const onMetricAdd = () => { + const newMetric: BuilderMetricField = { field: '', aggregation: BuilderMetricFieldAggregation.Count }; + onAggregatesChange([...aggregates, newMetric]); + }; + const onMetricRemove = (index: number) => { + const newMetrics: BuilderMetricField[] = [...aggregates]; + newMetrics.splice(index, 1); + onAggregatesChange(newMetrics); + }; + return ( + <> + {aggregates.map((metric, index) => { + return ( +
+ {index === 0 ? ( + + {label} + + ) : ( +
+ )} + + {aggregates.length > 1 && ( + + )} +
+ ); + })} +
+
+ +
+ + ); +}; diff --git a/src/components/v4/queryBuilder/ColumnSelect.tsx b/src/components/v4/queryBuilder/ColumnSelect.tsx new file mode 100644 index 00000000..55042429 --- /dev/null +++ b/src/components/v4/queryBuilder/ColumnSelect.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { SelectableValue } from '@grafana/data'; +import { InlineFormLabel, Select } from '@grafana/ui'; +import { FullField } from 'types/queryBuilder'; + +interface ColumnSelectProps { + allColumns: FullField[]; + selectedColumn: string; + onColumnChange: (c: string) => void, + columnFilterFn?: (c: FullField) => boolean, + label: string; + tooltip: string; +} + +const defaultFilterFn = () => true; + +export const ColumnSelect = (props: ColumnSelectProps) => { + const { allColumns, selectedColumn, onColumnChange, columnFilterFn, label, tooltip } = props; + const columns: SelectableValue[] = (allColumns || []). + filter(columnFilterFn || defaultFilterFn). + map(f => ({ label: f.label, value: f.name })); + + return ( +
+ + {label} + + setValue(e.currentTarget.valueAsNumber || 0)} + onBlur={() => props.onChange(value)} + /> +
+ ); +}; + +const FilterValueSingleStringItem = (props: { value: string; onChange: (value: string) => void }) => { + return ( +
+ props.onChange(e.currentTarget.value)} + /> +
+ ); +}; + +const FilterValueMultiStringItem = (props: { value: string[]; onChange: (value: string[]) => void }) => { + const [value, setValue] = useState(props.value || []); + return ( +
+ setValue((e.currentTarget.value || '').split(','))} + onBlur={() => props.onChange(value)} + /> +
+ ); +}; + +export const FilterValueEditor = (props: { + fieldsList: FullField[]; + filter: Filter; + onFilterChange: (filter: Filter) => void; +}) => { + const { filter, onFilterChange, fieldsList } = props; + const getOptions = () => { + const matchedFilter = fieldsList.find((f) => f.name === filter.key); + return matchedFilter?.picklistValues || []; + }; + if (utils.isNullFilter(filter)) { + return <>; + } else if (utils.isBooleanFilter(filter)) { + const onBoolFilterValueChange = (value: boolean) => { + onFilterChange({ ...filter, value }); + }; + return ( +
+ onBoolFilterValueChange(e!)} /> +
+ ); + } else if (utils.isNumberFilter(filter)) { + return onFilterChange({ ...filter, value })} />; + } else if (utils.isDateFilter(filter)) { + const onDateFilterValueChange = (value: string) => { + onFilterChange({ ...filter, value }); + }; + return utils.isDateFilterWithOutValue(filter) ? null : ( +
+ onStringFilterValueChange(e.value!)} options={getOptions()} /> +
+ ); + } + + return ( + + ); + } else if (utils.isMultiFilter(filter)) { + const onMultiFilterValueChange = (value: string[]) => { + onFilterChange({ ...filter, value }); + }; + if (filter.type === 'picklist') { + return ( +
+ onMultiFilterValueChange(e.map((v) => v.value!))} + /> +
+ ); + } + return ; + } else { + return <>; + } +}; + +export const FilterEditor = (props: { + fieldsList: FullField[]; + index: number; + filter: Filter & PredefinedFilter; + onFilterChange: (index: number, filter: Filter) => void; +}) => { + const { index, filter, fieldsList, onFilterChange } = props; + const [isOpen, setIsOpen] = useState(false); + const getFields = () => { + const values = (filter.restrictToFields || fieldsList).map((f) => { + return { label: f.label, value: f.name }; + }); + // Add selected value to the list if it does not exist. + if (filter?.key && !values.find((x) => x.value === filter.key)) { + values.push({ label: filter.key!, value: filter.key! }); + } + return values; + }; + const getFilterOperatorsByType = (type = 'string'): Array> => { + if (utils.isBooleanType(type)) { + return filterOperators.filter((f) => [FilterOperator.Equals, FilterOperator.NotEquals].includes(f.value!)); + } else if (utils.isNumberType(type)) { + return filterOperators.filter((f) => + [ + FilterOperator.IsNull, + FilterOperator.IsNotNull, + FilterOperator.Equals, + FilterOperator.NotEquals, + FilterOperator.LessThan, + FilterOperator.LessThanOrEqual, + FilterOperator.GreaterThan, + FilterOperator.GreaterThanOrEqual, + ].includes(f.value!) + ); + } else if (utils.isDateType(type)) { + return filterOperators.filter((f) => + [ + FilterOperator.IsNull, + FilterOperator.IsNotNull, + FilterOperator.Equals, + FilterOperator.NotEquals, + FilterOperator.LessThan, + FilterOperator.LessThanOrEqual, + FilterOperator.GreaterThan, + FilterOperator.GreaterThanOrEqual, + FilterOperator.WithInGrafanaTimeRange, + FilterOperator.OutsideGrafanaTimeRange, + ].includes(f.value!) + ); + } else { + return filterOperators.filter((f) => + [ + FilterOperator.IsNull, + FilterOperator.IsNotNull, + FilterOperator.Equals, + FilterOperator.NotEquals, + FilterOperator.Like, + FilterOperator.NotLike, + FilterOperator.In, + FilterOperator.NotIn, + ].includes(f.value!) + ); + } + }; + const onFilterNameChange = (fieldName: string) => { + setIsOpen(false); + const matchingField = fieldsList.find((f) => f.name === fieldName); + let filterData: { key: string; type: string } | null = null; + + if (matchingField) { + filterData = { + key: matchingField.name, + type: matchingField.type, + }; + } else { + // In case user wants to add a custom filter for the + // field with `Map` type (e.g. colName['keyName']) + // More info: https://clickhouse.com/docs/en/sql-reference/data-types/map/ + const matchingMapField = fieldsList.find((f) => { + return ( + f.type.startsWith('Map') && + fieldName.startsWith(f.name) && + new RegExp(`^${f.name}\\[['"].+['"]\\]$`).test(fieldName) + ); + }); + + if (matchingMapField) { + // Getting the field type. Example: getting `UInt64` from `Map(String, UInt64)`. + const mapFieldType = /^Map\(\w+, (\w+)\)$/.exec(matchingMapField.type)?.[1]; + + if (mapFieldType) { + filterData = { + key: fieldName, + type: mapFieldType, + }; + } + } + } + + if (!filterData) { + return; + } + + let newFilter: Filter & PredefinedFilter; + // this is an auto-generated TimeRange filter + if (filter.restrictToFields) { + newFilter = { + filterType: 'custom', + key: filterData.key, + type: 'datetime', + condition: filter.condition || 'AND', + operator: FilterOperator.WithInGrafanaTimeRange, + restrictToFields: filter.restrictToFields, + }; + } else if (utils.isBooleanType(filterData.type)) { + newFilter = { + filterType: 'custom', + key: filterData.key, + type: 'boolean', + condition: filter.condition || 'AND', + operator: FilterOperator.Equals, + value: false, + }; + } else if (utils.isDateType(filterData.type)) { + newFilter = { + filterType: 'custom', + key: filterData.key, + type: filterData.type as 'date', + condition: filter.condition || 'AND', + operator: FilterOperator.Equals, + value: 'TODAY', + }; + } else { + newFilter = { + filterType: 'custom', + key: filterData.key, + type: filterData.type, + condition: filter.condition || 'AND', + operator: FilterOperator.IsNotNull, + }; + } + onFilterChange(index, newFilter); + }; + const onFilterOperatorChange = (operator: FilterOperator) => { + let newFilter: Filter = filter; + newFilter.operator = operator; + if (utils.isMultiFilter(newFilter)) { + if (!Array.isArray(newFilter.value)) { + newFilter.value = [newFilter.value || '']; + } + } + onFilterChange(index, newFilter); + }; + const onFilterConditionChange = (condition: 'AND' | 'OR') => { + let newFilter: Filter = filter; + newFilter.condition = condition; + onFilterChange(index, newFilter); + }; + const onFilterValueChange = (filter: Filter) => { + onFilterChange(index, filter); + }; + return ( + <> + {index !== 0 && ( + onFilterConditionChange(e!)} /> + )} + onFilterOperatorChange(e.value!)} + menuPlacement={'bottom'} + /> + + + ); +}; + +export const FiltersEditor = (props: { + allColumns: FullField[]; + filters: Filter[]; + onFiltersChange: (filters: Filter[]) => void; +}) => { + const { filters = [], onFiltersChange, allColumns: fieldsList = [] } = props; + const { label, tooltip, AddLabel, RemoveLabel } = selectors.components.QueryEditor.QueryBuilder.WHERE; + const addFilter = () => { + onFiltersChange([...filters, { ...defaultNewFilter }]); + }; + const removeFilter = (index: number) => { + const newFilters = [...filters]; + newFilters.splice(index, 1); + onFiltersChange(newFilters); + }; + const onFilterChange = (index: number, filter: Filter) => { + const newFilters = [...filters]; + newFilters[index] = filter; + onFiltersChange(newFilters); + }; + return ( + <> + {filters.length === 0 && ( +
+ + {label} + + +
+ )} + {filters.map((filter, index) => { + return ( +
+ {index === 0 ? ( + + {label} + + ) : ( +
+ )} + + +
+ ); + })} + {filters.length !== 0 && ( +
+
+ +
+ )} + + ); +}; diff --git a/src/components/v4/queryBuilder/GroupByEditor.tsx b/src/components/v4/queryBuilder/GroupByEditor.tsx new file mode 100644 index 00000000..e4a81fea --- /dev/null +++ b/src/components/v4/queryBuilder/GroupByEditor.tsx @@ -0,0 +1,45 @@ +import React, { useState } from 'react'; +import { InlineFormLabel, MultiSelect } from '@grafana/ui'; +import { SelectableValue } from '@grafana/data'; +import { FullField } from 'types/queryBuilder'; +import { selectors } from 'selectors'; +import { styles } from 'styles'; + +interface GroupByEditorProps { + allColumns: FullField[]; + groupBy: string[]; + onGroupByChange: (groupBy: string[]) => void; +} +export const GroupByEditor = (props: GroupByEditorProps) => { + const columns: SelectableValue[] = (props.allColumns || []).map((f) => ({ label: f.label, value: f.name })); + const [isOpen, setIsOpen] = useState(false); + const [groupBy, setGroupBy] = useState(props.groupBy || []); + const { label, tooltip } = selectors.components.QueryEditor.QueryBuilder.GROUP_BY; + const onChange = (e: Array>) => { + setIsOpen(false); + setGroupBy(e.map((item) => item.value!)); + }; + // Add selected value to the list if it does not exist. + groupBy.filter((x) => !columns.some((y) => y.value === x)).forEach((x) => columns.push({ value: x, label: x })); + return ( +
+ + {label} + +
+ setIsOpen(true)} + onCloseMenu={() => setIsOpen(false)} + onChange={onChange} + onBlur={() => props.onGroupByChange(groupBy)} + value={groupBy} + allowCustomValue={true} + menuPlacement={'bottom'} + /> +
+
+ ); +}; diff --git a/src/components/v4/queryBuilder/LimitEditor.tsx b/src/components/v4/queryBuilder/LimitEditor.tsx new file mode 100644 index 00000000..0c36a18e --- /dev/null +++ b/src/components/v4/queryBuilder/LimitEditor.tsx @@ -0,0 +1,29 @@ +import React, { useState } from 'react'; +import { InlineFormLabel, Input } from '@grafana/ui'; +import selectors from 'v4/selectors'; + +interface LimitEditorProps { + limit: number; + onLimitChange: (limit: number) => void; +} + +export const LimitEditor = (props: LimitEditorProps) => { + const [limit, setLimit] = useState(props.limit || 100); + const { label, tooltip } = selectors.components.LimitEditor; + + return ( +
+ + {label} + + setLimit(e.currentTarget.valueAsNumber)} + onBlur={() => props.onLimitChange(limit)} + /> +
+ ); +}; diff --git a/src/components/v4/queryBuilder/ModeSwitch.tsx b/src/components/v4/queryBuilder/ModeSwitch.tsx new file mode 100644 index 00000000..23cfb164 --- /dev/null +++ b/src/components/v4/queryBuilder/ModeSwitch.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { RadioButtonGroup, InlineFormLabel } from '@grafana/ui'; + +export interface ModeSwitchProps { + labelA: string; + labelB: string; + value: boolean; + onChange: (value: boolean) => void; + label: string; + tooltip: string; +}; + +/** + * Component for switching between modes. Boxes are labled unlike regular Switch. + */ +export const ModeSwitch = (props: ModeSwitchProps) => { + const { labelA, labelB, value, onChange, label, tooltip } = props; + + const options = [ + { + label: labelA, + value: false, + }, + { + label: labelB, + value: true, + }, + ]; + + return ( +
+ + {label} + + options={options} value={value} onChange={v => onChange(v)} /> +
+ ); +} diff --git a/src/components/v4/queryBuilder/OrderByEditor.tsx b/src/components/v4/queryBuilder/OrderByEditor.tsx new file mode 100644 index 00000000..77e7dc07 --- /dev/null +++ b/src/components/v4/queryBuilder/OrderByEditor.tsx @@ -0,0 +1,191 @@ +import React from 'react'; +import { SelectableValue } from '@grafana/data'; +import { Button, InlineFormLabel, Select } from '@grafana/ui'; +import { + OrderBy, + OrderByDirection, + SqlBuilderOptions, + FullField, + BuilderMode, + BuilderMetricField, + SqlBuilderOptionsAggregate, +} from 'types/queryBuilder'; +import {selectors} from 'selectors'; +import { styles } from 'styles'; + +interface OrderByItemProps { + index: number; + allColumns: Array>; + orderBy: OrderBy[]; + orderByItem: OrderBy; + onOrderByItemsChange: (orderBy: OrderBy[]) => void; +} + +const OrderByItem = (props: OrderByItemProps) => { + const columns: SelectableValue[] = props.allColumns || []; + const { index, orderByItem } = props; + const sortOptions = [ + { value: OrderByDirection.ASC, label: 'ASC' }, + { value: OrderByDirection.DESC, label: 'DESC' }, + ]; + const onOrderBySortFieldUpdate = (name: string) => { + const orderByItems: OrderBy[] = [...props.orderBy].map((o, i) => { + return { ...o, name: i === index ? name : o.name }; + }); + props.onOrderByItemsChange(orderByItems); + }; + const onOrderBySortDirectionUpdate = (direction: OrderByDirection) => { + const orderByItems: OrderBy[] = [...props.orderBy].map((o, i) => { + return { ...o, dir: i === index ? direction : o.dir }; + }); + props.onOrderByItemsChange(orderByItems); + }; + return ( + <> + + + value={orderByItem.dir} + className={styles.Common.inlineSelect} + width={12} + options={sortOptions} + onChange={(e) => onOrderBySortDirectionUpdate(e.value!)} + menuPlacement={'bottom'} + /> + + ); +}; + +interface OrderByEditorProps { + allColumns: Array>; + orderBy: OrderBy[]; + onOrderByItemsChange: (orderBy: OrderBy[]) => void; +} +export const OrderByEditor = (props: OrderByEditorProps) => { + const columns: SelectableValue[] = props.allColumns || []; + const { label, tooltip, AddLabel, RemoveLabel } = selectors.components.QueryEditor.QueryBuilder.ORDER_BY; + const onOrderByAdd = () => { + const orderByItems: OrderBy[] = [...props.orderBy]; + orderByItems.push({ + name: columns[0]?.value || 'Name', + dir: OrderByDirection.ASC, + }); + props.onOrderByItemsChange(orderByItems); + }; + const onOrderByRemove = (index: number) => { + const orderByItems: OrderBy[] = [...props.orderBy]; + orderByItems.splice(index, 1); + props.onOrderByItemsChange(orderByItems); + }; + return columns.length === 0 ? null : ( + <> + {props.orderBy.length === 0 ? ( +
+ + {label} + + +
+ ) : ( + <> + {props.orderBy.map((o, index) => { + return ( +
+ {index === 0 ? ( + + {label} + + ) : ( +
+ )} + + +
+ ); + })} +
+
+ +
+ + )} + + ); +}; + +export const getOrderByFields = ( + builder: SqlBuilderOptions, + allColumns: FullField[] +): Array> => { + let values: Array> | Array<{ value: string; label: string }> = []; + switch (builder.mode) { + case BuilderMode.Aggregate: + values = [ + ...(builder.fields || []).map((g) => { + return { value: g, label: g }; + }), + ...((builder.metrics as BuilderMetricField[]) || []).map((m) => { + return { value: `${m.aggregation}(${m.field})`, label: `${m.aggregation}(${m.field})` }; + }), + ...((builder.groupBy as string[]) || []).map((g) => { + return { value: g, label: g }; + }), + ]; + break; + case BuilderMode.List: + default: + values = allColumns.map((m) => { + return { value: m.name, label: m.label }; + }); + } + // Add selected value to the list if it does not exist. + (builder as SqlBuilderOptionsAggregate).orderBy + ?.filter((x) => !values.some((y: { value: string; label: string } | SelectableValue) => y.value === x.name)) + .forEach((x) => values.push({ value: x.name, label: x.name })); + return values; +}; diff --git a/src/components/v4/queryBuilder/OtelVersionSelect.tsx b/src/components/v4/queryBuilder/OtelVersionSelect.tsx new file mode 100644 index 00000000..89644392 --- /dev/null +++ b/src/components/v4/queryBuilder/OtelVersionSelect.tsx @@ -0,0 +1,60 @@ +import React, { useEffect } from 'react'; +import { SelectableValue } from '@grafana/data'; +import { InlineFormLabel, Select, Switch as GrafanaSwitch, useTheme } from '@grafana/ui'; +import { versions as allVersions } from 'v4/otel'; +import selectors from 'v4/selectors'; + +interface OtelVersionSelectProps { + enabled: boolean, + onEnabledChange: (enabled: boolean) => void, + selectedVersion: string, + onVersionChange: (version: string) => void, + defaultToLatest?: boolean, +} + +export const OtelVersionSelect = (props: OtelVersionSelectProps) => { + const { enabled, onEnabledChange, selectedVersion, onVersionChange, defaultToLatest } = props; + const { label, tooltip } = selectors.components.OtelVersionSelect; + const options: SelectableValue[] = (allVersions || []). + map(v => ({ + label: `${v.version}${v.name ? (` (${v.name})`) : ''}`, + value: v.version + })); + + const theme = useTheme(); + const switchContainerStyle: React.CSSProperties = { + padding: `0 ${theme.spacing.sm}`, + height: `${theme.spacing.formInputHeight}px`, + display: 'flex', + alignItems: 'center', + }; + + useEffect(() => { + if (defaultToLatest && selectedVersion === '') { + onVersionChange(allVersions[0].version); + } + }, [selectedVersion, onVersionChange, defaultToLatest]) + + return ( +
+ + {label} + +
+ onEnabledChange(e.currentTarget.checked)} + /> +
+ onDatabaseChange(e.value!)} - options={list} - value={db} menuPlacement={'bottom'} - allowCustomValue={true} + allowCustomValue > ); @@ -60,21 +59,23 @@ export type TableSelectProps = { export const TableSelect = (props: TableSelectProps) => { const { datasource, onTableChange, database, table } = props; - const tables = useDatabaseTables(datasource, database); - const [list, setList] = useState>>([]); - const { label, tooltip } = selectors.components.TableSelect; + const tables = useTables(datasource, database); + const { label, tooltip, empty } = selectors.components.TableSelect; - useEffect(() => { - const values = tables.map(t => ({ label: t, value: t })); - // Add selected value to the list if it does not exist. - if (table && !tables.includes(table)) { - values.push({ label: table, value: table }); - } + const options = tables.map(t => ({ label: t, value: t })); + options.push({ label: empty, value: '' }); // Allow a blank value + + if (table && !tables.includes(table)) { + options.push({ label: table, value: table }); + } - // TODO: Can't seem to reset the select to unselected - values.push({ label: '-- Choose --', value: '' }); - setList(values); - }, [tables, table]); + // useEffect(() => { + // // TODO: broken. tables are loaded async when the db is changed, so it picks the first table from the previous db + // // Auto select first table + // if (database && !table && tables.length > 0) { + // onTableChange(tables[0]); + // } + // }, [database, table, tables, onTableChange]); return ( <> @@ -83,11 +84,11 @@ export const TableSelect = (props: TableSelectProps) => { ); diff --git a/src/components/v4/EditorTypeSwitcher.tsx b/src/components/v4/EditorTypeSwitcher.tsx index fa83dfdc..8d8a4792 100644 --- a/src/components/v4/EditorTypeSwitcher.tsx +++ b/src/components/v4/EditorTypeSwitcher.tsx @@ -4,7 +4,7 @@ import { RadioButtonGroup, ConfirmModal, InlineFormLabel } from '@grafana/ui'; import { getQueryOptionsFromSql, getSQLFromQueryOptions } from '../queryBuilder/utils'; import selectors from '../../v4/selectors'; import { EditorType, CHQuery, defaultCHBuilderQuery, CHSqlQuery } from 'types/sql'; -import { SqlBuilderOptions } from 'types/queryBuilder'; +import { QueryBuilderOptions } from 'types/queryBuilder'; import isString from 'lodash/isString'; interface CHEditorTypeSwitcherProps { @@ -41,14 +41,14 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { } } else { setEditor(editorType); - let builderOptions: SqlBuilderOptions; + let builderOptions: QueryBuilderOptions; switch (query.editorType) { case EditorType.Builder: builderOptions = query.builderOptions; break; case EditorType.SQL: builderOptions = - (getQueryOptionsFromSql(query.rawSql) as SqlBuilderOptions) || defaultCHBuilderQuery.builderOptions; + (getQueryOptionsFromSql(query.rawSql) as QueryBuilderOptions) || defaultCHBuilderQuery.builderOptions; break; default: builderOptions = defaultCHBuilderQuery.builderOptions; @@ -58,8 +58,7 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { onChange({ ...query, editorType: EditorType.SQL, - // queryType: QueryType.SQL, - rawSql: getSQLFromQueryOptions(query.database, query.table, builderOptions), + rawSql: getSQLFromQueryOptions(builderOptions.database, builderOptions.table, builderOptions), meta: { builderOptions }, // format: query.format, // selectedFormat: query.selectedFormat, @@ -68,8 +67,7 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { onChange({ ...query, editorType: EditorType.Builder, - // queryType: QueryType.Builder, - rawSql: getSQLFromQueryOptions(query.database, query.table, builderOptions), + rawSql: getSQLFromQueryOptions(builderOptions.database, builderOptions.table, builderOptions), builderOptions }); } diff --git a/src/components/v4/QueryTypeSwitcher.tsx b/src/components/v4/QueryTypeSwitcher.tsx index 27a61c75..5c9af8ab 100644 --- a/src/components/v4/QueryTypeSwitcher.tsx +++ b/src/components/v4/QueryTypeSwitcher.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { RadioButtonGroup, InlineFormLabel } from '@grafana/ui'; import selectors from '../../v4/selectors'; -import { QueryType } from 'types/sql'; +import { QueryType } from 'types/queryBuilder'; export interface QueryTypeSwitcherProps { queryType: QueryType; @@ -34,6 +34,12 @@ export const QueryTypeSwitcher = (props: QueryTypeSwitcherProps) => { const { queryType, onChange } = props; const { label, tooltip } = selectors.components.QueryTypeSwitcher; + useEffect(() => { + if (!queryType) { + onChange(QueryType.Table); + } + }, [queryType, onChange]); + return ( diff --git a/src/components/v4/SqlEditor.tsx b/src/components/v4/SqlEditor.tsx index 71e78c90..d65c319e 100644 --- a/src/components/v4/SqlEditor.tsx +++ b/src/components/v4/SqlEditor.tsx @@ -4,7 +4,7 @@ import { CodeEditor } from '@grafana/ui'; import { Datasource } from 'data/CHDatasource'; import { registerSQL, Range, Fetcher } from '../sqlProvider'; import { CHConfig } from 'types/config'; -import { CHQuery, EditorType, QueryType, CHSqlQuery } from 'types/sql'; +import { CHQuery, EditorType, CHSqlQuery } from 'types/sql'; import { styles } from 'styles'; import { fetchSuggestions as sugg, Schema } from '../suggestions'; import { selectors } from 'selectors'; @@ -31,7 +31,7 @@ export const SqlEditor = (props: SqlEditorProps) => { const onSqlChange = (sql: string) => { // const format = getFormat(sql, query.selectedFormat); - onChange({ ...query, rawSql: sql, editorType: EditorType.SQL, queryType: QueryType.Table }); + onChange({ ...query, rawSql: sql, editorType: EditorType.SQL }); onRunQuery(); }; diff --git a/src/components/v4/queryBuilder/AggregateEditor.tsx b/src/components/v4/queryBuilder/AggregateEditor.tsx index fa064673..6ddf023e 100644 --- a/src/components/v4/queryBuilder/AggregateEditor.tsx +++ b/src/components/v4/queryBuilder/AggregateEditor.tsx @@ -1,145 +1,142 @@ import React, { useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { InlineFormLabel, Select, Button, Input } from '@grafana/ui'; -import { BuilderMetricField, BuilderMetricFieldAggregation, FullField } from 'types/queryBuilder'; -import { selectors } from 'selectors'; +import { AggregateColumn, AggregateType, TableColumn } from 'types/queryBuilder'; +import selectors from 'v4/selectors'; import { styles } from 'styles'; -const AggregateEditor = (props: { - allColumns: FullField[]; - index: number; - metric: BuilderMetricField; - metrics: BuilderMetricField[]; - onMetricsChange: (metrics: BuilderMetricField[]) => void; -}) => { - const columns: SelectableValue[] = (props.allColumns || []).map((f) => ({ label: f.label, value: f.name })); +interface AggregateProps { + columnOptions: Array>; + index: number, + aggregate: AggregateColumn; + updateAggregate: (index: number, aggregate: AggregateColumn) => void; +} + +const aggregateOptions: Array> = [ + { label: 'Count', value: AggregateType.Count }, + { label: 'Sum', value: AggregateType.Sum }, + { label: 'Min', value: AggregateType.Min }, + { label: 'Max', value: AggregateType.Max }, + { label: 'Average', value: AggregateType.Average }, + { label: 'Any', value: AggregateType.Any }, + // { label: 'Distinct Count', value: AggregateType.Count_Distinct }, +]; + +const Aggregate = (props: AggregateProps) => { + const { columnOptions, index, aggregate, updateAggregate } = props; const [isOpen, setIsOpen] = useState(false); - const { metric, index, metrics, onMetricsChange } = props; - const [alias, setAlias] = useState(metric.alias || ''); - const { ALIAS } = selectors.components.QueryEditor.QueryBuilder.AGGREGATES; - const aggregationTypes: Array> = [ - { value: BuilderMetricFieldAggregation.Count, label: 'Count' }, - { value: BuilderMetricFieldAggregation.Sum, label: 'Sum' }, - { value: BuilderMetricFieldAggregation.Min, label: 'Min' }, - { value: BuilderMetricFieldAggregation.Max, label: 'Max' }, - { value: BuilderMetricFieldAggregation.Average, label: 'Average' }, - { value: BuilderMetricFieldAggregation.Any, label: 'Any' }, - // { value: BuilderMetricFieldAggregation.Count_Distinct, label: 'Distinct Count' }, - ]; - const onMetricFieldChange = (e: SelectableValue) => { - setIsOpen(false); - const newMetrics: BuilderMetricField[] = [...metrics].map((o, i) => { - return { ...o, field: i === index ? e.value! : o.field }; - }); - onMetricsChange(newMetrics); - }; - const onMetricAggregationChange = (aggregation: BuilderMetricFieldAggregation) => { - const newMetrics: BuilderMetricField[] = [...metrics].map((o, i) => { - return { ...o, aggregation: i === index ? aggregation : o.aggregation }; - }); - onMetricsChange(newMetrics); - }; - const onMetricAliasChange = () => { - const newMetrics: BuilderMetricField[] = [...metrics].map((o, i) => { - return { ...o, alias: i === index ? alias : o.alias }; - }); - onMetricsChange(newMetrics); - }; + const [alias, setAlias] = useState(aggregate.alias || ''); + const { aliasLabel } = selectors.components.AggregatesEditor; + return ( <> setAlias(e.currentTarget.value)} - onBlur={onMetricAliasChange} + onChange={e => setAlias(e.currentTarget.value)} + onBlur={e => updateAggregate(index, { ...aggregate, alias: e.currentTarget.value })} placeholder="alias" /> ); }; -interface AggregatesEditorProps { - allColumns: FullField[]; - aggregates: BuilderMetricField[]; - onAggregatesChange: (metrics: BuilderMetricField[]) => void; +interface AggregateEditorProps { + allColumns: TableColumn[]; + aggregates: AggregateColumn[]; + onAggregatesChange: (aggregates: AggregateColumn[]) => void; } -export const AggregatesEditor = (props: AggregatesEditorProps) => { - const { aggregates, onAggregatesChange, allColumns = [] } = props; - const { label, tooltipAggregate, AddLabel, RemoveLabel } = selectors.components.QueryEditor.QueryBuilder.AGGREGATES; - const onMetricAdd = () => { - const newMetric: BuilderMetricField = { field: '', aggregation: BuilderMetricFieldAggregation.Count }; - onAggregatesChange([...aggregates, newMetric]); +export const AggregateEditor = (props: AggregateEditorProps) => { + const { allColumns, aggregates, onAggregatesChange } = props; + const columnOptions: Array> = (allColumns || []).map(c => ({ label: c.name, value: c.name })); + const { label, tooltip, addLabel } = selectors.components.AggregatesEditor; + + const addAggregate = () => { + const nextAggregates: AggregateColumn[] = aggregates.slice(); + nextAggregates.push({ column: '', aggregateType: AggregateType.Count }); + onAggregatesChange(nextAggregates); }; - const onMetricRemove = (index: number) => { - const newMetrics: BuilderMetricField[] = [...aggregates]; - newMetrics.splice(index, 1); - onAggregatesChange(newMetrics); + const removeAggregate = (index: number) => { + const nextAggregates: AggregateColumn[] = aggregates.slice(); + nextAggregates.splice(index, 1); + onAggregatesChange(nextAggregates); }; + const updateAggregate = (index: number, aggregatesItem: AggregateColumn) => { + const nextAggregates: AggregateColumn[] = aggregates.slice(); + nextAggregates[index] = aggregatesItem; + onAggregatesChange(nextAggregates); + }; + + const fieldLabel = ( + + {label} + + ); + const fieldSpacer =
; + return ( <> - {aggregates.map((metric, index) => { + {aggregates.map((aggregate, index) => { + const key = `${index}-${aggregate.column}-${aggregate.aggregateType}-${aggregate.alias}`; return ( -
- {index === 0 ? ( - - {label} - - ) : ( -
- )} - + { index === 0 ? fieldLabel : fieldSpacer } + + - )}
); })} +
-
+ {aggregates.length === 0 ? fieldLabel : fieldSpacer}
diff --git a/src/components/v4/queryBuilder/ColumnSelect.tsx b/src/components/v4/queryBuilder/ColumnSelect.tsx index 55042429..cfe4f86f 100644 --- a/src/components/v4/queryBuilder/ColumnSelect.tsx +++ b/src/components/v4/queryBuilder/ColumnSelect.tsx @@ -1,35 +1,54 @@ import React from 'react'; import { SelectableValue } from '@grafana/data'; import { InlineFormLabel, Select } from '@grafana/ui'; -import { FullField } from 'types/queryBuilder'; +import { ColumnHint, SelectedColumn, TableColumn } from 'types/queryBuilder'; +import { styles } from 'styles'; interface ColumnSelectProps { - allColumns: FullField[]; - selectedColumn: string; - onColumnChange: (c: string) => void, - columnFilterFn?: (c: FullField) => boolean, + allColumns: TableColumn[]; + selectedColumn: SelectedColumn | undefined; + onColumnChange: (c: SelectedColumn) => void; + columnFilterFn?: (c: TableColumn) => boolean; + columnHint?: ColumnHint; label: string; tooltip: string; + wide?: boolean; + inline?: boolean; } const defaultFilterFn = () => true; export const ColumnSelect = (props: ColumnSelectProps) => { - const { allColumns, selectedColumn, onColumnChange, columnFilterFn, label, tooltip } = props; - const columns: SelectableValue[] = (allColumns || []). + const { allColumns, selectedColumn, onColumnChange, columnFilterFn, columnHint, label, tooltip, wide, inline } = props; + const selectedColumnName = selectedColumn?.name; + const columns: Array> = (allColumns || []). filter(columnFilterFn || defaultFilterFn). - map(f => ({ label: f.label, value: f.name })); + map(c => ({ label: c.name, value: c.name })); + + const onChange = (selected: SelectableValue) => { + const column = allColumns.find(c => c.name === selected.value); + if (column) { + onColumnChange({ + name: column.name, + type: column.type, + custom: false, + hint: columnHint + }) + } + } + + const labelStyle = 'query-keyword ' + (inline ? styles.QueryEditor.inlineField : ''); return (
- + {label} - onOrderBySortFieldUpdate(e.value!)} + options={columnOptions} + onChange={e => updateOrderByItem(index, { ...orderByItem, name: e.value! })} allowCustomValue={true} menuPlacement={'bottom'} > @@ -56,7 +43,7 @@ const OrderByItem = (props: OrderByItemProps) => { className={styles.Common.inlineSelect} width={12} options={sortOptions} - onChange={(e) => onOrderBySortDirectionUpdate(e.value!)} + onChange={e => updateOrderByItem(index, { ...orderByItem, dir: e.value! })} menuPlacement={'bottom'} /> @@ -64,113 +51,102 @@ const OrderByItem = (props: OrderByItemProps) => { }; interface OrderByEditorProps { - allColumns: Array>; + allColumns: TableColumn[]; orderBy: OrderBy[]; - onOrderByItemsChange: (orderBy: OrderBy[]) => void; + onOrderByChange: (orderBy: OrderBy[]) => void; } export const OrderByEditor = (props: OrderByEditorProps) => { - const columns: SelectableValue[] = props.allColumns || []; - const { label, tooltip, AddLabel, RemoveLabel } = selectors.components.QueryEditor.QueryBuilder.ORDER_BY; - const onOrderByAdd = () => { - const orderByItems: OrderBy[] = [...props.orderBy]; - orderByItems.push({ - name: columns[0]?.value || 'Name', - dir: OrderByDirection.ASC, - }); - props.onOrderByItemsChange(orderByItems); + const { allColumns, orderBy, onOrderByChange } = props; + const columnOptions: Array> = (allColumns || []).map(c => ({ label: c.name, value: c.name })); + const { label, tooltip, addLabel } = selectors.components.OrderByEditor; + + const addOrderByItem = () => { + const nextOrderBy: OrderBy[] = orderBy.slice(); + nextOrderBy.push({ name: allColumns[0]?.name, dir: OrderByDirection.ASC }); + onOrderByChange(nextOrderBy); + }; + const removeOrderByItem = (index: number) => { + const nextOrderBy: OrderBy[] = orderBy.slice(); + nextOrderBy.splice(index, 1); + onOrderByChange(nextOrderBy); }; - const onOrderByRemove = (index: number) => { - const orderByItems: OrderBy[] = [...props.orderBy]; - orderByItems.splice(index, 1); - props.onOrderByItemsChange(orderByItems); + const updateOrderByItem = (index: number, orderByItem: OrderBy) => { + const nextOrderBy: OrderBy[] = orderBy.slice(); + nextOrderBy[index] = orderByItem; + onOrderByChange(nextOrderBy); }; - return columns.length === 0 ? null : ( + + if (allColumns.length === 0) { + return null; + } + + const fieldLabel = ( + + {label} + + ); + const fieldSpacer =
; + + return ( <> - {props.orderBy.length === 0 ? ( -
- - {label} - - -
- ) : ( - <> - {props.orderBy.map((o, index) => { - return ( -
- {index === 0 ? ( - - {label} - - ) : ( -
- )} - - -
- ); - })} -
-
+ {orderBy.map((orderByItem, index) => { + const key = `${index}-${orderByItem.name}-${orderByItem.dir}`; + return ( +
+ { index === 0 ? fieldLabel : fieldSpacer } + + variant="destructive" + size="sm" + icon="trash-alt" + onClick={() => removeOrderByItem(index)} + />
- - )} + ); + })} + +
+ {orderBy.length === 0 ? fieldLabel : fieldSpacer} + +
); }; export const getOrderByFields = ( - builder: SqlBuilderOptions, - allColumns: FullField[] + builder: QueryBuilderOptions, + allColumns: TableColumn[] ): Array> => { let values: Array> | Array<{ value: string; label: string }> = []; switch (builder.mode) { case BuilderMode.Aggregate: values = [ - ...(builder.fields || []).map((g) => { - return { value: g, label: g }; + ...(builder.columns || []).map((g) => { + return { value: g.name, label: g.name }; }), - ...((builder.metrics as BuilderMetricField[]) || []).map((m) => { - return { value: `${m.aggregation}(${m.field})`, label: `${m.aggregation}(${m.field})` }; + ...((builder.aggregates as AggregateColumn[]) || []).map((m) => { + return { value: `${m.aggregateType}(${m.column})`, label: `${m.aggregateType}(${m.column})` }; }), ...((builder.groupBy as string[]) || []).map((g) => { return { value: g, label: g }; @@ -184,8 +160,8 @@ export const getOrderByFields = ( }); } // Add selected value to the list if it does not exist. - (builder as SqlBuilderOptionsAggregate).orderBy - ?.filter((x) => !values.some((y: { value: string; label: string } | SelectableValue) => y.value === x.name)) - .forEach((x) => values.push({ value: x.name, label: x.name })); + builder.orderBy + ?.filter(x => !values.some((y: { value: string; label: string } | SelectableValue) => y.value === x.name)) + .forEach(x => values.push({ value: x.name, label: x.name })); return values; }; diff --git a/src/components/v4/queryBuilder/QueryBuilder.tsx b/src/components/v4/queryBuilder/QueryBuilder.tsx index 4e41dab1..80093dcf 100644 --- a/src/components/v4/queryBuilder/QueryBuilder.tsx +++ b/src/components/v4/queryBuilder/QueryBuilder.tsx @@ -1,312 +1,52 @@ -import React, { useEffect, useState } from 'react'; -import defaultsDeep from 'lodash/defaultsDeep'; +import React from 'react'; import { Datasource } from '../../../data/CHDatasource'; -import { FieldsEditor } from '../../queryBuilder/Fields'; -import { MetricsEditor } from '../../queryBuilder/Metrics'; -import { TimeFieldEditor } from '../../queryBuilder/TimeField'; -import { FiltersEditor, PredefinedFilter } from '../../queryBuilder/Filters'; -import { GroupByEditor } from '../../queryBuilder/GroupBy'; -import { getOrderByFields, OrderByEditor } from '../../queryBuilder/OrderBy'; -import { LimitEditor } from '../../queryBuilder/Limit'; -import { QueryType, defaultCHBuilderQuery } from 'types/sql'; -import { - BuilderMetricField, - BuilderMode, - Filter, - FilterOperator, - FullField, - OrderBy, - SqlBuilderOptions, -} from 'types/queryBuilder'; -import { isDateTimeType, isDateType } from '../../queryBuilder/utils'; -import { selectors } from 'selectors'; -import { LogLevelFieldEditor } from '../../queryBuilder/LogLevelField'; +import { QueryType, QueryBuilderOptions } from 'types/queryBuilder'; import { CoreApp } from '@grafana/data'; -import useTableColumns from 'hooks/useTableColumns'; +import useColumns from 'hooks/useColumns'; import { LogsQueryBuilder } from './views/LogsQueryBuilder'; import { TimeSeriesQueryBuilder } from './views/TimeSeriesQueryBuilder'; +import { TableQueryBuilder } from './views/TableQueryBuilder'; +import { SqlPreview } from './SqlPreview'; +import { DatabaseTableSelect } from 'components/v4/DatabaseTableSelect'; +import { QueryTypeSwitcher } from 'components/v4/QueryTypeSwitcher'; +import { styles } from 'styles'; +import { TraceQueryBuilder } from './views/TraceQueryBuilder'; interface QueryBuilderProps { - builderOptions: SqlBuilderOptions; - onBuilderOptionsChange: (builderOptions: SqlBuilderOptions) => void; - datasource: Datasource; - queryType: QueryType; - database: string; - table: string; app: CoreApp | undefined; + builderOptions: QueryBuilderOptions; + onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; + datasource: Datasource; + generatedSql: string; } export const QueryBuilder = (props: QueryBuilderProps) => { - const { datasource, database, table, builderOptions, onBuilderOptionsChange } = props; - const allColumns = useTableColumns(datasource, database, table); - const builder = defaultsDeep(builderOptions, defaultCHBuilderQuery.builderOptions); + const { datasource, builderOptions, onBuilderOptionsChange, generatedSql } = props; + const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); - useEffect(() => { - if (builder.database === database) { - return; - } - const queryOptions: SqlBuilderOptions = { - ...builder, - database, - table: '', - fields: [], - filters: [], - orderBy: [], - timeField: undefined, - logLevelField: undefined, - }; - onBuilderOptionsChange(queryOptions); - }, [builder, database, onBuilderOptionsChange]); - useEffect(() => { - if (builder.table === table) { - return; - } - const queryOptions: SqlBuilderOptions = { - ...builder, - table: table, - fields: [], - filters: [], - orderBy: [], - timeField: undefined, - logLevelField: undefined, - }; - onBuilderOptionsChange(queryOptions); - }, [builder, table, onBuilderOptionsChange]); + const onDatabaseChange = (database: string) => onBuilderOptionsChange({ ...builderOptions, database, table: '' }); + const onTableChange = (table: string) => onBuilderOptionsChange({ ...builderOptions, table }); + const onQueryTypeChange = (queryType: QueryType) => onBuilderOptionsChange({ ...builderOptions, queryType }); return ( -
- { props.queryType === QueryType.Table && } - { props.queryType === QueryType.Logs && } - { props.queryType === QueryType.TimeSeries && } +
+
+ +
+
+ +
+ + { builderOptions.queryType === QueryType.Table && } + { builderOptions.queryType === QueryType.Logs && } + { builderOptions.queryType === QueryType.TimeSeries && } + { builderOptions.queryType === QueryType.Traces && } + +
); } - -export const OldQueryBuilder = (props: QueryBuilderProps) => { - const { onBuilderOptionsChange, database, table } = props; - const [baseFieldsList, setBaseFieldsList] = useState([]); - const [timeField, setTimeField] = useState(null); - const [logLevelField, setLogLevelField] = useState(null); - const builder = defaultsDeep(props.builderOptions, defaultCHBuilderQuery.builderOptions); - useEffect(() => { - const fetchBaseFields = async (database: string, table: string) => { - props.datasource - .fetchFieldsFull(database, table) - .then(async (fields) => { - fields.push({ name: '*', label: 'ALL', type: 'string', picklistValues: [] }); - setBaseFieldsList(fields); - - // if no filters are set, we add a default one for the time range - if (builder.filters?.length === 0) { - const dateTimeFields = fields.filter((f) => isDateTimeType(f.type)); - if (dateTimeFields.length > 0) { - const filter: Filter & PredefinedFilter = { - operator: FilterOperator.WithInGrafanaTimeRange, - filterType: 'custom', - key: dateTimeFields[0].name, - type: 'datetime', - condition: 'AND', - restrictToFields: dateTimeFields, - }; - onFiltersChange([filter]); - } - } - - // When changing from SQL Editor to Query Builder, we need to find out if the - // first value is a datetime or date, so we can change the mode to Time Series - if (builder.fields?.length > 0) { - const fieldName = builder.fields[0]; - const timeFields = fields.filter((f) => isDateType(f.type)); - const timeField = timeFields.find((x) => x.name === fieldName); - if (timeField) { - const queryOptions: SqlBuilderOptions = { - ...builder, - timeField: timeField.name, - timeFieldType: timeField.type, - mode: BuilderMode.Trend, - fields: builder.fields.slice(1, builder.fields.length), - }; - props.onBuilderOptionsChange(queryOptions); - } - } - }) - .catch((ex: any) => { - console.error(ex); - throw ex; - }); - }; - - if (props.database && props.table) { - fetchBaseFields(props.database, props.table); - } - // We want to run this only when the table changes or first time load. - // If we add 'builder.fields' / 'builder.groupBy' / 'builder.metrics' / 'builder.filters' to the deps array, this will be called every time query editor changes - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.datasource, props.database, props.table]); - - useEffect(() => { - if (builder.database === database) { - return; - } - setBaseFieldsList([]); - setTimeField(null); - setLogLevelField(null); - const queryOptions: SqlBuilderOptions = { - ...builder, - database, - table: '', - fields: [], - filters: [], - orderBy: [], - timeField: undefined, - logLevelField: undefined, - }; - onBuilderOptionsChange(queryOptions); - }, [builder, database, onBuilderOptionsChange]); - useEffect(() => { - if (builder.table === table) { - return; - } - setTimeField(null); - setLogLevelField(null); - const queryOptions: SqlBuilderOptions = { - ...builder, - table, - fields: [], - filters: [], - orderBy: [], - timeField: undefined, - logLevelField: undefined, - }; - onBuilderOptionsChange(queryOptions); - }, [builder, table, onBuilderOptionsChange]); - - // const onModeChange = (mode: BuilderMode) => { - // if (mode === BuilderMode.List) { - // const queryOptions: SqlBuilderOptions = { ...builder, mode, fields: builder.fields || [], orderBy: [] }; - // props.onBuilderOptionsChange(queryOptions); - // } else if (mode === BuilderMode.Aggregate) { - // const queryOptions: SqlBuilderOptions = { - // ...builder, - // mode, - // orderBy: [], - // metrics: builder.metrics || [], - // }; - // props.onBuilderOptionsChange(queryOptions); - // } else if (mode === BuilderMode.Trend) { - // const queryOptions: SqlBuilderOptionsTrend = { - // ...builder, - // mode: BuilderMode.Trend, - // timeField: builder.timeField || '', - // timeFieldType: builder.timeFieldType || 'datetime', - // metrics: builder.metrics || [], - // }; - // props.onBuilderOptionsChange(queryOptions); - // } - // }; - - const onFieldsChange = (fields: string[] = []) => { - const queryOptions: SqlBuilderOptions = { ...builder, fields }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onMetricsChange = (metrics: BuilderMetricField[] = []) => { - const queryOptions: SqlBuilderOptions = { ...builder, metrics }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onFiltersChange = (filters: Filter[] = []) => { - const queryOptions: SqlBuilderOptions = { ...builder, filters }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onGroupByChange = (groupBy: string[] = []) => { - const queryOptions: SqlBuilderOptions = { ...builder, groupBy }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onTimeFieldChange = (timeField = '', timeFieldType = '') => { - setTimeField(timeField); - const queryOptions: SqlBuilderOptions = { ...builder, timeField, timeFieldType }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onLogLevelFieldChange = (logLevelField = '') => { - setLogLevelField(logLevelField); - const queryOptions: SqlBuilderOptions = { ...builder, logLevelField }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onOrderByChange = (orderBy: OrderBy[] = []) => { - const queryOptions: SqlBuilderOptions = { ...builder, orderBy }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onLimitChange = (limit = 20) => { - const queryOptions: SqlBuilderOptions = { ...builder, limit }; - props.onBuilderOptionsChange(queryOptions); - }; - - const getFieldList = (): FullField[] => { - const newArray: FullField[] = []; - baseFieldsList.forEach((bf) => { - newArray.push(bf); - }); - return newArray; - }; - const fieldsList = getFieldList(); - return builder ? ( - <> - {/* */} - {builder.mode === BuilderMode.Trend && ( - - )} - { - // Time and LogLevel fields selection for Logs Volume histogram in the Explore mode - builder.mode === BuilderMode.List && props.queryType === QueryType.Logs && props.app === CoreApp.Explore && ( - <> - - - - ) - } - {builder.mode !== BuilderMode.Trend && ( - - )} - - {(builder.mode === BuilderMode.Aggregate || builder.mode === BuilderMode.Trend) && ( - - )} - - {(builder.mode === BuilderMode.Aggregate || builder.mode === BuilderMode.Trend) && ( - - )} - <> - - - - - ) : null; -}; diff --git a/src/components/v4/queryBuilder/SqlPreview.tsx b/src/components/v4/queryBuilder/SqlPreview.tsx new file mode 100644 index 00000000..15858d4b --- /dev/null +++ b/src/components/v4/queryBuilder/SqlPreview.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { InlineFormLabel } from '@grafana/ui'; +import selectors from 'v4/selectors'; + +interface SqlPreviewProps { + sql: string; +} + +export const SqlPreview = (props: SqlPreviewProps) => { + const { sql } = props; + const { label, tooltip } = selectors.components.SqlPreview; + + return ( +
+ + {label} + +
{sql}
+
+ ); +}; diff --git a/src/components/v4/queryBuilder/Switch.tsx b/src/components/v4/queryBuilder/Switch.tsx index 7736c1db..769f1c01 100644 --- a/src/components/v4/queryBuilder/Switch.tsx +++ b/src/components/v4/queryBuilder/Switch.tsx @@ -1,15 +1,17 @@ import React from 'react'; import { InlineFormLabel, Switch as GrafanaSwitch, useTheme } from '@grafana/ui'; +import { styles } from 'styles'; interface SwitchProps { value: boolean; onChange: (value: boolean) => void; label: string; tooltip: string; + inline?: boolean; } export const Switch = (props: SwitchProps) => { - const { value, onChange, label, tooltip } = props; + const { value, onChange, label, tooltip, inline } = props; const theme = useTheme(); const switchContainerStyle: React.CSSProperties = { @@ -19,9 +21,11 @@ export const Switch = (props: SwitchProps) => { alignItems: 'center', }; + const labelStyle = 'query-keyword ' + (inline ? styles.QueryEditor.inlineField : '') + return (
- + {label}
diff --git a/src/components/v4/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/v4/queryBuilder/views/LogsQueryBuilder.tsx index 44130e22..4a535ee3 100644 --- a/src/components/v4/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/v4/queryBuilder/views/LogsQueryBuilder.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { BuilderMode, Filter, FullField, OrderBy, SqlBuilderOptions } from 'types/queryBuilder'; +import { BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; import { Switch } from '../Switch'; import { OtelVersionSelect } from '../OtelVersionSelect'; @@ -8,21 +8,22 @@ import { OrderByEditor } from '../OrderByEditor'; import { LimitEditor } from '../LimitEditor'; import { FiltersEditor } from '../FilterEditor'; import allSelectors from 'v4/selectors'; +import { getColumnByHint } from 'components/queryBuilder/utils'; interface LogsQueryBuilderProps { - allColumns: FullField[]; - builderOptions: SqlBuilderOptions, - onBuilderOptionsChange: (builderOptions: SqlBuilderOptions) => void; + allColumns: TableColumn[]; + builderOptions: QueryBuilderOptions, + onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; } export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { const { allColumns, builderOptions, onBuilderOptionsChange } = props; const [otelEnabled, setOtelEnabled] = useState(false); const [otelVersion, setOtelVersion] = useState(''); - const [selectedColumns, setSelectedColumns] = useState([]); - const [timeColumn, setTimeColumn] = useState(''); - const [logLevelColumn, setLogLevelColumn] = useState(''); - const [messageColumn, setMessageColumn] = useState(''); + const [selectedColumns, setSelectedColumns] = useState([]); + const [timeColumn, setTimeColumn] = useState(); + const [logLevelColumn, setLogLevelColumn] = useState(); + const [messageColumn, setMessageColumn] = useState(); const [liveView, setLiveView] = useState(false); const [orderBy, setOrderBy] = useState([]); const [limit, setLimit] = useState(100); @@ -30,17 +31,54 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { const selectors = allSelectors.components.LogsQueryBuilder; useEffect(() => { + if (!builderOptions) { + return; + } + + builderOptions.meta?.otelEnabled !== undefined && setOtelEnabled(builderOptions.meta.otelEnabled); + builderOptions.meta?.otelVersion && setOtelVersion(builderOptions.meta.otelVersion); + setTimeColumn(getColumnByHint(builderOptions, ColumnHint.Time)); + setLogLevelColumn(getColumnByHint(builderOptions, ColumnHint.LogLevel)); + setMessageColumn(getColumnByHint(builderOptions, ColumnHint.LogMessage)); + builderOptions.columns && setSelectedColumns(builderOptions.columns.filter(c => c.hint === undefined)); + builderOptions.meta?.liveView && setLiveView(builderOptions.meta.liveView); + builderOptions.orderBy && setOrderBy(builderOptions.orderBy); + builderOptions.limit && setLimit(builderOptions.limit); + builderOptions.filters && setFilters(builderOptions.filters); + + // Run on load + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + const nextColumns = selectedColumns.slice(); + if (timeColumn) { + nextColumns.push(timeColumn); + } + if (logLevelColumn) { + nextColumns.push(logLevelColumn); + } + if (messageColumn) { + nextColumns.push(messageColumn); + } + onBuilderOptionsChange({ ...builderOptions, mode: BuilderMode.List, - fields: selectedColumns, + columns: nextColumns, filters, orderBy, limit, - timeField: timeColumn, - logLevelField: logLevelColumn, + meta: { + ...builderOptions.meta, + otelEnabled, + otelVersion, + } }); - }, [selectedColumns, filters, orderBy, limit, timeColumn, logLevelColumn, messageColumn]); + + // TODO: ignore when builderOptions changes? + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [otelEnabled, otelVersion, selectedColumns, filters, orderBy, limit, timeColumn, logLevelColumn, messageColumn]); return (
@@ -51,12 +89,13 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { onVersionChange={setOtelVersion} defaultToLatest /> - +
@@ -64,8 +103,10 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { allColumns={allColumns} selectedColumn={logLevelColumn} onColumnChange={setLogLevelColumn} + columnHint={ColumnHint.LogLevel} label={selectors.logLevelColumn.label} tooltip={selectors.logLevelColumn.tooltip} + inline />
@@ -73,6 +114,7 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { allColumns={allColumns} selectedColumn={messageColumn} onColumnChange={setMessageColumn} + columnHint={ColumnHint.LogMessage} label={selectors.logMessageColumn.label} tooltip={selectors.logMessageColumn.tooltip} /> @@ -81,12 +123,13 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { onChange={setLiveView} label={selectors.liveView.label} tooltip={selectors.liveView.tooltip} + inline />
diff --git a/src/components/v4/queryBuilder/views/TableQueryBuilder.tsx b/src/components/v4/queryBuilder/views/TableQueryBuilder.tsx new file mode 100644 index 00000000..f9067f8b --- /dev/null +++ b/src/components/v4/queryBuilder/views/TableQueryBuilder.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useState } from 'react'; +import { ColumnsEditor } from '../ColumnsEditor'; +import { AggregateColumn, BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn, AggregateType } from 'types/queryBuilder'; +import { OrderByEditor } from '../OrderByEditor'; +import { LimitEditor } from '../LimitEditor'; +import { FiltersEditor } from '../FilterEditor'; +import allSelectors from 'v4/selectors'; +import { ModeSwitch } from '../ModeSwitch'; +import { AggregateEditor } from '../AggregateEditor'; +import { GroupByEditor } from '../GroupByEditor'; + +interface TableQueryBuilderProps { + allColumns: TableColumn[]; + builderOptions: QueryBuilderOptions, + onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; +} + +const emptyAggregate: AggregateColumn = { column: '', aggregateType: AggregateType.Count }; + +export const TableQueryBuilder = (props: TableQueryBuilderProps) => { + const { allColumns, builderOptions, onBuilderOptionsChange } = props; + const [isAggregateMode, setAggregateMode] = useState(false); + const [selectedColumns, setSelectedColumns] = useState([]); + const [aggregates, setAggregates] = useState([emptyAggregate]); + const [groupBy, setGroupBy] = useState([]); + const [orderBy, setOrderBy] = useState([]); + const [limit, setLimit] = useState(100); + const [filters, setFilters] = useState([]); + const selectors = allSelectors.components.TableQueryBuilder; + + useEffect(() => { + if (!builderOptions) { + return; + } + + builderOptions.aggregates && setAggregateMode(builderOptions.aggregates.length > 0); + builderOptions.columns && setSelectedColumns(builderOptions.columns); + builderOptions.aggregates && setAggregates(builderOptions.aggregates); + builderOptions.groupBy && setGroupBy(builderOptions.groupBy); + builderOptions.orderBy && setOrderBy(builderOptions.orderBy); + builderOptions.limit && setLimit(builderOptions.limit); + builderOptions.filters && setFilters(builderOptions.filters); + + // Run on load + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + const nextOptions: QueryBuilderOptions = { + ...builderOptions, + mode: isAggregateMode ? BuilderMode.Aggregate : BuilderMode.List, + columns: selectedColumns, + filters, + orderBy, + limit + }; + + if (isAggregateMode) { + nextOptions.aggregates = aggregates; + nextOptions.groupBy = groupBy; + } + + onBuilderOptionsChange(nextOptions); + + // TODO: ignore when builderOptions changes? + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isAggregateMode, selectedColumns, filters, aggregates, groupBy, orderBy, limit]); + + const aggregateFields = ( + <> + + + + ); + + return ( +
+ + + + + {isAggregateMode && aggregateFields} + + + + +
+ ); +} diff --git a/src/components/v4/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/v4/queryBuilder/views/TimeSeriesQueryBuilder.tsx index 07d79d9e..8b4f105b 100644 --- a/src/components/v4/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/v4/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -1,26 +1,30 @@ import React, { useEffect, useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { BuilderMetricField, BuilderMode, Filter, FullField, OrderBy, SqlBuilderOptions } from 'types/queryBuilder'; +import { AggregateColumn, BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, ColumnHint, SelectedColumn, AggregateType } from 'types/queryBuilder'; import { OrderByEditor } from '../OrderByEditor'; import { LimitEditor } from '../LimitEditor'; import { FiltersEditor } from '../FilterEditor'; import allSelectors from 'v4/selectors'; import { ModeSwitch } from '../ModeSwitch'; -import { SqlBuilderOptionsAggregate, SqlBuilderOptionsList } from 'types'; -import { AggregatesEditor } from '../AggregateEditor'; +import { AggregateEditor } from '../AggregateEditor'; import { GroupByEditor } from '../GroupByEditor'; +import { ColumnSelect } from '../ColumnSelect'; +import { getColumnByHint } from 'components/queryBuilder/utils'; interface TimeSeriesQueryBuilderProps { - allColumns: FullField[]; - builderOptions: SqlBuilderOptions, - onBuilderOptionsChange: (builderOptions: SqlBuilderOptions) => void; + allColumns: TableColumn[]; + builderOptions: QueryBuilderOptions, + onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; } +const emptyAggregate: AggregateColumn = { column: '', aggregateType: AggregateType.Count }; + export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { const { allColumns, builderOptions, onBuilderOptionsChange } = props; const [isAggregateMode, setAggregateMode] = useState(false); - const [selectedColumns, setSelectedColumns] = useState([]); - const [aggregates, setAggregates] = useState([]); + const [timeColumn, setTimeColumn] = useState(); + const [selectedColumns, setSelectedColumns] = useState([]); + const [aggregates, setAggregates] = useState([emptyAggregate]); const [groupBy, setGroupBy] = useState([]); const [orderBy, setOrderBy] = useState([]); const [limit, setLimit] = useState(100); @@ -28,60 +32,52 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { const selectors = allSelectors.components.TimeSeriesQueryBuilder; useEffect(() => { - let nextOptions: SqlBuilderOptions; + if (!builderOptions) { + return; + } + + builderOptions.aggregates && setAggregateMode(builderOptions.aggregates.length > 0); + setTimeColumn(getColumnByHint(builderOptions, ColumnHint.Time)); + builderOptions.columns && setSelectedColumns(builderOptions.columns.filter(c => c.hint === undefined)); + builderOptions.aggregates && setAggregates(builderOptions.aggregates); + builderOptions.groupBy && setGroupBy(builderOptions.groupBy); + builderOptions.orderBy && setOrderBy(builderOptions.orderBy); + builderOptions.limit && setLimit(builderOptions.limit); + builderOptions.filters && setFilters(builderOptions.filters); + + // Run on load + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + const nextColumns = selectedColumns.slice(); + if (timeColumn) { + nextColumns.push(timeColumn); + } + + const nextOptions: QueryBuilderOptions = { + ...builderOptions, + mode: isAggregateMode ? BuilderMode.Aggregate : BuilderMode.List, + columns: nextColumns, + filters, + orderBy, + limit + }; if (isAggregateMode) { - const aggregateOptions: SqlBuilderOptionsAggregate = { - ...builderOptions as SqlBuilderOptionsAggregate, - mode: BuilderMode.Aggregate, - fields: selectedColumns, - metrics: aggregates, - groupBy: groupBy, - filters, - orderBy, - limit, - }; - nextOptions = aggregateOptions; - } else { - const simpleOptions: SqlBuilderOptionsList = { - ...builderOptions, - mode: BuilderMode.List, - fields: selectedColumns, - filters, - orderBy, - limit, - } - nextOptions = simpleOptions; + nextOptions.aggregates = aggregates; + nextOptions.groupBy = groupBy; } onBuilderOptionsChange(nextOptions); - }, [isAggregateMode, selectedColumns, filters, aggregates, groupBy, orderBy, limit]); - - const simpleView = ( - <> - - - - - - ); + // TODO: ignore when builderOptions changes? + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isAggregateMode, timeColumn, selectedColumns, filters, aggregates, groupBy, orderBy, limit]); - const aggregateView = ( + const aggregateFields = ( <> - - + - - - ); @@ -96,7 +92,25 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { tooltip={selectors.builderModeTooltip} /> - {isAggregateMode ? aggregateView : simpleView} + + + + {isAggregateMode && aggregateFields} + + + +
); } diff --git a/src/components/v4/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/v4/queryBuilder/views/TraceQueryBuilder.tsx new file mode 100644 index 00000000..ae52ea02 --- /dev/null +++ b/src/components/v4/queryBuilder/views/TraceQueryBuilder.tsx @@ -0,0 +1,198 @@ +import React, { useEffect, useState } from 'react'; +import { BuilderMode, Filter, TableColumn, QueryBuilderOptions, SelectedColumn, ColumnHint } from 'types/queryBuilder'; +import { ColumnSelect } from '../ColumnSelect'; +import { FiltersEditor } from '../FilterEditor'; +import allSelectors from 'v4/selectors'; +import { ModeSwitch } from '../ModeSwitch'; +import { getColumnByHint } from 'components/queryBuilder/utils'; + +interface TraceQueryBuilderProps { + allColumns: TableColumn[]; + builderOptions: QueryBuilderOptions, + onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; +} + +export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { + const { allColumns, builderOptions, onBuilderOptionsChange } = props; + const [isSearchMode, setSearchMode] = useState(false); + const [traceIdColumn, setTraceIdColumn] = useState(); + const [spanIdColumn, setSpanIdColumn] = useState(); + const [parentSpanIdColumn, setParentSpanIdColumn] = useState(); + const [serviceNameColumn, setServiceNameColumn] = useState(); + const [operationNameColumn, setOperationNameColumn] = useState(); + const [startTimeColumn, setStartTimeColumn] = useState(); + const [durationTimeColumn, setDurationTimeColumn] = useState(); + const [, setDurationUnit] = useState(); + const [tagsColumn, setTagsColumn] = useState(); + const [serviceTagsColumn, setServiceTagsColumn] = useState(); + const [, setTraceId] = useState(); + const [filters, setFilters] = useState([]); + const selectors = allSelectors.components.TraceQueryBuilder; + + useEffect(() => { + if (!builderOptions) { + return; + } + + builderOptions.meta?.isTraceSearchMode !== undefined && setSearchMode(builderOptions.meta.isTraceSearchMode); + setTraceIdColumn(getColumnByHint(builderOptions, ColumnHint.TraceId)); + setSpanIdColumn(getColumnByHint(builderOptions, ColumnHint.TraceSpanId)); + setParentSpanIdColumn(getColumnByHint(builderOptions, ColumnHint.TraceParentSpanId)); + setServiceNameColumn(getColumnByHint(builderOptions, ColumnHint.TraceServiceName)); + setOperationNameColumn(getColumnByHint(builderOptions, ColumnHint.TraceOperationName)); + setStartTimeColumn(getColumnByHint(builderOptions, ColumnHint.TraceStartTime)); + setDurationTimeColumn(getColumnByHint(builderOptions, ColumnHint.TraceDurationTime)); + builderOptions.meta?.traceDurationUnit && setDurationUnit(builderOptions.meta.traceDurationUnit); + setTagsColumn(getColumnByHint(builderOptions, ColumnHint.TraceTags)); + setServiceTagsColumn(getColumnByHint(builderOptions, ColumnHint.TraceServiceTags)); + builderOptions.meta?.traceId && setTraceId(builderOptions.meta.traceId); + builderOptions.filters && setFilters(builderOptions.filters); + + // Run on load + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + const nextColumns = [ + traceIdColumn, + spanIdColumn, + parentSpanIdColumn, + serviceNameColumn, + operationNameColumn, + startTimeColumn, + durationTimeColumn, + tagsColumn, + serviceTagsColumn + ].filter(c => c !== undefined) as SelectedColumn[]; + + + onBuilderOptionsChange({ + ...builderOptions, + mode: BuilderMode.List, + columns: nextColumns, + filters, + }); + + // TODO: ignore when builderOptions changes? + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [traceIdColumn, spanIdColumn, parentSpanIdColumn, serviceNameColumn, operationNameColumn, startTimeColumn, durationTimeColumn, tagsColumn, serviceTagsColumn]); + + return ( +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + {/* */} +
+
+ + +
+ +
+ ); +} diff --git a/src/data/CHDatasource.test.ts b/src/data/CHDatasource.test.ts index 62258e5d..09ccd9e1 100644 --- a/src/data/CHDatasource.test.ts +++ b/src/data/CHDatasource.test.ts @@ -12,8 +12,9 @@ import { DataQuery } from '@grafana/schema'; import { Observable, of } from 'rxjs'; import { DataSourceWithBackend } from '@grafana/runtime'; import { mockDatasource } from '__mocks__/datasource'; -import { CHBuilderQuery, CHQuery, CHSqlQuery, EditorType, QueryType } from 'types/sql'; -import { BuilderMode, SqlBuilderOptionsList } from 'types/queryBuilder'; +import { CHBuilderQuery, CHQuery, CHSqlQuery, EditorType } from 'types/sql'; +import { ColumnHint, QueryType } from 'types/queryBuilder'; +import { BuilderMode, QueryBuilderOptions } from 'types/queryBuilder'; import { cloneDeep } from 'lodash'; import { Datasource } from './CHDatasource'; import * as logs from './logs'; @@ -249,12 +250,12 @@ describe('ClickHouseDatasource', () => { }); }); - describe('fetchFieldsFull', () => { + describe('fetchColumnsFull', () => { it('sends a correct query when database and table names are provided', async () => { const ds = cloneDeep(mockDatasource); const frame = new ArrayDataFrame([{ name: 'foo', type: 'string', table: 'table' }]); const spyOnQuery = jest.spyOn(ds, 'query').mockImplementation((_request) => of({ data: [frame] })); - await ds.fetchFieldsFull('db_name', 'table_name'); + await ds.fetchColumnsFull('db_name', 'table_name'); const expected = { rawSql: 'DESC TABLE "db_name"."table_name"' }; expect(spyOnQuery).toHaveBeenCalledWith( @@ -266,7 +267,7 @@ describe('ClickHouseDatasource', () => { const ds = cloneDeep(mockDatasource); const frame = new ArrayDataFrame([{ name: 'foo', type: 'string', table: 'table' }]); const spyOnQuery = jest.spyOn(ds, 'query').mockImplementation((_request) => of({ data: [frame] })); - await ds.fetchFieldsFull('', 'table_name'); + await ds.fetchColumnsFull('', 'table_name'); const expected = { rawSql: 'DESC TABLE "table_name"' }; expect(spyOnQuery).toHaveBeenCalledWith( @@ -279,7 +280,7 @@ describe('ClickHouseDatasource', () => { const frame = new ArrayDataFrame([{ name: 'foo', type: 'string', table: 'table' }]); const spyOnQuery = jest.spyOn(ds, 'query').mockImplementation((_) => of({ data: [frame] })); - await ds.fetchFieldsFull('', 'table.name'); + await ds.fetchColumnsFull('', 'table.name'); const expected = { rawSql: 'DESC TABLE "table.name"' }; expect(spyOnQuery).toHaveBeenCalledWith( @@ -314,16 +315,16 @@ describe('ClickHouseDatasource', () => { const query: CHBuilderQuery = { refId: '42', editorType: EditorType.Builder, - queryType: QueryType.Logs, - database: 'system', - table: 'numbers', rawSql: 'SELECT * FROM system.numbers LIMIT 1', builderOptions: { + database: 'system', + table: 'numbers', + queryType: QueryType.Logs, mode: BuilderMode.List, - database: 'default', - table: 'logs', - timeField: 'created_at', - logLevelField: 'level', + columns: [ + { name: 'created_at', hint: ColumnHint.Time }, + { name: 'level', hint: ColumnHint.LogLevel }, + ] }, }; const request: DataQueryRequest = { @@ -377,8 +378,8 @@ describe('ClickHouseDatasource', () => { ...query, builderOptions: { ...query.builderOptions, - database: undefined, - } as SqlBuilderOptionsList, + database: '', + } as QueryBuilderOptions, }) ).toBeUndefined(); expect( @@ -386,8 +387,8 @@ describe('ClickHouseDatasource', () => { ...query, builderOptions: { ...query.builderOptions, - table: undefined, - } as SqlBuilderOptionsList, + table: '', + } as QueryBuilderOptions, }) ).toBeUndefined(); expect( @@ -396,7 +397,7 @@ describe('ClickHouseDatasource', () => { builderOptions: { ...query.builderOptions, timeField: undefined, - } as SqlBuilderOptionsList, + } as QueryBuilderOptions, }) ).toBeUndefined(); }); @@ -410,7 +411,7 @@ describe('ClickHouseDatasource', () => { builderOptions: { ...query.builderOptions, logLevelField: undefined, - } as SqlBuilderOptionsList, + } as QueryBuilderOptions, }); expect(result?.rawSql).toEqual( 'SELECT toStartOfInterval("created_at", INTERVAL 1 DAY) AS time, count(*) logs ' + diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index 47817c75..be3bc3fd 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -17,16 +17,18 @@ import { import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime'; import { Observable } from 'rxjs'; import { CHConfig } from 'types/config'; -import { EditorType, QueryType, CHQuery } from 'types/sql'; +import { EditorType, CHQuery } from 'types/sql'; import { - BuilderMetricField, - BuilderMetricFieldAggregation, + QueryType, + AggregateColumn, + AggregateType, BuilderMode, Filter, FilterOperator, - FullField, + TableColumn, OrderByDirection, - SqlBuilderOptionsAggregate, + QueryBuilderOptions, + ColumnHint, } from 'types/queryBuilder'; import { AdHocFilter } from './adHocFilter'; import { cloneDeep, isEmpty, isString } from 'lodash'; @@ -38,7 +40,7 @@ import { queryLogsVolume, TIME_FIELD_ALIAS, } from './logs'; -import { getSQLFromQueryOptions } from '../components/queryBuilder/utils'; +import { getSQLFromQueryOptions, getColumnByHint } from '../components/queryBuilder/utils'; export class Datasource extends DataSourceWithBackend @@ -115,41 +117,47 @@ export class Datasource query.editorType !== EditorType.Builder || query.queryType !== QueryType.Logs || query.builderOptions.mode !== BuilderMode.List || - query.builderOptions.timeField === undefined || + getColumnByHint(query.builderOptions, ColumnHint.Time) === undefined || query.builderOptions.database === undefined || query.builderOptions.table === undefined ) { return undefined; } + const timeColumn = getColumnByHint(query.builderOptions, ColumnHint.Time)!; + const timeFieldRoundingClause = getTimeFieldRoundingClause( logsVolumeRequest.scopedVars, - query.builderOptions.timeField + timeColumn.name ); const fields: string[] = []; - const metrics: BuilderMetricField[] = []; + const aggregates: AggregateColumn[] = []; // could be undefined or an empty string (if user deselects the field) - if (query.builderOptions.logLevelField) { + const logLevelColumn = getColumnByHint(query.builderOptions, ColumnHint.LogLevel); + if (logLevelColumn) { // Generate "fields" like // sum(toString("log_level") IN ('dbug', 'debug', 'DBUG', 'DEBUG', 'Dbug', 'Debug')) AS debug - const llf = `toString("${query.builderOptions.logLevelField}")`; + const llf = `toString("${logLevelColumn.name}")`; let level: keyof typeof LOG_LEVEL_TO_IN_CLAUSE; for (level in LOG_LEVEL_TO_IN_CLAUSE) { fields.push(`sum(${llf} ${LOG_LEVEL_TO_IN_CLAUSE[level]}) AS ${level}`); } } else { - metrics.push({ - aggregation: BuilderMetricFieldAggregation.Count, + aggregates.push({ + aggregateType: AggregateType.Count, + column: '*', alias: DEFAULT_LOGS_ALIAS, - field: '*', }); } - const logVolumeSqlBuilderOptions: SqlBuilderOptionsAggregate = { + const logVolumeSqlBuilderOptions: QueryBuilderOptions = { + database: query.builderOptions.database, + table: query.builderOptions.table, + queryType: QueryType.TimeSeries, mode: BuilderMode.Aggregate, filters: query.builderOptions.filters, - fields, - metrics, + columns: fields.map(f => ({ name: f })), + aggregates, groupBy: [`${timeFieldRoundingClause} AS ${TIME_FIELD_ALIAS}`], orderBy: [ { @@ -159,14 +167,11 @@ export class Datasource ], }; - const logVolumeSupplementaryQuery = getSQLFromQueryOptions(query.database, query.table, logVolumeSqlBuilderOptions); + const logVolumeSupplementaryQuery = getSQLFromQueryOptions(query.builderOptions.database, query.builderOptions.table, logVolumeSqlBuilderOptions); return { // format: Format.AUTO, // selectedFormat: Format.AUTO, editorType: EditorType.SQL, - queryType: QueryType.Table, - database: query.database, - table: query.table, rawSql: logVolumeSupplementaryQuery, refId: '', }; @@ -308,7 +313,7 @@ export class Datasource return { ...query, // the query is updated to trigger the URL update and propagation to the panels - rawSql: getSQLFromQueryOptions(query.database, query.table, updatedBuilder), + rawSql: getSQLFromQueryOptions(query.builderOptions.database, query.builderOptions.table, updatedBuilder), builderOptions: updatedBuilder, }; } @@ -376,7 +381,7 @@ export class Datasource return this.fetchData(`DESC TABLE "${database}"."${table}"`); } - async fetchFieldsFull(database: string | undefined, table: string): Promise { + async fetchColumnsFull(database: string | undefined, table: string): Promise { const prefix = Boolean(database) ? `"${database}".` : ''; const rawSql = `DESC TABLE ${prefix}"${table}"`; const frame = await this.runQuery({ rawSql }); @@ -384,7 +389,7 @@ export class Datasource return []; } const view = new DataFrameView(frame); - return view.map((item) => ({ + return view.map(item => ({ name: item[0], type: item[1], label: item[0], diff --git a/src/hooks/useTableColumns.tsx b/src/hooks/useColumns.tsx similarity index 77% rename from src/hooks/useTableColumns.tsx rename to src/hooks/useColumns.tsx index 4f12deed..ed165d49 100644 --- a/src/hooks/useTableColumns.tsx +++ b/src/hooks/useColumns.tsx @@ -1,16 +1,16 @@ import { useState, useEffect } from 'react'; -import { FullField } from 'types/queryBuilder'; +import { TableColumn } from 'types/queryBuilder'; import { Datasource } from 'data/CHDatasource'; const allColumn = { name: '*', label: 'ALL', type: 'string', picklistValues: [] }; -export default (datasource: Datasource, database: string, table: string): FullField[] => { - const [columns, setColumns] = useState([allColumn]); +export default (datasource: Datasource, database: string, table: string): TableColumn[] => { + const [columns, setColumns] = useState([allColumn]); useEffect(() => { const fetchTableColumns = async () => { datasource - .fetchFieldsFull(database, table) + .fetchColumnsFull(database, table) .then(columns => { columns.push(allColumn); setColumns(columns); diff --git a/src/hooks/useDatabases.tsx b/src/hooks/useDatabases.tsx index 4bbdb0d4..043ba20a 100644 --- a/src/hooks/useDatabases.tsx +++ b/src/hooks/useDatabases.tsx @@ -5,7 +5,7 @@ export default (datasource: Datasource): string[] => { const [databases, setDatabases] = useState([]); useEffect(() => { - const fetchDatabaseDatabases = async () => { + const fetchDatabases = async () => { datasource. fetchDatabases(). then(databases => setDatabases(databases)). @@ -16,7 +16,7 @@ export default (datasource: Datasource): string[] => { }; if (datasource) { - fetchDatabaseDatabases(); + fetchDatabases(); } }, [datasource]); diff --git a/src/hooks/useDatabaseTables.tsx b/src/hooks/useTables.tsx similarity index 50% rename from src/hooks/useDatabaseTables.tsx rename to src/hooks/useTables.tsx index 53658c8b..5e772456 100644 --- a/src/hooks/useDatabaseTables.tsx +++ b/src/hooks/useTables.tsx @@ -5,19 +5,17 @@ export default (datasource: Datasource, database: string): string[] => { const [tables, setTables] = useState([]); useEffect(() => { - const fetchDatabaseTables = async () => { - datasource. - fetchTables(database). - then(tables => setTables(tables)). - catch((ex: any) => { - console.error(ex); - throw ex; - }); - }; + if (!database) { + return; + } - if (database) { - fetchDatabaseTables(); - } + datasource. + fetchTables(database). + then(tables => setTables(tables)). + catch((ex: any) => { + console.error(ex); + throw ex; + }); }, [datasource, database]); return tables; diff --git a/src/styles.ts b/src/styles.ts index 032eb7ce..16a0f59c 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -55,6 +55,9 @@ export const styles = { display: flex; } `, + inlineField: css` + margin-left: 7px; + ` }, FormatSelector: { formatSelector: css` diff --git a/src/tracking.test.ts b/src/tracking.test.ts index e126d417..95021b38 100644 --- a/src/tracking.test.ts +++ b/src/tracking.test.ts @@ -1,5 +1,6 @@ import { analyzeQueries } from 'tracking'; -import { CHQuery, EditorType, QueryType } from 'types/sql'; +import { CHQuery, EditorType } from 'types/sql'; +import { QueryType } from 'types/queryBuilder'; import { BuilderMode } from 'types/queryBuilder'; describe('analyzeQueries', () => { diff --git a/src/tracking.ts b/src/tracking.ts index c5552ddf..a62a167c 100644 --- a/src/tracking.ts +++ b/src/tracking.ts @@ -1,6 +1,6 @@ import { reportInteraction } from '@grafana/runtime'; -import { CHQuery, EditorType, QueryType } from 'types/sql'; -import { BuilderMode } from 'types/queryBuilder'; +import { CHQuery, EditorType } from 'types/sql'; +import { QueryType, BuilderMode } from 'types/queryBuilder'; // TODO: v4, determine new/updated fields to track diff --git a/src/types/queryBuilder.ts b/src/types/queryBuilder.ts index 9a6320ae..57e2a8b2 100644 --- a/src/types/queryBuilder.ts +++ b/src/types/queryBuilder.ts @@ -10,22 +10,46 @@ export enum BuilderMode { } /** - * @property {string} timeField Explore only: used for Logs Volume histogram - * @property {string} logLevelField Explore only: used for Logs Volume histogram + * QueryType determines the display/query format. */ -export interface SqlBuilderOptionsList { - mode: BuilderMode.List; - database?: string; - table?: string; - fields?: string[]; +export enum QueryType { + Table = 'table', + Logs = 'logs', + TimeSeries = 'timeseries', + Traces = 'traces', +} + +export interface QueryBuilderOptions { + database: string; + table: string; + queryType: QueryType; + + mode?: BuilderMode; // TODO: no longer required? + + columns?: SelectedColumn[]; + aggregates?: AggregateColumn[]; filters?: Filter[]; + groupBy?: string[]; orderBy?: OrderBy[]; limit?: number; - timeField?: string; - logLevelField?: string; + + /** + * Contains metadata for editor-specific use cases. + */ + meta?: { + // Logs + otelEnabled?: boolean; + otelVersion?: string; + liveView?: boolean; + + // Trace + isTraceSearchMode?: boolean; + traceDurationUnit?: string; + traceId?: string; // TODO: this doesn't need to be persisted? + } } -export enum BuilderMetricFieldAggregation { +export enum AggregateType { Sum = 'sum', Average = 'avg', Min = 'min', @@ -35,36 +59,12 @@ export enum BuilderMetricFieldAggregation { // Count_Distinct = 'count_distinct', } -export type BuilderMetricField = { - field: string; - aggregation: BuilderMetricFieldAggregation; +export type AggregateColumn = { + aggregateType: AggregateType; + column: string; alias?: string; } -export interface SqlBuilderOptionsAggregate { - mode: BuilderMode.Aggregate; - fields: string[]; - metrics: BuilderMetricField[]; - groupBy?: string[]; - filters?: Filter[]; - orderBy?: OrderBy[]; - limit?: number; -} - -export interface SqlBuilderOptionsTrend { - mode: BuilderMode.Trend; - fields: string[]; - metrics: BuilderMetricField[]; - filters?: Filter[]; - groupBy?: string[]; - timeField: string; - timeFieldType: string; - orderBy?: OrderBy[]; - limit?: number; -} - -export type SqlBuilderOptions = SqlBuilderOptionsList | SqlBuilderOptionsAggregate | SqlBuilderOptionsTrend; - export interface Field { name: string; type: string; @@ -80,22 +80,58 @@ export interface FullEntity { queryable: boolean; } -interface FullFieldPickListItem { - value: string; +interface TableColumnPickListItem { label: string; + value: string; } -export interface FullField { +/** + * Represents a column retrieved from ClickHouse + */ +export interface TableColumn { name: string; label: string; type: string; - picklistValues: FullFieldPickListItem[]; + picklistValues: TableColumnPickListItem[]; filterable?: boolean; sortable?: boolean; groupable?: boolean; aggregatable?: boolean; } +/** + * Some columns are used to enable certain features. + * This enum defines the different use cases that a column may be used for in the query generator. + * For example, "Time" would be used to identify the primary time column for a time series. + */ +export enum ColumnHint { + Time = 'time', + + LogLevel = 'log_level', + LogMessage = 'log_message', + + TraceId = 'trace_id', + TraceSpanId = 'trace_span_id', + TraceParentSpanId = 'trace_parent_span_id', + TraceServiceName = 'trace_service_name', + TraceOperationName = 'trace_operation_name', + TraceStartTime = 'trace_start_time', + TraceDurationTime = 'trace_duration_time', + TraceTags = 'trace_tags', + TraceServiceTags = 'trace_service_tags', +} + +/** + * Represents a column selection, including metadata for the query generator to use. + */ +export interface SelectedColumn { + name: string; + type?: string; + alias?: string; + custom?: boolean; + hint?: ColumnHint; +} + export enum OrderByDirection { ASC = 'ASC', DESC = 'DESC', diff --git a/src/types/sql.ts b/src/types/sql.ts index 2518115b..6ffa65f6 100644 --- a/src/types/sql.ts +++ b/src/types/sql.ts @@ -1,5 +1,5 @@ import { DataQuery } from '@grafana/schema'; -import { BuilderMode, SqlBuilderOptions } from './queryBuilder'; +import { BuilderMode, QueryType, QueryBuilderOptions } from './queryBuilder'; /** * EditorType determines the query editor type. @@ -9,23 +9,8 @@ export enum EditorType { Builder = 'builder', } -/** - * QueryType determines the display/query format. - */ -export enum QueryType { - Table = 'table', - Logs = 'logs', - TimeSeries = 'timeSeries', - Traces = 'traces', -} - export interface CHQueryBase extends DataQuery { editorType: EditorType; - queryType: QueryType; - database: string; - table: string; - selectedQueryType?: QueryType; - rawSql: string; } @@ -34,14 +19,14 @@ export interface CHSqlQuery extends CHQueryBase { meta?: { timezone?: string; // meta fields to be used just for building builder options when migrating back to EditorType.Builder - builderOptions?: SqlBuilderOptions; + builderOptions?: QueryBuilderOptions; }; expand?: boolean; } export interface CHBuilderQuery extends CHQueryBase { editorType: EditorType.Builder; - builderOptions: SqlBuilderOptions; + builderOptions: QueryBuilderOptions; meta?: { timezone?: string; }; @@ -53,13 +38,13 @@ export type CHQuery = CHSqlQuery | CHBuilderQuery; export const defaultEditorType: EditorType = EditorType.Builder; export const defaultCHBuilderQuery: Omit = { editorType: EditorType.Builder, - queryType: QueryType.Table, - database: '', - table: '', rawSql: '', builderOptions: { + database: '', + table: '', + queryType: QueryType.Table, mode: BuilderMode.List, - fields: [], + columns: [], limit: 100, }, // format: Format.TABLE, @@ -67,11 +52,6 @@ export const defaultCHBuilderQuery: Omit = { }; export const defaultCHSqlQuery: Omit = { editorType: EditorType.SQL, - queryType: QueryType.Table, - database: '', - table: '', rawSql: '', - // format: Format.TABLE, - // selectedFormat: Format.AUTO, expand: false, }; diff --git a/src/v4/selectors.ts b/src/v4/selectors.ts index 91668f1c..551458c2 100644 --- a/src/v4/selectors.ts +++ b/src/v4/selectors.ts @@ -21,10 +21,12 @@ export default { DatabaseSelect: { label: 'Database', tooltip: 'ClickHouse database to query from', + empty: '', }, ColumnsEditor: { label: 'Columns', @@ -38,6 +40,26 @@ export default { label: 'Limit', tooltip: 'Limits the number of rows returned by the query' }, + SqlPreview: { + label: 'SQL Preview', + tooltip: 'Preview of the generated SQL. You can safely switch to SQL Editor to customize the generated query', + }, + AggregatesEditor: { + label: 'Aggregates', + tooltip: 'Aggregate functions to use', + aliasLabel: 'as', + aliasTooltip: 'alias for this aggregate function', + addLabel: 'Aggregate', + }, + OrderByEditor: { + label: 'Order By', + tooltip: 'Order by column', + addLabel: 'Order By', + }, + GroupByEditor: { + label: 'Group By', + tooltip: 'Group the results by specific column', + }, LogsQueryBuilder: { logTimeColumn: { label: 'Time', @@ -60,7 +82,66 @@ export default { simpleQueryModeLabel: 'Simple', aggregateQueryModeLabel: 'Aggregate', builderModeLabel: 'Builder Mode', - builderModeTooltip: 'Switches the query builder between the simple and aggregate modes' + builderModeTooltip: 'Switches the query builder between the simple and aggregate modes', + timeColumn: { + label: 'Time', + tooltip: 'Column to use for the time series' + }, + }, + TableQueryBuilder: { + simpleQueryModeLabel: 'Simple', + aggregateQueryModeLabel: 'Aggregate', + builderModeLabel: 'Builder Mode', + builderModeTooltip: 'Switches the query builder between the simple and aggregate modes', + }, + TraceQueryBuilder: { + traceIdModeLabel: 'Trace ID', + traceSearchModeLabel: 'Trace Search', + traceModeLabel: 'Trace Mode', + traceModeTooltip: 'Switches between trace ID and trace search mode', + + fields: { + traceId: { + label: 'Trace ID Column', + tooltip: 'Column that contains the trace ID' + }, + spanId: { + label: 'Span ID Column', + tooltip: 'Column that contains the span ID' + }, + parentSpanId: { + label: 'Parent Span ID Column', + tooltip: 'Column that contains the parent span ID' + }, + serviceName: { + label: 'Service Name Column', + tooltip: 'Column that contains the service name' + }, + operationName: { + label: 'Operation Name Column', + tooltip: 'Column that contains the operation name' + }, + startTime: { + label: 'Start Time Column', + tooltip: 'Column that contains the start time' + }, + durationTime: { + label: 'Duration Time Column', + tooltip: 'Column that contains the duration time' + }, + durationUnit: { + label: 'Duration Unit', + tooltip: 'The unit of time used for the duration time' + }, + tags: { + label: 'Tags Column', + tooltip: 'Column that contains the trace tags' + }, + serviceTags: { + label: 'Service Tags Column', + tooltip: 'Column that contains the service tags' + } + }, } }, types: { diff --git a/src/views/v4/CHQueryEditor.tsx b/src/views/v4/CHQueryEditor.tsx index d16e12dc..33e8714a 100644 --- a/src/views/v4/CHQueryEditor.tsx +++ b/src/views/v4/CHQueryEditor.tsx @@ -1,18 +1,15 @@ import React from 'react'; import { QueryEditorProps } from '@grafana/data'; import { Datasource } from '../../data/CHDatasource'; -import { Preview } from 'components/queryBuilder/Preview'; import { EditorTypeSwitcher } from 'components/v4/EditorTypeSwitcher'; -import { QueryTypeSwitcher } from 'components/v4/QueryTypeSwitcher'; import { styles } from 'styles'; import { Button } from '@grafana/ui'; -import { CHQuery, EditorType, QueryType, CHBuilderQuery, defaultCHBuilderQuery } from 'types/sql'; +import { CHQuery, EditorType, CHBuilderQuery, defaultCHBuilderQuery } from 'types/sql'; import { CHConfig } from 'types/config'; -import { SqlBuilderOptions } from 'types/queryBuilder'; +import { QueryBuilderOptions } from 'types/queryBuilder'; import { SqlEditor } from 'components/v4/SqlEditor'; import { QueryBuilder } from 'components/v4/queryBuilder/QueryBuilder'; import { getSQLFromQueryOptions } from 'components/queryBuilder/utils'; -import { DatabaseTableSelect } from 'components/v4/DatabaseTableSelect'; export type CHQueryEditorProps = QueryEditorProps; @@ -20,16 +17,7 @@ export type CHQueryEditorProps = QueryEditorProps * Top level query editor component */ export const CHQueryEditor = (props: CHQueryEditorProps) => { - const { datasource, query, onChange, onRunQuery } = props; - - React.useEffect(() => { - if (!query.queryType) { - onChange({ ...query, queryType: QueryType.Table }); - } - if (!query.database) { - onChange({ ...query, database: datasource.getDefaultDatabase() || 'default' }); - } - }, [query, onChange, datasource]); + const { query, onRunQuery } = props; const runQuery = () => { if (query.editorType === EditorType.SQL) { @@ -42,9 +30,6 @@ export const CHQueryEditor = (props: CHQueryEditorProps) => { onRunQuery(); }; - const onDatabaseChange = (db: string) => onChange({ ...query, database: db }); - const onTableChange = (t: string) => onChange({ ...query, table: t }); - // const onFormatChange = (selectedFormat: Format) => { // switch (query.queryType) { // case QueryType.SQL: @@ -67,16 +52,6 @@ export const CHQueryEditor = (props: CHQueryEditorProps) => {
-
- -
-
- onChange({ ...query, queryType: t })} /> -
); @@ -84,8 +59,8 @@ export const CHQueryEditor = (props: CHQueryEditorProps) => { const CHEditorByType = (props: CHQueryEditorProps) => { const { query, onChange, app } = props; - const onBuilderOptionsChange = (builderOptions: SqlBuilderOptions) => { - const sql = getSQLFromQueryOptions(query.database, query.table, builderOptions); + const onBuilderOptionsChange = (builderOptions: QueryBuilderOptions) => { + const sql = getSQLFromQueryOptions(builderOptions.database, builderOptions.table, builderOptions); onChange({ ...query, editorType: EditorType.Builder, rawSql: sql, builderOptions }); }; @@ -106,6 +81,7 @@ const CHEditorByType = (props: CHQueryEditorProps) => {
); } + if (!query.rawSql || !query.builderOptions) { newQuery = { ...newQuery, @@ -115,19 +91,15 @@ const CHEditorByType = (props: CHQueryEditorProps) => { }, }; } + return ( -
- - -
+ ); } }; From d5fd4016eb8e0d99db4b6a327eb9cab1189bc6cb Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sun, 10 Sep 2023 23:30:15 -0400 Subject: [PATCH 04/95] move v4 components into original directories --- package.json | 2 +- src/components/FormatSelect.test.tsx | 11 - src/components/FormatSelect.tsx | 48 -- src/components/QueryTypeSwitcher._tsx | 99 ---- src/components/SQLEditor._tsx | 139 ------ ...SQLEditor.test._tsx => SqlEditor.test.tsx} | 14 +- src/components/{v4 => }/SqlEditor.tsx | 5 +- src/components/editor.ts | 23 - ...rics.test.tsx => AggregateEditor.test.tsx} | 2 +- .../{v4 => }/queryBuilder/AggregateEditor.tsx | 6 +- .../queryBuilder/ColumnSelect.test.tsx | 14 + .../{v4 => }/queryBuilder/ColumnSelect.tsx | 0 ...Fields.test.tsx => ColumnsEditor.test.tsx} | 0 .../{v4 => }/queryBuilder/ColumnsEditor.tsx | 4 +- .../queryBuilder/DatabaseSelect.tsx | 44 -- ....test.tsx => DatabaseTableSelect.test.tsx} | 0 .../DatabaseTableSelect.tsx | 6 +- .../EditorTypeSwitcher.tsx | 8 +- src/components/queryBuilder/Fields.tsx | 84 ---- ...Filters.test.tsx => FilterEditor.test.tsx} | 2 +- .../{v4 => }/queryBuilder/FilterEditor.tsx | 14 +- src/components/queryBuilder/Filters.tsx | 444 ------------------ src/components/queryBuilder/GroupBy.tsx | 45 -- ...roupBy.test.tsx => GroupByEditor.test.tsx} | 0 .../{v4 => }/queryBuilder/GroupByEditor.tsx | 4 +- src/components/queryBuilder/Limit.tsx | 27 -- .../{Limit.test.tsx => LimitEditor.test.tsx} | 0 .../{v4 => }/queryBuilder/LimitEditor.tsx | 4 +- src/components/queryBuilder/LogLevelField.tsx | 33 -- src/components/queryBuilder/Metrics.tsx | 147 ------ ...deEditor.test.tsx => ModeEditor.test._tsx} | 2 +- src/components/queryBuilder/ModeEditor.tsx | 25 - .../{v4 => }/queryBuilder/ModeSwitch.tsx | 0 src/components/queryBuilder/OrderBy.tsx | 189 -------- ...rderBy.test.tsx => OrderByEditor.test.tsx} | 128 ++--- .../{v4 => }/queryBuilder/OrderByEditor.tsx | 4 +- .../queryBuilder/OtelVersionSelect.tsx | 4 +- src/components/queryBuilder/Preview.tsx | 17 - src/components/queryBuilder/QueryBuilder._tsx | 271 ----------- .../{v4 => }/queryBuilder/QueryBuilder.tsx | 6 +- .../QueryTypeSwitcher.test._tsx | 7 +- .../QueryTypeSwitcher.tsx | 12 +- .../{Preview.test.tsx => SqlPreview.test.tsx} | 6 +- .../{v4 => }/queryBuilder/SqlPreview.tsx | 4 +- .../{v4 => }/queryBuilder/Switch.tsx | 0 src/components/queryBuilder/TableSelect.tsx | 53 --- .../queryBuilder/TimeField.test.tsx | 19 - src/components/queryBuilder/TimeField.tsx | 39 -- src/components/queryBuilder/utils.spec.ts | 30 +- src/components/queryBuilder/utils.ts | 1 + .../queryBuilder/views/LogsQueryBuilder.tsx | 20 +- .../queryBuilder/views/TableQueryBuilder.tsx | 12 +- .../views/TimeSeriesQueryBuilder.tsx | 16 +- .../queryBuilder/views/TraceQueryBuilder.tsx | 48 +- src/{v4/selectors.ts => labels.ts} | 5 + src/module.ts | 6 +- src/{v4/otel.tsx => otel.ts} | 0 src/types.ts | 239 ---------- src/views/CHConfigEditor.test.tsx | 4 +- src/views/CHConfigEditor.tsx | 4 +- src/views/CHQueryEditor._tsx | 130 ----- ...ditor.test._tsx => CHQueryEditor.test.tsx} | 4 +- src/views/CHQueryEditor.tsx | 76 +++ src/views/v4/CHQueryEditor.tsx | 105 ----- 64 files changed, 296 insertions(+), 2419 deletions(-) delete mode 100644 src/components/FormatSelect.test.tsx delete mode 100644 src/components/FormatSelect.tsx delete mode 100644 src/components/QueryTypeSwitcher._tsx delete mode 100644 src/components/SQLEditor._tsx rename src/components/{SQLEditor.test._tsx => SqlEditor.test.tsx} (80%) rename src/components/{v4 => }/SqlEditor.tsx (96%) delete mode 100644 src/components/editor.ts rename src/components/queryBuilder/{Metrics.test.tsx => AggregateEditor.test.tsx} (92%) rename src/components/{v4 => }/queryBuilder/AggregateEditor.tsx (96%) create mode 100644 src/components/queryBuilder/ColumnSelect.test.tsx rename src/components/{v4 => }/queryBuilder/ColumnSelect.tsx (100%) rename src/components/queryBuilder/{Fields.test.tsx => ColumnsEditor.test.tsx} (100%) rename src/components/{v4 => }/queryBuilder/ColumnsEditor.tsx (96%) delete mode 100644 src/components/queryBuilder/DatabaseSelect.tsx rename src/components/queryBuilder/{TableSelect.test.tsx => DatabaseTableSelect.test.tsx} (100%) rename src/components/{v4 => queryBuilder}/DatabaseTableSelect.tsx (95%) rename src/components/{v4 => queryBuilder}/EditorTypeSwitcher.tsx (93%) delete mode 100644 src/components/queryBuilder/Fields.tsx rename src/components/queryBuilder/{Filters.test.tsx => FilterEditor.test.tsx} (99%) rename src/components/{v4 => }/queryBuilder/FilterEditor.tsx (97%) delete mode 100644 src/components/queryBuilder/Filters.tsx delete mode 100644 src/components/queryBuilder/GroupBy.tsx rename src/components/queryBuilder/{GroupBy.test.tsx => GroupByEditor.test.tsx} (100%) rename src/components/{v4 => }/queryBuilder/GroupByEditor.tsx (93%) delete mode 100644 src/components/queryBuilder/Limit.tsx rename src/components/queryBuilder/{Limit.test.tsx => LimitEditor.test.tsx} (100%) rename src/components/{v4 => }/queryBuilder/LimitEditor.tsx (87%) delete mode 100644 src/components/queryBuilder/LogLevelField.tsx delete mode 100644 src/components/queryBuilder/Metrics.tsx rename src/components/queryBuilder/{ModeEditor.test.tsx => ModeEditor.test._tsx} (87%) delete mode 100644 src/components/queryBuilder/ModeEditor.tsx rename src/components/{v4 => }/queryBuilder/ModeSwitch.tsx (100%) delete mode 100644 src/components/queryBuilder/OrderBy.tsx rename src/components/queryBuilder/{OrderBy.test.tsx => OrderByEditor.test.tsx} (72%) rename src/components/{v4 => }/queryBuilder/OrderByEditor.tsx (98%) rename src/components/{v4 => }/queryBuilder/OtelVersionSelect.tsx (95%) delete mode 100644 src/components/queryBuilder/Preview.tsx delete mode 100644 src/components/queryBuilder/QueryBuilder._tsx rename src/components/{v4 => }/queryBuilder/QueryBuilder.tsx (92%) rename src/components/{ => queryBuilder}/QueryTypeSwitcher.test._tsx (90%) rename src/components/{v4 => queryBuilder}/QueryTypeSwitcher.tsx (77%) rename src/components/queryBuilder/{Preview.test.tsx => SqlPreview.test.tsx} (58%) rename src/components/{v4 => }/queryBuilder/SqlPreview.tsx (80%) rename src/components/{v4 => }/queryBuilder/Switch.tsx (100%) delete mode 100644 src/components/queryBuilder/TableSelect.tsx delete mode 100644 src/components/queryBuilder/TimeField.test.tsx delete mode 100644 src/components/queryBuilder/TimeField.tsx rename src/components/{v4 => }/queryBuilder/views/LogsQueryBuilder.tsx (90%) rename src/components/{v4 => }/queryBuilder/views/TableQueryBuilder.tsx (92%) rename src/components/{v4 => }/queryBuilder/views/TimeSeriesQueryBuilder.tsx (91%) rename src/components/{v4 => }/queryBuilder/views/TraceQueryBuilder.tsx (83%) rename src/{v4/selectors.ts => labels.ts} (97%) rename src/{v4/otel.tsx => otel.ts} (100%) delete mode 100644 src/types.ts delete mode 100644 src/views/CHQueryEditor._tsx rename src/views/{CHQueryEditor.test._tsx => CHQueryEditor.test.tsx} (88%) create mode 100644 src/views/CHQueryEditor.tsx delete mode 100644 src/views/v4/CHQueryEditor.tsx diff --git a/package.json b/package.json index 85b180c7..85acc823 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clickhouse-datasource", - "version": "3.3.0", + "version": "4.0.0", "description": "Clickhouse Datasource", "engines": { "node": ">=16" diff --git a/src/components/FormatSelect.test.tsx b/src/components/FormatSelect.test.tsx deleted file mode 100644 index b12c23f9..00000000 --- a/src/components/FormatSelect.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { FormatSelect } from './FormatSelect'; -import { Format } from '../types'; - -describe('FormatSelect', () => { - it('renders a format', () => { - const result = render( {}} />); - expect(result.container.firstChild).not.toBeNull(); - }); -}); diff --git a/src/components/FormatSelect.tsx b/src/components/FormatSelect.tsx deleted file mode 100644 index c9e65ff1..00000000 --- a/src/components/FormatSelect.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { InlineFormLabel, Select } from '@grafana/ui'; -import { selectors } from './../selectors'; -import { Format } from '../types'; -import { styles } from '../styles'; - -export type Props = { format: Format; value?: string; onChange: (format: Format) => void }; - -export const FormatSelect = (props: Props) => { - const { onChange, format } = props; - const { label, tooltip, options: formatLabels } = selectors.components.QueryEditor.Format; - return ( -
- - {label} - - - className={`width-8 ${styles.Common.inlineSelect}`} - onChange={(e) => onChange(e.value!)} - options={[ - { - label: formatLabels.AUTO, - value: Format.AUTO, - }, - { - label: formatLabels.TABLE, - value: Format.TABLE, - }, - { - label: formatLabels.TIME_SERIES, - value: Format.TIMESERIES, - }, - { - label: formatLabels.LOGS, - value: Format.LOGS, - }, - { - label: formatLabels.TRACE, - value: Format.TRACE, - }, - ]} - value={format} - menuPlacement={'bottom'} - allowCustomValue={false} - /> -
- ); -}; diff --git a/src/components/QueryTypeSwitcher._tsx b/src/components/QueryTypeSwitcher._tsx deleted file mode 100644 index 79640f84..00000000 --- a/src/components/QueryTypeSwitcher._tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React, { useState } from 'react'; -import { SelectableValue } from '@grafana/data'; -import { RadioButtonGroup, ConfirmModal, InlineFormLabel } from '@grafana/ui'; -import { getQueryOptionsFromSql, getSQLFromQueryOptions } from './queryBuilder/utils'; -import { selectors } from './../selectors'; -import { CHQuery, QueryType, defaultCHBuilderQuery, SqlBuilderOptions, CHSQLQuery } from 'types'; -import { isString } from 'lodash'; - -interface QueryTypeSwitcherProps { - query: CHQuery; - onChange: (query: CHQuery) => void; - onRunQuery: () => void; -} - -export const QueryTypeSwitcher = (props: QueryTypeSwitcherProps) => { - const { query, onChange } = props; - const { label, tooltip, options: queryTypeLabels, switcher, cannotConvert } = selectors.components.QueryEditor.Types; - let queryType: QueryType = - query.queryType || - ((query as CHSQLQuery).rawSql && !(query as CHQuery).queryType ? QueryType.SQL : QueryType.Builder); - const [editor, setEditor] = useState(queryType); - const [confirmModalState, setConfirmModalState] = useState(false); - const [cannotConvertModalState, setCannotConvertModalState] = useState(false); - const options: Array> = [ - { label: queryTypeLabels.SQLEditor, value: QueryType.SQL }, - { label: queryTypeLabels.QueryBuilder, value: QueryType.Builder }, - ]; - const [errorMessage, setErrorMessage] = useState(''); - const onQueryTypeChange = (queryType: QueryType, confirm = false) => { - if (query.queryType === QueryType.SQL && queryType === QueryType.Builder && !confirm) { - const queryOptionsFromSql = getQueryOptionsFromSql(query.rawSql); - if (isString(queryOptionsFromSql)) { - setCannotConvertModalState(true); - setErrorMessage(queryOptionsFromSql); - } else { - setConfirmModalState(true); - } - } else { - setEditor(queryType); - let builderOptions: SqlBuilderOptions; - switch (query.queryType) { - case QueryType.Builder: - builderOptions = query.builderOptions; - break; - case QueryType.SQL: - builderOptions = - (getQueryOptionsFromSql(query.rawSql) as SqlBuilderOptions) || defaultCHBuilderQuery.builderOptions; - break; - default: - builderOptions = defaultCHBuilderQuery.builderOptions; - break; - } - if (queryType === QueryType.SQL) { - onChange({ - ...query, - queryType, - rawSql: getSQLFromQueryOptions(builderOptions), - meta: { builderOptions }, - format: query.format, - selectedFormat: query.selectedFormat, - }); - } else if (queryType === QueryType.Builder) { - onChange({ ...query, queryType, rawSql: getSQLFromQueryOptions(builderOptions), builderOptions }); - } - } - }; - const onConfirmQueryTypeChange = () => { - onQueryTypeChange(QueryType.Builder, true); - setConfirmModalState(false); - setCannotConvertModalState(false); - }; - return ( - <> - - {label} - - onQueryTypeChange(e!)} /> - setConfirmModalState(false)} - /> - setCannotConvertModalState(false)} - /> - - ); -}; diff --git a/src/components/SQLEditor._tsx b/src/components/SQLEditor._tsx deleted file mode 100644 index aaf55dc5..00000000 --- a/src/components/SQLEditor._tsx +++ /dev/null @@ -1,139 +0,0 @@ -import React, { useState } from 'react'; -import { QueryEditorProps } from '@grafana/data'; -import { CodeEditor } from '@grafana/ui'; -import { Datasource } from '../data/CHDatasource'; -import { registerSQL, Range, Fetcher } from './sqlProvider'; -import { CHQuery, CHConfig, QueryType, CHSQLQuery } from '../types'; -import { styles } from '../styles'; -import { fetchSuggestions as sugg, Schema } from './suggestions'; -import { selectors } from 'selectors'; -import { getFormat } from './editor'; -import { validate } from 'data/validate'; - -type SQLEditorProps = QueryEditorProps; - -interface Expand { - height: string; - icon: 'plus' | 'minus'; - on: boolean; -} - -export const SQLEditor = (props: SQLEditorProps) => { - const defaultHeight = '150px'; - const { query, onRunQuery, onChange, datasource } = props; - const [codeEditor, setCodeEditor] = useState(); - const [expand, setExpand] = useState({ - height: defaultHeight, - icon: 'plus', - on: (query as CHSQLQuery).expand || false, - }); - - const onSqlChange = (sql: string) => { - const format = getFormat(sql, query.selectedFormat); - onChange({ ...query, rawSql: sql, format, queryType: QueryType.SQL }); - onRunQuery(); - }; - - const onToggleExpand = () => { - const sqlQuery = query as CHSQLQuery; - const on = !expand.on; - const icon = on ? 'minus' : 'plus'; - onChange({ ...sqlQuery, expand: on }); - - if (!codeEditor) { - return; - } - if (on) { - codeEditor.expanded = true; - const height = getEditorHeight(codeEditor); - setExpand({ height: `${height}px`, on, icon }); - return; - } - - codeEditor.expanded = false; - setExpand({ height: defaultHeight, icon, on }); - }; - - const schema: Schema = { - databases: () => datasource.fetchDatabases(), - tables: (db?: string) => datasource.fetchTables(db), - fields: (db: string, table: string) => datasource.fetchFields(db, table), - defaultDatabase: datasource.getDefaultDatabase(), - }; - - const fetchSuggestions: Fetcher = async (text: string, range: Range) => { - const suggestions = await sugg(text, schema, range); - return Promise.resolve({ suggestions }); - }; - - const validateSql = (sql: string, model: any, me: any) => { - const v = validate(sql); - const errorSeverity = 8; - if (v.valid) { - me.setModelMarkers(model, 'clickhouse', []); - } else { - const err = v.error!; - me.setModelMarkers(model, 'clickhouse', [ - { - startLineNumber: err.startLine, - startColumn: err.startCol, - endLineNumber: err.endLine, - endColumn: err.endCol, - message: err.expected, - severity: errorSeverity, - }, - ]); - } - }; - - const handleMount = (editor: any) => { - const me = registerSQL('chSql', editor, fetchSuggestions); - editor.expanded = (query as CHSQLQuery).expand; - editor.onDidChangeModelDecorations((a: any) => { - if (editor.expanded) { - const height = getEditorHeight(editor); - setExpand({ height: `${height}px`, on: true, icon: 'minus' }); - } - }); - editor.onKeyUp((e: any) => { - if (datasource.settings.jsonData.validate) { - const sql = editor.getValue(); - validateSql(sql, editor.getModel(), me); - } - }); - setCodeEditor(editor); - }; - - return ( -
- onToggleExpand()} - className={styles.Common.expand} - data-testid={selectors.components.QueryEditor.CodeEditor.Expand} - > - - - onChange({ ...query, rawSql: text })} - onEditorDidMount={(editor: any) => handleMount(editor)} - /> -
- ); -}; - -const getEditorHeight = (editor: any): number | undefined => { - const editorElement = editor.getDomNode(); - if (!editorElement) { - return; - } - - const lineCount = editor.getModel()?.getLineCount() || 1; - return editor.getTopForLineNumber(lineCount + 1) + 40; -}; diff --git a/src/components/SQLEditor.test._tsx b/src/components/SqlEditor.test.tsx similarity index 80% rename from src/components/SQLEditor.test._tsx rename to src/components/SqlEditor.test.tsx index 621c1459..facf4e2c 100644 --- a/src/components/SQLEditor.test._tsx +++ b/src/components/SqlEditor.test.tsx @@ -2,11 +2,11 @@ import React from 'react'; import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; -import { SQLEditor } from './SQLEditor'; -import { Components } from '../selectors'; +import { SqlEditor } from './SqlEditor'; import * as ui from '@grafana/ui'; import { mockDatasource } from '__mocks__/datasource'; -import { QueryType } from 'types'; +import { EditorType } from 'types/sql'; +import { Components } from 'selectors'; jest.mock('@grafana/ui', () => ({ ...jest.requireActual('@grafana/ui'), @@ -26,8 +26,8 @@ describe('SQL Editor', () => { it('Should display sql in the editor', () => { const rawSql = 'foo'; render( - { const onRunQuery = jest.fn(); await act(async () => { render( - ; diff --git a/src/components/editor.ts b/src/components/editor.ts deleted file mode 100644 index 1f8c4982..00000000 --- a/src/components/editor.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { getFields } from 'data/ast'; -import { Format } from 'types'; -import { isString } from 'lodash'; - -export const getFormat = (sql: string, selectedFormat: Format): Format => { - if (selectedFormat === Format.AUTO) { - // convention to format as time series - // first field as "time" alias and requires at least 2 fields (time and metric) - const selectList = getFields(sql); - // if there are more than 2 fields, index 1 will be a ',' - if (selectList.length >= 2 && isString(selectList[0])) { - const firstProjection = selectList[0].trim().toLowerCase(); - if (firstProjection.endsWith('as time')) { - return Format.TIMESERIES; - } - if (firstProjection.endsWith('as log_time')) { - return Format.LOGS; - } - } - return Format.TABLE; - } - return selectedFormat; -}; diff --git a/src/components/queryBuilder/Metrics.test.tsx b/src/components/queryBuilder/AggregateEditor.test.tsx similarity index 92% rename from src/components/queryBuilder/Metrics.test.tsx rename to src/components/queryBuilder/AggregateEditor.test.tsx index b3e7e759..48a4c0a5 100644 --- a/src/components/queryBuilder/Metrics.test.tsx +++ b/src/components/queryBuilder/AggregateEditor.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { MetricsEditor } from './Metrics'; -import { selectors } from './../../selectors'; +import { selectors } from '../../labels'; describe('MetricsEditor', () => { it('renders correctly', () => { diff --git a/src/components/v4/queryBuilder/AggregateEditor.tsx b/src/components/queryBuilder/AggregateEditor.tsx similarity index 96% rename from src/components/v4/queryBuilder/AggregateEditor.tsx rename to src/components/queryBuilder/AggregateEditor.tsx index 6ddf023e..823fd002 100644 --- a/src/components/v4/queryBuilder/AggregateEditor.tsx +++ b/src/components/queryBuilder/AggregateEditor.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { InlineFormLabel, Select, Button, Input } from '@grafana/ui'; import { AggregateColumn, AggregateType, TableColumn } from 'types/queryBuilder'; -import selectors from 'v4/selectors'; +import labels from 'labels'; import { styles } from 'styles'; interface AggregateProps { @@ -26,7 +26,7 @@ const Aggregate = (props: AggregateProps) => { const { columnOptions, index, aggregate, updateAggregate } = props; const [isOpen, setIsOpen] = useState(false); const [alias, setAlias] = useState(aggregate.alias || ''); - const { aliasLabel } = selectors.components.AggregatesEditor; + const { aliasLabel } = labels.components.AggregatesEditor; return ( <> @@ -71,7 +71,7 @@ interface AggregateEditorProps { export const AggregateEditor = (props: AggregateEditorProps) => { const { allColumns, aggregates, onAggregatesChange } = props; const columnOptions: Array> = (allColumns || []).map(c => ({ label: c.name, value: c.name })); - const { label, tooltip, addLabel } = selectors.components.AggregatesEditor; + const { label, tooltip, addLabel } = labels.components.AggregatesEditor; const addAggregate = () => { const nextAggregates: AggregateColumn[] = aggregates.slice(); diff --git a/src/components/queryBuilder/ColumnSelect.test.tsx b/src/components/queryBuilder/ColumnSelect.test.tsx new file mode 100644 index 00000000..5755675c --- /dev/null +++ b/src/components/queryBuilder/ColumnSelect.test.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +// import { ColumnSelect } from './ColumnSelect'; + +describe('ColumnSelect', () => { + it('renders correctly', () => { + const result = render( + <> + // + ); + expect(result.container.firstChild).not.toBeNull(); + }); +}); diff --git a/src/components/v4/queryBuilder/ColumnSelect.tsx b/src/components/queryBuilder/ColumnSelect.tsx similarity index 100% rename from src/components/v4/queryBuilder/ColumnSelect.tsx rename to src/components/queryBuilder/ColumnSelect.tsx diff --git a/src/components/queryBuilder/Fields.test.tsx b/src/components/queryBuilder/ColumnsEditor.test.tsx similarity index 100% rename from src/components/queryBuilder/Fields.test.tsx rename to src/components/queryBuilder/ColumnsEditor.test.tsx diff --git a/src/components/v4/queryBuilder/ColumnsEditor.tsx b/src/components/queryBuilder/ColumnsEditor.tsx similarity index 96% rename from src/components/v4/queryBuilder/ColumnsEditor.tsx rename to src/components/queryBuilder/ColumnsEditor.tsx index 620c116c..f5b94c64 100644 --- a/src/components/v4/queryBuilder/ColumnsEditor.tsx +++ b/src/components/queryBuilder/ColumnsEditor.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { InlineFormLabel, MultiSelect } from '@grafana/ui'; import { SelectableValue } from '@grafana/data'; import { TableColumn, SelectedColumn } from 'types/queryBuilder'; -import selectors from 'v4/selectors'; +import labels from 'labels'; import { styles } from 'styles'; interface ColumnsEditorProps { @@ -24,7 +24,7 @@ export const ColumnsEditor = (props: ColumnsEditorProps) => { const [isOpen, setIsOpen] = useState(false); const allColumnNames = (allColumns || []).map(c => ({ label: c.name, value: c.name })); const selectedColumnNames = (selectedColumns || []).map(c => ({ label: c.name, value: c.name })); - const { label, tooltip } = selectors.components.ColumnsEditor; + const { label, tooltip } = labels.components.ColumnsEditor; const options = [...allColumnNames, ...customColumns]; diff --git a/src/components/queryBuilder/DatabaseSelect.tsx b/src/components/queryBuilder/DatabaseSelect.tsx deleted file mode 100644 index 32327bb1..00000000 --- a/src/components/queryBuilder/DatabaseSelect.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { InlineFormLabel, Select } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { Datasource } from '../../data/CHDatasource'; -import { selectors } from './../../selectors'; -import { styles } from '../../styles'; - -export type Props = { datasource: Datasource; value?: string; onChange: (value: string) => void }; - -export const DatabaseSelect = (props: Props) => { - const { datasource, onChange, value } = props; - const [list, setList] = useState>>([]); - const { label, tooltip } = selectors.components.QueryEditor.QueryBuilder.DATABASE; - useEffect(() => { - async function fetchList() { - const list = await datasource.fetchDatabases(); - const values = list.map((t) => ({ label: t, value: t })); - // Add selected value to the list if it does not exist. - if (value && !list.find((x) => x === value)) { - values.push({ label: value!, value: value! }); - } - setList(values); - } - fetchList(); - }, [datasource, value]); - - const defaultDatabase = datasource.settings.jsonData.defaultDatabase; - const db = value ?? defaultDatabase; - return ( - <> - - {label} - - - - ); -}; diff --git a/src/components/queryBuilder/TableSelect.test.tsx b/src/components/queryBuilder/DatabaseTableSelect.test.tsx similarity index 100% rename from src/components/queryBuilder/TableSelect.test.tsx rename to src/components/queryBuilder/DatabaseTableSelect.test.tsx diff --git a/src/components/v4/DatabaseTableSelect.tsx b/src/components/queryBuilder/DatabaseTableSelect.tsx similarity index 95% rename from src/components/v4/DatabaseTableSelect.tsx rename to src/components/queryBuilder/DatabaseTableSelect.tsx index ccf0b88a..b5ee1be4 100644 --- a/src/components/v4/DatabaseTableSelect.tsx +++ b/src/components/queryBuilder/DatabaseTableSelect.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; import { InlineFormLabel, Select } from '@grafana/ui'; import { Datasource } from '../../data/CHDatasource'; -import selectors from 'v4/selectors'; +import labels from 'labels'; import { styles } from '../../styles'; import useTables from 'hooks/useTables'; import useDatabases from 'hooks/useDatabases'; @@ -15,7 +15,7 @@ export type DatabaseSelectProps = { export const DatabaseSelect = (props: DatabaseSelectProps) => { const { datasource, onDatabaseChange, database } = props; const databases = useDatabases(datasource); - const { label, tooltip, empty } = selectors.components.DatabaseSelect; + const { label, tooltip, empty } = labels.components.DatabaseSelect; const options = databases.map(d => ({ label: d, value: d })); options.push({ label: empty, value: '' }); // Allow a blank value @@ -60,7 +60,7 @@ export type TableSelectProps = { export const TableSelect = (props: TableSelectProps) => { const { datasource, onTableChange, database, table } = props; const tables = useTables(datasource, database); - const { label, tooltip, empty } = selectors.components.TableSelect; + const { label, tooltip, empty } = labels.components.TableSelect; const options = tables.map(t => ({ label: t, value: t })); options.push({ label: empty, value: '' }); // Allow a blank value diff --git a/src/components/v4/EditorTypeSwitcher.tsx b/src/components/queryBuilder/EditorTypeSwitcher.tsx similarity index 93% rename from src/components/v4/EditorTypeSwitcher.tsx rename to src/components/queryBuilder/EditorTypeSwitcher.tsx index 8d8a4792..a9c2881a 100644 --- a/src/components/v4/EditorTypeSwitcher.tsx +++ b/src/components/queryBuilder/EditorTypeSwitcher.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { RadioButtonGroup, ConfirmModal, InlineFormLabel } from '@grafana/ui'; import { getQueryOptionsFromSql, getSQLFromQueryOptions } from '../queryBuilder/utils'; -import selectors from '../../v4/selectors'; +import labels from 'labels'; import { EditorType, CHQuery, defaultCHBuilderQuery, CHSqlQuery } from 'types/sql'; import { QueryBuilderOptions } from 'types/queryBuilder'; import isString from 'lodash/isString'; @@ -18,7 +18,7 @@ interface CHEditorTypeSwitcherProps { */ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { const { query, onChange } = props; - const { label, tooltip, switcher, cannotConvert } = selectors.components.EditorTypeSwitcher; + const { label, tooltip, switcher, cannotConvert } = labels.components.EditorTypeSwitcher; let editorType: EditorType = query.editorType || ((query as CHSqlQuery).rawSql && !(query as CHQuery).editorType ? EditorType.SQL : EditorType.Builder); @@ -26,8 +26,8 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { const [confirmModalState, setConfirmModalState] = useState(false); const [cannotConvertModalState, setCannotConvertModalState] = useState(false); const options: Array> = [ - { label: selectors.types.EditorType.sql, value: EditorType.SQL }, - { label: selectors.types.EditorType.builder, value: EditorType.Builder }, + { label: labels.types.EditorType.sql, value: EditorType.SQL }, + { label: labels.types.EditorType.builder, value: EditorType.Builder }, ]; const [errorMessage, setErrorMessage] = useState(''); const onEditorTypeChange = (editorType: EditorType, confirm = false) => { diff --git a/src/components/queryBuilder/Fields.tsx b/src/components/queryBuilder/Fields.tsx deleted file mode 100644 index b037951f..00000000 --- a/src/components/queryBuilder/Fields.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { InlineFormLabel, MultiSelect } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { FullField } from './../../types'; -import { selectors } from './../../selectors'; -import { styles } from '../../styles'; - -interface FieldsEditorProps { - fieldsList: FullField[]; - fields: string[]; - onFieldsChange: (fields: string[]) => void; -} -export const FieldsEditor = (props: FieldsEditorProps) => { - const columns = (props.fieldsList || []).map((f) => ({ label: f.label, value: f.name })); - const [custom, setCustom] = useState>>([]); - const [isOpen, setIsOpen] = useState(false); - const defaultFields: Array> = []; - const [fields, setFields] = useState(props.fields || []); - const { label, tooltipTable } = selectors.components.QueryEditor.QueryBuilder.SELECT; - - useEffect(() => { - if (props.fieldsList.length === 0) { - return; - } - setFields(props.fields); - const customFields = getCustomFields(props.fields, props.fieldsList); - setCustom(customFields); - }, [props.fieldsList, props.fields]); - - const onFieldsChange = (fields: string[]) => { - const cleanFields = cleanupFields(fields); - setFields(cleanFields); - const customFields = getCustomFields(fields, props.fieldsList); - setCustom(customFields); - }; - - const cleanupFields = (fields: string[]): string[] => { - if ( - defaultFields.map((d) => d.value).includes(fields[0]) || - defaultFields.map((d) => d.value).includes(fields[fields.length - 1]) - ) { - return [fields[fields.length - 1]]; - } - return fields; - }; - - const onUpdateField = () => { - props.onFieldsChange(fields); - }; - - const onChange = (e: Array>): void => { - setIsOpen(false); - onFieldsChange(e.map((v) => v.value!)); - }; - - return ( -
- - {label} - -
- - options={[...columns, ...defaultFields, ...custom]} - value={fields && fields.length > 0 ? fields : []} - isOpen={isOpen} - onOpenMenu={() => setIsOpen(true)} - onCloseMenu={() => setIsOpen(false)} - onChange={onChange} - onBlur={onUpdateField} - allowCustomValue={true} - menuPlacement={'bottom'} - /> -
-
- ); -}; - -function getCustomFields(fields: string[], columns: FullField[]) { - return fields - .filter((f) => { - return columns.findIndex((c) => c.name === f) === -1; - }) - .map((f) => ({ label: f, value: f })); -} diff --git a/src/components/queryBuilder/Filters.test.tsx b/src/components/queryBuilder/FilterEditor.test.tsx similarity index 99% rename from src/components/queryBuilder/Filters.test.tsx rename to src/components/queryBuilder/FilterEditor.test.tsx index 54dff632..92d4ec95 100644 --- a/src/components/queryBuilder/Filters.test.tsx +++ b/src/components/queryBuilder/FilterEditor.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { fireEvent, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { defaultNewFilter, FilterEditor, FiltersEditor, FilterValueEditor } from './Filters'; -import { selectors } from './../../selectors'; +import { selectors } from '../../labels'; import { BooleanFilter, DateFilter, Filter, FilterOperator, MultiFilter, NumberFilter, StringFilter } from 'types'; describe('FiltersEditor', () => { diff --git a/src/components/v4/queryBuilder/FilterEditor.tsx b/src/components/queryBuilder/FilterEditor.tsx similarity index 97% rename from src/components/v4/queryBuilder/FilterEditor.tsx rename to src/components/queryBuilder/FilterEditor.tsx index 9b164297..12b83e7d 100644 --- a/src/components/v4/queryBuilder/FilterEditor.tsx +++ b/src/components/queryBuilder/FilterEditor.tsx @@ -2,8 +2,8 @@ import React, { useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { Button, InlineFormLabel, Input, MultiSelect, RadioButtonGroup, Select } from '@grafana/ui'; import { Filter, FilterOperator, TableColumn, NullFilter } from 'types/queryBuilder'; -import * as utils from '../../queryBuilder/utils'; -import { selectors } from 'selectors'; +import * as utils from 'components/queryBuilder/utils'; +import labels from 'labels'; import { styles } from 'styles'; const boolValues: Array> = [ @@ -367,7 +367,7 @@ export const FiltersEditor = (props: { onFiltersChange: (filters: Filter[]) => void; }) => { const { filters = [], onFiltersChange, allColumns: fieldsList = [] } = props; - const { label, tooltip, AddLabel, RemoveLabel } = selectors.components.QueryEditor.QueryBuilder.WHERE; + const { label, tooltip, addLabel } = labels.components.FilterEditor; const addFilter = () => { onFiltersChange([...filters, { ...defaultNewFilter }]); }; @@ -396,7 +396,7 @@ export const FiltersEditor = (props: { className={styles.Common.smallBtn} onClick={addFilter} > - {AddLabel} + {addLabel}
)} @@ -418,9 +418,7 @@ export const FiltersEditor = (props: { size="sm" className={styles.Common.smallBtn} onClick={() => removeFilter(index)} - > - {RemoveLabel} - + />
); })} @@ -435,7 +433,7 @@ export const FiltersEditor = (props: { className={styles.Common.smallBtn} onClick={addFilter} > - {AddLabel} + {addLabel}
)} diff --git a/src/components/queryBuilder/Filters.tsx b/src/components/queryBuilder/Filters.tsx deleted file mode 100644 index e09aa114..00000000 --- a/src/components/queryBuilder/Filters.tsx +++ /dev/null @@ -1,444 +0,0 @@ -import React, { useState } from 'react'; -import { SelectableValue } from '@grafana/data'; -import { Button, InlineFormLabel, Input, MultiSelect, RadioButtonGroup, Select } from '@grafana/ui'; -import { Filter, FilterOperator, FullField, NullFilter } from '../../types'; -import * as utils from './utils'; -import { selectors } from '../../selectors'; -import { styles } from '../../styles'; - -const boolValues: Array> = [ - { value: true, label: 'True' }, - { value: false, label: 'False' }, -]; -const conditions: Array> = [ - { value: 'AND', label: 'AND' }, - { value: 'OR', label: 'OR' }, -]; -const filterOperators: Array> = [ - { value: FilterOperator.Equals, label: '=' }, - { value: FilterOperator.NotEquals, label: '!=' }, - { value: FilterOperator.LessThan, label: '<' }, - { value: FilterOperator.LessThanOrEqual, label: '<=' }, - { value: FilterOperator.GreaterThan, label: '>' }, - { value: FilterOperator.GreaterThanOrEqual, label: '>=' }, - { value: FilterOperator.Like, label: 'LIKE' }, - { value: FilterOperator.NotLike, label: 'NOT LIKE' }, - { value: FilterOperator.In, label: 'IN' }, - { value: FilterOperator.NotIn, label: 'NOT IN' }, - { value: FilterOperator.IsNull, label: 'IS NULL' }, - { value: FilterOperator.IsNotNull, label: 'IS NOT NULL' }, - { value: FilterOperator.WithInGrafanaTimeRange, label: 'WITHIN DASHBOARD TIME RANGE' }, - { value: FilterOperator.OutsideGrafanaTimeRange, label: 'OUTSIDE DASHBOARD TIME RANGE' }, -]; -const standardTimeOptions: Array> = [ - { value: 'today()', label: 'TODAY' }, - { value: 'yesterday()', label: 'YESTERDAY' }, - { value: 'now()', label: 'NOW' }, - { value: 'GRAFANA_START_TIME', label: 'DASHBOARD START TIME' }, - { value: 'GRAFANA_END_TIME', label: 'DASHBOARD END TIME' }, -]; -export const defaultNewFilter: NullFilter = { - filterType: 'custom', - condition: 'AND', - key: 'Id', - type: 'id', - operator: FilterOperator.IsNotNull, -}; -export interface PredefinedFilter { - restrictToFields?: FullField[]; -} - -const FilterValueNumberItem = (props: { value: number; onChange: (value: number) => void }) => { - const [value, setValue] = useState(props.value || 0); - return ( -
- setValue(e.currentTarget.valueAsNumber || 0)} - onBlur={() => props.onChange(value)} - /> -
- ); -}; - -const FilterValueSingleStringItem = (props: { value: string; onChange: (value: string) => void }) => { - return ( -
- props.onChange(e.currentTarget.value)} - /> -
- ); -}; - -const FilterValueMultiStringItem = (props: { value: string[]; onChange: (value: string[]) => void }) => { - const [value, setValue] = useState(props.value || []); - return ( -
- setValue((e.currentTarget.value || '').split(','))} - onBlur={() => props.onChange(value)} - /> -
- ); -}; - -export const FilterValueEditor = (props: { - fieldsList: FullField[]; - filter: Filter; - onFilterChange: (filter: Filter) => void; -}) => { - const { filter, onFilterChange, fieldsList } = props; - const getOptions = () => { - const matchedFilter = fieldsList.find((f) => f.name === filter.key); - return matchedFilter?.picklistValues || []; - }; - if (utils.isNullFilter(filter)) { - return <>; - } else if (utils.isBooleanFilter(filter)) { - const onBoolFilterValueChange = (value: boolean) => { - onFilterChange({ ...filter, value }); - }; - return ( -
- onBoolFilterValueChange(e!)} /> -
- ); - } else if (utils.isNumberFilter(filter)) { - return onFilterChange({ ...filter, value })} />; - } else if (utils.isDateFilter(filter)) { - const onDateFilterValueChange = (value: string) => { - onFilterChange({ ...filter, value }); - }; - return utils.isDateFilterWithOutValue(filter) ? null : ( -
- onStringFilterValueChange(e.value!)} options={getOptions()} /> -
- ); - } - - return ( - - ); - } else if (utils.isMultiFilter(filter)) { - const onMultiFilterValueChange = (value: string[]) => { - onFilterChange({ ...filter, value }); - }; - if (filter.type === 'picklist') { - return ( -
- onMultiFilterValueChange(e.map((v) => v.value!))} - /> -
- ); - } - return ; - } else { - return <>; - } -}; - -export const FilterEditor = (props: { - fieldsList: FullField[]; - index: number; - filter: Filter & PredefinedFilter; - onFilterChange: (index: number, filter: Filter) => void; -}) => { - const { index, filter, fieldsList, onFilterChange } = props; - const [isOpen, setIsOpen] = useState(false); - const getFields = () => { - const values = (filter.restrictToFields || fieldsList).map((f) => { - return { label: f.label, value: f.name }; - }); - // Add selected value to the list if it does not exist. - if (filter?.key && !values.find((x) => x.value === filter.key)) { - values.push({ label: filter.key!, value: filter.key! }); - } - return values; - }; - const getFilterOperatorsByType = (type = 'string'): Array> => { - if (utils.isBooleanType(type)) { - return filterOperators.filter((f) => [FilterOperator.Equals, FilterOperator.NotEquals].includes(f.value!)); - } else if (utils.isNumberType(type)) { - return filterOperators.filter((f) => - [ - FilterOperator.IsNull, - FilterOperator.IsNotNull, - FilterOperator.Equals, - FilterOperator.NotEquals, - FilterOperator.LessThan, - FilterOperator.LessThanOrEqual, - FilterOperator.GreaterThan, - FilterOperator.GreaterThanOrEqual, - ].includes(f.value!) - ); - } else if (utils.isDateType(type)) { - return filterOperators.filter((f) => - [ - FilterOperator.IsNull, - FilterOperator.IsNotNull, - FilterOperator.Equals, - FilterOperator.NotEquals, - FilterOperator.LessThan, - FilterOperator.LessThanOrEqual, - FilterOperator.GreaterThan, - FilterOperator.GreaterThanOrEqual, - FilterOperator.WithInGrafanaTimeRange, - FilterOperator.OutsideGrafanaTimeRange, - ].includes(f.value!) - ); - } else { - return filterOperators.filter((f) => - [ - FilterOperator.IsNull, - FilterOperator.IsNotNull, - FilterOperator.Equals, - FilterOperator.NotEquals, - FilterOperator.Like, - FilterOperator.NotLike, - FilterOperator.In, - FilterOperator.NotIn, - ].includes(f.value!) - ); - } - }; - const onFilterNameChange = (fieldName: string) => { - setIsOpen(false); - const matchingField = fieldsList.find((f) => f.name === fieldName); - let filterData: { key: string; type: string } | null = null; - - if (matchingField) { - filterData = { - key: matchingField.name, - type: matchingField.type, - }; - } else { - // In case user wants to add a custom filter for the - // field with `Map` type (e.g. colName['keyName']) - // More info: https://clickhouse.com/docs/en/sql-reference/data-types/map/ - const matchingMapField = fieldsList.find((f) => { - return ( - f.type.startsWith('Map') && - fieldName.startsWith(f.name) && - new RegExp(`^${f.name}\\[['"].+['"]\\]$`).test(fieldName) - ); - }); - - if (matchingMapField) { - // Getting the field type. Example: getting `UInt64` from `Map(String, UInt64)`. - const mapFieldType = /^Map\(\w+, (\w+)\)$/.exec(matchingMapField.type)?.[1]; - - if (mapFieldType) { - filterData = { - key: fieldName, - type: mapFieldType, - }; - } - } - } - - if (!filterData) { - return; - } - - let newFilter: Filter & PredefinedFilter; - // this is an auto-generated TimeRange filter - if (filter.restrictToFields) { - newFilter = { - filterType: 'custom', - key: filterData.key, - type: 'datetime', - condition: filter.condition || 'AND', - operator: FilterOperator.WithInGrafanaTimeRange, - restrictToFields: filter.restrictToFields, - }; - } else if (utils.isBooleanType(filterData.type)) { - newFilter = { - filterType: 'custom', - key: filterData.key, - type: 'boolean', - condition: filter.condition || 'AND', - operator: FilterOperator.Equals, - value: false, - }; - } else if (utils.isDateType(filterData.type)) { - newFilter = { - filterType: 'custom', - key: filterData.key, - type: filterData.type as 'date', - condition: filter.condition || 'AND', - operator: FilterOperator.Equals, - value: 'TODAY', - }; - } else { - newFilter = { - filterType: 'custom', - key: filterData.key, - type: filterData.type, - condition: filter.condition || 'AND', - operator: FilterOperator.IsNotNull, - }; - } - onFilterChange(index, newFilter); - }; - const onFilterOperatorChange = (operator: FilterOperator) => { - let newFilter: Filter = filter; - newFilter.operator = operator; - if (utils.isMultiFilter(newFilter)) { - if (!Array.isArray(newFilter.value)) { - newFilter.value = [newFilter.value || '']; - } - } - onFilterChange(index, newFilter); - }; - const onFilterConditionChange = (condition: 'AND' | 'OR') => { - let newFilter: Filter = filter; - newFilter.condition = condition; - onFilterChange(index, newFilter); - }; - const onFilterValueChange = (filter: Filter) => { - onFilterChange(index, filter); - }; - return ( - <> - {index !== 0 && ( - onFilterConditionChange(e!)} /> - )} - onFilterOperatorChange(e.value!)} - menuPlacement={'bottom'} - /> - - - ); -}; - -export const FiltersEditor = (props: { - fieldsList: FullField[]; - filters: Filter[]; - onFiltersChange: (filters: Filter[]) => void; -}) => { - const { filters = [], onFiltersChange, fieldsList = [] } = props; - const { label, tooltip, AddLabel, RemoveLabel } = selectors.components.QueryEditor.QueryBuilder.WHERE; - const addFilter = () => { - onFiltersChange([...filters, { ...defaultNewFilter }]); - }; - const removeFilter = (index: number) => { - const newFilters = [...filters]; - newFilters.splice(index, 1); - onFiltersChange(newFilters); - }; - const onFilterChange = (index: number, filter: Filter) => { - const newFilters = [...filters]; - newFilters[index] = filter; - onFiltersChange(newFilters); - }; - return ( - <> - {filters.length === 0 && ( -
- - {label} - - -
- )} - {filters.map((filter, index) => { - return ( -
- {index === 0 ? ( - - {label} - - ) : ( -
- )} - - -
- ); - })} - {filters.length !== 0 && ( -
-
- -
- )} - - ); -}; diff --git a/src/components/queryBuilder/GroupBy.tsx b/src/components/queryBuilder/GroupBy.tsx deleted file mode 100644 index a174780b..00000000 --- a/src/components/queryBuilder/GroupBy.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { useState } from 'react'; -import { InlineFormLabel, MultiSelect } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { FullField } from './../../types'; -import { selectors } from './../../selectors'; -import { styles } from '../../styles'; - -interface GroupByEditorProps { - fieldsList: FullField[]; - groupBy: string[]; - onGroupByChange: (groupBy: string[]) => void; -} -export const GroupByEditor = (props: GroupByEditorProps) => { - const columns: SelectableValue[] = (props.fieldsList || []).map((f) => ({ label: f.label, value: f.name })); - const [isOpen, setIsOpen] = useState(false); - const [groupBy, setGroupBy] = useState(props.groupBy || []); - const { label, tooltip } = selectors.components.QueryEditor.QueryBuilder.GROUP_BY; - const onChange = (e: Array>) => { - setIsOpen(false); - setGroupBy(e.map((item) => item.value!)); - }; - // Add selected value to the list if it does not exist. - groupBy.filter((x) => !columns.some((y) => y.value === x)).forEach((x) => columns.push({ value: x, label: x })); - return ( -
- - {label} - -
- setIsOpen(true)} - onCloseMenu={() => setIsOpen(false)} - onChange={onChange} - onBlur={() => props.onGroupByChange(groupBy)} - value={groupBy} - allowCustomValue={true} - menuPlacement={'bottom'} - /> -
-
- ); -}; diff --git a/src/components/queryBuilder/GroupBy.test.tsx b/src/components/queryBuilder/GroupByEditor.test.tsx similarity index 100% rename from src/components/queryBuilder/GroupBy.test.tsx rename to src/components/queryBuilder/GroupByEditor.test.tsx diff --git a/src/components/v4/queryBuilder/GroupByEditor.tsx b/src/components/queryBuilder/GroupByEditor.tsx similarity index 93% rename from src/components/v4/queryBuilder/GroupByEditor.tsx rename to src/components/queryBuilder/GroupByEditor.tsx index 918f5694..4c82f002 100644 --- a/src/components/v4/queryBuilder/GroupByEditor.tsx +++ b/src/components/queryBuilder/GroupByEditor.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { InlineFormLabel, MultiSelect } from '@grafana/ui'; import { SelectableValue } from '@grafana/data'; import { TableColumn } from 'types/queryBuilder'; -import selectors from 'v4/selectors'; +import labels from 'labels'; import { styles } from 'styles'; interface GroupByEditorProps { @@ -14,7 +14,7 @@ interface GroupByEditorProps { export const GroupByEditor = (props: GroupByEditorProps) => { const { allColumns, groupBy, onGroupByChange } = props; const [isOpen, setIsOpen] = useState(false); - const { label, tooltip } = selectors.components.GroupByEditor; + const { label, tooltip } = labels.components.GroupByEditor; const options: Array> = (allColumns || []).map(c => ({ label: c.name, value: c.name })); const onChange = (selection: Array>) => { diff --git a/src/components/queryBuilder/Limit.tsx b/src/components/queryBuilder/Limit.tsx deleted file mode 100644 index 55f7238e..00000000 --- a/src/components/queryBuilder/Limit.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { useState } from 'react'; -import { InlineFormLabel, Input } from '@grafana/ui'; -import { selectors } from './../../selectors'; - -interface LimitEditorProps { - limit: number; - onLimitChange: (limit: number) => void; -} -export const LimitEditor = (props: LimitEditorProps) => { - const [limit, setLimit] = useState(props.limit || 10); - const { label, tooltip } = selectors.components.QueryEditor.QueryBuilder.LIMIT; - return ( -
- - {label} - - setLimit(e.currentTarget.valueAsNumber)} - onBlur={() => props.onLimitChange(limit)} - /> -
- ); -}; diff --git a/src/components/queryBuilder/Limit.test.tsx b/src/components/queryBuilder/LimitEditor.test.tsx similarity index 100% rename from src/components/queryBuilder/Limit.test.tsx rename to src/components/queryBuilder/LimitEditor.test.tsx diff --git a/src/components/v4/queryBuilder/LimitEditor.tsx b/src/components/queryBuilder/LimitEditor.tsx similarity index 87% rename from src/components/v4/queryBuilder/LimitEditor.tsx rename to src/components/queryBuilder/LimitEditor.tsx index 0c36a18e..af28018b 100644 --- a/src/components/v4/queryBuilder/LimitEditor.tsx +++ b/src/components/queryBuilder/LimitEditor.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { InlineFormLabel, Input } from '@grafana/ui'; -import selectors from 'v4/selectors'; +import labels from 'labels'; interface LimitEditorProps { limit: number; @@ -9,7 +9,7 @@ interface LimitEditorProps { export const LimitEditor = (props: LimitEditorProps) => { const [limit, setLimit] = useState(props.limit || 100); - const { label, tooltip } = selectors.components.LimitEditor; + const { label, tooltip } = labels.components.LimitEditor; return (
diff --git a/src/components/queryBuilder/LogLevelField.tsx b/src/components/queryBuilder/LogLevelField.tsx deleted file mode 100644 index 68f47758..00000000 --- a/src/components/queryBuilder/LogLevelField.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { SelectableValue } from '@grafana/data'; -import { InlineFormLabel, Select } from '@grafana/ui'; -import { selectors } from './../../selectors'; -import { FullField } from 'types'; - -interface LogLevelEditorProps { - fieldsList: FullField[]; - onLogLevelFieldChange: (logLevelField: string) => void; - logLevelField: string | null; -} - -export const LogLevelFieldEditor = (props: LogLevelEditorProps) => { - const { label, tooltip } = selectors.components.QueryEditor.QueryBuilder.LOG_LEVEL_FIELD; - const columns: SelectableValue[] = (props.fieldsList || []) - .filter((f) => f.name !== '*') - .map((f) => ({ label: f.label, value: f.name })); - return ( -
- - {label} - - onMetricAggregationChange(e.value!)} - value={metric.aggregation} - menuPlacement={'bottom'} - /> - - width={28} - className={styles.Common.inlineSelect} - options={columns} - isOpen={isOpen} - onOpenMenu={() => setIsOpen(true)} - onCloseMenu={() => setIsOpen(false)} - onChange={onMetricFieldChange} - value={metric.field} - menuPlacement={'bottom'} - /> - - {ALIAS.label} - - setAlias(e.currentTarget.value)} - onBlur={onMetricAliasChange} - placeholder="alias" - /> - - ); -}; - -interface MetricsEditorProps { - fieldsList: FullField[]; - metrics: BuilderMetricField[]; - onMetricsChange: (metrics: BuilderMetricField[]) => void; -} -export const MetricsEditor = (props: MetricsEditorProps) => { - const { metrics, onMetricsChange, fieldsList = [] } = props; - const { label, tooltipAggregate, AddLabel, RemoveLabel } = selectors.components.QueryEditor.QueryBuilder.AGGREGATES; - const onMetricAdd = () => { - const newMetric: BuilderMetricField = { field: '', aggregation: BuilderMetricFieldAggregation.Count }; - onMetricsChange([...metrics, newMetric]); - }; - const onMetricRemove = (index: number) => { - const newMetrics: BuilderMetricField[] = [...metrics]; - newMetrics.splice(index, 1); - onMetricsChange(newMetrics); - }; - return ( - <> - {metrics.map((metric, index) => { - return ( -
- {index === 0 ? ( - - {label} - - ) : ( -
- )} - - {metrics.length > 1 && ( - - )} -
- ); - })} -
-
- -
- - ); -}; diff --git a/src/components/queryBuilder/ModeEditor.test.tsx b/src/components/queryBuilder/ModeEditor.test._tsx similarity index 87% rename from src/components/queryBuilder/ModeEditor.test.tsx rename to src/components/queryBuilder/ModeEditor.test._tsx index cabae035..af6f7563 100644 --- a/src/components/queryBuilder/ModeEditor.test.tsx +++ b/src/components/queryBuilder/ModeEditor.test._tsx @@ -1,7 +1,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { ModeEditor } from './ModeEditor'; -import { BuilderMode } from 'types'; +import { BuilderMode } from 'types/queryBuilder'; describe('ModeEditor', () => { it('renders correctly', () => { diff --git a/src/components/queryBuilder/ModeEditor.tsx b/src/components/queryBuilder/ModeEditor.tsx deleted file mode 100644 index a00707fb..00000000 --- a/src/components/queryBuilder/ModeEditor.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { RadioButtonGroup, InlineFormLabel } from '@grafana/ui'; -import { BuilderMode } from 'types'; -import { selectors } from './../../selectors'; - -interface ModeEditorProps { - mode: BuilderMode; - onModeChange: (mode: BuilderMode) => void; -} -export const ModeEditor = (props: ModeEditorProps) => { - const { options, label, tooltip } = selectors.components.QueryEditor.QueryBuilder.TYPES; - const modes = [ - { value: BuilderMode.List, label: options.LIST }, - { value: BuilderMode.Aggregate, label: options.AGGREGATE }, - { value: BuilderMode.Trend, label: options.TREND }, - ]; - return ( - <> - - {label} - - options={modes} value={props.mode} onChange={(e) => props.onModeChange(e!)} /> - - ); -}; diff --git a/src/components/v4/queryBuilder/ModeSwitch.tsx b/src/components/queryBuilder/ModeSwitch.tsx similarity index 100% rename from src/components/v4/queryBuilder/ModeSwitch.tsx rename to src/components/queryBuilder/ModeSwitch.tsx diff --git a/src/components/queryBuilder/OrderBy.tsx b/src/components/queryBuilder/OrderBy.tsx deleted file mode 100644 index 793b4dad..00000000 --- a/src/components/queryBuilder/OrderBy.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import React from 'react'; -import { SelectableValue } from '@grafana/data'; -import { Button, InlineFormLabel, Select } from '@grafana/ui'; -import { - OrderBy, - OrderByDirection, - SqlBuilderOptions, - FullField, - BuilderMode, - BuilderMetricField, - SqlBuilderOptionsAggregate, -} from './../../types'; -import { selectors } from './../../selectors'; -import { styles } from '../../styles'; - -const OrderByItem = (props: { - index: number; - fieldsList: Array>; - orderBy: OrderBy[]; - orderByItem: OrderBy; - onOrderByItemsChange: (orderBy: OrderBy[]) => void; -}) => { - const columns: SelectableValue[] = props.fieldsList || []; - const { index, orderByItem } = props; - const sortOptions = [ - { value: OrderByDirection.ASC, label: 'ASC' }, - { value: OrderByDirection.DESC, label: 'DESC' }, - ]; - const onOrderBySortFieldUpdate = (name: string) => { - const orderByItems: OrderBy[] = [...props.orderBy].map((o, i) => { - return { ...o, name: i === index ? name : o.name }; - }); - props.onOrderByItemsChange(orderByItems); - }; - const onOrderBySortDirectionUpdate = (direction: OrderByDirection) => { - const orderByItems: OrderBy[] = [...props.orderBy].map((o, i) => { - return { ...o, dir: i === index ? direction : o.dir }; - }); - props.onOrderByItemsChange(orderByItems); - }; - return ( - <> - - - value={orderByItem.dir} - className={styles.Common.inlineSelect} - width={12} - options={sortOptions} - onChange={(e) => onOrderBySortDirectionUpdate(e.value!)} - menuPlacement={'bottom'} - /> - - ); -}; - -interface OrderByEditorProps { - fieldsList: Array>; - orderBy: OrderBy[]; - onOrderByItemsChange: (orderBy: OrderBy[]) => void; -} -export const OrderByEditor = (props: OrderByEditorProps) => { - const columns: SelectableValue[] = props.fieldsList || []; - const { label, tooltip, AddLabel, RemoveLabel } = selectors.components.QueryEditor.QueryBuilder.ORDER_BY; - const onOrderByAdd = () => { - const orderByItems: OrderBy[] = [...props.orderBy]; - orderByItems.push({ - name: columns[0]?.value || 'Name', - dir: OrderByDirection.ASC, - }); - props.onOrderByItemsChange(orderByItems); - }; - const onOrderByRemove = (index: number) => { - const orderByItems: OrderBy[] = [...props.orderBy]; - orderByItems.splice(index, 1); - props.onOrderByItemsChange(orderByItems); - }; - return columns.length === 0 ? null : ( - <> - {props.orderBy.length === 0 ? ( -
- - {label} - - -
- ) : ( - <> - {props.orderBy.map((o, index) => { - return ( -
- {index === 0 ? ( - - {label} - - ) : ( -
- )} - - -
- ); - })} -
-
- -
- - )} - - ); -}; - -export const getOrderByFields = ( - builder: SqlBuilderOptions, - fieldsList: FullField[] -): Array> => { - let values: Array> | Array<{ value: string; label: string }> = []; - switch (builder.mode) { - case BuilderMode.Aggregate: - values = [ - ...(builder.fields || []).map((g) => { - return { value: g, label: g }; - }), - ...((builder.metrics as BuilderMetricField[]) || []).map((m) => { - return { value: `${m.aggregation}(${m.field})`, label: `${m.aggregation}(${m.field})` }; - }), - ...((builder.groupBy as string[]) || []).map((g) => { - return { value: g, label: g }; - }), - ]; - break; - case BuilderMode.List: - default: - values = fieldsList.map((m) => { - return { value: m.name, label: m.label }; - }); - } - // Add selected value to the list if it does not exist. - (builder as SqlBuilderOptionsAggregate).orderBy - ?.filter((x) => !values.some((y: { value: string; label: string } | SelectableValue) => y.value === x.name)) - .forEach((x) => values.push({ value: x.name, label: x.name })); - return values; -}; diff --git a/src/components/queryBuilder/OrderBy.test.tsx b/src/components/queryBuilder/OrderByEditor.test.tsx similarity index 72% rename from src/components/queryBuilder/OrderBy.test.tsx rename to src/components/queryBuilder/OrderByEditor.test.tsx index 30086ba3..c8821b69 100644 --- a/src/components/queryBuilder/OrderBy.test.tsx +++ b/src/components/queryBuilder/OrderByEditor.test.tsx @@ -1,23 +1,37 @@ import React from 'react'; import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { OrderByEditor, getOrderByFields } from './OrderBy'; -import { BuilderMetricFieldAggregation, BuilderMode, OrderByDirection } from './../../types'; +import { OrderByEditor, getOrderByFields } from './OrderByEditor'; +import { AggregateType, BuilderMode, ColumnHint, OrderByDirection, QueryType, TableColumn } from 'types/queryBuilder'; + +const newTestColumn = (name: string): TableColumn => ({ + name, + label: name, + sortable: true, + type: 'String', + picklistValues: [] +}); + +const testColumns: TableColumn[] = [ + newTestColumn('foo'), + newTestColumn('bar'), + newTestColumn('baz'), +]; describe('OrderByEditor', () => { it('should render null when no fields passed', () => { - const result = render( {}} />); + const result = render( {}} />); expect(result.container.firstChild).toBeNull(); }); it('should render component when fields passed', () => { const result = render( - {}} /> + {}} /> ); expect(result.container.firstChild).not.toBeNull(); }); it('should render default add button when no orderby fields passed', () => { const result = render( - {}} /> + {}} /> ); expect(result.container.firstChild).not.toBeNull(); expect(result.getByTestId('query-builder-orderby-add-button')).toBeInTheDocument(); @@ -27,9 +41,9 @@ describe('OrderByEditor', () => { it('should render remove button when at least one orderby fields passed', () => { const result = render( {}} + onOrderByChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -41,12 +55,12 @@ describe('OrderByEditor', () => { it('should be only one inline add button when multiple orderby items passed', () => { const result = render( {}} + onOrderByChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -56,12 +70,12 @@ describe('OrderByEditor', () => { it('should render add/remove buttons correctly when multiple orderby elements passed', () => { const result = render( {}} + onOrderByChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -73,60 +87,56 @@ describe('OrderByEditor', () => { it('should render label only once', () => { const result = render( {}} + onOrderByChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); expect(result.getByTestId('query-builder-orderby-item-label')).toBeInTheDocument(); }); it('should add default item when add button clicked', async () => { - const onOrderByItemsChange = jest.fn(); + const onOrderByChange = jest.fn(); const result = render( ); expect(result.container.firstChild).not.toBeNull(); expect(result.getByTestId('query-builder-orderby-add-button')).toBeInTheDocument(); expect(result.queryByTestId('query-builder-orderby-item-wrapper')).not.toBeInTheDocument(); expect(result.queryByTestId('query-builder-orderby-remove-button')).not.toBeInTheDocument(); - expect(onOrderByItemsChange).toBeCalledTimes(0); + expect(onOrderByChange).toBeCalledTimes(0); await userEvent.click(result.getByTestId('query-builder-orderby-add-button')); - expect(onOrderByItemsChange).toBeCalledTimes(1); - expect(onOrderByItemsChange).toBeCalledWith([{ name: 'foo', dir: OrderByDirection.ASC }]); + expect(onOrderByChange).toBeCalledTimes(1); + expect(onOrderByChange).toBeCalledWith([{ name: 'foo', dir: OrderByDirection.ASC }]); }); it('should add and remove items when remove button clicked', async () => { - const onOrderByItemsChange = jest.fn(); + const onOrderByChange = jest.fn(); const result = render( ); expect(result.container.firstChild).not.toBeNull(); - expect(onOrderByItemsChange).toBeCalledTimes(0); + expect(onOrderByChange).toBeCalledTimes(0); await userEvent.click(result.getAllByTestId('query-builder-orderby-remove-button')[1]); await userEvent.click(result.getAllByTestId('query-builder-orderby-remove-button')[0]); await userEvent.click(result.getAllByTestId('query-builder-orderby-inline-add-button')[0]); - expect(onOrderByItemsChange).toBeCalledTimes(3); - expect(onOrderByItemsChange).toHaveBeenNthCalledWith(1, [{ name: 'foo', dir: OrderByDirection.ASC }]); - expect(onOrderByItemsChange).toHaveBeenNthCalledWith(2, [{ name: 'bar', dir: OrderByDirection.ASC }]); - expect(onOrderByItemsChange).toHaveBeenNthCalledWith(3, [ + expect(onOrderByChange).toBeCalledTimes(3); + expect(onOrderByChange).toHaveBeenNthCalledWith(1, [{ name: 'foo', dir: OrderByDirection.ASC }]); + expect(onOrderByChange).toHaveBeenNthCalledWith(2, [{ name: 'bar', dir: OrderByDirection.ASC }]); + expect(onOrderByChange).toHaveBeenNthCalledWith(3, [ { name: 'foo', dir: OrderByDirection.ASC }, { name: 'bar', dir: OrderByDirection.ASC }, { name: 'foo', dir: OrderByDirection.ASC }, @@ -165,10 +175,11 @@ describe('getOrderByFields', () => { expect( getOrderByFields( { - mode: BuilderMode.List, database: 'db', table: 'foo', - fields: ['field1', 'field3'], + queryType: QueryType.Table, + mode: BuilderMode.List, + columns: [{ name: 'field1' }, { name: 'field3' }], }, sampleFields ) @@ -191,31 +202,33 @@ describe('getOrderByFields', () => { }, ]); }); - it('aggregated view - no group by and no metrics', () => { + it('aggregated view - no group by and no aggregates', () => { expect( getOrderByFields( { - mode: BuilderMode.Aggregate, database: 'db', table: 'foo', - fields: [], - metrics: [], + queryType: QueryType.Table, + mode: BuilderMode.Aggregate, + columns: [], + aggregates: [], }, sampleFields ) ).toStrictEqual([]); }); - it('aggregated view - no group by and with two metrics', () => { + it('aggregated view - no group by and with two aggregates', () => { expect( getOrderByFields( { - mode: BuilderMode.Aggregate, database: 'db', table: 'foo', - fields: [], - metrics: [ - { field: 'field2', aggregation: BuilderMetricFieldAggregation.Max }, - { field: 'field1', aggregation: BuilderMetricFieldAggregation.Sum }, + queryType: QueryType.Table, + mode: BuilderMode.Aggregate, + columns: [], + aggregates: [ + { column: 'field2', aggregateType: AggregateType.Max }, + { column: 'field1', aggregateType: AggregateType.Sum }, ], }, sampleFields @@ -231,15 +244,16 @@ describe('getOrderByFields', () => { }, ]); }); - it('aggregated view - two group by and with no metrics', () => { + it('aggregated view - two group by and with no aggregates', () => { expect( getOrderByFields( { - mode: BuilderMode.Aggregate, database: 'db', table: 'foo', - fields: [], - metrics: [], + queryType: QueryType.Table, + mode: BuilderMode.Aggregate, + columns: [], + aggregates: [], groupBy: ['field3', 'field1'], }, sampleFields @@ -259,13 +273,14 @@ describe('getOrderByFields', () => { expect( getOrderByFields( { - mode: BuilderMode.Aggregate, database: 'db', table: 'foo', - fields: [], - metrics: [ - { field: 'field2', aggregation: BuilderMetricFieldAggregation.Max }, - { field: 'field1', aggregation: BuilderMetricFieldAggregation.Sum }, + queryType: QueryType.Table, + mode: BuilderMode.Aggregate, + columns: [], + aggregates: [ + { column: 'field2', aggregateType: AggregateType.Max }, + { column: 'field1', aggregateType: AggregateType.Sum }, ], groupBy: ['field3', 'field1'], }, @@ -294,13 +309,12 @@ describe('getOrderByFields', () => { expect( getOrderByFields( { - mode: BuilderMode.Trend, database: 'db', table: 'foo', - fields: [], - metrics: [{ field: 'field2', aggregation: BuilderMetricFieldAggregation.Max }], - timeField: 'field3', - timeFieldType: 'datetime', + queryType: QueryType.Table, + mode: BuilderMode.Trend, + columns: [{ name: 'field3', type: 'datetime', hint: ColumnHint.Time }], + aggregates: [{ column: 'field2', aggregateType: AggregateType.Max }], }, sampleFields ) diff --git a/src/components/v4/queryBuilder/OrderByEditor.tsx b/src/components/queryBuilder/OrderByEditor.tsx similarity index 98% rename from src/components/v4/queryBuilder/OrderByEditor.tsx rename to src/components/queryBuilder/OrderByEditor.tsx index 83ea206e..966dd24b 100644 --- a/src/components/v4/queryBuilder/OrderByEditor.tsx +++ b/src/components/queryBuilder/OrderByEditor.tsx @@ -9,7 +9,7 @@ import { BuilderMode, AggregateColumn, } from 'types/queryBuilder'; -import selectors from 'v4/selectors'; +import allLabels from 'labels'; import { styles } from 'styles'; interface OrderByItemProps { @@ -58,7 +58,7 @@ interface OrderByEditorProps { export const OrderByEditor = (props: OrderByEditorProps) => { const { allColumns, orderBy, onOrderByChange } = props; const columnOptions: Array> = (allColumns || []).map(c => ({ label: c.name, value: c.name })); - const { label, tooltip, addLabel } = selectors.components.OrderByEditor; + const { label, tooltip, addLabel } = allLabels.components.OrderByEditor; const addOrderByItem = () => { const nextOrderBy: OrderBy[] = orderBy.slice(); diff --git a/src/components/v4/queryBuilder/OtelVersionSelect.tsx b/src/components/queryBuilder/OtelVersionSelect.tsx similarity index 95% rename from src/components/v4/queryBuilder/OtelVersionSelect.tsx rename to src/components/queryBuilder/OtelVersionSelect.tsx index 89644392..5e7e2cf2 100644 --- a/src/components/v4/queryBuilder/OtelVersionSelect.tsx +++ b/src/components/queryBuilder/OtelVersionSelect.tsx @@ -1,8 +1,8 @@ import React, { useEffect } from 'react'; import { SelectableValue } from '@grafana/data'; import { InlineFormLabel, Select, Switch as GrafanaSwitch, useTheme } from '@grafana/ui'; -import { versions as allVersions } from 'v4/otel'; -import selectors from 'v4/selectors'; +import { versions as allVersions } from 'otel'; +import selectors from 'labels'; interface OtelVersionSelectProps { enabled: boolean, diff --git a/src/components/queryBuilder/Preview.tsx b/src/components/queryBuilder/Preview.tsx deleted file mode 100644 index 61756cd9..00000000 --- a/src/components/queryBuilder/Preview.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { InlineFormLabel } from '@grafana/ui'; -import { selectors } from './../../selectors'; -interface PreviewProps { - sql: string; -} -export const Preview = (props: PreviewProps) => { - const { label, tooltip } = selectors.components.QueryEditor.QueryBuilder.PREVIEW; - return ( -
- - {label} - -
{props.sql}
-
- ); -}; diff --git a/src/components/queryBuilder/QueryBuilder._tsx b/src/components/queryBuilder/QueryBuilder._tsx deleted file mode 100644 index c0ea0a8d..00000000 --- a/src/components/queryBuilder/QueryBuilder._tsx +++ /dev/null @@ -1,271 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import defaultsDeep from 'lodash/defaultsDeep'; -import { Datasource } from '../../data/CHDatasource'; -import { TableSelect } from './TableSelect'; -import { ModeEditor } from './ModeEditor'; -import { FieldsEditor } from './Fields'; -import { MetricsEditor } from './Metrics'; -import { TimeFieldEditor } from './TimeField'; -import { FiltersEditor, PredefinedFilter } from './Filters'; -import { GroupByEditor } from './GroupBy'; -import { getOrderByFields, OrderByEditor } from './OrderBy'; -import { LimitEditor } from './Limit'; -import { - BuilderMetricField, - BuilderMode, - defaultCHBuilderQuery, - Filter, - FilterOperator, - Format, - FullField, - OrderBy, - SqlBuilderOptions, - SqlBuilderOptionsTrend, -} from '../../types'; -import { DatabaseSelect } from './DatabaseSelect'; -import { isDateTimeType, isDateType } from './utils'; -import { selectors } from '../../selectors'; -import { LogLevelFieldEditor } from './LogLevelField'; -import { CoreApp } from '@grafana/data'; - -interface QueryBuilderProps { - builderOptions: SqlBuilderOptions; - onBuilderOptionsChange: (builderOptions: SqlBuilderOptions) => void; - datasource: Datasource; - format: Format; - app: CoreApp | undefined; -} - -export const QueryBuilder = (props: QueryBuilderProps) => { - const [baseFieldsList, setBaseFieldsList] = useState([]); - const [timeField, setTimeField] = useState(null); - const [logLevelField, setLogLevelField] = useState(null); - const builder = defaultsDeep(props.builderOptions, defaultCHBuilderQuery.builderOptions); - useEffect(() => { - const fetchBaseFields = async (database: string, table: string) => { - props.datasource - .fetchFieldsFull(database, table) - .then(async (fields) => { - fields.push({ name: '*', label: 'ALL', type: 'string', picklistValues: [] }); - setBaseFieldsList(fields); - - // if no filters are set, we add a default one for the time range - if (builder.filters?.length === 0) { - const dateTimeFields = fields.filter((f) => isDateTimeType(f.type)); - if (dateTimeFields.length > 0) { - const filter: Filter & PredefinedFilter = { - operator: FilterOperator.WithInGrafanaTimeRange, - filterType: 'custom', - key: dateTimeFields[0].name, - type: 'datetime', - condition: 'AND', - restrictToFields: dateTimeFields, - }; - onFiltersChange([filter]); - } - } - - // When changing from SQL Editor to Query Builder, we need to find out if the - // first value is a datetime or date, so we can change the mode to Time Series - if (builder.fields?.length > 0) { - const fieldName = builder.fields[0]; - const timeFields = fields.filter((f) => isDateType(f.type)); - const timeField = timeFields.find((x) => x.name === fieldName); - if (timeField) { - const queryOptions: SqlBuilderOptions = { - ...builder, - timeField: timeField.name, - timeFieldType: timeField.type, - mode: BuilderMode.Trend, - fields: builder.fields.slice(1, builder.fields.length), - }; - props.onBuilderOptionsChange(queryOptions); - } - } - }) - .catch((ex: any) => { - console.error(ex); - throw ex; - }); - }; - - if (builder.table) { - fetchBaseFields(builder.database, builder.table); - } - // We want to run this only when the table changes or first time load. - // If we add 'builder.fields' / 'builder.groupBy' / 'builder.metrics' / 'builder.filters' to the deps array, this will be called every time query editor changes - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.datasource, builder.table]); - - const onDatabaseChange = (database = '') => { - setBaseFieldsList([]); - setTimeField(null); - setLogLevelField(null); - const queryOptions: SqlBuilderOptions = { - ...builder, - database, - table: '', - fields: [], - filters: [], - orderBy: [], - timeField: undefined, - logLevelField: undefined, - }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onTableChange = (table = '') => { - setTimeField(null); - setLogLevelField(null); - const queryOptions: SqlBuilderOptions = { - ...builder, - table, - fields: [], - filters: [], - orderBy: [], - timeField: undefined, - logLevelField: undefined, - }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onModeChange = (mode: BuilderMode) => { - if (mode === BuilderMode.List) { - const queryOptions: SqlBuilderOptions = { ...builder, mode, fields: builder.fields || [], orderBy: [] }; - props.onBuilderOptionsChange(queryOptions); - } else if (mode === BuilderMode.Aggregate) { - const queryOptions: SqlBuilderOptions = { - ...builder, - mode, - orderBy: [], - metrics: builder.metrics || [], - }; - props.onBuilderOptionsChange(queryOptions); - } else if (mode === BuilderMode.Trend) { - const queryOptions: SqlBuilderOptionsTrend = { - ...builder, - mode: BuilderMode.Trend, - timeField: builder.timeField || '', - timeFieldType: builder.timeFieldType || 'datetime', - metrics: builder.metrics || [], - }; - props.onBuilderOptionsChange(queryOptions); - } - }; - - const onFieldsChange = (fields: string[] = []) => { - const queryOptions: SqlBuilderOptions = { ...builder, fields }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onMetricsChange = (metrics: BuilderMetricField[] = []) => { - const queryOptions: SqlBuilderOptions = { ...builder, metrics }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onFiltersChange = (filters: Filter[] = []) => { - const queryOptions: SqlBuilderOptions = { ...builder, filters }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onGroupByChange = (groupBy: string[] = []) => { - const queryOptions: SqlBuilderOptions = { ...builder, groupBy }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onTimeFieldChange = (timeField = '', timeFieldType = '') => { - setTimeField(timeField); - const queryOptions: SqlBuilderOptions = { ...builder, timeField, timeFieldType }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onLogLevelFieldChange = (logLevelField = '') => { - setLogLevelField(logLevelField); - const queryOptions: SqlBuilderOptions = { ...builder, logLevelField }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onOrderByChange = (orderBy: OrderBy[] = []) => { - const queryOptions: SqlBuilderOptions = { ...builder, orderBy }; - props.onBuilderOptionsChange(queryOptions); - }; - - const onLimitChange = (limit = 20) => { - const queryOptions: SqlBuilderOptions = { ...builder, limit }; - props.onBuilderOptionsChange(queryOptions); - }; - - const getFieldList = (): FullField[] => { - const newArray: FullField[] = []; - baseFieldsList.forEach((bf) => { - newArray.push(bf); - }); - return newArray; - }; - const fieldsList = getFieldList(); - return builder ? ( - <> -
- - -
-
- -
- {builder.mode === BuilderMode.Trend && ( - - )} - { - // Time and LogLevel fields selection for Logs Volume histogram in the Explore mode - builder.mode === BuilderMode.List && props.format === Format.LOGS && props.app === CoreApp.Explore && ( - <> - - - - ) - } - {builder.mode !== BuilderMode.Trend && ( - - )} - - {(builder.mode === BuilderMode.Aggregate || builder.mode === BuilderMode.Trend) && ( - - )} - - {(builder.mode === BuilderMode.Aggregate || builder.mode === BuilderMode.Trend) && ( - - )} - <> - - - - - ) : null; -}; diff --git a/src/components/v4/queryBuilder/QueryBuilder.tsx b/src/components/queryBuilder/QueryBuilder.tsx similarity index 92% rename from src/components/v4/queryBuilder/QueryBuilder.tsx rename to src/components/queryBuilder/QueryBuilder.tsx index 80093dcf..5c3195d5 100644 --- a/src/components/v4/queryBuilder/QueryBuilder.tsx +++ b/src/components/queryBuilder/QueryBuilder.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Datasource } from '../../../data/CHDatasource'; +import { Datasource } from 'data/CHDatasource'; import { QueryType, QueryBuilderOptions } from 'types/queryBuilder'; import { CoreApp } from '@grafana/data'; import useColumns from 'hooks/useColumns'; @@ -7,8 +7,8 @@ import { LogsQueryBuilder } from './views/LogsQueryBuilder'; import { TimeSeriesQueryBuilder } from './views/TimeSeriesQueryBuilder'; import { TableQueryBuilder } from './views/TableQueryBuilder'; import { SqlPreview } from './SqlPreview'; -import { DatabaseTableSelect } from 'components/v4/DatabaseTableSelect'; -import { QueryTypeSwitcher } from 'components/v4/QueryTypeSwitcher'; +import { DatabaseTableSelect } from 'components/queryBuilder/DatabaseTableSelect'; +import { QueryTypeSwitcher } from 'components/queryBuilder/QueryTypeSwitcher'; import { styles } from 'styles'; import { TraceQueryBuilder } from './views/TraceQueryBuilder'; diff --git a/src/components/QueryTypeSwitcher.test._tsx b/src/components/queryBuilder/QueryTypeSwitcher.test._tsx similarity index 90% rename from src/components/QueryTypeSwitcher.test._tsx rename to src/components/queryBuilder/QueryTypeSwitcher.test._tsx index 9c14d5f0..fc11c9d3 100644 --- a/src/components/QueryTypeSwitcher.test._tsx +++ b/src/components/queryBuilder/QueryTypeSwitcher.test._tsx @@ -1,8 +1,9 @@ import React from 'react'; import { render } from '@testing-library/react'; import { QueryTypeSwitcher } from './QueryTypeSwitcher'; -import { selectors } from '../selectors'; -import { QueryType, CHQuery, CHSQLQuery, Format } from '../types'; +import { selectors } from 'selectors'; +import { CHQuery, CHSqlQuery } from 'types/sql'; +import { QueryType } from 'types/queryBuilder'; const { options } = selectors.components.QueryEditor.Types; @@ -18,7 +19,7 @@ describe('QueryTypeSwitcher', () => { it('renders legacy query (query without query type)', () => { const result = render( {}} onRunQuery={() => {}} /> diff --git a/src/components/v4/QueryTypeSwitcher.tsx b/src/components/queryBuilder/QueryTypeSwitcher.tsx similarity index 77% rename from src/components/v4/QueryTypeSwitcher.tsx rename to src/components/queryBuilder/QueryTypeSwitcher.tsx index 5c9af8ab..acb730dd 100644 --- a/src/components/v4/QueryTypeSwitcher.tsx +++ b/src/components/queryBuilder/QueryTypeSwitcher.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { RadioButtonGroup, InlineFormLabel } from '@grafana/ui'; -import selectors from '../../v4/selectors'; +import labels from 'labels'; import { QueryType } from 'types/queryBuilder'; export interface QueryTypeSwitcherProps { @@ -10,19 +10,19 @@ export interface QueryTypeSwitcherProps { const options = [ { - label: selectors.types.QueryType.table, + label: labels.types.QueryType.table, value: QueryType.Table, }, { - label: selectors.types.QueryType.logs, + label: labels.types.QueryType.logs, value: QueryType.Logs, }, { - label: selectors.types.QueryType.timeSeries, + label: labels.types.QueryType.timeSeries, value: QueryType.TimeSeries, }, { - label: selectors.types.QueryType.traces, + label: labels.types.QueryType.traces, value: QueryType.Traces, }, ]; @@ -32,7 +32,7 @@ const options = [ */ export const QueryTypeSwitcher = (props: QueryTypeSwitcherProps) => { const { queryType, onChange } = props; - const { label, tooltip } = selectors.components.QueryTypeSwitcher; + const { label, tooltip } = labels.components.QueryTypeSwitcher; useEffect(() => { if (!queryType) { diff --git a/src/components/queryBuilder/Preview.test.tsx b/src/components/queryBuilder/SqlPreview.test.tsx similarity index 58% rename from src/components/queryBuilder/Preview.test.tsx rename to src/components/queryBuilder/SqlPreview.test.tsx index b430f626..27b11622 100644 --- a/src/components/queryBuilder/Preview.test.tsx +++ b/src/components/queryBuilder/SqlPreview.test.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { Preview } from './Preview'; +import { SqlPreview } from './SqlPreview'; -describe('Preview', () => { +describe('SqlPreview', () => { it('renders correctly', () => { - const result = render(); + const result = render(); expect(result.container.firstChild).not.toBeNull(); }); }); diff --git a/src/components/v4/queryBuilder/SqlPreview.tsx b/src/components/queryBuilder/SqlPreview.tsx similarity index 80% rename from src/components/v4/queryBuilder/SqlPreview.tsx rename to src/components/queryBuilder/SqlPreview.tsx index 15858d4b..1bffd7e6 100644 --- a/src/components/v4/queryBuilder/SqlPreview.tsx +++ b/src/components/queryBuilder/SqlPreview.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { InlineFormLabel } from '@grafana/ui'; -import selectors from 'v4/selectors'; +import labels from 'labels'; interface SqlPreviewProps { sql: string; @@ -8,7 +8,7 @@ interface SqlPreviewProps { export const SqlPreview = (props: SqlPreviewProps) => { const { sql } = props; - const { label, tooltip } = selectors.components.SqlPreview; + const { label, tooltip } = labels.components.SqlPreview; return (
diff --git a/src/components/v4/queryBuilder/Switch.tsx b/src/components/queryBuilder/Switch.tsx similarity index 100% rename from src/components/v4/queryBuilder/Switch.tsx rename to src/components/queryBuilder/Switch.tsx diff --git a/src/components/queryBuilder/TableSelect.tsx b/src/components/queryBuilder/TableSelect.tsx deleted file mode 100644 index c6b6bf78..00000000 --- a/src/components/queryBuilder/TableSelect.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { InlineFormLabel, Select } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { Datasource } from '../../data/CHDatasource'; -import { selectors } from './../../selectors'; -import { styles } from '../../styles'; - -export type Props = { - datasource: Datasource; - database?: string; - table?: string; - onTableChange: (value: string) => void; -}; - -export const TableSelect = (props: Props) => { - const { datasource, onTableChange, database, table } = props; - const [list, setList] = useState>>([]); - const { label, tooltip } = selectors.components.QueryEditor.QueryBuilder.FROM; - useEffect(() => { - async function fetchTables() { - const tables = await datasource.fetchTables(database); - const values = tables.map((t) => ({ label: t, value: t })); - // Add selected value to the list if it does not exist. - if (table && !tables.find((x) => x === table)) { - values.push({ label: table!, value: table! }); - } - // TODO - can't seem to reset the select to unselected - values.push({ label: '-- Choose --', value: '' }); - setList(values); - } - fetchTables(); - }, [datasource, database, table]); - - const onChange = (value: string) => { - onTableChange(value); - }; - - return ( - <> - - {label} - - - - ); -}; diff --git a/src/components/queryBuilder/TimeField.test.tsx b/src/components/queryBuilder/TimeField.test.tsx deleted file mode 100644 index 50c52440..00000000 --- a/src/components/queryBuilder/TimeField.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { TimeFieldEditor } from './TimeField'; - -describe('TimeFieldEditor', () => { - it('renders correctly', () => { - const result = render( - {}} - timeFieldType="" - timeFieldTypeCheckFn={() => true} - labelAndTooltip={{ label: 'foo', tooltip: 'bar' }} - /> - ); - expect(result.container.firstChild).not.toBeNull(); - }); -}); diff --git a/src/components/queryBuilder/TimeField.tsx b/src/components/queryBuilder/TimeField.tsx deleted file mode 100644 index 31cb3219..00000000 --- a/src/components/queryBuilder/TimeField.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import { SelectableValue } from '@grafana/data'; -import { InlineFormLabel, Select } from '@grafana/ui'; -import { selectors } from './../../selectors'; -import { FullField } from 'types'; - -interface TimeFieldEditorProps { - fieldsList: FullField[]; - timeField: string | null; - timeFieldType: string; - onTimeFieldChange: (timeField: string, timeFieldType: string) => void; - timeFieldTypeCheckFn: (type: string) => boolean; - labelAndTooltip: typeof selectors.components.QueryEditor.QueryBuilder.TIME_FIELD; -} - -export const TimeFieldEditor = (props: TimeFieldEditorProps) => { - const { label, tooltip } = props.labelAndTooltip; - const columns: SelectableValue[] = (props.fieldsList || []) - .filter((f) => props.timeFieldTypeCheckFn(f.type)) - .map((f) => ({ label: f.label, value: f.name })); - const getColumnType = (columnName: string): string => { - const matchedColumn = props.fieldsList.find((f) => f.name === columnName); - return matchedColumn ? matchedColumn.type : ''; - }; - return ( -
- - {label} - - { - it('renders correctly', () => { - const result = render( {}} />); - expect(result.container.firstChild).not.toBeNull(); - }); -}); diff --git a/src/components/queryBuilder/ModeSwitch.test.tsx b/src/components/queryBuilder/ModeSwitch.test.tsx new file mode 100644 index 00000000..f4d0bfda --- /dev/null +++ b/src/components/queryBuilder/ModeSwitch.test.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import { ModeSwitch } from './ModeSwitch'; + +describe('ModeSwitch', () => { + const labelA = 'A'; + const labelB = 'B'; + const label = 'test'; + const tooltip = 'tooltip'; + + it('should render', () => { + const result = render( + {}} + label={label} + tooltip={tooltip} + /> + ); + + expect(result.container.firstChild).not.toBeNull(); + }); + + it('should call onChange when mode is changed', () => { + const onChange = jest.fn(); + const result = render( + + ); + expect(result.container.firstChild).not.toBeNull(); + + const buttonA = result.getByText(labelA); + expect(buttonA).toBeInTheDocument(); + const buttonB = result.getByText(labelB); + expect(buttonB).toBeInTheDocument(); + + fireEvent.click(buttonB); + expect(onChange).toBeCalledTimes(1); + expect(onChange).toBeCalledWith(true); + + result.rerender( + + ); + + fireEvent.click(buttonA); + expect(onChange).toBeCalledTimes(2); + expect(onChange).toBeCalledWith(false); + }); +}); diff --git a/src/components/queryBuilder/ModeSwitch.tsx b/src/components/queryBuilder/ModeSwitch.tsx index 23cfb164..05a93856 100644 --- a/src/components/queryBuilder/ModeSwitch.tsx +++ b/src/components/queryBuilder/ModeSwitch.tsx @@ -32,7 +32,11 @@ export const ModeSwitch = (props: ModeSwitchProps) => { {label} - options={options} value={value} onChange={v => onChange(v)} /> + + options={options} + value={value} + onChange={v => onChange(v)} + />
); } diff --git a/src/components/queryBuilder/OrderByEditor.test.tsx b/src/components/queryBuilder/OrderByEditor.test.tsx index c8821b69..d8bbb14b 100644 --- a/src/components/queryBuilder/OrderByEditor.test.tsx +++ b/src/components/queryBuilder/OrderByEditor.test.tsx @@ -6,7 +6,6 @@ import { AggregateType, BuilderMode, ColumnHint, OrderByDirection, QueryType, Ta const newTestColumn = (name: string): TableColumn => ({ name, - label: name, sortable: true, type: 'String', picklistValues: [] @@ -47,26 +46,10 @@ describe('OrderByEditor', () => { /> ); expect(result.container.firstChild).not.toBeNull(); - expect(result.queryByTestId('query-builder-orderby-add-button')).not.toBeInTheDocument(); - expect(result.getByTestId('query-builder-orderby-inline-add-button')).toBeInTheDocument(); + expect(result.getByTestId('query-builder-orderby-add-button')).toBeInTheDocument(); expect(result.getByTestId('query-builder-orderby-item-wrapper')).toBeInTheDocument(); expect(result.getByTestId('query-builder-orderby-remove-button')).toBeInTheDocument(); }); - it('should be only one inline add button when multiple orderby items passed', () => { - const result = render( - {}} - /> - ); - expect(result.container.firstChild).not.toBeNull(); - expect(result.queryByTestId('query-builder-orderby-add-button')).not.toBeInTheDocument(); - expect(result.getByTestId('query-builder-orderby-inline-add-button')).toBeInTheDocument(); - }); it('should render add/remove buttons correctly when multiple orderby elements passed', () => { const result = render( { /> ); expect(result.container.firstChild).not.toBeNull(); - expect(result.queryByTestId('query-builder-orderby-add-button')).not.toBeInTheDocument(); - expect(result.getByTestId('query-builder-orderby-inline-add-button')).toBeInTheDocument(); + expect(result.queryByTestId('query-builder-orderby-add-button')).toBeInTheDocument(); expect(result.getAllByTestId('query-builder-orderby-item-wrapper').length).toBe(2); expect(result.getAllByTestId('query-builder-orderby-remove-button').length).toBe(2); }); @@ -132,7 +114,7 @@ describe('OrderByEditor', () => { expect(onOrderByChange).toBeCalledTimes(0); await userEvent.click(result.getAllByTestId('query-builder-orderby-remove-button')[1]); await userEvent.click(result.getAllByTestId('query-builder-orderby-remove-button')[0]); - await userEvent.click(result.getAllByTestId('query-builder-orderby-inline-add-button')[0]); + await userEvent.click(result.getAllByTestId('query-builder-orderby-add-button')[0]); expect(onOrderByChange).toBeCalledTimes(3); expect(onOrderByChange).toHaveBeenNthCalledWith(1, [{ name: 'foo', dir: OrderByDirection.ASC }]); expect(onOrderByChange).toHaveBeenNthCalledWith(2, [{ name: 'bar', dir: OrderByDirection.ASC }]); @@ -148,25 +130,25 @@ describe('getOrderByFields', () => { const sampleFields = [ { name: 'field1', - label: 'Field1', + label: 'field1', type: 'string', picklistValues: [], }, { name: 'field11', - label: 'Field11', + label: 'field11', type: 'string', picklistValues: [], }, { name: 'field2', - label: 'Field2', + label: 'field2', type: 'string', picklistValues: [], }, { name: 'field3', - label: 'Field3', + label: 'field3', type: 'string', picklistValues: [], }, @@ -185,19 +167,19 @@ describe('getOrderByFields', () => { ) ).toStrictEqual([ { - label: 'Field1', + label: 'field1', value: 'field1', }, { - label: 'Field11', + label: 'field11', value: 'field11', }, { - label: 'Field2', + label: 'field2', value: 'field2', }, { - label: 'Field3', + label: 'field3', value: 'field3', }, ]); @@ -320,19 +302,19 @@ describe('getOrderByFields', () => { ) ).toStrictEqual([ { - label: 'Field1', + label: 'field1', value: 'field1', }, { - label: 'Field11', + label: 'field11', value: 'field11', }, { - label: 'Field2', + label: 'field2', value: 'field2', }, { - label: 'Field3', + label: 'field3', value: 'field3', }, ]); diff --git a/src/components/queryBuilder/OrderByEditor.tsx b/src/components/queryBuilder/OrderByEditor.tsx index 966dd24b..a6a8b738 100644 --- a/src/components/queryBuilder/OrderByEditor.tsx +++ b/src/components/queryBuilder/OrderByEditor.tsx @@ -156,7 +156,7 @@ export const getOrderByFields = ( case BuilderMode.List: default: values = allColumns.map((m) => { - return { value: m.name, label: m.label }; + return { label: m.name, value: m.name }; }); } // Add selected value to the list if it does not exist. diff --git a/src/components/queryBuilder/OtelVersionSelect.test.tsx b/src/components/queryBuilder/OtelVersionSelect.test.tsx new file mode 100644 index 00000000..e874d73a --- /dev/null +++ b/src/components/queryBuilder/OtelVersionSelect.test.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { OtelVersionSelect } from './OtelVersionSelect'; + +describe('OtelVersionSelect', () => { + const testVersion = '1.0.0-test'; + + it('should render with empty properties', () => { + const result = render( + {}} + selectedVersion="" + onVersionChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + }); + + it('should render with valid properties', () => { + const result = render( + {}} + selectedVersion={testVersion} + onVersionChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + expect(result.getByText(testVersion)).toBeInTheDocument(); + }); + + it('should call onEnabledChange when the switch is enabled', () => { + const onEnabledChange = jest.fn(); + const result = render( + {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const toggle = result.getByRole('checkbox'); + expect(toggle).toBeInTheDocument(); + fireEvent.click(toggle); + expect(onEnabledChange).toBeCalledTimes(1); + expect(onEnabledChange).toBeCalledWith(true); + }); + + it('should call onEnabledChange when the switch is disabled', () => { + const onEnabledChange = jest.fn(); + const result = render( + {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const toggle = result.getByRole('checkbox'); + expect(toggle).toBeInTheDocument(); + fireEvent.click(toggle); + expect(onEnabledChange).toBeCalledTimes(1); + expect(onEnabledChange).toBeCalledWith(false); + }); + + it('should call onVersionChange when a new version is selected', () => { + const onVersionChange = jest.fn(); + const result = render( + {}} + selectedVersion={testVersion} + onVersionChange={onVersionChange} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const select = result.getByRole('combobox'); + expect(select).toBeInTheDocument(); + fireEvent.keyDown(select, { key: 'ArrowDown' }); + fireEvent.keyDown(select, { key: 'Enter' }); + expect(onVersionChange).toBeCalledTimes(1); + expect(onVersionChange).toBeCalledWith(expect.any(String)); + }); +}); diff --git a/src/components/queryBuilder/OtelVersionSelect.tsx b/src/components/queryBuilder/OtelVersionSelect.tsx index 5e7e2cf2..08100317 100644 --- a/src/components/queryBuilder/OtelVersionSelect.tsx +++ b/src/components/queryBuilder/OtelVersionSelect.tsx @@ -21,6 +21,11 @@ export const OtelVersionSelect = (props: OtelVersionSelectProps) => { value: v.version })); + const hasCurrentVersion = allVersions.find(v => v.version === selectedVersion); + if (!hasCurrentVersion) { + options.push({ label: selectedVersion, value: selectedVersion }); + } + const theme = useTheme(); const switchContainerStyle: React.CSSProperties = { padding: `0 ${theme.spacing.sm}`, diff --git a/src/components/queryBuilder/QueryBuilder.test._tsx b/src/components/queryBuilder/QueryBuilder.test.tsx similarity index 81% rename from src/components/queryBuilder/QueryBuilder.test._tsx rename to src/components/queryBuilder/QueryBuilder.test.tsx index 5206a9da..4fa70933 100644 --- a/src/components/queryBuilder/QueryBuilder.test._tsx +++ b/src/components/queryBuilder/QueryBuilder.test.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { render, waitFor } from '@testing-library/react'; import { QueryBuilder } from './QueryBuilder'; -import { Datasource } from '../../data/CHDatasource'; -import { BuilderMode, Format } from 'types'; +import { Datasource } from 'data/CHDatasource'; +import { BuilderMode, QueryType } from 'types/queryBuilder'; import { CoreApp } from '@grafana/data'; describe('QueryBuilder', () => { @@ -11,7 +11,7 @@ describe('QueryBuilder', () => { const mockDs = { settings: { jsonData: {} } } as Datasource; mockDs.fetchDatabases = jest.fn(() => Promise.resolve([])); mockDs.fetchTables = jest.fn((_db?: string) => Promise.resolve([])); - mockDs.fetchFieldsFull = jest.fn(() => { + mockDs.fetchColumnsFull = jest.fn(() => { setState(); return Promise.resolve([]); }); @@ -20,17 +20,18 @@ describe('QueryBuilder', () => { const result = await waitFor(() => render( {}} datasource={mockDs} - format={Format.AUTO} - app={CoreApp.PanelEditor} + generatedSql='' /> ) ); diff --git a/src/components/queryBuilder/QueryTypeSwitcher.test.tsx b/src/components/queryBuilder/QueryTypeSwitcher.test.tsx new file mode 100644 index 00000000..a925fe71 --- /dev/null +++ b/src/components/queryBuilder/QueryTypeSwitcher.test.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import { QueryTypeSwitcher } from './QueryTypeSwitcher'; +import labels from 'labels'; +import { QueryType } from 'types/queryBuilder'; + +const options = { + Table: labels.types.QueryType.table, + Logs: labels.types.QueryType.logs, + TimeSeries: labels.types.QueryType.timeseries, + Traces: labels.types.QueryType.traces, +}; + +describe('QueryTypeSwitcher', () => { + it('should render with default props', () => { + const result = render( + {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + expect(result.getByLabelText(options.Table)).toBeChecked(); + expect(result.getByLabelText(options.Logs)).not.toBeChecked(); + expect(result.getByLabelText(options.TimeSeries)).not.toBeChecked(); + expect(result.getByLabelText(options.Traces)).not.toBeChecked(); + }); + + it('should call onChange when a new option is selected', async () => { + const onChange = jest.fn(); + const result = render( + + ); + expect(result.container.firstChild).not.toBeNull(); + const timeSeriesButton = result.getByLabelText(options.TimeSeries); + expect(timeSeriesButton).toBeInTheDocument(); + fireEvent.click(timeSeriesButton); + expect(onChange).toBeCalledTimes(1); + expect(onChange).toBeCalledWith(QueryType.TimeSeries); + }); +}); diff --git a/src/components/queryBuilder/QueryTypeSwitcher.tsx b/src/components/queryBuilder/QueryTypeSwitcher.tsx index acb730dd..e7817d47 100644 --- a/src/components/queryBuilder/QueryTypeSwitcher.tsx +++ b/src/components/queryBuilder/QueryTypeSwitcher.tsx @@ -18,7 +18,7 @@ const options = [ value: QueryType.Logs, }, { - label: labels.types.QueryType.timeSeries, + label: labels.types.QueryType.timeseries, value: QueryType.TimeSeries, }, { diff --git a/src/components/queryBuilder/Switch.test.tsx b/src/components/queryBuilder/Switch.test.tsx new file mode 100644 index 00000000..f818cb25 --- /dev/null +++ b/src/components/queryBuilder/Switch.test.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import { Switch } from './Switch'; + +describe('Switch', () => { + const label = 'test'; + const tooltip = 'tooltip'; + + it('should render', () => { + const result = render( + {}} + label={label} + tooltip={tooltip} + /> + ); + + expect(result.container.firstChild).not.toBeNull(); + }); + + it('should call onChange when mode is changed', () => { + const onChange = jest.fn(); + const result = render( + + ); + expect(result.container.firstChild).not.toBeNull(); + + const checkbox = result.getByRole("checkbox"); + expect(checkbox).toBeInTheDocument(); + + fireEvent.click(checkbox); + expect(onChange).toBeCalledTimes(1); + expect(onChange).toBeCalledWith(true); + + result.rerender( + + ); + + fireEvent.click(checkbox); + expect(onChange).toBeCalledTimes(2); + expect(onChange).toBeCalledWith(false); + }); +}); diff --git a/src/components/queryBuilder/utils.spec.ts b/src/components/queryBuilder/utils.test.ts similarity index 76% rename from src/components/queryBuilder/utils.spec.ts rename to src/components/queryBuilder/utils.test.ts index c5c5217e..79032f85 100644 --- a/src/components/queryBuilder/utils.spec.ts +++ b/src/components/queryBuilder/utils.test.ts @@ -1,5 +1,5 @@ import { AggregateType, BuilderMode, FilterOperator, OrderByDirection } from 'types/queryBuilder'; -import { getQueryOptionsFromSql, getSQLFromQueryOptions, isDateTimeType, isDateType, isNumberType } from './utils'; +import { getQueryOptionsFromSql, getSqlFromQueryBuilderOptions, isDateTimeType, isDateType, isNumberType } from './utils'; import { ColumnHint, QueryType } from 'types/queryBuilder'; describe('isDateType', () => { @@ -117,46 +117,52 @@ describe('isNumberType', () => { }); }); -describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { +describe('Utils: getSqlFromQueryBuilderOptions and getQueryOptionsFromSql', () => { testCondition('handles a table without a database', 'SELECT "name" FROM "foo"', { + queryType: QueryType.Table, mode: BuilderMode.List, table: 'foo', - fields: ['name'], + columns: [{ name: 'name' }], }); testCondition('handles a database with a special character', 'SELECT "name" FROM "foo-bar"."buzz"', { + queryType: QueryType.Table, mode: BuilderMode.List, database: 'foo-bar', table: 'buzz', - fields: ['name'], + columns: [{ name: 'name' }], }); testCondition('handles a database and a table', 'SELECT "name" FROM "db"."foo"', { + queryType: QueryType.Table, mode: BuilderMode.List, database: 'db', table: 'foo', - fields: ['name'], + columns: [{ name: 'name' }], }); testCondition('handles a database and a table with a dot', 'SELECT "name" FROM "db"."foo.bar"', { + queryType: QueryType.Table, mode: BuilderMode.List, database: 'db', table: 'foo.bar', - fields: ['name'], + columns: [{ name: 'name' }], }); - testCondition('handles 2 fields', 'SELECT "field1", "field2" FROM "db"."foo"', { + testCondition('handles 2 columns', 'SELECT "field1", "field2" FROM "db"."foo"', { + queryType: QueryType.Table, mode: BuilderMode.List, database: 'db', table: 'foo', - fields: ['field1', 'field2'], + columns: [{ name: 'field1'}, { name: 'field2' }], }); testCondition('handles a limit', 'SELECT "field1", "field2" FROM "db"."foo" LIMIT 20', { + queryType: QueryType.Table, mode: BuilderMode.List, database: 'db', table: 'foo', - fields: ['field1', 'field2'], + columns: [{ name: 'field1'}, { name: 'field2' }], limit: 20, }); @@ -164,10 +170,11 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { 'handles empty orderBy array', 'SELECT "field1", "field2" FROM "db"."foo" LIMIT 20', { + queryType: QueryType.Table, mode: BuilderMode.List, database: 'db', table: 'foo', - fields: ['field1', 'field2'], + columns: [{ name: 'field1'}, { name: 'field2' }], orderBy: [], limit: 20, }, @@ -175,10 +182,11 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { ); testCondition('handles order by', 'SELECT "field1", "field2" FROM "db"."foo" ORDER BY field1 ASC LIMIT 20', { + queryType: QueryType.Table, mode: BuilderMode.List, database: 'db', table: 'foo', - fields: ['field1', 'field2'], + columns: [{ name: 'field1'}, { name: 'field2' }], orderBy: [{ name: 'field1', dir: OrderByDirection.ASC }], limit: 20, }); @@ -190,8 +198,8 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { mode: BuilderMode.Aggregate, database: 'db', table: '', - fields: [], - metrics: [], + columns: [], + aggregates: [], }, false ); @@ -200,43 +208,51 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { 'does not escape * field', 'SELECT * FROM "db"', { + queryType: QueryType.Table, mode: BuilderMode.Aggregate, database: 'db', table: '', - fields: ['*'], - metrics: [], + columns: [{ name: '*' }], + aggregates: [], + limit: undefined }, false ); testCondition('handles aggregation function', 'SELECT sum(field1) FROM "db"."foo"', { + queryType: QueryType.Table, mode: BuilderMode.Aggregate, database: 'db', table: 'foo', - fields: [], - metrics: [{ field: 'field1', aggregation: AggregateType.Sum }], + columns: [], + aggregates: [{ column: 'field1', aggregateType: AggregateType.Sum, alias: undefined }], + limit: undefined }); testCondition('handles aggregation with alias', 'SELECT sum(field1) total_records FROM "db"."foo"', { + queryType: QueryType.Table, mode: BuilderMode.Aggregate, database: 'db', table: 'foo', - fields: [], - metrics: [{ field: 'field1', aggregation: AggregateType.Sum, alias: 'total_records' }], + columns: [], + aggregates: [{ column: 'field1', aggregateType: AggregateType.Sum, alias: 'total_records' }], + limit: undefined }); testCondition( 'handles 2 aggregations', 'SELECT sum(field1) total_records, count(field2) total_records2 FROM "db"."foo"', { + queryType: QueryType.Table, mode: BuilderMode.Aggregate, table: 'foo', database: 'db', - fields: [], - metrics: [ - { field: 'field1', aggregation: AggregateType.Sum, alias: 'total_records' }, - { field: 'field2', aggregation: AggregateType.Count, alias: 'total_records2' }, + columns: [], + aggregates: [ + { column: 'field1', aggregateType: AggregateType.Sum, alias: 'total_records' }, + { column: 'field2', aggregateType: AggregateType.Count, alias: 'total_records2' }, ], + limit: undefined } ); @@ -247,10 +263,10 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { mode: BuilderMode.Aggregate, table: 'foo', database: 'db', - fields: [], - metrics: [ - { field: 'field1', aggregation: AggregateType.Sum, alias: 'total_records' }, - { field: 'field2', aggregation: AggregateType.Count, alias: 'total_records2' }, + columns: [], + aggregates: [ + { column: 'field1', aggregateType: AggregateType.Sum, alias: 'total_records' }, + { column: 'field2', aggregateType: AggregateType.Count, alias: 'total_records2' }, ], groupBy: ['field3'], }, @@ -258,18 +274,20 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { ); testCondition( - 'handles aggregation with groupBy with fields having group by value', + 'handles aggregation with groupBy with columns having group by value', 'SELECT field3, sum(field1) total_records, count(field2) total_records2 FROM "db"."foo" GROUP BY field3', { + queryType: QueryType.Table, mode: BuilderMode.Aggregate, table: 'foo', database: 'db', - fields: ['field3'], - metrics: [ - { field: 'field1', aggregation: AggregateType.Sum, alias: 'total_records' }, - { field: 'field2', aggregation: AggregateType.Count, alias: 'total_records2' }, + columns: [{ name: 'field3' }], + aggregates: [ + { column: 'field1', aggregateType: AggregateType.Sum, alias: 'total_records' }, + { column: 'field2', aggregateType: AggregateType.Count, alias: 'total_records2' }, ], groupBy: ['field3'], + limit: undefined } ); @@ -280,10 +298,10 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { mode: BuilderMode.Aggregate, database: 'db', table: 'foo', - fields: [], - metrics: [ - { field: 'Id', aggregation: AggregateType.Count, alias: 'count_of' }, - { field: 'Amount', aggregation: AggregateType.Sum }, + columns: [], + aggregates: [ + { column: 'Id', aggregateType: AggregateType.Count, alias: 'count_of' }, + { column: 'Amount', aggregateType: AggregateType.Sum, alias: undefined }, ], groupBy: ['StageName', 'Type'], orderBy: [ @@ -298,11 +316,12 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { 'handles aggregation with a IN filter', `SELECT count(id) FROM "db"."foo" WHERE ( stagename IN ('Deal Won', 'Deal Lost' ) )`, { + queryType: QueryType.Table, mode: BuilderMode.Aggregate, database: 'db', table: 'foo', - fields: [], - metrics: [{ field: 'id', aggregation: AggregateType.Count }], + columns: [], + aggregates: [{ column: 'id', aggregateType: AggregateType.Count, alias: undefined }], filters: [ { key: 'stagename', @@ -311,6 +330,7 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { type: 'string', }, ], + limit: undefined } ); @@ -318,11 +338,12 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { 'handles aggregation with a NOT IN filter', `SELECT count(id) FROM "db"."foo" WHERE ( stagename NOT IN ('Deal Won', 'Deal Lost' ) )`, { + queryType: QueryType.Table, mode: BuilderMode.Aggregate, database: 'db', table: 'foo', - fields: [], - metrics: [{ field: 'id', aggregation: AggregateType.Count }], + columns: [], + aggregates: [{ column: 'id', aggregateType: AggregateType.Count, alias: undefined }], filters: [ { key: 'stagename', @@ -331,6 +352,7 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { type: 'string', }, ], + limit: undefined } ); @@ -338,11 +360,12 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { 'handles aggregation with datetime filter', `SELECT count(id) FROM "db"."foo" WHERE ( createddate >= $__fromTime AND createddate <= $__toTime )`, { + queryType: QueryType.Table, mode: BuilderMode.Aggregate, database: 'db', table: 'foo', - fields: [], - metrics: [{ field: 'id', aggregation: AggregateType.Count }], + columns: [], + aggregates: [{ column: 'id', aggregateType: AggregateType.Count, alias: undefined }], filters: [ { key: 'createddate', @@ -350,6 +373,7 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { type: 'datetime', }, ], + limit: undefined } ); @@ -357,11 +381,12 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { 'handles aggregation with date filter', `SELECT count(id) FROM "db"."foo" WHERE ( NOT ( closedate >= $__fromTime AND closedate <= $__toTime ) )`, { + queryType: QueryType.Table, mode: BuilderMode.Aggregate, database: 'db', table: 'foo', - fields: [], - metrics: [{ field: 'id', aggregation: AggregateType.Count }], + columns: [], + aggregates: [{ column: 'id', aggregateType: AggregateType.Count, alias: undefined }], filters: [ { key: 'closedate', @@ -369,6 +394,7 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { type: 'datetime', }, ], + limit: undefined } ); @@ -376,13 +402,12 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { 'handles timeseries function with "timeFieldType: DateType"', 'SELECT $__timeInterval(time) as time FROM "db"."foo" WHERE $__timeFilter(time) GROUP BY time ORDER BY time ASC', { + queryType: QueryType.TimeSeries, mode: BuilderMode.Trend, database: 'db', table: 'foo', - fields: [], - timeField: 'time', - timeFieldType: 'datetime', - metrics: [], + columns: [{ name: 'time', type: 'datetime', hint: ColumnHint.Time }], + aggregates: [], filters: [], }, false @@ -392,13 +417,12 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { 'handles timeseries function with "timeFieldType: DateType" with a filter', 'SELECT $__timeInterval(time) as time FROM "db"."foo" WHERE $__timeFilter(time) AND ( base IS NOT NULL ) GROUP BY time ORDER BY time ASC', { + queryType: QueryType.TimeSeries, mode: BuilderMode.Trend, database: 'db', table: 'foo', - fields: [], - timeField: 'time', - timeFieldType: 'datetime', - metrics: [], + columns: [{ name: 'time', type: 'datetime', hint: ColumnHint.Time }], + aggregates: [], filters: [ { condition: 'AND', @@ -415,7 +439,7 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { it('timeseries function throws if "timeFieldType" not a DateType', () => { expect(() => - getSQLFromQueryOptions('db', 'foo', { + getSqlFromQueryBuilderOptions({ database: 'db', table: 'foo', queryType: QueryType.TimeSeries, @@ -430,7 +454,7 @@ describe('Utils: getSQLFromQueryOptions and getQueryOptionsFromSql', () => { function testCondition(name: string, sql: string, builder: any, testQueryOptionsFromSql = true) { it(name, () => { - expect(getSQLFromQueryOptions('db', 'foo', builder)).toBe(sql); + expect(getSqlFromQueryBuilderOptions(builder)).toBe(sql); if (testQueryOptionsFromSql) { expect(getQueryOptionsFromSql(sql)).toEqual(builder); } diff --git a/src/components/queryBuilder/utils.ts b/src/components/queryBuilder/utils.ts index 4cea2254..7e464dd0 100644 --- a/src/components/queryBuilder/utils.ts +++ b/src/components/queryBuilder/utils.ts @@ -29,6 +29,7 @@ import { ColumnHint, SelectedColumn as CHSelectedColumn, StringFilter, + QueryType, } from 'types/queryBuilder'; import { sqlToStatement } from 'data/ast'; @@ -228,7 +229,8 @@ const getLimit = (limit?: number): string => { return ` LIMIT ` + (limit || 100); }; -export const getSQLFromQueryOptions = (database: string, table: string, options: QueryBuilderOptions): string => { +export const getSqlFromQueryBuilderOptions = (options: QueryBuilderOptions): string => { + const { database, table } = options; const limit = options.limit ? getLimit(options.limit) : ''; let query = ``; switch (options.mode) { @@ -297,32 +299,34 @@ export function getQueryOptionsFromSql(sql: string): QueryBuilderOptions | strin } const fromTable = ast.from[0] as FromTable; - const fieldsAndMetrics = getMetricsFromAst(ast.columns ? ast.columns : null); + const columnsAndAggregates = getAggregatesFromAst(ast.columns ? ast.columns : null); let builder = { + queryType: QueryType.Table, mode: BuilderMode.List, database: fromTable.name.schema, table: fromTable.name.name, } as QueryBuilderOptions; - if (fieldsAndMetrics.fields) { - builder.columns = fieldsAndMetrics.fields.map(f => ({ name: f })); + if (columnsAndAggregates.columns) { + builder.columns = columnsAndAggregates.columns.map(f => ({ name: f })); } - if (fieldsAndMetrics.metrics.length > 0) { + if (columnsAndAggregates.aggregates.length > 0) { builder.mode = BuilderMode.Aggregate; - builder.aggregates = fieldsAndMetrics.metrics; + builder.aggregates = columnsAndAggregates.aggregates; } - if (fieldsAndMetrics.timeField) { + if (columnsAndAggregates.timeField) { + builder.queryType = QueryType.TimeSeries; builder.mode = BuilderMode.Trend; const columns: CHSelectedColumn[] = builder.columns || []; - columns.push({ name: fieldsAndMetrics.timeField, type: 'datetime', hint: ColumnHint.Time }); + columns.push({ name: columnsAndAggregates.timeField, type: 'datetime', hint: ColumnHint.Time }); builder.columns = columns; } if (ast.where) { - builder.filters = getFiltersFromAst(ast.where, fieldsAndMetrics.timeField); + builder.filters = getFiltersFromAst(ast.where, columnsAndAggregates.timeField); } const orderBy = ast.orderBy @@ -535,39 +539,39 @@ function selectCallFunc(s: SelectedColumn): AggregateColumn | string { return fields[0]; } -function getMetricsFromAst(selectClauses: SelectedColumn[] | null): { +function getAggregatesFromAst(selectClauses: SelectedColumn[] | null): { timeField: string; - metrics: AggregateColumn[]; - fields: string[]; + aggregates: AggregateColumn[]; + columns: string[]; } { if (!selectClauses) { - return { timeField: '', metrics: [], fields: [] }; + return { timeField: '', aggregates: [], columns: [] }; } - const metrics: AggregateColumn[] = []; - const fields: string[] = []; + const aggregates: AggregateColumn[] = []; + const columns: string[] = []; let timeField = ''; for (let s of selectClauses) { switch (s.expr.type) { case 'ref': - fields.push(s.expr.name); + columns.push(s.expr.name); break; case 'call': const f = selectCallFunc(s); if (!f) { - return { timeField: '', metrics: [], fields: [] }; + return { timeField: '', aggregates: [], columns: [] }; } if (isString(f)) { timeField = f; } else { - metrics.push(f); + aggregates.push(f); } break; default: - return { timeField: '', metrics: [], fields: [] }; + return { timeField: '', aggregates: [], columns: [] }; } } - return { timeField, metrics, fields }; + return { timeField, aggregates, columns }; } function formatStringValue(currentFilter: string): string { diff --git a/src/data/CHDatasource.test.ts b/src/data/CHDatasource.test.ts index 09ccd9e1..28f0ba50 100644 --- a/src/data/CHDatasource.test.ts +++ b/src/data/CHDatasource.test.ts @@ -317,8 +317,8 @@ describe('ClickHouseDatasource', () => { editorType: EditorType.Builder, rawSql: 'SELECT * FROM system.numbers LIMIT 1', builderOptions: { - database: 'system', - table: 'numbers', + database: 'default', + table: 'logs', queryType: QueryType.Logs, mode: BuilderMode.List, columns: [ @@ -351,11 +351,14 @@ describe('ClickHouseDatasource', () => { describe('getSupplementaryLogsVolumeQuery', () => { it('should return undefined if any of the conditions are not met', async () => { - [QueryType.Table, QueryType.Logs, QueryType.TimeSeries, QueryType.Traces].forEach(queryType => { + [QueryType.Table, QueryType.TimeSeries, QueryType.Traces].forEach(queryType => { expect( datasource.getSupplementaryLogsVolumeQuery(request, { ...query, - queryType, + builderOptions: { + ...query.builderOptions, + queryType + } }) ).toBeUndefined(); }); @@ -363,7 +366,10 @@ describe('ClickHouseDatasource', () => { expect( datasource.getSupplementaryLogsVolumeQuery(request, { ...query, - builderOptions: { mode } as any, + builderOptions: { + ...query.builderOptions, + mode + }, }) ).toBeUndefined(); }); @@ -379,7 +385,7 @@ describe('ClickHouseDatasource', () => { builderOptions: { ...query.builderOptions, database: '', - } as QueryBuilderOptions, + }, }) ).toBeUndefined(); expect( @@ -388,7 +394,7 @@ describe('ClickHouseDatasource', () => { builderOptions: { ...query.builderOptions, table: '', - } as QueryBuilderOptions, + }, }) ).toBeUndefined(); expect( @@ -396,7 +402,7 @@ describe('ClickHouseDatasource', () => { ...query, builderOptions: { ...query.builderOptions, - timeField: undefined, + columns: query.builderOptions.columns?.filter(c => c.hint !== ColumnHint.Time) } as QueryBuilderOptions, }) ).toBeUndefined(); @@ -410,7 +416,7 @@ describe('ClickHouseDatasource', () => { ...query, builderOptions: { ...query.builderOptions, - logLevelField: undefined, + columns: query.builderOptions.columns?.filter(c => c.hint !== ColumnHint.LogLevel) } as QueryBuilderOptions, }); expect(result?.rawSql).toEqual( diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index be3bc3fd..a69261b4 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -40,7 +40,7 @@ import { queryLogsVolume, TIME_FIELD_ALIAS, } from './logs'; -import { getSQLFromQueryOptions, getColumnByHint } from '../components/queryBuilder/utils'; +import { getSqlFromQueryBuilderOptions, getColumnByHint } from '../components/queryBuilder/utils'; export class Datasource extends DataSourceWithBackend @@ -115,11 +115,11 @@ export class Datasource getSupplementaryLogsVolumeQuery(logsVolumeRequest: DataQueryRequest, query: CHQuery): CHQuery | undefined { if ( query.editorType !== EditorType.Builder || - query.queryType !== QueryType.Logs || + query.builderOptions.queryType !== QueryType.Logs || query.builderOptions.mode !== BuilderMode.List || getColumnByHint(query.builderOptions, ColumnHint.Time) === undefined || - query.builderOptions.database === undefined || - query.builderOptions.table === undefined + query.builderOptions.database === '' || + query.builderOptions.table === '' ) { return undefined; } @@ -167,7 +167,7 @@ export class Datasource ], }; - const logVolumeSupplementaryQuery = getSQLFromQueryOptions(query.builderOptions.database, query.builderOptions.table, logVolumeSqlBuilderOptions); + const logVolumeSupplementaryQuery = getSqlFromQueryBuilderOptions(logVolumeSqlBuilderOptions); return { // format: Format.AUTO, // selectedFormat: Format.AUTO, @@ -313,7 +313,7 @@ export class Datasource return { ...query, // the query is updated to trigger the URL update and propagation to the panels - rawSql: getSQLFromQueryOptions(query.builderOptions.database, query.builderOptions.table, updatedBuilder), + rawSql: getSqlFromQueryBuilderOptions(updatedBuilder), builderOptions: updatedBuilder, }; } diff --git a/src/hooks/useColumns.test.ts b/src/hooks/useColumns.test.ts new file mode 100644 index 00000000..da62f618 --- /dev/null +++ b/src/hooks/useColumns.test.ts @@ -0,0 +1,61 @@ +import { renderHook } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; +import { Datasource } from 'data/CHDatasource'; +import useColumns from './useColumns'; +import { TableColumn } from 'types/queryBuilder'; + +describe('useColumns', () => { + const defaultColumnCount = 1; // the "all" column is always in the array + + it('should return empty array if datasource is invalid', async () => { + let result: { current: TableColumn[] }; + await act(async () => { + const r = renderHook(() => useColumns(undefined!, 'db', 'table')); + result = r.result; + }); + + expect(result!.current).toHaveLength(defaultColumnCount); + }); + + it('should return empty array if database string is empty', async () => { + const mockDs = {} as Datasource; + mockDs.fetchColumnsFull = jest.fn((db: string, table: string) => Promise.resolve([])); + let result: { current: TableColumn[] }; + await act(async () => { + const r = renderHook(() => useColumns(mockDs, '', 'table')); + result = r.result; + }); + + expect(result!.current).toHaveLength(defaultColumnCount); + }); + + it('should return empty array if table string is empty', async () => { + const mockDs = {} as Datasource; + mockDs.fetchColumnsFull = jest.fn((db: string, table: string) => Promise.resolve([])); + let result: { current: TableColumn[] }; + await act(async () => { + const r = renderHook(() => useColumns(mockDs, 'db', '')); + result = r.result; + }); + + expect(result!.current).toHaveLength(defaultColumnCount); + }); + + it('should fetch table columns', async () => { + const mockDs = {} as Datasource; + mockDs.fetchColumnsFull = jest.fn( + (db: string, table: string) => Promise.resolve([ + { name: 'a', type: 'string', picklistValues: [] }, + { name: 'b', type: 'string', picklistValues: [] }, + // { name: '*' } (an "all" column is added by the hook) + ])); + + let result: { current: TableColumn[] }; + await act(async () => { + const r = renderHook(() => useColumns(mockDs, 'db', 'table')); + result = r.result; + }); + + expect(result!.current).toHaveLength(3); + }); +}); diff --git a/src/hooks/useColumns.tsx b/src/hooks/useColumns.ts similarity index 56% rename from src/hooks/useColumns.tsx rename to src/hooks/useColumns.ts index ed165d49..49f13ee1 100644 --- a/src/hooks/useColumns.tsx +++ b/src/hooks/useColumns.ts @@ -8,21 +8,19 @@ export default (datasource: Datasource, database: string, table: string): TableC const [columns, setColumns] = useState([allColumn]); useEffect(() => { - const fetchTableColumns = async () => { - datasource - .fetchColumnsFull(database, table) - .then(columns => { - columns.push(allColumn); - setColumns(columns); - }).catch((ex: any) => { - console.error(ex); - throw ex; - }); - }; + if (!datasource || !database || !table) { + return; + } - if (database && table) { - fetchTableColumns(); - } + datasource + .fetchColumnsFull(database, table) + .then(columns => { + columns.push(allColumn); + setColumns(columns); + }).catch((ex: any) => { + console.error(ex); + throw ex; + }); }, [datasource, database, table]); return columns; diff --git a/src/hooks/useDatabases.test.ts b/src/hooks/useDatabases.test.ts new file mode 100644 index 00000000..fc18c7d4 --- /dev/null +++ b/src/hooks/useDatabases.test.ts @@ -0,0 +1,29 @@ +import { renderHook } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; +import { Datasource } from 'data/CHDatasource'; +import useDatabases from './useDatabases'; + +describe('useDatabases', () => { + it('should return empty array if invalid datasource is provided', async () => { + let result: { current: string[] }; + await act(async () => { + const r = renderHook(() => useDatabases(undefined!)); + result = r.result; + }); + + expect(result!.current).toHaveLength(0); + }); + + it('should fetch databases', async () => { + const mockDs = {} as Datasource; + mockDs.fetchDatabases = jest.fn(() => Promise.resolve(['a', 'b'])); + + let result: { current: string[] }; + await act(async () => { + const r = renderHook(() => useDatabases(mockDs)); + result = r.result; + }); + + expect(result!.current).toHaveLength(2); + }); +}); diff --git a/src/hooks/useDatabases.ts b/src/hooks/useDatabases.ts new file mode 100644 index 00000000..0f6847f6 --- /dev/null +++ b/src/hooks/useDatabases.ts @@ -0,0 +1,22 @@ +import { useState, useEffect } from 'react'; +import { Datasource } from 'data/CHDatasource'; + +export default (datasource: Datasource): string[] => { + const [databases, setDatabases] = useState([]); + + useEffect(() => { + if (!datasource) { + return; + } + + datasource. + fetchDatabases(). + then(databases => setDatabases(databases)). + catch((ex: any) => { + console.error(ex); + throw ex; + }); + }, [datasource]); + + return databases; +} diff --git a/src/hooks/useDatabases.tsx b/src/hooks/useDatabases.tsx deleted file mode 100644 index 043ba20a..00000000 --- a/src/hooks/useDatabases.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Datasource } from 'data/CHDatasource'; - -export default (datasource: Datasource): string[] => { - const [databases, setDatabases] = useState([]); - - useEffect(() => { - const fetchDatabases = async () => { - datasource. - fetchDatabases(). - then(databases => setDatabases(databases)). - catch((ex: any) => { - console.error(ex); - throw ex; - }); - }; - - if (datasource) { - fetchDatabases(); - } - }, [datasource]); - - return databases; -} diff --git a/src/hooks/useTables.test.ts b/src/hooks/useTables.test.ts new file mode 100644 index 00000000..51ea182e --- /dev/null +++ b/src/hooks/useTables.test.ts @@ -0,0 +1,42 @@ +import { renderHook } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; +import { Datasource } from 'data/CHDatasource'; +import useTables from './useTables'; + +describe('useTables', () => { + it('should return empty array if invalid datasource is provided', async () => { + let result: { current: string[] }; + await act(async () => { + const r = renderHook(() => useTables(undefined!, 'db')); + result = r.result; + }); + + expect(result!.current).toHaveLength(0); + }); + + it('should return empty array if empty database string is provided', async () => { + const mockDs = {} as Datasource; + mockDs.fetchTables = jest.fn((db: string) => Promise.resolve(['a', 'b'])); + + let result: { current: string[] }; + await act(async () => { + const r = renderHook(() => useTables(mockDs, '')); + result = r.result; + }); + + expect(result!.current).toHaveLength(0); + }); + + it('should fetch tables', async () => { + const mockDs = {} as Datasource; + mockDs.fetchTables = jest.fn((db: string) => Promise.resolve(['a', 'b'])); + + let result: { current: string[] }; + await act(async () => { + const r = renderHook(() => useTables(mockDs, 'db')); + result = r.result; + }); + + expect(result!.current).toHaveLength(2); + }); +}); diff --git a/src/hooks/useTables.tsx b/src/hooks/useTables.ts similarity index 93% rename from src/hooks/useTables.tsx rename to src/hooks/useTables.ts index 5e772456..cca65429 100644 --- a/src/hooks/useTables.tsx +++ b/src/hooks/useTables.ts @@ -5,7 +5,7 @@ export default (datasource: Datasource, database: string): string[] => { const [tables, setTables] = useState([]); useEffect(() => { - if (!database) { + if (!datasource || !database) { return; } diff --git a/src/labels.ts b/src/labels.ts index aa8f4a47..432b2c46 100644 --- a/src/labels.ts +++ b/src/labels.ts @@ -157,7 +157,7 @@ export default { QueryType: { table: 'Table', logs: 'Logs', - timeSeries: 'Time Series', + timeseries: 'Time Series', traces: 'Traces', } } diff --git a/src/selectors.ts b/src/selectors.ts index b97c5985..d238acdb 100644 --- a/src/selectors.ts +++ b/src/selectors.ts @@ -190,6 +190,23 @@ export const Components = { }, }, }, + QueryBuilder: { + AggregateEditor: { + sectionLabel: 'query-builder-aggregate-editor-section-label', + itemWrapper: 'query-builder-aggregate-editor-item-wrapper', + itemRemoveButton: 'query-builder-aggregate-editor-item-remove-button', + addButton: 'query-builder-aggregate-editor-add-button', + }, + ColumnsEditor: { + multiSelectWrapper: 'query-builder-columns-editor-multi-select-wrapper' + }, + GroupByEditor: { + multiSelectWrapper: 'query-builder-group-by-multi-select-wrapper' + }, + LimitEditor: { + input: 'query-builder-limit-editor-input' + } + } }; export const selectors: { components: E2ESelectors } = { components: Components, diff --git a/src/tracking.test.ts b/src/tracking.test.ts index 95021b38..380c46b8 100644 --- a/src/tracking.test.ts +++ b/src/tracking.test.ts @@ -26,7 +26,6 @@ describe('analyzeQueries', () => { queries: [{ editorType: EditorType.SQL, queryType: QueryType.Table }], expectedCounters: { sql_queries: 1, - sql_query_format_auto: 0, sql_query_format_table: 1, sql_query_format_logs: 0, sql_query_format_time_series: 0, @@ -42,7 +41,6 @@ describe('analyzeQueries', () => { queries: [{ editorType: EditorType.SQL, queryType: QueryType.Logs }], expectedCounters: { sql_queries: 1, - sql_query_format_auto: 0, sql_query_format_table: 0, sql_query_format_logs: 1, sql_query_format_time_series: 0, @@ -58,7 +56,6 @@ describe('analyzeQueries', () => { queries: [{ editorType: EditorType.SQL, queryType: QueryType.TimeSeries }], expectedCounters: { sql_queries: 1, - sql_query_format_auto: 0, sql_query_format_table: 0, sql_query_format_logs: 0, sql_query_format_time_series: 1, @@ -74,7 +71,6 @@ describe('analyzeQueries', () => { queries: [{ editorType: EditorType.SQL, queryType: QueryType.Traces }], expectedCounters: { sql_queries: 1, - sql_query_format_auto: 0, sql_query_format_table: 0, sql_query_format_logs: 0, sql_query_format_time_series: 0, @@ -90,7 +86,6 @@ describe('analyzeQueries', () => { queries: [{ editorType: EditorType.Builder, builderOptions: { mode: BuilderMode.List } }], expectedCounters: { sql_queries: 0, - sql_query_format_auto: 0, sql_query_format_table: 0, sql_query_format_logs: 0, sql_query_format_time_series: 0, @@ -106,7 +101,6 @@ describe('analyzeQueries', () => { queries: [{ editorType: EditorType.Builder, builderOptions: { mode: BuilderMode.Aggregate } }], expectedCounters: { sql_queries: 0, - sql_query_format_auto: 0, sql_query_format_table: 0, sql_query_format_logs: 0, sql_query_format_time_series: 0, @@ -122,7 +116,6 @@ describe('analyzeQueries', () => { queries: [{ editorType: EditorType.Builder, builderOptions: { mode: BuilderMode.Trend } }], expectedCounters: { sql_queries: 0, - sql_query_format_auto: 0, sql_query_format_table: 0, sql_query_format_logs: 0, sql_query_format_time_series: 0, diff --git a/src/types/queryBuilder.ts b/src/types/queryBuilder.ts index 57e2a8b2..33e0d0e3 100644 --- a/src/types/queryBuilder.ts +++ b/src/types/queryBuilder.ts @@ -90,7 +90,6 @@ interface TableColumnPickListItem { */ export interface TableColumn { name: string; - label: string; type: string; picklistValues: TableColumnPickListItem[]; filterable?: boolean; diff --git a/src/views/CHQueryEditor.tsx b/src/views/CHQueryEditor.tsx index a0b70196..c42575ba 100644 --- a/src/views/CHQueryEditor.tsx +++ b/src/views/CHQueryEditor.tsx @@ -8,7 +8,7 @@ import { CHQuery, EditorType, CHBuilderQuery, defaultCHBuilderQuery } from 'type import { CHConfig } from 'types/config'; import { QueryBuilderOptions } from 'types/queryBuilder'; import { QueryBuilder } from 'components/queryBuilder/QueryBuilder'; -import { getSQLFromQueryOptions } from 'components/queryBuilder/utils'; +import { getSqlFromQueryBuilderOptions } from 'components/queryBuilder/utils'; import { SqlEditor } from 'components/SqlEditor'; export type CHQueryEditorProps = QueryEditorProps; @@ -33,7 +33,7 @@ export const CHQueryEditor = (props: CHQueryEditorProps) => { const CHEditorByType = (props: CHQueryEditorProps) => { const { query, onChange, app } = props; const onBuilderOptionsChange = (builderOptions: QueryBuilderOptions) => { - const sql = getSQLFromQueryOptions(builderOptions.database, builderOptions.table, builderOptions); + const sql = getSqlFromQueryBuilderOptions(builderOptions); onChange({ ...query, editorType: EditorType.Builder, rawSql: sql, builderOptions }); }; diff --git a/yarn.lock b/yarn.lock index 3c519d44..1f959722 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,6 +50,14 @@ dependencies: "@babel/highlight" "^7.18.6" +"@babel/code-frame@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.0.tgz#c241dc454e5b5917e40d37e525e2f4530c399298" @@ -60,6 +68,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.13.tgz#6aff7b350a1e8c3e40b029e46cbe78e24a913483" integrity sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw== +"@babel/compat-data@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" + integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== + "@babel/core@7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.5.tgz#45e2114dc6cd4ab167f81daf7820e8fa1250d113" @@ -81,6 +94,27 @@ json5 "^2.2.1" semver "^6.3.0" +"@babel/core@^7.0.1": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.19.tgz#b38162460a6f3baf2a424bda720b24a8aafea241" + integrity sha512-Q8Yj5X4LHVYTbLCKVz0//2D2aDmHF4xzCdEttYvKOnWvErGsa6geHXD6w46x64n5tP69VfeH+IfSrdyH3MLhwA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.22.15" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.22.19" + "@babel/helpers" "^7.22.15" + "@babel/parser" "^7.22.16" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.22.19" + "@babel/types" "^7.22.19" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.16.7": version "7.21.3" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.3.tgz#cf1c877284a469da5d1ce1d1e53665253fae712e" @@ -102,6 +136,16 @@ json5 "^2.2.2" semver "^6.3.0" +"@babel/generator@^7.17.9", "@babel/generator@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.15.tgz#1564189c7ec94cb8f77b5e8a90c4d200d21b2339" + integrity sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA== + dependencies: + "@babel/types" "^7.22.15" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/generator@^7.18.13": version "7.18.13" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.13.tgz#59550cbb9ae79b8def15587bdfbaa388c4abf212" @@ -164,6 +208,17 @@ lru-cache "^5.1.1" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" + integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== + dependencies: + "@babel/compat-data" "^7.22.9" + "@babel/helper-validator-option" "^7.22.15" + browserslist "^4.21.9" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.18.6": version "7.18.13" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.13.tgz#63e771187bd06d234f95fdf8bd5f8b6429de6298" @@ -232,6 +287,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== +"@babel/helper-environment-visitor@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" + integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== + "@babel/helper-explode-assignable-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" @@ -255,6 +315,14 @@ "@babel/template" "^7.20.7" "@babel/types" "^7.21.0" +"@babel/helper-function-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" + integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== + dependencies: + "@babel/template" "^7.22.5" + "@babel/types" "^7.22.5" + "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" @@ -262,6 +330,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-member-expression-to-functions@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" @@ -290,6 +365,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + "@babel/helper-module-transforms@^7.18.6": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz#5a1079c005135ed627442df31a42887e80fcb712" @@ -318,6 +400,17 @@ "@babel/traverse" "^7.21.2" "@babel/types" "^7.21.2" +"@babel/helper-module-transforms@^7.22.19": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.19.tgz#94b1f281caa6518f02ec0f5ea2b5348e298ce266" + integrity sha512-m6h1cJvn+OJ+R3jOHp30faq5xKJ7VbjwDj5RGgHuRlU9hrMeKsGC+JpihkR5w1g7IfseCPPtZ0r7/hB4UKaYlA== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.19" + "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" @@ -397,6 +490,13 @@ dependencies: "@babel/types" "^7.20.2" +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818" @@ -418,6 +518,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-string-parser@^7.18.10": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" @@ -428,6 +535,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + "@babel/helper-validator-identifier@^7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" @@ -453,11 +565,21 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.22.19", "@babel/helper-validator-identifier@^7.22.5": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.19.tgz#2f34ab1e445f5b95e2e6edfe50ea2449e610583a" + integrity sha512-Tinq7ybnEPFFXhlYOYFiSjespWQk0dq2dRNAiMdRTOYQzEGqnnNyrTxPYHP5r6wGjlF1rFgABdDV0g8EwD6Qbg== + "@babel/helper-validator-option@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== +"@babel/helper-validator-option@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" + integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== + "@babel/helper-wrap-function@^7.18.9": version "7.18.11" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz#bff23ace436e3f6aefb61f85ffae2291c80ed1fb" @@ -477,6 +599,15 @@ "@babel/traverse" "^7.21.0" "@babel/types" "^7.21.0" +"@babel/helpers@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.15.tgz#f09c3df31e86e3ea0b7ff7556d85cdebd47ea6f1" + integrity sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.22.15" + "@babel/types" "^7.22.15" + "@babel/highlight@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c" @@ -513,11 +644,25 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.13.tgz#9cda839e5d3be9ca9e8c26b6dd69e7548f0cbf16" + integrity sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ== + dependencies: + "@babel/helper-validator-identifier" "^7.22.5" + chalk "^2.4.2" + js-tokens "^4.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.12.13": version "7.12.14" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.14.tgz#4adb7c5eef1d437ef965ad1569cd826db8c11dc9" integrity sha512-xcfxDq3OrBnDsA/Z8eK5/2iPcLD8qbOaSSfOw4RA6jp4i7e6dEQ7+wTwxItEwzcXPQcsry5nZk96gmVPKletjQ== +"@babel/parser@^7.13.0", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16": + version "7.22.16" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" + integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== + "@babel/parser@^7.14.7", "@babel/parser@^7.20.5", "@babel/parser@^7.20.7", "@babel/parser@^7.21.3": version "7.21.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3" @@ -1232,6 +1377,15 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" +"@babel/template@^7.22.15", "@babel/template@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + "@babel/template@^7.3.3": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" @@ -1241,6 +1395,22 @@ "@babel/parser" "^7.12.13" "@babel/types" "^7.12.13" +"@babel/traverse@^7.17.9", "@babel/traverse@^7.22.15", "@babel/traverse@^7.22.19": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.19.tgz#bb2b12b7de9d7fec9e812ed89eea097b941954f8" + integrity sha512-ZCcpVPK64krfdScRbpxF6xA5fz7IOsfMwx1tcACvCzt6JY+0aHkBk7eIU8FRDSZRU5Zei6Z4JfgAxN1bqXGECg== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.22.15" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.22.16" + "@babel/types" "^7.22.19" + debug "^4.1.0" + globals "^11.1.0" + "@babel/traverse@^7.18.11", "@babel/traverse@^7.18.9": version "7.18.13" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.13.tgz#5ab59ef51a997b3f10c4587d648b9696b6cb1a68" @@ -1308,6 +1478,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684" + integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.19" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1318,6 +1497,11 @@ resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.1.tgz#45ff061b9ded1c6e4474b33b336ebb1b986b825a" integrity sha512-zr9Qs9KFQiEvMWdZesjcmRJlUck5NR+eKGS1uyKk+oYTWwlYrsoPEi6VmG6/TzBD1hKCGEimrhTgGS6hvn/xIQ== +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + "@cspell/cspell-bundled-dicts@6.30.2": version "6.30.2" resolved "https://registry.yarnpkg.com/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-6.30.2.tgz#cfab603ac339b72ba4fc8535ab9a5f5153d3019d" @@ -1634,6 +1818,56 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@cypress/request@^2.88.10": + version "2.88.12" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.12.tgz#ba4911431738494a85e93fb04498cb38bc55d590" + integrity sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + http-signature "~1.3.6" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + performance-now "^2.1.0" + qs "~6.10.3" + safe-buffer "^5.1.2" + tough-cookie "^4.1.3" + tunnel-agent "^0.6.0" + uuid "^8.3.2" + +"@cypress/webpack-preprocessor@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.16.0.tgz#68dbd8ba0fb4d14e8f3d346fa31a567617a5134d" + integrity sha512-cQM0pPMZ/3DjA8ohFL5vBOrkD7CCpk7nH8k0ML4pLJrN6WDeIHRWuFQRISZoNXom4KP2M0kJNxJ7fCV7EbOYIg== + dependencies: + "@babel/core" "^7.0.1" + "@babel/generator" "^7.17.9" + "@babel/parser" "^7.13.0" + "@babel/traverse" "^7.17.9" + bluebird "3.7.1" + debug "^4.3.2" + fs-extra "^10.1.0" + loader-utils "^2.0.0" + lodash "^4.17.20" + md5 "2.3.0" + source-map "^0.6.1" + webpack-virtual-modules "^0.4.4" + +"@cypress/xvfb@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a" + integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q== + dependencies: + debug "^3.1.0" + lodash.once "^4.1.1" + "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" @@ -3181,7 +3415,7 @@ "@jest/create-cache-key-function" "^27.4.2" jsonc-parser "^3.2.0" -"@testing-library/dom@>=7", "@testing-library/dom@^8.0.0": +"@testing-library/dom@>=7": version "8.11.1" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.11.1.tgz#03fa2684aa09ade589b460db46b4c7be9fc69753" integrity sha512-3KQDyx9r0RKYailW2MiYrSSKEfH0GTkI51UGEvJenvcoDoeRYs0PZpi2SXqtnMClQvCqdtTTpOfFETDTVADpAg== @@ -3195,6 +3429,20 @@ lz-string "^1.4.4" pretty-format "^27.0.2" +"@testing-library/dom@^9.0.0": + version "9.3.1" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.1.tgz#8094f560e9389fb973fe957af41bf766937a9ee9" + integrity sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "5.1.3" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + pretty-format "^27.0.2" + "@testing-library/jest-dom@^5.16.2": version "5.16.5" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" @@ -3210,19 +3458,19 @@ lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react@^12.1.4": - version "12.1.5" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" - integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg== +"@testing-library/react@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.0.0.tgz#59030392a6792450b9ab8e67aea5f3cc18d6347c" + integrity sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg== dependencies: "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^8.0.0" - "@types/react-dom" "<18.0.0" + "@testing-library/dom" "^9.0.0" + "@types/react-dom" "^18.0.0" -"@testing-library/user-event@^14.4.3": - version "14.4.3" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.3.tgz#af975e367743fa91989cd666666aec31a8f50591" - integrity sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q== +"@testing-library/user-event@^14.5.0": + version "14.5.0" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.0.tgz#4036add379525b635a64bce4d727820d4ba516a7" + integrity sha512-nQRCteEZvULJJrlcGQuNhwGekz25TOUILA+sTWI9PB/vNKKivS+7K7XRTwoikw/2fmJPaM4pPKy+hLWEGg9+JA== "@tootallnate/once@2": version "2.0.0" @@ -3254,6 +3502,11 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== +"@types/aria-query@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc" + integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q== + "@types/babel__core@^7.1.14": version "7.20.0" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891" @@ -3435,6 +3688,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.22.tgz#0d29f382472c4ccf3bd96ff0ce47daf5b7b84b18" integrity sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw== +"@types/node@^14.14.31": + version "14.18.60" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.60.tgz#986a33f3d15717d076a68a59ac8656c73e6b4ef5" + integrity sha512-F2dfYDznasZ6XfuWvTmQcrElTHfxCdC+F23WCcuAJaIrMIhhBUSARJQdy0lUY+MPDNLqGvTo8/IuiF+QX64IHQ== + "@types/node@^18.11.9": version "18.15.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.5.tgz#3af577099a99c61479149b716183e70b5239324a" @@ -3455,12 +3713,12 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== -"@types/react-dom@<18.0.0": - version "17.0.19" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.19.tgz#36feef3aa35d045cacd5ed60fe0eef5272f19492" - integrity sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ== +"@types/react-dom@^18.0.0": + version "18.2.7" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63" + integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA== dependencies: - "@types/react" "^17" + "@types/react" "*" "@types/react-redux@^7.1.20": version "7.1.20" @@ -3504,25 +3762,21 @@ "@types/prop-types" "*" csstype "^3.0.2" -"@types/react@^17": - version "17.0.53" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.53.tgz#10d4d5999b8af3d6bc6a9369d7eb953da82442ab" - integrity sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/scheduler@*": - version "0.16.2" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" - integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== - "@types/semver@^7.3.12": version "7.3.13" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== + +"@types/sizzle@^2.3.2": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" + integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -3611,6 +3865,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yauzl@^2.9.1": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" + integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== + dependencies: + "@types/node" "*" + "@typescript-eslint/eslint-plugin@5.42.0": version "5.42.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.0.tgz#36a8c0c379870127059889a9cc7e05c260d2aaa5" @@ -3904,6 +4165,14 @@ agent-base@6: dependencies: debug "4" +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -3948,6 +4217,11 @@ ansi-colors@4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + ansi-escapes@^4.2.1: version "4.3.1" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" @@ -3955,6 +4229,13 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.11.0" +ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -4008,6 +4289,11 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +arch@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" + integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -4025,6 +4311,13 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +aria-query@5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" + integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== + dependencies: + deep-equal "^2.0.5" + aria-query@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" @@ -4102,6 +4395,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + async@^2.6.4: version "2.6.4" resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" @@ -4109,11 +4407,21 @@ async@^2.6.4: dependencies: lodash "^4.17.14" +async@^3.2.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + attr-accept@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b" @@ -4248,6 +4556,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -4274,6 +4587,21 @@ blink-diff@1.0.13: preceptor-core "~0.10.0" promise "6.0.0" +blob-util@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" + integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== + +bluebird@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de" + integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg== + +bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + body@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" @@ -4331,6 +4659,16 @@ browserslist@^4.20.2, browserslist@^4.21.3: node-releases "^2.0.6" update-browserslist-db "^1.0.5" +browserslist@^4.21.9: + version "4.21.10" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" + integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== + dependencies: + caniuse-lite "^1.0.30001517" + electron-to-chromium "^1.4.477" + node-releases "^2.0.13" + update-browserslist-db "^1.0.11" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -4338,16 +4676,34 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + bytes@1: version "1.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" integrity sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ== +cachedir@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.4.0.tgz#7fef9cf7367233d7c88068fe6e34ed0d355a610d" + integrity sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ== + calculate-size@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/calculate-size/-/calculate-size-1.1.1.tgz#ae7caa1c7795f82c4f035dc7be270e3581dae3ee" @@ -4391,12 +4747,17 @@ caniuse-lite@^1.0.30001449: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001469.tgz#3dd505430c8522fdc9f94b4a19518e330f5c945a" integrity sha512-Rcp7221ScNqQPP3W+lVOYDyjdR6dC+neEQCttoNr5bAyz54AboB4iwpnWgyi8P4YUsPybVzT4LgWiBbI3drL4g== +caniuse-lite@^1.0.30001517: + version "1.0.30001534" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz#f24a9b2a6d39630bac5c132b5dff89b39a12e7dd" + integrity sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -4434,6 +4795,16 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== + +check-more-types@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" + integrity sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA== + chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -4489,6 +4860,11 @@ classnames@^2.3.1: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + clear-module@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/clear-module/-/clear-module-4.1.2.tgz#5a58a5c9f8dccf363545ad7284cad3c887352a80" @@ -4497,6 +4873,30 @@ clear-module@^4.1.2: parent-module "^2.0.0" resolve-from "^5.0.0" +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-table3@~0.6.1: + version "0.6.3" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" + integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -4568,6 +4968,11 @@ colorette@^2.0.14: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== +colorette@^2.0.16: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -4600,6 +5005,11 @@ commander@^2.19.0, commander@^2.20.0, commander@^2.20.3: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + comment-json@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.2.3.tgz#50b487ebbf43abe44431f575ebda07d30d015365" @@ -4616,6 +5026,11 @@ comment-parser@1.3.1: resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.1.tgz#3d7ea3adaf9345594aedee6563f422348f165c1b" integrity sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA== +common-tags@^1.8.0: + version "1.8.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -4761,7 +5176,7 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -4770,6 +5185,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -4967,6 +5387,59 @@ csstype@^3.0.6: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== +cypress-file-upload@5.0.8: + version "5.0.8" + resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1" + integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g== + +cypress@9.5.1: + version "9.5.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.1.tgz#51162f3688cedf5ffce311b914ef49a7c1ece076" + integrity sha512-H7lUWB3Svr44gz1rNnj941xmdsCljXoJa2cDneAltjI9leKLMQLm30x6jLlpQ730tiVtIbW5HdUmBzPzwzfUQg== + dependencies: + "@cypress/request" "^2.88.10" + "@cypress/xvfb" "^1.2.4" + "@types/node" "^14.14.31" + "@types/sinonjs__fake-timers" "8.1.1" + "@types/sizzle" "^2.3.2" + arch "^2.2.0" + blob-util "^2.0.2" + bluebird "^3.7.2" + buffer "^5.6.0" + cachedir "^2.3.0" + chalk "^4.1.0" + check-more-types "^2.24.0" + cli-cursor "^3.1.0" + cli-table3 "~0.6.1" + commander "^5.1.0" + common-tags "^1.8.0" + dayjs "^1.10.4" + debug "^4.3.2" + enquirer "^2.3.6" + eventemitter2 "^6.4.3" + execa "4.1.0" + executable "^4.1.1" + extract-zip "2.0.1" + figures "^3.2.0" + fs-extra "^9.1.0" + getos "^3.2.1" + is-ci "^3.0.0" + is-installed-globally "~0.4.0" + lazy-ass "^1.6.0" + listr2 "^3.8.3" + lodash "^4.17.21" + log-symbols "^4.0.0" + minimist "^1.2.5" + ospath "^1.2.2" + pretty-bytes "^5.6.0" + proxy-from-env "1.0.0" + request-progress "^3.0.0" + semver "^7.3.2" + supports-color "^8.1.1" + tmp "~0.2.1" + untildify "^4.0.0" + yauzl "^2.10.0" + "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: version "3.2.3" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.3.tgz#39f1f4954e4a09ff69ac597c2d61906b04e84740" @@ -5237,6 +5710,11 @@ date-format@^0.0.0: resolved "https://registry.yarnpkg.com/date-format/-/date-format-0.0.0.tgz#09206863ab070eb459acea5542cbd856b11966b3" integrity sha1-CSBoY6sHDrRZrOpVQsvYVrEZZrM= +dayjs@^1.10.4: + version "1.11.9" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a" + integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA== + debug@4: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" @@ -5292,6 +5770,30 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +deep-equal@^2.0.5: + version "2.2.2" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.2.tgz#9b2635da569a13ba8e1cc159c2f744071b115daa" + integrity sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.1" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.0" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -5302,6 +5804,15 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +define-data-property@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" + integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -5317,6 +5828,15 @@ define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +define-properties@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + delaunator@5: version "5.0.0" resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b" @@ -5454,6 +5974,11 @@ electron-to-chromium@^1.4.284: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.334.tgz#eacdcb1145534202d569610c5915b63a3fec0eb9" integrity sha512-laZ1odk+TRen6q0GeyQx/JEkpD3iSZT7ewopCpKqg9bTjP1l8XRfU3Bg20CFjNPZkp5+NDBl3iqd4o/kPO+Vew== +electron-to-chromium@^1.4.477: + version "1.4.522" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.522.tgz#ef29e5508571d52cf45806536dba5d397492667d" + integrity sha512-KGKjcafTpOxda0kqwQ72M0tDmX6RsGhUJTy0Hr7slt0+CgHh9Oex8JdjY9Og68dUkTLUlBOJC0A5W5Mw3QSGCg== + emittery@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" @@ -5469,6 +5994,13 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + enhanced-resolve@^4.0.0: version "4.5.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" @@ -5486,6 +6018,14 @@ enhanced-resolve@^5.10.0: graceful-fs "^4.2.4" tapable "^2.2.0" +enquirer@^2.3.6: + version "2.4.1" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" + integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== + dependencies: + ansi-colors "^4.1.1" + strip-ansi "^6.0.1" + entities@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" @@ -5590,6 +6130,21 @@ es-abstract@^1.20.4: unbox-primitive "^1.0.2" which-typed-array "^1.1.9" +es-get-iterator@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" + es-module-lexer@^0.9.0: version "0.9.3" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" @@ -5837,6 +6392,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +eventemitter2@^6.4.3: + version "6.4.9" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125" + integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg== + eventemitter3@4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" @@ -5847,6 +6407,21 @@ events@^3.2.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +execa@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + execa@5.1.1, execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -5862,6 +6437,13 @@ execa@5.1.1, execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +executable@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" + integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== + dependencies: + pify "^2.2.0" + exenv@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" @@ -5888,6 +6470,17 @@ extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +extract-zip@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -5981,6 +6574,20 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + +figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -6098,7 +6705,7 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -fs-extra@^10.0.0: +fs-extra@^10.0.0, fs-extra@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== @@ -6107,6 +6714,16 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-monkey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" @@ -6137,7 +6754,7 @@ function.prototype.name@^1.1.5: es-abstract "^1.19.0" functions-have-names "^1.2.2" -functions-have-names@^1.2.2: +functions-have-names@^1.2.2, functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== @@ -6193,6 +6810,16 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: has "^1.0.3" has-symbols "^1.0.3" +get-intrinsic@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -6203,6 +6830,13 @@ get-stdin@^8.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== +get-stream@^5.0.0, get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -6230,6 +6864,13 @@ get-window@^1.1.1: dependencies: get-document "1" +getos@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" + integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== + dependencies: + async "^3.2.0" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -6298,6 +6939,13 @@ global-dirs@^0.1.1: dependencies: ini "^1.3.4" +global-dirs@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" + integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== + dependencies: + ini "2.0.0" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -6517,6 +7165,15 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +http-signature@~1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" + integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== + dependencies: + assert-plus "^1.0.0" + jsprim "^2.0.2" + sshpk "^1.14.1" + https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -6525,6 +7182,11 @@ https-proxy-agent@^5.0.1: agent-base "6" debug "4" +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -6568,7 +7230,7 @@ identity-obj-proxy@3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.12: +ieee754@^1.1.12, ieee754@^1.1.13: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -6637,6 +7299,11 @@ inherits@2, inherits@~2.0.1, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + ini@^1.3.4: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" @@ -6658,7 +7325,7 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" -internal-slot@^1.0.5: +internal-slot@^1.0.4, internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== @@ -6694,6 +7361,14 @@ invariant@^2.2.2: dependencies: loose-envify "^1.0.0" +is-arguments@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" @@ -6730,6 +7405,11 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-buffer@~1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + is-callable@^1.1.3, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -6745,6 +7425,13 @@ is-callable@^1.2.4: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== +is-ci@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" + integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== + dependencies: + ci-info "^3.2.0" + is-core-module@^2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" @@ -6764,6 +7451,13 @@ is-date-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== +is-date-object@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -6808,6 +7502,19 @@ is-in-browser@^1.1.3: resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= +is-installed-globally@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + +is-map@^2.0.1, is-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== + is-negative-zero@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" @@ -6835,7 +7542,7 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-path-inside@^3.0.3: +is-path-inside@^3.0.2, is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== @@ -6865,6 +7572,11 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-set@^2.0.1, is-set@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== + is-shared-array-buffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" @@ -6924,6 +7636,11 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== +is-weakmap@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== + is-weakref@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.1.tgz#842dba4ec17fa9ac9850df2d6efbc1737274f2a2" @@ -6938,6 +7655,14 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" +is-weakset@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" + integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + is-window@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-window/-/is-window-1.0.2.tgz#2c896ca53db97de45d3c33133a65d8c9f563480d" @@ -6948,6 +7673,11 @@ isarray@0.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -7540,6 +8270,11 @@ json-schema@0.2.3: resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -7555,7 +8290,7 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json5@^2.1.2, json5@^2.2.1, json5@^2.2.2: +json5@^2.1.2, json5@^2.2.1, json5@^2.2.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -7584,6 +8319,16 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jsprim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" + integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.2.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b" @@ -7607,6 +8352,11 @@ klona@^2.0.4: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== +lazy-ass@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" + integrity sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw== + lerc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lerc/-/lerc-3.0.0.tgz#36f36fbd4ba46f0abf4833799fff2e7d6865f5cb" @@ -7638,6 +8388,20 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +listr2@^3.8.3: + version "3.14.0" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.14.0.tgz#23101cc62e1375fd5836b248276d1d2b51fdbe9e" + integrity sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.5.1" + through "^2.3.8" + wrap-ansi "^7.0.0" + livereload-js@^2.3.0: version "2.4.0" resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c" @@ -7686,12 +8450,12 @@ lodash.once@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash@4.17.21, lodash@^4.1.1, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4: +lodash@4.17.21, lodash@^4.1.1, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@4.1.0: +log-symbols@4.1.0, log-symbols@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -7699,6 +8463,16 @@ log-symbols@4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + log4js@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/log4js/-/log4js-1.1.1.tgz#c21d29c7604089e4f255833e7f94b3461de1ff43" @@ -7734,6 +8508,11 @@ lz-string@^1.4.4: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + make-dir@^3.0.0, make-dir@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -7763,6 +8542,15 @@ marked@4.2.0: resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.0.tgz#f1683b077626a6c53e28926b798a18184aa13a91" integrity sha512-1qWHjHlBKwjnDfrkxd0L3Yx4LTad/WO7+d13YsXAC/ZfKj7p0xkLV3sDXJzfWgL7GfW4IBZwMAYWaz+ifyQouQ== +md5@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -8051,6 +8839,11 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= +node-releases@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== + node-releases@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" @@ -8066,7 +8859,7 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-run-path@^4.0.1: +npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== @@ -8103,6 +8896,14 @@ object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== +object-is@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -8182,14 +8983,14 @@ ol@7.1.0: pbf "3.2.1" rbush "^3.0.1" -once@^1.3.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" -onetime@^5.1.2: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -8220,6 +9021,11 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +ospath@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" + integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA== + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -8248,6 +9054,13 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -8349,6 +9162,11 @@ pbf@3.2.1: ieee754 "^1.1.12" resolve-protobuf-schema "^2.1.0" +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -8377,6 +9195,11 @@ picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pify@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + pirates@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -8508,6 +9331,11 @@ prettier@^2.5.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.6.tgz#5c174b29befd507f14b83e3c19f83fdc0e974b71" integrity sha512-mtuzdiBbHwPEgl7NxWlqOkithPyp4VN93V7VeHVWBF+ad3I5avc0RVDT4oImXQy9H/AqxA2NSQH8pSxHW6FYbQ== +pretty-bytes@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + pretty-format@^26.0.0, pretty-format@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" @@ -8585,6 +9413,11 @@ protocol-buffers-schema@^3.3.1: resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03" integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw== +proxy-from-env@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A== + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -8595,6 +9428,14 @@ psl@^1.1.28, psl@^1.1.33: resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -8612,6 +9453,13 @@ qs@^6.4.0: dependencies: side-channel "^1.0.4" +qs@~6.10.3: + version "6.10.5" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.5.tgz#974715920a80ff6a262264acd2c7e6c2a53282b4" + integrity sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ== + dependencies: + side-channel "^1.0.4" + qs@~6.5.2: version "6.5.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" @@ -8952,14 +9800,13 @@ react-custom-scrollbars-2@4.5.0: prop-types "^15.5.10" raf "^3.1.0" -react-dom@17.0.2: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" - integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler "^0.20.2" + scheduler "^0.23.0" react-dropzone@14.2.3: version "14.2.3" @@ -9180,13 +10027,12 @@ react-window@1.8.8: "@babel/runtime" "^7.0.0" memoize-one ">=3.1.1 <6" -react@17.0.2: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" - integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" readable-stream@^1.1.7: version "1.1.14" @@ -9310,6 +10156,15 @@ regexp.prototype.flags@^1.4.3: define-properties "^1.1.3" functions-have-names "^1.2.2" +regexp.prototype.flags@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + set-function-name "^2.0.0" + regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -9392,6 +10247,13 @@ replace-in-file-webpack-plugin@^1.0.6: resolved "https://registry.yarnpkg.com/replace-in-file-webpack-plugin/-/replace-in-file-webpack-plugin-1.0.6.tgz#eee7e139be967e8e48a0552f73037ed567b54dbd" integrity sha512-+KRgNYL2nbc6nza6SeF+wTBNkovuHFTfJF8QIEqZg5MbwkYpU9no0kH2YU354wvY/BK8mAC2UKoJ7q+sJTvciw== +request-progress@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" + integrity sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg== + dependencies: + throttleit "^1.0.0" + request@^2.55.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" @@ -9513,6 +10375,14 @@ resolve@^2.0.0-next.3: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -9523,7 +10393,12 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@3.0.2, rimraf@^3.0.2: +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + +rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -9552,7 +10427,7 @@ rw@1, rw@^1.3.3: resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= -rxjs@7.5.7, rxjs@^7.5.6: +rxjs@7.5.7, rxjs@^7.5.1, rxjs@^7.5.6: version "7.5.6" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.6.tgz#0446577557862afd6903517ce7cae79ecb9662bc" integrity sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw== @@ -9612,13 +10487,12 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" -scheduler@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" - integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" schema-utils@>1.0.0, schema-utils@^4.0.0: version "4.0.0" @@ -9654,12 +10528,12 @@ semver@^5.3.0, semver@^5.5.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: +semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -9680,6 +10554,15 @@ serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: dependencies: randombytes "^2.1.0" +set-function-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== + dependencies: + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" + set-harmonic-interval@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249" @@ -9833,6 +10716,24 @@ slate@0.47.9: tiny-warning "^0.0.3" type-of "^2.0.1" +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + sort-asc@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/sort-asc/-/sort-asc-0.1.0.tgz#ab799df61fc73ea0956c79c4b531ed1e9e7727e9" @@ -9920,6 +10821,21 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +sshpk@^1.14.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -9976,6 +10892,13 @@ state-local@^1.0.6: resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5" integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w== +stop-iteration-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" + stream-buffers@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-1.0.1.tgz#9a44a37555f96a5b78a5a765f0c48446cb160b8c" @@ -10152,7 +11075,7 @@ stylis@^4.0.6: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240" integrity sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg== -supports-color@8.1.1, supports-color@^8.0.0: +supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== @@ -10248,6 +11171,16 @@ throttle-debounce@^3.0.1: resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb" integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg== +throttleit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" + integrity sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g== + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + tiny-invariant@^1.0.1, tiny-invariant@^1.0.6: version "1.1.0" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" @@ -10285,6 +11218,13 @@ tinycolor2@1.4.2: resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== +tmp@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -10336,6 +11276,16 @@ tough-cookie@^4.1.2: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" + integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -10478,6 +11428,11 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + type-of@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/type-of/-/type-of-2.0.1.tgz#e72a1741896568e9f628378d816d6912f7f23972" @@ -10612,6 +11567,11 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + update-browserslist-db@^1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" @@ -10620,6 +11580,14 @@ update-browserslist-db@^1.0.10: escalade "^3.1.1" picocolors "^1.0.0" +update-browserslist-db@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + update-browserslist-db@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38" @@ -10673,6 +11641,11 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -10806,6 +11779,11 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== +webpack-virtual-modules@^0.4.4: + version "0.4.6" + resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.4.6.tgz#3e4008230731f1db078d9cb6f68baf8571182b45" + integrity sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA== + webpack@^5.69.1: version "5.76.2" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.2.tgz#6f80d1c1d1e3bf704db571b2504a0461fac80230" @@ -10889,6 +11867,16 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-collection@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" + integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== + dependencies: + is-map "^2.0.1" + is-set "^2.0.1" + is-weakmap "^2.0.1" + is-weakset "^2.0.1" + which-typed-array@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" @@ -10930,6 +11918,15 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -11076,6 +12073,14 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.1.1" +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From 43dc31f6a2153c651c464b714b79cef8984c51f3 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sun, 17 Sep 2023 18:23:15 -0400 Subject: [PATCH 06/95] change: version, Http, refactor EditorTypeSwitcher --- package.json | 2 +- .../queryBuilder/EditorTypeSwitcher.test.tsx | 4 ++-- .../queryBuilder/EditorTypeSwitcher.tsx | 15 +++++++-------- src/types/config.ts | 2 +- src/views/CHConfigEditor.test.tsx | 2 +- src/views/CHConfigEditor.tsx | 2 +- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 45e22fbd..39e59283 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clickhouse-datasource", - "version": "4.0.0", + "version": "3.3.0", "description": "Clickhouse Datasource", "engines": { "node": ">=16" diff --git a/src/components/queryBuilder/EditorTypeSwitcher.test.tsx b/src/components/queryBuilder/EditorTypeSwitcher.test.tsx index 00c37f33..5642f0d7 100644 --- a/src/components/queryBuilder/EditorTypeSwitcher.test.tsx +++ b/src/components/queryBuilder/EditorTypeSwitcher.test.tsx @@ -13,7 +13,7 @@ describe('EditorTypeSwitcher', () => { it('should render default query', () => { const result = render( {}} onRunQuery={() => {}} /> @@ -26,7 +26,7 @@ describe('EditorTypeSwitcher', () => { it('should render legacy query (query without query type)', () => { const result = render( {}} onRunQuery={() => {}} /> diff --git a/src/components/queryBuilder/EditorTypeSwitcher.tsx b/src/components/queryBuilder/EditorTypeSwitcher.tsx index 2994fd5e..d35b3a01 100644 --- a/src/components/queryBuilder/EditorTypeSwitcher.tsx +++ b/src/components/queryBuilder/EditorTypeSwitcher.tsx @@ -3,7 +3,7 @@ import { SelectableValue } from '@grafana/data'; import { RadioButtonGroup, ConfirmModal, InlineFormLabel } from '@grafana/ui'; import { getQueryOptionsFromSql, getSqlFromQueryBuilderOptions } from '../queryBuilder/utils'; import labels from 'labels'; -import { EditorType, CHQuery, defaultCHBuilderQuery, CHSqlQuery } from 'types/sql'; +import { EditorType, CHQuery, defaultCHBuilderQuery } from 'types/sql'; import { QueryBuilderOptions } from 'types/queryBuilder'; import isString from 'lodash/isString'; @@ -13,22 +13,21 @@ interface CHEditorTypeSwitcherProps { onRunQuery: () => void; } +const options: Array> = [ + { label: labels.types.EditorType.sql, value: EditorType.SQL }, + { label: labels.types.EditorType.builder, value: EditorType.Builder }, +]; + /** * Component for switching between the SQL and Query Builder editors. */ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { const { query, onChange } = props; const { label, tooltip, switcher, cannotConvert } = labels.components.EditorTypeSwitcher; - let editorType: EditorType = - query.editorType || - ((query as CHSqlQuery).rawSql && !(query as CHQuery).editorType ? EditorType.SQL : EditorType.Builder); + const editorType: EditorType = query.editorType; const [editor, setEditor] = useState(editorType); const [confirmModalState, setConfirmModalState] = useState(false); const [cannotConvertModalState, setCannotConvertModalState] = useState(false); - const options: Array> = [ - { label: labels.types.EditorType.sql, value: EditorType.SQL }, - { label: labels.types.EditorType.builder, value: EditorType.Builder }, - ]; const [errorMessage, setErrorMessage] = useState(''); const onEditorTypeChange = (editorType: EditorType, confirm = false) => { if (query.editorType === EditorType.SQL && editorType === EditorType.Builder && !confirm) { diff --git a/src/types/config.ts b/src/types/config.ts index 8b21ef29..992a4bdd 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -31,5 +31,5 @@ export interface CHSecureConfig { export enum Protocol { Native = 'native', - HTTP = 'http', + Http = 'http', } diff --git a/src/views/CHConfigEditor.test.tsx b/src/views/CHConfigEditor.test.tsx index 98fab23f..595ddab3 100644 --- a/src/views/CHConfigEditor.test.tsx +++ b/src/views/CHConfigEditor.test.tsx @@ -55,7 +55,7 @@ describe('ConfigEditor', () => { {...mockConfigEditorProps()} options={{ ...mockConfigEditorProps().options, - jsonData: { ...mockConfigEditorProps().options.jsonData, protocol: Protocol.HTTP }, + jsonData: { ...mockConfigEditorProps().options.jsonData, protocol: Protocol.Http }, }} /> ); diff --git a/src/views/CHConfigEditor.tsx b/src/views/CHConfigEditor.tsx index f8a23b9f..355e3a44 100644 --- a/src/views/CHConfigEditor.tsx +++ b/src/views/CHConfigEditor.tsx @@ -34,7 +34,7 @@ export const ConfigEditor: React.FC = (props) => { const hasTLSClientKey = secureJsonFields && secureJsonFields.tlsClientKey; const protocolOptions = [ { label: 'Native', value: Protocol.Native }, - { label: 'HTTP', value: Protocol.HTTP }, + { label: 'HTTP', value: Protocol.Http }, ]; const switchContainerStyle: React.CSSProperties = { padding: `0 ${theme.spacing.sm}`, From e58e25cf1b837bb0a083e677a73a99d7f82fd091 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sun, 17 Sep 2023 19:26:07 -0400 Subject: [PATCH 07/95] ReadonlyArray table columns, remove exception --- src/components/SqlEditor.tsx | 4 ++-- src/components/queryBuilder/AggregateEditor.tsx | 4 ++-- src/components/queryBuilder/ColumnSelect.test.tsx | 4 ++-- src/components/queryBuilder/ColumnSelect.tsx | 4 ++-- src/components/queryBuilder/ColumnsEditor.test.tsx | 2 +- src/components/queryBuilder/ColumnsEditor.tsx | 6 +++--- src/components/queryBuilder/EditorTypeSwitcher.tsx | 3 ++- src/components/queryBuilder/FilterEditor.tsx | 8 ++++---- src/components/queryBuilder/GroupByEditor.test.tsx | 4 ++-- src/components/queryBuilder/GroupByEditor.tsx | 4 ++-- src/components/queryBuilder/LimitEditor.tsx | 2 +- src/components/queryBuilder/OrderByEditor.test.tsx | 2 +- src/components/queryBuilder/OrderByEditor.tsx | 6 +++--- src/components/queryBuilder/OtelVersionSelect.tsx | 2 +- src/components/queryBuilder/views/LogsQueryBuilder.tsx | 2 +- src/components/queryBuilder/views/TableQueryBuilder.tsx | 2 +- .../queryBuilder/views/TimeSeriesQueryBuilder.tsx | 2 +- src/components/queryBuilder/views/TraceQueryBuilder.tsx | 2 +- src/hooks/useColumns.test.ts | 8 ++++---- src/hooks/useColumns.ts | 5 ++--- src/hooks/useDatabases.ts | 1 - src/hooks/useTables.ts | 1 - 22 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/components/SqlEditor.tsx b/src/components/SqlEditor.tsx index 7cb05849..8bcec0a4 100644 --- a/src/components/SqlEditor.tsx +++ b/src/components/SqlEditor.tsx @@ -21,11 +21,12 @@ interface Expand { export const SqlEditor = (props: SqlEditorProps) => { const defaultHeight = '150px'; const { query, onRunQuery, onChange, datasource } = props; + const sqlQuery = query as CHSqlQuery; const [codeEditor, setCodeEditor] = useState(); const [expand, setExpand] = useState({ height: defaultHeight, icon: 'plus', - on: (query as CHSqlQuery).expand || false, + on: sqlQuery.expand || false, }); const onSqlChange = (sql: string) => { @@ -35,7 +36,6 @@ export const SqlEditor = (props: SqlEditorProps) => { }; const onToggleExpand = () => { - const sqlQuery = query as CHSqlQuery; const on = !expand.on; const icon = on ? 'minus' : 'plus'; onChange({ ...sqlQuery, expand: on }); diff --git a/src/components/queryBuilder/AggregateEditor.tsx b/src/components/queryBuilder/AggregateEditor.tsx index 57bba96c..78669b14 100644 --- a/src/components/queryBuilder/AggregateEditor.tsx +++ b/src/components/queryBuilder/AggregateEditor.tsx @@ -65,13 +65,13 @@ const Aggregate = (props: AggregateProps) => { }; interface AggregateEditorProps { - allColumns: TableColumn[]; + allColumns: ReadonlyArray; aggregates: AggregateColumn[]; onAggregatesChange: (aggregates: AggregateColumn[]) => void; } export const AggregateEditor = (props: AggregateEditorProps) => { const { allColumns, aggregates, onAggregatesChange } = props; - const columnOptions: Array> = (allColumns || []).map(c => ({ label: c.name, value: c.name })); + const columnOptions: Array> = allColumns.map(c => ({ label: c.name, value: c.name })); const { label, tooltip, addLabel } = labels.components.AggregatesEditor; const addAggregate = () => { diff --git a/src/components/queryBuilder/ColumnSelect.test.tsx b/src/components/queryBuilder/ColumnSelect.test.tsx index 64c656b8..1a67a0f4 100644 --- a/src/components/queryBuilder/ColumnSelect.test.tsx +++ b/src/components/queryBuilder/ColumnSelect.test.tsx @@ -21,7 +21,7 @@ describe('ColumnSelect', () => { }); it('should render with valid properties', () => { - const allColumns: TableColumn[] = [{ name: 'foo', type: 'string', picklistValues: [] }]; + const allColumns: ReadonlyArray = [{ name: 'foo', type: 'string', picklistValues: [] }]; const selectedColumn: SelectedColumn = { name: 'foo' }; const result = render( { }); it('should call onColumnChange when a new column is selected', () => { - const allColumns: TableColumn[] = [ + const allColumns: ReadonlyArray = [ { name: 'one', type: 'string', picklistValues: [] }, { name: 'two', type: 'string', picklistValues: [] } ]; diff --git a/src/components/queryBuilder/ColumnSelect.tsx b/src/components/queryBuilder/ColumnSelect.tsx index cfe4f86f..947438dd 100644 --- a/src/components/queryBuilder/ColumnSelect.tsx +++ b/src/components/queryBuilder/ColumnSelect.tsx @@ -5,7 +5,7 @@ import { ColumnHint, SelectedColumn, TableColumn } from 'types/queryBuilder'; import { styles } from 'styles'; interface ColumnSelectProps { - allColumns: TableColumn[]; + allColumns: ReadonlyArray; selectedColumn: SelectedColumn | undefined; onColumnChange: (c: SelectedColumn) => void; columnFilterFn?: (c: TableColumn) => boolean; @@ -21,7 +21,7 @@ const defaultFilterFn = () => true; export const ColumnSelect = (props: ColumnSelectProps) => { const { allColumns, selectedColumn, onColumnChange, columnFilterFn, columnHint, label, tooltip, wide, inline } = props; const selectedColumnName = selectedColumn?.name; - const columns: Array> = (allColumns || []). + const columns: Array> = allColumns. filter(columnFilterFn || defaultFilterFn). map(c => ({ label: c.name, value: c.name })); diff --git a/src/components/queryBuilder/ColumnsEditor.test.tsx b/src/components/queryBuilder/ColumnsEditor.test.tsx index 8bf52a4c..1a7d794e 100644 --- a/src/components/queryBuilder/ColumnsEditor.test.tsx +++ b/src/components/queryBuilder/ColumnsEditor.test.tsx @@ -5,7 +5,7 @@ import { TableColumn, SelectedColumn } from 'types/queryBuilder'; import { selectors } from 'selectors'; describe('ColumnsEditor', () => { - const allColumns: TableColumn[] = [ + const allColumns: ReadonlyArray = [ { name: 'name', type: 'string', picklistValues: [] }, { name: 'dummy', type: 'string', picklistValues: [] }, ]; diff --git a/src/components/queryBuilder/ColumnsEditor.tsx b/src/components/queryBuilder/ColumnsEditor.tsx index ca6a06b0..015f9310 100644 --- a/src/components/queryBuilder/ColumnsEditor.tsx +++ b/src/components/queryBuilder/ColumnsEditor.tsx @@ -7,12 +7,12 @@ import { selectors } from 'selectors'; import { styles } from 'styles'; interface ColumnsEditorProps { - allColumns: TableColumn[]; + allColumns: ReadonlyArray; selectedColumns: SelectedColumn[]; onSelectedColumnsChange: (selectedColumns: SelectedColumn[]) => void; } -function getCustomColumns(columnNames: string[], allColumns: TableColumn[]): Array> { +function getCustomColumns(columnNames: string[], allColumns: ReadonlyArray): Array> { const columnNamesSet = new Set(columnNames); return allColumns. filter(c => columnNamesSet.has(c.name)). @@ -23,7 +23,7 @@ export const ColumnsEditor = (props: ColumnsEditorProps) => { const { allColumns, selectedColumns, onSelectedColumnsChange } = props; const [customColumns, setCustomColumns] = useState>>([]); const [isOpen, setIsOpen] = useState(false); - const allColumnNames = (allColumns || []).map(c => ({ label: c.name, value: c.name })); + const allColumnNames = allColumns.map(c => ({ label: c.name, value: c.name })); const selectedColumnNames = (selectedColumns || []).map(c => ({ label: c.name, value: c.name })); const { label, tooltip } = labels.components.ColumnsEditor; diff --git a/src/components/queryBuilder/EditorTypeSwitcher.tsx b/src/components/queryBuilder/EditorTypeSwitcher.tsx index d35b3a01..21cee27a 100644 --- a/src/components/queryBuilder/EditorTypeSwitcher.tsx +++ b/src/components/queryBuilder/EditorTypeSwitcher.tsx @@ -30,6 +30,7 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { const [cannotConvertModalState, setCannotConvertModalState] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const onEditorTypeChange = (editorType: EditorType, confirm = false) => { + // TODO: component state has updated, but not local state. if (query.editorType === EditorType.SQL && editorType === EditorType.Builder && !confirm) { const queryOptionsFromSql = getQueryOptionsFromSql(query.rawSql); if (isString(queryOptionsFromSql)) { @@ -80,7 +81,7 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { {label} - onEditorTypeChange(e!)} /> + onEditorTypeChange(e)} /> ; } const FilterValueNumberItem = (props: { value: number; onChange: (value: number) => void }) => { @@ -91,7 +91,7 @@ const FilterValueMultiStringItem = (props: { value: string[]; onChange: (value: }; export const FilterValueEditor = (props: { - allColumns: TableColumn[]; + allColumns: ReadonlyArray; filter: Filter; onFilterChange: (filter: Filter) => void; }) => { @@ -171,7 +171,7 @@ export const FilterValueEditor = (props: { }; export const FilterEditor = (props: { - allColumns: TableColumn[]; + allColumns: ReadonlyArray; index: number; filter: Filter & PredefinedFilter; onFilterChange: (index: number, filter: Filter) => void; @@ -362,7 +362,7 @@ export const FilterEditor = (props: { }; export const FiltersEditor = (props: { - allColumns: TableColumn[]; + allColumns: ReadonlyArray; filters: Filter[]; onFiltersChange: (filters: Filter[]) => void; }) => { diff --git a/src/components/queryBuilder/GroupByEditor.test.tsx b/src/components/queryBuilder/GroupByEditor.test.tsx index 47405a54..7c7b4e90 100644 --- a/src/components/queryBuilder/GroupByEditor.test.tsx +++ b/src/components/queryBuilder/GroupByEditor.test.tsx @@ -10,14 +10,14 @@ describe('GroupByEditor', () => { }); it('should render with valid properties', () => { - const allColumns: TableColumn[] = [{ name: 'a', type: 'string', picklistValues: [] }]; + const allColumns: ReadonlyArray = [{ name: 'a', type: 'string', picklistValues: [] }]; const groupBy: string[] = ['a', 'b']; const result = render( {}} />); expect(result.container.firstChild).not.toBeNull(); }); it('should call onGroupByChange when a new column is selected', () => { - const allColumns: TableColumn[] = [{ name: 'a', type: 'string', picklistValues: [] }]; + const allColumns: ReadonlyArray = [{ name: 'a', type: 'string', picklistValues: [] }]; const groupBy: string[] = ['b']; const onGroupByChange = jest.fn(); const result = render(); diff --git a/src/components/queryBuilder/GroupByEditor.tsx b/src/components/queryBuilder/GroupByEditor.tsx index 85a362eb..11c34499 100644 --- a/src/components/queryBuilder/GroupByEditor.tsx +++ b/src/components/queryBuilder/GroupByEditor.tsx @@ -7,7 +7,7 @@ import { styles } from 'styles'; import { selectors } from 'selectors'; interface GroupByEditorProps { - allColumns: TableColumn[]; + allColumns: ReadonlyArray; groupBy: string[]; onGroupByChange: (groupBy: string[]) => void; } @@ -16,7 +16,7 @@ export const GroupByEditor = (props: GroupByEditorProps) => { const { allColumns, groupBy, onGroupByChange } = props; const [isOpen, setIsOpen] = useState(false); const { label, tooltip } = labels.components.GroupByEditor; - const options: Array> = (allColumns || []).map(c => ({ label: c.name, value: c.name })); + const options: Array> = allColumns.map(c => ({ label: c.name, value: c.name })); const onChange = (selection: Array>) => { setIsOpen(false); diff --git a/src/components/queryBuilder/LimitEditor.tsx b/src/components/queryBuilder/LimitEditor.tsx index 931a5fbd..6be2919c 100644 --- a/src/components/queryBuilder/LimitEditor.tsx +++ b/src/components/queryBuilder/LimitEditor.tsx @@ -9,7 +9,7 @@ interface LimitEditorProps { } export const LimitEditor = (props: LimitEditorProps) => { - const [limit, setLimit] = useState(props.limit || 100); + const [limit, setLimit] = useState(props.limit || 1000); const { label, tooltip } = labels.components.LimitEditor; return ( diff --git a/src/components/queryBuilder/OrderByEditor.test.tsx b/src/components/queryBuilder/OrderByEditor.test.tsx index d8bbb14b..7bdf67f8 100644 --- a/src/components/queryBuilder/OrderByEditor.test.tsx +++ b/src/components/queryBuilder/OrderByEditor.test.tsx @@ -11,7 +11,7 @@ const newTestColumn = (name: string): TableColumn => ({ picklistValues: [] }); -const testColumns: TableColumn[] = [ +const testColumns: ReadonlyArray = [ newTestColumn('foo'), newTestColumn('bar'), newTestColumn('baz'), diff --git a/src/components/queryBuilder/OrderByEditor.tsx b/src/components/queryBuilder/OrderByEditor.tsx index a6a8b738..4352359b 100644 --- a/src/components/queryBuilder/OrderByEditor.tsx +++ b/src/components/queryBuilder/OrderByEditor.tsx @@ -51,13 +51,13 @@ const OrderByItem = (props: OrderByItemProps) => { }; interface OrderByEditorProps { - allColumns: TableColumn[]; + allColumns: ReadonlyArray; orderBy: OrderBy[]; onOrderByChange: (orderBy: OrderBy[]) => void; } export const OrderByEditor = (props: OrderByEditorProps) => { const { allColumns, orderBy, onOrderByChange } = props; - const columnOptions: Array> = (allColumns || []).map(c => ({ label: c.name, value: c.name })); + const columnOptions: Array> = allColumns.map(c => ({ label: c.name, value: c.name })); const { label, tooltip, addLabel } = allLabels.components.OrderByEditor; const addOrderByItem = () => { @@ -136,7 +136,7 @@ export const OrderByEditor = (props: OrderByEditorProps) => { export const getOrderByFields = ( builder: QueryBuilderOptions, - allColumns: TableColumn[] + allColumns: ReadonlyArray ): Array> => { let values: Array> | Array<{ value: string; label: string }> = []; switch (builder.mode) { diff --git a/src/components/queryBuilder/OtelVersionSelect.tsx b/src/components/queryBuilder/OtelVersionSelect.tsx index 08100317..02733bc1 100644 --- a/src/components/queryBuilder/OtelVersionSelect.tsx +++ b/src/components/queryBuilder/OtelVersionSelect.tsx @@ -15,7 +15,7 @@ interface OtelVersionSelectProps { export const OtelVersionSelect = (props: OtelVersionSelectProps) => { const { enabled, onEnabledChange, selectedVersion, onVersionChange, defaultToLatest } = props; const { label, tooltip } = selectors.components.OtelVersionSelect; - const options: SelectableValue[] = (allVersions || []). + const options: SelectableValue[] = allVersions. map(v => ({ label: `${v.version}${v.name ? (` (${v.name})`) : ''}`, value: v.version diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index bae506eb..5864b9c1 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -11,7 +11,7 @@ import allLabels from 'labels'; import { getColumnByHint } from 'components/queryBuilder/utils'; interface LogsQueryBuilderProps { - allColumns: TableColumn[]; + allColumns: ReadonlyArray; builderOptions: QueryBuilderOptions, onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; } diff --git a/src/components/queryBuilder/views/TableQueryBuilder.tsx b/src/components/queryBuilder/views/TableQueryBuilder.tsx index 1958c4d5..7ebe92d3 100644 --- a/src/components/queryBuilder/views/TableQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TableQueryBuilder.tsx @@ -10,7 +10,7 @@ import { AggregateEditor } from '../AggregateEditor'; import { GroupByEditor } from '../GroupByEditor'; interface TableQueryBuilderProps { - allColumns: TableColumn[]; + allColumns: ReadonlyArray; builderOptions: QueryBuilderOptions, onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; } diff --git a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx index 8a146194..79f0ffa6 100644 --- a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -12,7 +12,7 @@ import { ColumnSelect } from '../ColumnSelect'; import { getColumnByHint } from 'components/queryBuilder/utils'; interface TimeSeriesQueryBuilderProps { - allColumns: TableColumn[]; + allColumns: ReadonlyArray; builderOptions: QueryBuilderOptions, onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; } diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index 11b8ded9..1a535219 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -7,7 +7,7 @@ import { ModeSwitch } from '../ModeSwitch'; import { getColumnByHint } from 'components/queryBuilder/utils'; interface TraceQueryBuilderProps { - allColumns: TableColumn[]; + allColumns: ReadonlyArray; builderOptions: QueryBuilderOptions, onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; } diff --git a/src/hooks/useColumns.test.ts b/src/hooks/useColumns.test.ts index da62f618..fc030fcd 100644 --- a/src/hooks/useColumns.test.ts +++ b/src/hooks/useColumns.test.ts @@ -8,7 +8,7 @@ describe('useColumns', () => { const defaultColumnCount = 1; // the "all" column is always in the array it('should return empty array if datasource is invalid', async () => { - let result: { current: TableColumn[] }; + let result: { current: ReadonlyArray }; await act(async () => { const r = renderHook(() => useColumns(undefined!, 'db', 'table')); result = r.result; @@ -20,7 +20,7 @@ describe('useColumns', () => { it('should return empty array if database string is empty', async () => { const mockDs = {} as Datasource; mockDs.fetchColumnsFull = jest.fn((db: string, table: string) => Promise.resolve([])); - let result: { current: TableColumn[] }; + let result: { current: ReadonlyArray }; await act(async () => { const r = renderHook(() => useColumns(mockDs, '', 'table')); result = r.result; @@ -32,7 +32,7 @@ describe('useColumns', () => { it('should return empty array if table string is empty', async () => { const mockDs = {} as Datasource; mockDs.fetchColumnsFull = jest.fn((db: string, table: string) => Promise.resolve([])); - let result: { current: TableColumn[] }; + let result: { current: ReadonlyArray }; await act(async () => { const r = renderHook(() => useColumns(mockDs, 'db', '')); result = r.result; @@ -50,7 +50,7 @@ describe('useColumns', () => { // { name: '*' } (an "all" column is added by the hook) ])); - let result: { current: TableColumn[] }; + let result: { current: ReadonlyArray }; await act(async () => { const r = renderHook(() => useColumns(mockDs, 'db', 'table')); result = r.result; diff --git a/src/hooks/useColumns.ts b/src/hooks/useColumns.ts index 49f13ee1..6b14ce3c 100644 --- a/src/hooks/useColumns.ts +++ b/src/hooks/useColumns.ts @@ -4,8 +4,8 @@ import { Datasource } from 'data/CHDatasource'; const allColumn = { name: '*', label: 'ALL', type: 'string', picklistValues: [] }; -export default (datasource: Datasource, database: string, table: string): TableColumn[] => { - const [columns, setColumns] = useState([allColumn]); +export default (datasource: Datasource, database: string, table: string): ReadonlyArray => { + const [columns, setColumns] = useState>([allColumn]); useEffect(() => { if (!datasource || !database || !table) { @@ -19,7 +19,6 @@ export default (datasource: Datasource, database: string, table: string): TableC setColumns(columns); }).catch((ex: any) => { console.error(ex); - throw ex; }); }, [datasource, database, table]); diff --git a/src/hooks/useDatabases.ts b/src/hooks/useDatabases.ts index 0f6847f6..635fdd98 100644 --- a/src/hooks/useDatabases.ts +++ b/src/hooks/useDatabases.ts @@ -14,7 +14,6 @@ export default (datasource: Datasource): string[] => { then(databases => setDatabases(databases)). catch((ex: any) => { console.error(ex); - throw ex; }); }, [datasource]); diff --git a/src/hooks/useTables.ts b/src/hooks/useTables.ts index cca65429..640bf4f5 100644 --- a/src/hooks/useTables.ts +++ b/src/hooks/useTables.ts @@ -14,7 +14,6 @@ export default (datasource: Datasource, database: string): string[] => { then(tables => setTables(tables)). catch((ex: any) => { console.error(ex); - throw ex; }); }, [datasource, database]); From b827507e07d8f11ad859f8c7c9529f9807131fb5 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sun, 17 Sep 2023 19:34:27 -0400 Subject: [PATCH 08/95] update new selectors --- src/selectors.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/selectors.ts b/src/selectors.ts index d238acdb..bae3b3b3 100644 --- a/src/selectors.ts +++ b/src/selectors.ts @@ -192,19 +192,19 @@ export const Components = { }, QueryBuilder: { AggregateEditor: { - sectionLabel: 'query-builder-aggregate-editor-section-label', - itemWrapper: 'query-builder-aggregate-editor-item-wrapper', - itemRemoveButton: 'query-builder-aggregate-editor-item-remove-button', - addButton: 'query-builder-aggregate-editor-add-button', + sectionLabel: 'query-builder__aggregate-editor__section-label', + itemWrapper: 'query-builder__aggregate-editor__item-wrapper', + itemRemoveButton: 'query-builder__aggregate-editor-item-remove-button', + addButton: 'query-builder__aggregate-editor__add-button', }, ColumnsEditor: { - multiSelectWrapper: 'query-builder-columns-editor-multi-select-wrapper' + multiSelectWrapper: 'query-builder__columns-editor__multi-select-wrapper' }, GroupByEditor: { - multiSelectWrapper: 'query-builder-group-by-multi-select-wrapper' + multiSelectWrapper: 'query-builder__group-by__multi-select-wrapper' }, LimitEditor: { - input: 'query-builder-limit-editor-input' + input: 'query-builder__limit-editor__input' } } }; From 060177de83102be0ad183cba3ddafb32b6cd7afa Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sun, 17 Sep 2023 19:40:06 -0400 Subject: [PATCH 09/95] split time column check --- src/data/CHDatasource.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index a69261b4..7be4e0ef 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -117,14 +117,16 @@ export class Datasource query.editorType !== EditorType.Builder || query.builderOptions.queryType !== QueryType.Logs || query.builderOptions.mode !== BuilderMode.List || - getColumnByHint(query.builderOptions, ColumnHint.Time) === undefined || query.builderOptions.database === '' || query.builderOptions.table === '' ) { return undefined; } - const timeColumn = getColumnByHint(query.builderOptions, ColumnHint.Time)!; + const timeColumn = getColumnByHint(query.builderOptions, ColumnHint.Time); + if (timeColumn === undefined) { + return undefined; + } const timeFieldRoundingClause = getTimeFieldRoundingClause( logsVolumeRequest.scopedVars, From 80c2b4107995d068a70557d39b3ecee259c9f365 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sun, 17 Sep 2023 20:06:42 -0400 Subject: [PATCH 10/95] default db, remove useless "if" --- src/components/queryBuilder/DatabaseTableSelect.tsx | 3 ++- src/components/queryBuilder/views/LogsQueryBuilder.tsx | 4 ---- src/components/queryBuilder/views/TableQueryBuilder.tsx | 4 ---- src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx | 4 ---- src/components/queryBuilder/views/TraceQueryBuilder.tsx | 4 ---- src/data/CHDatasource.ts | 4 ++-- 6 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/components/queryBuilder/DatabaseTableSelect.tsx b/src/components/queryBuilder/DatabaseTableSelect.tsx index b5ee1be4..7aea088f 100644 --- a/src/components/queryBuilder/DatabaseTableSelect.tsx +++ b/src/components/queryBuilder/DatabaseTableSelect.tsx @@ -21,6 +21,7 @@ export const DatabaseSelect = (props: DatabaseSelectProps) => { options.push({ label: empty, value: '' }); // Allow a blank value // Add selected value to the list if it does not exist. + // When loading an existing query, the saved value may no longer be in the list if (database && !databases.includes(database)) { options.push({ label: database, value: database }); } @@ -28,7 +29,7 @@ export const DatabaseSelect = (props: DatabaseSelectProps) => { useEffect(() => { // Auto select default db if (!database) { - onDatabaseChange(datasource.getDefaultDatabase() || 'default'); + onDatabaseChange(datasource.getDefaultDatabase()); } }, [datasource, database, onDatabaseChange]); diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index 5864b9c1..d2412083 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -31,10 +31,6 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { const labels = allLabels.components.LogsQueryBuilder; useEffect(() => { - if (!builderOptions) { - return; - } - builderOptions.meta?.otelEnabled !== undefined && setOtelEnabled(builderOptions.meta.otelEnabled); builderOptions.meta?.otelVersion && setOtelVersion(builderOptions.meta.otelVersion); setTimeColumn(getColumnByHint(builderOptions, ColumnHint.Time)); diff --git a/src/components/queryBuilder/views/TableQueryBuilder.tsx b/src/components/queryBuilder/views/TableQueryBuilder.tsx index 7ebe92d3..93d262e1 100644 --- a/src/components/queryBuilder/views/TableQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TableQueryBuilder.tsx @@ -29,10 +29,6 @@ export const TableQueryBuilder = (props: TableQueryBuilderProps) => { const labels = allLabels.components.TableQueryBuilder; useEffect(() => { - if (!builderOptions) { - return; - } - builderOptions.aggregates && setAggregateMode(builderOptions.aggregates.length > 0); builderOptions.columns && setSelectedColumns(builderOptions.columns); builderOptions.aggregates && setAggregates(builderOptions.aggregates); diff --git a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx index 79f0ffa6..ac8139c3 100644 --- a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -32,10 +32,6 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { const labels = allLabels.components.TimeSeriesQueryBuilder; useEffect(() => { - if (!builderOptions) { - return; - } - builderOptions.aggregates && setAggregateMode(builderOptions.aggregates.length > 0); setTimeColumn(getColumnByHint(builderOptions, ColumnHint.Time)); builderOptions.columns && setSelectedColumns(builderOptions.columns.filter(c => c.hint === undefined)); diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index 1a535219..4d9756c6 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -30,10 +30,6 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { const labels = allLabels.components.TraceQueryBuilder; useEffect(() => { - if (!builderOptions) { - return; - } - builderOptions.meta?.isTraceSearchMode !== undefined && setSearchMode(builderOptions.meta.isTraceSearchMode); setTraceIdColumn(getColumnByHint(builderOptions, ColumnHint.TraceId)); setSpanIdColumn(getColumnByHint(builderOptions, ColumnHint.TraceSpanId)); diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index 7be4e0ef..e6ca5dac 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -362,8 +362,8 @@ export class Datasource return value; } - getDefaultDatabase() { - return this.settings.jsonData.defaultDatabase; + getDefaultDatabase(): string { + return this.settings.jsonData.defaultDatabase || 'default'; } async fetchDatabases(): Promise { From 625d212a35f11b44cd51e49f8eadbce4d40f5923 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sun, 17 Sep 2023 20:26:14 -0400 Subject: [PATCH 11/95] inline conditional component, fix test --- .../queryBuilder/views/TableQueryBuilder.tsx | 16 +++++++--------- .../views/TimeSeriesQueryBuilder.tsx | 14 ++++++-------- src/data/CHDatasource.test.ts | 2 +- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/components/queryBuilder/views/TableQueryBuilder.tsx b/src/components/queryBuilder/views/TableQueryBuilder.tsx index 93d262e1..cd8cd4a8 100644 --- a/src/components/queryBuilder/views/TableQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TableQueryBuilder.tsx @@ -24,7 +24,7 @@ export const TableQueryBuilder = (props: TableQueryBuilderProps) => { const [aggregates, setAggregates] = useState([emptyAggregate]); const [groupBy, setGroupBy] = useState([]); const [orderBy, setOrderBy] = useState([]); - const [limit, setLimit] = useState(100); + const [limit, setLimit] = useState(1000); const [filters, setFilters] = useState([]); const labels = allLabels.components.TableQueryBuilder; @@ -62,13 +62,6 @@ export const TableQueryBuilder = (props: TableQueryBuilderProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isAggregateMode, selectedColumns, filters, aggregates, groupBy, orderBy, limit]); - const aggregateFields = ( - <> - - - - ); - return (
{ - {isAggregateMode && aggregateFields} + {isAggregateMode && ( + <> + + + + )} { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isAggregateMode, timeColumn, selectedColumns, filters, aggregates, groupBy, orderBy, limit]); - const aggregateFields = ( - <> - - - - ); - return (
{ /> - {isAggregateMode && aggregateFields} + {isAggregateMode && ( + <> + + + + )} { it('should Fetch Default Tags When No Second AdHoc Variable', async () => { const spyOnReplace = jest.spyOn(templateSrvMock, 'replace').mockImplementation(() => '$clickhouse_adhoc_query'); const ds = cloneDeep(mockDatasource); - ds.settings.jsonData.defaultDatabase = undefined; const frame = new ArrayDataFrame([{ name: 'foo', type: 'string', table: 'table' }]); + jest.spyOn(ds, 'getDefaultDatabase').mockImplementation(() => undefined!); // Disable default DB const spyOnQuery = jest.spyOn(ds, 'query').mockImplementation((_request) => of({ data: [frame] })); const keys = await ds.getTagKeys(); From 48e5ad9defe5226198363e41d0e71e43afe93012 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sun, 17 Sep 2023 23:59:49 -0400 Subject: [PATCH 12/95] added tests, comments --- .../queryBuilder/AggregateEditor.test.tsx | 17 ++++++++++++++++- src/components/queryBuilder/ColumnSelect.tsx | 16 +++++++--------- .../queryBuilder/GroupByEditor.test.tsx | 4 ++++ .../queryBuilder/views/TableQueryBuilder.tsx | 2 +- .../views/TimeSeriesQueryBuilder.tsx | 2 +- .../queryBuilder/views/TraceQueryBuilder.tsx | 2 +- 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/components/queryBuilder/AggregateEditor.test.tsx b/src/components/queryBuilder/AggregateEditor.test.tsx index e148798b..59505829 100644 --- a/src/components/queryBuilder/AggregateEditor.test.tsx +++ b/src/components/queryBuilder/AggregateEditor.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { AggregateEditor } from './AggregateEditor'; import { selectors } from 'selectors'; @@ -43,4 +43,19 @@ describe('AggregateEditor', () => { await userEvent.click(removeButton); expect(onAggregatesChange).toBeCalledWith([]); }); + + it('should call onAggregatesChange when aggregate is updated', async () => { + const inputAggregate: AggregateColumn = { aggregateType: AggregateType.Count, column: 'foo', alias: 'f' }; + const expectedAggregate: AggregateColumn = { aggregateType: AggregateType.Sum, column: 'foo', alias: 'f' }; + const onAggregatesChange = jest.fn(); + const result = render(); + expect(result.container.firstChild).not.toBeNull(); + + const aggregateSelect = result.getAllByRole('combobox')[0]; + expect(aggregateSelect).toBeInTheDocument(); + fireEvent.keyDown(aggregateSelect, { key: 'ArrowDown' }); + fireEvent.keyDown(aggregateSelect, { key: 'ArrowDown' }); + fireEvent.keyDown(aggregateSelect, { key: 'Enter' }); + expect(onAggregatesChange).toBeCalledWith([expectedAggregate]); + }); }); diff --git a/src/components/queryBuilder/ColumnSelect.tsx b/src/components/queryBuilder/ColumnSelect.tsx index 947438dd..4f381fb8 100644 --- a/src/components/queryBuilder/ColumnSelect.tsx +++ b/src/components/queryBuilder/ColumnSelect.tsx @@ -26,15 +26,13 @@ export const ColumnSelect = (props: ColumnSelectProps) => { map(c => ({ label: c.name, value: c.name })); const onChange = (selected: SelectableValue) => { - const column = allColumns.find(c => c.name === selected.value); - if (column) { - onColumnChange({ - name: column.name, - type: column.type, - custom: false, - hint: columnHint - }) - } + const column = allColumns.find(c => c.name === selected.value)!; + onColumnChange({ + name: column.name, + type: column.type, + custom: false, + hint: columnHint + }); } const labelStyle = 'query-keyword ' + (inline ? styles.QueryEditor.inlineField : ''); diff --git a/src/components/queryBuilder/GroupByEditor.test.tsx b/src/components/queryBuilder/GroupByEditor.test.tsx index 7c7b4e90..df571d95 100644 --- a/src/components/queryBuilder/GroupByEditor.test.tsx +++ b/src/components/queryBuilder/GroupByEditor.test.tsx @@ -25,8 +25,12 @@ describe('GroupByEditor', () => { const multiSelect = result.getByRole('combobox'); expect(multiSelect).toBeInTheDocument(); + + expect(result.queryAllByText('a').length).toBe(0); // is popup closed fireEvent.keyDown(multiSelect, { key: 'ArrowDown' }); + expect(result.queryAllByText('a').length).toBe(1); // is popup open fireEvent.keyDown(multiSelect, { key: 'Enter' }); + expect(result.queryAllByText('a').length).toBe(0); // is popup closed expect(onGroupByChange).toBeCalledTimes(1); expect(onGroupByChange).toBeCalledWith(expect.any(Object)); }); diff --git a/src/components/queryBuilder/views/TableQueryBuilder.tsx b/src/components/queryBuilder/views/TableQueryBuilder.tsx index cd8cd4a8..535a2a08 100644 --- a/src/components/queryBuilder/views/TableQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TableQueryBuilder.tsx @@ -19,7 +19,7 @@ const emptyAggregate: AggregateColumn = { column: '', aggregateType: AggregateTy export const TableQueryBuilder = (props: TableQueryBuilderProps) => { const { allColumns, builderOptions, onBuilderOptionsChange } = props; - const [isAggregateMode, setAggregateMode] = useState(false); + const [isAggregateMode, setAggregateMode] = useState(false); // Toggle Simple vs Aggregate mode const [selectedColumns, setSelectedColumns] = useState([]); const [aggregates, setAggregates] = useState([emptyAggregate]); const [groupBy, setGroupBy] = useState([]); diff --git a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx index 91de046e..db1afdd1 100644 --- a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -21,7 +21,7 @@ const emptyAggregate: AggregateColumn = { column: '', aggregateType: AggregateTy export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { const { allColumns, builderOptions, onBuilderOptionsChange } = props; - const [isAggregateMode, setAggregateMode] = useState(false); + const [isAggregateMode, setAggregateMode] = useState(false); // Toggle Simple vs Aggregate mode const [timeColumn, setTimeColumn] = useState(); const [selectedColumns, setSelectedColumns] = useState([]); const [aggregates, setAggregates] = useState([emptyAggregate]); diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index 4d9756c6..89fb0745 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -14,7 +14,7 @@ interface TraceQueryBuilderProps { export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { const { allColumns, builderOptions, onBuilderOptionsChange } = props; - const [isSearchMode, setSearchMode] = useState(false); + const [isSearchMode, setSearchMode] = useState(false); // Toggle for Trace ID vs Trace Search mode const [traceIdColumn, setTraceIdColumn] = useState(); const [spanIdColumn, setSpanIdColumn] = useState(); const [parentSpanIdColumn, setParentSpanIdColumn] = useState(); From a81eb1fa0e2c9cbd19a5bb25731879106223aee0 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 18 Sep 2023 01:25:27 -0400 Subject: [PATCH 13/95] tests for OtelVersionSelect and getColumnByHint --- .../queryBuilder/OtelVersionSelect.test.tsx | 15 +++++++++++++++ src/components/queryBuilder/utils.test.ts | 18 ++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/components/queryBuilder/OtelVersionSelect.test.tsx b/src/components/queryBuilder/OtelVersionSelect.test.tsx index e874d73a..23dfc9cc 100644 --- a/src/components/queryBuilder/OtelVersionSelect.test.tsx +++ b/src/components/queryBuilder/OtelVersionSelect.test.tsx @@ -87,4 +87,19 @@ describe('OtelVersionSelect', () => { expect(onVersionChange).toBeCalledTimes(1); expect(onVersionChange).toBeCalledWith(expect.any(String)); }); + + it('should disable version selection when switch is disabled', () => { + const result = render( + {}} + selectedVersion={testVersion} + onVersionChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const select = result.getByRole('combobox', { hidden: true }); + expect(select).toBeDisabled(); + }); }); diff --git a/src/components/queryBuilder/utils.test.ts b/src/components/queryBuilder/utils.test.ts index 79032f85..69fec8fa 100644 --- a/src/components/queryBuilder/utils.test.ts +++ b/src/components/queryBuilder/utils.test.ts @@ -1,5 +1,5 @@ -import { AggregateType, BuilderMode, FilterOperator, OrderByDirection } from 'types/queryBuilder'; -import { getQueryOptionsFromSql, getSqlFromQueryBuilderOptions, isDateTimeType, isDateType, isNumberType } from './utils'; +import { AggregateType, BuilderMode, FilterOperator, OrderByDirection, QueryBuilderOptions } from 'types/queryBuilder'; +import { getColumnByHint, getQueryOptionsFromSql, getSqlFromQueryBuilderOptions, isDateTimeType, isDateType, isNumberType } from './utils'; import { ColumnHint, QueryType } from 'types/queryBuilder'; describe('isDateType', () => { @@ -460,3 +460,17 @@ function testCondition(name: string, sql: string, builder: any, testQueryOptions } }); } + + +describe('getColumnByHint', () => { + it('returns a selected column when present', () => { + const testColumn = { name: 'time', type: 'datetime', hint: ColumnHint.Time }; + const builderOptions = { columns: [testColumn] } as QueryBuilderOptions; + expect(getColumnByHint(builderOptions, ColumnHint.Time)).toMatchObject(testColumn); + }); + it('returns a undefined when column not present', () => { + const testColumn = { name: 'time', type: 'datetime' }; + const builderOptions = { columns: [testColumn] } as QueryBuilderOptions; + expect(getColumnByHint(builderOptions, ColumnHint.Time)).toBeUndefined(); + }); +}); From fe3bfdb21699ef3d7a1888491a733cfd658f0aa8 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 18 Sep 2023 17:37:26 -0400 Subject: [PATCH 14/95] re-enable orderBby option filter logic --- .../queryBuilder/OrderByEditor.test.tsx | 180 +++++++++--------- src/components/queryBuilder/OrderByEditor.tsx | 75 ++++---- src/components/queryBuilder/utils.test.ts | 12 +- src/components/queryBuilder/utils.ts | 1 + .../queryBuilder/views/LogsQueryBuilder.tsx | 4 +- .../queryBuilder/views/TableQueryBuilder.tsx | 4 +- .../views/TimeSeriesQueryBuilder.tsx | 4 +- 7 files changed, 143 insertions(+), 137 deletions(-) diff --git a/src/components/queryBuilder/OrderByEditor.test.tsx b/src/components/queryBuilder/OrderByEditor.test.tsx index 7bdf67f8..82615591 100644 --- a/src/components/queryBuilder/OrderByEditor.test.tsx +++ b/src/components/queryBuilder/OrderByEditor.test.tsx @@ -1,36 +1,30 @@ import React from 'react'; import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { OrderByEditor, getOrderByFields } from './OrderByEditor'; -import { AggregateType, BuilderMode, ColumnHint, OrderByDirection, QueryType, TableColumn } from 'types/queryBuilder'; +import { OrderByEditor, getOrderByOptions } from './OrderByEditor'; +import { AggregateType, BuilderMode, OrderByDirection, QueryType, TableColumn } from 'types/queryBuilder'; +import { SelectableValue } from '@grafana/data'; -const newTestColumn = (name: string): TableColumn => ({ - name, - sortable: true, - type: 'String', - picklistValues: [] -}); - -const testColumns: ReadonlyArray = [ - newTestColumn('foo'), - newTestColumn('bar'), - newTestColumn('baz'), +const testOptions: Array> = [ + { label: 'foo', value: 'foo' }, + { label: 'bar', value: 'bar' }, + { label: 'baz', value: 'baz' }, ]; describe('OrderByEditor', () => { it('should render null when no fields passed', () => { - const result = render( {}} />); + const result = render( {}} />); expect(result.container.firstChild).toBeNull(); }); it('should render component when fields passed', () => { const result = render( - {}} /> + {}} /> ); expect(result.container.firstChild).not.toBeNull(); }); it('should render default add button when no orderby fields passed', () => { const result = render( - {}} /> + {}} /> ); expect(result.container.firstChild).not.toBeNull(); expect(result.getByTestId('query-builder-orderby-add-button')).toBeInTheDocument(); @@ -40,7 +34,7 @@ describe('OrderByEditor', () => { it('should render remove button when at least one orderby fields passed', () => { const result = render( {}} /> @@ -53,7 +47,7 @@ describe('OrderByEditor', () => { it('should render add/remove buttons correctly when multiple orderby elements passed', () => { const result = render( { it('should render label only once', () => { const result = render( { const onOrderByChange = jest.fn(); const result = render( @@ -98,11 +92,11 @@ describe('OrderByEditor', () => { expect(onOrderByChange).toBeCalledTimes(1); expect(onOrderByChange).toBeCalledWith([{ name: 'foo', dir: OrderByDirection.ASC }]); }); - it('should add and remove items when remove button clicked', async () => { + it('should remove items when remove button clicked', async () => { const onOrderByChange = jest.fn(); const result = render( { }); }); -describe('getOrderByFields', () => { - const sampleFields = [ +describe('getOrderByOptions', () => { + const allColumms: ReadonlyArray = [ { name: 'field1', - label: 'field1', type: 'string', picklistValues: [], }, { - name: 'field11', - label: 'field11', + name: 'field2', type: 'string', picklistValues: [], }, { - name: 'field2', - label: 'field2', + name: 'field3', type: 'string', picklistValues: [], }, { - name: 'field3', - label: 'field3', + name: 'field4', type: 'string', picklistValues: [], }, ]; - it('list view', () => { + + it('should return all columns as options', () => { expect( - getOrderByFields( + getOrderByOptions( { database: 'db', table: 'foo', queryType: QueryType.Table, - mode: BuilderMode.List, columns: [{ name: 'field1' }, { name: 'field3' }], }, - sampleFields + allColumms ) ).toStrictEqual([ { label: 'field1', value: 'field1', }, - { - label: 'field11', - value: 'field11', - }, { label: 'field2', value: 'field2', @@ -182,78 +168,118 @@ describe('getOrderByFields', () => { label: 'field3', value: 'field3', }, + { + label: 'field4', + value: 'field4', + }, ]); }); - it('aggregated view - no group by and no aggregates', () => { + it('should return only selected columns for aggregate query', () => { expect( - getOrderByFields( + getOrderByOptions( { database: 'db', table: 'foo', queryType: QueryType.Table, - mode: BuilderMode.Aggregate, - columns: [], - aggregates: [], + columns: [{ name: 'field1' }], + aggregates: [{ column: 'field2', aggregateType: AggregateType.Max }], }, - sampleFields + allColumms ) - ).toStrictEqual([]); + ).toStrictEqual([ + { + label: 'field1', + value: 'field1', + }, + { + label: 'max(field2)', + value: 'max(field2)', + } + ]); }); - it('aggregated view - no group by and with two aggregates', () => { + it('should return correct label and value for aggregates with aliases', () => { expect( - getOrderByFields( + getOrderByOptions( { database: 'db', table: 'foo', queryType: QueryType.Table, - mode: BuilderMode.Aggregate, - columns: [], + aggregates: [{ column: 'field1', aggregateType: AggregateType.Max, alias: 'a' }], + }, + allColumms + ) + ).toStrictEqual([ + { + label: 'max(field1) as a', + value: 'a', + } + ]); + }); + it('should show options from selected columns, aggregates, and groupBy', () => { + expect( + getOrderByOptions( + { + database: 'db', + table: 'foo', + queryType: QueryType.Table, + columns: [{ name: 'field1' }], aggregates: [ { column: 'field2', aggregateType: AggregateType.Max }, - { column: 'field1', aggregateType: AggregateType.Sum }, ], + groupBy: ['field2'] }, - sampleFields + allColumms ) ).toStrictEqual([ + { + value: 'field1', + label: 'field1', + }, { value: 'max(field2)', label: 'max(field2)', }, { - value: 'sum(field1)', - label: 'sum(field1)', + value: 'field2', + label: 'field2', }, ]); }); it('aggregated view - two group by and with no aggregates', () => { expect( - getOrderByFields( + getOrderByOptions( { database: 'db', table: 'foo', queryType: QueryType.Table, - mode: BuilderMode.Aggregate, columns: [], aggregates: [], groupBy: ['field3', 'field1'], }, - sampleFields + allColumms ) ).toStrictEqual([ + { + value: 'field1', + label: 'field1', + }, + { + value: 'field2', + label: 'field2', + }, { value: 'field3', label: 'field3', }, { - value: 'field1', - label: 'field1', + value: 'field4', + label: 'field4', }, ]); }); it('aggregated view - two group by and with two metrics', () => { expect( - getOrderByFields( + getOrderByOptions( { database: 'db', table: 'foo', @@ -266,7 +292,7 @@ describe('getOrderByFields', () => { ], groupBy: ['field3', 'field1'], }, - sampleFields + allColumms ) ).toStrictEqual([ { @@ -287,36 +313,4 @@ describe('getOrderByFields', () => { }, ]); }); - it('trend view', () => { - expect( - getOrderByFields( - { - database: 'db', - table: 'foo', - queryType: QueryType.Table, - mode: BuilderMode.Trend, - columns: [{ name: 'field3', type: 'datetime', hint: ColumnHint.Time }], - aggregates: [{ column: 'field2', aggregateType: AggregateType.Max }], - }, - sampleFields - ) - ).toStrictEqual([ - { - label: 'field1', - value: 'field1', - }, - { - label: 'field11', - value: 'field11', - }, - { - label: 'field2', - value: 'field2', - }, - { - label: 'field3', - value: 'field3', - }, - ]); - }); }); diff --git a/src/components/queryBuilder/OrderByEditor.tsx b/src/components/queryBuilder/OrderByEditor.tsx index 4352359b..7a4ab986 100644 --- a/src/components/queryBuilder/OrderByEditor.tsx +++ b/src/components/queryBuilder/OrderByEditor.tsx @@ -6,11 +6,10 @@ import { OrderByDirection, QueryBuilderOptions, TableColumn, - BuilderMode, - AggregateColumn, } from 'types/queryBuilder'; import allLabels from 'labels'; import { styles } from 'styles'; +import { isAggregateQuery } from './utils'; interface OrderByItemProps { columnOptions: Array>; @@ -32,7 +31,7 @@ const OrderByItem = (props: OrderByItemProps) => { setInputId(e.currentTarget.value)} + onBlur={() => onChange(inputId)} + /> +
+ ) +} \ No newline at end of file diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index 2c0c0edb..9419c86e 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -15,6 +15,11 @@ export const generateSql = (options: QueryBuilderOptions): string => { return getSqlFromQueryBuilderOptions(options); } +/** + * Generates trace query with columns that fit Grafana's Trace panel + * Column aliases follow this structure: + * https://grafana.com/docs/grafana/latest/explore/trace-integration/#data-frame-structure + */ const generateTraceQuery = (options: QueryBuilderOptions): string => { const { database, table } = options; const limit = getLimit(options.limit); @@ -75,6 +80,12 @@ const generateTraceQuery = (options: QueryBuilderOptions): string => { queryParts.push('FROM'); queryParts.push(getTableIdentifier(database, table)); + if (!options.meta?.isTraceSearchMode && options.meta?.traceId) { + const traceId = options.meta.traceId; + queryParts.push('WHERE'); + queryParts.push(`traceID = '${traceId}'`); + } + if (traceStartTime !== undefined) { queryParts.push('ORDER BY startTime ASC'); } diff --git a/src/labels.ts b/src/labels.ts index 432b2c46..c13e9c17 100644 --- a/src/labels.ts +++ b/src/labels.ts @@ -104,8 +104,10 @@ export default { traceSearchModeLabel: 'Trace Search', traceModeLabel: 'Trace Mode', traceModeTooltip: 'Switches between trace ID and trace search mode', + columnsSection: 'Columns', + filtersSection: 'Filters', - fields: { + columns: { traceId: { label: 'Trace ID Column', tooltip: 'Column that contains the trace ID' @@ -145,6 +147,10 @@ export default { serviceTags: { label: 'Service Tags Column', tooltip: 'Column that contains the service tags' + }, + traceIdFilter: { + label: 'Trace ID', + tooltip: 'filter by a specific trace ID' } }, } diff --git a/src/types/queryBuilder.ts b/src/types/queryBuilder.ts index f18be1d0..1d6f9e6e 100644 --- a/src/types/queryBuilder.ts +++ b/src/types/queryBuilder.ts @@ -45,7 +45,7 @@ export interface QueryBuilderOptions { // Trace isTraceSearchMode?: boolean; traceDurationUnit?: TimeUnit; - traceId?: string; // TODO: this doesn't need to be persisted? + traceId?: string; } } From e592872ad0d61f60edf7dc76e6c91cdb06aca7a3 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Fri, 29 Sep 2023 04:20:50 -0400 Subject: [PATCH 18/95] change incorrect sql function --- src/data/sqlGenerator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index 9419c86e..8c010c4a 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -136,7 +136,7 @@ const getTraceDurationSelectSql = (columnIdentifier: string, timeUnit?: TimeUnit const alias = 'duration'; switch (timeUnit) { case TimeUnit.Seconds: - return `intMul(${columnIdentifier}, 1000) as ${alias}`; + return `multiply(${columnIdentifier}, 1000) as ${alias}`; case TimeUnit.Milliseconds: return `${columnIdentifier} as ${alias}`; case TimeUnit.Microseconds: From 350523064a9d2688d6b67b857dfb8382bbc9f9c0 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Fri, 29 Sep 2023 04:42:19 -0400 Subject: [PATCH 19/95] add generic trace filters --- src/components/queryBuilder/utils.ts | 2 +- .../queryBuilder/views/TraceQueryBuilder.tsx | 2 +- src/data/sqlGenerator.ts | 20 ++++++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/queryBuilder/utils.ts b/src/components/queryBuilder/utils.ts index 8ce9042b..017ca993 100644 --- a/src/components/queryBuilder/utils.ts +++ b/src/components/queryBuilder/utils.ts @@ -137,7 +137,7 @@ const getTrendByQuery = ( return `SELECT ${metricsQuery} FROM ${escaped(database)}${sep}${escaped(table)}`; }; -const getFilters = (filters: Filter[]): string => { +export const getFilters = (filters: Filter[]): string => { return filters.reduce((previousValue, currentFilter, currentIndex) => { const prefixCondition = currentIndex === 0 ? '' : currentFilter.condition; let filter = ''; diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index bf62dd3d..6ddc85b0 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -82,7 +82,7 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { // TODO: ignore when builderOptions changes? // eslint-disable-next-line react-hooks/exhaustive-deps - }, [traceIdColumn, spanIdColumn, parentSpanIdColumn, serviceNameColumn, operationNameColumn, startTimeColumn, durationTimeColumn, tagsColumn, serviceTagsColumn, isSearchMode, durationUnit, traceId]); + }, [traceIdColumn, spanIdColumn, parentSpanIdColumn, serviceNameColumn, operationNameColumn, startTimeColumn, durationTimeColumn, tagsColumn, serviceTagsColumn, filters, isSearchMode, durationUnit, traceId]); return (
diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index 8c010c4a..e1d46f4f 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -1,4 +1,4 @@ -import { getSqlFromQueryBuilderOptions } from 'components/queryBuilder/utils'; +import { getSqlFromQueryBuilderOptions, getFilters } from 'components/queryBuilder/utils'; import { ColumnHint, QueryBuilderOptions, QueryType, SelectedColumn, TimeUnit } from 'types/queryBuilder'; @@ -80,12 +80,22 @@ const generateTraceQuery = (options: QueryBuilderOptions): string => { queryParts.push('FROM'); queryParts.push(getTableIdentifier(database, table)); - if (!options.meta?.isTraceSearchMode && options.meta?.traceId) { - const traceId = options.meta.traceId; + const hasTraceIdFilter = !options.meta?.isTraceSearchMode && options.meta?.traceId + const hasFilters = (options.filters?.length || 0) > 0; + + if (hasTraceIdFilter || hasFilters) { queryParts.push('WHERE'); + } + + if (hasTraceIdFilter) { + const traceId = options.meta!.traceId; queryParts.push(`traceID = '${traceId}'`); } + if (hasFilters) { + queryParts.push(getFilters(options.filters!)); + } + if (traceStartTime !== undefined) { queryParts.push('ORDER BY startTime ASC'); } @@ -140,9 +150,9 @@ const getTraceDurationSelectSql = (columnIdentifier: string, timeUnit?: TimeUnit case TimeUnit.Milliseconds: return `${columnIdentifier} as ${alias}`; case TimeUnit.Microseconds: - return `intDiv(${columnIdentifier}, 1000) as ${alias}`; + return `intDivOrZero(${columnIdentifier}, 1000) as ${alias}`; case TimeUnit.Nanoseconds: - return `intDiv(${columnIdentifier}, 1000000) as ${alias}`; + return `intDivOrZero(${columnIdentifier}, 1000000) as ${alias}`; default: return `${columnIdentifier} as ${alias}`; } From 837ad2db2180b0ef2c42352f6a8325eb4db3d133 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Fri, 29 Sep 2023 15:35:00 -0400 Subject: [PATCH 20/95] fix explore view panel format --- src/data/utils.ts | 21 +++++++++++++++++++++ src/plugin.json | 1 + src/types/sql.ts | 6 ++++++ src/views/CHQueryEditor.tsx | 9 ++++++++- 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/data/utils.ts diff --git a/src/data/utils.ts b/src/data/utils.ts new file mode 100644 index 00000000..5425a13a --- /dev/null +++ b/src/data/utils.ts @@ -0,0 +1,21 @@ +import { QueryType } from "types/queryBuilder" + + +/** + * Converts QueryType to Grafana format + * src: https://github.com/grafana/sqlds/blob/main/query.go#L20 + */ +export const mapQueryTypeToGrafanaFormat = (t: QueryType): number => { + switch (t) { + case QueryType.Table: + return 1; + case QueryType.Logs: + return 2; + case QueryType.TimeSeries: + return 0; + case QueryType.Traces: + return 3 + default: + return -1 // defaults to timeseries/graph on plugin backend. + } +} \ No newline at end of file diff --git a/src/plugin.json b/src/plugin.json index ae2b7467..06ced739 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -6,6 +6,7 @@ "metrics": true, "backend": true, "logs": true, + "tracing": true, "alerting": true, "annotations": true, "executable": "gpx_clickhouse", diff --git a/src/types/sql.ts b/src/types/sql.ts index 6ffa65f6..511fe7a2 100644 --- a/src/types/sql.ts +++ b/src/types/sql.ts @@ -12,6 +12,12 @@ export enum EditorType { export interface CHQueryBase extends DataQuery { editorType: EditorType; rawSql: string; + + /** + * REQUIRED by backend for auto selecting preferredVisualisationType. + * src: https://github.com/grafana/sqlds/blob/main/query.go#L36 + */ + format?: number; } export interface CHSqlQuery extends CHQueryBase { diff --git a/src/views/CHQueryEditor.tsx b/src/views/CHQueryEditor.tsx index f3cf03f7..0bf891b6 100644 --- a/src/views/CHQueryEditor.tsx +++ b/src/views/CHQueryEditor.tsx @@ -10,6 +10,7 @@ import { QueryBuilderOptions } from 'types/queryBuilder'; import { QueryBuilder } from 'components/queryBuilder/QueryBuilder'; import { generateSql } from 'data/sqlGenerator'; import { SqlEditor } from 'components/SqlEditor'; +import { mapQueryTypeToGrafanaFormat } from 'data/utils'; export type CHQueryEditorProps = QueryEditorProps; @@ -34,7 +35,13 @@ const CHEditorByType = (props: CHQueryEditorProps) => { const { query, onChange, app } = props; const onBuilderOptionsChange = (builderOptions: QueryBuilderOptions) => { const sql = generateSql(builderOptions); - onChange({ ...query, editorType: EditorType.Builder, rawSql: sql, builderOptions }); + onChange({ + ...query, + editorType: EditorType.Builder, + rawSql: sql, + builderOptions, + format: mapQueryTypeToGrafanaFormat(builderOptions.queryType) + }); }; if (query.editorType === EditorType.SQL) { From cf6acd0d51c021aecf5cb7efa73284d43e5022a4 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Fri, 29 Sep 2023 15:50:09 -0400 Subject: [PATCH 21/95] logs query generator --- src/components/queryBuilder/utils.ts | 2 +- .../queryBuilder/views/LogsQueryBuilder.tsx | 2 +- src/data/sqlGenerator.ts | 84 +++++++++++++++++-- 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/components/queryBuilder/utils.ts b/src/components/queryBuilder/utils.ts index 017ca993..3a9ca1a8 100644 --- a/src/components/queryBuilder/utils.ts +++ b/src/components/queryBuilder/utils.ts @@ -212,7 +212,7 @@ const getGroupBy = (groupBy: string[] = [], timeField?: string): string => { return `${clause}, time`; }; -const getOrderBy = (orderBy?: OrderBy[], prefix = true): string => { +export const getOrderBy = (orderBy?: OrderBy[], prefix = true): string => { const pfx = prefix ? ' ORDER BY ' : ''; return orderBy && orderBy.filter((o) => o.name).length > 0 ? pfx + diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index fab6d69e..95d69587 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -26,7 +26,7 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { const [messageColumn, setMessageColumn] = useState(); const [liveView, setLiveView] = useState(false); const [orderBy, setOrderBy] = useState([]); - const [limit, setLimit] = useState(100); + const [limit, setLimit] = useState(1000); const [filters, setFilters] = useState([]); const labels = allLabels.components.LogsQueryBuilder; diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index e1d46f4f..4e6a8cf7 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -1,4 +1,4 @@ -import { getSqlFromQueryBuilderOptions, getFilters } from 'components/queryBuilder/utils'; +import { getSqlFromQueryBuilderOptions, getFilters, getOrderBy } from 'components/queryBuilder/utils'; import { ColumnHint, QueryBuilderOptions, QueryType, SelectedColumn, TimeUnit } from 'types/queryBuilder'; @@ -8,6 +8,8 @@ export const generateSql = (options: QueryBuilderOptions): string => { if (options.queryType === QueryType.Traces) { return generateTraceQuery(options); + } else if (options.queryType === QueryType.Logs) { + return generateLogsQuery(options); } // const queryParts = []; @@ -107,7 +109,68 @@ const generateTraceQuery = (options: QueryBuilderOptions): string => { return queryParts.join(' '); } +/** + * Generates logs query with columns that fit Grafana's Logs panel + * Column aliases follow this structure: + * https://grafana.com/developers/plugin-tools/tutorials/build-a-logs-data-source-plugin#logs-data-frame-format + * + * note: column order seems to matter as well as alias name + */ +const generateLogsQuery = (options: QueryBuilderOptions): string => { + const { database, table } = options; + const limit = getLimit(options.limit); + + const queryParts: string[] = []; + + // TODO: these columns could be a map or some other convenience function + const selectParts: string[] = []; + const logTime = getColumnByHint(options, ColumnHint.Time); + if (logTime !== undefined) { + // Must be first column in list. + logTime.alias = 'timestamp'; + selectParts.push(getColumnIdentifier(logTime)); + } + + const logMessage = getColumnByHint(options, ColumnHint.LogMessage); + if (logMessage !== undefined) { + // Must be second column in list. + logMessage.alias = 'body'; + selectParts.push(getColumnIdentifier(logMessage)); + } + + const logLevel = getColumnByHint(options, ColumnHint.LogLevel); + if (logLevel !== undefined) { + // TODO: "severity" should be a number, but "level" can be a string? Perhaps we can check the column type here? + logLevel.alias = 'level'; + selectParts.push(getColumnIdentifier(logLevel)); + } + + const selectPartsSql = selectParts.join(', '); + + queryParts.push('SELECT'); + queryParts.push(selectPartsSql); + queryParts.push('FROM'); + queryParts.push(getTableIdentifier(database, table)); + + if ((options.filters?.length || 0) > 0) { + queryParts.push('WHERE'); + queryParts.push(getFilters(options.filters!)); + } + + if ((options.orderBy?.length || 0) > 0) { + queryParts.push('ORDER BY'); + queryParts.push(getOrderBy(options.orderBy, false)); + } + + if (limit !== '') { + queryParts.push(limit); + } + + return queryParts.join(' '); +} + export const getColumnByHint = (options: QueryBuilderOptions, hint: ColumnHint): SelectedColumn | undefined => options.columns?.find(c => c.hint === hint); +export const getColumnIndexByHint = (options: QueryBuilderOptions, hint: ColumnHint): number => options.columns?.findIndex(c => c.hint === hint) || -1; export const getColumnsByHints = (options: QueryBuilderOptions, hints: readonly ColumnHint[]): readonly SelectedColumn[] => { const columns = []; @@ -121,13 +184,20 @@ export const getColumnsByHints = (options: QueryBuilderOptions, hints: readonly return columns; } -// const getColumnIdentifier = (col: SelectedColumn): string => { -// if (col.alias) { -// return `${col.name} as ${col.alias}` -// } +const getColumnIdentifier = (col: SelectedColumn): string => { + let colName = escapeIdentifier(col.name); -// return col.name; -// } + // allow for functions like count() + if (colName.includes('(') || colName.includes(')')) { + colName = col.name + } + + if (col.alias) { + return `${colName} as ${col.alias}` + } + + return colName; +} const getTableIdentifier = (database: string, table: string): string => { const sep = (database === '' || table === '') ? '' : '.'; From 313d574acd418320eca574ba0c3e5284244fa75a Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sat, 30 Sep 2023 03:53:20 -0400 Subject: [PATCH 22/95] fix sql editor explore view --- pkg/plugin/driver.go | 2 - src/components/SqlEditor.tsx | 82 ++++++++++++------- .../queryBuilder/EditorTypeSwitcher.tsx | 3 + .../queryBuilder/QueryTypeSwitcher.tsx | 7 +- .../queryBuilder/views/LogsQueryBuilder.tsx | 2 +- src/data/CHDatasource.test.ts | 1 + src/data/utils.ts | 2 +- src/labels.ts | 3 +- src/types/sql.ts | 2 + 9 files changed, 68 insertions(+), 36 deletions(-) diff --git a/pkg/plugin/driver.go b/pkg/plugin/driver.go index 1d2df7a8..e97c82b7 100644 --- a/pkg/plugin/driver.go +++ b/pkg/plugin/driver.go @@ -263,7 +263,6 @@ func (h *Clickhouse) MutateQuery(ctx context.Context, req backend.DataQuery) (co // MutateResponse For any view other than traces we convert FieldTypeNullableJSON to string func (h *Clickhouse) MutateResponse(ctx context.Context, res data.Frames) (data.Frames, error) { for _, frame := range res { - frame.Meta.PreferredVisualization = data.VisTypeTrace // TODO: Temporary fix for Traces. if frame.Meta.PreferredVisualization != data.VisTypeTrace && frame.Meta.PreferredVisualization != data.VisTypeTable { var fields []*data.Field @@ -291,7 +290,6 @@ func (h *Clickhouse) MutateResponse(ctx context.Context, res data.Frames) (data. } } frame.Fields = fields - //frame.Meta.PreferredVisualization = data.VisTypeTable // TODO: Temporary fix for Explorer view. } } return res, nil diff --git a/src/components/SqlEditor.tsx b/src/components/SqlEditor.tsx index 8bcec0a4..b7bca6de 100644 --- a/src/components/SqlEditor.tsx +++ b/src/components/SqlEditor.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { QueryEditorProps } from '@grafana/data'; +import React, { useEffect, useState } from 'react'; +import { CoreApp, QueryEditorProps } from '@grafana/data'; import { CodeEditor } from '@grafana/ui'; import { Datasource } from 'data/CHDatasource'; import { registerSQL, Range, Fetcher } from './sqlProvider'; @@ -9,6 +9,9 @@ import { styles } from 'styles'; import { fetchSuggestions as sugg, Schema } from './suggestions'; import { selectors } from 'selectors'; import { validate } from 'data/validate'; +import { mapQueryTypeToGrafanaFormat } from 'data/utils'; +import { QueryType } from 'types/queryBuilder'; +import { QueryTypeSwitcher } from 'components/queryBuilder/QueryTypeSwitcher'; type SqlEditorProps = QueryEditorProps; @@ -20,20 +23,36 @@ interface Expand { export const SqlEditor = (props: SqlEditorProps) => { const defaultHeight = '150px'; - const { query, onRunQuery, onChange, datasource } = props; + const { app, query, onChange, datasource } = props; const sqlQuery = query as CHSqlQuery; const [codeEditor, setCodeEditor] = useState(); + const [queryType, setQueryType] = useState(QueryType.Table); + const [sql, setSql] = useState(''); const [expand, setExpand] = useState({ height: defaultHeight, icon: 'plus', on: sqlQuery.expand || false, }); - const onSqlChange = (sql: string) => { - // const format = getFormat(sql, query.selectedFormat); - onChange({ ...query, rawSql: sql, editorType: EditorType.SQL }); - onRunQuery(); - }; + useEffect(() => { + sqlQuery.queryType && setQueryType(sqlQuery.queryType); + setSql(sqlQuery.rawSql); + + // Run on load + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + onChange({ + ...query, + editorType: EditorType.SQL, + queryType, + rawSql: sql, + format: mapQueryTypeToGrafanaFormat(queryType), + }); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [queryType, sql]) const onToggleExpand = () => { const on = !expand.on; @@ -105,26 +124,33 @@ export const SqlEditor = (props: SqlEditorProps) => { }; return ( -
- onToggleExpand()} - className={styles.Common.expand} - data-testid={selectors.components.QueryEditor.CodeEditor.Expand} - > - - - onChange({ ...query, rawSql: text })} - onEditorDidMount={(editor: any) => handleMount(editor)} - /> -
+ <> + { app === CoreApp.Explore && +
+ +
+ } + + ); }; diff --git a/src/components/queryBuilder/EditorTypeSwitcher.tsx b/src/components/queryBuilder/EditorTypeSwitcher.tsx index 53ef18d5..a6b6ac8b 100644 --- a/src/components/queryBuilder/EditorTypeSwitcher.tsx +++ b/src/components/queryBuilder/EditorTypeSwitcher.tsx @@ -7,6 +7,7 @@ import labels from 'labels'; import { EditorType, CHQuery, defaultCHBuilderQuery } from 'types/sql'; import { QueryBuilderOptions } from 'types/queryBuilder'; import isString from 'lodash/isString'; +import { mapQueryTypeToGrafanaFormat } from 'data/utils'; interface CHEditorTypeSwitcherProps { query: CHQuery; @@ -59,7 +60,9 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { onChange({ ...query, editorType: EditorType.SQL, + queryType: builderOptions.queryType, rawSql: generateSql(builderOptions), + format: mapQueryTypeToGrafanaFormat(builderOptions.queryType), meta: { builderOptions }, }); } else if (editorType === EditorType.Builder) { diff --git a/src/components/queryBuilder/QueryTypeSwitcher.tsx b/src/components/queryBuilder/QueryTypeSwitcher.tsx index e7817d47..6b3843ac 100644 --- a/src/components/queryBuilder/QueryTypeSwitcher.tsx +++ b/src/components/queryBuilder/QueryTypeSwitcher.tsx @@ -6,6 +6,7 @@ import { QueryType } from 'types/queryBuilder'; export interface QueryTypeSwitcherProps { queryType: QueryType; onChange: (queryType: QueryType) => void; + sqlEditor?: boolean; }; const options = [ @@ -31,8 +32,8 @@ const options = [ * Component for switching between the different query builder interfaces */ export const QueryTypeSwitcher = (props: QueryTypeSwitcherProps) => { - const { queryType, onChange } = props; - const { label, tooltip } = labels.components.QueryTypeSwitcher; + const { queryType, onChange, sqlEditor } = props; + const { label, tooltip, sqlTooltip } = labels.components.QueryTypeSwitcher; useEffect(() => { if (!queryType) { @@ -42,7 +43,7 @@ export const QueryTypeSwitcher = (props: QueryTypeSwitcherProps) => { return ( - + {label} onChange(v)} /> diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index 95d69587..75caf6b9 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -11,7 +11,7 @@ import allLabels from 'labels'; import { getColumnByHint } from 'components/queryBuilder/utils'; interface LogsQueryBuilderProps { - allColumns: ReadonlyArray; + allColumns: readonly TableColumn[]; builderOptions: QueryBuilderOptions, onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; } diff --git a/src/data/CHDatasource.test.ts b/src/data/CHDatasource.test.ts index f70ff6c2..77055ea0 100644 --- a/src/data/CHDatasource.test.ts +++ b/src/data/CHDatasource.test.ts @@ -377,6 +377,7 @@ describe('ClickHouseDatasource', () => { datasource.getSupplementaryLogsVolumeQuery(request, { ...query, editorType: EditorType.SQL, + queryType: undefined }) ).toBeUndefined(); expect( diff --git a/src/data/utils.ts b/src/data/utils.ts index 5425a13a..44353e88 100644 --- a/src/data/utils.ts +++ b/src/data/utils.ts @@ -18,4 +18,4 @@ export const mapQueryTypeToGrafanaFormat = (t: QueryType): number => { default: return -1 // defaults to timeseries/graph on plugin backend. } -} \ No newline at end of file +} diff --git a/src/labels.ts b/src/labels.ts index c13e9c17..14fcf2c8 100644 --- a/src/labels.ts +++ b/src/labels.ts @@ -16,7 +16,8 @@ export default { }, QueryTypeSwitcher: { label: 'Query Type', - tooltip: 'Sets the layout for the query builder' + tooltip: 'Sets the layout for the query builder', + sqlTooltip: 'Sets the panel type for explore view' }, DatabaseSelect: { label: 'Database', diff --git a/src/types/sql.ts b/src/types/sql.ts index 511fe7a2..0c3b3b90 100644 --- a/src/types/sql.ts +++ b/src/types/sql.ts @@ -15,6 +15,7 @@ export interface CHQueryBase extends DataQuery { /** * REQUIRED by backend for auto selecting preferredVisualisationType. + * Only used in explore view. * src: https://github.com/grafana/sqlds/blob/main/query.go#L36 */ format?: number; @@ -22,6 +23,7 @@ export interface CHQueryBase extends DataQuery { export interface CHSqlQuery extends CHQueryBase { editorType: EditorType.SQL; + queryType?: QueryType; // only used in explore view meta?: { timezone?: string; // meta fields to be used just for building builder options when migrating back to EditorType.Builder From 2f8ea6e198ece42c54e38fc6fa1d66eabe52728d Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sat, 30 Sep 2023 04:55:11 -0400 Subject: [PATCH 23/95] auto select logs columns from otel config --- src/components/queryBuilder/ColumnSelect.tsx | 6 ++-- src/components/queryBuilder/ColumnsEditor.tsx | 8 +++-- .../queryBuilder/views/LogsQueryBuilder.tsx | 35 +++++++++++++++++-- src/data/sqlGenerator.ts | 5 --- src/otel.ts | 16 +++++---- 5 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/components/queryBuilder/ColumnSelect.tsx b/src/components/queryBuilder/ColumnSelect.tsx index 4f381fb8..464647ce 100644 --- a/src/components/queryBuilder/ColumnSelect.tsx +++ b/src/components/queryBuilder/ColumnSelect.tsx @@ -5,13 +5,14 @@ import { ColumnHint, SelectedColumn, TableColumn } from 'types/queryBuilder'; import { styles } from 'styles'; interface ColumnSelectProps { - allColumns: ReadonlyArray; + allColumns: readonly TableColumn[]; selectedColumn: SelectedColumn | undefined; onColumnChange: (c: SelectedColumn) => void; columnFilterFn?: (c: TableColumn) => boolean; columnHint?: ColumnHint; label: string; tooltip: string; + disabled?: boolean; wide?: boolean; inline?: boolean; } @@ -19,7 +20,7 @@ interface ColumnSelectProps { const defaultFilterFn = () => true; export const ColumnSelect = (props: ColumnSelectProps) => { - const { allColumns, selectedColumn, onColumnChange, columnFilterFn, columnHint, label, tooltip, wide, inline } = props; + const { allColumns, selectedColumn, onColumnChange, columnFilterFn, columnHint, label, tooltip, disabled, wide, inline } = props; const selectedColumnName = selectedColumn?.name; const columns: Array> = allColumns. filter(columnFilterFn || defaultFilterFn). @@ -43,6 +44,7 @@ export const ColumnSelect = (props: ColumnSelectProps) => { {label} + disabled={disabled} options={columns} value={selectedColumnName} onChange={onChange} diff --git a/src/components/queryBuilder/ColumnsEditor.tsx b/src/components/queryBuilder/ColumnsEditor.tsx index cbe9921c..baf8d731 100644 --- a/src/components/queryBuilder/ColumnsEditor.tsx +++ b/src/components/queryBuilder/ColumnsEditor.tsx @@ -7,12 +7,13 @@ import { selectors } from 'selectors'; import { styles } from 'styles'; interface ColumnsEditorProps { - allColumns: ReadonlyArray; + allColumns: readonly TableColumn[]; selectedColumns: SelectedColumn[]; onSelectedColumnsChange: (selectedColumns: SelectedColumn[]) => void; + disabled?: boolean; } -function getCustomColumns(columnNames: string[], allColumns: ReadonlyArray): Array> { +function getCustomColumns(columnNames: string[], allColumns: readonly TableColumn[]): Array> { const columnNamesSet = new Set(columnNames); return allColumns. filter(c => columnNamesSet.has(c.name)). @@ -20,7 +21,7 @@ function getCustomColumns(columnNames: string[], allColumns: ReadonlyArray { - const { allColumns, selectedColumns, onSelectedColumnsChange } = props; + const { allColumns, selectedColumns, onSelectedColumnsChange, disabled } = props; const [customColumns, setCustomColumns] = useState>>([]); const [isOpen, setIsOpen] = useState(false); const allColumnNames = allColumns.map(c => ({ label: c.name, value: c.name })); @@ -74,6 +75,7 @@ export const ColumnsEditor = (props: ColumnsEditorProps) => {
+ disabled={disabled} options={options} value={selectedColumnNames} isOpen={isOpen} diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index 75caf6b9..614a880e 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint } from 'types/queryBuilder'; +import { BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint, OrderByDirection } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; import { Switch } from '../Switch'; import { OtelVersionSelect } from '../OtelVersionSelect'; @@ -9,6 +9,7 @@ import { LimitEditor } from '../LimitEditor'; import { FiltersEditor } from '../FilterEditor'; import allLabels from 'labels'; import { getColumnByHint } from 'components/queryBuilder/utils'; +import { versions as otelVersions } from 'otel'; interface LogsQueryBuilderProps { allColumns: readonly TableColumn[]; @@ -51,6 +52,33 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { + const otelConfig = otelVersions.find(v => v.version === otelVersion); + if (!otelEnabled || !otelConfig) { + return; + } + + const columnMap = new Map(); + allColumns.forEach(c => columnMap.set(c.name, c)); + const logColumnMap = otelConfig.logColumnMap; + for (const [hint, colName] of logColumnMap) { + const col = columnMap.get(colName); + if (!col) { + continue; + } + + const selectedColumn: SelectedColumn = { name: col.name, type: col.type, hint }; + switch (hint) { + case ColumnHint.Time: + setTimeColumn(selectedColumn); + case ColumnHint.LogMessage: + setMessageColumn(selectedColumn); + case ColumnHint.LogLevel: + setLogLevelColumn(selectedColumn); + } + } + }, [otelEnabled, otelVersion, allColumns]); + useEffect(() => { const nextColumns = selectedColumns.slice(); if (timeColumn) { @@ -90,9 +118,10 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { onVersionChange={setOtelVersion} defaultToLatest /> - +
{ tooltip={labels.logTimeColumn.tooltip} /> {
{ - // const { database, table } = options; - // const limit = getLimit(options.limit); - if (options.queryType === QueryType.Traces) { return generateTraceQuery(options); } else if (options.queryType === QueryType.Logs) { return generateLogsQuery(options); } - // const queryParts = []; - // return queryParts.join(' ').trim(); return getSqlFromQueryBuilderOptions(options); } diff --git a/src/otel.ts b/src/otel.ts index 431084d3..cc421190 100644 --- a/src/otel.ts +++ b/src/otel.ts @@ -1,11 +1,13 @@ +import { ColumnHint } from "types/queryBuilder"; + export const versions = [ { - name: 'demo', - version: '0.0.1', - columnMap: { - timestamp: 'timestamp', - level: 'severity', - message: 'body' - } + name: 'default', + version: 'default', + logColumnMap: new Map([ + [ColumnHint.Time, 'Timestamp'], + [ColumnHint.LogMessage, 'Body'], + [ColumnHint.LogLevel, 'SeverityText'], + ]), }, ]; From 0a15e7ed374b6e92caa8ab200dcc3f4168676c5a Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sat, 30 Sep 2023 05:08:34 -0400 Subject: [PATCH 24/95] filter column types --- .../queryBuilder/views/LogsQueryBuilder.tsx | 6 +++++- .../queryBuilder/views/TimeSeriesQueryBuilder.tsx | 2 ++ src/data/columnFilters.ts | 13 +++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/data/columnFilters.ts diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index 614a880e..33598bc7 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint, OrderByDirection } from 'types/queryBuilder'; +import { BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; import { Switch } from '../Switch'; import { OtelVersionSelect } from '../OtelVersionSelect'; @@ -10,6 +10,7 @@ import { FiltersEditor } from '../FilterEditor'; import allLabels from 'labels'; import { getColumnByHint } from 'components/queryBuilder/utils'; import { versions as otelVersions } from 'otel'; +import { columnFilterDateTime, columnFilterString } from 'data/columnFilters'; interface LogsQueryBuilderProps { allColumns: readonly TableColumn[]; @@ -125,6 +126,7 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { allColumns={allColumns} selectedColumn={timeColumn} onColumnChange={setTimeColumn} + columnFilterFn={columnFilterDateTime} columnHint={ColumnHint.Time} label={labels.logTimeColumn.label} tooltip={labels.logTimeColumn.tooltip} @@ -134,6 +136,7 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { allColumns={allColumns} selectedColumn={logLevelColumn} onColumnChange={setLogLevelColumn} + columnFilterFn={columnFilterString} columnHint={ColumnHint.LogLevel} label={labels.logLevelColumn.label} tooltip={labels.logLevelColumn.tooltip} @@ -146,6 +149,7 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { allColumns={allColumns} selectedColumn={messageColumn} onColumnChange={setMessageColumn} + columnFilterFn={columnFilterString} columnHint={ColumnHint.LogMessage} label={labels.logMessageColumn.label} tooltip={labels.logMessageColumn.tooltip} diff --git a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx index b649795e..5cace414 100644 --- a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -10,6 +10,7 @@ import { AggregateEditor } from '../AggregateEditor'; import { GroupByEditor } from '../GroupByEditor'; import { ColumnSelect } from '../ColumnSelect'; import { getColumnByHint } from 'components/queryBuilder/utils'; +import { columnFilterDateTime } from 'data/columnFilters'; interface TimeSeriesQueryBuilderProps { allColumns: ReadonlyArray; @@ -85,6 +86,7 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { allColumns={allColumns} selectedColumn={timeColumn} onColumnChange={setTimeColumn} + columnFilterFn={columnFilterDateTime} columnHint={ColumnHint.Time} label={labels.timeColumn.label} tooltip={labels.timeColumn.tooltip} diff --git a/src/data/columnFilters.ts b/src/data/columnFilters.ts new file mode 100644 index 00000000..9f593ec2 --- /dev/null +++ b/src/data/columnFilters.ts @@ -0,0 +1,13 @@ +import { SelectedColumn } from "types/queryBuilder"; + + +export const columnFilterDateTime = (s: SelectedColumn): boolean => (s.type || '').toLowerCase().startsWith('date'); +export const columnFilterString = (s: SelectedColumn): boolean => (s.type || '').toLowerCase().includes('string') || (s.type || '').toLowerCase().includes('enum'); +export const columnFilterOr = (s: SelectedColumn, ...filterFuncs: ((s: SelectedColumn) => boolean)[]): boolean => { + for (let filterFn of filterFuncs) { + if (!filterFn(s)) { + return false; + } + } + return true; +}; From 83184810bf2bc5622e196a56790ba11d341d943a Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sat, 30 Sep 2023 05:14:27 -0400 Subject: [PATCH 25/95] fix warnings/tests --- src/components/SqlEditor.test.tsx | 2 +- src/components/SqlEditor.tsx | 4 ++-- src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx | 2 +- src/data/columnFilters.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/SqlEditor.test.tsx b/src/components/SqlEditor.test.tsx index 807db28c..9f63ea5e 100644 --- a/src/components/SqlEditor.test.tsx +++ b/src/components/SqlEditor.test.tsx @@ -50,6 +50,6 @@ describe('SQL Editor', () => { expect(screen.queryByText('test')).toBeInTheDocument(); await userEvent.click(screen.getByTestId(Components.QueryEditor.CodeEditor.Expand)); - expect(onChangeValue).toHaveBeenCalledTimes(1); + expect(onChangeValue).toHaveBeenCalledTimes(2); }); }); diff --git a/src/components/SqlEditor.tsx b/src/components/SqlEditor.tsx index b7bca6de..52809888 100644 --- a/src/components/SqlEditor.tsx +++ b/src/components/SqlEditor.tsx @@ -48,16 +48,16 @@ export const SqlEditor = (props: SqlEditorProps) => { editorType: EditorType.SQL, queryType, rawSql: sql, + expand: expand.on, format: mapQueryTypeToGrafanaFormat(queryType), }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [queryType, sql]) + }, [queryType, sql, expand]) const onToggleExpand = () => { const on = !expand.on; const icon = on ? 'minus' : 'plus'; - onChange({ ...sqlQuery, expand: on }); if (!codeEditor) { return; diff --git a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx index 5cace414..9fd4ba3d 100644 --- a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -13,7 +13,7 @@ import { getColumnByHint } from 'components/queryBuilder/utils'; import { columnFilterDateTime } from 'data/columnFilters'; interface TimeSeriesQueryBuilderProps { - allColumns: ReadonlyArray; + allColumns: readonly TableColumn[]; builderOptions: QueryBuilderOptions, onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; } diff --git a/src/data/columnFilters.ts b/src/data/columnFilters.ts index 9f593ec2..268b5571 100644 --- a/src/data/columnFilters.ts +++ b/src/data/columnFilters.ts @@ -3,7 +3,7 @@ import { SelectedColumn } from "types/queryBuilder"; export const columnFilterDateTime = (s: SelectedColumn): boolean => (s.type || '').toLowerCase().startsWith('date'); export const columnFilterString = (s: SelectedColumn): boolean => (s.type || '').toLowerCase().includes('string') || (s.type || '').toLowerCase().includes('enum'); -export const columnFilterOr = (s: SelectedColumn, ...filterFuncs: ((s: SelectedColumn) => boolean)[]): boolean => { +export const columnFilterOr = (s: SelectedColumn, ...filterFuncs: ReadonlyArray<(s: SelectedColumn) => boolean>): boolean => { for (let filterFn of filterFuncs) { if (!filterFn(s)) { return false; From a90274556068a38987212f1c88b5ccdf2cd48ae0 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sat, 30 Sep 2023 05:25:58 -0400 Subject: [PATCH 26/95] fix lint errors --- src/components/queryBuilder/AggregateEditor.tsx | 2 +- src/components/queryBuilder/ColumnSelect.test.tsx | 4 ++-- src/components/queryBuilder/ColumnsEditor.test.tsx | 2 +- src/components/queryBuilder/FilterEditor.tsx | 8 ++++---- src/components/queryBuilder/GroupByEditor.test.tsx | 4 ++-- src/components/queryBuilder/GroupByEditor.tsx | 2 +- src/components/queryBuilder/OrderByEditor.test.tsx | 2 +- src/components/queryBuilder/OrderByEditor.tsx | 2 +- src/components/queryBuilder/utils.test.ts | 5 ++--- src/components/queryBuilder/views/TableQueryBuilder.tsx | 2 +- src/components/queryBuilder/views/TraceQueryBuilder.tsx | 4 ++-- src/data/CHDatasource.test.ts | 3 +-- src/hooks/useColumns.test.ts | 8 ++++---- src/hooks/useColumns.ts | 4 ++-- src/tracking.test.ts | 3 +-- 15 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/components/queryBuilder/AggregateEditor.tsx b/src/components/queryBuilder/AggregateEditor.tsx index 78669b14..269359fe 100644 --- a/src/components/queryBuilder/AggregateEditor.tsx +++ b/src/components/queryBuilder/AggregateEditor.tsx @@ -65,7 +65,7 @@ const Aggregate = (props: AggregateProps) => { }; interface AggregateEditorProps { - allColumns: ReadonlyArray; + allColumns: readonly TableColumn[]; aggregates: AggregateColumn[]; onAggregatesChange: (aggregates: AggregateColumn[]) => void; } diff --git a/src/components/queryBuilder/ColumnSelect.test.tsx b/src/components/queryBuilder/ColumnSelect.test.tsx index 1a67a0f4..71133174 100644 --- a/src/components/queryBuilder/ColumnSelect.test.tsx +++ b/src/components/queryBuilder/ColumnSelect.test.tsx @@ -21,7 +21,7 @@ describe('ColumnSelect', () => { }); it('should render with valid properties', () => { - const allColumns: ReadonlyArray = [{ name: 'foo', type: 'string', picklistValues: [] }]; + const allColumns: readonly TableColumn[] = [{ name: 'foo', type: 'string', picklistValues: [] }]; const selectedColumn: SelectedColumn = { name: 'foo' }; const result = render( { }); it('should call onColumnChange when a new column is selected', () => { - const allColumns: ReadonlyArray = [ + const allColumns: readonly TableColumn[] = [ { name: 'one', type: 'string', picklistValues: [] }, { name: 'two', type: 'string', picklistValues: [] } ]; diff --git a/src/components/queryBuilder/ColumnsEditor.test.tsx b/src/components/queryBuilder/ColumnsEditor.test.tsx index 1a7d794e..e79c62cb 100644 --- a/src/components/queryBuilder/ColumnsEditor.test.tsx +++ b/src/components/queryBuilder/ColumnsEditor.test.tsx @@ -5,7 +5,7 @@ import { TableColumn, SelectedColumn } from 'types/queryBuilder'; import { selectors } from 'selectors'; describe('ColumnsEditor', () => { - const allColumns: ReadonlyArray = [ + const allColumns: readonly TableColumn[] = [ { name: 'name', type: 'string', picklistValues: [] }, { name: 'dummy', type: 'string', picklistValues: [] }, ]; diff --git a/src/components/queryBuilder/FilterEditor.tsx b/src/components/queryBuilder/FilterEditor.tsx index 4def3147..fdec9c1d 100644 --- a/src/components/queryBuilder/FilterEditor.tsx +++ b/src/components/queryBuilder/FilterEditor.tsx @@ -45,7 +45,7 @@ export const defaultNewFilter: NullFilter = { operator: FilterOperator.IsNotNull, }; export interface PredefinedFilter { - restrictToFields?: ReadonlyArray; + restrictToFields?: readonly TableColumn[]; } const FilterValueNumberItem = (props: { value: number; onChange: (value: number) => void }) => { @@ -91,7 +91,7 @@ const FilterValueMultiStringItem = (props: { value: string[]; onChange: (value: }; export const FilterValueEditor = (props: { - allColumns: ReadonlyArray; + allColumns: readonly TableColumn[]; filter: Filter; onFilterChange: (filter: Filter) => void; }) => { @@ -171,7 +171,7 @@ export const FilterValueEditor = (props: { }; export const FilterEditor = (props: { - allColumns: ReadonlyArray; + allColumns: readonly TableColumn[]; index: number; filter: Filter & PredefinedFilter; onFilterChange: (index: number, filter: Filter) => void; @@ -362,7 +362,7 @@ export const FilterEditor = (props: { }; export const FiltersEditor = (props: { - allColumns: ReadonlyArray; + allColumns: readonly TableColumn[]; filters: Filter[]; onFiltersChange: (filters: Filter[]) => void; }) => { diff --git a/src/components/queryBuilder/GroupByEditor.test.tsx b/src/components/queryBuilder/GroupByEditor.test.tsx index df571d95..6b69bd00 100644 --- a/src/components/queryBuilder/GroupByEditor.test.tsx +++ b/src/components/queryBuilder/GroupByEditor.test.tsx @@ -10,14 +10,14 @@ describe('GroupByEditor', () => { }); it('should render with valid properties', () => { - const allColumns: ReadonlyArray = [{ name: 'a', type: 'string', picklistValues: [] }]; + const allColumns: readonly TableColumn[] = [{ name: 'a', type: 'string', picklistValues: [] }]; const groupBy: string[] = ['a', 'b']; const result = render( {}} />); expect(result.container.firstChild).not.toBeNull(); }); it('should call onGroupByChange when a new column is selected', () => { - const allColumns: ReadonlyArray = [{ name: 'a', type: 'string', picklistValues: [] }]; + const allColumns: readonly TableColumn[] = [{ name: 'a', type: 'string', picklistValues: [] }]; const groupBy: string[] = ['b']; const onGroupByChange = jest.fn(); const result = render(); diff --git a/src/components/queryBuilder/GroupByEditor.tsx b/src/components/queryBuilder/GroupByEditor.tsx index 11c34499..69a31041 100644 --- a/src/components/queryBuilder/GroupByEditor.tsx +++ b/src/components/queryBuilder/GroupByEditor.tsx @@ -7,7 +7,7 @@ import { styles } from 'styles'; import { selectors } from 'selectors'; interface GroupByEditorProps { - allColumns: ReadonlyArray; + allColumns: readonly TableColumn[]; groupBy: string[]; onGroupByChange: (groupBy: string[]) => void; } diff --git a/src/components/queryBuilder/OrderByEditor.test.tsx b/src/components/queryBuilder/OrderByEditor.test.tsx index 82615591..8301e079 100644 --- a/src/components/queryBuilder/OrderByEditor.test.tsx +++ b/src/components/queryBuilder/OrderByEditor.test.tsx @@ -121,7 +121,7 @@ describe('OrderByEditor', () => { }); describe('getOrderByOptions', () => { - const allColumms: ReadonlyArray = [ + const allColumms: readonly TableColumn[] = [ { name: 'field1', type: 'string', diff --git a/src/components/queryBuilder/OrderByEditor.tsx b/src/components/queryBuilder/OrderByEditor.tsx index 7a4ab986..cd539935 100644 --- a/src/components/queryBuilder/OrderByEditor.tsx +++ b/src/components/queryBuilder/OrderByEditor.tsx @@ -132,7 +132,7 @@ export const OrderByEditor = (props: OrderByEditorProps) => { ); }; -export const getOrderByOptions = (builder: QueryBuilderOptions, allColumns: ReadonlyArray): Array> => { +export const getOrderByOptions = (builder: QueryBuilderOptions, allColumns: readonly TableColumn[]): Array> => { let allOptions: Array> = []; if (isAggregateQuery(builder)) { diff --git a/src/components/queryBuilder/utils.test.ts b/src/components/queryBuilder/utils.test.ts index 26f31ca3..7e354fc4 100644 --- a/src/components/queryBuilder/utils.test.ts +++ b/src/components/queryBuilder/utils.test.ts @@ -1,6 +1,5 @@ -import { AggregateType, BuilderMode, FilterOperator, OrderByDirection, QueryBuilderOptions } from 'types/queryBuilder'; +import { AggregateType, BuilderMode, FilterOperator, OrderByDirection, QueryBuilderOptions, ColumnHint, QueryType } from 'types/queryBuilder'; import { getColumnByHint, getQueryOptionsFromSql, getSqlFromQueryBuilderOptions, isAggregateQuery, isDateTimeType, isDateType, isNumberType } from './utils'; -import { ColumnHint, QueryType } from 'types/queryBuilder'; describe('isDateType', () => { it('returns true for Date type', () => { @@ -483,4 +482,4 @@ describe('isAggregateQuery', () => { const builderOptions = {} as QueryBuilderOptions; expect(isAggregateQuery(builderOptions)).toEqual(false); }); -}); \ No newline at end of file +}); diff --git a/src/components/queryBuilder/views/TableQueryBuilder.tsx b/src/components/queryBuilder/views/TableQueryBuilder.tsx index 07d79337..5e324d7f 100644 --- a/src/components/queryBuilder/views/TableQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TableQueryBuilder.tsx @@ -10,7 +10,7 @@ import { AggregateEditor } from '../AggregateEditor'; import { GroupByEditor } from '../GroupByEditor'; interface TableQueryBuilderProps { - allColumns: ReadonlyArray; + allColumns: readonly TableColumn[]; builderOptions: QueryBuilderOptions, onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; } diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index 6ddc85b0..41ce67c0 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -10,7 +10,7 @@ import { SelectableValue } from '@grafana/data'; import { styles } from 'styles'; interface TraceQueryBuilderProps { - allColumns: ReadonlyArray; + allColumns: readonly TableColumn[]; builderOptions: QueryBuilderOptions, onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; } @@ -276,4 +276,4 @@ const TraceIdInput = (props: TraceIdInputProps) => { />
) -} \ No newline at end of file +} diff --git a/src/data/CHDatasource.test.ts b/src/data/CHDatasource.test.ts index 77055ea0..8968b00a 100644 --- a/src/data/CHDatasource.test.ts +++ b/src/data/CHDatasource.test.ts @@ -13,8 +13,7 @@ import { Observable, of } from 'rxjs'; import { DataSourceWithBackend } from '@grafana/runtime'; import { mockDatasource } from '__mocks__/datasource'; import { CHBuilderQuery, CHQuery, CHSqlQuery, EditorType } from 'types/sql'; -import { ColumnHint, QueryType } from 'types/queryBuilder'; -import { BuilderMode, QueryBuilderOptions } from 'types/queryBuilder'; +import { ColumnHint, QueryType, BuilderMode, QueryBuilderOptions} from 'types/queryBuilder'; import { cloneDeep } from 'lodash'; import { Datasource } from './CHDatasource'; import * as logs from './logs'; diff --git a/src/hooks/useColumns.test.ts b/src/hooks/useColumns.test.ts index fc030fcd..9efe6215 100644 --- a/src/hooks/useColumns.test.ts +++ b/src/hooks/useColumns.test.ts @@ -8,7 +8,7 @@ describe('useColumns', () => { const defaultColumnCount = 1; // the "all" column is always in the array it('should return empty array if datasource is invalid', async () => { - let result: { current: ReadonlyArray }; + let result: { current: readonly TableColumn[] }; await act(async () => { const r = renderHook(() => useColumns(undefined!, 'db', 'table')); result = r.result; @@ -20,7 +20,7 @@ describe('useColumns', () => { it('should return empty array if database string is empty', async () => { const mockDs = {} as Datasource; mockDs.fetchColumnsFull = jest.fn((db: string, table: string) => Promise.resolve([])); - let result: { current: ReadonlyArray }; + let result: { current: readonly TableColumn[] }; await act(async () => { const r = renderHook(() => useColumns(mockDs, '', 'table')); result = r.result; @@ -32,7 +32,7 @@ describe('useColumns', () => { it('should return empty array if table string is empty', async () => { const mockDs = {} as Datasource; mockDs.fetchColumnsFull = jest.fn((db: string, table: string) => Promise.resolve([])); - let result: { current: ReadonlyArray }; + let result: { current: readonly TableColumn[] }; await act(async () => { const r = renderHook(() => useColumns(mockDs, 'db', '')); result = r.result; @@ -50,7 +50,7 @@ describe('useColumns', () => { // { name: '*' } (an "all" column is added by the hook) ])); - let result: { current: ReadonlyArray }; + let result: { current: readonly TableColumn[] }; await act(async () => { const r = renderHook(() => useColumns(mockDs, 'db', 'table')); result = r.result; diff --git a/src/hooks/useColumns.ts b/src/hooks/useColumns.ts index 6b14ce3c..e979b3ae 100644 --- a/src/hooks/useColumns.ts +++ b/src/hooks/useColumns.ts @@ -4,8 +4,8 @@ import { Datasource } from 'data/CHDatasource'; const allColumn = { name: '*', label: 'ALL', type: 'string', picklistValues: [] }; -export default (datasource: Datasource, database: string, table: string): ReadonlyArray => { - const [columns, setColumns] = useState>([allColumn]); +export default (datasource: Datasource, database: string, table: string): readonly TableColumn[] => { + const [columns, setColumns] = useState([allColumn]); useEffect(() => { if (!datasource || !database || !table) { diff --git a/src/tracking.test.ts b/src/tracking.test.ts index 380c46b8..30068fa2 100644 --- a/src/tracking.test.ts +++ b/src/tracking.test.ts @@ -1,7 +1,6 @@ import { analyzeQueries } from 'tracking'; import { CHQuery, EditorType } from 'types/sql'; -import { QueryType } from 'types/queryBuilder'; -import { BuilderMode } from 'types/queryBuilder'; +import { QueryType, BuilderMode } from 'types/queryBuilder'; describe('analyzeQueries', () => { [ From 23456922e16b3055dfd2a97ac2f55396e5f72e94 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sat, 30 Sep 2023 06:00:22 -0400 Subject: [PATCH 27/95] fix default editor type --- src/data/CHDatasource.ts | 3 ++- src/types/sql.ts | 1 - src/views/CHQueryEditor.tsx | 46 ++++++++++++++++++------------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index e6ca5dac..2985be97 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -41,6 +41,7 @@ import { TIME_FIELD_ALIAS, } from './logs'; import { getSqlFromQueryBuilderOptions, getColumnByHint } from '../components/queryBuilder/utils'; +import { generateSql } from './sqlGenerator'; export class Datasource extends DataSourceWithBackend @@ -315,7 +316,7 @@ export class Datasource return { ...query, // the query is updated to trigger the URL update and propagation to the panels - rawSql: getSqlFromQueryBuilderOptions(updatedBuilder), + rawSql: generateSql(updatedBuilder), builderOptions: updatedBuilder, }; } diff --git a/src/types/sql.ts b/src/types/sql.ts index 0c3b3b90..c472dbe3 100644 --- a/src/types/sql.ts +++ b/src/types/sql.ts @@ -53,7 +53,6 @@ export const defaultCHBuilderQuery: Omit = { queryType: QueryType.Table, mode: BuilderMode.List, columns: [], - limit: 100, }, // format: Format.TABLE, // selectedFormat: Format.AUTO, diff --git a/src/views/CHQueryEditor.tsx b/src/views/CHQueryEditor.tsx index 0bf891b6..052b17a7 100644 --- a/src/views/CHQueryEditor.tsx +++ b/src/views/CHQueryEditor.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { QueryEditorProps } from '@grafana/data'; import { Datasource } from 'data/CHDatasource'; import { EditorTypeSwitcher } from 'components/queryBuilder/EditorTypeSwitcher'; @@ -18,7 +18,25 @@ export type CHQueryEditorProps = QueryEditorProps * Top level query editor component */ export const CHQueryEditor = (props: CHQueryEditorProps) => { - const { onRunQuery } = props; + const { query, onChange, onRunQuery } = props; + + useEffect(() => { + if (query.editorType) { + return; + } + + onChange({ + ...query as CHQuery, + ...defaultCHBuilderQuery, + builderOptions: { + ...defaultCHBuilderQuery.builderOptions, + }, + }); + }, [query, query.editorType, onChange]); + + if (!query.editorType) { + return null; + } return ( <> @@ -51,32 +69,14 @@ const CHEditorByType = (props: CHQueryEditorProps) => {
); } - - let newQuery: CHBuilderQuery = { ...query }; - if (query.rawSql && !query.builderOptions) { - return ( -
- -
- ); - } - - if (!query.rawSql || !query.builderOptions) { - newQuery = { - ...newQuery, - rawSql: defaultCHBuilderQuery.rawSql, - builderOptions: { - ...defaultCHBuilderQuery.builderOptions, - }, - }; - } + const builderQuery: CHBuilderQuery = { ...query }; return ( ); From 08b8959b208fc8f9217b9274fdda2416c097d635 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 25 Oct 2023 08:37:45 -0400 Subject: [PATCH 28/95] upgrade dependencies --- .config/jest.config.js | 2 +- .config/jest/utils.js | 9 +- .config/webpack/utils.ts | 61 +- package.json | 58 +- yarn.lock | 3366 ++++++++++++++++++++++++++++---------- 5 files changed, 2533 insertions(+), 963 deletions(-) diff --git a/.config/jest.config.js b/.config/jest.config.js index 027c0ffc..3cb011e0 100644 --- a/.config/jest.config.js +++ b/.config/jest.config.js @@ -25,7 +25,7 @@ module.exports = { '^.+\\.(t|j)sx?$': [ '@swc/jest', { - sourceMaps: true, + sourceMaps: 'inline', jsc: { parser: { syntax: 'typescript', diff --git a/.config/jest/utils.js b/.config/jest/utils.js index a51d626d..174f8a2e 100644 --- a/.config/jest/utils.js +++ b/.config/jest/utils.js @@ -8,10 +8,12 @@ * This utility function is useful in combination with jest `transformIgnorePatterns` config * to transform specific packages (e.g.ES modules) in a projects node_modules folder. */ -const nodeModulesToTransform = (moduleNames) => `node_modules\/(?!(${moduleNames.join('|')})\/)`; +const nodeModulesToTransform = (moduleNames) => `node_modules\/(?!.*(${moduleNames.join('|')})\/.*)`; // Array of known nested grafana package dependencies that only bundle an ESM version const grafanaESModules = [ + '.pnpm', // Support using pnpm symlinked packages + '@grafana/schema', 'd3', 'd3-color', 'd3-force', @@ -19,10 +21,11 @@ const grafanaESModules = [ 'd3-scale-chromatic', 'ol', 'react-colorful', + 'rxjs', 'uuid', ]; module.exports = { nodeModulesToTransform, - grafanaESModules -} \ No newline at end of file + grafanaESModules, +}; \ No newline at end of file diff --git a/.config/webpack/utils.ts b/.config/webpack/utils.ts index 02540a71..144ea195 100644 --- a/.config/webpack/utils.ts +++ b/.config/webpack/utils.ts @@ -1,42 +1,59 @@ -import fs from 'fs' -import path from 'path' -import util from 'util' -import glob from 'glob' -import { SOURCE_DIR } from './constants' - -const globAsync = util.promisify(glob) +import fs from 'fs'; +import process from 'process'; +import os from 'os'; +import path from 'path'; +import util from 'util'; +import { glob } from 'glob'; +import { SOURCE_DIR } from './constants'; + + +export function isWSL() { + if (process.platform !== 'linux') { + return false; + } + + if (os.release().toLowerCase().includes('microsoft')) { + return true; + } + + try { + return fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft'); + } catch { + return false; + } +} export function getPackageJson() { - return require(path.resolve(process.cwd(), 'package.json')) + return require(path.resolve(process.cwd(), 'package.json')); } export function getPluginJson() { - return require(path.resolve(process.cwd(), `${SOURCE_DIR}/plugin.json`)) + return require(path.resolve(process.cwd(), `${SOURCE_DIR}/plugin.json`)); } export function hasReadme() { - return fs.existsSync(path.resolve(process.cwd(), SOURCE_DIR, 'README.md')) + return fs.existsSync(path.resolve(process.cwd(), SOURCE_DIR, 'README.md')); } // Support bundling nested plugins by finding all plugin.json files in src directory // then checking for a sibling module.[jt]sx? file. export async function getEntries(): Promise> { - const pluginsJson = await globAsync('**/src/**/plugin.json', { absolute: true }) + const pluginsJson = await glob('**/src/**/plugin.json', { absolute: true }); const plugins = await Promise.all(pluginsJson.map((pluginJson) => { - const folder = path.dirname(pluginJson) - return globAsync(`${folder}/module.{ts,tsx,js,jsx}`, { absolute: true }) + const folder = path.dirname(pluginJson); + return glob(`${folder}/module.{ts,tsx,js,jsx}`, { absolute: true }); }) - ) + ); return plugins.reduce((result, modules) => { return modules.reduce((result, module) => { - const pluginPath = path.dirname(module) - const pluginName = path.relative(process.cwd(), pluginPath).replace(/src\/?/i, '') - const entryName = pluginName === '' ? 'module' : `${pluginName}/module` - - result[entryName] = module - return result - }, result) - }, {}) + const pluginPath = path.dirname(module); + const pluginName = path.relative(process.cwd(), pluginPath).replace(/src\/?/i, ''); + const entryName = pluginName === '' ? 'module' : `${pluginName}/module`; + + result[entryName] = module; + return result; + }, result); + }, {}); } diff --git a/package.json b/package.json index 39e59283..0ee86ff9 100644 --- a/package.json +++ b/package.json @@ -20,43 +20,43 @@ "author": "Grafana Labs", "license": "Apache-2.0", "devDependencies": { - "@babel/core": "^7.16.7", + "@babel/core": "^7.21.4", "@grafana/e2e": "9.4.3", "@grafana/e2e-selectors": "9.4.3", - "@grafana/eslint-config": "^5.1.0", + "@grafana/eslint-config": "^6.0.0", "@grafana/tsconfig": "^1.2.0-rc1", "@swc/core": "^1.2.144", - "@swc/helpers": "^0.4.12", - "@swc/jest": "^0.2.23", - "@testing-library/jest-dom": "^5.16.2", + "@swc/helpers": "^0.5.0", + "@swc/jest": "^0.2.26", + "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.5.0", + "@testing-library/user-event": "^14.5.1", "@types/glob": "^8.0.0", - "@types/jest": "^29.2.2", - "@types/lodash": "^4.14.188", - "@types/node": "^18.11.9", + "@types/jest": "^29.5.0", + "@types/lodash": "^4.14.194", + "@types/node": "^18.15.11", "@types/react-router-dom": "^5.3.3", - "@types/webpack-livereload-plugin": "^2.3.3", + "@types/webpack-livereload-plugin": "^2.3.4", "copy-webpack-plugin": "^11.0.0", "cspell": "^6.30.2", - "css-loader": "^6.7.1", - "eslint-webpack-plugin": "^3.1.1", - "fork-ts-checker-webpack-plugin": "^7.2.0", - "glob": "^8.0.3", + "css-loader": "^6.7.3", + "eslint-webpack-plugin": "^4.0.1", + "fork-ts-checker-webpack-plugin": "^8.0.0", + "glob": "^10.2.7", "identity-obj-proxy": "3.0.0", - "jest": "^29.3.1", - "jest-environment-jsdom": "^29.3.1", - "prettier": "^2.5.0", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "prettier": "^2.8.7", "replace-in-file-webpack-plugin": "^1.0.6", - "sass": "1.56.1", - "sass-loader": "13.2.0", - "style-loader": "3.3.1", + "sass": "1.63.2", + "sass-loader": "13.3.1", + "style-loader": "3.3.3", "swc-loader": "^0.2.3", - "ts-node": "^10.5.0", - "tsconfig-paths": "^4.1.0", - "typescript": "^4.4.0", - "webpack": "^5.69.1", - "webpack-cli": "^4.9.2", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^4.8.4", + "webpack": "^5.86.0", + "webpack-cli": "^5.1.4", "webpack-livereload-plugin": "^3.0.2" }, "resolutions": { @@ -65,13 +65,15 @@ "dependencies": { "@emotion/css": "^11.1.3", "@grafana/data": "9.4.3", + "@grafana/experimental": "^1.7.0", "@grafana/runtime": "9.4.3", - "@grafana/ui": "9.4.3", + "@grafana/ui": "10.1.0", "js-sql-parser": "^1.4.1", "pgsql-ast-parser": "^11.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^5.2.0", - "tslib": "^2.5.0" - } + "tslib": "^2.5.3" + }, + "packageManager": "yarn@1.22.19" } diff --git a/yarn.lock b/yarn.lock index 1f959722..eff9dffb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -115,7 +115,7 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.16.7": +"@babel/core@^7.11.6", "@babel/core@^7.12.3": version "7.21.3" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.3.tgz#cf1c877284a469da5d1ce1d1e53665253fae712e" integrity sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw== @@ -136,6 +136,27 @@ json5 "^2.2.2" semver "^6.3.0" +"@babel/core@^7.21.4": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.2.tgz#ed10df0d580fff67c5f3ee70fd22e2e4c90a9f94" + integrity sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.23.0" + "@babel/helpers" "^7.23.2" + "@babel/parser" "^7.23.0" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.2" + "@babel/types" "^7.23.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@^7.17.9", "@babel/generator@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.15.tgz#1564189c7ec94cb8f77b5e8a90c4d200d21b2339" @@ -165,6 +186,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" + integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== + dependencies: + "@babel/types" "^7.23.0" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab" @@ -287,6 +318,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + "@babel/helper-environment-visitor@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" @@ -323,6 +359,14 @@ "@babel/template" "^7.22.5" "@babel/types" "^7.22.5" +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" @@ -411,6 +455,17 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.19" +"@babel/helper-module-transforms@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e" + integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" @@ -570,6 +625,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.19.tgz#2f34ab1e445f5b95e2e6edfe50ea2449e610583a" integrity sha512-Tinq7ybnEPFFXhlYOYFiSjespWQk0dq2dRNAiMdRTOYQzEGqnnNyrTxPYHP5r6wGjlF1rFgABdDV0g8EwD6Qbg== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + "@babel/helper-validator-option@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" @@ -608,6 +668,15 @@ "@babel/traverse" "^7.22.15" "@babel/types" "^7.22.15" +"@babel/helpers@^7.23.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.2.tgz#2832549a6e37d484286e15ba36a5330483cac767" + integrity sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.2" + "@babel/types" "^7.23.0" + "@babel/highlight@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c" @@ -673,6 +742,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.13.tgz#5b2dd21cae4a2c5145f1fbd8ca103f9313d3b7e4" integrity sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg== +"@babel/parser@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -1359,6 +1433,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.19.4", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" + integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.18.10", "@babel/template@^7.18.6": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" @@ -1427,7 +1508,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.3", "@babel/traverse@^7.7.2": +"@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.3": version "7.21.3" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.3.tgz#4747c5e7903d224be71f90788b06798331896f67" integrity sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ== @@ -1443,6 +1524,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.23.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" + integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.0" + "@babel/types" "^7.23.0" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.12.13", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.13.tgz#8be1aa8f2c876da11a9cf650c0ecf656913ad611" @@ -1487,6 +1584,15 @@ "@babel/helper-validator-identifier" "^7.22.19" to-fast-properties "^2.0.0" +"@babel/types@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1497,6 +1603,11 @@ resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.1.tgz#45ff061b9ded1c6e4474b33b336ebb1b986b825a" integrity sha512-zr9Qs9KFQiEvMWdZesjcmRJlUck5NR+eKGS1uyKk+oYTWwlYrsoPEi6VmG6/TzBD1hKCGEimrhTgGS6hvn/xIQ== +"@braintree/sanitize-url@6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz#6110f918d273fe2af8ea1c4398a88774bb9fc12f" + integrity sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg== + "@colors/colors@1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" @@ -1890,6 +2001,23 @@ source-map "^0.5.7" stylis "4.1.3" +"@emotion/babel-plugin@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" + integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/serialize" "^1.1.2" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + "@emotion/cache@^11.10.5": version "11.10.5" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.5.tgz#c142da9351f94e47527ed458f7bbbbe40bb13c12" @@ -1901,6 +2029,17 @@ "@emotion/weak-memoize" "^0.3.0" stylis "4.1.3" +"@emotion/cache@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" + integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + stylis "4.2.0" + "@emotion/cache@^11.4.0": version "11.10.3" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.3.tgz#c4f67904fad10c945fea5165c3a5a0583c164b87" @@ -1923,6 +2062,17 @@ "@emotion/sheet" "^1.2.1" "@emotion/utils" "^1.2.0" +"@emotion/css@11.11.2": + version "11.11.2" + resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.11.2.tgz#e5fa081d0c6e335352e1bc2b05953b61832dca5a" + integrity sha512-VJxe1ucoMYMS7DkiMdC2T7PWNbrEI0a39YRiyDvK2qq4lXwjRbVP/z4lpG+odCsRzadlR+1ywwrTzhdm5HNdew== + dependencies: + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.2" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + "@emotion/css@^11.1.3": version "11.10.6" resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.10.6.tgz#5d226fdd8ef2a46d28e4eb09f66dc01a3bda5a04" @@ -1939,11 +2089,21 @@ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.0.tgz#c5153d50401ee3c027a57a177bc269b16d889cb7" integrity sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ== +"@emotion/hash@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" + integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== + "@emotion/memoize@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + "@emotion/react@11.10.5": version "11.10.5" resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.5.tgz#95fff612a5de1efa9c0d535384d3cfa115fe175d" @@ -1958,6 +2118,20 @@ "@emotion/weak-memoize" "^0.3.0" hoist-non-react-statics "^3.3.1" +"@emotion/react@11.11.1": + version "11.11.1" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.1.tgz#b2c36afac95b184f73b08da8c214fdf861fa4157" + integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.2" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + hoist-non-react-statics "^3.3.1" + "@emotion/react@^11.8.1": version "11.10.6" resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.6.tgz#dbe5e650ab0f3b1d2e592e6ab1e006e75fd9ac11" @@ -1983,6 +2157,17 @@ "@emotion/utils" "^1.2.0" csstype "^3.0.2" +"@emotion/serialize@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51" + integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA== + dependencies: + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/unitless" "^0.8.1" + "@emotion/utils" "^1.2.1" + csstype "^3.0.2" + "@emotion/sheet@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.0.tgz#771b1987855839e214fc1741bde43089397f7be5" @@ -1993,43 +2178,80 @@ resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.1.tgz#0767e0305230e894897cadb6c8df2c51e61a6c2c" integrity sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA== +"@emotion/sheet@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" + integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== + "@emotion/unitless@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db" integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw== +"@emotion/unitless@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + "@emotion/use-insertion-effect-with-fallbacks@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz#ffadaec35dbb7885bd54de3fa267ab2f860294df" integrity sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A== +"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" + integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== + "@emotion/utils@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561" integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw== +"@emotion/utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" + integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== + "@emotion/weak-memoize@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== -"@es-joy/jsdoccomment@~0.36.0": - version "0.36.1" - resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz#c37db40da36e4b848da5fd427a74bae3b004a30f" - integrity sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg== +"@emotion/weak-memoize@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" + integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== + +"@es-joy/jsdoccomment@~0.39.4": + version "0.39.4" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.39.4.tgz#6b8a62e9b3077027837728818d3c4389a898b392" + integrity sha512-Jvw915fjqQct445+yron7Dufix9A+m9j1fCJYlCo1FWlRvTxa3pjJelxdSTdaLWcTwRU6vbL+NYjO4YuNIS5Qg== dependencies: comment-parser "1.3.1" - esquery "^1.4.0" - jsdoc-type-pratt-parser "~3.1.0" + esquery "^1.5.0" + jsdoc-type-pratt-parser "~4.0.0" -"@eslint/eslintrc@^1.3.3": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e" - integrity sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA== +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.9.1.tgz#449dfa81a57a1d755b09aa58d826c1262e4283b4" + integrity sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA== + +"@eslint/eslintrc@^2.0.3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" + integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.4.0" + espree "^9.6.0" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -2037,6 +2259,11 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@eslint/js@8.42.0": + version "8.42.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6" + integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw== + "@floating-ui/core@^1.2.4": version "1.2.4" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.4.tgz#89e6311b021190c9e121fcf20306e76ac66e4066" @@ -2088,6 +2315,37 @@ dependencies: tslib "2.4.0" +"@grafana/data@10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@grafana/data/-/data-10.1.0.tgz#9b2568b61ab62e63ad69886203b4d83f278148ff" + integrity sha512-PzjeXSJJP14p4mSvL/+7+iDFVWQcU/T8deB5ppZG5efZS20fnWoiLL+JZOvjSMtXlPcsroJ2Rr4ygUciu7R+4g== + dependencies: + "@braintree/sanitize-url" "6.0.2" + "@grafana/schema" "10.1.0" + "@types/d3-interpolate" "^3.0.0" + "@types/string-hash" "1.1.1" + d3-interpolate "3.0.1" + date-fns "2.30.0" + dompurify "^2.4.3" + eventemitter3 "5.0.0" + fast_array_intersect "1.1.0" + history "4.10.1" + lodash "4.17.21" + marked "5.1.1" + marked-mangle "1.1.0" + moment "2.29.4" + moment-timezone "0.5.41" + ol "7.4.0" + papaparse "5.4.1" + react-use "17.4.0" + regenerator-runtime "0.13.11" + rxjs "7.8.0" + string-hash "^1.1.3" + tinycolor2 "1.6.0" + tslib "2.6.0" + uplot "1.6.24" + xss "^1.0.14" + "@grafana/data@9.4.3": version "9.4.3" resolved "https://registry.yarnpkg.com/@grafana/data/-/data-9.4.3.tgz#e3206e5348f6f90e5647b8ff31b646c0e8d8a22e" @@ -2115,6 +2373,15 @@ uplot "1.6.24" xss "1.0.14" +"@grafana/e2e-selectors@10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@grafana/e2e-selectors/-/e2e-selectors-10.1.0.tgz#421881e385abae6e355ea38e9e6e4d3451ddb0da" + integrity sha512-6c7FSVTw0V3A34GIXvbQz2/TL9r3io7rkYMPd0IodEYCJ5nfQCi/qdyuOtt3KA10HjDIl+0ctNXSaMg3pw6CXg== + dependencies: + "@grafana/tsconfig" "^1.2.0-rc1" + tslib "2.6.0" + typescript "4.8.4" + "@grafana/e2e-selectors@9.4.3": version "9.4.3" resolved "https://registry.yarnpkg.com/@grafana/e2e-selectors/-/e2e-selectors-9.4.3.tgz#55061983ba397e93502c20e550296e696e1f708a" @@ -2154,20 +2421,29 @@ uuid "9.0.0" yaml "^2.0.0" -"@grafana/eslint-config@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@grafana/eslint-config/-/eslint-config-5.1.0.tgz#020d12db231bd7448401d56179376c7119d4ffae" - integrity sha512-JukObDh3hG8JVIZH2SfF3IUfLxfiL/O9kSGpLBpVyEWOzVle79KagQArCgi0r8LlMhL1m6srViY59J+B8+BTYA== - dependencies: - "@typescript-eslint/eslint-plugin" "5.42.0" - "@typescript-eslint/parser" "5.42.0" - eslint "8.26.0" - eslint-config-prettier "8.5.0" - eslint-plugin-jsdoc "39.6.2" - eslint-plugin-react "7.31.10" +"@grafana/eslint-config@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@grafana/eslint-config/-/eslint-config-6.0.1.tgz#70f3e1990ab83591b566dec7bce214a1eb4d09c4" + integrity sha512-Tv6BUyJu2kHjrghkQJGhVCwJfNeyNbf/UqqUDW5j04+R0Y4YgWshZ4FrJibkfJc26/3UvfYSnYd+On1psbQIlA== + dependencies: + "@typescript-eslint/eslint-plugin" "5.59.9" + "@typescript-eslint/parser" "5.59.9" + eslint "8.42.0" + eslint-config-prettier "8.8.0" + eslint-plugin-jsdoc "46.2.6" + eslint-plugin-react "7.32.2" eslint-plugin-react-hooks "4.6.0" typescript "4.8.4" +"@grafana/experimental@^1.7.0": + version "1.7.4" + resolved "https://registry.yarnpkg.com/@grafana/experimental/-/experimental-1.7.4.tgz#5b5efe89abf38b1d3358251148d42b9111de539e" + integrity sha512-uYkv9HRx+cqJRktsY43ApG0+kgG4fNR8lv+bkaFvGyCg46dcTeNqokujoPnHp6lt9MEn+0Y3jKEQbvCXjcz2bA== + dependencies: + "@types/uuid" "^8.3.3" + semver "^7.5.4" + uuid "^8.3.2" + "@grafana/faro-core@^1.0.0-beta2": version "1.0.2" resolved "https://registry.yarnpkg.com/@grafana/faro-core/-/faro-core-1.0.2.tgz#ac3d635a4ac464ab577c7e475c78211acd09bf02" @@ -2177,6 +2453,16 @@ "@opentelemetry/api-metrics" "^0.33.0" "@opentelemetry/otlp-transformer" "^0.35.0" +"@grafana/faro-core@^1.1.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@grafana/faro-core/-/faro-core-1.2.1.tgz#a95fd1376a928917f855068f101d356db067a0f4" + integrity sha512-gI8CpyhAKRsMbPHom3sAa0qCgiQAXZrlv43Tv2q30PgMgNsV4iWI6UKHN/7NPJyvUFd+h0B/plukYDGZxO1kew== + dependencies: + "@opentelemetry/api" "^1.4.1" + "@opentelemetry/api-metrics" "^0.33.0" + "@opentelemetry/otlp-transformer" "^0.41.2" + murmurhash-js "^1.0.0" + "@grafana/faro-web-sdk@1.0.0-beta2": version "1.0.0-beta2" resolved "https://registry.yarnpkg.com/@grafana/faro-web-sdk/-/faro-web-sdk-1.0.0-beta2.tgz#d096a350d6366a108428a205753c797802eb480d" @@ -2186,6 +2472,15 @@ ua-parser-js "^1.0.32" web-vitals "^3.0.4" +"@grafana/faro-web-sdk@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@grafana/faro-web-sdk/-/faro-web-sdk-1.1.0.tgz#19428ee513521f1fd9d23ca021956f97aa6dde16" + integrity sha512-MJ9E1f/FaOdwvI/63PIW6ClkF3b/sCfXhubl4/ulAEwsljLRZ6rP/AyTkm2iq7h9eVehz/fHhV9ojYcLsrbFJg== + dependencies: + "@grafana/faro-core" "^1.1.0" + ua-parser-js "^1.0.32" + web-vitals "^3.1.1" + "@grafana/runtime@9.4.3": version "9.4.3" resolved "https://registry.yarnpkg.com/@grafana/runtime/-/runtime-9.4.3.tgz#f57fcfd840c217ce6f0a86a01ce40e7060a44404" @@ -2202,6 +2497,13 @@ systemjs "0.20.19" tslib "2.4.1" +"@grafana/schema@10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@grafana/schema/-/schema-10.1.0.tgz#a797b6787788fca27d80bf835e2be8c82d90a0b3" + integrity sha512-v3qYSsxJ32Dm1/XIqr+PqgFvukZYKEz38CAAK4jOx8bxPF8XGLqxwqLC2GmXcXwXoudstcNKM4oy9Z/V5HBQLg== + dependencies: + tslib "2.6.0" + "@grafana/schema@9.4.3": version "9.4.3" resolved "https://registry.yarnpkg.com/@grafana/schema/-/schema-9.4.3.tgz#a3fc3eed38e5b56f04404aec255043ab2a1809f2" @@ -2214,6 +2516,78 @@ resolved "https://registry.yarnpkg.com/@grafana/tsconfig/-/tsconfig-1.2.0-rc1.tgz#10973c978ec95b0ea637511254b5f478bce04de7" integrity sha512-+SgQeBQ1pT6D/E3/dEdADqTrlgdIGuexUZ8EU+8KxQFKUeFeU7/3z/ayI2q/wpJ/Kr6WxBBNlrST6aOKia19Ag== +"@grafana/ui@10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@grafana/ui/-/ui-10.1.0.tgz#605e32abd0dc41166629f6a872ee71bca3093cc9" + integrity sha512-nmx7H4Hcq2KcPpajmM2jpKgpzAyxznpub7JHSMDLEvKEg5zz7PgdzSpFL9Sq6pELNYMfdGUSR8lf0/sY3lSrgg== + dependencies: + "@emotion/css" "11.11.2" + "@emotion/react" "11.11.1" + "@grafana/data" "10.1.0" + "@grafana/e2e-selectors" "10.1.0" + "@grafana/faro-web-sdk" "1.1.0" + "@grafana/schema" "10.1.0" + "@leeoniya/ufuzzy" "1.0.8" + "@monaco-editor/react" "4.5.1" + "@popperjs/core" "2.11.6" + "@react-aria/button" "3.8.0" + "@react-aria/dialog" "3.5.3" + "@react-aria/focus" "3.13.0" + "@react-aria/menu" "3.10.0" + "@react-aria/overlays" "3.15.0" + "@react-aria/utils" "3.18.0" + "@react-stately/menu" "3.5.3" + ansicolor "1.1.100" + calculate-size "1.1.1" + classnames "2.3.2" + core-js "3.31.0" + d3 "7.8.5" + date-fns "2.30.0" + hoist-non-react-statics "3.3.2" + i18next "^22.0.0" + i18next-browser-languagedetector "^7.0.2" + immutable "4.3.0" + is-hotkey "0.2.0" + jquery "3.7.0" + lodash "4.17.21" + memoize-one "6.0.0" + moment "2.29.4" + monaco-editor "0.34.0" + ol "7.4.0" + prismjs "1.29.0" + rc-cascader "3.12.1" + rc-drawer "6.3.0" + rc-slider "10.2.1" + rc-time-picker "^3.7.3" + rc-tooltip "6.0.1" + react-beautiful-dnd "13.1.1" + react-calendar "4.3.0" + react-colorful "5.6.1" + react-custom-scrollbars-2 "4.5.0" + react-dropzone "14.2.3" + react-highlight-words "0.20.0" + react-hook-form "7.5.3" + react-i18next "^12.0.0" + react-inlinesvg "3.0.2" + react-loading-skeleton "3.3.1" + react-popper "2.3.0" + react-popper-tooltip "4.4.2" + react-router-dom "5.3.3" + react-select "5.7.0" + react-select-event "^5.1.0" + react-table "7.8.0" + react-transition-group "4.4.5" + react-use "17.4.0" + react-window "1.8.8" + rxjs "7.8.0" + slate "0.47.9" + slate-plain-serializer "0.7.13" + slate-react "0.22.10" + tinycolor2 "1.6.0" + tslib "2.6.0" + uplot "1.6.24" + uuid "9.0.0" + "@grafana/ui@9.4.3": version "9.4.3" resolved "https://registry.yarnpkg.com/@grafana/ui/-/ui-9.4.3.tgz#ad9a929a94686f2762eacf93b439a657abe9fe22" @@ -2284,12 +2658,12 @@ uplot "1.6.24" uuid "9.0.0" -"@humanwhocodes/config-array@^0.11.6": - version "0.11.8" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" - integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== +"@humanwhocodes/config-array@^0.11.10": + version "0.11.12" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.12.tgz#549afec9bfce5232ac6325db12765f407e70e3a0" + integrity sha512-NlGesA1usRNn6ctHCZ21M4/dKPgW9Nn1FypRdIKKgZOKzkVV4T1FlK5mBiLhHBCDmEbdQG0idrcXlbZfksJ+RA== dependencies: - "@humanwhocodes/object-schema" "^1.2.1" + "@humanwhocodes/object-schema" "^2.0.0" debug "^4.1.1" minimatch "^3.0.5" @@ -2298,10 +2672,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@humanwhocodes/object-schema@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.0.tgz#04ad39d82176c7da1591c81e78b993cffd8348d8" + integrity sha512-9S9QrXY2K0L4AGDcSgTi9vgiCcG8VcBv4Mp7/1hDPYoswIy6Z6KO5blYto82BT8M0MZNRWmCFLpCs3HlpYGGdw== "@internationalized/date@^3.0.1": version "3.0.1" @@ -2317,6 +2691,13 @@ dependencies: "@swc/helpers" "^0.4.14" +"@internationalized/date@^3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.5.0.tgz#67f1dd62355f05140cc80e324842e9bfb4553abe" + integrity sha512-nw0Q+oRkizBWMioseI8+2TeUPEyopJVz5YxoYVzR0W1v+2YytiYah7s/ot35F149q/xAg4F1gT/6eTd+tsUpFQ== + dependencies: + "@swc/helpers" "^0.5.0" + "@internationalized/message@^3.0.9": version "3.0.9" resolved "https://registry.yarnpkg.com/@internationalized/message/-/message-3.0.9.tgz#52bc20debe5296375d66ffcf56c3df5d8118a37d" @@ -2333,6 +2714,14 @@ "@swc/helpers" "^0.4.14" intl-messageformat "^10.1.0" +"@internationalized/message@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@internationalized/message/-/message-3.1.1.tgz#0f29c5a239b5dcd457b55f21dcd38d1a44a1236a" + integrity sha512-ZgHxf5HAPIaR0th+w0RUD62yF6vxitjlprSxmLJ1tam7FOekqRSDELMg4Cr/DdszG5YLsp5BG3FgHgqquQZbqw== + dependencies: + "@swc/helpers" "^0.5.0" + intl-messageformat "^10.1.0" + "@internationalized/number@^3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.1.1.tgz#160584316741de4381689ab759001603ee17b595" @@ -2347,6 +2736,13 @@ dependencies: "@swc/helpers" "^0.4.14" +"@internationalized/number@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.3.0.tgz#92233d130a0591085f93be86a9e6356cfa0e2de2" + integrity sha512-PuxgnKE5NJMOGKUcX1QROo8jq7sW7UWLrL5B6Rfe8BdWgU/be04cVvLyCeALD46vvbAv3d1mUvyHav/Q9a237g== + dependencies: + "@swc/helpers" "^0.5.0" + "@internationalized/string@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@internationalized/string/-/string-3.0.0.tgz#de563871e1b19e4d0ce3246ec18d25da1a73db73" @@ -2361,6 +2757,25 @@ dependencies: "@swc/helpers" "^0.4.14" +"@internationalized/string@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@internationalized/string/-/string-3.1.1.tgz#2ab7372d58bbb7ffd3de62fc2a311e4690186981" + integrity sha512-fvSr6YRoVPgONiVIUhgCmIAlifMVCeej/snPZVzbzRPxGpHl3o1GRe+d/qh92D8KhgOciruDUH8I5mjdfdjzfA== + dependencies: + "@swc/helpers" "^0.5.0" + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -2377,49 +2792,49 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@jest/console@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.5.0.tgz#593a6c5c0d3f75689835f1b3b4688c4f8544cb57" - integrity sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ== +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" slash "^3.0.0" -"@jest/core@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.5.0.tgz#76674b96904484e8214614d17261cc491e5f1f03" - integrity sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ== +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== dependencies: - "@jest/console" "^29.5.0" - "@jest/reporters" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^29.5.0" - jest-config "^29.5.0" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" - jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-resolve-dependencies "^29.5.0" - jest-runner "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" - jest-watcher "^29.5.0" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" micromatch "^4.0.4" - pretty-format "^29.5.0" + pretty-format "^29.7.0" slash "^3.0.0" strip-ansi "^6.0.0" @@ -2430,15 +2845,15 @@ dependencies: "@jest/types" "^27.5.1" -"@jest/environment@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65" - integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ== +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== dependencies: - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-mock "^29.5.0" + jest-mock "^29.7.0" "@jest/expect-utils@^29.5.0": version "29.5.0" @@ -2447,47 +2862,54 @@ dependencies: jest-get-type "^29.4.3" -"@jest/expect@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.5.0.tgz#80952f5316b23c483fbca4363ce822af79c38fba" - integrity sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g== +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== dependencies: - expect "^29.5.0" - jest-snapshot "^29.5.0" + jest-get-type "^29.6.3" -"@jest/fake-timers@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c" - integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg== +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== dependencies: - "@jest/types" "^29.5.0" + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" -"@jest/globals@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.5.0.tgz#6166c0bfc374c58268677539d0c181f9c1833298" - integrity sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ== +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/types" "^29.5.0" - jest-mock "^29.5.0" + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" -"@jest/reporters@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.5.0.tgz#985dfd91290cd78ddae4914ba7921bcbabe8ac9b" - integrity sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA== +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@jridgewell/trace-mapping" "^0.3.15" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" @@ -2495,13 +2917,13 @@ glob "^7.1.3" graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" + istanbul-lib-instrument "^6.0.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.5.0" - jest-util "^29.5.0" - jest-worker "^29.5.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" @@ -2514,51 +2936,58 @@ dependencies: "@sinclair/typebox" "^0.25.16" -"@jest/source-map@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.3.tgz#ff8d05cbfff875d4a791ab679b4333df47951d20" - integrity sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== dependencies: - "@jridgewell/trace-mapping" "^0.3.15" + "@jridgewell/trace-mapping" "^0.3.18" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.5.0.tgz#7c856a6ca84f45cc36926a4e9c6b57f1973f1408" - integrity sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ== +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== dependencies: - "@jest/console" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz#34d7d82d3081abd523dbddc038a3ddcb9f6d3cc4" - integrity sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ== +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== dependencies: - "@jest/test-result" "^29.5.0" + "@jest/test-result" "^29.7.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.7.0" slash "^3.0.0" -"@jest/transform@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.5.0.tgz#cf9c872d0965f0cbd32f1458aa44a2b1988b00f9" - integrity sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw== +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.5.0" - "@jridgewell/trace-mapping" "^0.3.15" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - jest-regex-util "^29.4.3" - jest-util "^29.5.0" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" @@ -2609,6 +3038,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -2636,15 +3077,20 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + "@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== +"@jridgewell/source-map@^0.3.3": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" + integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== dependencies: "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" @@ -2659,6 +3105,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== +"@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" @@ -2675,7 +3126,7 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17": version "0.3.17" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== @@ -2683,6 +3134,14 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@jridgewell/trace-mapping@^0.3.18": + version "0.3.20" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" + integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@jridgewell/trace-mapping@^0.3.9": version "0.3.15" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" @@ -2696,6 +3155,11 @@ resolved "https://registry.yarnpkg.com/@leeoniya/ufuzzy/-/ufuzzy-0.9.0.tgz#efb8f19f64ef6ff754fc49935c9ad53ab99712c1" integrity sha512-p2zWsX0GwO1x723Yhb3KLAoSwp1geQvzRPHgIoOR/0qn8Ptpsb3b01+W47iAYR/NWo0pX36XQoTU0alVRykMAg== +"@leeoniya/ufuzzy@1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@leeoniya/ufuzzy/-/ufuzzy-1.0.8.tgz#6a01b561749df84ff28637051865fdde3cbfc3a9" + integrity sha512-HQ6aJlYpWLq1f9AiApJl0aOIXlJUtuhBOYfSfv5rt3XNYkCBveojtnL6FvOVpJ2gEJ2wqgMW8xOHkLVYAbXghg== + "@mapbox/jsonlint-lines-primitives@~2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234" @@ -2737,6 +3201,13 @@ dependencies: state-local "^1.0.6" +"@monaco-editor/loader@^1.3.3": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.4.0.tgz#f08227057331ec890fa1e903912a5b711a2ad558" + integrity sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg== + dependencies: + state-local "^1.0.6" + "@monaco-editor/react@4.4.6": version "4.4.6" resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.4.6.tgz#8ae500b0edf85276d860ed702e7056c316548218" @@ -2745,6 +3216,13 @@ "@monaco-editor/loader" "^1.3.2" prop-types "^15.7.2" +"@monaco-editor/react@4.5.1": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.5.1.tgz#fbc76c692aee9a33b9ab24ae0c5f219b8f002fdb" + integrity sha512-NNDFdP+2HojtNhCkRfE6/D6ro6pBNihaOzMbGK84lNWzRu+CfBjwzGt4jmnqimLuqp5yE5viHS2vi+QOAnD5FQ== + dependencies: + "@monaco-editor/loader" "^1.3.3" + "@nodelib/fs.scandir@2.1.4": version "2.1.4" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" @@ -2787,6 +3265,13 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@opentelemetry/api-logs@0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.41.2.tgz#600c9b3d79018e7421d2ff7189f41b6d2c987d6a" + integrity sha512-JEV2RAqijAFdWeT6HddYymfnkiRu2ASxoTBr4WsnGJhOjWZkEy6vp+Sx9ozr1NaIODOa2HUyckExIqQjn6qywQ== + dependencies: + "@opentelemetry/api" "^1.0.0" + "@opentelemetry/api-metrics@^0.33.0": version "0.33.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api-metrics/-/api-metrics-0.33.0.tgz#753d355289b7811ad254d6e5b0193bd1b9f23ab0" @@ -2799,6 +3284,18 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.4.1.tgz#ff22eb2e5d476fbc2450a196e40dd243cc20c28f" integrity sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA== +"@opentelemetry/api@^1.4.1": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.6.0.tgz#de2c6823203d6f319511898bb5de7e70f5267e19" + integrity sha512-OWlrQAnWn9577PhVgqjUvMr1pg57Bc4jv0iL4w0PRuOSRvq67rvHW9Ie/dZVMvCzhSCB+UxhcY/PmCmFj33Q+g== + +"@opentelemetry/core@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.15.2.tgz#5b170bf223a2333884bbc2d29d95812cdbda7c9f" + integrity sha512-+gBv15ta96WqkHZaPpcDHiaz0utiiHZVfm2YOYSqFGrUaJpPkMoSuLBB58YFQGi6Rsb9EHos84X6X5+9JspmLw== + dependencies: + "@opentelemetry/semantic-conventions" "1.15.2" + "@opentelemetry/core@1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.9.1.tgz#e343337e1a7bf30e9a6aef3ef659b9b76379762a" @@ -2816,6 +3313,26 @@ "@opentelemetry/sdk-metrics" "1.9.1" "@opentelemetry/sdk-trace-base" "1.9.1" +"@opentelemetry/otlp-transformer@^0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.41.2.tgz#cd3a7185ef77fe9b7b4c2d2f9e001fa1d2fa6cf8" + integrity sha512-jJbPwB0tNu2v+Xi0c/v/R3YBLJKLonw1p+v3RVjT2VfzeUyzSp/tBeVdY7RZtL6dzZpA9XSmp8UEfWIFQo33yA== + dependencies: + "@opentelemetry/api-logs" "0.41.2" + "@opentelemetry/core" "1.15.2" + "@opentelemetry/resources" "1.15.2" + "@opentelemetry/sdk-logs" "0.41.2" + "@opentelemetry/sdk-metrics" "1.15.2" + "@opentelemetry/sdk-trace-base" "1.15.2" + +"@opentelemetry/resources@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.15.2.tgz#0c9e26cb65652a1402834a3c030cce6028d6dd9d" + integrity sha512-xmMRLenT9CXmm5HMbzpZ1hWhaUowQf8UB4jMjFlAxx1QzQcsD3KFNAVX/CAWzFPtllTyTplrA4JrQ7sCH3qmYw== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/semantic-conventions" "1.15.2" + "@opentelemetry/resources@1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.9.1.tgz#5ad3d80ba968a3a0e56498ce4bc82a6a01f2682f" @@ -2824,6 +3341,23 @@ "@opentelemetry/core" "1.9.1" "@opentelemetry/semantic-conventions" "1.9.1" +"@opentelemetry/sdk-logs@0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-logs/-/sdk-logs-0.41.2.tgz#c3eeb6793bdfa52351d66e2e66637e433abed672" + integrity sha512-smqKIw0tTW15waj7BAPHFomii5c3aHnSE4LQYTszGoK5P9nZs8tEAIpu15UBxi3aG31ZfsLmm4EUQkjckdlFrw== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/resources" "1.15.2" + +"@opentelemetry/sdk-metrics@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.15.2.tgz#eadd0a049de9cd860e1e0d49eea01156469c4b60" + integrity sha512-9aIlcX8GnhcsAHW/Wl8bzk4ZnWTpNlLtud+fxUfBtFATu6OZ6TrGrF4JkT9EVrnoxwtPIDtjHdEsSjOqisY/iA== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/resources" "1.15.2" + lodash.merge "^4.6.2" + "@opentelemetry/sdk-metrics@1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.9.1.tgz#babc162a81df9884c16b1e67c2dd26ab478f3080" @@ -2833,6 +3367,15 @@ "@opentelemetry/resources" "1.9.1" lodash.merge "4.6.2" +"@opentelemetry/sdk-trace-base@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.15.2.tgz#4821f94033c55a6c8bbd35ae387b715b6108517a" + integrity sha512-BEaxGZbWtvnSPchV98qqqqa96AOcb41pjgvhfzDij10tkBhIu9m0Jd6tZ1tJB5ZHfHbTffqYVYE0AOGobec/EQ== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/resources" "1.15.2" + "@opentelemetry/semantic-conventions" "1.15.2" + "@opentelemetry/sdk-trace-base@1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.9.1.tgz#c349491b432a7e0ae7316f0b48b2d454d79d2b84" @@ -2842,6 +3385,11 @@ "@opentelemetry/resources" "1.9.1" "@opentelemetry/semantic-conventions" "1.9.1" +"@opentelemetry/semantic-conventions@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.15.2.tgz#3bafb5de3e20e841dff6cb3c66f4d6e9694c4241" + integrity sha512-CjbOKwk2s+3xPIMcd5UNYQzsf+v94RczbdNix9/kQh38WiQkM90sUOi3if8eyHFgiBjBjhwXrA7W3ydiSQP9mw== + "@opentelemetry/semantic-conventions@1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.9.1.tgz#ad3367684a57879392513479e0a436cb2ac46dad" @@ -2852,6 +3400,11 @@ resolved "https://registry.yarnpkg.com/@petamoriken/float16/-/float16-3.5.11.tgz#69744fde033692c09dbb02191e0c391f8c857e30" integrity sha512-aKJaQhvWcP4XRo4eb34VygcqNsE1+Ej5687oexkK+qYWC7tejxaWRkAfE54Ze3xQGnvwXHZ5Ahx6CWq5sS4q7Q== +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@popperjs/core@2.11.6", "@popperjs/core@^2.11.5": version "2.11.6" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" @@ -2866,6 +3419,27 @@ classnames "^2.3.2" rc-util "^5.24.4" +"@rc-component/portal@^1.1.0", "@rc-component/portal@^1.1.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@rc-component/portal/-/portal-1.1.2.tgz#55db1e51d784e034442e9700536faaa6ab63fc71" + integrity sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg== + dependencies: + "@babel/runtime" "^7.18.0" + classnames "^2.3.2" + rc-util "^5.24.4" + +"@rc-component/trigger@^1.0.4", "@rc-component/trigger@^1.5.0": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@rc-component/trigger/-/trigger-1.17.1.tgz#3de00f7a8997bb4d8e608502e095d6d4e713ddc9" + integrity sha512-ocD6GlyrPMtWfSdGmfURpudj6ZQqykG/+GH9QVhziG/0EtpPqK5FUbptwXDJGBJwvKhk4Z6jhxJE7utH464SgQ== + dependencies: + "@babel/runtime" "^7.23.2" + "@rc-component/portal" "^1.1.0" + classnames "^2.3.2" + rc-motion "^2.0.0" + rc-resize-observer "^1.3.1" + rc-util "^5.38.0" + "@react-aria/button@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@react-aria/button/-/button-3.6.1.tgz#111e296df8e171e4eb227c306f087337490bc896" @@ -2879,6 +3453,19 @@ "@react-types/button" "^3.6.1" "@react-types/shared" "^3.14.1" +"@react-aria/button@3.8.0": + version "3.8.0" + resolved "https://registry.yarnpkg.com/@react-aria/button/-/button-3.8.0.tgz#24ccdee450f588d1edeaea3045b0755ae54cc2ce" + integrity sha512-QdvXTQgn+QEWOHoMbUIPXSBIN5P2r1zthRvqDJMTCzuT0I6LbNAq7RoojEbRrcn0DbTa/nZPzOOYsZXjgteRdw== + dependencies: + "@react-aria/focus" "^3.13.0" + "@react-aria/interactions" "^3.16.0" + "@react-aria/utils" "^3.18.0" + "@react-stately/toggle" "^3.6.0" + "@react-types/button" "^3.7.3" + "@react-types/shared" "^3.18.1" + "@swc/helpers" "^0.5.0" + "@react-aria/dialog@3.3.1": version "3.3.1" resolved "https://registry.yarnpkg.com/@react-aria/dialog/-/dialog-3.3.1.tgz#16e250ecc25ddd5da140a4b3dccb4af0d2bfacb8" @@ -2891,6 +3478,30 @@ "@react-types/dialog" "^3.4.3" "@react-types/shared" "^3.14.1" +"@react-aria/dialog@3.5.3": + version "3.5.3" + resolved "https://registry.yarnpkg.com/@react-aria/dialog/-/dialog-3.5.3.tgz#50c3b49906706e366cb5feae1089e6b7bf51fef9" + integrity sha512-wXpAqnt6TtR4X/5Xk5HCTBM0qyPcF2bXFQ5z2gSwl1olgoQ5znZEgMqMLbMmwb4dsWGGtAueULs6fVZk766ygA== + dependencies: + "@react-aria/focus" "^3.13.0" + "@react-aria/overlays" "^3.15.0" + "@react-aria/utils" "^3.18.0" + "@react-stately/overlays" "^3.6.0" + "@react-types/dialog" "^3.5.3" + "@react-types/shared" "^3.18.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/focus@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.13.0.tgz#0134112d52a83a53f15b5f7e7435833c6a69d913" + integrity sha512-9DW7RqgbFWiImZmkmTIJGe9LrQBqEeLYwlKY+F1FTVXerIPiCCQ3JO3ESEa4lFMmkaHoueFLUrq2jkYjRNqoTw== + dependencies: + "@react-aria/interactions" "^3.16.0" + "@react-aria/utils" "^3.18.0" + "@react-types/shared" "^3.18.1" + "@swc/helpers" "^0.5.0" + clsx "^1.1.1" + "@react-aria/focus@3.8.0", "@react-aria/focus@^3.8.0": version "3.8.0" resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.8.0.tgz#b292df7e35ed1b57af43f98df8135b00c4667d17" @@ -2913,6 +3524,17 @@ "@swc/helpers" "^0.4.14" clsx "^1.1.1" +"@react-aria/focus@^3.13.0", "@react-aria/focus@^3.14.3": + version "3.14.3" + resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.14.3.tgz#5e66dbf47e1d92aebf67d52b3b08d1631591f5b6" + integrity sha512-gvO/frZ7SxyfyHJYC+kRsUXnXct8hGHKlG1TwbkzCCXim9XIPKDgRzfNGuFfj0i8ZpR9xmsjOBUkHZny0uekFA== + dependencies: + "@react-aria/interactions" "^3.19.1" + "@react-aria/utils" "^3.21.1" + "@react-types/shared" "^3.21.0" + "@swc/helpers" "^0.5.0" + clsx "^1.1.1" + "@react-aria/i18n@^3.6.0": version "3.6.0" resolved "https://registry.yarnpkg.com/@react-aria/i18n/-/i18n-3.6.0.tgz#0caf4d2173de411839ee55c1d4591aaf3919d6dc" @@ -2941,6 +3563,20 @@ "@react-types/shared" "^3.17.0" "@swc/helpers" "^0.4.14" +"@react-aria/i18n@^3.8.0", "@react-aria/i18n@^3.8.4": + version "3.8.4" + resolved "https://registry.yarnpkg.com/@react-aria/i18n/-/i18n-3.8.4.tgz#e7ecd3edcaa66ceaf9ebb1034395e021685163af" + integrity sha512-YlTJn7YJlUxds/T5dNtme551qc118NoDQhK+IgGpzcmPQ3xSnwBAQP4Zwc7wCpAU+xEwnNcsGw+L1wJd49He/A== + dependencies: + "@internationalized/date" "^3.5.0" + "@internationalized/message" "^3.1.1" + "@internationalized/number" "^3.3.0" + "@internationalized/string" "^3.1.1" + "@react-aria/ssr" "^3.8.0" + "@react-aria/utils" "^3.21.1" + "@react-types/shared" "^3.21.0" + "@swc/helpers" "^0.5.0" + "@react-aria/interactions@^3.11.0": version "3.11.0" resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.11.0.tgz#aa6118af58ff443670152393edab97e403d6d359" @@ -2959,6 +3595,35 @@ "@react-types/shared" "^3.17.0" "@swc/helpers" "^0.4.14" +"@react-aria/interactions@^3.16.0", "@react-aria/interactions@^3.19.1": + version "3.19.1" + resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.19.1.tgz#b17b1f9dc84624d4222c7fa0a4fa6b4c14fe125a" + integrity sha512-2QFOvq/rJfMGEezmtYcGcJmfaD16kHKcSTLFrZ8aeBK6hYFddGVZJZk+dXf+G7iNaffa8rMt6uwzVe/malJPBA== + dependencies: + "@react-aria/ssr" "^3.8.0" + "@react-aria/utils" "^3.21.1" + "@react-types/shared" "^3.21.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/menu@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@react-aria/menu/-/menu-3.10.0.tgz#7f94e84c3ed2e18efa4b537e20e1e4125e9e6f51" + integrity sha512-zOOOXvx21aGSxZsXvLa3NV48hLk0jBC/zu5WZHT0Mo/wAe0+43f8p/U3AT8Gc4WnxYbIestcdLaIwgeagSoLtQ== + dependencies: + "@react-aria/focus" "^3.13.0" + "@react-aria/i18n" "^3.8.0" + "@react-aria/interactions" "^3.16.0" + "@react-aria/overlays" "^3.15.0" + "@react-aria/selection" "^3.16.0" + "@react-aria/utils" "^3.18.0" + "@react-stately/collections" "^3.9.0" + "@react-stately/menu" "^3.5.3" + "@react-stately/tree" "^3.7.0" + "@react-types/button" "^3.7.3" + "@react-types/menu" "^3.9.2" + "@react-types/shared" "^3.18.1" + "@swc/helpers" "^0.5.0" + "@react-aria/menu@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@react-aria/menu/-/menu-3.6.1.tgz#91ad540795316623e539b32163a5d6a95f09052c" @@ -2993,6 +3658,23 @@ "@react-types/overlays" "^3.6.3" "@react-types/shared" "^3.14.1" +"@react-aria/overlays@3.15.0": + version "3.15.0" + resolved "https://registry.yarnpkg.com/@react-aria/overlays/-/overlays-3.15.0.tgz#9ae71209735b9020921c02a6603bae58f25bcbc9" + integrity sha512-MeLn74GvXZfi881NSx5sSd5eTduki/PMk4vPvMNp2Xm+9nGHm0FbGu2GMIGgarYy5JC7l/bOO7H01YrS4AozPg== + dependencies: + "@react-aria/focus" "^3.13.0" + "@react-aria/i18n" "^3.8.0" + "@react-aria/interactions" "^3.16.0" + "@react-aria/ssr" "^3.7.0" + "@react-aria/utils" "^3.18.0" + "@react-aria/visually-hidden" "^3.8.2" + "@react-stately/overlays" "^3.6.0" + "@react-types/button" "^3.7.3" + "@react-types/overlays" "^3.8.0" + "@react-types/shared" "^3.18.1" + "@swc/helpers" "^0.5.0" + "@react-aria/overlays@^3.10.1": version "3.13.0" resolved "https://registry.yarnpkg.com/@react-aria/overlays/-/overlays-3.13.0.tgz#f5a8cdce4754b4fd487baf9f356a5a3cdd731c12" @@ -3010,6 +3692,23 @@ "@react-types/shared" "^3.17.0" "@swc/helpers" "^0.4.14" +"@react-aria/overlays@^3.15.0": + version "3.18.1" + resolved "https://registry.yarnpkg.com/@react-aria/overlays/-/overlays-3.18.1.tgz#b53093b2e1004feff155c81730e0101179cd6c47" + integrity sha512-C74eZbTp3OA/gXy9/+4iPrZiz7g27Zy6Q1+plbg5QTLpsFLBt2Ypy9jTTANNRZfW7a5NW/Bnw9WIRjCdtTBRXw== + dependencies: + "@react-aria/focus" "^3.14.3" + "@react-aria/i18n" "^3.8.4" + "@react-aria/interactions" "^3.19.1" + "@react-aria/ssr" "^3.8.0" + "@react-aria/utils" "^3.21.1" + "@react-aria/visually-hidden" "^3.8.6" + "@react-stately/overlays" "^3.6.3" + "@react-types/button" "^3.9.0" + "@react-types/overlays" "^3.8.3" + "@react-types/shared" "^3.21.0" + "@swc/helpers" "^0.5.0" + "@react-aria/selection@^3.10.1": version "3.13.1" resolved "https://registry.yarnpkg.com/@react-aria/selection/-/selection-3.13.1.tgz#770c5ff2ace1db397c09576deb39c576c0fede30" @@ -3024,6 +3723,20 @@ "@react-types/shared" "^3.17.0" "@swc/helpers" "^0.4.14" +"@react-aria/selection@^3.16.0": + version "3.17.1" + resolved "https://registry.yarnpkg.com/@react-aria/selection/-/selection-3.17.1.tgz#12df277b8806fd26093e16f6a2734bd1e6fbb3e2" + integrity sha512-g5gkSc/M+zJiVgWbUpKN095ea0D4fxdluH9ZcXxN4AAvcrVfEJyAnMmWOIKRebN8xR0KPfNRnKB7E6jld2tbuQ== + dependencies: + "@react-aria/focus" "^3.14.3" + "@react-aria/i18n" "^3.8.4" + "@react-aria/interactions" "^3.19.1" + "@react-aria/utils" "^3.21.1" + "@react-stately/collections" "^3.10.2" + "@react-stately/selection" "^3.14.0" + "@react-types/shared" "^3.21.0" + "@swc/helpers" "^0.5.0" + "@react-aria/ssr@^3.2.0", "@react-aria/ssr@^3.5.0": version "3.5.0" resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.5.0.tgz#40c1270a75868185f72a88cafe37bd1392f690cb" @@ -3038,6 +3751,13 @@ dependencies: "@babel/runtime" "^7.6.2" +"@react-aria/ssr@^3.7.0", "@react-aria/ssr@^3.8.0": + version "3.8.0" + resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.8.0.tgz#e7f467ac42f72504682724304ce221f785d70d49" + integrity sha512-Y54xs483rglN5DxbwfCPHxnkvZ+gZ0LbSYmR72LyWPGft8hN/lrl1VRS1EW2SMjnkEWlj+Km2mwvA3kEHDUA0A== + dependencies: + "@swc/helpers" "^0.5.0" + "@react-aria/utils@3.13.1": version "3.13.1" resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.13.1.tgz#45557fdc7ae9de057a83014013bf09e54d074c96" @@ -3049,6 +3769,17 @@ "@react-types/shared" "^3.13.1" clsx "^1.1.1" +"@react-aria/utils@3.18.0": + version "3.18.0" + resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.18.0.tgz#50e555ac049f47bff25bc2cef1078352e853d229" + integrity sha512-eLs0ExzXx/D3P9qe6ophJ87ZFcI1oRTyRa51M59pCad7grrpk0gWcYrBjMwcR457YWOQQWCeLuq8QJl2QxCW6Q== + dependencies: + "@react-aria/ssr" "^3.7.0" + "@react-stately/utils" "^3.7.0" + "@react-types/shared" "^3.18.1" + "@swc/helpers" "^0.5.0" + clsx "^1.1.1" + "@react-aria/utils@^3.13.3": version "3.13.3" resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.13.3.tgz#1b27912e4630f0db6a7b39eb1013f6c4f710075c" @@ -3071,6 +3802,17 @@ "@swc/helpers" "^0.4.14" clsx "^1.1.1" +"@react-aria/utils@^3.18.0", "@react-aria/utils@^3.21.1": + version "3.21.1" + resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.21.1.tgz#35f5d545757ea38f05a0d2f5492f13217ebb03ce" + integrity sha512-tySfyWHXOhd/b6JSrSOl7krngEXN3N6pi1hCAXObRu3+MZlaZOMDf/j18aoteaIF2Jpv8HMWUJUJtQKGmBJGRA== + dependencies: + "@react-aria/ssr" "^3.8.0" + "@react-stately/utils" "^3.8.0" + "@react-types/shared" "^3.21.0" + "@swc/helpers" "^0.5.0" + clsx "^1.1.1" + "@react-aria/visually-hidden@^3.4.1": version "3.4.1" resolved "https://registry.yarnpkg.com/@react-aria/visually-hidden/-/visually-hidden-3.4.1.tgz#cd87eece49eddc89e93b9616741d6d5a6e738785" @@ -3093,6 +3835,25 @@ "@swc/helpers" "^0.4.14" clsx "^1.1.1" +"@react-aria/visually-hidden@^3.8.2", "@react-aria/visually-hidden@^3.8.6": + version "3.8.6" + resolved "https://registry.yarnpkg.com/@react-aria/visually-hidden/-/visually-hidden-3.8.6.tgz#9b149851ac41e9c72c7819f8d4ad47ddfb45b863" + integrity sha512-6DmS/JLbK9KgU/ClK1WjwOyvpn8HtwYn+uisMLdP7HlCm692peYOkXDR1jqYbHL4GlyLCD0JLI+/xGdVh5aR/w== + dependencies: + "@react-aria/interactions" "^3.19.1" + "@react-aria/utils" "^3.21.1" + "@react-types/shared" "^3.21.0" + "@swc/helpers" "^0.5.0" + clsx "^1.1.1" + +"@react-stately/collections@^3.10.2", "@react-stately/collections@^3.9.0": + version "3.10.2" + resolved "https://registry.yarnpkg.com/@react-stately/collections/-/collections-3.10.2.tgz#c739d9d596ecb744be15fde6f064ad85dd6145db" + integrity sha512-h+LzCa1gWhVRWVH8uR+ZxsKmFSx7kW3RIlcjWjhfyc59BzXCuojsOJKTTAyPVFP/3kOdJeltw8g/reV1Cw/x6Q== + dependencies: + "@react-types/shared" "^3.21.0" + "@swc/helpers" "^0.5.0" + "@react-stately/collections@^3.4.3": version "3.4.3" resolved "https://registry.yarnpkg.com/@react-stately/collections/-/collections-3.4.3.tgz#aaff67e697006a7c38dfb639180b79df4b202b46" @@ -3120,6 +3881,17 @@ "@react-types/menu" "^3.7.1" "@react-types/shared" "^3.14.1" +"@react-stately/menu@3.5.3": + version "3.5.3" + resolved "https://registry.yarnpkg.com/@react-stately/menu/-/menu-3.5.3.tgz#c25fc231502cae639f5b557a9e1d8016a7e474cc" + integrity sha512-RFgwVD/4BgTtJkexi1WaHpAEkQWZPvpyri0LQUgXWVqBf9PpjB8wigF3XBLMDNkL+YXE0QtzQZBNS1nJECf7rg== + dependencies: + "@react-stately/overlays" "^3.6.0" + "@react-stately/utils" "^3.7.0" + "@react-types/menu" "^3.9.2" + "@react-types/shared" "^3.18.1" + "@swc/helpers" "^0.5.0" + "@react-stately/menu@^3.4.1": version "3.5.0" resolved "https://registry.yarnpkg.com/@react-stately/menu/-/menu-3.5.0.tgz#04fda461dc3971bc84bf49436625b3e044bca3bc" @@ -3131,6 +3903,17 @@ "@react-types/shared" "^3.17.0" "@swc/helpers" "^0.4.14" +"@react-stately/menu@^3.5.3": + version "3.5.6" + resolved "https://registry.yarnpkg.com/@react-stately/menu/-/menu-3.5.6.tgz#21861b7cfba579d69272509aef8197d3fad7463a" + integrity sha512-Cm82SVda1qP71Fcz8ohIn3JYKmKCuSUIFr1WsEo/YwDPkX0x9+ev6rmphHTsxDdkCLcYHSTQL6e2KL0wAg50zA== + dependencies: + "@react-stately/overlays" "^3.6.3" + "@react-stately/utils" "^3.8.0" + "@react-types/menu" "^3.9.5" + "@react-types/shared" "^3.21.0" + "@swc/helpers" "^0.5.0" + "@react-stately/overlays@^3.4.1": version "3.4.1" resolved "https://registry.yarnpkg.com/@react-stately/overlays/-/overlays-3.4.1.tgz#e6b095c7dae96b2c969ed7e029ab5d9f74149051" @@ -3149,6 +3932,15 @@ "@react-types/overlays" "^3.7.0" "@swc/helpers" "^0.4.14" +"@react-stately/overlays@^3.6.0", "@react-stately/overlays@^3.6.3": + version "3.6.3" + resolved "https://registry.yarnpkg.com/@react-stately/overlays/-/overlays-3.6.3.tgz#cdfe5edb1ed6ad84fc1022af931586489cb23552" + integrity sha512-K3eIiYAdAGTepYqNf2pVb+lPqLoVudXwmxPhyOSZXzjgpynD6tR3E9QfWQtkMazBuU73PnNX7zkH4l87r2AmTg== + dependencies: + "@react-stately/utils" "^3.8.0" + "@react-types/overlays" "^3.8.3" + "@swc/helpers" "^0.5.0" + "@react-stately/selection@^3.12.0": version "3.12.0" resolved "https://registry.yarnpkg.com/@react-stately/selection/-/selection-3.12.0.tgz#feb5ad753ea93870566f2c0b07f0387b690bd860" @@ -3159,6 +3951,16 @@ "@react-types/shared" "^3.17.0" "@swc/helpers" "^0.4.14" +"@react-stately/selection@^3.14.0": + version "3.14.0" + resolved "https://registry.yarnpkg.com/@react-stately/selection/-/selection-3.14.0.tgz#26a574bf2e35657db1988974df8bd2747b09f5c6" + integrity sha512-E5rNH+gVGDJQDSnPO30ynu6jZ0Z0++VPUbM5Bu3P/bZ3+TgoTtDDvlONba3fspgSBDfdnHpsuG9eqYnDtEAyYA== + dependencies: + "@react-stately/collections" "^3.10.2" + "@react-stately/utils" "^3.8.0" + "@react-types/shared" "^3.21.0" + "@swc/helpers" "^0.5.0" + "@react-stately/toggle@^3.4.1": version "3.5.0" resolved "https://registry.yarnpkg.com/@react-stately/toggle/-/toggle-3.5.0.tgz#fee5a29d7699e43867c52981834af5393f47c1c4" @@ -3169,6 +3971,16 @@ "@react-types/shared" "^3.17.0" "@swc/helpers" "^0.4.14" +"@react-stately/toggle@^3.6.0": + version "3.6.3" + resolved "https://registry.yarnpkg.com/@react-stately/toggle/-/toggle-3.6.3.tgz#4de25fd458890e37f6c363d058b018e5f11a9882" + integrity sha512-4kIMTjRjtaapFk4NVmBoFDUYfkmyqDaYAmHpRyEIHTDpBYn0xpxZL/MHv9WuLYa4MjJLRp0MeicuWiZ4ai7f6Q== + dependencies: + "@react-stately/utils" "^3.8.0" + "@react-types/checkbox" "^3.5.2" + "@react-types/shared" "^3.21.0" + "@swc/helpers" "^0.5.0" + "@react-stately/tree@^3.3.3": version "3.5.0" resolved "https://registry.yarnpkg.com/@react-stately/tree/-/tree-3.5.0.tgz#1d0dffd93c17b004953c27817be1ae2da9f96e5b" @@ -3180,6 +3992,17 @@ "@react-types/shared" "^3.17.0" "@swc/helpers" "^0.4.14" +"@react-stately/tree@^3.7.0": + version "3.7.3" + resolved "https://registry.yarnpkg.com/@react-stately/tree/-/tree-3.7.3.tgz#d0b3da5db553e64e8f3def5bae45f765f62a3fd8" + integrity sha512-wB/68qetgCYTe7OMqbTFmtWRrEqVdIH2VlACPCsMlECr3lW9TrrbrOwlHIJfLhkxWvY3kSCoKcOJ5KTiJC9LGA== + dependencies: + "@react-stately/collections" "^3.10.2" + "@react-stately/selection" "^3.14.0" + "@react-stately/utils" "^3.8.0" + "@react-types/shared" "^3.21.0" + "@swc/helpers" "^0.5.0" + "@react-stately/utils@^3.5.0", "@react-stately/utils@^3.6.0": version "3.6.0" resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.6.0.tgz#f273e7fcb348254347d2e88c8f0c45571060c207" @@ -3194,6 +4017,13 @@ dependencies: "@babel/runtime" "^7.6.2" +"@react-stately/utils@^3.7.0", "@react-stately/utils@^3.8.0": + version "3.8.0" + resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.8.0.tgz#88a45742c58bde804f6cbecb20ea3833915cfdf0" + integrity sha512-wCIoFDbt/uwNkWIBF+xV+21k8Z8Sj5qGO3uptTcVmjYcZngOaGGyB4NkiuZhmhG70Pkv+yVrRwoC1+4oav9cCg== + dependencies: + "@swc/helpers" "^0.5.0" + "@react-types/button@^3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@react-types/button/-/button-3.6.1.tgz#0bc75fe4129966673cf239df7a7aea83b6c68585" @@ -3208,6 +4038,13 @@ dependencies: "@react-types/shared" "^3.17.0" +"@react-types/button@^3.7.3", "@react-types/button@^3.9.0": + version "3.9.0" + resolved "https://registry.yarnpkg.com/@react-types/button/-/button-3.9.0.tgz#66df80cafaa98aaa34c331e927d21fdf4a0bdc4a" + integrity sha512-YhbchUDB7yL88ZFA0Zqod6qOMdzCLD5yVRmhWymk0yNLvB7EB1XX4c5sRANalfZSFP0RpCTlkjB05Hzp4+xOYg== + dependencies: + "@react-types/shared" "^3.21.0" + "@react-types/checkbox@^3.4.2": version "3.4.2" resolved "https://registry.yarnpkg.com/@react-types/checkbox/-/checkbox-3.4.2.tgz#6089e9ef2d023415a5f871e312f30bae54143ba5" @@ -3215,6 +4052,13 @@ dependencies: "@react-types/shared" "^3.17.0" +"@react-types/checkbox@^3.5.2": + version "3.5.2" + resolved "https://registry.yarnpkg.com/@react-types/checkbox/-/checkbox-3.5.2.tgz#f463befdd37bc2c9e5c6febd62e53131e8983fa4" + integrity sha512-iRQrbY8vRRya3bt3i7sHAifhP/ozfkly1/TItkRK5MNPRNPRDKns55D8ZFkRMj4NSyKQpjVt1zzlBXrnSOxWdQ== + dependencies: + "@react-types/shared" "^3.21.0" + "@react-types/dialog@^3.4.3": version "3.5.0" resolved "https://registry.yarnpkg.com/@react-types/dialog/-/dialog-3.5.0.tgz#51d942ba377ac1483c90db69e84bfdc3d5f65129" @@ -3223,6 +4067,14 @@ "@react-types/overlays" "^3.7.0" "@react-types/shared" "^3.17.0" +"@react-types/dialog@^3.5.3": + version "3.5.6" + resolved "https://registry.yarnpkg.com/@react-types/dialog/-/dialog-3.5.6.tgz#e874f0896d595e5a7f5924165b0db78e5f62fe9d" + integrity sha512-lwwaAgoi4xe4eEJxBns+cBIRstIPTKWWddMkp51r7Teeh2uKs1Wki7N+Acb9CfT6JQTQDqtVJm6K76rcqNBVwg== + dependencies: + "@react-types/overlays" "^3.8.3" + "@react-types/shared" "^3.21.0" + "@react-types/menu@^3.7.1": version "3.7.1" resolved "https://registry.yarnpkg.com/@react-types/menu/-/menu-3.7.1.tgz#79955fc63f3bb7c867594bcbead5dd37dc47848d" @@ -3239,6 +4091,14 @@ "@react-types/overlays" "^3.7.0" "@react-types/shared" "^3.17.0" +"@react-types/menu@^3.9.2", "@react-types/menu@^3.9.5": + version "3.9.5" + resolved "https://registry.yarnpkg.com/@react-types/menu/-/menu-3.9.5.tgz#9f67aebda9f491f0e94e2de7a15898c6cabf0772" + integrity sha512-KB5lJM0p9PxwpVlHV9sRdpjh+sqINeHrJgGizy/cQI9bj26nupiEgamSD14dULNI6BFT9DkgKCsobBtE04DDKQ== + dependencies: + "@react-types/overlays" "^3.8.3" + "@react-types/shared" "^3.21.0" + "@react-types/overlays@^3.6.3": version "3.6.3" resolved "https://registry.yarnpkg.com/@react-types/overlays/-/overlays-3.6.3.tgz#ba2204dd4be1948e8d2ab38995eb51d81dfd498f" @@ -3253,6 +4113,13 @@ dependencies: "@react-types/shared" "^3.17.0" +"@react-types/overlays@^3.8.0", "@react-types/overlays@^3.8.3": + version "3.8.3" + resolved "https://registry.yarnpkg.com/@react-types/overlays/-/overlays-3.8.3.tgz#47132f08ae3a115273036d98b9441a51d4a4ab09" + integrity sha512-TrCG2I2+V+TD0PGi3CqfnyU5jEzcelSGgYJQvVxsl5Vv3ri7naBLIsOjF9x66tPxhINLCPUtOze/WYRAexp8aw== + dependencies: + "@react-types/shared" "^3.21.0" + "@react-types/shared@^3.13.1", "@react-types/shared@^3.17.0": version "3.17.0" resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.17.0.tgz#b7c5e318664aadb315d305a27dd2a209d1837d95" @@ -3263,6 +4130,11 @@ resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.14.1.tgz#8fe25f729426e8043054e442eb5392364200e028" integrity sha512-yPPgVRWWanXqbdxFTgJmVwx0JlcnEK3dqkKDIbVk6mxAHvEESI9+oDnHvO8IMHqF+GbrTCzVtAs0zwhYI/uHJA== +"@react-types/shared@^3.18.1", "@react-types/shared@^3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.21.0.tgz#1af41fdf7dfbdbd33bbc1210617c43ed0d4ef20c" + integrity sha512-wJA2cUF8dP4LkuNUt9Vh2kkfiQb2NLnV2pPXxVnKJZ7d4x2/7VPccN+LYPnH8m0X3+rt50cxWuPKQmjxSsCFOg== + "@sentry/browser@6.19.7": version "6.19.7" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.19.7.tgz#a40b6b72d911b5f1ed70ed3b4e7d4d4e625c0b5f" @@ -3320,6 +4192,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@sinonjs/commons@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" @@ -3400,17 +4277,24 @@ "@swc/core-win32-ia32-msvc" "1.3.41" "@swc/core-win32-x64-msvc" "1.3.41" -"@swc/helpers@^0.4.12", "@swc/helpers@^0.4.14": +"@swc/helpers@^0.4.14": version "0.4.14" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.14.tgz#1352ac6d95e3617ccb7c1498ff019654f1e12a74" integrity sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw== dependencies: tslib "^2.4.0" -"@swc/jest@^0.2.23": - version "0.2.24" - resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.24.tgz#35d9377ede049613cd5fdd6c24af2b8dcf622875" - integrity sha512-fwgxQbM1wXzyKzl1+IW0aGrRvAA8k0Y3NxFhKigbPjOJ4mCKnWEcNX9HQS3gshflcxq8YKhadabGUVfdwjCr6Q== +"@swc/helpers@^0.5.0": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.3.tgz#98c6da1e196f5f08f977658b80d6bd941b5f294f" + integrity sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A== + dependencies: + tslib "^2.4.0" + +"@swc/jest@^0.2.26": + version "0.2.29" + resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.29.tgz#b27d647ec430c909f9bb567d1df2a47eaa3841f4" + integrity sha512-8reh5RvHBsSikDC3WGCd5ZTd2BXKkyOdK7QwynrCH58jk2cQFhhHhFBg/jvnWZehUQe/EoOImLENc9/DwbBFow== dependencies: "@jest/create-cache-key-function" "^27.4.2" jsonc-parser "^3.2.0" @@ -3443,10 +4327,10 @@ lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/jest-dom@^5.16.2": - version "5.16.5" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" - integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== +"@testing-library/jest-dom@^5.16.5": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz#5e97c8f9a15ccf4656da00fecab505728de81e0c" + integrity sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg== dependencies: "@adobe/css-tools" "^4.0.1" "@babel/runtime" "^7.9.2" @@ -3467,10 +4351,10 @@ "@testing-library/dom" "^9.0.0" "@types/react-dom" "^18.0.0" -"@testing-library/user-event@^14.5.0": - version "14.5.0" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.0.tgz#4036add379525b635a64bce4d727820d4ba516a7" - integrity sha512-nQRCteEZvULJJrlcGQuNhwGekz25TOUILA+sTWI9PB/vNKKivS+7K7XRTwoikw/2fmJPaM4pPKy+hLWEGg9+JA== +"@testing-library/user-event@^14.5.1": + version "14.5.1" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.1.tgz#27337d72046d5236b32fd977edee3f74c71d332f" + integrity sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg== "@tootallnate/once@2": version "2.0.0" @@ -3568,10 +4452,10 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/eslint@^7.29.0 || ^8.4.1": - version "8.21.3" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.21.3.tgz#5794b3911f0f19e34e3a272c49cbdf48d6f543f2" - integrity sha512-fa7GkppZVEByMWGbTtE5MbmXWJTVbrjjaS8K6uQj+XtuuUv1fsuPAxhygfqLmsb/Ufb3CV8deFCpiMfAgi00Sw== +"@types/eslint@^8.37.0": + version "8.44.6" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.6.tgz#60e564551966dd255f4c01c459f0b4fb87068603" + integrity sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -3581,10 +4465,10 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== -"@types/estree@^0.0.51": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/estree@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.3.tgz#2be19e759a3dd18c79f9f436bd7363556c1a73dd" + integrity sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ== "@types/glob@^8.0.0": version "8.1.0" @@ -3641,10 +4525,10 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/jest@^29.2.2": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.0.tgz#337b90bbcfe42158f39c2fb5619ad044bbb518ac" - integrity sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg== +"@types/jest@^29.5.0": + version "29.5.6" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.6.tgz#f4cf7ef1b5b0bfc1aa744e41b24d9cc52533130b" + integrity sha512-/t9NnzkOpXb4Nfvg17ieHE6EeSjDS2SGSpNYfoLbUAeL/EOueU/RSdOWFpfQTXBEM7BguYW1XQ0EbM+6RlIh6w== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -3673,10 +4557,17 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/lodash@^4.14.188": - version "4.14.191" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa" - integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ== +"@types/lodash.memoize@^4.1.7": + version "4.1.8" + resolved "https://registry.yarnpkg.com/@types/lodash.memoize/-/lodash.memoize-4.1.8.tgz#a5bb773a4644de4ff10640e7a7f7f6152d5e5c5c" + integrity sha512-mf2QpcedTC4qXJxqgbmCuST3a/SNTuqz2kMtojazqeLhjXaLXgoSwAgVbAz6FINw90Ahg0y1m69pm+rhta3c8Q== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*", "@types/lodash@^4.14.194": + version "4.14.200" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.200.tgz#435b6035c7eba9cdf1e039af8212c9e9281e7149" + integrity sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q== "@types/minimatch@^5.1.2": version "5.1.2" @@ -3693,21 +4584,16 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.60.tgz#986a33f3d15717d076a68a59ac8656c73e6b4ef5" integrity sha512-F2dfYDznasZ6XfuWvTmQcrElTHfxCdC+F23WCcuAJaIrMIhhBUSARJQdy0lUY+MPDNLqGvTo8/IuiF+QX64IHQ== -"@types/node@^18.11.9": - version "18.15.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.5.tgz#3af577099a99c61479149b716183e70b5239324a" - integrity sha512-Ark2WDjjZO7GmvsyFFf81MXuGTA/d6oP38anyxWOL6EREyBKAxKoFHwBhaZxCfLRLpO8JgVXwqOwSwa7jRcjew== +"@types/node@^18.15.11": + version "18.18.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.6.tgz#26da694f75cdb057750f49d099da5e3f3824cb3e" + integrity sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w== "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/prettier@^2.1.5": - version "2.7.2" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" - integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== - "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -3787,6 +4673,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/string-hash@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/string-hash/-/string-hash-1.1.1.tgz#4c336e61d1e13ce2d3efaaa5910005fd080e106b" + integrity sha512-ijt3zdHi2DmZxQpQTmozXszzDo78V4R3EdvX0jFMfnMH2ZzQSmCbaWOMPGXFUYSzSIdStv78HDjg32m5dxc+tA== + "@types/tapable@^1": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" @@ -3811,10 +4702,15 @@ dependencies: source-map "^0.6.1" -"@types/webpack-livereload-plugin@^2.3.3": - version "2.3.3" - resolved "https://registry.yarnpkg.com/@types/webpack-livereload-plugin/-/webpack-livereload-plugin-2.3.3.tgz#96f34133f1e1515571233a3e5099d863dc8723e7" - integrity sha512-R8P2HG2mAHY3Qptspt0il8zYVNqiEeOmMe2cGFcEjH7qnJZ4uC7hujLwfAm6jrIO7I5uEs6CxfpKSXM9ULAggw== +"@types/uuid@^8.3.3": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + +"@types/webpack-livereload-plugin@^2.3.4": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@types/webpack-livereload-plugin/-/webpack-livereload-plugin-2.3.5.tgz#978536664041a39218951043ffb2863e4a30d3cb" + integrity sha512-byidvlLtT1aQRiC+FlbQtNzejkMRF9fEDU4CB7+1dWd/Z64T2nG9h7T1jEkPt+YzuR42pN7kwNYxRpcgqkOFEw== dependencies: "@types/webpack" "^4" @@ -3872,232 +4768,236 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.0.tgz#36a8c0c379870127059889a9cc7e05c260d2aaa5" - integrity sha512-5TJh2AgL6+wpL8H/GTSjNb4WrjKoR2rqvFxR/DDTqYNk6uXn8BJMEcncLSpMbf/XV1aS0jAjYwn98uvVCiAywQ== +"@typescript-eslint/eslint-plugin@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.9.tgz#2604cfaf2b306e120044f901e20c8ed926debf15" + integrity sha512-4uQIBq1ffXd2YvF7MAvehWKW3zVv/w+mSfRAu+8cKbfj3nwzyqJLNcZJpQ/WZ1HLbJDiowwmQ6NO+63nCA+fqA== dependencies: - "@typescript-eslint/scope-manager" "5.42.0" - "@typescript-eslint/type-utils" "5.42.0" - "@typescript-eslint/utils" "5.42.0" + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.59.9" + "@typescript-eslint/type-utils" "5.59.9" + "@typescript-eslint/utils" "5.59.9" debug "^4.3.4" + grapheme-splitter "^1.0.4" ignore "^5.2.0" natural-compare-lite "^1.4.0" - regexpp "^3.2.0" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.42.0.tgz#be0ffbe279e1320e3d15e2ef0ad19262f59e9240" - integrity sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA== +"@typescript-eslint/parser@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.9.tgz#a85c47ccdd7e285697463da15200f9a8561dd5fa" + integrity sha512-FsPkRvBtcLQ/eVK1ivDiNYBjn3TGJdXy2fhXX+rc7czWl4ARwnpArwbihSOHI2Peg9WbtGHrbThfBUkZZGTtvQ== dependencies: - "@typescript-eslint/scope-manager" "5.42.0" - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/typescript-estree" "5.42.0" + "@typescript-eslint/scope-manager" "5.59.9" + "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/typescript-estree" "5.59.9" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.42.0.tgz#e1f2bb26d3b2a508421ee2e3ceea5396b192f5ef" - integrity sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow== +"@typescript-eslint/scope-manager@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.9.tgz#eadce1f2733389cdb58c49770192c0f95470d2f4" + integrity sha512-8RA+E+w78z1+2dzvK/tGZ2cpGigBZ58VMEHDZtpE1v+LLjzrYGc8mMaTONSxKyEkz3IuXFM0IqYiGHlCsmlZxQ== dependencies: - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/visitor-keys" "5.42.0" + "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/visitor-keys" "5.59.9" -"@typescript-eslint/type-utils@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.42.0.tgz#4206d7192d4fe903ddf99d09b41d4ac31b0b7dca" - integrity sha512-HW14TXC45dFVZxnVW8rnUGnvYyRC0E/vxXShFCthcC9VhVTmjqOmtqj6H5rm9Zxv+ORxKA/1aLGD7vmlLsdlOg== +"@typescript-eslint/type-utils@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.9.tgz#53bfaae2e901e6ac637ab0536d1754dfef4dafc2" + integrity sha512-ksEsT0/mEHg9e3qZu98AlSrONAQtrSTljL3ow9CGej8eRo7pe+yaC/mvTjptp23Xo/xIf2mLZKC6KPv4Sji26Q== dependencies: - "@typescript-eslint/typescript-estree" "5.42.0" - "@typescript-eslint/utils" "5.42.0" + "@typescript-eslint/typescript-estree" "5.59.9" + "@typescript-eslint/utils" "5.59.9" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.42.0.tgz#5aeff9b5eced48f27d5b8139339bf1ef805bad7a" - integrity sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw== +"@typescript-eslint/types@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.9.tgz#3b4e7ae63718ce1b966e0ae620adc4099a6dcc52" + integrity sha512-uW8H5NRgTVneSVTfiCVffBb8AbwWSKg7qcA4Ot3JI3MPCJGsB4Db4BhvAODIIYE5mNj7Q+VJkK7JxmRhk2Lyjw== -"@typescript-eslint/typescript-estree@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.0.tgz#2592d24bb5f89bf54a63384ff3494870f95b3fd8" - integrity sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg== +"@typescript-eslint/typescript-estree@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.9.tgz#6bfea844e468427b5e72034d33c9fffc9557392b" + integrity sha512-pmM0/VQ7kUhd1QyIxgS+aRvMgw+ZljB3eDb+jYyp6d2bC0mQWLzUDF+DLwCTkQ3tlNyVsvZRXjFyV0LkU/aXjA== dependencies: - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/visitor-keys" "5.42.0" + "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/visitor-keys" "5.59.9" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.42.0.tgz#f06bd43b9a9a06ed8f29600273240e84a53f2f15" - integrity sha512-JZ++3+h1vbeG1NUECXQZE3hg0kias9kOtcQr3+JVQ3whnjvKuMyktJAAIj6743OeNPnGBmjj7KEmiDL7qsdnCQ== +"@typescript-eslint/utils@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.9.tgz#adee890107b5ffe02cd46fdaa6c2125fb3c6c7c4" + integrity sha512-1PuMYsju/38I5Ggblaeb98TOoUvjhRvLpLa1DoTOFaLWqaXl/1iQ1eGurTXgBY58NUdtfTXKP5xBq7q9NDaLKg== dependencies: + "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.42.0" - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/typescript-estree" "5.42.0" + "@typescript-eslint/scope-manager" "5.59.9" + "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/typescript-estree" "5.59.9" eslint-scope "^5.1.1" - eslint-utils "^3.0.0" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.0.tgz#ee8d62d486f41cfe646632fab790fbf0c1db5bb0" - integrity sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg== +"@typescript-eslint/visitor-keys@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.9.tgz#9f86ef8e95aca30fb5a705bb7430f95fc58b146d" + integrity sha512-bT7s0td97KMaLwpEBckbzj/YohnvXtqbe2XgqNvTl6RJVakY5mvENOTPvw5u66nljfZxthESpDozs86U+oLY8Q== dependencies: - "@typescript-eslint/types" "5.42.0" + "@typescript-eslint/types" "5.59.9" eslint-visitor-keys "^3.3.0" -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== +"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" + integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== +"@webassemblyjs/helper-buffer@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" + integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== +"@webassemblyjs/helper-wasm-section@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" + integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== - -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" - -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== - dependencies: - "@webassemblyjs/ast" "1.11.1" +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" + integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-opt" "1.11.6" + "@webassemblyjs/wasm-parser" "1.11.6" + "@webassemblyjs/wast-printer" "1.11.6" + +"@webassemblyjs/wasm-gen@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" + integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" + integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-parser" "1.11.6" + +"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" + integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" + integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== + dependencies: + "@webassemblyjs/ast" "1.11.6" "@xtuc/long" "4.2.2" -"@webpack-cli/configtest@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5" - integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== +"@webpack-cli/configtest@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz#3b2f852e91dac6e3b85fb2a314fb8bef46d94646" + integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== -"@webpack-cli/info@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1" - integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ== - dependencies: - envinfo "^7.7.3" +"@webpack-cli/info@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.2.tgz#cc3fbf22efeb88ff62310cf885c5b09f44ae0fdd" + integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== -"@webpack-cli/serve@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1" - integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== +"@webpack-cli/serve@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e" + integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== "@wojtekmaj/date-utils@^1.0.2": version "1.0.3" resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.0.3.tgz#2dcfd92881425c5923e429c2aec86fb3609032a1" integrity sha512-1VPkkTBk07gMR1fjpBtse4G+oJqpmE+0gUFB0dg3VIL7qJmUVaBoD/vlzMm/jNeOPfvlmerl1lpnsZyBUFIRuw== +"@wojtekmaj/date-utils@^1.1.3": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz#c3cd67177ac781cfa5736219d702a55a2aea5f2b" + integrity sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww== + "@xobotyi/scrollbar-width@^1.9.5": version "1.9.5" resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" @@ -4126,10 +5026,10 @@ acorn-globals@^7.0.0: acorn "^8.1.0" acorn-walk "^8.0.2" -acorn-import-assertions@^1.7.6: - version "1.8.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" - integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== +acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== acorn-jsx@^5.3.2: version "5.3.2" @@ -4141,11 +5041,16 @@ acorn-walk@^8.0.2, acorn-walk@^8.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^8.1.0, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.1: +acorn@^8.1.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.1: version "8.8.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== +acorn@^8.8.2, acorn@^8.9.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== + add-dom-event-listener@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310" @@ -4241,6 +5146,11 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -4260,6 +5170,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + ansicolor@1.1.100: version "1.1.100" resolved "https://registry.yarnpkg.com/ansicolor/-/ansicolor-1.1.100.tgz#811f1afbf726edca3aafb942a14df8351996304a" @@ -4294,6 +5209,11 @@ arch@^2.2.0: resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== +are-docs-informative@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz#387f0e93f5d45280373d387a59d34c96db321963" + integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig== + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -4342,15 +5262,15 @@ array-includes@^3.1.3: get-intrinsic "^1.1.1" is-string "^1.0.7" -array-includes@^3.1.5: - version "3.1.6" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" - integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== +array-includes@^3.1.6: + version "3.1.7" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" + integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" is-string "^1.0.7" array-timsort@^1.0.3: @@ -4368,15 +5288,39 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.flatmap@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" - integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== +array.prototype.flatmap@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.tosorted@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz#620eff7442503d66c799d95503f82b475745cefd" + integrity sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" + get-intrinsic "^1.2.1" + +arraybuffer.prototype.slice@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" + integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" asap@~1.0.0: version "1.0.0" @@ -4442,15 +5386,15 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -babel-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5" - integrity sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q== +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== dependencies: - "@jest/transform" "^29.5.0" + "@jest/transform" "^29.7.0" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.5.0" + babel-preset-jest "^29.6.3" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" @@ -4474,10 +5418,10 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" - integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -4535,12 +5479,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" - integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== dependencies: - babel-plugin-jest-hoist "^29.5.0" + babel-plugin-jest-hoist "^29.6.3" babel-preset-current-node-syntax "^1.0.0" babel-runtime@6.x, babel-runtime@^6.26.0: @@ -4694,6 +5638,11 @@ buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + bytes@1: version "1.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" @@ -4929,6 +5878,11 @@ clsx@^1.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== +clsx@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -4985,7 +5939,7 @@ commander@2.11.x: resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ== -commander@7, commander@^7.0.0: +commander@7: version "7.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== @@ -5000,6 +5954,11 @@ commander@^10.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.0.tgz#71797971162cd3cf65f0b9d24eb28f8d303acdf1" integrity sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA== +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + commander@^2.19.0, commander@^2.20.0, commander@^2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -5113,6 +6072,11 @@ core-js@3.27.1: resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.27.1.tgz#23cc909b315a6bb4e418bf40a52758af2103ba46" integrity sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww== +core-js@3.31.0: + version "3.31.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.31.0.tgz#4471dd33e366c79d8c0977ed2d940821719db344" + integrity sha512-NIp2TQSGfR6ba5aalZD+ZQ1fSxGhDo/s1w0nx3RYzf2pnJxt7YynxFlFScP6eV7+GZsKO95NSjGxyJsU3DZgeQ== + core-js@^2.4.0: version "2.6.12" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" @@ -5160,6 +6124,19 @@ cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -5318,15 +6295,15 @@ css-in-js-utils@^2.0.0: hyphenate-style-name "^1.0.2" isobject "^3.0.1" -css-loader@^6.7.1: - version "6.7.3" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.3.tgz#1e8799f3ccc5874fdd55461af51137fcc5befbcd" - integrity sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ== +css-loader@^6.7.3: + version "6.8.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.8.1.tgz#0f8f52699f60f5e679eab4ec0fcd68b8e8a50a88" + integrity sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g== dependencies: icss-utils "^5.1.0" - postcss "^8.4.19" + postcss "^8.4.21" postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" + postcss-modules-local-by-default "^4.0.3" postcss-modules-scope "^3.0.0" postcss-modules-values "^4.0.0" postcss-value-parser "^4.2.0" @@ -5648,10 +6625,46 @@ d3-zoom@3: d3-selection "2 - 3" d3-transition "2 - 3" -d3@7.8.2: - version "7.8.2" - resolved "https://registry.yarnpkg.com/d3/-/d3-7.8.2.tgz#2bdb3c178d095ae03b107a18837ae049838e372d" - integrity sha512-WXty7qOGSHb7HR7CfOzwN1Gw04MUOzN8qh9ZUsvwycIMb4DYMpY9xczZ6jUorGtO6bR9BPMPaueIKwiDxu9uiQ== +d3@7.8.2: + version "7.8.2" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.8.2.tgz#2bdb3c178d095ae03b107a18837ae049838e372d" + integrity sha512-WXty7qOGSHb7HR7CfOzwN1Gw04MUOzN8qh9ZUsvwycIMb4DYMpY9xczZ6jUorGtO6bR9BPMPaueIKwiDxu9uiQ== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + +d3@7.8.5: + version "7.8.5" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.8.5.tgz#fde4b760d4486cdb6f0cc8e2cbff318af844635c" + integrity sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA== dependencies: d3-array "3" d3-axis "3" @@ -5705,6 +6718,13 @@ date-fns@2.29.3: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== +date-fns@2.30.0: + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== + dependencies: + "@babel/runtime" "^7.21.0" + date-format@^0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/date-format/-/date-format-0.0.0.tgz#09206863ab070eb459acea5542cbd856b11966b3" @@ -5765,10 +6785,10 @@ decimal.js@^10.4.2: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +dedent@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" + integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== deep-equal@^2.0.5: version "2.2.2" @@ -5869,6 +6889,11 @@ diff-sequences@^29.4.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + diff@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" @@ -5944,6 +6969,11 @@ domexception@^4.0.0: dependencies: webidl-conversions "^7.0.0" +dompurify@^2.4.3: + version "2.4.7" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.7.tgz#277adeb40a2c84be2d42a8bcd45f582bfa4d0cfc" + integrity sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ== + dot-prop@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -5956,6 +6986,11 @@ earcut@^2.2.3: resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.4.tgz#6d02fd4d68160c114825d06890a92ecaae60343a" integrity sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ== +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -5989,6 +7024,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" @@ -6010,10 +7050,10 @@ enhanced-resolve@^4.0.0: memory-fs "^0.5.0" tapable "^1.0.0" -enhanced-resolve@^5.10.0: - version "5.12.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634" - integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ== +enhanced-resolve@^5.15.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -6064,7 +7104,7 @@ error@^7.0.0: dependencies: string-template "~0.2.1" -es-abstract@^1.19.0, es-abstract@^1.19.1: +es-abstract@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== @@ -6090,18 +7130,19 @@ es-abstract@^1.19.0, es-abstract@^1.19.1: string.prototype.trimstart "^1.0.4" unbox-primitive "^1.0.1" -es-abstract@^1.20.4: - version "1.21.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff" - integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg== +es-abstract@^1.22.1: + version "1.22.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.2.tgz#90f7282d91d0ad577f505e423e52d4c1d93c1b8a" + integrity sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA== dependencies: array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.2" available-typed-arrays "^1.0.5" call-bind "^1.0.2" es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.2.0" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.1" get-symbol-description "^1.0.0" globalthis "^1.0.3" gopd "^1.0.1" @@ -6116,19 +7157,23 @@ es-abstract@^1.20.4: is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" is-string "^1.0.7" - is-typed-array "^1.1.10" + is-typed-array "^1.1.12" is-weakref "^1.0.2" object-inspect "^1.12.3" object-keys "^1.1.1" object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" + regexp.prototype.flags "^1.5.1" + safe-array-concat "^1.0.1" safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.7" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" typed-array-length "^1.0.4" unbox-primitive "^1.0.2" - which-typed-array "^1.1.9" + which-typed-array "^1.1.11" es-get-iterator@^1.1.3: version "1.1.3" @@ -6145,10 +7190,10 @@ es-get-iterator@^1.1.3: isarray "^2.0.5" stop-iteration-iterator "^1.0.0" -es-module-lexer@^0.9.0: - version "0.9.3" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" - integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== +es-module-lexer@^1.2.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.1.tgz#c1b0dd5ada807a3b3155315911f364dc4e909db1" + integrity sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q== es-set-tostringtag@^2.0.1: version "2.0.1" @@ -6207,22 +7252,24 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-config-prettier@8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" - integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== +eslint-config-prettier@8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" + integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== -eslint-plugin-jsdoc@39.6.2: - version "39.6.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.6.2.tgz#dcc86cec7cce47aa1a646e38debd5bdf76f63742" - integrity sha512-dvgY/W7eUFoAIIiaWHERIMI61ZWqcz9YFjEeyTzdPlrZc3TY/3aZm5aB91NUoTLWYZmO/vFlYSuQi15tF7uE5A== +eslint-plugin-jsdoc@46.2.6: + version "46.2.6" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.2.6.tgz#f25590d371859f20691d65b5dcd4cbe370d65564" + integrity sha512-zIaK3zbSrKuH12bP+SPybPgcHSM6MFzh3HFeaODzmsF1N8C1l8dzJ22cW1aq4g0+nayU1VMjmNf7hg0dpShLrA== dependencies: - "@es-joy/jsdoccomment" "~0.36.0" + "@es-joy/jsdoccomment" "~0.39.4" + are-docs-informative "^0.0.2" comment-parser "1.3.1" debug "^4.3.4" escape-string-regexp "^4.0.0" - esquery "^1.4.0" - semver "^7.3.8" + esquery "^1.5.0" + is-builtin-module "^3.2.1" + semver "^7.5.1" spdx-expression-parse "^3.0.1" eslint-plugin-react-hooks@4.6.0: @@ -6230,25 +7277,26 @@ eslint-plugin-react-hooks@4.6.0: resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== -eslint-plugin-react@7.31.10: - version "7.31.10" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz#6782c2c7fe91c09e715d536067644bbb9491419a" - integrity sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA== +eslint-plugin-react@7.32.2: + version "7.32.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz#e71f21c7c265ebce01bcbc9d0955170c55571f10" + integrity sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg== dependencies: - array-includes "^3.1.5" - array.prototype.flatmap "^1.3.0" + array-includes "^3.1.6" + array.prototype.flatmap "^1.3.1" + array.prototype.tosorted "^1.1.1" doctrine "^2.1.0" estraverse "^5.3.0" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" - object.entries "^1.1.5" - object.fromentries "^2.0.5" - object.hasown "^1.1.1" - object.values "^1.1.5" + object.entries "^1.1.6" + object.fromentries "^2.0.6" + object.hasown "^1.1.2" + object.values "^1.1.6" prop-types "^15.8.1" - resolve "^2.0.0-next.3" + resolve "^2.0.0-next.4" semver "^6.3.0" - string.prototype.matchall "^4.0.7" + string.prototype.matchall "^4.0.8" eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" @@ -6258,49 +7306,45 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== +eslint-scope@^7.2.0: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" - integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== - eslint-visitor-keys@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint-webpack-plugin@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz#1978cdb9edc461e4b0195a20da950cf57988347c" - integrity sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w== +eslint-visitor-keys@^3.4.1: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-webpack-plugin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-4.0.1.tgz#f0f0e9afff2801d8bd41eac88e5409821ecbaccb" + integrity sha512-fUFcXpui/FftGx3NzvWgLZXlLbu+m74sUxGEgxgoxYcUtkIQbS6SdNNZkS99m5ycb23TfoNYrDpp1k/CK5j6Hw== dependencies: - "@types/eslint" "^7.29.0 || ^8.4.1" - jest-worker "^28.0.2" + "@types/eslint" "^8.37.0" + jest-worker "^29.5.0" micromatch "^4.0.5" normalize-path "^3.0.0" schema-utils "^4.0.0" -eslint@8.26.0: - version "8.26.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.26.0.tgz#2bcc8836e6c424c4ac26a5674a70d44d84f2181d" - integrity sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg== +eslint@8.42.0: + version "8.42.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.42.0.tgz#7bebdc3a55f9ed7167251fe7259f75219cade291" + integrity sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A== dependencies: - "@eslint/eslintrc" "^1.3.3" - "@humanwhocodes/config-array" "^0.11.6" + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.4.0" + "@eslint/eslintrc" "^2.0.3" + "@eslint/js" "8.42.0" + "@humanwhocodes/config-array" "^0.11.10" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" ajv "^6.10.0" @@ -6309,24 +7353,22 @@ eslint@8.26.0: debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-utils "^3.0.0" - eslint-visitor-keys "^3.3.0" - espree "^9.4.0" - esquery "^1.4.0" + eslint-scope "^7.2.0" + eslint-visitor-keys "^3.4.1" + espree "^9.5.2" + esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" find-up "^5.0.0" glob-parent "^6.0.2" - globals "^13.15.0" - grapheme-splitter "^1.0.4" + globals "^13.19.0" + graphemer "^1.4.0" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" - js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" @@ -6334,29 +7376,28 @@ eslint@8.26.0: minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.1" - regexpp "^3.2.0" strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.4.0: - version "9.5.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.0.tgz#3646d4e3f58907464edba852fa047e6a27bdf113" - integrity sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw== +espree@^9.5.2, espree@^9.6.0: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: - acorn "^8.8.0" + acorn "^8.9.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" + eslint-visitor-keys "^3.4.1" esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== +esquery@^1.4.2, esquery@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== dependencies: estraverse "^5.1.0" @@ -6402,6 +7443,11 @@ eventemitter3@4.0.7: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +eventemitter3@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.0.tgz#084eb7f5b5388df1451e63f4c2aafd71b217ccb3" + integrity sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg== + events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -6454,7 +7500,7 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= -expect@^29.0.0, expect@^29.5.0: +expect@^29.0.0: version "29.5.0" resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== @@ -6465,6 +7511,17 @@ expect@^29.0.0, expect@^29.5.0: jest-message-util "^29.5.0" jest-util "^29.5.0" +expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -6664,15 +7721,23 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -fork-ts-checker-webpack-plugin@^7.2.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.3.0.tgz#a9c984a018493962360d7c7e77a67b44a2d5f3aa" - integrity sha512-IN+XTzusCjR5VgntYFgxbxVx3WraPRnKehBFrf00cMSrtUuW9MsG9dhL6MWpY6MkjC3wVwoujfCDgZZCQwbswA== +fork-ts-checker-webpack-plugin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz#dae45dfe7298aa5d553e2580096ced79b6179504" + integrity sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg== dependencies: "@babel/code-frame" "^7.16.7" chalk "^4.1.2" @@ -6744,17 +7809,17 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" -functions-have-names@^1.2.2, functions-have-names@^1.2.3: +functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== @@ -6782,6 +7847,19 @@ geotiff@2.0.4: web-worker "^1.2.0" xml-utils "^1.0.2" +geotiff@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/geotiff/-/geotiff-2.0.7.tgz#358e578233af70bfb0b4dee62d599ad78fc5cfca" + integrity sha512-FKvFTNowMU5K6lHYY2f83d4lS2rsCNdpUC28AX61x9ZzzqPNaWFElWv93xj0eJFaNyOYA63ic5OzJ88dHpoA5Q== + dependencies: + "@petamoriken/float16" "^3.4.7" + lerc "^3.0.0" + pako "^2.0.4" + parse-headers "^2.0.2" + quick-lru "^6.1.1" + web-worker "^1.2.0" + xml-utils "^1.0.2" + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -6857,6 +7935,14 @@ get-user-locale@^1.2.0: dependencies: lodash.once "^4.1.1" +get-user-locale@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/get-user-locale/-/get-user-locale-2.3.1.tgz#fc7319429c8a70fac01b3b2a0b08b0c71c1d3fe2" + integrity sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ== + dependencies: + "@types/lodash.memoize" "^4.1.7" + lodash.memoize "^4.1.1" + get-window@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/get-window/-/get-window-1.1.2.tgz#65fbaa999fb87f86ea5d30770f4097707044f47f" @@ -6909,6 +7995,17 @@ glob@7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^10.2.7: + version "10.3.10" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" + integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.3.5" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" + glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -6921,17 +8018,6 @@ glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" - integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - global-dirs@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -6951,7 +8037,7 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.15.0, globals@^13.19.0: +globals@^13.19.0: version "13.20.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== @@ -7010,6 +8096,11 @@ grapheme-splitter@^1.0.4: resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -7197,6 +8288,13 @@ hyphenate-style-name@^1.0.2: resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== +i18next-browser-languagedetector@^7.0.2: + version "7.1.0" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.1.0.tgz#01876fac51f86b78975e79b48ccb62e2313a2d7d" + integrity sha512-cr2k7u1XJJ4HTOjM9GyOMtbOA47RtUoWRAtt52z43r3AoMs2StYKyjS3URPhzHaf+mn10hY9dZWamga5WPQjhA== + dependencies: + "@babel/runtime" "^7.19.4" + i18next@^22.0.0: version "22.4.13" resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.13.tgz#02e291ab0056eab13b7d356fb454ff991923eaa0" @@ -7245,7 +8343,7 @@ immutable@4.2.2: resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.2.2.tgz#2da9ff4384a4330c36d4d1bc88e90f9e0b0ccd16" integrity sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og== -immutable@^4.0.0: +immutable@4.3.0, immutable@^4.0.0: version "4.3.0" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be" integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg== @@ -7339,10 +8437,10 @@ internal-slot@^1.0.4, internal-slot@^1.0.5: resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== -interpret@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" - integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== intl-messageformat@^10.1.0: version "10.1.4" @@ -7410,6 +8508,13 @@ is-buffer@~1.1.6: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.3, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -7432,6 +8537,13 @@ is-ci@^3.0.0: dependencies: ci-info "^3.2.0" +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + is-core-module@^2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" @@ -7626,6 +8738,13 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.9: gopd "^1.0.1" has-tostringtag "^1.0.0" +is-typed-array@^1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== + dependencies: + which-typed-array "^1.1.11" + is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -7713,7 +8832,7 @@ istanbul-lib-coverage@^3.2.0: resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: +istanbul-lib-instrument@^5.0.4: version "5.2.1" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== @@ -7724,6 +8843,17 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" +istanbul-lib-instrument@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz#71e87707e8041428732518c6fb5211761753fbdf" + integrity sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + istanbul-lib-report@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" @@ -7750,83 +8880,92 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" - integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== +jackspeak@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" + integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== dependencies: execa "^5.0.0" + jest-util "^29.7.0" p-limit "^3.1.0" -jest-circus@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.5.0.tgz#b5926989449e75bff0d59944bae083c9d7fb7317" - integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA== +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - dedent "^0.7.0" + dedent "^1.0.0" is-generator-fn "^2.0.0" - jest-each "^29.5.0" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" p-limit "^3.1.0" - pretty-format "^29.5.0" + pretty-format "^29.7.0" pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.5.0.tgz#b34c20a6d35968f3ee47a7437ff8e53e086b4a67" - integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw== +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== dependencies: - "@jest/core" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" chalk "^4.0.0" + create-jest "^29.7.0" exit "^0.1.2" - graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" - prompts "^2.0.1" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" yargs "^17.3.1" -jest-config@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.5.0.tgz#3cc972faec8c8aaea9ae158c694541b79f3748da" - integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA== +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.5.0" - "@jest/types" "^29.5.0" - babel-jest "^29.5.0" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.5.0" - jest-environment-node "^29.5.0" - jest-get-type "^29.4.3" - jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-runner "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.5.0" + pretty-format "^29.7.0" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -7850,49 +8989,59 @@ jest-diff@^29.5.0: jest-get-type "^29.4.3" pretty-format "^29.5.0" -jest-docblock@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" - integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== dependencies: - detect-newline "^3.0.0" + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" -jest-each@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.5.0.tgz#fc6e7014f83eac68e22b7195598de8554c2e5c06" - integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA== +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== dependencies: - "@jest/types" "^29.5.0" - chalk "^4.0.0" - jest-get-type "^29.4.3" - jest-util "^29.5.0" - pretty-format "^29.5.0" + detect-newline "^3.0.0" -jest-environment-jsdom@^29.3.1: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.5.0.tgz#cfe86ebaf1453f3297b5ff3470fbe94739c960cb" - integrity sha512-/KG8yEK4aN8ak56yFVdqFDzKNHgF4BAymCx2LbPNPsUshUlfAl0eX402Xm1pt+eoG9SLZEUVifqXtX8SK74KCw== +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-jsdom@^29.5.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz#d206fa3551933c3fd519e5dfdb58a0f5139a837f" + integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/jsdom" "^20.0.0" "@types/node" "*" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" jsdom "^20.0.0" -jest-environment-node@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.5.0.tgz#f17219d0f0cc0e68e0727c58b792c040e332c967" - integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw== +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" jest-get-type@^26.3.0: version "26.3.0" @@ -7904,32 +9053,37 @@ jest-get-type@^29.4.3: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== -jest-haste-map@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.5.0.tgz#69bd67dc9012d6e2723f20a945099e972b2e94de" - integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^29.4.3" - jest-util "^29.5.0" - jest-worker "^29.5.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz#cf4bdea9615c72bac4a3a7ba7e7930f9c0610c8c" - integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow== +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== dependencies: - jest-get-type "^29.4.3" - pretty-format "^29.5.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" jest-matcher-utils@^29.5.0: version "29.5.0" @@ -7941,6 +9095,16 @@ jest-matcher-utils@^29.5.0: jest-get-type "^29.4.3" pretty-format "^29.5.0" +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-message-util@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" @@ -7956,131 +9120,143 @@ jest-message-util@^29.5.0: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed" - integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw== +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== dependencies: - "@jest/types" "^29.5.0" + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" "@types/node" "*" - jest-util "^29.5.0" + jest-util "^29.7.0" jest-pnp-resolver@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== -jest-regex-util@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" - integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== -jest-resolve-dependencies@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz#f0ea29955996f49788bf70996052aa98e7befee4" - integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg== +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== dependencies: - jest-regex-util "^29.4.3" - jest-snapshot "^29.5.0" + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" -jest-resolve@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.5.0.tgz#b053cc95ad1d5f6327f0ac8aae9f98795475ecdc" - integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w== +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.7.0" jest-pnp-resolver "^1.2.2" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" resolve "^1.20.0" resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.5.0.tgz#6a57c282eb0ef749778d444c1d758c6a7693b6f8" - integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ== +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== dependencies: - "@jest/console" "^29.5.0" - "@jest/environment" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^29.4.3" - jest-environment-node "^29.5.0" - jest-haste-map "^29.5.0" - jest-leak-detector "^29.5.0" - jest-message-util "^29.5.0" - jest-resolve "^29.5.0" - jest-runtime "^29.5.0" - jest-util "^29.5.0" - jest-watcher "^29.5.0" - jest-worker "^29.5.0" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.5.0.tgz#c83f943ee0c1da7eb91fa181b0811ebd59b03420" - integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw== - dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/globals" "^29.5.0" - "@jest/source-map" "^29.4.3" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" - jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.5.0.tgz#c9c1ce0331e5b63cd444e2f95a55a73b84b1e8ce" - integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g== +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/babel__traverse" "^7.0.6" - "@types/prettier" "^2.1.5" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.5.0" + expect "^29.7.0" graceful-fs "^4.2.9" - jest-diff "^29.5.0" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" natural-compare "^1.4.0" - pretty-format "^29.5.0" - semver "^7.3.5" + pretty-format "^29.7.0" + semver "^7.5.3" jest-util@^29.5.0: version "29.5.0" @@ -8094,30 +9270,42 @@ jest-util@^29.5.0: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.5.0.tgz#8e5a8f36178d40e47138dc00866a5f3bd9916ffc" - integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ== +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^29.4.3" + jest-get-type "^29.6.3" leven "^3.1.0" - pretty-format "^29.5.0" + pretty-format "^29.7.0" -jest-watcher@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.5.0.tgz#cf7f0f949828ba65ddbbb45c743a382a4d911363" - integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA== +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== dependencies: - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.5.0" + jest-util "^29.7.0" string-length "^4.0.1" jest-worker@^27.4.5: @@ -8129,15 +9317,6 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^28.0.2: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.3.tgz#7e3c4ce3fa23d1bb6accb169e7f396f98ed4bb98" - integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - jest-worker@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.5.0.tgz#bdaefb06811bd3384d93f009755014d8acb4615d" @@ -8148,31 +9327,41 @@ jest-worker@^29.5.0: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.3.1: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e" - integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== dependencies: - "@jest/core" "^29.5.0" - "@jest/types" "^29.5.0" + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.5.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" import-local "^3.0.2" - jest-cli "^29.5.0" + jest-cli "^29.7.0" jquery@3.6.1: version "3.6.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.1.tgz#fab0408f8b45fc19f956205773b62b292c147a16" integrity sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw== +jquery@3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.0.tgz#fe2c01a05da500709006d8790fe21c8a39d75612" + integrity sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ== + js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== -js-sdsl@^4.1.4: - version "4.3.0" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711" - integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== - js-sql-parser@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/js-sql-parser/-/js-sql-parser-1.4.1.tgz#775516b3187dd5872ecec04bef8ed4a430242fda" @@ -8203,10 +9392,10 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdoc-type-pratt-parser@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz#a4a56bdc6e82e5865ffd9febc5b1a227ff28e67e" - integrity sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw== +jsdoc-type-pratt-parser@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz#136f0571a99c184d84ec84662c45c29ceff71114" + integrity sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ== jsdom@^20.0.0: version "20.0.3" @@ -8347,7 +9536,7 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -klona@^2.0.4: +klona@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== @@ -8440,6 +9629,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.memoize@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + lodash.merge@4.6.2, lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -8503,6 +9697,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +"lru-cache@^9.1.1 || ^10.0.0": + version "10.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" + integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== + lz-string@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" @@ -8537,11 +9736,21 @@ mapbox-to-css-font@^2.4.1: resolved "https://registry.yarnpkg.com/mapbox-to-css-font/-/mapbox-to-css-font-2.4.1.tgz#41bf38faed36b7dab069828aa3654e4bd91a1eda" integrity sha512-QQ/iKiM43DM9+aujTL45Iz5o7gDeSFmy4LPl3HZmNcwCE++NxGazf+yFpY+wCb+YS23sDa1ghpo3zrNFOcHlow== +marked-mangle@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/marked-mangle/-/marked-mangle-1.1.0.tgz#f9f0adfbb841079d7342368bc5c7592ba93e3527" + integrity sha512-ed2W2gMB2HIBaYasBZveMFJfDRTL2OFycr0GgUSPcBSNl5dX+1r6lHG6u1eFXw7kej2hBTWa1m6YZqcfn4Coxw== + marked@4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.0.tgz#f1683b077626a6c53e28926b798a18184aa13a91" integrity sha512-1qWHjHlBKwjnDfrkxd0L3Yx4LTad/WO7+d13YsXAC/ZfKj7p0xkLV3sDXJzfWgL7GfW4IBZwMAYWaz+ifyQouQ== +marked@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/marked/-/marked-5.1.1.tgz#40b3963bb9da225314f746d5012ba7e34942f636" + integrity sha512-bTmmGdEINWmOMDjnPWDxGPQ4qkDLeYorpYbEtFOXzOruTwUE671q4Guiuchn4N8h/v6NGd7916kXsm3Iz4iUSg== + md5@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" @@ -8678,10 +9887,10 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== +minimatch@^9.0.1: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== dependencies: brace-expansion "^2.0.1" @@ -8690,6 +9899,11 @@ minimist@^1.2.5, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.0.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" + integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== + mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -8738,7 +9952,14 @@ moment-timezone@0.5.38: dependencies: moment ">= 2.9.0" -moment@2.29.4, moment@2.x, "moment@>= 2.9.0": +moment-timezone@0.5.41: + version "0.5.41" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.41.tgz#a7ad3285fd24aaf5f93b8119a9d749c8039c64c5" + integrity sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg== + dependencies: + moment "^2.29.4" + +moment@2.29.4, moment@2.x, "moment@>= 2.9.0", moment@^2.29.4: version "2.29.4" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== @@ -8768,6 +9989,11 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +murmurhash-js@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51" + integrity sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw== + nano-css@^5.3.1: version "5.3.4" resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.3.4.tgz#40af6a83a76f84204f346e8ccaa9169cdae9167b" @@ -8787,10 +10013,10 @@ nanoid@3.3.3: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== -nanoid@^3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== natural-compare-lite@^1.4.0: version "1.4.0" @@ -8929,40 +10155,40 @@ object.assign@^4.1.4: has-symbols "^1.0.3" object-keys "^1.1.1" -object.entries@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861" - integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g== +object.entries@^1.1.6: + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.7.tgz#2b47760e2a2e3a752f39dd874655c61a7f03c131" + integrity sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.2.0" + es-abstract "^1.22.1" -object.fromentries@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.5.tgz#7b37b205109c21e741e605727fe8b0ad5fa08251" - integrity sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw== +object.fromentries@^2.0.6: + version "2.0.7" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" + integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.2.0" + es-abstract "^1.22.1" -object.hasown@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.2.tgz#f919e21fad4eb38a57bc6345b3afd496515c3f92" - integrity sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw== +object.hasown@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.3.tgz#6a5f2897bb4d3668b8e79364f98ccf971bda55ae" + integrity sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA== dependencies: - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" -object.values@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" - integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== +object.values@^1.1.6: + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" + integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.2.0" + es-abstract "^1.22.1" ol-mapbox-style@9.1.0: version "9.1.0" @@ -8972,6 +10198,15 @@ ol-mapbox-style@9.1.0: "@mapbox/mapbox-gl-style-spec" "^13.23.1" mapbox-to-css-font "^2.4.1" +ol-mapbox-style@^10.1.0: + version "10.7.0" + resolved "https://registry.yarnpkg.com/ol-mapbox-style/-/ol-mapbox-style-10.7.0.tgz#8837912da2a16fbd22992d76cbc4f491c838b973" + integrity sha512-S/UdYBuOjrotcR95Iq9AejGYbifKeZE85D9VtH11ryJLQPTZXZSW1J5bIXcr4AlAH6tyjPPHTK34AdkwB32Myw== + dependencies: + "@mapbox/mapbox-gl-style-spec" "^13.23.1" + mapbox-to-css-font "^2.4.1" + ol "^7.3.0" + ol@7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/ol/-/ol-7.1.0.tgz#aab69a0539e59d6a4361cbc0f69f8b00c7298c9c" @@ -8983,6 +10218,28 @@ ol@7.1.0: pbf "3.2.1" rbush "^3.0.1" +ol@7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/ol/-/ol-7.4.0.tgz#935436c0843d1f939972e076d4fcb130530ce9d7" + integrity sha512-bgBbiah694HhC0jt8ptEFNRXwgO8d6xWH3G97PCg4bmn9Li5nLLbi59oSrvqUI6VPVwonPQF1YcqJymxxyMC6A== + dependencies: + earcut "^2.2.3" + geotiff "^2.0.7" + ol-mapbox-style "^10.1.0" + pbf "3.2.1" + rbush "^3.0.1" + +ol@^7.3.0: + version "7.5.2" + resolved "https://registry.yarnpkg.com/ol/-/ol-7.5.2.tgz#2e40a16b45331dbee86ca86876fcc7846be0dbb7" + integrity sha512-HJbb3CxXrksM6ct367LsP3N+uh+iBBMdP3DeGGipdV9YAYTP0vTJzqGnoqQ6C2IW4qf8krw9yuyQbc9fjOIaOQ== + dependencies: + earcut "^2.2.3" + geotiff "^2.0.7" + ol-mapbox-style "^10.1.0" + pbf "3.2.1" + rbush "^3.0.1" + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -9081,6 +10338,11 @@ papaparse@5.3.2: resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.3.2.tgz#d1abed498a0ee299f103130a6109720404fbd467" integrity sha512-6dNZu0Ki+gyV0eBsFKJhYr+MdQYAzFUGlBMNj3GNrmHxmz1lfRa24CjFObPXtjcetlOv5Ad299MhIK0znp3afw== +papaparse@5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.4.1.tgz#f45c0f871853578bd3a30f92d96fdcfb6ebea127" + integrity sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -9142,6 +10404,14 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== + dependencies: + lru-cache "^9.1.1 || ^10.0.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" @@ -9243,10 +10513,10 @@ postcss-modules-extract-imports@^3.0.0: resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== +postcss-modules-local-by-default@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524" + integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA== dependencies: icss-utils "^5.0.0" postcss-selector-parser "^6.0.2" @@ -9294,12 +10564,12 @@ postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.19: - version "8.4.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" - integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== +postcss@^8.4.21: + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== dependencies: - nanoid "^3.3.4" + nanoid "^3.3.6" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -9326,10 +10596,10 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prettier@^2.5.0: - version "2.8.6" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.6.tgz#5c174b29befd507f14b83e3c19f83fdc0e974b71" - integrity sha512-mtuzdiBbHwPEgl7NxWlqOkithPyp4VN93V7VeHVWBF+ad3I5avc0RVDT4oImXQy9H/AqxA2NSQH8pSxHW6FYbQ== +prettier@^2.8.7: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== pretty-bytes@^5.6.0: version "5.6.0" @@ -9365,6 +10635,15 @@ pretty-format@^29.0.0, pretty-format@^29.5.0: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + prismjs@1.29.0: version "1.29.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" @@ -9470,6 +10749,11 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== +quick-lru@^6.1.1: + version "6.1.2" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-6.1.2.tgz#e9a90524108629be35287d0b864e7ad6ceb3659e" + integrity sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ== + quickselect@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018" @@ -9556,6 +10840,18 @@ rc-animate@2.x: rc-util "^4.15.3" react-lifecycles-compat "^3.0.4" +rc-cascader@3.12.1: + version "3.12.1" + resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.12.1.tgz#35f9db14a2d32a2a413801d4625cb61cdaa3f706" + integrity sha512-g6In2y6eudHXS/Fs9dKFhp9acvHRUPqem/7xReR9ng8M1pNAE137uGBOt9WNpgsKT/cDGudXZQVehaBwAKg6hQ== + dependencies: + "@babel/runtime" "^7.12.5" + array-tree-filter "^2.1.0" + classnames "^2.3.1" + rc-select "~14.5.0" + rc-tree "~5.7.0" + rc-util "^5.6.1" + rc-cascader@3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.8.0.tgz#5eaca8998b2e3f5692d13f16bfe2346eccc87c6a" @@ -9579,6 +10875,17 @@ rc-drawer@6.1.2: rc-motion "^2.6.1" rc-util "^5.21.2" +rc-drawer@6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-6.3.0.tgz#f8af5fafbab19b83722360dcf93e966d8a2875ad" + integrity sha512-uBZVb3xTAR+dBV53d/bUhTctCw3pwcwJoM7g5aX+7vgwt2zzVzoJ6aqFjYJpBlZ9zp0dVYN8fV+hykFE7c4lig== + dependencies: + "@babel/runtime" "^7.10.1" + "@rc-component/portal" "^1.1.1" + classnames "^2.2.6" + rc-motion "^2.6.1" + rc-util "^5.21.2" + rc-motion@^2.0.0: version "2.4.1" resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.4.1.tgz#323f47c8635e6b2bc0cba2dfad25fc415b58e1dc" @@ -9626,6 +10933,16 @@ rc-resize-observer@^1.0.0: rc-util "^5.15.0" resize-observer-polyfill "^1.5.1" +rc-resize-observer@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-1.3.1.tgz#b61b9f27048001243617b81f95e53d7d7d7a6a3d" + integrity sha512-iFUdt3NNhflbY3mwySv5CA1TC06zdJ+pfo0oc27xpf4PIOvfZwZGtD9Kz41wGYqC4SLio93RVAirSSpYlV/uYg== + dependencies: + "@babel/runtime" "^7.20.7" + classnames "^2.2.1" + rc-util "^5.27.0" + resize-observer-polyfill "^1.5.1" + rc-select@~14.2.0: version "14.2.2" resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.2.2.tgz#03558848b190d24fc9010a3bf1104c6dbea9b122" @@ -9639,6 +10956,19 @@ rc-select@~14.2.0: rc-util "^5.16.1" rc-virtual-list "^3.4.13" +rc-select@~14.5.0: + version "14.5.2" + resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.5.2.tgz#1ac1ab58c874696cfa01cb15e1fc9a7bba81b29e" + integrity sha512-Np/lDHvxCnVhVsheQjSV1I/OMJTWJf1n10wq8q1AGy3ytyYLfjNpi6uaz/pmjsbbiSddSWzJnNZCli9LmgBZsA== + dependencies: + "@babel/runtime" "^7.10.1" + "@rc-component/trigger" "^1.5.0" + classnames "2.x" + rc-motion "^2.0.1" + rc-overflow "^1.0.0" + rc-util "^5.16.1" + rc-virtual-list "^3.5.2" + rc-slider@10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-10.1.0.tgz#11e401d8412ae20f9c2ee478bdbaddd042158753" @@ -9649,6 +10979,15 @@ rc-slider@10.1.0: rc-util "^5.18.1" shallowequal "^1.1.0" +rc-slider@10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-10.2.1.tgz#9b571d19f740adcacdde271f44901a47717fd8da" + integrity sha512-l355C/65iV4UFp7mXq5xBTNX2/tF2g74VWiTVlTpNp+6vjE/xaHHNiQq5Af+Uu28uUiqCuH/QXs5HfADL9KJ/A== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.5" + rc-util "^5.27.0" + rc-time-picker@^3.7.3: version "3.7.3" resolved "https://registry.yarnpkg.com/rc-time-picker/-/rc-time-picker-3.7.3.tgz#65a8de904093250ae9c82b02a4905e0f995e23e2" @@ -9670,6 +11009,15 @@ rc-tooltip@5.3.1: classnames "^2.3.1" rc-trigger "^5.3.1" +rc-tooltip@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-6.0.1.tgz#6a5e33bd6c3f6afe8851ea90e7af43e5c26b3cc6" + integrity sha512-MdvPlsD1fDSxKp9+HjXrc/CxLmA/s11QYIh1R7aExxfodKP7CZA++DG1AjrW80F8IUdHYcR43HAm0Y2BYPelHA== + dependencies: + "@babel/runtime" "^7.11.2" + "@rc-component/trigger" "^1.0.4" + classnames "^2.3.1" + rc-tree@~5.7.0: version "5.7.3" resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-5.7.3.tgz#5da576ba87039486d59092eb4490831690b8b3b5" @@ -9753,6 +11101,14 @@ rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.5.0: react-is "^16.12.0" shallowequal "^1.1.0" +rc-util@^5.27.0, rc-util@^5.36.0, rc-util@^5.38.0: + version "5.38.0" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.38.0.tgz#18a3d1c26ba3c43fabfbe6303e825dabd9e5f4f0" + integrity sha512-yV/YBNdFn+edyBpBdCqkPE29Su0jWcHNgwx2dJbRqMrMfrUcMJUjCRV+ZPhcvWyKFJ63GzEerPrz9JIVo0zXmA== + dependencies: + "@babel/runtime" "^7.18.3" + react-is "^18.2.0" + rc-virtual-list@^3.4.13, rc-virtual-list@^3.4.8: version "3.4.13" resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.4.13.tgz#20acc934b263abcf7b7c161f50ef82281b2f7e8d" @@ -9763,6 +11119,16 @@ rc-virtual-list@^3.4.13, rc-virtual-list@^3.4.8: rc-resize-observer "^1.0.0" rc-util "^5.15.0" +rc-virtual-list@^3.5.2: + version "3.11.2" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.11.2.tgz#eb859c2257233aff10864f041e5bcc89f7814bb7" + integrity sha512-MTFLL2LOHr3+/+r+WjTIs6j8XmJE6EqdOsJvCH8SWig7qyik3aljCEImUtw5tdWR0tQhXUfbv7P7nZaLY91XPg== + dependencies: + "@babel/runtime" "^7.20.0" + classnames "^2.2.6" + rc-resize-observer "^1.0.0" + rc-util "^5.36.0" + react-beautiful-dnd@13.1.1: version "13.1.1" resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2" @@ -9786,6 +11152,17 @@ react-calendar@3.9.0: merge-class-names "^1.1.1" prop-types "^15.6.0" +react-calendar@4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/react-calendar/-/react-calendar-4.3.0.tgz#030364faab6d0d1516af14121d18148163ebc9a9" + integrity sha512-TyCv8NbXnqXADyXNtMG0szkGvJNH3NG/WMTEE2q6g3RqAsFNyHwYbQD5Kvb6jRV/CqO0WB+oMCtkxblprdeT5A== + dependencies: + "@types/react" "*" + "@wojtekmaj/date-utils" "^1.1.3" + clsx "^1.2.1" + get-user-locale "^2.2.1" + prop-types "^15.6.0" + react-colorful@5.6.1: version "5.6.1" resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b" @@ -9864,6 +11241,14 @@ react-inlinesvg@3.0.1: exenv "^1.2.2" react-from-dom "^0.6.2" +react-inlinesvg@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/react-inlinesvg/-/react-inlinesvg-3.0.2.tgz#5c59799966ae7926057091b2ac230ddcee01bea0" + integrity sha512-BEzkpMGQwEY68fgaouY7ZWvAUPb8jbj7dE9iDbWZxstDhMuz9qfpxNgvGSENKcDMdpq/XHduSk/LAmNKin4nKw== + dependencies: + exenv "^1.2.2" + react-from-dom "^0.6.2" + react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -9879,7 +11264,7 @@ react-is@^17.0.2: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== @@ -9889,6 +11274,11 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-loading-skeleton@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/react-loading-skeleton/-/react-loading-skeleton-3.3.1.tgz#cd6e3a626ee86c76a46c14e2379243f2f8834e1b" + integrity sha512-NilqqwMh2v9omN7LteiDloEVpFyMIa0VGqF+ukqp0ncVlYu1sKYbYGX9JEl+GtOT9TKsh04zCHAbavnQ2USldA== + react-popper-tooltip@4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/react-popper-tooltip/-/react-popper-tooltip-4.4.2.tgz#0dc4894b8e00ba731f89bd2d30584f6032ec6163" @@ -9918,6 +11308,19 @@ react-redux@^7.2.0: prop-types "^15.7.2" react-is "^17.0.2" +react-router-dom@5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.3.tgz#8779fc28e6691d07afcaf98406d3812fe6f11199" + integrity sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng== + dependencies: + "@babel/runtime" "^7.12.13" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.3.3" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + react-router-dom@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363" @@ -9947,6 +11350,22 @@ react-router@5.2.1: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-router@5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.3.tgz#8e3841f4089e728cf82a429d92cdcaa5e4a3a288" + integrity sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w== + dependencies: + "@babel/runtime" "^7.12.13" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.4.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + react-select-event@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/react-select-event/-/react-select-event-5.3.0.tgz#4548fffd615a47176951cbb301ee21a0c60b582a" @@ -9969,6 +11388,21 @@ react-select@5.6.0: react-transition-group "^4.3.0" use-isomorphic-layout-effect "^1.1.2" +react-select@5.7.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.7.0.tgz#82921b38f1fcf1471a0b62304da01f2896cd8ce6" + integrity sha512-lJGiMxCa3cqnUr2Jjtg9YHsaytiZqeNOKeibv6WF5zbK/fPegZ1hg3y/9P1RZVLhqBTs0PfqQLKuAACednYGhQ== + dependencies: + "@babel/runtime" "^7.12.0" + "@emotion/cache" "^11.4.0" + "@emotion/react" "^11.8.1" + "@floating-ui/dom" "^1.0.1" + "@types/react-transition-group" "^4.4.0" + memoize-one "^6.0.0" + prop-types "^15.6.0" + react-transition-group "^4.3.0" + use-isomorphic-layout-effect "^1.1.2" + react-table@7.8.0: version "7.8.0" resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.8.0.tgz#07858c01c1718c09f7f1aed7034fcfd7bda907d2" @@ -10064,12 +11498,12 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -rechoir@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" - integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== dependencies: - resolve "^1.9.0" + resolve "^1.20.0" redent@^3.0.0: version "3.0.0" @@ -10125,21 +11559,26 @@ regenerator-runtime@0.13.10: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee" integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== +regenerator-runtime@0.13.11, regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - regenerator-runtime@^0.13.4: version "0.13.7" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + regenerator-transform@^0.15.1: version "0.15.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" @@ -10147,16 +11586,7 @@ regenerator-transform@^0.15.1: dependencies: "@babel/runtime" "^7.8.4" -regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" - -regexp.prototype.flags@^1.5.0: +regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== @@ -10165,11 +11595,6 @@ regexp.prototype.flags@^1.5.0: define-properties "^1.2.0" set-function-name "^2.0.0" -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - regexpu-core@^4.7.1: version "4.7.1" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" @@ -10357,7 +11782,7 @@ resolve@^1.14.2: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: +resolve@^1.19.0, resolve@^1.20.0: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -10366,12 +11791,12 @@ resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^2.0.0-next.3: - version "2.0.0-next.4" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660" - integrity sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ== +resolve@^2.0.0-next.4: + version "2.0.0-next.5" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -10427,13 +11852,23 @@ rw@1, rw@^1.3.3: resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= -rxjs@7.5.7, rxjs@^7.5.1, rxjs@^7.5.6: +rxjs@7.5.7, rxjs@7.8.0, rxjs@^7.5.1, rxjs@^7.5.6: version "7.5.6" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.6.tgz#0446577557862afd6903517ce7cae79ecb9662bc" integrity sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw== dependencies: tslib "^2.1.0" +safe-array-concat@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" + integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + isarray "^2.0.5" + safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -10463,18 +11898,18 @@ safe-regex-test@^1.0.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass-loader@13.2.0: - version "13.2.0" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-13.2.0.tgz#80195050f58c9aac63b792fa52acb6f5e0f6bdc3" - integrity sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg== +sass-loader@13.3.1: + version "13.3.1" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-13.3.1.tgz#32ee5791434b9b4dbd1adcce76fcb4cea49cc12c" + integrity sha512-cBTxmgyVA1nXPvIK4brjJMXOMJ2v2YrQEuHqLw3LylGb3gsR6jAvdjHMcy/+JGTmmIF9SauTrLLR7bsWDMWqgg== dependencies: - klona "^2.0.4" + klona "^2.0.6" neo-async "^2.6.2" -sass@1.56.1: - version "1.56.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.56.1.tgz#94d3910cd468fd075fa87f5bb17437a0b617d8a7" - integrity sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ== +sass@1.63.2: + version "1.63.2" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.63.2.tgz#75f7d9a8e67d1d5b98a989507f4d98b6067b1f75" + integrity sha512-u56TU0AIFqMtauKl/OJ1AeFsXqRHkgO7nCWmHaDwfxDo9GUMSqBA4NEh6GMuh1CYVM7zuROYtZrHzPc2ixK+ww== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -10504,7 +11939,7 @@ schema-utils@>1.0.0, schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.0.0" -schema-utils@^3.1.0, schema-utils@^3.1.1: +schema-utils@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== @@ -10513,6 +11948,15 @@ schema-utils@^3.1.0, schema-utils@^3.1.1: ajv "^6.12.5" ajv-keywords "^3.5.2" +schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + screenfull@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba" @@ -10533,7 +11977,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: +semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1, semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -10628,6 +12072,11 @@ signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -10914,6 +12363,11 @@ streamroller@^0.4.0: mkdirp "^0.5.1" readable-stream "^1.1.7" +string-hash@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" + integrity sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -10927,6 +12381,16 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.2.3: + name string-width-cjs + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^4.1.0, string-width@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" @@ -10936,37 +12400,38 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" -string.prototype.matchall@^4.0.7: - version "4.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3" - integrity sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg== +string.prototype.matchall@^4.0.8: + version "4.0.10" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz#a1553eb532221d4180c51581d6072cd65d1ee100" + integrity sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" has-symbols "^1.0.3" - internal-slot "^1.0.3" - regexp.prototype.flags "^1.4.3" + internal-slot "^1.0.5" + regexp.prototype.flags "^1.5.0" + set-function-name "^2.0.0" side-channel "^1.0.4" -string.prototype.trim@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" - integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== +string.prototype.trim@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" + integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" string.prototype.trimend@^1.0.4: version "1.0.4" @@ -10976,14 +12441,14 @@ string.prototype.trimend@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -string.prototype.trimend@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== +string.prototype.trimend@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" string.prototype.trimstart@^1.0.4: version "1.0.4" @@ -10993,14 +12458,14 @@ string.prototype.trimstart@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -string.prototype.trimstart@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" string_decoder@0.10, string_decoder@~0.10.x: version "0.10.31" @@ -11014,6 +12479,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" @@ -11021,12 +12493,12 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" -strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== dependencies: - ansi-regex "^5.0.1" + ansi-regex "^6.0.1" strip-bom@^3.0.0: version "3.0.0" @@ -11055,10 +12527,10 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1. resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -style-loader@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" - integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== +style-loader@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.3.tgz#bba8daac19930169c0c9c96706749a597ae3acff" + integrity sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw== stylis@4.0.13: version "4.0.13" @@ -11070,6 +12542,11 @@ stylis@4.1.3: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7" integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA== +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + stylis@^4.0.6: version "4.0.10" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240" @@ -11131,24 +12608,24 @@ tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -terser-webpack-plugin@^5.1.3: - version "5.3.7" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz#ef760632d24991760f339fe9290deb936ad1ffc7" - integrity sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw== +terser-webpack-plugin@^5.3.7: + version "5.3.9" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" + integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== dependencies: "@jridgewell/trace-mapping" "^0.3.17" jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.1" - terser "^5.16.5" + terser "^5.16.8" -terser@^5.16.5: - version "5.16.6" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.6.tgz#f6c7a14a378ee0630fbe3ac8d1f41b4681109533" - integrity sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg== +terser@^5.16.8: + version "5.22.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.22.0.tgz#4f18103f84c5c9437aafb7a14918273310a8a49d" + integrity sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw== dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" commander "^2.20.0" source-map-support "~0.5.20" @@ -11218,6 +12695,11 @@ tinycolor2@1.4.2: resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== +tinycolor2@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" + integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== + tmp@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" @@ -11327,7 +12809,7 @@ ts-loader@8.4.0: micromatch "^4.0.0" semver "^7.3.4" -ts-node@^10.5.0: +ts-node@^10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== @@ -11346,10 +12828,10 @@ ts-node@^10.5.0: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tsconfig-paths@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.1.2.tgz#4819f861eef82e6da52fb4af1e8c930a39ed979a" - integrity sha512-uhxiMgnXQp1IR622dUXI+9Ehnws7i/y6xvpZB9IbUVOPy0muvdvgXeZOn88UcGPiT98Vp3rJPTa8bFoalZ3Qhw== +tsconfig-paths@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== dependencies: json5 "^2.2.2" minimist "^1.2.6" @@ -11365,6 +12847,11 @@ tslib@2.4.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== +tslib@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" + integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== + tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -11375,11 +12862,16 @@ tslib@^2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== -tslib@^2.4.0, tslib@^2.5.0: +tslib@^2.4.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== +tslib@^2.5.3: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -11438,6 +12930,36 @@ type-of@^2.0.1: resolved "https://registry.yarnpkg.com/type-of/-/type-of-2.0.1.tgz#e72a1741896568e9f628378d816d6912f7f23972" integrity sha1-5yoXQYllaOn2KDeNgW1pEvfyOXI= +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" + +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + typed-array-length@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" @@ -11459,7 +12981,7 @@ typescript@4.8.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== -typescript@^4.4.0: +typescript@^4.8.4: version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== @@ -11723,6 +13245,11 @@ web-vitals@^3.0.4: resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-3.3.0.tgz#2e14bcbb3acf71d00c49519ab92cc517511476d5" integrity sha512-GZsEmJBNclIpViS/7QVOTr7Kbt4BgLeR7kQ5zCCtJVuiWsA+K6xTXaoEXssvl8yYFICEyNmA2Nr+vgBYTnS4bA== +web-vitals@^3.1.1: + version "3.5.0" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-3.5.0.tgz#3a5571f00743ecd059394b61e0adceec7fac2634" + integrity sha512-f5YnCHVG9Y6uLCePD4tY8bO/Ge15NPEQWtvm3tPzDKygloiqtb4SVqRHBcrIAqo2ztqX5XueqDn97zHF0LdT6w== + web-worker@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.2.0.tgz#5d85a04a7fbc1e7db58f66595d7a3ac7c9c180da" @@ -11738,22 +13265,23 @@ webidl-conversions@^7.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -webpack-cli@^4.9.2: - version "4.10.0" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" - integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w== +webpack-cli@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.4.tgz#c8e046ba7eaae4911d7e71e2b25b776fcc35759b" + integrity sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg== dependencies: "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^1.2.0" - "@webpack-cli/info" "^1.5.0" - "@webpack-cli/serve" "^1.7.0" + "@webpack-cli/configtest" "^2.1.1" + "@webpack-cli/info" "^2.0.2" + "@webpack-cli/serve" "^2.0.5" colorette "^2.0.14" - commander "^7.0.0" + commander "^10.0.1" cross-spawn "^7.0.3" + envinfo "^7.7.3" fastest-levenshtein "^1.0.12" import-local "^3.0.2" - interpret "^2.2.0" - rechoir "^0.7.0" + interpret "^3.1.1" + rechoir "^0.8.0" webpack-merge "^5.7.3" webpack-livereload-plugin@^3.0.2: @@ -11784,22 +13312,22 @@ webpack-virtual-modules@^0.4.4: resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.4.6.tgz#3e4008230731f1db078d9cb6f68baf8571182b45" integrity sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA== -webpack@^5.69.1: - version "5.76.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.2.tgz#6f80d1c1d1e3bf704db571b2504a0461fac80230" - integrity sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w== +webpack@^5.86.0: + version "5.89.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.89.0.tgz#56b8bf9a34356e93a6625770006490bf3a7f32dc" + integrity sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw== dependencies: "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" + "@types/estree" "^1.0.0" + "@webassemblyjs/ast" "^1.11.5" + "@webassemblyjs/wasm-edit" "^1.11.5" + "@webassemblyjs/wasm-parser" "^1.11.5" acorn "^8.7.1" - acorn-import-assertions "^1.7.6" + acorn-import-assertions "^1.9.0" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.10.0" - es-module-lexer "^0.9.0" + enhanced-resolve "^5.15.0" + es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" @@ -11808,9 +13336,9 @@ webpack@^5.69.1: loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.1.0" + schema-utils "^3.2.0" tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" + terser-webpack-plugin "^5.3.7" watchpack "^2.4.0" webpack-sources "^3.2.3" @@ -11877,6 +13405,17 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" +which-typed-array@^1.1.11: + version "1.1.11" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" + integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + which-typed-array@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" @@ -11918,6 +13457,15 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -11927,14 +13475,14 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" wrappy@1: version "1.0.2" @@ -11989,7 +13537,7 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xss@1.0.14: +xss@1.0.14, xss@^1.0.14: version "1.0.14" resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.14.tgz#4f3efbde75ad0d82e9921cc3c95e6590dd336694" integrity sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw== From b9fb80fcc741dd826f8ee121660b298aedffda28 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 25 Oct 2023 08:39:16 -0400 Subject: [PATCH 29/95] config view changes --- pkg/plugin/driver.go | 8 +- pkg/plugin/errors.go | 2 +- pkg/plugin/settings.go | 88 +-- pkg/plugin/settings_test.go | 8 +- src/__mocks__/ConfigEditor.ts | 6 +- src/__mocks__/datasource.ts | 2 +- .../DefaultDatabaseTableConfig.tsx | 46 ++ src/components/configEditor/LabeledInput.tsx | 30 + src/components/configEditor/LogsConfig.tsx | 110 ++++ .../configEditor/QuerySettingsConfig.tsx | 55 ++ src/components/configEditor/TracesConfig.tsx | 187 ++++++ src/data/CHDatasource.ts | 92 +++ src/selectors.ts | 6 +- src/types/config.ts | 54 +- src/utils/version.test.ts | 54 ++ src/utils/version.ts | 52 ++ src/views/CHConfigEditor.test.tsx | 32 +- src/views/CHConfigEditor.tsx | 536 ++++++++++-------- 18 files changed, 1056 insertions(+), 312 deletions(-) create mode 100644 src/components/configEditor/DefaultDatabaseTableConfig.tsx create mode 100644 src/components/configEditor/LabeledInput.tsx create mode 100644 src/components/configEditor/LogsConfig.tsx create mode 100644 src/components/configEditor/QuerySettingsConfig.tsx create mode 100644 src/components/configEditor/TracesConfig.tsx create mode 100644 src/utils/version.test.ts create mode 100644 src/utils/version.ts diff --git a/pkg/plugin/driver.go b/pkg/plugin/driver.go index e97c82b7..f6cd63e7 100644 --- a/pkg/plugin/driver.go +++ b/pkg/plugin/driver.go @@ -35,7 +35,7 @@ type Clickhouse struct{} func getTLSConfig(settings Settings) (*tls.Config, error) { tlsConfig := &tls.Config{ InsecureSkipVerify: settings.InsecureSkipVerify, - ServerName: settings.Server, + ServerName: settings.Host, } if settings.TlsClientAuth || settings.TlsAuthWithCACert { if settings.TlsAuthWithCACert && len(settings.TlsCACert) > 0 { @@ -117,9 +117,9 @@ func (h *Clickhouse) Connect(config backend.DataSourceInstanceSettings, message InsecureSkipVerify: settings.InsecureSkipVerify, } } - t, err := strconv.Atoi(settings.Timeout) + t, err := strconv.Atoi(settings.DialTimeout) if err != nil { - return nil, errors.New(fmt.Sprintf("invalid timeout: %s", settings.Timeout)) + return nil, errors.New(fmt.Sprintf("invalid timeout: %s", settings.DialTimeout)) } qt, err := strconv.Atoi(settings.QueryTimeout) if err != nil { @@ -145,7 +145,7 @@ func (h *Clickhouse) Connect(config backend.DataSourceInstanceSettings, message Products: getClientInfoProducts(), }, TLS: tlsConfig, - Addr: []string{fmt.Sprintf("%s:%d", settings.Server, settings.Port)}, + Addr: []string{fmt.Sprintf("%s:%d", settings.Host, settings.Port)}, Auth: clickhouse.Auth{ Username: settings.Username, Password: settings.Password, diff --git a/pkg/plugin/errors.go b/pkg/plugin/errors.go index 0053a5bb..98b1dcd9 100644 --- a/pkg/plugin/errors.go +++ b/pkg/plugin/errors.go @@ -4,7 +4,7 @@ import "github.com/pkg/errors" var ( ErrorMessageInvalidJSON = errors.New("could not parse json") - ErrorMessageInvalidServerName = errors.New("invalid server name. Either empty or not set") + ErrorMessageInvalidHost = errors.New("invalid server host. Either empty or not set") ErrorMessageInvalidPort = errors.New("invalid port") ErrorMessageInvalidUserName = errors.New("username is either empty or not set") ErrorMessageInvalidPassword = errors.New("password is either empty or not set") diff --git a/pkg/plugin/settings.go b/pkg/plugin/settings.go index a5d35195..711939a8 100644 --- a/pkg/plugin/settings.go +++ b/pkg/plugin/settings.go @@ -13,23 +13,28 @@ import ( // Settings - data loaded from grafana settings database type Settings struct { - Server string `json:"server,omitempty"` - Port int64 `json:"port,omitempty"` - Username string `json:"username,omitempty"` - DefaultDatabase string `json:"defaultDatabase,omitempty"` - InsecureSkipVerify bool `json:"tlsSkipVerify,omitempty"` - TlsClientAuth bool `json:"tlsAuth,omitempty"` - TlsAuthWithCACert bool `json:"tlsAuthWithCACert,omitempty"` - Password string `json:"-,omitempty"` - TlsCACert string + Host string `json:"host,omitempty"` + Port int64 `json:"port,omitempty"` + Protocol string `json:"protocol"` + Secure bool `json:"secure,omitempty"` + + InsecureSkipVerify bool `json:"tlsSkipVerify,omitempty"` + TlsClientAuth bool `json:"tlsAuth,omitempty"` + TlsAuthWithCACert bool `json:"tlsAuthWithCACert,omitempty"` TlsClientCert string + TlsCACert string TlsClientKey string - Secure bool `json:"secure,omitempty"` - Timeout string `json:"timeout,omitempty"` - QueryTimeout string `json:"queryTimeout,omitempty"` - Protocol string `json:"protocol"` - CustomSettings []CustomSetting `json:"customSettings"` - ProxyOptions *proxy.Options + + Username string `json:"username,omitempty"` + Password string `json:"-,omitempty"` + + DefaultDatabase string `json:"defaultDatabase,omitempty"` + + DialTimeout string `json:"dialTimeout,omitempty"` + QueryTimeout string `json:"queryTimeout,omitempty"` + + CustomSettings []CustomSetting `json:"customSettings"` + ProxyOptions *proxy.Options } type CustomSetting struct { @@ -38,8 +43,8 @@ type CustomSetting struct { } func (settings *Settings) isValid() (err error) { - if settings.Server == "" { - return ErrorMessageInvalidServerName + if settings.Host == "" { + return ErrorMessageInvalidHost } if settings.Port == 0 { return ErrorMessageInvalidPort @@ -54,8 +59,8 @@ func LoadSettings(config backend.DataSourceInstanceSettings) (settings Settings, return settings, fmt.Errorf("%s: %w", err.Error(), ErrorMessageInvalidJSON) } - if jsonData["server"] != nil { - settings.Server = jsonData["server"].(string) + if jsonData["host"] != nil { + settings.Host = jsonData["host"].(string) } if jsonData["port"] != nil { if port, ok := jsonData["port"].(string); ok { @@ -67,11 +72,18 @@ func LoadSettings(config backend.DataSourceInstanceSettings) (settings Settings, settings.Port = int64(jsonData["port"].(float64)) } } - if jsonData["username"] != nil { - settings.Username = jsonData["username"].(string) + if jsonData["protocol"] != nil { + settings.Protocol = jsonData["protocol"].(string) } - if jsonData["defaultDatabase"] != nil { - settings.DefaultDatabase = jsonData["defaultDatabase"].(string) + if jsonData["secure"] != nil { + if secure, ok := jsonData["secure"].(string); ok { + settings.Secure, err = strconv.ParseBool(secure) + if err != nil { + return settings, fmt.Errorf("could not parse secure value: %w", err) + } + } else { + settings.Secure = jsonData["secure"].(bool) + } } if jsonData["tlsSkipVerify"] != nil { @@ -104,19 +116,16 @@ func LoadSettings(config backend.DataSourceInstanceSettings) (settings Settings, settings.TlsAuthWithCACert = jsonData["tlsAuthWithCACert"].(bool) } } - if jsonData["secure"] != nil { - if secure, ok := jsonData["secure"].(string); ok { - settings.Secure, err = strconv.ParseBool(secure) - if err != nil { - return settings, fmt.Errorf("could not parse secure value: %w", err) - } - } else { - settings.Secure = jsonData["secure"].(bool) - } + + if jsonData["username"] != nil { + settings.Username = jsonData["username"].(string) + } + if jsonData["defaultDatabase"] != nil { + settings.DefaultDatabase = jsonData["defaultDatabase"].(string) } - if jsonData["timeout"] != nil { - settings.Timeout = jsonData["timeout"].(string) + if jsonData["dialTimeout"] != nil { + settings.DialTimeout = jsonData["dialTimeout"].(string) } if jsonData["queryTimeout"] != nil { if val, ok := jsonData["queryTimeout"].(string); ok { @@ -126,9 +135,6 @@ func LoadSettings(config backend.DataSourceInstanceSettings) (settings Settings, settings.QueryTimeout = fmt.Sprintf("%d", int64(val)) } } - if jsonData["protocol"] != nil { - settings.Protocol = jsonData["protocol"].(string) - } if jsonData["customSettings"] != nil { customSettingsRaw := jsonData["customSettings"].([]interface{}) customSettings := make([]CustomSetting, len(customSettingsRaw)) @@ -144,8 +150,8 @@ func LoadSettings(config backend.DataSourceInstanceSettings) (settings Settings, settings.CustomSettings = customSettings } - if strings.TrimSpace(settings.Timeout) == "" { - settings.Timeout = "10" + if strings.TrimSpace(settings.DialTimeout) == "" { + settings.DialTimeout = "10" } if strings.TrimSpace(settings.QueryTimeout) == "" { settings.QueryTimeout = "60" @@ -170,9 +176,9 @@ func LoadSettings(config backend.DataSourceInstanceSettings) (settings Settings, proxyOpts, err := config.ProxyOptions() if err == nil && proxyOpts != nil { // the sdk expects the timeout to not be a string - timeout, err := strconv.ParseFloat(settings.Timeout, 64) + timeout, err := strconv.ParseFloat(settings.DialTimeout, 64) if err == nil { - proxyOpts.Timeouts.Timeout = (time.Duration(timeout) * time.Second) + proxyOpts.Timeouts.Timeout = time.Duration(timeout) * time.Second } settings.ProxyOptions = proxyOpts diff --git a/pkg/plugin/settings_test.go b/pkg/plugin/settings_test.go index 543af4dd..c6a46949 100644 --- a/pkg/plugin/settings_test.go +++ b/pkg/plugin/settings_test.go @@ -33,7 +33,7 @@ func TestLoadSettings(t *testing.T) { }, }, wantSettings: Settings{ - Server: "foo", + Host: "foo", Port: 443, Username: "baz", DefaultDatabase: "example", @@ -44,7 +44,7 @@ func TestLoadSettings(t *testing.T) { TlsCACert: "caCert", TlsClientCert: "clientCert", TlsClientKey: "clientKey", - Timeout: "10", + DialTimeout: "10", QueryTimeout: "60", ProxyOptions: &proxy.Options{ Enabled: true, @@ -69,12 +69,12 @@ func TestLoadSettings(t *testing.T) { }, }, wantSettings: Settings{ - Server: "test", + Host: "test", Port: 443, InsecureSkipVerify: true, TlsClientAuth: true, TlsAuthWithCACert: true, - Timeout: "10", + DialTimeout: "10", QueryTimeout: "60", ProxyOptions: nil, }, diff --git a/src/__mocks__/ConfigEditor.ts b/src/__mocks__/ConfigEditor.ts index b490c611..0dbd8793 100644 --- a/src/__mocks__/ConfigEditor.ts +++ b/src/__mocks__/ConfigEditor.ts @@ -1,9 +1,10 @@ import * as fs from 'fs'; -import { Props } from '../views/CHConfigEditor'; +import { ConfigEditorProps } from 'views/CHConfigEditor'; +import { CHConfig } from 'types/config'; const pluginJson = JSON.parse(fs.readFileSync('./src/plugin.json', 'utf-8')); -export const mockConfigEditorProps = (): Props => ({ +export const mockConfigEditorProps = (overrides?: Partial): ConfigEditorProps => ({ options: { ...pluginJson, jsonData: { @@ -11,6 +12,7 @@ export const mockConfigEditorProps = (): Props => ({ port: 443, username: 'user', protocol: 'native', + ...overrides, }, }, onOptionsChange: jest.fn(), diff --git a/src/__mocks__/datasource.ts b/src/__mocks__/datasource.ts index c29cadc9..63d1c813 100644 --- a/src/__mocks__/datasource.ts +++ b/src/__mocks__/datasource.ts @@ -10,7 +10,7 @@ export const mockDatasource = new Datasource({ type: 'grafana-clickhouse-datasource', name: 'ClickHouse', jsonData: { - server: 'foo.com', + host: 'foo.com', port: 443, username: 'user', defaultDatabase: 'foo', diff --git a/src/components/configEditor/DefaultDatabaseTableConfig.tsx b/src/components/configEditor/DefaultDatabaseTableConfig.tsx new file mode 100644 index 00000000..07506e48 --- /dev/null +++ b/src/components/configEditor/DefaultDatabaseTableConfig.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { ConfigSection } from '@grafana/experimental'; +import { Input, Field } from '@grafana/ui'; + +interface DefaultDatabaseTableConfigProps { + defaultDatabase?: string; + defaultTable?: string; + onDefaultDatabaseChange: (e: any) => void; + onDefaultTableChange: (e: any) => void; +} + +export const DefaultDatabaseTableConfig = (props: DefaultDatabaseTableConfigProps) => { + const { defaultDatabase, defaultTable, onDefaultDatabaseChange, onDefaultTableChange } = props; + return ( + + + + + + + + + ); +} diff --git a/src/components/configEditor/LabeledInput.tsx b/src/components/configEditor/LabeledInput.tsx new file mode 100644 index 00000000..eeaf471a --- /dev/null +++ b/src/components/configEditor/LabeledInput.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Input, InlineFormLabel } from '@grafana/ui'; + +interface LabeledInputProps { + label: string; + tooltip?: string; + placeholder?: string; + disabled?: boolean; + value: string; + onChange: (value: string) => void; +} + +export function LabeledInput(props: LabeledInputProps) { + const { label, tooltip, placeholder, disabled, value, onChange } = props; + + return ( +
+ + {label} + + onChange(e.currentTarget.value)} + placeholder={placeholder || label.toLowerCase().replace(/ /g, '_')} + /> +
+ ) +} diff --git a/src/components/configEditor/LogsConfig.tsx b/src/components/configEditor/LogsConfig.tsx new file mode 100644 index 00000000..38364645 --- /dev/null +++ b/src/components/configEditor/LogsConfig.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import { ConfigSection, ConfigSubSection } from '@grafana/experimental'; +import { Input, Field } from '@grafana/ui'; +import { OtelVersionSelect } from 'components/queryBuilder/OtelVersionSelect'; +import { ColumnHint } from 'types/queryBuilder'; +import { versions as otelVersions } from 'otel'; +import { LabeledInput } from './LabeledInput'; + +interface LogsConfigProps { + defaultDatabase?: string; + defaultTable?: string; + onDefaultDatabaseChange: (v: any) => void; + onDefaultTableChange: (v: any) => void; + + otelEnabled?: boolean; + otelVersion?: string; + onOtelEnabledChange: (v: any) => void; + onOtelVersionChange: (v: any) => void; + + timeColumn?: string; + levelColumn?: string; + messageColumn?: string; + onTimeColumnChange: (v: any) => void; + onLevelColumnChange: (v: any) => void; + onMessageColumnChange: (v: any) => void; +} + +export const LogsConfig = (props: LogsConfigProps) => { + const { defaultDatabase, defaultTable, onDefaultDatabaseChange, onDefaultTableChange } = props; + const { otelEnabled, otelVersion, onOtelEnabledChange, onOtelVersionChange } = props; + const { onTimeColumnChange, onLevelColumnChange, onMessageColumnChange } = props; + let { timeColumn, levelColumn, messageColumn } = props; + + const otelConfig = otelVersions.find(v => v.version === otelVersion); + if (otelEnabled && otelConfig) { + timeColumn = otelConfig.logColumnMap.get(ColumnHint.Time); + levelColumn = otelConfig.logColumnMap.get(ColumnHint.LogLevel); + messageColumn = otelConfig.logColumnMap.get(ColumnHint.LogMessage); + } + + return ( + + + onDefaultDatabaseChange(e.currentTarget.value)} + label="Default logs database" + aria-label="Default logs database" + placeholder="default" + /> + + + onDefaultTableChange(e.currentTarget.value)} + label="Default logs table" + aria-label="Default logs table" + placeholder="logs" + /> + + + + + + + + + ); +} diff --git a/src/components/configEditor/QuerySettingsConfig.tsx b/src/components/configEditor/QuerySettingsConfig.tsx new file mode 100644 index 00000000..16e9bf21 --- /dev/null +++ b/src/components/configEditor/QuerySettingsConfig.tsx @@ -0,0 +1,55 @@ +import React, { } from 'react'; +import { Switch, Input, Field } from '@grafana/ui'; +import { Components } from 'selectors'; +import { ConfigSection } from '@grafana/experimental'; + +interface QuerySettingsConfigProps { + dialTimeout?: string; + queryTimeout?: string; + validateSql?: boolean; + onDialTimeoutChange: (e: any) => void; + onQueryTimeoutChange: (e: any) => void; + onValidateSqlChange: (e: any) => void; +} + +export const QuerySettingsConfig = (props: QuerySettingsConfigProps) => { + const { dialTimeout, queryTimeout, validateSql, onDialTimeoutChange, onQueryTimeoutChange, onValidateSqlChange } = props; + return ( + + + + + + + + + + + + ); +} diff --git a/src/components/configEditor/TracesConfig.tsx b/src/components/configEditor/TracesConfig.tsx new file mode 100644 index 00000000..3cf2da87 --- /dev/null +++ b/src/components/configEditor/TracesConfig.tsx @@ -0,0 +1,187 @@ + +import React from 'react'; +import { ConfigSection, ConfigSubSection } from '@grafana/experimental'; +import { Input, Field } from '@grafana/ui'; +import { OtelVersionSelect } from 'components/queryBuilder/OtelVersionSelect'; +import { ColumnHint, TimeUnit } from 'types/queryBuilder'; +import { versions as otelVersions } from 'otel'; +import { LabeledInput } from './LabeledInput'; +import { DurationUnitSelect } from 'components/queryBuilder/DurationUnitSelect'; + +interface TraceConfigProps { + defaultDatabase?: string; + defaultTable?: string; + onDefaultDatabaseChange: (v: any) => void; + onDefaultTableChange: (v: any) => void; + + otelEnabled?: boolean; + otelVersion?: string; + onOtelEnabledChange: (v: any) => void; + onOtelVersionChange: (v: any) => void; + + traceIdColumn?: string; + spanIdColumn?: string; + operationNameColumn?: string; + parentSpanIdColumn?: string; + serviceNameColumn?: string; + durationColumn?: string; + durationUnit?: string; + startTimeColumn?: string; + tagsColumn?: string; + serviceTagsColumn?: string; + onTraceIdColumnChange: (v: any) => void; + onSpanIdColumnChange: (v: any) => void; + onOperationNameColumnChange: (v: any) => void; + onParentSpanIdColumnChange: (v: any) => void; + onServiceNameColumnChange: (v: any) => void; + onDurationColumnChange: (v: any) => void; + onDurationUnitChange: (v: any) => void; + onStartTimeColumnChange: (v: any) => void; + onTagsColumnChange: (v: any) => void; + onServiceTagsColumnChange: (v: any) => void; +} + +export const TracesConfig = (props: TraceConfigProps) => { + const { defaultDatabase, defaultTable, onDefaultDatabaseChange, onDefaultTableChange } = props; + const { otelEnabled, otelVersion, onOtelEnabledChange, onOtelVersionChange } = props; + const { + onTraceIdColumnChange, onSpanIdColumnChange, onOperationNameColumnChange, onParentSpanIdColumnChange, + onServiceNameColumnChange, onDurationColumnChange, onDurationUnitChange, onStartTimeColumnChange, + onTagsColumnChange, onServiceTagsColumnChange, + } = props; + let { + traceIdColumn, spanIdColumn, operationNameColumn, parentSpanIdColumn, serviceNameColumn, + durationColumn, durationUnit, startTimeColumn, tagsColumn, serviceTagsColumn + } = props; + + const otelConfig = otelVersions.find(v => v.version === otelVersion); + if (otelEnabled && otelConfig) { + startTimeColumn = otelConfig.traceColumnMap.get(ColumnHint.Time); + traceIdColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceId); + spanIdColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceSpanId); + parentSpanIdColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceParentSpanId); + serviceNameColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceServiceName); + operationNameColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceOperationName); + durationColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceDurationTime); + tagsColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceTags); + serviceTagsColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceServiceTags); + durationUnit = otelConfig.traceDurationUnit.toString(); + } + + return ( + + + onDefaultDatabaseChange(e.currentTarget.value)} + label="Default trace database" + aria-label="Default trace database" + placeholder="default" + /> + + + onDefaultTableChange(e.currentTarget.value)} + label="Default trace table" + aria-label="Default trace table" + placeholder="traces" + /> + + + + + + + + + + + + + + + + ); +} diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index 2985be97..deb0f233 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -29,6 +29,7 @@ import { OrderByDirection, QueryBuilderOptions, ColumnHint, + TimeUnit, } from 'types/queryBuilder'; import { AdHocFilter } from './adHocFilter'; import { cloneDeep, isEmpty, isString } from 'lodash'; @@ -42,6 +43,7 @@ import { } from './logs'; import { getSqlFromQueryBuilderOptions, getColumnByHint } from '../components/queryBuilder/utils'; import { generateSql } from './sqlGenerator'; +import { versions as otelVersions } from 'otel'; export class Datasource extends DataSourceWithBackend @@ -367,6 +369,96 @@ export class Datasource return this.settings.jsonData.defaultDatabase || 'default'; } + getDefaultTable(): string | undefined { + return this.settings.jsonData.defaultTable; + } + + getDefaultLogsDatabase(): string | undefined { + return this.settings.jsonData.logs?.defaultDatabase; + } + + getDefaultLogsTable(): string | undefined { + return this.settings.jsonData.logs?.defaultTable; + } + + getDefaultLogsColumns(): Map { + const result = new Map(); + const logsConfig = this.settings.jsonData.logs; + if (!logsConfig) { + return result; + } + + const otelEnabled = logsConfig.otelEnabled; + const otelVersion = logsConfig.otelVersion; + + const otelConfig = otelVersions.find(v => v.version === otelVersion); + if (otelEnabled && otelConfig) { + return otelConfig.logColumnMap; + } + + logsConfig.timeColumn && result.set(ColumnHint.Time, logsConfig.timeColumn); + logsConfig.levelColumn && result.set(ColumnHint.LogLevel, logsConfig.levelColumn); + logsConfig.messageColumn && result.set(ColumnHint.LogMessage, logsConfig.messageColumn); + + return result; + } + + /** + * Get configured OTEL version for logs. Returns undefined when versioning is disabled/unset. + */ + getLogsOtelVersion(): string | undefined { + const logConfig = this.settings.jsonData.logs; + return logConfig?.otelEnabled ? (logConfig.otelVersion || undefined) : undefined; + } + + getDefaultTraceDatabase(): string | undefined { + return this.settings.jsonData.traces?.defaultDatabase; + } + + getDefaultTraceTable(): string | undefined { + return this.settings.jsonData.traces?.defaultTable; + } + + getDefaultTraceColumns(): Map { + const result = new Map(); + const traceConfig = this.settings.jsonData.traces; + if (!traceConfig) { + return result; + } + + const otelEnabled = traceConfig.otelEnabled; + const otelVersion = traceConfig.otelVersion; + + const otelConfig = otelVersions.find(v => v.version === otelVersion); + if (otelEnabled && otelConfig) { + return otelConfig.traceColumnMap; + } + + traceConfig.traceIdColumn && result.set(ColumnHint.TraceId, traceConfig.traceIdColumn); + traceConfig.spanIdColumn && result.set(ColumnHint.TraceSpanId, traceConfig.spanIdColumn); + traceConfig.operationNameColumn && result.set(ColumnHint.TraceOperationName, traceConfig.operationNameColumn); + traceConfig.parentSpanIdColumn && result.set(ColumnHint.TraceParentSpanId, traceConfig.parentSpanIdColumn); + traceConfig.serviceNameColumn && result.set(ColumnHint.TraceServiceName, traceConfig.serviceNameColumn); + traceConfig.durationColumn && result.set(ColumnHint.TraceDurationTime, traceConfig.durationColumn); + traceConfig.startTimeColumn && result.set(ColumnHint.Time, traceConfig.startTimeColumn); + traceConfig.tagsColumn && result.set(ColumnHint.TraceTags, traceConfig.tagsColumn); + traceConfig.serviceTagsColumn && result.set(ColumnHint.TraceServiceTags, traceConfig.serviceTagsColumn); + + return result; + } + + /** + * Get configured OTEL version for traces. Returns undefined when versioning is disabled/unset. + */ + getTraceOtelVersion(): string | undefined { + const traceConfig = this.settings.jsonData.traces; + return traceConfig?.otelEnabled ? (traceConfig.otelVersion || undefined) : undefined; + } + + getDefaultTraceDurationUnit(): TimeUnit { + return this.settings.jsonData.traces?.durationUnit as TimeUnit || TimeUnit.Nanoseconds; + } + async fetchDatabases(): Promise { return this.fetchData('SHOW DATABASES'); } diff --git a/src/selectors.ts b/src/selectors.ts index bae3b3b3..87f11016 100644 --- a/src/selectors.ts +++ b/src/selectors.ts @@ -54,7 +54,7 @@ export const Components = { placeholder: 'Default database', tooltip: 'Default database to be used. Can be empty.', }, - Timeout: { + DialTimeout: { label: 'Dial Timeout (seconds)', placeholder: '10', tooltip: 'Timeout in seconds for connection', @@ -68,9 +68,9 @@ export const Components = { label: 'Secure Connection', tooltip: 'Toggle on if the connection is secure', }, - Validate: { + ValidateSql: { label: 'Validate SQL', - tooltip: 'Validate Sql in the editor.', + tooltip: 'Validate SQL in the editor.', }, SecureSocksProxy: { label: 'Enable Secure Socks Proxy', diff --git a/src/types/config.ts b/src/types/config.ts index 992a4bdd..5e425919 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1,18 +1,27 @@ import { DataSourceJsonData } from '@grafana/data'; export interface CHConfig extends DataSourceJsonData { - username: string; - server: string; - protocol: Protocol; + host: string; port: number; - defaultDatabase?: string; + protocol: Protocol; + secure?: boolean; + tlsSkipVerify?: boolean; tlsAuth?: boolean; tlsAuthWithCACert?: boolean; - secure?: boolean; - validate?: boolean; - timeout?: string; + + username: string; + + defaultDatabase?: string; + defaultTable?: string; + + dialTimeout?: string; queryTimeout?: string; + validateSql?: boolean; + + logs?: CHLogsConfig; + traces?: CHTracesConfig; + customSettings?: CHCustomSetting[]; enableSecureSocksProxy?: boolean; } @@ -29,6 +38,37 @@ export interface CHSecureConfig { tlsClientKey?: string; } +export interface CHLogsConfig { + defaultDatabase?: string; + defaultTable?: string; + + otelEnabled?: boolean; + otelVersion?: string; + + timeColumn?: string; + levelColumn?: string; + messageColumn?: string; +} + +export interface CHTracesConfig { + defaultDatabase?: string; + defaultTable?: string; + + otelEnabled?: boolean; + otelVersion?: string; + + traceIdColumn?: string; + spanIdColumn?: string; + operationNameColumn?: string; + parentSpanIdColumn?: string; + serviceNameColumn?: string; + durationColumn?: string; + durationUnit?: string; + startTimeColumn?: string; + tagsColumn?: string; + serviceTagsColumn?: string; +} + export enum Protocol { Native = 'native', Http = 'http', diff --git a/src/utils/version.test.ts b/src/utils/version.test.ts new file mode 100644 index 00000000..52691b3b --- /dev/null +++ b/src/utils/version.test.ts @@ -0,0 +1,54 @@ +import { SemVersion, isVersionGtOrEq } from './version'; + +describe('SemVersion', () => { + let version = '1.0.0-alpha.1'; + + describe('parsing', () => { + it('should parse version properly', () => { + const semver = new SemVersion(version); + expect(semver.major).toBe(1); + expect(semver.minor).toBe(0); + expect(semver.patch).toBe(0); + expect(semver.meta).toBe('alpha.1'); + }); + }); + + describe('comparing', () => { + beforeEach(() => { + version = '3.4.5'; + }); + + it('should detect greater version properly', () => { + const semver = new SemVersion(version); + const cases = [ + { value: '3.4.5', expected: true }, + { value: '3.4.4', expected: true }, + { value: '3.4.6', expected: false }, + { value: '4', expected: false }, + { value: '3.5', expected: false }, + ]; + cases.forEach((testCase) => { + expect(semver.isGtOrEq(testCase.value)).toBe(testCase.expected); + }); + }); + }); + + describe('isVersionGtOrEq', () => { + it('should compare versions properly (a >= b)', () => { + const cases = [ + { values: ['3.4.5', '3.4.5'], expected: true }, + { values: ['3.4.5', '3.4.4'], expected: true }, + { values: ['3.4.5', '3.4.6'], expected: false }, + { values: ['3.4', '3.4.0'], expected: true }, + { values: ['3', '3.0.0'], expected: true }, + { values: ['3.1.1-beta1', '3.1'], expected: true }, + { values: ['3.4.5', '4'], expected: false }, + { values: ['3.4.5', '3.5'], expected: false }, + { values: ['6.0.0', '5.2.0'], expected: true }, + ]; + cases.forEach((testCase) => { + expect(isVersionGtOrEq(testCase.values[0], testCase.values[1])).toBe(testCase.expected); + }); + }); + }); +}); diff --git a/src/utils/version.ts b/src/utils/version.ts new file mode 100644 index 00000000..f7e6fccb --- /dev/null +++ b/src/utils/version.ts @@ -0,0 +1,52 @@ +import { isNumber } from 'lodash'; + +const versionPattern = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z\.]+))?/; + +export class SemVersion { + major: number; + minor: number; + patch: number; + meta: string; + + constructor(version: string) { + this.major = 0; + this.minor = 0; + this.patch = 0; + this.meta = ''; + + const match = versionPattern.exec(version); + if (match) { + this.major = Number(match[1]); + this.minor = Number(match[2] || 0); + this.patch = Number(match[3] || 0); + this.meta = match[4]; + } + } + + isGtOrEq(version: string): boolean { + const compared = new SemVersion(version); + + for (let i = 0; i < this.comparable.length; ++i) { + if (this.comparable[i] > compared.comparable[i]) { + return true; + } + if (this.comparable[i] < compared.comparable[i]) { + return false; + } + } + return true; + } + + isValid(): boolean { + return isNumber(this.major); + } + + get comparable() { + return [this.major, this.minor, this.patch]; + } +} + +export function isVersionGtOrEq(a: string, b: string): boolean { + const aSemver = new SemVersion(a); + return aSemver.isGtOrEq(b); +} diff --git a/src/views/CHConfigEditor.test.tsx b/src/views/CHConfigEditor.test.tsx index 595ddab3..c2f9f93b 100644 --- a/src/views/CHConfigEditor.test.tsx +++ b/src/views/CHConfigEditor.test.tsx @@ -6,6 +6,14 @@ import { Components } from 'selectors'; import '@testing-library/jest-dom'; import { Protocol } from 'types/config'; +jest.mock('@grafana/runtime', () => { + const original = jest.requireActual('@grafana/runtime'); + return { + ...original, + config: { buildInfo: { version: '10.0.0' }, featureToggles: { secureSocksDSProxyEnabled: true } }, + }; +}); + describe('ConfigEditor', () => { it('new editor', () => { render(); @@ -13,9 +21,6 @@ describe('ConfigEditor', () => { expect(screen.getByPlaceholderText(Components.ConfigEditor.ServerPort.placeholder('false'))).toBeInTheDocument(); expect(screen.getByPlaceholderText(Components.ConfigEditor.Username.placeholder)).toBeInTheDocument(); expect(screen.getByPlaceholderText(Components.ConfigEditor.Password.placeholder)).toBeInTheDocument(); - expect(screen.getByPlaceholderText(Components.ConfigEditor.DefaultDatabase.placeholder)).toBeInTheDocument(); - expect(screen.getByPlaceholderText(Components.ConfigEditor.Timeout.placeholder)).toBeInTheDocument(); - expect(screen.getByPlaceholderText(Components.ConfigEditor.QueryTimeout.placeholder)).toBeInTheDocument(); }); it('with password', async () => { render( @@ -33,9 +38,6 @@ describe('ConfigEditor', () => { expect(screen.getByPlaceholderText(Components.ConfigEditor.Username.placeholder)).toBeInTheDocument(); const a = screen.getByText('Reset'); expect(a).toBeInTheDocument(); - expect(screen.getByPlaceholderText(Components.ConfigEditor.DefaultDatabase.placeholder)).toBeInTheDocument(); - expect(screen.getByPlaceholderText(Components.ConfigEditor.Timeout.placeholder)).toBeInTheDocument(); - expect(screen.getByPlaceholderText(Components.ConfigEditor.QueryTimeout.placeholder)).toBeInTheDocument(); }); it('with secure connection', async () => { render( @@ -96,4 +98,22 @@ describe('ConfigEditor', () => { expect(screen.getByPlaceholderText(Components.ConfigEditor.TLSClientCert.placeholder)).toBeInTheDocument(); expect(screen.getByPlaceholderText(Components.ConfigEditor.TLSClientKey.placeholder)).toBeInTheDocument(); }); + it('with additional properties', async () => { + const jsonDataOverrides = { + defaultDatabase: 'default', + queryTimeout: '100', + timeout: '100', + validate: true, + enableSecureSocksProxy: true, + customSettings: [{ setting: 'test-setting', value: 'test-value' }], + }; + render(); + expect(screen.getByPlaceholderText(Components.ConfigEditor.DefaultDatabase.placeholder)).toBeInTheDocument(); + expect(screen.getByPlaceholderText(Components.ConfigEditor.QueryTimeout.placeholder)).toBeInTheDocument(); + expect(screen.getByPlaceholderText(Components.ConfigEditor.DialTimeout.placeholder)).toBeInTheDocument(); + expect(screen.getByText(Components.ConfigEditor.ValidateSql.label)).toBeInTheDocument(); + expect(screen.getByText(Components.ConfigEditor.SecureSocksProxy.label)).toBeInTheDocument(); + expect(screen.getByDisplayValue(jsonDataOverrides.customSettings[0].setting)).toBeInTheDocument(); + expect(screen.getByDisplayValue(jsonDataOverrides.customSettings[0].value)).toBeInTheDocument(); + }); }); diff --git a/src/views/CHConfigEditor.tsx b/src/views/CHConfigEditor.tsx index 355e3a44..3bf24509 100644 --- a/src/views/CHConfigEditor.tsx +++ b/src/views/CHConfigEditor.tsx @@ -4,28 +4,23 @@ import { onUpdateDatasourceJsonDataOption, onUpdateDatasourceSecureJsonDataOption, } from '@grafana/data'; -import { - Button, - InlineFormLabel, - LegacyForms, - RadioButtonGroup, - useTheme, - Switch, - InlineFieldRow, - InlineField, - Input, -} from '@grafana/ui'; -import { CertificationKey } from 'components/ui/CertificationKey'; -import { Components } from 'selectors'; -import { config } from '@grafana/runtime'; -import { CHConfig, CHCustomSetting, CHSecureConfig, Protocol } from 'types/config'; +import { RadioButtonGroup, Switch, Input, SecretInput, Button, Field, HorizontalGroup } from '@grafana/ui'; +import { CertificationKey } from '../components/ui/CertificationKey'; +import { Components } from './../selectors'; +import { CHConfig, CHCustomSetting, CHSecureConfig, CHLogsConfig, Protocol, CHTracesConfig } from 'types/config'; import { gte } from 'semver'; +import { ConfigSection, ConfigSubSection, DataSourceDescription } from '@grafana/experimental'; +import { config } from '@grafana/runtime'; +import { Divider } from 'components/Divider'; +import { TimeUnit } from 'types/queryBuilder'; +import { DefaultDatabaseTableConfig } from 'components/configEditor/DefaultDatabaseTableConfig'; +import { QuerySettingsConfig } from 'components/configEditor/QuerySettingsConfig'; +import { LogsConfig } from 'components/configEditor/LogsConfig'; +import { TracesConfig } from 'components/configEditor/TracesConfig'; -export interface Props extends DataSourcePluginOptionsEditorProps {} +export interface ConfigEditorProps extends DataSourcePluginOptionsEditorProps {} -export const ConfigEditor: React.FC = (props) => { - const theme = useTheme(); - const { FormField, SecretFormField } = LegacyForms; +export const ConfigEditor: React.FC = (props) => { const { options, onOptionsChange } = props; const { jsonData, secureJsonFields } = options; const secureJsonData = (options.secureJsonData || {}) as CHSecureConfig; @@ -36,12 +31,6 @@ export const ConfigEditor: React.FC = (props) => { { label: 'Native', value: Protocol.Native }, { label: 'HTTP', value: Protocol.Http }, ]; - const switchContainerStyle: React.CSSProperties = { - padding: `0 ${theme.spacing.sm}`, - height: `${theme.spacing.formInputHeight}px`, - display: 'flex', - alignItems: 'center', - }; const onPortChange = (port: string) => { onOptionsChange({ ...options, @@ -63,7 +52,10 @@ export const ConfigEditor: React.FC = (props) => { }, }); }; - const onSwitchToggle = (key: keyof Pick, value: boolean) => { + const onSwitchToggle = ( + key: keyof Pick, + value: boolean + ) => { onOptionsChange({ ...options, jsonData: { @@ -127,136 +119,140 @@ export const ConfigEditor: React.FC = (props) => { }, }); }; + const onLogsConfigChange = (key: keyof CHLogsConfig, value: string) => { + onOptionsChange({ + ...options, + jsonData: { + ...options.jsonData, + logs: { + ...options.jsonData.logs, + [key]: value + } + } + }); + }; + const onTracesConfigChange = (key: keyof CHTracesConfig, value: string) => { + onOptionsChange({ + ...options, + jsonData: { + ...options.jsonData, + traces: { + ...options.jsonData.traces, + durationUnit: options.jsonData.traces?.durationUnit || TimeUnit.Nanoseconds, + [key]: value + } + } + }); + }; const [customSettings, setCustomSettings] = useState(jsonData.customSettings || []); + const hasAdditionalSettings = !!( + options.jsonData.defaultDatabase || + options.jsonData.defaultTable || + options.jsonData.dialTimeout || + options.jsonData.queryTimeout || + options.jsonData.validateSql || + options.jsonData.enableSecureSocksProxy || + options.jsonData.customSettings || + options.jsonData.logs || + options.jsonData.traces + ); + return ( <> -
-

Server

-
-
- + + + + -
-
- + + onPortChange(e.currentTarget.value)} label={Components.ConfigEditor.ServerPort.label} aria-label={Components.ConfigEditor.ServerPort.label} placeholder={Components.ConfigEditor.ServerPort.placeholder(jsonData.secure?.toString() || 'false')} - tooltip={Components.ConfigEditor.ServerPort.tooltip} /> -
-
- - {Components.ConfigEditor.Protocol.label} - + + options={protocolOptions} disabledOptions={[]} value={jsonData.protocol || Protocol.Native} onChange={(e) => onProtocolToggle(e!)} /> -
-
- - {Components.ConfigEditor.Secure.label} - -
- onSwitchToggle('secure', e.currentTarget.checked)} - /> -
-
-
-
-

Credentials

-
-
- + + onSwitchToggle('secure', e.currentTarget.checked)} /> -
-
- + + + + + + onTLSSettingsChange('tlsSkipVerify', e.currentTarget.checked)} /> -
-
-
-

TLS / SSL Settings

-
-
- - {Components.ConfigEditor.TLSSkipVerify.label} - -
- onTLSSettingsChange('tlsSkipVerify', e.currentTarget.checked)} - /> -
-
-
- - {Components.ConfigEditor.TLSClientAuth.label} - -
- onTLSSettingsChange('tlsAuth', e.currentTarget.checked)} - /> -
- - {Components.ConfigEditor.TLSAuthWithCACert.label} - -
- onTLSSettingsChange('tlsAuthWithCACert', e.currentTarget.checked)} - /> -
-
+ + + onTLSSettingsChange('tlsAuth', e.currentTarget.checked)} + /> + + + onTLSSettingsChange('tlsAuthWithCACert', e.currentTarget.checked)} + /> + {jsonData.tlsAuthWithCACert && ( = (props) => { /> )} -
-
-

Additional Properties

-
-
- -
-
- + + + + + -
-
- + + -
-
- - {Components.ConfigEditor.Validate.label} - -
+ + + + + + + + + + onSwitchToggle('validateSql', e.currentTarget.checked)} + /> + + + onLogsConfigChange('defaultDatabase', db)} + onDefaultTableChange={table => onLogsConfigChange('defaultTable', table)} + + otelEnabled={jsonData.logs?.otelEnabled} + otelVersion={jsonData.logs?.otelVersion} + onOtelEnabledChange={v => onLogsConfigChange('otelEnabled', v)} + onOtelVersionChange={v => onLogsConfigChange('otelVersion', v)} + + timeColumn={jsonData.logs?.timeColumn} + levelColumn={jsonData.logs?.levelColumn} + messageColumn={jsonData.logs?.messageColumn} + onTimeColumnChange={c => onLogsConfigChange('timeColumn', c)} + onLevelColumnChange={c => onLogsConfigChange('levelColumn', c)} + onMessageColumnChange={c => onLogsConfigChange('messageColumn', c)} + /> + + + onTracesConfigChange('defaultDatabase', db)} + onDefaultTableChange={table => onTracesConfigChange('defaultTable', table)} + + otelEnabled={jsonData.traces?.otelEnabled} + otelVersion={jsonData.traces?.otelVersion} + onOtelEnabledChange={v => onTracesConfigChange('otelEnabled', v)} + onOtelVersionChange={v => onTracesConfigChange('otelVersion', v)} + + traceIdColumn={jsonData.traces?.traceIdColumn} + spanIdColumn={jsonData.traces?.spanIdColumn} + operationNameColumn={jsonData.traces?.operationNameColumn} + parentSpanIdColumn={jsonData.traces?.parentSpanIdColumn} + serviceNameColumn={jsonData.traces?.serviceNameColumn} + durationColumn={jsonData.traces?.durationColumn} + durationUnit={jsonData.traces?.durationUnit} + startTimeColumn={jsonData.traces?.startTimeColumn} + tagsColumn={jsonData.traces?.tagsColumn} + serviceTagsColumn={jsonData.traces?.serviceTagsColumn} + onTraceIdColumnChange={c => onTracesConfigChange('traceIdColumn', c)} + onSpanIdColumnChange={c => onTracesConfigChange('spanIdColumn', c)} + onOperationNameColumnChange={c => onTracesConfigChange('operationNameColumn', c)} + onParentSpanIdColumnChange={c => onTracesConfigChange('parentSpanIdColumn', c)} + onServiceNameColumnChange={c => onTracesConfigChange('serviceNameColumn', c)} + onDurationColumnChange={c => onTracesConfigChange('durationColumn', c)} + onDurationUnitChange={c => onTracesConfigChange('durationUnit', c)} + onStartTimeColumnChange={c => onTracesConfigChange('startTimeColumn', c)} + onTagsColumnChange={c => onTracesConfigChange('tagsColumn', c)} + onServiceTagsColumnChange={c => onTracesConfigChange('serviceTagsColumn', c)} + /> + + + {config.featureToggles['secureSocksDSProxyEnabled'] && gte(config.buildInfo.version, '10.0.0') && ( + onSwitchToggle('validate', e.currentTarget.checked)} + value={jsonData.enableSecureSocksProxy || false} + onChange={(e) => onSwitchToggle('enableSecureSocksProxy', e.currentTarget.checked)} /> -
-
- {config.featureToggles['secureSocksDSProxyEnabled'] && gte(config.buildInfo.version, '10.0.0') && ( -
- - {Components.ConfigEditor.SecureSocksProxy.label} - -
- onSwitchToggle('enableSecureSocksProxy', e.currentTarget.checked)} - /> -
-
+ )} -
-
-

Custom Settings

-
- {customSettings.map(({ setting, value }, i) => { - return ( - - - ) => { - let newSettings = customSettings.concat(); - newSettings[i] = { setting: changeEvent.target.value, value }; - setCustomSettings(newSettings); - }} - onBlur={() => { - onCustomSettingsChange(customSettings); - }} - > - - - ) => { - let newSettings = customSettings.concat(); - newSettings[i] = { setting, value: changeEvent.target.value }; - setCustomSettings(newSettings); - }} - onBlur={() => { - onCustomSettingsChange(customSettings); - }} - > - - - ); - })} -
- -
+ + {customSettings.map(({ setting, value }, i) => { + return ( + + + ) => { + let newSettings = customSettings.concat(); + newSettings[i] = { setting: changeEvent.target.value, value }; + setCustomSettings(newSettings); + }} + onBlur={() => { + onCustomSettingsChange(customSettings); + }} + > + + + ) => { + let newSettings = customSettings.concat(); + newSettings[i] = { setting, value: changeEvent.target.value }; + setCustomSettings(newSettings); + }} + onBlur={() => { + onCustomSettingsChange(customSettings); + }} + > + + + ); + })} + + + ); }; From b7ee09324f147be28cb288e19fdbe5b30ed418ec Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 25 Oct 2023 08:44:06 -0400 Subject: [PATCH 30/95] Update query builder/sqlGenerator with new state/config changes --- src/components/Divider.tsx | 15 + src/components/SqlEditor.tsx | 2 +- src/components/queryBuilder/ColumnSelect.tsx | 31 +- src/components/queryBuilder/ColumnsEditor.tsx | 13 +- .../queryBuilder/DatabaseTableSelect.tsx | 16 +- .../queryBuilder/DurationUnitSelect.tsx | 41 +++ src/components/queryBuilder/FilterEditor.tsx | 4 +- .../queryBuilder/OtelVersionSelect.tsx | 27 +- src/components/queryBuilder/QueryBuilder.tsx | 16 +- .../queryBuilder/QueryTypeSwitcher.tsx | 8 +- .../queryBuilder/views/LogsQueryBuilder.tsx | 268 ++++++++++++------ .../queryBuilder/views/TableQueryBuilder.tsx | 95 +++---- .../views/TimeSeriesQueryBuilder.tsx | 119 ++++---- .../queryBuilder/views/TraceQueryBuilder.tsx | 249 ++++++++-------- src/data/sqlGenerator.test.ts | 2 +- src/data/sqlGenerator.ts | 13 +- src/data/utils.ts | 4 +- src/hooks/useBuilderOptionChanges.ts | 33 +++ src/hooks/useColumns.ts | 10 +- src/labels.ts | 2 +- src/otel.ts | 28 +- src/types/queryBuilder.ts | 11 +- src/views/CHQueryEditor.tsx | 31 +- 23 files changed, 661 insertions(+), 377 deletions(-) create mode 100644 src/components/Divider.tsx create mode 100644 src/components/queryBuilder/DurationUnitSelect.tsx create mode 100644 src/hooks/useBuilderOptionChanges.ts diff --git a/src/components/Divider.tsx b/src/components/Divider.tsx new file mode 100644 index 00000000..17513c6d --- /dev/null +++ b/src/components/Divider.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Divider as GrafanaDivider, useTheme2 } from '@grafana/ui'; +import { config } from '@grafana/runtime'; +import { isVersionGtOrEq } from 'utils/version'; + +export function Divider() { + const theme = useTheme2(); + return isVersionGtOrEq(config.buildInfo.version, '10.1.0') ? ( + + ) : ( +
+ ); +} diff --git a/src/components/SqlEditor.tsx b/src/components/SqlEditor.tsx index 52809888..0be4c324 100644 --- a/src/components/SqlEditor.tsx +++ b/src/components/SqlEditor.tsx @@ -115,7 +115,7 @@ export const SqlEditor = (props: SqlEditorProps) => { } }); editor.onKeyUp((e: any) => { - if (datasource.settings.jsonData.validate) { + if (datasource.settings.jsonData.validateSql) { const sql = editor.getValue(); validateSql(sql, editor.getModel(), me); } diff --git a/src/components/queryBuilder/ColumnSelect.tsx b/src/components/queryBuilder/ColumnSelect.tsx index 464647ce..3b6a322d 100644 --- a/src/components/queryBuilder/ColumnSelect.tsx +++ b/src/components/queryBuilder/ColumnSelect.tsx @@ -7,12 +7,13 @@ import { styles } from 'styles'; interface ColumnSelectProps { allColumns: readonly TableColumn[]; selectedColumn: SelectedColumn | undefined; - onColumnChange: (c: SelectedColumn) => void; + onColumnChange: (c: SelectedColumn | undefined) => void; columnFilterFn?: (c: TableColumn) => boolean; columnHint?: ColumnHint; label: string; tooltip: string; disabled?: boolean; + invalid?: boolean; wide?: boolean; inline?: boolean; } @@ -20,18 +21,33 @@ interface ColumnSelectProps { const defaultFilterFn = () => true; export const ColumnSelect = (props: ColumnSelectProps) => { - const { allColumns, selectedColumn, onColumnChange, columnFilterFn, columnHint, label, tooltip, disabled, wide, inline } = props; + const { allColumns, selectedColumn, onColumnChange, columnFilterFn, columnHint, label, tooltip, disabled, invalid, wide, inline } = props; const selectedColumnName = selectedColumn?.name; const columns: Array> = allColumns. filter(columnFilterFn || defaultFilterFn). map(c => ({ label: c.name, value: c.name })); - const onChange = (selected: SelectableValue) => { - const column = allColumns.find(c => c.name === selected.value)!; + // Select component WILL NOT display the value if it isn't present in the options. + let staleOption = false; + if (selectedColumn && !columns.find(c => c.value === selectedColumn.name)) { + columns.push({ label: selectedColumn.name, value: selectedColumn.name }); + staleOption = true; + } + + const onChange = (selected: SelectableValue) => { + if (!selected || !selected.value) { + onColumnChange(undefined); + return; + } + + const column = allColumns.find(c => c.name === selected!.value)!; + if (!column) { + return; + } + onColumnChange({ name: column.name, type: column.type, - custom: false, hint: columnHint }); } @@ -43,13 +59,16 @@ export const ColumnSelect = (props: ColumnSelectProps) => { {label} - + disabled={disabled} + invalid={invalid || staleOption} options={columns} value={selectedColumnName} + placeholder={selectedColumnName || undefined} onChange={onChange} width={wide ? 25 : 20} menuPlacement={'bottom'} + isClearable /> ); diff --git a/src/components/queryBuilder/ColumnsEditor.tsx b/src/components/queryBuilder/ColumnsEditor.tsx index baf8d731..66be0e88 100644 --- a/src/components/queryBuilder/ColumnsEditor.tsx +++ b/src/components/queryBuilder/ColumnsEditor.tsx @@ -11,6 +11,7 @@ interface ColumnsEditorProps { selectedColumns: SelectedColumn[]; onSelectedColumnsChange: (selectedColumns: SelectedColumn[]) => void; disabled?: boolean; + showAllOption?: boolean; } function getCustomColumns(columnNames: string[], allColumns: readonly TableColumn[]): Array> { @@ -20,11 +21,16 @@ function getCustomColumns(columnNames: string[], allColumns: readonly TableColum map(c => ({ label: c.name, value: c.name })); } +const allColumnName = '*'; + export const ColumnsEditor = (props: ColumnsEditorProps) => { - const { allColumns, selectedColumns, onSelectedColumnsChange, disabled } = props; + const { allColumns, selectedColumns, onSelectedColumnsChange, disabled, showAllOption } = props; const [customColumns, setCustomColumns] = useState>>([]); const [isOpen, setIsOpen] = useState(false); const allColumnNames = allColumns.map(c => ({ label: c.name, value: c.name })); + if (showAllOption) { + allColumnNames.push({ label: allColumnName, value: allColumnName }); + } const selectedColumnNames = (selectedColumns || []).map(c => ({ label: c.name, value: c.name })); const { label, tooltip } = labels.components.ColumnsEditor; @@ -49,8 +55,13 @@ export const ColumnsEditor = (props: ColumnsEditorProps) => { allColumns.forEach(c => columnMap.set(c.name, c)); selectedColumns.forEach(c => currentColumnMap.set(c.name, c)); + const excludeAllColumn = selectedColumnNames.size > 1; const nextSelectedColumns: SelectedColumn[] = []; for (let columnName of selectedColumnNames) { + if (excludeAllColumn && columnName === allColumnName) { + continue; + } + const tableColumn = columnMap.get(columnName); const existingColumn = currentColumnMap.get(columnName); diff --git a/src/components/queryBuilder/DatabaseTableSelect.tsx b/src/components/queryBuilder/DatabaseTableSelect.tsx index 7aea088f..6da1875c 100644 --- a/src/components/queryBuilder/DatabaseTableSelect.tsx +++ b/src/components/queryBuilder/DatabaseTableSelect.tsx @@ -57,7 +57,6 @@ export type TableSelectProps = { onTableChange: (value: string) => void; }; - export const TableSelect = (props: TableSelectProps) => { const { datasource, onTableChange, database, table } = props; const tables = useTables(datasource, database); @@ -66,17 +65,18 @@ export const TableSelect = (props: TableSelectProps) => { const options = tables.map(t => ({ label: t, value: t })); options.push({ label: empty, value: '' }); // Allow a blank value + // Include saved value in case it's no longer listed if (table && !tables.includes(table)) { options.push({ label: table, value: table }); } - // useEffect(() => { - // // TODO: broken. tables are loaded async when the db is changed, so it picks the first table from the previous db - // // Auto select first table - // if (database && !table && tables.length > 0) { - // onTableChange(tables[0]); - // } - // }, [database, table, tables, onTableChange]); + useEffect(() => { + // Auto select first/default table + // TODO: this still seems to lag behind when switching DB, probably due to async table fetch + if (database && !table && tables.length > 0) { + onTableChange(datasource.getDefaultTable() || tables[0]); + } + }, [database, table, tables, datasource, onTableChange]); return ( <> diff --git a/src/components/queryBuilder/DurationUnitSelect.tsx b/src/components/queryBuilder/DurationUnitSelect.tsx new file mode 100644 index 00000000..78896b19 --- /dev/null +++ b/src/components/queryBuilder/DurationUnitSelect.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { TimeUnit } from "types/queryBuilder"; +import allLabels from 'labels'; +import { InlineFormLabel, Select } from '@grafana/ui'; +import { SelectableValue } from '@grafana/data'; +import { styles } from 'styles'; + +interface DurationUnitSelectProps { + unit: TimeUnit; + onChange: (u: TimeUnit) => void; + disabled?: boolean; + inline?: boolean; +}; + +const durationUnitOptions: ReadonlyArray> = [ + { label: TimeUnit.Seconds, value: TimeUnit.Seconds }, + { label: TimeUnit.Milliseconds, value: TimeUnit.Milliseconds }, + { label: TimeUnit.Microseconds, value: TimeUnit.Microseconds }, + { label: TimeUnit.Nanoseconds, value: TimeUnit.Nanoseconds }, +]; + +export const DurationUnitSelect = (props: DurationUnitSelectProps) => { + const { unit, onChange, disabled, inline } = props; + const { label, tooltip } = allLabels.components.TraceQueryBuilder.columns.durationUnit; + + return ( +
+ + {label} + + + disabled={disabled} + options={durationUnitOptions as Array>} + value={unit} + onChange={v => onChange(v.value!)} + width={inline ? 25 : 30} + menuPlacement={'bottom'} + /> +
+ ); +}; diff --git a/src/components/queryBuilder/FilterEditor.tsx b/src/components/queryBuilder/FilterEditor.tsx index fdec9c1d..7c48211a 100644 --- a/src/components/queryBuilder/FilterEditor.tsx +++ b/src/components/queryBuilder/FilterEditor.tsx @@ -40,8 +40,8 @@ const standardTimeOptions: Array> = [ export const defaultNewFilter: NullFilter = { filterType: 'custom', condition: 'AND', - key: 'Id', - type: 'id', + key: '', + type: '', operator: FilterOperator.IsNotNull, }; export interface PredefinedFilter { diff --git a/src/components/queryBuilder/OtelVersionSelect.tsx b/src/components/queryBuilder/OtelVersionSelect.tsx index 02733bc1..5c5de72e 100644 --- a/src/components/queryBuilder/OtelVersionSelect.tsx +++ b/src/components/queryBuilder/OtelVersionSelect.tsx @@ -10,21 +10,22 @@ interface OtelVersionSelectProps { selectedVersion: string, onVersionChange: (version: string) => void, defaultToLatest?: boolean, + wide?: boolean, } export const OtelVersionSelect = (props: OtelVersionSelectProps) => { - const { enabled, onEnabledChange, selectedVersion, onVersionChange, defaultToLatest } = props; + const { enabled, onEnabledChange, selectedVersion, onVersionChange, defaultToLatest, wide } = props; const { label, tooltip } = selectors.components.OtelVersionSelect; - const options: SelectableValue[] = allVersions. - map(v => ({ - label: `${v.version}${v.name ? (` (${v.name})`) : ''}`, - value: v.version - })); + const options: SelectableValue[] = allVersions.map(v => ({ + label: `${v.version}${v.name ? (` (${v.name})`) : ''}`, + value: v.version + })); - const hasCurrentVersion = allVersions.find(v => v.version === selectedVersion); - if (!hasCurrentVersion) { - options.push({ label: selectedVersion, value: selectedVersion }); + useEffect(() => { + if (defaultToLatest && selectedVersion === '') { + onVersionChange(allVersions[0].version); } + }, [defaultToLatest, selectedVersion, onVersionChange]); const theme = useTheme(); const switchContainerStyle: React.CSSProperties = { @@ -34,15 +35,9 @@ export const OtelVersionSelect = (props: OtelVersionSelectProps) => { alignItems: 'center', }; - useEffect(() => { - if (defaultToLatest && selectedVersion === '') { - onVersionChange(allVersions[0].version); - } - }, [selectedVersion, onVersionChange, defaultToLatest]) - return (
- + {label}
diff --git a/src/components/queryBuilder/QueryBuilder.tsx b/src/components/queryBuilder/QueryBuilder.tsx index 5c3195d5..b7761f2a 100644 --- a/src/components/queryBuilder/QueryBuilder.tsx +++ b/src/components/queryBuilder/QueryBuilder.tsx @@ -15,7 +15,7 @@ import { TraceQueryBuilder } from './views/TraceQueryBuilder'; interface QueryBuilderProps { app: CoreApp | undefined; builderOptions: QueryBuilderOptions; - onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; + onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; datasource: Datasource; generatedSql: string; } @@ -24,9 +24,9 @@ export const QueryBuilder = (props: QueryBuilderProps) => { const { datasource, builderOptions, onBuilderOptionsChange, generatedSql } = props; const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); - const onDatabaseChange = (database: string) => onBuilderOptionsChange({ ...builderOptions, database, table: '' }); - const onTableChange = (table: string) => onBuilderOptionsChange({ ...builderOptions, table }); - const onQueryTypeChange = (queryType: QueryType) => onBuilderOptionsChange({ ...builderOptions, queryType }); + const onDatabaseChange = (database: string) => onBuilderOptionsChange({ database, table: '' }); + const onTableChange = (table: string) => onBuilderOptionsChange({ table }); + const onQueryTypeChange = (queryType: QueryType) => onBuilderOptionsChange({ queryType }); return (
@@ -41,10 +41,10 @@ export const QueryBuilder = (props: QueryBuilderProps) => {
- { builderOptions.queryType === QueryType.Table && } - { builderOptions.queryType === QueryType.Logs && } - { builderOptions.queryType === QueryType.TimeSeries && } - { builderOptions.queryType === QueryType.Traces && } + { builderOptions.queryType === QueryType.Table && } + { builderOptions.queryType === QueryType.Logs && } + { builderOptions.queryType === QueryType.TimeSeries && } + { builderOptions.queryType === QueryType.Traces && }
diff --git a/src/components/queryBuilder/QueryTypeSwitcher.tsx b/src/components/queryBuilder/QueryTypeSwitcher.tsx index 6b3843ac..26b05fb5 100644 --- a/src/components/queryBuilder/QueryTypeSwitcher.tsx +++ b/src/components/queryBuilder/QueryTypeSwitcher.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { RadioButtonGroup, InlineFormLabel } from '@grafana/ui'; import labels from 'labels'; import { QueryType } from 'types/queryBuilder'; @@ -35,12 +35,6 @@ export const QueryTypeSwitcher = (props: QueryTypeSwitcherProps) => { const { queryType, onChange, sqlEditor } = props; const { label, tooltip, sqlTooltip } = labels.components.QueryTypeSwitcher; - useEffect(() => { - if (!queryType) { - onChange(QueryType.Table); - } - }, [queryType, onChange]); - return ( diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index 33598bc7..54855d8b 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -1,141 +1,222 @@ import React, { useEffect, useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint } from 'types/queryBuilder'; +import { Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint, DateFilterWithoutValue, FilterOperator } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; -import { Switch } from '../Switch'; import { OtelVersionSelect } from '../OtelVersionSelect'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; import { LimitEditor } from '../LimitEditor'; import { FiltersEditor } from '../FilterEditor'; import allLabels from 'labels'; import { getColumnByHint } from 'components/queryBuilder/utils'; -import { versions as otelVersions } from 'otel'; import { columnFilterDateTime, columnFilterString } from 'data/columnFilters'; +import { Datasource } from 'data/CHDatasource'; +import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; +import { versions as otelVersions } from 'otel'; +import { Alert, VerticalGroup } from '@grafana/ui'; interface LogsQueryBuilderProps { allColumns: readonly TableColumn[]; + datasource: Datasource; builderOptions: QueryBuilderOptions, - onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; + onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; +} + +interface LogsQueryBuilderState { + otelEnabled: boolean; + otelVersion: string; + selectedColumns: SelectedColumn[]; + timeColumn?: SelectedColumn; + logLevelColumn?: SelectedColumn; + messageColumn?: SelectedColumn; + // liveView: boolean; + orderBy: OrderBy[]; + limit: number; + filters: Filter[]; } export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { - const { allColumns, builderOptions, onBuilderOptionsChange } = props; - const [otelEnabled, setOtelEnabled] = useState(false); - const [otelVersion, setOtelVersion] = useState(''); - const [selectedColumns, setSelectedColumns] = useState([]); - const [timeColumn, setTimeColumn] = useState(); - const [logLevelColumn, setLogLevelColumn] = useState(); - const [messageColumn, setMessageColumn] = useState(); - const [liveView, setLiveView] = useState(false); - const [orderBy, setOrderBy] = useState([]); - const [limit, setLimit] = useState(1000); - const [filters, setFilters] = useState([]); + const { allColumns, datasource, builderOptions, onBuilderOptionsChange } = props; const labels = allLabels.components.LogsQueryBuilder; - - useEffect(() => { - builderOptions.meta?.otelEnabled !== undefined && setOtelEnabled(builderOptions.meta.otelEnabled); - builderOptions.meta?.otelVersion && setOtelVersion(builderOptions.meta.otelVersion); - setTimeColumn(getColumnByHint(builderOptions, ColumnHint.Time)); - setLogLevelColumn(getColumnByHint(builderOptions, ColumnHint.LogLevel)); - setMessageColumn(getColumnByHint(builderOptions, ColumnHint.LogMessage)); - builderOptions.columns && setSelectedColumns(builderOptions.columns.filter(c => ( + const builderState: LogsQueryBuilderState = { + otelEnabled: builderOptions.meta?.otelEnabled || false, + otelVersion: builderOptions.meta?.otelVersion || '', + timeColumn: getColumnByHint(builderOptions, ColumnHint.Time), + logLevelColumn: getColumnByHint(builderOptions, ColumnHint.LogLevel), + messageColumn: getColumnByHint(builderOptions, ColumnHint.LogMessage), + selectedColumns: builderOptions.columns?.filter(c => ( // Only select columns that don't have their own box c.hint !== ColumnHint.Time && c.hint !== ColumnHint.LogLevel && c.hint !== ColumnHint.LogMessage - ))); - builderOptions.meta?.liveView && setLiveView(builderOptions.meta.liveView); - builderOptions.orderBy && setOrderBy(builderOptions.orderBy); - builderOptions.limit && setLimit(builderOptions.limit); - builderOptions.filters && setFilters(builderOptions.filters); + )) || [], + // liveView: builderOptions.meta?.liveView || false, + orderBy: builderOptions.orderBy || [], + limit: builderOptions.limit || 1000, + filters: builderOptions.filters || [], + }; + const showConfigWarning = datasource.getDefaultLogsColumns().size === 0; - // Run on load - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + function setOtelColumns(builderState: LogsQueryBuilderState) { + if (!builderState.otelEnabled) { + return; + } - useEffect(() => { - const otelConfig = otelVersions.find(v => v.version === otelVersion); - if (!otelEnabled || !otelConfig) { + const otelConfig = otelVersions.find(v => v.version === builderState.otelVersion); + const logColumnMap = otelConfig?.logColumnMap; + if (!otelConfig || !logColumnMap) { return; } - const columnMap = new Map(); - allColumns.forEach(c => columnMap.set(c.name, c)); - const logColumnMap = otelConfig.logColumnMap; - for (const [hint, colName] of logColumnMap) { - const col = columnMap.get(colName); - if (!col) { - continue; - } + builderState.selectedColumns = []; + if (logColumnMap.has(ColumnHint.Time)) { + builderState.timeColumn = { name: logColumnMap.get(ColumnHint.Time)!, hint: ColumnHint.Time }; + } + if (logColumnMap.has(ColumnHint.LogLevel)) { + builderState.logLevelColumn = { name: logColumnMap.get(ColumnHint.LogLevel)!, hint: ColumnHint.LogLevel }; + } + if (logColumnMap.has(ColumnHint.LogMessage)) { + builderState.messageColumn = { name: logColumnMap.get(ColumnHint.LogMessage)!, hint: ColumnHint.LogMessage }; + } + } - const selectedColumn: SelectedColumn = { name: col.name, type: col.type, hint }; - switch (hint) { - case ColumnHint.Time: - setTimeColumn(selectedColumn); - case ColumnHint.LogMessage: - setMessageColumn(selectedColumn); - case ColumnHint.LogLevel: - setLogLevelColumn(selectedColumn); - } + const onOptionChange = useBuilderOptionChanges(next => { + if (next.otelEnabled) { + setOtelColumns(next); } - }, [otelEnabled, otelVersion, allColumns]); - useEffect(() => { - const nextColumns = selectedColumns.slice(); - if (timeColumn) { - nextColumns.push(timeColumn); + const nextColumns = next.selectedColumns.slice(); + if (next.timeColumn) { + nextColumns.push(next.timeColumn); + } + if (next.logLevelColumn) { + nextColumns.push(next.logLevelColumn); + } + if (next.messageColumn) { + nextColumns.push(next.messageColumn); } - if (logLevelColumn) { - nextColumns.push(logLevelColumn); + + onBuilderOptionsChange({ + columns: nextColumns, + filters: next.filters, + orderBy: next.orderBy, + limit: next.limit, + meta: { + otelEnabled: next.otelEnabled, + otelVersion: next.otelVersion, + } + }); + }, builderState); + + useEffect(() => { + const shouldApplyDefaults = (builderOptions.columns || []).length === 0 && (builderOptions.orderBy || []).length === 0; + if (!shouldApplyDefaults) { + return; } - if (messageColumn) { - nextColumns.push(messageColumn); + + const defaultDb = datasource.getDefaultLogsDatabase() || datasource.getDefaultDatabase(); + const defaultTable = datasource.getDefaultLogsTable() || datasource.getDefaultTable(); + const otelVersion = datasource.getLogsOtelVersion(); + const defaultColumns = datasource.getDefaultLogsColumns(); + + const nextColumns: SelectedColumn[] = []; + for (let [hint, colName] of defaultColumns) { + nextColumns.push({ name: colName, hint }); } onBuilderOptionsChange({ - ...builderOptions, - mode: BuilderMode.List, + database: defaultDb, + table: defaultTable || builderOptions.table, columns: nextColumns, - filters, - orderBy, - limit, + // filters, + // orderBy, meta: { - ...builderOptions.meta, - otelEnabled, + otelEnabled: Boolean(otelVersion), otelVersion, } }); - // TODO: ignore when builderOptions changes? + // Run on load // eslint-disable-next-line react-hooks/exhaustive-deps - }, [otelEnabled, otelVersion, selectedColumns, filters, orderBy, limit, timeColumn, logLevelColumn, messageColumn]); + }, []); + + // TODO: default filter + // const timeRangeFilter: DateFilterWithoutValue = { + // type: 'date', + // operator: FilterOperator.WithInGrafanaTimeRange, + // filterType: 'custom', + // key: timeColumn.name, + // id: 'timeRange', + // condition: 'AND' + // }; + + // TODO: fix default table selection AND default time column selection + // useEffect(() => { + // if (allColumns.length === 0) { + // return; + // } + + // const col = allColumns.filter(columnFilterDateTime)[0]; + // const currentColumnExists = (builderState.timeColumn && allColumns.find(c => c.name === builderState.timeColumn?.name)); + // if (!col || currentColumnExists) { + // return; + // } + + // const timeColumn: SelectedColumn = { + // name: col.name, + // type: col.type, + // hint: ColumnHint.Time + // }; + + // onOptionChange('timeColumn')(timeColumn); + + // // Find and select a default time column, update when table changes + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, [allColumns, builderOptions.table]); + const configWarning = showConfigWarning && ( + + +
+ {'To speed up your query building, enter your default logs configuration in your '} + ClickHouse Data Source settings +
+
+
+ ); + return (
+ { configWarning } - +
{
- + /> */}
+ + - -
); } diff --git a/src/components/queryBuilder/views/TableQueryBuilder.tsx b/src/components/queryBuilder/views/TableQueryBuilder.tsx index 5e324d7f..fa4b8bd8 100644 --- a/src/components/queryBuilder/views/TableQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TableQueryBuilder.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { AggregateColumn, BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn, AggregateType } from 'types/queryBuilder'; +import { AggregateColumn, BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn } from 'types/queryBuilder'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; import { LimitEditor } from '../LimitEditor'; import { FiltersEditor } from '../FilterEditor'; @@ -8,59 +8,49 @@ import allLabels from 'labels'; import { ModeSwitch } from '../ModeSwitch'; import { AggregateEditor } from '../AggregateEditor'; import { GroupByEditor } from '../GroupByEditor'; +import { Datasource } from 'data/CHDatasource'; +import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; interface TableQueryBuilderProps { allColumns: readonly TableColumn[]; + datasource: Datasource; builderOptions: QueryBuilderOptions, - onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; + onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; } -const emptyAggregate: AggregateColumn = { column: '', aggregateType: AggregateType.Count }; +interface TableQueryBuilderState { + selectedColumns: SelectedColumn[]; + aggregates: AggregateColumn[]; + groupBy: string[]; + orderBy: OrderBy[]; + limit: number; + filters: Filter[]; +} export const TableQueryBuilder = (props: TableQueryBuilderProps) => { const { allColumns, builderOptions, onBuilderOptionsChange } = props; - const [isAggregateMode, setAggregateMode] = useState(false); // Toggle Simple vs Aggregate mode - const [selectedColumns, setSelectedColumns] = useState([]); - const [aggregates, setAggregates] = useState([emptyAggregate]); - const [groupBy, setGroupBy] = useState([]); - const [orderBy, setOrderBy] = useState([]); - const [limit, setLimit] = useState(1000); - const [filters, setFilters] = useState([]); const labels = allLabels.components.TableQueryBuilder; + const [isAggregateMode, setAggregateMode] = useState((builderOptions.aggregates?.length || 0) > 0); // Toggle Simple vs Aggregate mode + const builderState: TableQueryBuilderState = { + selectedColumns: builderOptions.columns || [], + aggregates: builderOptions.aggregates || [], + groupBy: builderOptions.groupBy || [], + orderBy: builderOptions.orderBy || [], + limit: builderOptions.limit || 1000, + filters: builderOptions.filters || [], + }; - useEffect(() => { - builderOptions.aggregates && setAggregateMode(builderOptions.aggregates.length > 0); - builderOptions.columns && setSelectedColumns(builderOptions.columns); - builderOptions.aggregates && setAggregates(builderOptions.aggregates); - builderOptions.groupBy && setGroupBy(builderOptions.groupBy); - builderOptions.orderBy && setOrderBy(builderOptions.orderBy); - builderOptions.limit && setLimit(builderOptions.limit); - builderOptions.filters && setFilters(builderOptions.filters); - - // Run on load - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - const nextOptions: QueryBuilderOptions = { - ...builderOptions, + const onOptionChange = useBuilderOptionChanges(next => { + onBuilderOptionsChange({ mode: isAggregateMode ? BuilderMode.Aggregate : BuilderMode.List, - columns: selectedColumns, - filters, - orderBy, - limit - }; - - if (isAggregateMode) { - nextOptions.aggregates = aggregates; - nextOptions.groupBy = groupBy; - } - - onBuilderOptionsChange(nextOptions); - - // TODO: ignore when builderOptions changes? - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isAggregateMode, selectedColumns, filters, aggregates, groupBy, orderBy, limit]); + columns: next.selectedColumns, + aggregates: isAggregateMode ? next.aggregates : [], + groupBy: isAggregateMode ? next.groupBy : [], + filters: next.filters, + orderBy: next.orderBy, + limit: next.limit + }); + }, builderState); return (
@@ -73,22 +63,27 @@ export const TableQueryBuilder = (props: TableQueryBuilderProps) => { tooltip={labels.builderModeTooltip} /> - + {isAggregateMode && ( <> - - + + )} - - + +
); } diff --git a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx index 9fd4ba3d..cec0bd53 100644 --- a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { AggregateColumn, BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, ColumnHint, SelectedColumn, AggregateType } from 'types/queryBuilder'; +import { AggregateColumn, BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, ColumnHint, SelectedColumn } from 'types/queryBuilder'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; import { LimitEditor } from '../LimitEditor'; import { FiltersEditor } from '../FilterEditor'; @@ -11,65 +11,79 @@ import { GroupByEditor } from '../GroupByEditor'; import { ColumnSelect } from '../ColumnSelect'; import { getColumnByHint } from 'components/queryBuilder/utils'; import { columnFilterDateTime } from 'data/columnFilters'; +import { Datasource } from 'data/CHDatasource'; +import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; interface TimeSeriesQueryBuilderProps { allColumns: readonly TableColumn[]; + datasource: Datasource; builderOptions: QueryBuilderOptions, - onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; + onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; } -const emptyAggregate: AggregateColumn = { column: '', aggregateType: AggregateType.Count }; +interface TimeSeriesQueryBuilderState { + timeColumn?: SelectedColumn; + selectedColumns: SelectedColumn[]; + aggregates: AggregateColumn[]; + groupBy: string[]; + orderBy: OrderBy[]; + limit: number; + filters: Filter[]; +} export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { const { allColumns, builderOptions, onBuilderOptionsChange } = props; - const [isAggregateMode, setAggregateMode] = useState(false); // Toggle Simple vs Aggregate mode - const [timeColumn, setTimeColumn] = useState(); - const [selectedColumns, setSelectedColumns] = useState([]); - const [aggregates, setAggregates] = useState([emptyAggregate]); - const [groupBy, setGroupBy] = useState([]); - const [orderBy, setOrderBy] = useState([]); - const [limit, setLimit] = useState(1000); - const [filters, setFilters] = useState([]); const labels = allLabels.components.TimeSeriesQueryBuilder; + const [isAggregateMode, setAggregateMode] = useState((builderOptions.aggregates?.length || 0) > 0); // Toggle Simple vs Aggregate mode + const builderState: TimeSeriesQueryBuilderState = { + timeColumn: getColumnByHint(builderOptions, ColumnHint.Time), + selectedColumns: (builderOptions.columns || []).filter(c => c.hint !== ColumnHint.Time), + aggregates: builderOptions.aggregates || [], + groupBy: builderOptions.groupBy || [], + orderBy: builderOptions.orderBy || [], + limit: builderOptions.limit || 1000, + filters: builderOptions.filters || [], + }; - useEffect(() => { - builderOptions.aggregates && setAggregateMode(builderOptions.aggregates.length > 0); - setTimeColumn(getColumnByHint(builderOptions, ColumnHint.Time)); - builderOptions.columns && setSelectedColumns(builderOptions.columns.filter(c => c.hint !== ColumnHint.Time)); - builderOptions.aggregates && setAggregates(builderOptions.aggregates); - builderOptions.groupBy && setGroupBy(builderOptions.groupBy); - builderOptions.orderBy && setOrderBy(builderOptions.orderBy); - builderOptions.limit && setLimit(builderOptions.limit); - builderOptions.filters && setFilters(builderOptions.filters); - - // Run on load - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - const nextColumns = selectedColumns.slice(); - if (timeColumn) { - nextColumns.push(timeColumn); + const onOptionChange = useBuilderOptionChanges(next => { + const nextColumns = next.selectedColumns.slice(); + if (next.timeColumn) { + nextColumns.push(next.timeColumn); } - const nextOptions: QueryBuilderOptions = { - ...builderOptions, + onBuilderOptionsChange({ mode: isAggregateMode ? BuilderMode.Aggregate : BuilderMode.List, columns: nextColumns, - filters, - orderBy, - limit - }; + aggregates: isAggregateMode ? next.aggregates : [], + groupBy: isAggregateMode ? next.groupBy : [], + filters: next.filters, + orderBy: next.orderBy, + limit: next.limit + }); + }, builderState); - if (isAggregateMode) { - nextOptions.aggregates = aggregates; - nextOptions.groupBy = groupBy; + useEffect(() => { + if (allColumns.length === 0) { + return; + } + + const col = allColumns.filter(columnFilterDateTime)[0]; + const currentColumnExists = (builderState.timeColumn && allColumns.find(c => c.name === builderState.timeColumn?.name)); + if (!col || currentColumnExists) { + return; } - onBuilderOptionsChange(nextOptions); - // TODO: ignore when builderOptions changes? + const timeColumn: SelectedColumn = { + name: col.name, + type: col.type, + hint: ColumnHint.Time + }; + + onOptionChange('timeColumn')(timeColumn); + + // Find and select a default time column, update when table changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isAggregateMode, timeColumn, selectedColumns, filters, aggregates, groupBy, orderBy, limit]); + }, [allColumns, builderOptions.table]); return (
@@ -84,29 +98,34 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { - + {isAggregateMode && ( <> - - + + )} - - + +
); } diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index 41ce67c0..f5b3e254 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -1,96 +1,136 @@ import React, { useEffect, useState } from 'react'; -import { BuilderMode, Filter, TableColumn, QueryBuilderOptions, SelectedColumn, ColumnHint, TimeUnit } from 'types/queryBuilder'; +import { Filter, TableColumn, QueryBuilderOptions, SelectedColumn, ColumnHint, TimeUnit } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; import { FiltersEditor } from '../FilterEditor'; import allLabels from 'labels'; import { ModeSwitch } from '../ModeSwitch'; import { getColumnByHint } from 'components/queryBuilder/utils'; -import { Collapse, InlineFormLabel, Input, Select } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { styles } from 'styles'; +import { Alert, Collapse, InlineFormLabel, Input, VerticalGroup } from '@grafana/ui'; +import { DurationUnitSelect } from 'components/queryBuilder/DurationUnitSelect'; +import { Datasource } from 'data/CHDatasource'; +import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; interface TraceQueryBuilderProps { allColumns: readonly TableColumn[]; + datasource: Datasource; builderOptions: QueryBuilderOptions, - onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void; + onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; +} + +interface TraceQueryBuilderState { + isSearchMode: boolean; + traceIdColumn?: SelectedColumn; + spanIdColumn?: SelectedColumn; + parentSpanIdColumn?: SelectedColumn; + serviceNameColumn?: SelectedColumn; + operationNameColumn?: SelectedColumn; + startTimeColumn?: SelectedColumn; + durationTimeColumn?: SelectedColumn; + durationUnit: TimeUnit; + tagsColumn?: SelectedColumn; + serviceTagsColumn?: SelectedColumn; + traceId: string; + filters: Filter[]; } export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { - const { allColumns, builderOptions, onBuilderOptionsChange } = props; - const [isSearchMode, setSearchMode] = useState(false); // Toggle for Trace ID vs Trace Search mode - const [isColumnsOpen, setColumnsOpen] = useState(true); // Toggle Columns collapsable section + const { allColumns, datasource, builderOptions, onBuilderOptionsChange } = props; + const showConfigWarning = datasource.getDefaultTraceColumns().size === 0; + const [isColumnsOpen, setColumnsOpen] = useState(showConfigWarning); // Toggle Columns collapsable section const [isFiltersOpen, setFiltersOpen] = useState(true); // Toggle Filters collapsable section - const [traceIdColumn, setTraceIdColumn] = useState(); - const [spanIdColumn, setSpanIdColumn] = useState(); - const [parentSpanIdColumn, setParentSpanIdColumn] = useState(); - const [serviceNameColumn, setServiceNameColumn] = useState(); - const [operationNameColumn, setOperationNameColumn] = useState(); - const [startTimeColumn, setStartTimeColumn] = useState(); - const [durationTimeColumn, setDurationTimeColumn] = useState(); - const [durationUnit, setDurationUnit] = useState(TimeUnit.Nanoseconds); - const [tagsColumn, setTagsColumn] = useState(); - const [serviceTagsColumn, setServiceTagsColumn] = useState(); - const [traceId, setTraceId] = useState(''); - const [filters, setFilters] = useState([]); const labels = allLabels.components.TraceQueryBuilder; + const builderState: TraceQueryBuilderState = { + isSearchMode: builderOptions.meta?.isTraceSearchMode || false, + traceIdColumn: getColumnByHint(builderOptions, ColumnHint.TraceId), + spanIdColumn: getColumnByHint(builderOptions, ColumnHint.TraceSpanId), + parentSpanIdColumn: getColumnByHint(builderOptions, ColumnHint.TraceParentSpanId), + serviceNameColumn: getColumnByHint(builderOptions, ColumnHint.TraceServiceName), + operationNameColumn: getColumnByHint(builderOptions, ColumnHint.TraceOperationName), + startTimeColumn: getColumnByHint(builderOptions, ColumnHint.Time), + durationTimeColumn: getColumnByHint(builderOptions, ColumnHint.TraceDurationTime), + durationUnit: builderOptions.meta?.traceDurationUnit || TimeUnit.Nanoseconds, + tagsColumn: getColumnByHint(builderOptions, ColumnHint.TraceTags), + serviceTagsColumn: getColumnByHint(builderOptions, ColumnHint.TraceServiceTags), + traceId: builderOptions.meta?.traceId || '', + filters: builderOptions.filters || [], + }; useEffect(() => { - builderOptions.meta?.isTraceSearchMode !== undefined && setSearchMode(builderOptions.meta.isTraceSearchMode); - setTraceIdColumn(getColumnByHint(builderOptions, ColumnHint.TraceId)); - setSpanIdColumn(getColumnByHint(builderOptions, ColumnHint.TraceSpanId)); - setParentSpanIdColumn(getColumnByHint(builderOptions, ColumnHint.TraceParentSpanId)); - setServiceNameColumn(getColumnByHint(builderOptions, ColumnHint.TraceServiceName)); - setOperationNameColumn(getColumnByHint(builderOptions, ColumnHint.TraceOperationName)); - setStartTimeColumn(getColumnByHint(builderOptions, ColumnHint.TraceStartTime)); - setDurationTimeColumn(getColumnByHint(builderOptions, ColumnHint.TraceDurationTime)); - builderOptions.meta?.traceDurationUnit && setDurationUnit(builderOptions.meta.traceDurationUnit); - setTagsColumn(getColumnByHint(builderOptions, ColumnHint.TraceTags)); - setServiceTagsColumn(getColumnByHint(builderOptions, ColumnHint.TraceServiceTags)); - builderOptions.meta?.traceId && setTraceId(builderOptions.meta.traceId); - builderOptions.filters && setFilters(builderOptions.filters); + const shouldApplyDefaults = (builderOptions.columns || []).length === 0 && (builderOptions.orderBy || []).length === 0; + if (!shouldApplyDefaults) { + return; + } + + const defaultDb = datasource.getDefaultTraceDatabase() || datasource.getDefaultDatabase(); + const defaultTable = datasource.getDefaultTraceTable() || datasource.getDefaultTable(); + const defaultDurationUnit = datasource.getDefaultTraceDurationUnit(); + const otelVersion = datasource.getTraceOtelVersion(); + const defaultColumns = datasource.getDefaultTraceColumns(); + + const nextColumns: SelectedColumn[] = []; + for (let [hint, colName] of defaultColumns) { + nextColumns.push({ name: colName, hint }); + } + + onBuilderOptionsChange({ + database: defaultDb, + table: defaultTable || builderOptions.table, + columns: nextColumns, + // filters, + // orderBy, + meta: { + otelEnabled: Boolean(otelVersion), + otelVersion, + traceDurationUnit: defaultDurationUnit + } + }); // Run on load // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { + const onOptionChange = useBuilderOptionChanges(next => { const nextColumns = [ - traceIdColumn, - spanIdColumn, - parentSpanIdColumn, - serviceNameColumn, - operationNameColumn, - startTimeColumn, - durationTimeColumn, - tagsColumn, - serviceTagsColumn + next.traceIdColumn, + next.spanIdColumn, + next.parentSpanIdColumn, + next.serviceNameColumn, + next.operationNameColumn, + next.startTimeColumn, + next.durationTimeColumn, + next.tagsColumn, + next.serviceTagsColumn ].filter(c => c !== undefined) as SelectedColumn[]; - onBuilderOptionsChange({ - ...builderOptions, - mode: BuilderMode.List, columns: nextColumns, - filters, + filters: next.filters, meta: { - ...builderOptions.meta, - isTraceSearchMode: isSearchMode, - traceDurationUnit: durationUnit, - traceId: traceId, + isTraceSearchMode: next.isSearchMode, + traceDurationUnit: next.durationUnit, + traceId: next.traceId, } }); - - // TODO: ignore when builderOptions changes? - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [traceIdColumn, spanIdColumn, parentSpanIdColumn, serviceNameColumn, operationNameColumn, startTimeColumn, durationTimeColumn, tagsColumn, serviceTagsColumn, filters, isSearchMode, durationUnit, traceId]); + }, builderState); + const configWarning = showConfigWarning && ( + + +
+ {'To speed up your query building, enter your default trace configuration in your '} + ClickHouse Data Source settings +
+
+
+ ); + return (
@@ -100,11 +140,13 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { isOpen={isColumnsOpen} onToggle={setColumnsOpen} > + { configWarning }
{ /> {
{ /> {
{ /> {
{ /> { />
- { isSearchMode ? ( + { builderState.isSearchMode ? ( - + ) : - + }
); } -interface DurationUnitSelectProps { - unit: TimeUnit; - onChange: (u: TimeUnit) => void; -}; - -const durationUnitOptions: ReadonlyArray> = [ - { label: TimeUnit.Seconds, value: TimeUnit.Seconds }, - { label: TimeUnit.Milliseconds, value: TimeUnit.Milliseconds }, - { label: TimeUnit.Microseconds, value: TimeUnit.Microseconds }, - { label: TimeUnit.Nanoseconds, value: TimeUnit.Nanoseconds }, -]; - -const DurationUnitSelect = (props: DurationUnitSelectProps) => { - const { unit, onChange } = props; - const { label, tooltip } = allLabels.components.TraceQueryBuilder.columns.durationUnit; - - return ( -
- - {label} - - - options={durationUnitOptions as Array>} - value={unit} - onChange={v => onChange(v.value!)} - width={25} - menuPlacement={'bottom'} - /> -
- ); -}; - interface TraceIdInputProps { traceId: string; onChange: (traceId: string) => void; diff --git a/src/data/sqlGenerator.test.ts b/src/data/sqlGenerator.test.ts index 202c981c..6e6814ac 100644 --- a/src/data/sqlGenerator.test.ts +++ b/src/data/sqlGenerator.test.ts @@ -13,7 +13,7 @@ describe('SQL Generator', () => { { name: 'ParentSpanId', type: 'String', hint: ColumnHint.TraceParentSpanId }, { name: 'ServiceName', type: 'LowCardinality(String)', hint: ColumnHint.TraceServiceName }, { name: 'SpanName', type: 'LowCardinality(String)', hint: ColumnHint.TraceOperationName }, - { name: 'Timestamp', type: 'DateTime64(9)', hint: ColumnHint.TraceStartTime }, + { name: 'Timestamp', type: 'DateTime64(9)', hint: ColumnHint.Time }, { name: 'Duration', type: 'Int64', hint: ColumnHint.TraceDurationTime }, { name: 'SpanAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceTags }, { name: 'ResourceAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceServiceTags }, diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index ac34e9fd..7c1132d0 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -50,7 +50,7 @@ const generateTraceQuery = (options: QueryBuilderOptions): string => { selectParts.push(`${escapeIdentifier(traceOperationName.name)} as operationName`); } - const traceStartTime = getColumnByHint(options, ColumnHint.TraceStartTime); + const traceStartTime = getColumnByHint(options, ColumnHint.Time); if (traceStartTime !== undefined) { selectParts.push(`toInt64(${escapeIdentifier(traceStartTime.name)}) as startTime`); } @@ -61,6 +61,7 @@ const generateTraceQuery = (options: QueryBuilderOptions): string => { selectParts.push(getTraceDurationSelectSql(escapeIdentifier(traceDurationTime.name), timeUnit)); } + // TODO: for tags and serviceTags, consider the column type. They might not require mapping, they could already be JSON. const traceTags = getColumnByHint(options, ColumnHint.TraceTags); if (traceTags !== undefined) { selectParts.push(`arrayMap(key -> map('key', key, 'value',${escapeIdentifier(traceTags.name)}[key]), mapKeys(${escapeIdentifier(traceTags.name)})) as tags`); @@ -140,6 +141,10 @@ const generateLogsQuery = (options: QueryBuilderOptions): string => { selectParts.push(getColumnIdentifier(logLevel)); } + options.columns?. + filter(c => c.hint === undefined). // remove specialized columns + forEach(c => selectParts.push(getColumnIdentifier(c))); + const selectPartsSql = selectParts.join(', '); queryParts.push('SELECT'); @@ -180,7 +185,11 @@ export const getColumnsByHints = (options: QueryBuilderOptions, hints: readonly } const getColumnIdentifier = (col: SelectedColumn): string => { - let colName = escapeIdentifier(col.name); + let colName = col.name; + + if (colName.includes(' ')) { + colName = escapeIdentifier(col.name); + } // allow for functions like count() if (colName.includes('(') || colName.includes(')')) { diff --git a/src/data/utils.ts b/src/data/utils.ts index 44353e88..b34ace0a 100644 --- a/src/data/utils.ts +++ b/src/data/utils.ts @@ -14,8 +14,8 @@ export const mapQueryTypeToGrafanaFormat = (t: QueryType): number => { case QueryType.TimeSeries: return 0; case QueryType.Traces: - return 3 + return 3; default: - return -1 // defaults to timeseries/graph on plugin backend. + return 1 << 8; // an unused u32, defaults to timeseries/graph on plugin backend. } } diff --git a/src/hooks/useBuilderOptionChanges.ts b/src/hooks/useBuilderOptionChanges.ts new file mode 100644 index 00000000..906ea0b2 --- /dev/null +++ b/src/hooks/useBuilderOptionChanges.ts @@ -0,0 +1,33 @@ +import React, { useCallback } from 'react'; + +type onOptionChangeFn = (key: keyof T | Object) => (nextValue: React.SetStateAction) => void; + +/** + * Returns a function that can apply changes with an object or a specific key in an object. When called + * will run another function with the changes applied. + * + * (Does not deep clone the object, for now) + * + * @param onChange a function that receives the updated state from the change function + * @param prevState the current (previous) state object + * @returns a function used to apply changes to individual fields + */ +export function useBuilderOptionChanges(onChange: (nextState: T) => void, prevState: T): onOptionChangeFn { + return useCallback((key: keyof T | Object) => + (nextValue: React.SetStateAction) => { + let nextState: T; + if (typeof key === 'object') { + nextState = { + ...prevState, + ...key, + }; + } else { + nextState = { + ...prevState, + [key]: nextValue + }; + } + + onChange(nextState); + }, [onChange, prevState]); +} diff --git a/src/hooks/useColumns.ts b/src/hooks/useColumns.ts index e979b3ae..d1c3bfad 100644 --- a/src/hooks/useColumns.ts +++ b/src/hooks/useColumns.ts @@ -2,10 +2,8 @@ import { useState, useEffect } from 'react'; import { TableColumn } from 'types/queryBuilder'; import { Datasource } from 'data/CHDatasource'; -const allColumn = { name: '*', label: 'ALL', type: 'string', picklistValues: [] }; - export default (datasource: Datasource, database: string, table: string): readonly TableColumn[] => { - const [columns, setColumns] = useState([allColumn]); + const [columns, setColumns] = useState([]); useEffect(() => { if (!datasource || !database || !table) { @@ -14,10 +12,8 @@ export default (datasource: Datasource, database: string, table: string): readon datasource .fetchColumnsFull(database, table) - .then(columns => { - columns.push(allColumn); - setColumns(columns); - }).catch((ex: any) => { + .then(columns => setColumns(columns)) + .catch((ex: any) => { console.error(ex); }); }, [datasource, database, table]); diff --git a/src/labels.ts b/src/labels.ts index 14fcf2c8..bd4d2631 100644 --- a/src/labels.ts +++ b/src/labels.ts @@ -34,7 +34,7 @@ export default { tooltip: 'A list of columns to include in the query' }, OtelVersionSelect: { - label: 'OTEL Logs', + label: 'Use OTEL', tooltip: 'Enables Open Telemetry schema versioning' }, LimitEditor: { diff --git a/src/otel.ts b/src/otel.ts index cc421190..a8f45035 100644 --- a/src/otel.ts +++ b/src/otel.ts @@ -1,13 +1,33 @@ -import { ColumnHint } from "types/queryBuilder"; +import { ColumnHint, TimeUnit } from "types/queryBuilder"; -export const versions = [ +export interface OtelVersion { + name: string; + version: string; + logColumnMap: Map; + traceColumnMap: Map; + traceDurationUnit: TimeUnit.Nanoseconds; +} + +export const versions: readonly OtelVersion[] = [ { - name: 'default', - version: 'default', + name: 'latest', + version: '1.26.0', logColumnMap: new Map([ [ColumnHint.Time, 'Timestamp'], [ColumnHint.LogMessage, 'Body'], [ColumnHint.LogLevel, 'SeverityText'], ]), + traceColumnMap: new Map([ + [ColumnHint.Time, 'Timestamp'], + [ColumnHint.TraceId, 'TraceId'], + [ColumnHint.TraceSpanId, 'SpanId'], + [ColumnHint.TraceParentSpanId, 'ParentSpanId'], + [ColumnHint.TraceServiceName, 'ServiceName'], + [ColumnHint.TraceOperationName, 'SpanName'], + [ColumnHint.TraceDurationTime, 'Duration'], + [ColumnHint.TraceTags, 'SpanAttributes'], + [ColumnHint.TraceServiceTags, 'ResourceAttributes'], + ]), + traceDurationUnit: TimeUnit.Nanoseconds, }, ]; diff --git a/src/types/queryBuilder.ts b/src/types/queryBuilder.ts index 1d6f9e6e..5f214355 100644 --- a/src/types/queryBuilder.ts +++ b/src/types/queryBuilder.ts @@ -38,14 +38,16 @@ export interface QueryBuilderOptions { */ meta?: { // Logs - otelEnabled?: boolean; - otelVersion?: string; liveView?: boolean; // Trace isTraceSearchMode?: boolean; traceDurationUnit?: TimeUnit; traceId?: string; + + // Logs & Traces + otelEnabled?: boolean; + otelVersion?: string; } } @@ -114,7 +116,6 @@ export enum ColumnHint { TraceParentSpanId = 'trace_parent_span_id', TraceServiceName = 'trace_service_name', TraceOperationName = 'trace_operation_name', - TraceStartTime = 'trace_start_time', TraceDurationTime = 'trace_duration_time', TraceTags = 'trace_tags', TraceServiceTags = 'trace_service_tags', @@ -173,6 +174,10 @@ export interface CommonFilterProps { key: string; type: string; condition: 'AND' | 'OR'; + /** + * Used to uniquely identify a dynamically added filter + */ + id?: string; } export interface NullFilter extends CommonFilterProps { diff --git a/src/views/CHQueryEditor.tsx b/src/views/CHQueryEditor.tsx index 052b17a7..37499307 100644 --- a/src/views/CHQueryEditor.tsx +++ b/src/views/CHQueryEditor.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { QueryEditorProps } from '@grafana/data'; import { Datasource } from 'data/CHDatasource'; import { EditorTypeSwitcher } from 'components/queryBuilder/EditorTypeSwitcher'; @@ -6,7 +6,7 @@ import { styles } from 'styles'; import { Button } from '@grafana/ui'; import { CHQuery, EditorType, CHBuilderQuery, defaultCHBuilderQuery } from 'types/sql'; import { CHConfig } from 'types/config'; -import { QueryBuilderOptions } from 'types/queryBuilder'; +import { QueryBuilderOptions, QueryType } from 'types/queryBuilder'; import { QueryBuilder } from 'components/queryBuilder/QueryBuilder'; import { generateSql } from 'data/sqlGenerator'; import { SqlEditor } from 'components/SqlEditor'; @@ -51,7 +51,30 @@ export const CHQueryEditor = (props: CHQueryEditorProps) => { const CHEditorByType = (props: CHQueryEditorProps) => { const { query, onChange, app } = props; - const onBuilderOptionsChange = (builderOptions: QueryBuilderOptions) => { + const onBuilderOptionsChange = useCallback((nextBuilderOptions: Partial) => { + let builderOptions: QueryBuilderOptions = { + ...(query as CHBuilderQuery).builderOptions, + ...nextBuilderOptions, + meta: { + ...(query as CHBuilderQuery).builderOptions.meta, + ...nextBuilderOptions.meta + }, + }; + + // If switching query type, reset the editor. + // Excludes Table/TimeSeries, since they're similar and less guided. + const prevQueryType = (query as CHBuilderQuery).builderOptions?.queryType; + const nextQueryType = nextBuilderOptions.queryType; + const queryTypeChanged = prevQueryType !== nextQueryType; + const isSwitchingBetweenTableAndTimeSeries = (prevQueryType === QueryType.Table && nextQueryType === QueryType.TimeSeries) || (prevQueryType === QueryType.TimeSeries && nextQueryType === QueryType.Table); + if (nextQueryType && queryTypeChanged && !isSwitchingBetweenTableAndTimeSeries) { + builderOptions = { + ...(query as CHBuilderQuery).builderOptions, + ...defaultCHBuilderQuery.builderOptions, + queryType: nextQueryType + } + } + const sql = generateSql(builderOptions); onChange({ ...query, @@ -60,7 +83,7 @@ const CHEditorByType = (props: CHQueryEditorProps) => { builderOptions, format: mapQueryTypeToGrafanaFormat(builderOptions.queryType) }); - }; + }, [query, onChange]); if (query.editorType === EditorType.SQL) { return ( From bb9211be243a04e71f07761f7fadfa7e23e27a56 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 25 Oct 2023 08:49:11 -0400 Subject: [PATCH 31/95] fix build warnings --- src/components/queryBuilder/views/LogsQueryBuilder.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index 54855d8b..f7eca339 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint, DateFilterWithoutValue, FilterOperator } from 'types/queryBuilder'; +import { Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; import { OtelVersionSelect } from '../OtelVersionSelect'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; From d5c06989e59cf850195395a27d30a80f7ae7a3fb Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Thu, 26 Oct 2023 04:12:20 -0400 Subject: [PATCH 32/95] fix builderOptions state change race condition --- .../queryBuilder/EditorTypeSwitcher.tsx | 2 +- src/hooks/useBuilderOptionChanges.ts | 6 +- src/types/sql.ts | 3 +- src/views/CHQueryEditor.tsx | 87 +++++++++---------- 4 files changed, 46 insertions(+), 52 deletions(-) diff --git a/src/components/queryBuilder/EditorTypeSwitcher.tsx b/src/components/queryBuilder/EditorTypeSwitcher.tsx index a6b6ac8b..f82e0631 100644 --- a/src/components/queryBuilder/EditorTypeSwitcher.tsx +++ b/src/components/queryBuilder/EditorTypeSwitcher.tsx @@ -27,7 +27,7 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { const { query, onChange } = props; const { label, tooltip, switcher, cannotConvert } = labels.components.EditorTypeSwitcher; const editorType: EditorType = query.editorType; - const [editor, setEditor] = useState(editorType); + const [editor, setEditor] = useState(editorType || EditorType.Builder); const [confirmModalState, setConfirmModalState] = useState(false); const [cannotConvertModalState, setCannotConvertModalState] = useState(false); const [errorMessage, setErrorMessage] = useState(''); diff --git a/src/hooks/useBuilderOptionChanges.ts b/src/hooks/useBuilderOptionChanges.ts index 906ea0b2..d6d8e2b3 100644 --- a/src/hooks/useBuilderOptionChanges.ts +++ b/src/hooks/useBuilderOptionChanges.ts @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React from 'react'; type onOptionChangeFn = (key: keyof T | Object) => (nextValue: React.SetStateAction) => void; @@ -13,7 +13,7 @@ type onOptionChangeFn = (key: keyof T | Object) => (nextValue: React.SetState * @returns a function used to apply changes to individual fields */ export function useBuilderOptionChanges(onChange: (nextState: T) => void, prevState: T): onOptionChangeFn { - return useCallback((key: keyof T | Object) => + return (key: keyof T | Object) => (nextValue: React.SetStateAction) => { let nextState: T; if (typeof key === 'object') { @@ -29,5 +29,5 @@ export function useBuilderOptionChanges(onChange: (nextState: T) => void, pre } onChange(nextState); - }, [onChange, prevState]); + }; } diff --git a/src/types/sql.ts b/src/types/sql.ts index c472dbe3..4397efeb 100644 --- a/src/types/sql.ts +++ b/src/types/sql.ts @@ -53,9 +53,8 @@ export const defaultCHBuilderQuery: Omit = { queryType: QueryType.Table, mode: BuilderMode.List, columns: [], + meta: {} }, - // format: Format.TABLE, - // selectedFormat: Format.AUTO, }; export const defaultCHSqlQuery: Omit = { editorType: EditorType.SQL, diff --git a/src/views/CHQueryEditor.tsx b/src/views/CHQueryEditor.tsx index 37499307..9243db0a 100644 --- a/src/views/CHQueryEditor.tsx +++ b/src/views/CHQueryEditor.tsx @@ -1,10 +1,10 @@ -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { QueryEditorProps } from '@grafana/data'; import { Datasource } from 'data/CHDatasource'; import { EditorTypeSwitcher } from 'components/queryBuilder/EditorTypeSwitcher'; import { styles } from 'styles'; import { Button } from '@grafana/ui'; -import { CHQuery, EditorType, CHBuilderQuery, defaultCHBuilderQuery } from 'types/sql'; +import { CHBuilderQuery, CHQuery, EditorType, defaultCHBuilderQuery } from 'types/sql'; import { CHConfig } from 'types/config'; import { QueryBuilderOptions, QueryType } from 'types/queryBuilder'; import { QueryBuilder } from 'components/queryBuilder/QueryBuilder'; @@ -18,25 +18,7 @@ export type CHQueryEditorProps = QueryEditorProps * Top level query editor component */ export const CHQueryEditor = (props: CHQueryEditorProps) => { - const { query, onChange, onRunQuery } = props; - - useEffect(() => { - if (query.editorType) { - return; - } - - onChange({ - ...query as CHQuery, - ...defaultCHBuilderQuery, - builderOptions: { - ...defaultCHBuilderQuery.builderOptions, - }, - }); - }, [query, query.editorType, onChange]); - - if (!query.editorType) { - return null; - } + const { onRunQuery } = props; return ( <> @@ -51,30 +33,42 @@ export const CHQueryEditor = (props: CHQueryEditorProps) => { const CHEditorByType = (props: CHQueryEditorProps) => { const { query, onChange, app } = props; - const onBuilderOptionsChange = useCallback((nextBuilderOptions: Partial) => { - let builderOptions: QueryBuilderOptions = { - ...(query as CHBuilderQuery).builderOptions, - ...nextBuilderOptions, - meta: { - ...(query as CHBuilderQuery).builderOptions.meta, - ...nextBuilderOptions.meta - }, - }; + const [builderOptions, setBuilderOptions] = useState({ + ...defaultCHBuilderQuery.builderOptions, + ...(query as CHBuilderQuery).builderOptions, + meta: { + ...defaultCHBuilderQuery.builderOptions.meta, + ...(query as CHBuilderQuery).builderOptions?.meta + } + }); - // If switching query type, reset the editor. - // Excludes Table/TimeSeries, since they're similar and less guided. - const prevQueryType = (query as CHBuilderQuery).builderOptions?.queryType; - const nextQueryType = nextBuilderOptions.queryType; - const queryTypeChanged = prevQueryType !== nextQueryType; - const isSwitchingBetweenTableAndTimeSeries = (prevQueryType === QueryType.Table && nextQueryType === QueryType.TimeSeries) || (prevQueryType === QueryType.TimeSeries && nextQueryType === QueryType.Table); - if (nextQueryType && queryTypeChanged && !isSwitchingBetweenTableAndTimeSeries) { - builderOptions = { - ...(query as CHBuilderQuery).builderOptions, - ...defaultCHBuilderQuery.builderOptions, - queryType: nextQueryType + const onBuilderOptionsChange = useCallback((nextBuilderOptions: Partial) => { + setBuilderOptions(prevBuilderOptions => { + // If switching query type, reset the editor. + // Excludes Table/TimeSeries, since they're similar and less guided. + const prevQueryType = prevBuilderOptions.queryType; + const nextQueryType = nextBuilderOptions.queryType; + const queryTypeChanged = prevQueryType !== nextQueryType; + const isSwitchingBetweenTableAndTimeSeries = (prevQueryType === QueryType.Table && nextQueryType === QueryType.TimeSeries) || (prevQueryType === QueryType.TimeSeries && nextQueryType === QueryType.Table); + if (nextQueryType && queryTypeChanged && !isSwitchingBetweenTableAndTimeSeries) { + return { + ...defaultCHBuilderQuery.builderOptions, + queryType: nextQueryType + } } - } + return { + ...prevBuilderOptions, + ...nextBuilderOptions, + meta: { + ...prevBuilderOptions.meta, + ...nextBuilderOptions.meta + } + }; + }); + }, []); + + useEffect(() => { const sql = generateSql(builderOptions); onChange({ ...query, @@ -83,7 +77,9 @@ const CHEditorByType = (props: CHQueryEditorProps) => { builderOptions, format: mapQueryTypeToGrafanaFormat(builderOptions.queryType) }); - }, [query, onChange]); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [builderOptions]); if (query.editorType === EditorType.SQL) { return ( @@ -93,13 +89,12 @@ const CHEditorByType = (props: CHQueryEditorProps) => { ); } - const builderQuery: CHBuilderQuery = { ...query }; return ( ); From c4e35bb779964ce1f081b207b70f39591f65c278 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Thu, 26 Oct 2023 04:12:54 -0400 Subject: [PATCH 33/95] enable default column/filter selection --- src/components/queryBuilder/QueryBuilder.tsx | 10 +- .../queryBuilder/views/LogsQueryBuilder.tsx | 93 ++++++++++++------- .../queryBuilder/views/TableQueryBuilder.tsx | 13 +-- .../views/TimeSeriesQueryBuilder.tsx | 13 +-- .../queryBuilder/views/TraceQueryBuilder.tsx | 13 +-- 5 files changed, 82 insertions(+), 60 deletions(-) diff --git a/src/components/queryBuilder/QueryBuilder.tsx b/src/components/queryBuilder/QueryBuilder.tsx index b7761f2a..1b2ee1e1 100644 --- a/src/components/queryBuilder/QueryBuilder.tsx +++ b/src/components/queryBuilder/QueryBuilder.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { Datasource } from 'data/CHDatasource'; import { QueryType, QueryBuilderOptions } from 'types/queryBuilder'; import { CoreApp } from '@grafana/data'; -import useColumns from 'hooks/useColumns'; import { LogsQueryBuilder } from './views/LogsQueryBuilder'; import { TimeSeriesQueryBuilder } from './views/TimeSeriesQueryBuilder'; import { TableQueryBuilder } from './views/TableQueryBuilder'; @@ -22,7 +21,6 @@ interface QueryBuilderProps { export const QueryBuilder = (props: QueryBuilderProps) => { const { datasource, builderOptions, onBuilderOptionsChange, generatedSql } = props; - const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); const onDatabaseChange = (database: string) => onBuilderOptionsChange({ database, table: '' }); const onTableChange = (table: string) => onBuilderOptionsChange({ table }); @@ -41,10 +39,10 @@ export const QueryBuilder = (props: QueryBuilderProps) => {
- { builderOptions.queryType === QueryType.Table && } - { builderOptions.queryType === QueryType.Logs && } - { builderOptions.queryType === QueryType.TimeSeries && } - { builderOptions.queryType === QueryType.Traces && } + { builderOptions.queryType === QueryType.Table && } + { builderOptions.queryType === QueryType.Logs && } + { builderOptions.queryType === QueryType.TimeSeries && } + { builderOptions.queryType === QueryType.Traces && }
diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index f7eca339..51a88bfb 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -1,6 +1,6 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint } from 'types/queryBuilder'; +import { Filter, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint, DateFilterWithoutValue, FilterOperator } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; import { OtelVersionSelect } from '../OtelVersionSelect'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; @@ -13,9 +13,9 @@ import { Datasource } from 'data/CHDatasource'; import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; import { versions as otelVersions } from 'otel'; import { Alert, VerticalGroup } from '@grafana/ui'; +import useColumns from 'hooks/useColumns'; interface LogsQueryBuilderProps { - allColumns: readonly TableColumn[]; datasource: Datasource; builderOptions: QueryBuilderOptions, onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; @@ -35,9 +35,10 @@ interface LogsQueryBuilderState { } export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { - const { allColumns, datasource, builderOptions, onBuilderOptionsChange } = props; + const { datasource, builderOptions, onBuilderOptionsChange } = props; const labels = allLabels.components.LogsQueryBuilder; - const builderState: LogsQueryBuilderState = { + const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); + const builderState: LogsQueryBuilderState = useMemo(() => ({ otelEnabled: builderOptions.meta?.otelEnabled || false, otelVersion: builderOptions.meta?.otelVersion || '', timeColumn: getColumnByHint(builderOptions, ColumnHint.Time), @@ -53,7 +54,7 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { orderBy: builderOptions.orderBy || [], limit: builderOptions.limit || 1000, filters: builderOptions.filters || [], - }; + }), [builderOptions]); const showConfigWarning = datasource.getDefaultLogsColumns().size === 0; function setOtelColumns(builderState: LogsQueryBuilderState) { @@ -95,7 +96,7 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { nextColumns.push(next.messageColumn); } - onBuilderOptionsChange({ + const nextOptions = { columns: nextColumns, filters: next.filters, orderBy: next.orderBy, @@ -104,7 +105,9 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { otelEnabled: next.otelEnabled, otelVersion: next.otelVersion, } - }); + }; + + onBuilderOptionsChange(nextOptions); }, builderState); useEffect(() => { @@ -139,39 +142,57 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // TODO: default filter - // const timeRangeFilter: DateFilterWithoutValue = { - // type: 'date', - // operator: FilterOperator.WithInGrafanaTimeRange, - // filterType: 'custom', - // key: timeColumn.name, - // id: 'timeRange', - // condition: 'AND' - // }; + // Select default time filter on timeColumn change + const lastTimeColumn = useRef(builderState.timeColumn?.name || ''); + useEffect(() => { + if (!builderState.timeColumn) { + return; + } else if ((builderState.timeColumn.name === lastTimeColumn.current) || builderState.filters.find(f => f.id === 'timeRange')) { + return; + } + + const timeRangeFilter: DateFilterWithoutValue = { + type: 'datetime', + operator: FilterOperator.WithInGrafanaTimeRange, + filterType: 'custom', + key: builderState.timeColumn.name, + id: 'timeRange', + condition: 'AND' + }; - // TODO: fix default table selection AND default time column selection - // useEffect(() => { - // if (allColumns.length === 0) { - // return; - // } + lastTimeColumn.current = builderState.timeColumn.name; + onOptionChange('filters')([timeRangeFilter, ...builderState.filters.filter(f => f.id !== 'timeRange')]); + }, [builderState.timeColumn, builderState.filters, onOptionChange]); - // const col = allColumns.filter(columnFilterDateTime)[0]; - // const currentColumnExists = (builderState.timeColumn && allColumns.find(c => c.name === builderState.timeColumn?.name)); - // if (!col || currentColumnExists) { - // return; - // } + // Find and select a default time column, update when table changes + const lastTable = useRef(builderOptions.table); + const defaultTimeSelected = useRef(Boolean(builderState.timeColumn)); + useEffect(() => { + if (builderOptions.table !== lastTable.current) { + defaultTimeSelected.current = false; + } + + if (allColumns.length === 0 || !builderOptions.table || defaultTimeSelected.current) { + return; + } + + const col = allColumns.filter(columnFilterDateTime)[0]; + const currentColumnExists = (builderState.timeColumn && allColumns.find(c => c.name === builderState.timeColumn?.name)); + if (!col || currentColumnExists) { + return; + } - // const timeColumn: SelectedColumn = { - // name: col.name, - // type: col.type, - // hint: ColumnHint.Time - // }; + const timeColumn: SelectedColumn = { + name: col.name, + type: col.type, + hint: ColumnHint.Time + }; - // onOptionChange('timeColumn')(timeColumn); + lastTable.current = builderOptions.table; + defaultTimeSelected.current = true; + onOptionChange('timeColumn')(timeColumn); - // // Find and select a default time column, update when table changes - // // eslint-disable-next-line react-hooks/exhaustive-deps - // }, [allColumns, builderOptions.table]); + }, [allColumns, builderOptions.table, builderState.timeColumn, onOptionChange]); const configWarning = showConfigWarning && ( diff --git a/src/components/queryBuilder/views/TableQueryBuilder.tsx b/src/components/queryBuilder/views/TableQueryBuilder.tsx index fa4b8bd8..900a4d7d 100644 --- a/src/components/queryBuilder/views/TableQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TableQueryBuilder.tsx @@ -1,6 +1,6 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { AggregateColumn, BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, SelectedColumn } from 'types/queryBuilder'; +import { AggregateColumn, BuilderMode, Filter, OrderBy, QueryBuilderOptions, SelectedColumn } from 'types/queryBuilder'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; import { LimitEditor } from '../LimitEditor'; import { FiltersEditor } from '../FilterEditor'; @@ -10,9 +10,9 @@ import { AggregateEditor } from '../AggregateEditor'; import { GroupByEditor } from '../GroupByEditor'; import { Datasource } from 'data/CHDatasource'; import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; +import useColumns from 'hooks/useColumns'; interface TableQueryBuilderProps { - allColumns: readonly TableColumn[]; datasource: Datasource; builderOptions: QueryBuilderOptions, onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; @@ -28,17 +28,18 @@ interface TableQueryBuilderState { } export const TableQueryBuilder = (props: TableQueryBuilderProps) => { - const { allColumns, builderOptions, onBuilderOptionsChange } = props; + const { datasource, builderOptions, onBuilderOptionsChange } = props; + const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); const labels = allLabels.components.TableQueryBuilder; const [isAggregateMode, setAggregateMode] = useState((builderOptions.aggregates?.length || 0) > 0); // Toggle Simple vs Aggregate mode - const builderState: TableQueryBuilderState = { + const builderState: TableQueryBuilderState = useMemo(() => ({ selectedColumns: builderOptions.columns || [], aggregates: builderOptions.aggregates || [], groupBy: builderOptions.groupBy || [], orderBy: builderOptions.orderBy || [], limit: builderOptions.limit || 1000, filters: builderOptions.filters || [], - }; + }), [builderOptions]); const onOptionChange = useBuilderOptionChanges(next => { onBuilderOptionsChange({ diff --git a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx index cec0bd53..fe0fc3ec 100644 --- a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { AggregateColumn, BuilderMode, Filter, TableColumn, OrderBy, QueryBuilderOptions, ColumnHint, SelectedColumn } from 'types/queryBuilder'; +import { AggregateColumn, BuilderMode, Filter, OrderBy, QueryBuilderOptions, ColumnHint, SelectedColumn } from 'types/queryBuilder'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; import { LimitEditor } from '../LimitEditor'; import { FiltersEditor } from '../FilterEditor'; @@ -13,9 +13,9 @@ import { getColumnByHint } from 'components/queryBuilder/utils'; import { columnFilterDateTime } from 'data/columnFilters'; import { Datasource } from 'data/CHDatasource'; import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; +import useColumns from 'hooks/useColumns'; interface TimeSeriesQueryBuilderProps { - allColumns: readonly TableColumn[]; datasource: Datasource; builderOptions: QueryBuilderOptions, onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; @@ -32,10 +32,11 @@ interface TimeSeriesQueryBuilderState { } export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { - const { allColumns, builderOptions, onBuilderOptionsChange } = props; + const { datasource, builderOptions, onBuilderOptionsChange } = props; + const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); const labels = allLabels.components.TimeSeriesQueryBuilder; const [isAggregateMode, setAggregateMode] = useState((builderOptions.aggregates?.length || 0) > 0); // Toggle Simple vs Aggregate mode - const builderState: TimeSeriesQueryBuilderState = { + const builderState: TimeSeriesQueryBuilderState = useMemo(() => ({ timeColumn: getColumnByHint(builderOptions, ColumnHint.Time), selectedColumns: (builderOptions.columns || []).filter(c => c.hint !== ColumnHint.Time), aggregates: builderOptions.aggregates || [], @@ -43,7 +44,7 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { orderBy: builderOptions.orderBy || [], limit: builderOptions.limit || 1000, filters: builderOptions.filters || [], - }; + }), [builderOptions]); const onOptionChange = useBuilderOptionChanges(next => { const nextColumns = next.selectedColumns.slice(); diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index f5b3e254..5d6a250a 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useState } from 'react'; -import { Filter, TableColumn, QueryBuilderOptions, SelectedColumn, ColumnHint, TimeUnit } from 'types/queryBuilder'; +import React, { useEffect, useMemo, useState } from 'react'; +import { Filter, QueryBuilderOptions, SelectedColumn, ColumnHint, TimeUnit } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; import { FiltersEditor } from '../FilterEditor'; import allLabels from 'labels'; @@ -9,9 +9,9 @@ import { Alert, Collapse, InlineFormLabel, Input, VerticalGroup } from '@grafana import { DurationUnitSelect } from 'components/queryBuilder/DurationUnitSelect'; import { Datasource } from 'data/CHDatasource'; import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; +import useColumns from 'hooks/useColumns'; interface TraceQueryBuilderProps { - allColumns: readonly TableColumn[]; datasource: Datasource; builderOptions: QueryBuilderOptions, onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; @@ -34,12 +34,13 @@ interface TraceQueryBuilderState { } export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { - const { allColumns, datasource, builderOptions, onBuilderOptionsChange } = props; + const { datasource, builderOptions, onBuilderOptionsChange } = props; + const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); const showConfigWarning = datasource.getDefaultTraceColumns().size === 0; const [isColumnsOpen, setColumnsOpen] = useState(showConfigWarning); // Toggle Columns collapsable section const [isFiltersOpen, setFiltersOpen] = useState(true); // Toggle Filters collapsable section const labels = allLabels.components.TraceQueryBuilder; - const builderState: TraceQueryBuilderState = { + const builderState: TraceQueryBuilderState = useMemo(() => ({ isSearchMode: builderOptions.meta?.isTraceSearchMode || false, traceIdColumn: getColumnByHint(builderOptions, ColumnHint.TraceId), spanIdColumn: getColumnByHint(builderOptions, ColumnHint.TraceSpanId), @@ -53,7 +54,7 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { serviceTagsColumn: getColumnByHint(builderOptions, ColumnHint.TraceServiceTags), traceId: builderOptions.meta?.traceId || '', filters: builderOptions.filters || [], - }; + }), [builderOptions]); useEffect(() => { const shouldApplyDefaults = (builderOptions.columns || []).length === 0 && (builderOptions.orderBy || []).length === 0; From 48b0af9e8f4224deaff692df2e43575f11ac1535 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Thu, 26 Oct 2023 06:15:55 -0400 Subject: [PATCH 34/95] default order by + filter log view, timeseries --- .../queryBuilder/views/LogsQueryBuilder.tsx | 47 +++++++++++++++---- .../views/TimeSeriesQueryBuilder.tsx | 43 ++++++++++++++--- src/data/sqlGenerator.ts | 2 +- src/types/queryBuilder.ts | 1 + 4 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index 51a88bfb..313bd6d7 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useRef } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { Filter, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint, DateFilterWithoutValue, FilterOperator } from 'types/queryBuilder'; +import { Filter, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint, DateFilterWithoutValue, FilterOperator, OrderByDirection } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; import { OtelVersionSelect } from '../OtelVersionSelect'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; @@ -126,12 +126,29 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { nextColumns.push({ name: colName, hint }); } + const nextFilters: Filter[] = []; + const nextOrderBy: OrderBy[] = []; + if (defaultColumns.has(ColumnHint.Time)) { + const timeRangeFilter: DateFilterWithoutValue = { + type: 'datetime', + operator: FilterOperator.WithInGrafanaTimeRange, + filterType: 'custom', + key: defaultColumns.get(ColumnHint.Time)!, + id: 'timeRange', + condition: 'AND' + }; + nextFilters.push(timeRangeFilter); + + const defaultOrderBy: OrderBy = { name: defaultColumns.get(ColumnHint.Time)!, dir: OrderByDirection.DESC, default: true }; + nextOrderBy.push(defaultOrderBy); + } + onBuilderOptionsChange({ database: defaultDb, table: defaultTable || builderOptions.table, columns: nextColumns, - // filters, - // orderBy, + filters: nextFilters, + orderBy: nextOrderBy, meta: { otelEnabled: Boolean(otelVersion), otelVersion, @@ -142,15 +159,16 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // Select default time filter on timeColumn change + // Apply default filters/orderBy on timeColumn change const lastTimeColumn = useRef(builderState.timeColumn?.name || ''); useEffect(() => { if (!builderState.timeColumn) { return; - } else if ((builderState.timeColumn.name === lastTimeColumn.current) || builderState.filters.find(f => f.id === 'timeRange')) { + } else if ((builderState.timeColumn.name === lastTimeColumn.current)) { return; } + const nextFilters: Filter[] = builderState.filters.filter(f => f.id !== 'timeRange'); const timeRangeFilter: DateFilterWithoutValue = { type: 'datetime', operator: FilterOperator.WithInGrafanaTimeRange, @@ -159,15 +177,27 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { id: 'timeRange', condition: 'AND' }; + nextFilters.unshift(timeRangeFilter); + const nextOrderBy: OrderBy[] = builderState.orderBy.filter(o => !o.default); + const defaultOrderBy: OrderBy = { name: builderState.timeColumn?.name, dir: OrderByDirection.DESC, default: true }; + nextOrderBy.unshift(defaultOrderBy); + lastTimeColumn.current = builderState.timeColumn.name; - onOptionChange('filters')([timeRangeFilter, ...builderState.filters.filter(f => f.id !== 'timeRange')]); - }, [builderState.timeColumn, builderState.filters, onOptionChange]); + onBuilderOptionsChange({ + filters: nextFilters, + orderBy: nextOrderBy + }); + }, [builderState.timeColumn, builderState.filters, builderState.orderBy, onBuilderOptionsChange]); // Find and select a default time column, update when table changes const lastTable = useRef(builderOptions.table); const defaultTimeSelected = useRef(Boolean(builderState.timeColumn)); useEffect(() => { + if (datasource.getDefaultLogsTable() === builderOptions.table && datasource.getDefaultLogsColumns().has(ColumnHint.Time)) { + return; + } + if (builderOptions.table !== lastTable.current) { defaultTimeSelected.current = false; } @@ -191,8 +221,7 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { lastTable.current = builderOptions.table; defaultTimeSelected.current = true; onOptionChange('timeColumn')(timeColumn); - - }, [allColumns, builderOptions.table, builderState.timeColumn, onOptionChange]); + }, [allColumns, datasource, builderOptions.table, builderState.timeColumn, onOptionChange]); const configWarning = showConfigWarning && ( diff --git a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx index fe0fc3ec..13a0fdfe 100644 --- a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { AggregateColumn, BuilderMode, Filter, OrderBy, QueryBuilderOptions, ColumnHint, SelectedColumn } from 'types/queryBuilder'; +import { AggregateColumn, BuilderMode, Filter, OrderBy, QueryBuilderOptions, ColumnHint, SelectedColumn, DateFilterWithoutValue, FilterOperator } from 'types/queryBuilder'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; import { LimitEditor } from '../LimitEditor'; import { FiltersEditor } from '../FilterEditor'; @@ -53,7 +53,7 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { } onBuilderOptionsChange({ - mode: isAggregateMode ? BuilderMode.Aggregate : BuilderMode.List, + mode: isAggregateMode ? BuilderMode.Aggregate : BuilderMode.Trend, columns: nextColumns, aggregates: isAggregateMode ? next.aggregates : [], groupBy: isAggregateMode ? next.groupBy : [], @@ -63,8 +63,37 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { }); }, builderState); + // Select default time filter on timeColumn change + const lastTimeColumn = useRef(builderState.timeColumn?.name || ''); useEffect(() => { - if (allColumns.length === 0) { + if (!builderState.timeColumn) { + return; + } else if ((builderState.timeColumn.name === lastTimeColumn.current) || builderState.filters.find(f => f.id === 'timeRange')) { + return; + } + + const timeRangeFilter: DateFilterWithoutValue = { + type: 'datetime', + operator: FilterOperator.WithInGrafanaTimeRange, + filterType: 'custom', + key: builderState.timeColumn.name, + id: 'timeRange', + condition: 'AND' + }; + + lastTimeColumn.current = builderState.timeColumn.name; + onOptionChange('filters')([timeRangeFilter, ...builderState.filters.filter(f => f.id !== 'timeRange')]); + }, [builderState.timeColumn, builderState.filters, onOptionChange]); + + // Find and select a default time column, update when table changes + const lastTable = useRef(builderOptions.table); + const defaultTimeSelected = useRef(Boolean(builderState.timeColumn)); + useEffect(() => { + if (builderOptions.table !== lastTable.current) { + defaultTimeSelected.current = false; + } + + if (allColumns.length === 0 || !builderOptions.table || defaultTimeSelected.current) { return; } @@ -80,11 +109,11 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { hint: ColumnHint.Time }; + lastTable.current = builderOptions.table; + defaultTimeSelected.current = true; onOptionChange('timeColumn')(timeColumn); - // Find and select a default time column, update when table changes - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [allColumns, builderOptions.table]); + }, [allColumns, builderOptions.table, builderState.timeColumn, onOptionChange]); return (
diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index 7c1132d0..6fc605f0 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -192,7 +192,7 @@ const getColumnIdentifier = (col: SelectedColumn): string => { } // allow for functions like count() - if (colName.includes('(') || colName.includes(')')) { + if (colName.includes('(') || colName.includes(')') || colName.includes('"') || colName.includes('"')) { colName = col.name } diff --git a/src/types/queryBuilder.ts b/src/types/queryBuilder.ts index 5f214355..a4055231 100644 --- a/src/types/queryBuilder.ts +++ b/src/types/queryBuilder.ts @@ -150,6 +150,7 @@ export enum OrderByDirection { export interface OrderBy { name: string; dir: OrderByDirection; + default?: boolean; } export enum FilterOperator { From 24f8d9857742b27b87b915cbc5ca3a834b219ed9 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 31 Oct 2023 04:01:35 -0400 Subject: [PATCH 35/95] upgrade state to useReducer, fix effects --- .../queryBuilder/DatabaseTableSelect.tsx | 1 - .../queryBuilder/QueryBuilder.test.tsx | 2 +- src/components/queryBuilder/QueryBuilder.tsx | 19 +- src/components/queryBuilder/utils.ts | 2 +- .../queryBuilder/views/LogsQueryBuilder.tsx | 316 +++++++++--------- .../queryBuilder/views/TableQueryBuilder.tsx | 11 +- .../views/TimeSeriesQueryBuilder.tsx | 127 +++---- .../queryBuilder/views/TraceQueryBuilder.tsx | 86 ++--- src/data/utils.ts | 16 +- src/hooks/useBuilderOptionsState.ts | 144 ++++++++ src/hooks/useColumns.ts | 35 +- src/hooks/useDatabases.ts | 2 +- src/hooks/useIsNewQuery.ts | 14 + src/hooks/useTables.ts | 28 +- src/types/queryBuilder.ts | 2 + src/types/sql.ts | 3 +- src/views/CHQueryEditor.tsx | 64 ++-- 17 files changed, 550 insertions(+), 322 deletions(-) create mode 100644 src/hooks/useBuilderOptionsState.ts create mode 100644 src/hooks/useIsNewQuery.ts diff --git a/src/components/queryBuilder/DatabaseTableSelect.tsx b/src/components/queryBuilder/DatabaseTableSelect.tsx index 6da1875c..76328086 100644 --- a/src/components/queryBuilder/DatabaseTableSelect.tsx +++ b/src/components/queryBuilder/DatabaseTableSelect.tsx @@ -72,7 +72,6 @@ export const TableSelect = (props: TableSelectProps) => { useEffect(() => { // Auto select first/default table - // TODO: this still seems to lag behind when switching DB, probably due to async table fetch if (database && !table && tables.length > 0) { onTableChange(datasource.getDefaultTable() || tables[0]); } diff --git a/src/components/queryBuilder/QueryBuilder.test.tsx b/src/components/queryBuilder/QueryBuilder.test.tsx index 4fa70933..5519965c 100644 --- a/src/components/queryBuilder/QueryBuilder.test.tsx +++ b/src/components/queryBuilder/QueryBuilder.test.tsx @@ -29,7 +29,7 @@ describe('QueryBuilder', () => { columns: [], filters: [], }} - onBuilderOptionsChange={() => {}} + builderOptionsDispatch={() => {}} datasource={mockDs} generatedSql='' /> diff --git a/src/components/queryBuilder/QueryBuilder.tsx b/src/components/queryBuilder/QueryBuilder.tsx index 1b2ee1e1..791548bd 100644 --- a/src/components/queryBuilder/QueryBuilder.tsx +++ b/src/components/queryBuilder/QueryBuilder.tsx @@ -10,21 +10,22 @@ import { DatabaseTableSelect } from 'components/queryBuilder/DatabaseTableSelect import { QueryTypeSwitcher } from 'components/queryBuilder/QueryTypeSwitcher'; import { styles } from 'styles'; import { TraceQueryBuilder } from './views/TraceQueryBuilder'; +import { BuilderOptionsReducerAction, setDatabase, setQueryType, setTable } from 'hooks/useBuilderOptionsState'; interface QueryBuilderProps { app: CoreApp | undefined; builderOptions: QueryBuilderOptions; - onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; + builderOptionsDispatch: React.Dispatch, datasource: Datasource; generatedSql: string; } export const QueryBuilder = (props: QueryBuilderProps) => { - const { datasource, builderOptions, onBuilderOptionsChange, generatedSql } = props; + const { datasource, builderOptions, builderOptionsDispatch, generatedSql } = props; - const onDatabaseChange = (database: string) => onBuilderOptionsChange({ database, table: '' }); - const onTableChange = (table: string) => onBuilderOptionsChange({ table }); - const onQueryTypeChange = (queryType: QueryType) => onBuilderOptionsChange({ queryType }); + const onDatabaseChange = (database: string) => builderOptionsDispatch(setDatabase(database)); + const onTableChange = (table: string) => builderOptionsDispatch(setTable(table)); + const onQueryTypeChange = (queryType: QueryType) => builderOptionsDispatch(setQueryType(queryType)); return (
@@ -39,10 +40,10 @@ export const QueryBuilder = (props: QueryBuilderProps) => {
- { builderOptions.queryType === QueryType.Table && } - { builderOptions.queryType === QueryType.Logs && } - { builderOptions.queryType === QueryType.TimeSeries && } - { builderOptions.queryType === QueryType.Traces && } + { builderOptions.queryType === QueryType.Table && } + { builderOptions.queryType === QueryType.Logs && } + { builderOptions.queryType === QueryType.TimeSeries && } + { builderOptions.queryType === QueryType.Traces && }
diff --git a/src/components/queryBuilder/utils.ts b/src/components/queryBuilder/utils.ts index 3a9ca1a8..cb9c9422 100644 --- a/src/components/queryBuilder/utils.ts +++ b/src/components/queryBuilder/utils.ts @@ -245,7 +245,7 @@ export const getSqlFromQueryBuilderOptions = (options: QueryBuilderOptions): str case BuilderMode.Trend: const timeColumn = getColumnByHint(options, ColumnHint.Time); if (!isDateType(timeColumn?.type || '')) { - throw new Error('timeFieldType is expected to be valid Date type.'); + throw new Error('time column is expected to be a valid Date type.'); } query += getTrendByQuery( database, diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index 313bd6d7..bfab873e 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useRef } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { Filter, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint, DateFilterWithoutValue, FilterOperator, OrderByDirection } from 'types/queryBuilder'; +import { Filter, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint, DateFilterWithoutValue, FilterOperator, OrderByDirection, TableColumn } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; import { OtelVersionSelect } from '../OtelVersionSelect'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; @@ -14,11 +14,13 @@ import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; import { versions as otelVersions } from 'otel'; import { Alert, VerticalGroup } from '@grafana/ui'; import useColumns from 'hooks/useColumns'; +import { BuilderOptionsReducerAction, setColumnByHint, setOptions, setOtelEnabled, setOtelVersion } from 'hooks/useBuilderOptionsState'; +import useIsNewQuery from 'hooks/useIsNewQuery'; interface LogsQueryBuilderProps { datasource: Datasource; - builderOptions: QueryBuilderOptions, - onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; + builderOptions: QueryBuilderOptions; + builderOptionsDispatch: React.Dispatch; } interface LogsQueryBuilderState { @@ -35,9 +37,10 @@ interface LogsQueryBuilderState { } export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { - const { datasource, builderOptions, onBuilderOptionsChange } = props; + const { datasource, builderOptions, builderOptionsDispatch } = props; const labels = allLabels.components.LogsQueryBuilder; const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); + const isNewQuery = useIsNewQuery(builderOptions); const builderState: LogsQueryBuilderState = useMemo(() => ({ otelEnabled: builderOptions.meta?.otelEnabled || false, otelVersion: builderOptions.meta?.otelVersion || '', @@ -51,40 +54,13 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { c.hint !== ColumnHint.LogMessage )) || [], // liveView: builderOptions.meta?.liveView || false, + filters: builderOptions.filters || [], orderBy: builderOptions.orderBy || [], limit: builderOptions.limit || 1000, - filters: builderOptions.filters || [], }), [builderOptions]); const showConfigWarning = datasource.getDefaultLogsColumns().size === 0; - function setOtelColumns(builderState: LogsQueryBuilderState) { - if (!builderState.otelEnabled) { - return; - } - - const otelConfig = otelVersions.find(v => v.version === builderState.otelVersion); - const logColumnMap = otelConfig?.logColumnMap; - if (!otelConfig || !logColumnMap) { - return; - } - - builderState.selectedColumns = []; - if (logColumnMap.has(ColumnHint.Time)) { - builderState.timeColumn = { name: logColumnMap.get(ColumnHint.Time)!, hint: ColumnHint.Time }; - } - if (logColumnMap.has(ColumnHint.LogLevel)) { - builderState.logLevelColumn = { name: logColumnMap.get(ColumnHint.LogLevel)!, hint: ColumnHint.LogLevel }; - } - if (logColumnMap.has(ColumnHint.LogMessage)) { - builderState.messageColumn = { name: logColumnMap.get(ColumnHint.LogMessage)!, hint: ColumnHint.LogMessage }; - } - } - const onOptionChange = useBuilderOptionChanges(next => { - if (next.otelEnabled) { - setOtelColumns(next); - } - const nextColumns = next.selectedColumns.slice(); if (next.timeColumn) { nextColumns.push(next.timeColumn); @@ -96,132 +72,18 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { nextColumns.push(next.messageColumn); } - const nextOptions = { + builderOptionsDispatch(setOptions({ columns: nextColumns, filters: next.filters, orderBy: next.orderBy, - limit: next.limit, - meta: { - otelEnabled: next.otelEnabled, - otelVersion: next.otelVersion, - } - }; - - onBuilderOptionsChange(nextOptions); + limit: next.limit + })); }, builderState); - useEffect(() => { - const shouldApplyDefaults = (builderOptions.columns || []).length === 0 && (builderOptions.orderBy || []).length === 0; - if (!shouldApplyDefaults) { - return; - } - - const defaultDb = datasource.getDefaultLogsDatabase() || datasource.getDefaultDatabase(); - const defaultTable = datasource.getDefaultLogsTable() || datasource.getDefaultTable(); - const otelVersion = datasource.getLogsOtelVersion(); - const defaultColumns = datasource.getDefaultLogsColumns(); - - const nextColumns: SelectedColumn[] = []; - for (let [hint, colName] of defaultColumns) { - nextColumns.push({ name: colName, hint }); - } - - const nextFilters: Filter[] = []; - const nextOrderBy: OrderBy[] = []; - if (defaultColumns.has(ColumnHint.Time)) { - const timeRangeFilter: DateFilterWithoutValue = { - type: 'datetime', - operator: FilterOperator.WithInGrafanaTimeRange, - filterType: 'custom', - key: defaultColumns.get(ColumnHint.Time)!, - id: 'timeRange', - condition: 'AND' - }; - nextFilters.push(timeRangeFilter); - - const defaultOrderBy: OrderBy = { name: defaultColumns.get(ColumnHint.Time)!, dir: OrderByDirection.DESC, default: true }; - nextOrderBy.push(defaultOrderBy); - } - - onBuilderOptionsChange({ - database: defaultDb, - table: defaultTable || builderOptions.table, - columns: nextColumns, - filters: nextFilters, - orderBy: nextOrderBy, - meta: { - otelEnabled: Boolean(otelVersion), - otelVersion, - } - }); - - // Run on load - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // Apply default filters/orderBy on timeColumn change - const lastTimeColumn = useRef(builderState.timeColumn?.name || ''); - useEffect(() => { - if (!builderState.timeColumn) { - return; - } else if ((builderState.timeColumn.name === lastTimeColumn.current)) { - return; - } - - const nextFilters: Filter[] = builderState.filters.filter(f => f.id !== 'timeRange'); - const timeRangeFilter: DateFilterWithoutValue = { - type: 'datetime', - operator: FilterOperator.WithInGrafanaTimeRange, - filterType: 'custom', - key: builderState.timeColumn.name, - id: 'timeRange', - condition: 'AND' - }; - nextFilters.unshift(timeRangeFilter); - - const nextOrderBy: OrderBy[] = builderState.orderBy.filter(o => !o.default); - const defaultOrderBy: OrderBy = { name: builderState.timeColumn?.name, dir: OrderByDirection.DESC, default: true }; - nextOrderBy.unshift(defaultOrderBy); - - lastTimeColumn.current = builderState.timeColumn.name; - onBuilderOptionsChange({ - filters: nextFilters, - orderBy: nextOrderBy - }); - }, [builderState.timeColumn, builderState.filters, builderState.orderBy, onBuilderOptionsChange]); - - // Find and select a default time column, update when table changes - const lastTable = useRef(builderOptions.table); - const defaultTimeSelected = useRef(Boolean(builderState.timeColumn)); - useEffect(() => { - if (datasource.getDefaultLogsTable() === builderOptions.table && datasource.getDefaultLogsColumns().has(ColumnHint.Time)) { - return; - } - - if (builderOptions.table !== lastTable.current) { - defaultTimeSelected.current = false; - } - - if (allColumns.length === 0 || !builderOptions.table || defaultTimeSelected.current) { - return; - } - - const col = allColumns.filter(columnFilterDateTime)[0]; - const currentColumnExists = (builderState.timeColumn && allColumns.find(c => c.name === builderState.timeColumn?.name)); - if (!col || currentColumnExists) { - return; - } - - const timeColumn: SelectedColumn = { - name: col.name, - type: col.type, - hint: ColumnHint.Time - }; - - lastTable.current = builderOptions.table; - defaultTimeSelected.current = true; - onOptionChange('timeColumn')(timeColumn); - }, [allColumns, datasource, builderOptions.table, builderState.timeColumn, onOptionChange]); + useLogDefaultsOnMount(datasource, isNewQuery, builderOptions, builderOptionsDispatch); + useOtelColumns(builderState.otelEnabled, builderState.otelVersion, builderOptionsDispatch); + useDefaultTimeColumn(datasource, allColumns, builderOptions.table, builderState.timeColumn, builderState.otelEnabled, builderOptionsDispatch); + useDefaultFilters(builderOptions.table, builderState.timeColumn, builderState.filters, builderState.orderBy, builderOptionsDispatch); const configWarning = showConfigWarning && ( @@ -239,9 +101,9 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { { configWarning } builderOptionsDispatch(setOtelEnabled(e))} selectedVersion={builderState.otelVersion} - onVersionChange={onOptionChange('otelVersion')} + onVersionChange={v => builderOptionsDispatch(setOtelVersion(v))} defaultToLatest /> { disabled={builderState.otelEnabled} allColumns={allColumns} selectedColumn={builderState.logLevelColumn} + invalid={!builderState.logLevelColumn} onColumnChange={onOptionChange('logLevelColumn')} columnFilterFn={columnFilterString} columnHint={ColumnHint.LogLevel} @@ -308,3 +171,146 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => {
); } + +/** + * Loads the default configuration for new queries. (Only runs on new queries) + */ +const useLogDefaultsOnMount = (datasource: Datasource, isNewQuery: boolean, builderOptions: QueryBuilderOptions, builderOptionsDispatch: React.Dispatch) => { + const didSetDefaults = useRef(false); + useEffect(() => { + if (!isNewQuery || didSetDefaults.current) { + return; + } + + const defaultDb = datasource.getDefaultLogsDatabase() || datasource.getDefaultDatabase(); + const defaultTable = datasource.getDefaultLogsTable() || datasource.getDefaultTable(); + const otelVersion = datasource.getLogsOtelVersion(); + const defaultColumns = datasource.getDefaultLogsColumns(); + + const nextColumns: SelectedColumn[] = []; + for (let [hint, colName] of defaultColumns) { + nextColumns.push({ name: colName, hint }); + } + + builderOptionsDispatch(setOptions({ + database: defaultDb, + table: defaultTable || builderOptions.table, + columns: nextColumns, + meta: { + otelEnabled: Boolean(otelVersion), + otelVersion, + } + })); + didSetDefaults.current = true; + }, [builderOptions.columns, builderOptions.orderBy, builderOptions.table, builderOptionsDispatch, datasource, isNewQuery]); +}; + +/** + * Sets OTEL Logs columns automatically when OTEL is enabled + */ +const useOtelColumns = (otelEnabled: boolean, otelVersion: string, builderOptionsDispatch: React.Dispatch) => { + const didSetColumns = useRef(otelEnabled); + if (!otelEnabled) { + didSetColumns.current = false; + } + + useEffect(() => { + if (!otelEnabled || didSetColumns.current) { + return; + } + + const otelConfig = otelVersions.find(v => v.version === otelVersion); + const logColumnMap = otelConfig?.logColumnMap; + if (!otelConfig || !logColumnMap) { + return; + } + + const columns: SelectedColumn[] = []; + if (logColumnMap.has(ColumnHint.Time)) { + columns.push({ name: logColumnMap.get(ColumnHint.Time)!, hint: ColumnHint.Time }); + } + if (logColumnMap.has(ColumnHint.LogLevel)) { + columns.push({ name: logColumnMap.get(ColumnHint.LogLevel)!, hint: ColumnHint.LogLevel }); + } + if (logColumnMap.has(ColumnHint.LogMessage)) { + columns.push({ name: logColumnMap.get(ColumnHint.LogMessage)!, hint: ColumnHint.LogMessage }); + } + + builderOptionsDispatch(setOptions({ columns })); + didSetColumns.current = true; + }, [otelEnabled, otelVersion, builderOptionsDispatch]); +}; + +// Finds and selects a default log time column, updates when table changes +const useDefaultTimeColumn = (datasource: Datasource, allColumns: readonly TableColumn[], table: string, timeColumn: SelectedColumn | undefined, otelEnabled: boolean, builderOptionsDispatch: React.Dispatch) => { + const hasDefaultColumnConfigured = useMemo(() => Boolean(datasource.getDefaultLogsTable()) && datasource.getDefaultLogsColumns().has(ColumnHint.Time), [datasource]); + const didSetDefaultTime = useRef(Boolean(timeColumn) || hasDefaultColumnConfigured); + const lastTable = useRef(table || ''); + if (table !== lastTable.current) { + didSetDefaultTime.current = false; + } + + if (otelEnabled) { + lastTable.current = table; + didSetDefaultTime.current = true; + } + + useEffect(() => { + if (didSetDefaultTime.current || allColumns.length === 0 || !table) { + return; + } + + const col = allColumns.filter(columnFilterDateTime)[0]; + if (!col) { + return; + } + + const timeColumn: SelectedColumn = { + name: col.name, + type: col.type, + hint: ColumnHint.Time + }; + + builderOptionsDispatch(setColumnByHint(timeColumn)); + lastTable.current = table; + didSetDefaultTime.current = true; + }, [datasource, allColumns, table, builderOptionsDispatch]); +}; + +// Apply default filters/orderBy on timeColumn change +const timeRangeFilterId = 'timeRange'; +const useDefaultFilters = (table: string, timeColumn: SelectedColumn | undefined, filters: Filter[], orderBy: OrderBy[], builderOptionsDispatch: React.Dispatch) => { + const lastTimeColumn = useRef(timeColumn?.name || ''); + const lastTable = useRef(table || ''); + if (!timeColumn || table !== lastTable.current) { + lastTimeColumn.current = ''; + } + + useEffect(() => { + if (!timeColumn || (timeColumn.name === lastTimeColumn.current) || !table) { + return; + } + + const nextFilters: Filter[] = filters.filter(f => f.id !== timeRangeFilterId); + const timeRangeFilter: DateFilterWithoutValue = { + type: 'datetime', + operator: FilterOperator.WithInGrafanaTimeRange, + filterType: 'custom', + key: timeColumn.name, + id: timeRangeFilterId, + condition: 'AND' + }; + nextFilters.unshift(timeRangeFilter); + + const nextOrderBy: OrderBy[] = orderBy.filter(o => !o.default); + const defaultOrderBy: OrderBy = { name: timeColumn?.name, dir: OrderByDirection.DESC, default: true }; + nextOrderBy.unshift(defaultOrderBy); + + lastTable.current = table; + lastTimeColumn.current = timeColumn.name; + builderOptionsDispatch(setOptions({ + filters: nextFilters, + orderBy: nextOrderBy + })); + }, [table, timeColumn, filters, orderBy, builderOptionsDispatch]); +}; diff --git a/src/components/queryBuilder/views/TableQueryBuilder.tsx b/src/components/queryBuilder/views/TableQueryBuilder.tsx index 900a4d7d..83d7b827 100644 --- a/src/components/queryBuilder/views/TableQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TableQueryBuilder.tsx @@ -11,11 +11,12 @@ import { GroupByEditor } from '../GroupByEditor'; import { Datasource } from 'data/CHDatasource'; import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; import useColumns from 'hooks/useColumns'; +import { BuilderOptionsReducerAction, setOptions } from 'hooks/useBuilderOptionsState'; interface TableQueryBuilderProps { datasource: Datasource; - builderOptions: QueryBuilderOptions, - onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; + builderOptions: QueryBuilderOptions; + builderOptionsDispatch: React.Dispatch; } interface TableQueryBuilderState { @@ -28,7 +29,7 @@ interface TableQueryBuilderState { } export const TableQueryBuilder = (props: TableQueryBuilderProps) => { - const { datasource, builderOptions, onBuilderOptionsChange } = props; + const { datasource, builderOptions, builderOptionsDispatch } = props; const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); const labels = allLabels.components.TableQueryBuilder; const [isAggregateMode, setAggregateMode] = useState((builderOptions.aggregates?.length || 0) > 0); // Toggle Simple vs Aggregate mode @@ -42,7 +43,7 @@ export const TableQueryBuilder = (props: TableQueryBuilderProps) => { }), [builderOptions]); const onOptionChange = useBuilderOptionChanges(next => { - onBuilderOptionsChange({ + builderOptionsDispatch(setOptions({ mode: isAggregateMode ? BuilderMode.Aggregate : BuilderMode.List, columns: next.selectedColumns, aggregates: isAggregateMode ? next.aggregates : [], @@ -50,7 +51,7 @@ export const TableQueryBuilder = (props: TableQueryBuilderProps) => { filters: next.filters, orderBy: next.orderBy, limit: next.limit - }); + })); }, builderState); return ( diff --git a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx index 13a0fdfe..7c1eaf82 100644 --- a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { AggregateColumn, BuilderMode, Filter, OrderBy, QueryBuilderOptions, ColumnHint, SelectedColumn, DateFilterWithoutValue, FilterOperator } from 'types/queryBuilder'; +import { AggregateColumn, BuilderMode, Filter, OrderBy, QueryBuilderOptions, ColumnHint, SelectedColumn, DateFilterWithoutValue, FilterOperator, TableColumn } from 'types/queryBuilder'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; import { LimitEditor } from '../LimitEditor'; import { FiltersEditor } from '../FilterEditor'; @@ -14,11 +14,12 @@ import { columnFilterDateTime } from 'data/columnFilters'; import { Datasource } from 'data/CHDatasource'; import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; import useColumns from 'hooks/useColumns'; +import { BuilderOptionsReducerAction, setColumnByHint, setOptions } from 'hooks/useBuilderOptionsState'; interface TimeSeriesQueryBuilderProps { datasource: Datasource; builderOptions: QueryBuilderOptions, - onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; + builderOptionsDispatch: React.Dispatch, } interface TimeSeriesQueryBuilderState { @@ -32,7 +33,7 @@ interface TimeSeriesQueryBuilderState { } export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { - const { datasource, builderOptions, onBuilderOptionsChange } = props; + const { datasource, builderOptions, builderOptionsDispatch } = props; const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); const labels = allLabels.components.TimeSeriesQueryBuilder; const [isAggregateMode, setAggregateMode] = useState((builderOptions.aggregates?.length || 0) > 0); // Toggle Simple vs Aggregate mode @@ -52,7 +53,7 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { nextColumns.push(next.timeColumn); } - onBuilderOptionsChange({ + builderOptionsDispatch(setOptions({ mode: isAggregateMode ? BuilderMode.Aggregate : BuilderMode.Trend, columns: nextColumns, aggregates: isAggregateMode ? next.aggregates : [], @@ -60,60 +61,11 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { filters: next.filters, orderBy: next.orderBy, limit: next.limit - }); + })); }, builderState); - // Select default time filter on timeColumn change - const lastTimeColumn = useRef(builderState.timeColumn?.name || ''); - useEffect(() => { - if (!builderState.timeColumn) { - return; - } else if ((builderState.timeColumn.name === lastTimeColumn.current) || builderState.filters.find(f => f.id === 'timeRange')) { - return; - } - - const timeRangeFilter: DateFilterWithoutValue = { - type: 'datetime', - operator: FilterOperator.WithInGrafanaTimeRange, - filterType: 'custom', - key: builderState.timeColumn.name, - id: 'timeRange', - condition: 'AND' - }; - - lastTimeColumn.current = builderState.timeColumn.name; - onOptionChange('filters')([timeRangeFilter, ...builderState.filters.filter(f => f.id !== 'timeRange')]); - }, [builderState.timeColumn, builderState.filters, onOptionChange]); - - // Find and select a default time column, update when table changes - const lastTable = useRef(builderOptions.table); - const defaultTimeSelected = useRef(Boolean(builderState.timeColumn)); - useEffect(() => { - if (builderOptions.table !== lastTable.current) { - defaultTimeSelected.current = false; - } - - if (allColumns.length === 0 || !builderOptions.table || defaultTimeSelected.current) { - return; - } - - const col = allColumns.filter(columnFilterDateTime)[0]; - const currentColumnExists = (builderState.timeColumn && allColumns.find(c => c.name === builderState.timeColumn?.name)); - if (!col || currentColumnExists) { - return; - } - - const timeColumn: SelectedColumn = { - name: col.name, - type: col.type, - hint: ColumnHint.Time - }; - - lastTable.current = builderOptions.table; - defaultTimeSelected.current = true; - onOptionChange('timeColumn')(timeColumn); - - }, [allColumns, builderOptions.table, builderState.timeColumn, onOptionChange]); + useDefaultTimeColumn(allColumns, builderOptions.table, builderState.timeColumn, builderOptionsDispatch); + useDefaultFilters(builderOptions.table, builderState.timeColumn, builderState.filters, builderOptionsDispatch); return (
@@ -159,3 +111,66 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => {
); } + +// Finds and selects a default log time column, updates when table changes +const useDefaultTimeColumn = (allColumns: readonly TableColumn[], table: string, timeColumn: SelectedColumn | undefined, builderOptionsDispatch: React.Dispatch) => { + const didSetDefaultTime = useRef(Boolean(timeColumn)); + const lastTable = useRef(table || ''); + if (table !== lastTable.current) { + didSetDefaultTime.current = false; + } + + useEffect(() => { + if (didSetDefaultTime.current || allColumns.length === 0 || !table) { + return; + } + + const col = allColumns.filter(columnFilterDateTime)[0]; + if (!col) { + return; + } + + const timeColumn: SelectedColumn = { + name: col.name, + type: col.type, + hint: ColumnHint.Time + }; + + builderOptionsDispatch(setColumnByHint(timeColumn)); + lastTable.current = table; + didSetDefaultTime.current = true; + }, [allColumns, table, builderOptionsDispatch]); +}; + +// Apply default filters/orderBy on timeColumn change +const timeRangeFilterId = 'timeRange'; +const useDefaultFilters = (table: string, timeColumn: SelectedColumn | undefined, filters: Filter[], builderOptionsDispatch: React.Dispatch) => { + const lastTimeColumn = useRef(timeColumn?.name || ''); + const lastTable = useRef(table || ''); + if (!timeColumn || table !== lastTable.current) { + lastTimeColumn.current = ''; + } + + useEffect(() => { + if (!timeColumn || (timeColumn.name === lastTimeColumn.current) || !table) { + return; + } + + const nextFilters: Filter[] = filters.filter(f => f.id !== timeRangeFilterId); + const timeRangeFilter: DateFilterWithoutValue = { + type: 'datetime', + operator: FilterOperator.WithInGrafanaTimeRange, + filterType: 'custom', + key: timeColumn.name, + id: timeRangeFilterId, + condition: 'AND' + }; + nextFilters.unshift(timeRangeFilter); + + lastTable.current = table; + lastTimeColumn.current = timeColumn.name; + builderOptionsDispatch(setOptions({ + filters: nextFilters + })); + }, [table, timeColumn, filters, builderOptionsDispatch]); +}; diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index 5d6a250a..93c41584 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Filter, QueryBuilderOptions, SelectedColumn, ColumnHint, TimeUnit } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; import { FiltersEditor } from '../FilterEditor'; @@ -10,11 +10,13 @@ import { DurationUnitSelect } from 'components/queryBuilder/DurationUnitSelect'; import { Datasource } from 'data/CHDatasource'; import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; import useColumns from 'hooks/useColumns'; +import { BuilderOptionsReducerAction, setOptions } from 'hooks/useBuilderOptionsState'; +import useIsNewQuery from 'hooks/useIsNewQuery'; interface TraceQueryBuilderProps { datasource: Datasource; - builderOptions: QueryBuilderOptions, - onBuilderOptionsChange: (nextBuilderOptions: Partial) => void; + builderOptions: QueryBuilderOptions; + builderOptionsDispatch: React.Dispatch; } interface TraceQueryBuilderState { @@ -34,8 +36,9 @@ interface TraceQueryBuilderState { } export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { - const { datasource, builderOptions, onBuilderOptionsChange } = props; + const { datasource, builderOptions, builderOptionsDispatch } = props; const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); + const isNewQuery = useIsNewQuery(builderOptions); const showConfigWarning = datasource.getDefaultTraceColumns().size === 0; const [isColumnsOpen, setColumnsOpen] = useState(showConfigWarning); // Toggle Columns collapsable section const [isFiltersOpen, setFiltersOpen] = useState(true); // Toggle Filters collapsable section @@ -56,40 +59,6 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { filters: builderOptions.filters || [], }), [builderOptions]); - useEffect(() => { - const shouldApplyDefaults = (builderOptions.columns || []).length === 0 && (builderOptions.orderBy || []).length === 0; - if (!shouldApplyDefaults) { - return; - } - - const defaultDb = datasource.getDefaultTraceDatabase() || datasource.getDefaultDatabase(); - const defaultTable = datasource.getDefaultTraceTable() || datasource.getDefaultTable(); - const defaultDurationUnit = datasource.getDefaultTraceDurationUnit(); - const otelVersion = datasource.getTraceOtelVersion(); - const defaultColumns = datasource.getDefaultTraceColumns(); - - const nextColumns: SelectedColumn[] = []; - for (let [hint, colName] of defaultColumns) { - nextColumns.push({ name: colName, hint }); - } - - onBuilderOptionsChange({ - database: defaultDb, - table: defaultTable || builderOptions.table, - columns: nextColumns, - // filters, - // orderBy, - meta: { - otelEnabled: Boolean(otelVersion), - otelVersion, - traceDurationUnit: defaultDurationUnit - } - }); - - // Run on load - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - const onOptionChange = useBuilderOptionChanges(next => { const nextColumns = [ next.traceIdColumn, @@ -103,7 +72,7 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { next.serviceTagsColumn ].filter(c => c !== undefined) as SelectedColumn[]; - onBuilderOptionsChange({ + builderOptionsDispatch(setOptions({ columns: nextColumns, filters: next.filters, meta: { @@ -111,8 +80,10 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { traceDurationUnit: next.durationUnit, traceId: next.traceId, } - }); + })); }, builderState); + + useTraceDefaultsOnMount(datasource, isNewQuery, builderOptions, builderOptionsDispatch); const configWarning = showConfigWarning && ( @@ -301,3 +272,38 @@ const TraceIdInput = (props: TraceIdInputProps) => {
) } + +/** + * Loads the default configuration for new queries. (Only runs on new queries) + */ +const useTraceDefaultsOnMount = (datasource: Datasource, isNewQuery: boolean, builderOptions: QueryBuilderOptions, builderOptionsDispatch: React.Dispatch) => { + const didSetDefaults = useRef(false); + useEffect(() => { + if (!isNewQuery || didSetDefaults.current) { + return; + } + + const defaultDb = datasource.getDefaultTraceDatabase() || datasource.getDefaultDatabase(); + const defaultTable = datasource.getDefaultTraceTable() || datasource.getDefaultTable(); + const defaultDurationUnit = datasource.getDefaultTraceDurationUnit(); + const otelVersion = datasource.getTraceOtelVersion(); + const defaultColumns = datasource.getDefaultTraceColumns(); + + const nextColumns: SelectedColumn[] = []; + for (let [hint, colName] of defaultColumns) { + nextColumns.push({ name: colName, hint }); + } + + builderOptionsDispatch(setOptions({ + database: defaultDb, + table: defaultTable || builderOptions.table, + columns: nextColumns, + meta: { + otelEnabled: Boolean(otelVersion), + otelVersion, + traceDurationUnit: defaultDurationUnit + } + })); + didSetDefaults.current = true; + }, [builderOptions.columns, builderOptions.orderBy, builderOptions.table, builderOptionsDispatch, datasource, isNewQuery]); +}; diff --git a/src/data/utils.ts b/src/data/utils.ts index b34ace0a..a702af28 100644 --- a/src/data/utils.ts +++ b/src/data/utils.ts @@ -1,5 +1,17 @@ -import { QueryType } from "types/queryBuilder" +import { QueryBuilderOptions, QueryType } from "types/queryBuilder" +/** + * Returns true if the builder options contain enough information to start showing a query + */ +export const isBuilderOptionsRunnable = (builderOptions: QueryBuilderOptions): boolean => { + return ( + (builderOptions.columns?.length || 0) > 0 || + (builderOptions.filters?.length || 0) > 0 || + (builderOptions.orderBy?.length || 0) > 0 || + (builderOptions.aggregates?.length || 0) > 0 || + (builderOptions.groupBy?.length || 0) > 0 + ); +}; /** * Converts QueryType to Grafana format @@ -18,4 +30,4 @@ export const mapQueryTypeToGrafanaFormat = (t: QueryType): number => { default: return 1 << 8; // an unused u32, defaults to timeseries/graph on plugin backend. } -} +}; diff --git a/src/hooks/useBuilderOptionsState.ts b/src/hooks/useBuilderOptionsState.ts new file mode 100644 index 00000000..1056d572 --- /dev/null +++ b/src/hooks/useBuilderOptionsState.ts @@ -0,0 +1,144 @@ +import { Reducer, useReducer } from "react"; +import { QueryBuilderOptions, QueryType, SelectedColumn } from "types/queryBuilder"; +import { defaultCHBuilderQuery } from "types/sql"; + +enum BuilderOptionsActionType { + SetOptions = 'set_options', + SetAllOptions = 'set_all_options', + SetQueryType = 'set_query_type', + SetDatabase = 'set_database', + SetTable = 'set_table', + SetOtelEnabled = 'set_otel_enabled', + SetOtelVersion = 'set_otel_version', + SetColumnByHint = 'set_column_by_hint', +}; + +type QueryBuilderOptionsReducerAction = { + type: BuilderOptionsActionType, + payload: Partial +}; + +type GenericReducerAction = { + type: BuilderOptionsActionType, + payload: any +}; + +export type BuilderOptionsReducerAction = QueryBuilderOptionsReducerAction | GenericReducerAction; + +const createAction = (type: BuilderOptionsActionType, payload: Partial): BuilderOptionsReducerAction => ({ type, payload }); +const createGenericAction = (type: BuilderOptionsActionType, payload: any): GenericReducerAction => ({ type, payload }); +export const setOptions = (options: Partial): BuilderOptionsReducerAction => createAction(BuilderOptionsActionType.SetOptions, options); +export const setAllOptions = (options: QueryBuilderOptions): BuilderOptionsReducerAction => createAction(BuilderOptionsActionType.SetAllOptions, options); +export const setQueryType = (queryType: QueryType): BuilderOptionsReducerAction => createAction(BuilderOptionsActionType.SetQueryType, { queryType }); +export const setDatabase = (database: string): BuilderOptionsReducerAction => createAction(BuilderOptionsActionType.SetDatabase, { database }); +export const setTable = (table: string): BuilderOptionsReducerAction => createAction(BuilderOptionsActionType.SetTable, { table }); +export const setOtelEnabled = (otelEnabled: boolean): BuilderOptionsReducerAction => createAction(BuilderOptionsActionType.SetOtelEnabled, { meta: { otelEnabled } }); +export const setOtelVersion = (otelVersion: string): BuilderOptionsReducerAction => createAction(BuilderOptionsActionType.SetOtelVersion, { meta: { otelVersion } }); +export const setColumnByHint = (column: SelectedColumn): GenericReducerAction => createGenericAction(BuilderOptionsActionType.SetColumnByHint, { column }); + +const reducer = (state: QueryBuilderOptions, action: BuilderOptionsReducerAction): QueryBuilderOptions => { + const actionFn = actions.get(action.type); + if (!actionFn) { + throw Error('missing function for BuilderOptionsActionType: ' + action.type); + } + + const nextState = actionFn(state, action); + // console.log('ACTION:', action.type, 'PAYLOAD:', action.payload, 'NEXT STATE:', nextState); + return nextState; +}; + +/** + * A mapping between action type and reducer function, used in reducer to apply action changes. + */ +const actions = new Map>([ + [BuilderOptionsActionType.SetOptions, (state: QueryBuilderOptions, action: BuilderOptionsReducerAction): QueryBuilderOptions => { + // A catch-all action for applying option changes. + const nextOptions = action.payload as Partial; + return mergeBuilderOptionsState(state, nextOptions); + }], + [BuilderOptionsActionType.SetAllOptions, (state: QueryBuilderOptions, action: BuilderOptionsReducerAction): QueryBuilderOptions => { + // Resets existing state with provided options. + const nextOptions = action.payload as Partial; + return buildInitialState(nextOptions); + }], + [BuilderOptionsActionType.SetQueryType, (state: QueryBuilderOptions, action: BuilderOptionsReducerAction): QueryBuilderOptions => { + // If switching query type, reset the editor. + const nextQueryType = action.payload.queryType; + if (state.queryType !== nextQueryType) { + return buildInitialState({ + queryType: nextQueryType + }); + } + + return state; + }], + [BuilderOptionsActionType.SetDatabase, (state: QueryBuilderOptions, action: BuilderOptionsReducerAction): QueryBuilderOptions => { + // Clear table and reset editor when database changes + return buildInitialState({ + database: action.payload.database, + table: '', + queryType: state.queryType + }); + }], + [BuilderOptionsActionType.SetTable, (state: QueryBuilderOptions, action: BuilderOptionsReducerAction): QueryBuilderOptions => { + // Reset editor when table changes + return buildInitialState({ + database: state.database, + table: action.payload.table, + queryType: state.queryType + }); + }], + [BuilderOptionsActionType.SetOtelEnabled, (state: QueryBuilderOptions, action: BuilderOptionsReducerAction): QueryBuilderOptions => { + return mergeBuilderOptionsState(state, { + meta: { + otelEnabled: Boolean(action.payload.meta?.otelEnabled), + } + }); + }], + [BuilderOptionsActionType.SetOtelVersion, (state: QueryBuilderOptions, action: BuilderOptionsReducerAction): QueryBuilderOptions => { + return mergeBuilderOptionsState(state, { + meta: { + otelVersion: action.payload.meta?.otelVersion + } + }); + }], + [BuilderOptionsActionType.SetColumnByHint, (state: QueryBuilderOptions, action: GenericReducerAction): QueryBuilderOptions => { + const col = action.payload.column as SelectedColumn; + const nextColumns = (state.columns || []).filter(c => c.hint !== col.hint); + nextColumns.push(col); + + return mergeBuilderOptionsState(state, { + columns: nextColumns + }); + }], +]); + +const buildInitialState = (savedOptions?: Partial): QueryBuilderOptions => { + const defaultOptions = defaultCHBuilderQuery.builderOptions; + const initialState = { + ...defaultOptions, + ...savedOptions, + meta: { + ...defaultOptions.meta, + ...savedOptions?.meta, + } + }; + + return initialState; +}; + +const mergeBuilderOptionsState = (prevState: QueryBuilderOptions, nextState: Partial): QueryBuilderOptions => { + return { + ...prevState, + ...nextState, + meta: { + ...prevState.meta, + ...nextState.meta + } + }; +} + +export const useBuilderOptionsState = (savedOptions: QueryBuilderOptions): [QueryBuilderOptions, React.Dispatch] => { + const [state, dispatch] = useReducer(reducer, savedOptions, buildInitialState); + return [state as QueryBuilderOptions, dispatch]; +}; diff --git a/src/hooks/useColumns.ts b/src/hooks/useColumns.ts index d1c3bfad..cd60322b 100644 --- a/src/hooks/useColumns.ts +++ b/src/hooks/useColumns.ts @@ -1,22 +1,41 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { TableColumn } from 'types/queryBuilder'; import { Datasource } from 'data/CHDatasource'; export default (datasource: Datasource, database: string, table: string): readonly TableColumn[] => { - const [columns, setColumns] = useState([]); - + const [columns, setColumns] = useState([]); + useEffect(() => { if (!datasource || !database || !table) { return; } + let ignore = false; datasource .fetchColumnsFull(database, table) - .then(columns => setColumns(columns)) + .then(columns => { + if (ignore) { + return; + } + setColumns(columns); + }) .catch((ex: any) => { console.error(ex); }); - }, [datasource, database, table]); - - return columns; -} + + return () => { + ignore = true; + }; + }, [datasource, database, table]); + + // Immediately return empty array on change so columns aren't stale + const lastDbTable = useRef(''); + const dbTable = database + table; + if (dbTable !== lastDbTable.current) { + lastDbTable.current = dbTable; + setColumns([]); + return []; + } + + return columns; +}; diff --git a/src/hooks/useDatabases.ts b/src/hooks/useDatabases.ts index 635fdd98..2af17965 100644 --- a/src/hooks/useDatabases.ts +++ b/src/hooks/useDatabases.ts @@ -3,7 +3,7 @@ import { Datasource } from 'data/CHDatasource'; export default (datasource: Datasource): string[] => { const [databases, setDatabases] = useState([]); - + useEffect(() => { if (!datasource) { return; diff --git a/src/hooks/useIsNewQuery.ts b/src/hooks/useIsNewQuery.ts new file mode 100644 index 00000000..67425f45 --- /dev/null +++ b/src/hooks/useIsNewQuery.ts @@ -0,0 +1,14 @@ +import { isBuilderOptionsRunnable } from "data/utils"; +import { useRef } from "react" +import { QueryBuilderOptions } from "types/queryBuilder"; + +/** + * Returns true if the initial builderOptions represent a new query. + * Returns false if the query was loaded from a saved URL or dashboard. + * + * Does not update on re-renders + */ +export default (builderOptions: QueryBuilderOptions): boolean => { + const isNewQuery = useRef(!isBuilderOptionsRunnable(builderOptions)); + return isNewQuery.current; +} diff --git a/src/hooks/useTables.ts b/src/hooks/useTables.ts index 640bf4f5..e3d184a4 100644 --- a/src/hooks/useTables.ts +++ b/src/hooks/useTables.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { Datasource } from 'data/CHDatasource'; export default (datasource: Datasource, database: string): string[] => { @@ -9,13 +9,31 @@ export default (datasource: Datasource, database: string): string[] => { return; } + let ignore = false; datasource. fetchTables(database). - then(tables => setTables(tables)). + then(tables => { + if (ignore) { + return; + } + setTables(tables); + }). catch((ex: any) => { console.error(ex); }); - }, [datasource, database]); - - return tables; + + return () => { + ignore = false; + }; + }, [datasource, database]); + + // Immediately return empty array on change so tables aren't stale + const lastDatabase = useRef(''); + if (database !== lastDatabase.current) { + lastDatabase.current = database; + setTables([]); + return []; + } + + return tables; } diff --git a/src/types/queryBuilder.ts b/src/types/queryBuilder.ts index a4055231..2e3811cb 100644 --- a/src/types/queryBuilder.ts +++ b/src/types/queryBuilder.ts @@ -175,8 +175,10 @@ export interface CommonFilterProps { key: string; type: string; condition: 'AND' | 'OR'; + /** * Used to uniquely identify a dynamically added filter + * For example, might be set to 'timeRange' for the default added time range filter. */ id?: string; } diff --git a/src/types/sql.ts b/src/types/sql.ts index 4397efeb..7e488682 100644 --- a/src/types/sql.ts +++ b/src/types/sql.ts @@ -53,7 +53,8 @@ export const defaultCHBuilderQuery: Omit = { queryType: QueryType.Table, mode: BuilderMode.List, columns: [], - meta: {} + meta: {}, + limit: 1000 }, }; export const defaultCHSqlQuery: Omit = { diff --git a/src/views/CHQueryEditor.tsx b/src/views/CHQueryEditor.tsx index 9243db0a..3daf6ac9 100644 --- a/src/views/CHQueryEditor.tsx +++ b/src/views/CHQueryEditor.tsx @@ -1,16 +1,16 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect, useRef } from 'react'; import { QueryEditorProps } from '@grafana/data'; import { Datasource } from 'data/CHDatasource'; import { EditorTypeSwitcher } from 'components/queryBuilder/EditorTypeSwitcher'; import { styles } from 'styles'; import { Button } from '@grafana/ui'; -import { CHBuilderQuery, CHQuery, EditorType, defaultCHBuilderQuery } from 'types/sql'; +import { CHBuilderQuery, CHQuery, EditorType } from 'types/sql'; import { CHConfig } from 'types/config'; -import { QueryBuilderOptions, QueryType } from 'types/queryBuilder'; import { QueryBuilder } from 'components/queryBuilder/QueryBuilder'; import { generateSql } from 'data/sqlGenerator'; import { SqlEditor } from 'components/SqlEditor'; -import { mapQueryTypeToGrafanaFormat } from 'data/utils'; +import { isBuilderOptionsRunnable, mapQueryTypeToGrafanaFormat } from 'data/utils'; +import { setAllOptions, useBuilderOptionsState } from 'hooks/useBuilderOptionsState'; export type CHQueryEditorProps = QueryEditorProps; @@ -33,42 +33,31 @@ export const CHQueryEditor = (props: CHQueryEditorProps) => { const CHEditorByType = (props: CHQueryEditorProps) => { const { query, onChange, app } = props; - const [builderOptions, setBuilderOptions] = useState({ - ...defaultCHBuilderQuery.builderOptions, - ...(query as CHBuilderQuery).builderOptions, - meta: { - ...defaultCHBuilderQuery.builderOptions.meta, - ...(query as CHBuilderQuery).builderOptions?.meta - } - }); + const [builderOptions, builderOptionsDispatch] = useBuilderOptionsState((query as CHBuilderQuery).builderOptions); - const onBuilderOptionsChange = useCallback((nextBuilderOptions: Partial) => { - setBuilderOptions(prevBuilderOptions => { - // If switching query type, reset the editor. - // Excludes Table/TimeSeries, since they're similar and less guided. - const prevQueryType = prevBuilderOptions.queryType; - const nextQueryType = nextBuilderOptions.queryType; - const queryTypeChanged = prevQueryType !== nextQueryType; - const isSwitchingBetweenTableAndTimeSeries = (prevQueryType === QueryType.Table && nextQueryType === QueryType.TimeSeries) || (prevQueryType === QueryType.TimeSeries && nextQueryType === QueryType.Table); - if (nextQueryType && queryTypeChanged && !isSwitchingBetweenTableAndTimeSeries) { - return { - ...defaultCHBuilderQuery.builderOptions, - queryType: nextQueryType - } - } + /** + * Grafana will sometimes replace the builder options directly, so we need to sync in both directions. + * For example, selecting an entry from the query history will cause the local state to fall out of sync. + * The "key" property is present on these historical entries. + */ + const queryKey = query.key || '' + const lastKey = useRef(queryKey); + if (queryKey !== lastKey.current) { + builderOptionsDispatch(setAllOptions((query as CHBuilderQuery).builderOptions || {})); + lastKey.current = queryKey; + } - return { - ...prevBuilderOptions, - ...nextBuilderOptions, - meta: { - ...prevBuilderOptions.meta, - ...nextBuilderOptions.meta - } - }; - }); - }, []); + // Prevent trying to run empty query on load + const shouldSkipChanges = useRef(true); + if (isBuilderOptionsRunnable(builderOptions)) { + shouldSkipChanges.current = false; + } useEffect(() => { + if (shouldSkipChanges.current) { + return; + } + const sql = generateSql(builderOptions); onChange({ ...query, @@ -78,6 +67,7 @@ const CHEditorByType = (props: CHQueryEditorProps) => { format: mapQueryTypeToGrafanaFormat(builderOptions.queryType) }); + // TODO: fix dependency warning with "useEffectEvent" once added to stable version of react // eslint-disable-next-line react-hooks/exhaustive-deps }, [builderOptions]); @@ -93,7 +83,7 @@ const CHEditorByType = (props: CHQueryEditorProps) => { From 6e50cb5648d6361a53e447ee3ef517e6cb1d2fa0 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 31 Oct 2023 04:57:13 -0400 Subject: [PATCH 36/95] prevent default log time from being overwritten --- src/components/queryBuilder/views/LogsQueryBuilder.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index bfab873e..8c3d1b07 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -250,7 +250,7 @@ const useDefaultTimeColumn = (datasource: Datasource, allColumns: readonly Table didSetDefaultTime.current = false; } - if (otelEnabled) { + if (Boolean(timeColumn) || otelEnabled) { lastTable.current = table; didSetDefaultTime.current = true; } From 40be22069f1fe4ea48698bc6037acbfcf38a6171 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 1 Nov 2023 07:49:20 -0400 Subject: [PATCH 37/95] trace startTime as datetime --- src/data/sqlGenerator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index 6fc605f0..f422ee43 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -52,7 +52,7 @@ const generateTraceQuery = (options: QueryBuilderOptions): string => { const traceStartTime = getColumnByHint(options, ColumnHint.Time); if (traceStartTime !== undefined) { - selectParts.push(`toInt64(${escapeIdentifier(traceStartTime.name)}) as startTime`); + selectParts.push(`${escapeIdentifier(traceStartTime.name)} as startTime`); } const traceDurationTime = getColumnByHint(options, ColumnHint.TraceDurationTime); From c5602b5349afefc14b16b66ee180ebbfcce31400 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 1 Nov 2023 09:15:08 -0400 Subject: [PATCH 38/95] Allow different timeseries modes --- src/components/queryBuilder/ColumnSelect.tsx | 5 ++- .../views/TimeSeriesQueryBuilder.tsx | 38 +++++++++++-------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/components/queryBuilder/ColumnSelect.tsx b/src/components/queryBuilder/ColumnSelect.tsx index 3b6a322d..340d2676 100644 --- a/src/components/queryBuilder/ColumnSelect.tsx +++ b/src/components/queryBuilder/ColumnSelect.tsx @@ -16,12 +16,13 @@ interface ColumnSelectProps { invalid?: boolean; wide?: boolean; inline?: boolean; + clearable?: boolean; } const defaultFilterFn = () => true; export const ColumnSelect = (props: ColumnSelectProps) => { - const { allColumns, selectedColumn, onColumnChange, columnFilterFn, columnHint, label, tooltip, disabled, invalid, wide, inline } = props; + const { allColumns, selectedColumn, onColumnChange, columnFilterFn, columnHint, label, tooltip, disabled, invalid, wide, inline, clearable } = props; const selectedColumnName = selectedColumn?.name; const columns: Array> = allColumns. filter(columnFilterFn || defaultFilterFn). @@ -68,7 +69,7 @@ export const ColumnSelect = (props: ColumnSelectProps) => { onChange={onChange} width={wide ? 25 : 20} menuPlacement={'bottom'} - isClearable + isClearable={clearable === undefined || clearable} /> ); diff --git a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx index 7c1eaf82..05c8f376 100644 --- a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; import { AggregateColumn, BuilderMode, Filter, OrderBy, QueryBuilderOptions, ColumnHint, SelectedColumn, DateFilterWithoutValue, FilterOperator, TableColumn } from 'types/queryBuilder'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; @@ -23,6 +23,7 @@ interface TimeSeriesQueryBuilderProps { } interface TimeSeriesQueryBuilderState { + isAggregateMode: boolean; timeColumn?: SelectedColumn; selectedColumns: SelectedColumn[]; aggregates: AggregateColumn[]; @@ -36,8 +37,9 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { const { datasource, builderOptions, builderOptionsDispatch } = props; const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); const labels = allLabels.components.TimeSeriesQueryBuilder; - const [isAggregateMode, setAggregateMode] = useState((builderOptions.aggregates?.length || 0) > 0); // Toggle Simple vs Aggregate mode const builderState: TimeSeriesQueryBuilderState = useMemo(() => ({ + // TODO: do not depend on "mode" + isAggregateMode: builderOptions.mode === BuilderMode.Trend, timeColumn: getColumnByHint(builderOptions, ColumnHint.Time), selectedColumns: (builderOptions.columns || []).filter(c => c.hint !== ColumnHint.Time), aggregates: builderOptions.aggregates || [], @@ -48,16 +50,20 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { }), [builderOptions]); const onOptionChange = useBuilderOptionChanges(next => { - const nextColumns = next.selectedColumns.slice(); + let nextColumns = next.selectedColumns.slice(); + if (next.isAggregateMode) { + nextColumns = []; + } + if (next.timeColumn) { nextColumns.push(next.timeColumn); } builderOptionsDispatch(setOptions({ - mode: isAggregateMode ? BuilderMode.Aggregate : BuilderMode.Trend, + mode: next.isAggregateMode ? BuilderMode.Trend : BuilderMode.Aggregate, columns: nextColumns, - aggregates: isAggregateMode ? next.aggregates : [], - groupBy: isAggregateMode ? next.groupBy : [], + aggregates: next.isAggregateMode ? next.aggregates : [], + groupBy: next.isAggregateMode ? next.groupBy : [], filters: next.filters, orderBy: next.orderBy, limit: next.limit @@ -72,8 +78,8 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { @@ -87,19 +93,21 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { columnHint={ColumnHint.Time} label={labels.timeColumn.label} tooltip={labels.timeColumn.tooltip} - /> - - {isAggregateMode && ( + { builderState.isAggregateMode ? <> - )} + : + + } Date: Fri, 3 Nov 2023 17:52:10 -0400 Subject: [PATCH 39/95] update filters builder --- src/data/CHDatasource.ts | 3 +- src/data/sqlGenerator.ts | 116 ++++++++++++++++++++++++++++++++++++-- src/types/queryBuilder.ts | 7 ++- 3 files changed, 119 insertions(+), 7 deletions(-) diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index deb0f233..9cf0ab1d 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -199,8 +199,7 @@ export class Datasource if (!chQuery.rawSql) { return []; } - const q = { ...chQuery, editorType: chQuery.editorType || EditorType.SQL }; - const frame = await this.runQuery(q, options); + const frame = await this.runQuery(chQuery, options); if (frame.fields?.length === 0) { return []; } diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index f422ee43..acbc11e6 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -1,5 +1,5 @@ -import { getSqlFromQueryBuilderOptions, getFilters, getOrderBy } from 'components/queryBuilder/utils'; -import { ColumnHint, QueryBuilderOptions, QueryType, SelectedColumn, TimeUnit } from 'types/queryBuilder'; +import { getSqlFromQueryBuilderOptions, getOrderBy } from 'components/queryBuilder/utils'; +import { BooleanFilter, ColumnHint, DateFilterWithValue, FilterOperator, MultiFilter, NumberFilter, QueryBuilderOptions, QueryType, SelectedColumn, StringFilter, TimeUnit } from 'types/queryBuilder'; export const generateSql = (options: QueryBuilderOptions): string => { @@ -91,7 +91,7 @@ const generateTraceQuery = (options: QueryBuilderOptions): string => { } if (hasFilters) { - queryParts.push(getFilters(options.filters!)); + queryParts.push(getFilters(options)); } if (traceStartTime !== undefined) { @@ -154,7 +154,7 @@ const generateLogsQuery = (options: QueryBuilderOptions): string => { if ((options.filters?.length || 0) > 0) { queryParts.push('WHERE'); - queryParts.push(getFilters(options.filters!)); + queryParts.push(getFilters(options)); } if ((options.orderBy?.length || 0) > 0) { @@ -239,3 +239,111 @@ const getLimit = (limit?: number | undefined): string => { return 'LIMIT ' + Math.max(0, limit || 1000); }; + +const getFilters = (options: QueryBuilderOptions): string => { + const filters = options.filters || []; + const builtFilters: string[] = []; + + for (const filter of filters) { + const filterParts: string[] = []; + + let column = filter.key; + let type = filter.type; + const hintedColumn = filter.hint && getColumnByHint(options, filter.hint); + if (hintedColumn) { + column = hintedColumn.name; + type = hintedColumn.type || type; + } + + if (!column) { + continue; + } + filterParts.push(column); + + let operator: string = filter.operator; + let negate = false; + if (filter.operator === FilterOperator.NotLike) { + operator = 'LIKE'; + negate = true; + } else if (filter.operator === FilterOperator.OutsideGrafanaTimeRange) { + operator = ''; + negate = true; + } else if (filter.operator === FilterOperator.WithInGrafanaTimeRange){ + operator = ''; + } + + if (operator) { + filterParts.push(operator); + } + + if (isNullFilter(filter.operator)) { + // empty + } else if (isBooleanFilter(type)) { + filterParts.push(String((filter as BooleanFilter).value)); + } else if (isNumberFilter(type)) { + filterParts.push(String((filter as NumberFilter).value || '0')); + } else if (isDateFilter(type)) { + if (isDateFilterWithoutValue(type, filter.operator)) { + if (isDateType(type)) { + filterParts.push('>=', '\$__fromTime', 'AND', column, '<=', '\$__toTime'); + } + } else { + switch ((filter as DateFilterWithValue).value) { + case 'GRAFANA_START_TIME': + if (isDateType(type)) { + filterParts.push('\$__fromTime'); + } + break; + case 'GRAFANA_END_TIME': + if (isDateType(type)) { + filterParts.push('\$__toTime'); + } + break; + default: + filterParts.push(String((filter as DateFilterWithValue).value || 'TODAY')); + } + } + } else if (isStringFilter(type, filter.operator)) { + if (filter.operator === FilterOperator.Like || filter.operator === FilterOperator.NotLike) { + filterParts.push(`'%${filter.value || ''}%'`); + } else { + filterParts.push(formatStringValue((filter as StringFilter).value || '')); + } + } else if (isMultiFilter(type, filter.operator)) { + filterParts.push(`(${(filter as MultiFilter).value?.map(v => formatStringValue(v)).join(', ')})`); + } else { + filterParts.push(formatStringValue((filter as StringFilter).value || '')); + } + + if (negate) { + filterParts.unshift('NOT', '('); + filterParts.push(')'); + } + + filterParts.unshift('('); + if (builtFilters.length > 0) { + filterParts.unshift(filter.condition); + } + filterParts.push(')'); + + const builtFilter = filterParts.join(' '); + builtFilters.push(builtFilter); + } + + return builtFilters.join(' '); +}; + +const isBooleanType = (type: string): boolean => (type?.toLowerCase().startsWith('boolean')); +const numberTypes = ['int', 'float', 'decimal']; +const isNumberType = (type: string): boolean => numberTypes.some(t => type?.toLowerCase().includes(t)); +const isDateType = (type: string): boolean => type?.toLowerCase().startsWith('date') || type?.toLowerCase().startsWith('nullable(date'); +// const isDateTimeType = (type: string): boolean => type?.toLowerCase().startsWith('datetime') || type?.toLowerCase().startsWith('nullable(datetime'); +const isStringType = (type: string): boolean => type === 'String' && !(isBooleanType(type) || isNumberType(type) || isDateType(type)); +const isNullFilter = (operator: FilterOperator): boolean => operator === FilterOperator.IsNull || operator === FilterOperator.IsNotNull; +const isBooleanFilter = (type: string): boolean => isBooleanType(type); +const isNumberFilter = (type: string): boolean => isNumberType(type); +const isDateFilterWithoutValue = (type: string, operator: FilterOperator): boolean => isDateType(type) && (operator === FilterOperator.WithInGrafanaTimeRange || operator === FilterOperator.OutsideGrafanaTimeRange); +const isDateFilter = (type: string): boolean => isDateType(type); +const isStringFilter = (type: string, operator: FilterOperator): boolean => isStringType(type) && !(operator === FilterOperator.In || operator === FilterOperator.NotIn); +const isMultiFilter = (type: string, operator: FilterOperator): boolean => isStringType(type) && (operator === FilterOperator.In || operator === FilterOperator.NotIn); +const formatStringValue = (filter: string): string => filter.startsWith('$') ? (filter || '') : `'${filter || ''}'`; diff --git a/src/types/queryBuilder.ts b/src/types/queryBuilder.ts index 2e3811cb..1b831376 100644 --- a/src/types/queryBuilder.ts +++ b/src/types/queryBuilder.ts @@ -172,7 +172,7 @@ export enum FilterOperator { export interface CommonFilterProps { filterType: 'custom'; - key: string; + key: string; // Column name type: string; condition: 'AND' | 'OR'; @@ -181,6 +181,11 @@ export interface CommonFilterProps { * For example, might be set to 'timeRange' for the default added time range filter. */ id?: string; + /** + * If provided, SQL generator will ignore "key" and instead + * find the intended column by the hint + */ + hint?: ColumnHint; } export interface NullFilter extends CommonFilterProps { From 713f9bf95e48bcdf35d2c5b026f3bbc2b0f6fc9e Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Fri, 3 Nov 2023 17:52:35 -0400 Subject: [PATCH 40/95] placeholder logs context implementation --- src/data/CHDatasource.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index 9cf0ab1d..8315f959 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -4,9 +4,11 @@ import { DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings, + DataSourceWithLogsContextSupport, DataSourceWithSupplementaryQueriesSupport, getTimeZone, getTimeZoneInfo, + LogRowModel, MetricFindValue, QueryFixAction, ScopedVars, @@ -44,10 +46,12 @@ import { import { getSqlFromQueryBuilderOptions, getColumnByHint } from '../components/queryBuilder/utils'; import { generateSql } from './sqlGenerator'; import { versions as otelVersions } from 'otel'; +import { ReactNode } from 'react'; export class Datasource extends DataSourceWithBackend - implements DataSourceWithSupplementaryQueriesSupport + implements DataSourceWithSupplementaryQueriesSupport, + DataSourceWithLogsContextSupport { // This enables default annotation support for 7.2+ annotations = {}; @@ -667,6 +671,19 @@ export class Datasource throw err; } } + + // interface DataSourceWithLogsContextSupport + async getLogRowContext(row: LogRowModel, options?: any | undefined, query?: CHQuery | undefined): Promise { + return {} as DataQueryResponse; + } + + showContextToggle(row?: LogRowModel): boolean { + return false; + } + + getLogRowContextUi(row: LogRowModel, runContextQuery?: (() => void) | undefined): ReactNode { + return false; + } } enum TagType { From 09e84d05496e904e97a9a588d33b6a0cab24fd8b Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Fri, 3 Nov 2023 17:53:19 -0400 Subject: [PATCH 41/95] disable column for filter with hint --- src/components/queryBuilder/FilterEditor.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/queryBuilder/FilterEditor.tsx b/src/components/queryBuilder/FilterEditor.tsx index 7c48211a..944b43f4 100644 --- a/src/components/queryBuilder/FilterEditor.tsx +++ b/src/components/queryBuilder/FilterEditor.tsx @@ -337,6 +337,8 @@ export const FilterEditor = (props: { onFilterConditionChange(e!)} /> )} onFilterOperatorChange(e.value!)} From 9e85043f03dc34e08ca109324bf8352814274c54 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 6 Nov 2023 19:53:26 -0600 Subject: [PATCH 42/95] otel switch for traces --- .../queryBuilder/views/LogsQueryBuilder.tsx | 12 +--- .../queryBuilder/views/TraceQueryBuilder.tsx | 63 ++++++++++++++++++- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index 8c3d1b07..1f1b21ac 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -226,15 +226,9 @@ const useOtelColumns = (otelEnabled: boolean, otelVersion: string, builderOption } const columns: SelectedColumn[] = []; - if (logColumnMap.has(ColumnHint.Time)) { - columns.push({ name: logColumnMap.get(ColumnHint.Time)!, hint: ColumnHint.Time }); - } - if (logColumnMap.has(ColumnHint.LogLevel)) { - columns.push({ name: logColumnMap.get(ColumnHint.LogLevel)!, hint: ColumnHint.LogLevel }); - } - if (logColumnMap.has(ColumnHint.LogMessage)) { - columns.push({ name: logColumnMap.get(ColumnHint.LogMessage)!, hint: ColumnHint.LogMessage }); - } + logColumnMap.forEach((name, hint) => { + columns.push({ name, hint }); + }); builderOptionsDispatch(setOptions({ columns })); didSetColumns.current = true; diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index 93c41584..6d19a33c 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -10,8 +10,10 @@ import { DurationUnitSelect } from 'components/queryBuilder/DurationUnitSelect'; import { Datasource } from 'data/CHDatasource'; import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; import useColumns from 'hooks/useColumns'; -import { BuilderOptionsReducerAction, setOptions } from 'hooks/useBuilderOptionsState'; +import { BuilderOptionsReducerAction, setOptions, setOtelEnabled, setOtelVersion } from 'hooks/useBuilderOptionsState'; import useIsNewQuery from 'hooks/useIsNewQuery'; +import { OtelVersionSelect } from '../OtelVersionSelect'; +import { versions as otelVersions } from 'otel'; interface TraceQueryBuilderProps { datasource: Datasource; @@ -21,6 +23,8 @@ interface TraceQueryBuilderProps { interface TraceQueryBuilderState { isSearchMode: boolean; + otelEnabled: boolean; + otelVersion: string; traceIdColumn?: SelectedColumn; spanIdColumn?: SelectedColumn; parentSpanIdColumn?: SelectedColumn; @@ -45,6 +49,8 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { const labels = allLabels.components.TraceQueryBuilder; const builderState: TraceQueryBuilderState = useMemo(() => ({ isSearchMode: builderOptions.meta?.isTraceSearchMode || false, + otelEnabled: builderOptions.meta?.otelEnabled || false, + otelVersion: builderOptions.meta?.otelVersion || '', traceIdColumn: getColumnByHint(builderOptions, ColumnHint.TraceId), spanIdColumn: getColumnByHint(builderOptions, ColumnHint.TraceSpanId), parentSpanIdColumn: getColumnByHint(builderOptions, ColumnHint.TraceParentSpanId), @@ -84,7 +90,8 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { }, builderState); useTraceDefaultsOnMount(datasource, isNewQuery, builderOptions, builderOptionsDispatch); - + useOtelColumns(builderState.otelEnabled, builderState.otelVersion, builderOptionsDispatch); + const configWarning = showConfigWarning && ( @@ -113,8 +120,17 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { onToggle={setColumnsOpen} > { configWarning } + builderOptionsDispatch(setOtelEnabled(e))} + selectedVersion={builderState.otelVersion} + onVersionChange={v => builderOptionsDispatch(setOtelVersion(v))} + defaultToLatest + wide + />
{ wide /> {
{ wide /> {
{ wide /> {
{ wide /> {
{ wide /> ) => { + const didSetColumns = useRef(otelEnabled); + if (!otelEnabled) { + didSetColumns.current = false; + } + + useEffect(() => { + if (!otelEnabled || didSetColumns.current) { + return; + } + + const otelConfig = otelVersions.find(v => v.version === otelVersion); + const traceColumnMap = otelConfig?.traceColumnMap; + if (!otelConfig || !traceColumnMap) { + return; + } + + const columns: SelectedColumn[] = []; + traceColumnMap.forEach((name, hint) => { + columns.push({ name, hint }); + }); + + builderOptionsDispatch(setOptions({ + columns, + meta: { + traceDurationUnit: otelConfig.traceDurationUnit + } + })); + didSetColumns.current = true; + }, [otelEnabled, otelVersion, builderOptionsDispatch]); +}; From f3fd7dd735550d94a886ef4684a41cf319e69b23 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 15 Nov 2023 01:41:33 -0500 Subject: [PATCH 43/95] Fix/add tests, reorganize labels/selectors --- .../DefaultDatabaseTableConfig.test.tsx | 37 ++ .../DefaultDatabaseTableConfig.tsx | 31 +- .../configEditor/LabeledInput.test.tsx | 23 + .../configEditor/LogsConfig.test.tsx | 191 +++++++ src/components/configEditor/LogsConfig.tsx | 71 +-- .../configEditor/QuerySettingsConfig.test.tsx | 76 +++ .../configEditor/QuerySettingsConfig.tsx | 31 +- .../configEditor/TracesConfig.test.tsx | 464 ++++++++++++++++++ src/components/configEditor/TracesConfig.tsx | 96 ++-- src/components/queryBuilder/OrderByEditor.tsx | 2 +- .../queryBuilder/OtelVersionSelect.test.tsx | 16 +- .../queryBuilder/OtelVersionSelect.tsx | 1 + src/components/queryBuilder/utils.test.ts | 30 +- src/components/queryBuilder/utils.ts | 6 +- .../queryBuilder/views/LogsQueryBuilder.tsx | 2 +- .../views/TimeSeriesQueryBuilder.tsx | 2 +- .../queryBuilder/views/TraceQueryBuilder.tsx | 2 +- src/data/CHDatasource.ts | 4 +- src/data/adHocFilter.test.ts | 2 +- src/data/adHocFilter.ts | 5 +- src/data/sqlGenerator.test.ts | 106 +++- src/data/sqlGenerator.ts | 1 + src/hooks/useColumns.test.ts | 10 +- src/labels.ts | 126 +++++ src/selectors.ts | 19 - src/views/CHConfigEditor.test.tsx | 8 +- src/views/CHConfigEditor.tsx | 100 ++-- 27 files changed, 1202 insertions(+), 260 deletions(-) create mode 100644 src/components/configEditor/DefaultDatabaseTableConfig.test.tsx create mode 100644 src/components/configEditor/LabeledInput.test.tsx create mode 100644 src/components/configEditor/LogsConfig.test.tsx create mode 100644 src/components/configEditor/QuerySettingsConfig.test.tsx create mode 100644 src/components/configEditor/TracesConfig.test.tsx diff --git a/src/components/configEditor/DefaultDatabaseTableConfig.test.tsx b/src/components/configEditor/DefaultDatabaseTableConfig.test.tsx new file mode 100644 index 00000000..8dce985f --- /dev/null +++ b/src/components/configEditor/DefaultDatabaseTableConfig.test.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { DefaultDatabaseTableConfig } from './DefaultDatabaseTableConfig'; +import allLabels from 'labels'; + +describe('DefaultDatabaseTableConfig', () => { + it('should render', () => { + const result = render( {}} onDefaultTableChange={() => {}} />); + expect(result.container.firstChild).not.toBeNull(); + }); + + it('should call onDefaultDatabaseChange when default database is changed', () => { + const onDefaultDatabaseChange = jest.fn(); + const result = render( {}} />); + expect(result.container.firstChild).not.toBeNull(); + + const databaseInput = result.getByLabelText(allLabels.components.Config.DefaultDatabaseTableConfig.database.label); + expect(databaseInput).toBeInTheDocument(); + fireEvent.change(databaseInput, { target: { value: 'test' } }); + fireEvent.blur(databaseInput); + expect(onDefaultDatabaseChange).toBeCalledTimes(1); + expect(onDefaultDatabaseChange).toBeCalledWith(expect.any(Object)); + }); + + it('should call onDefaultTableChange when default table is changed', () => { + const onDefaultTableChange = jest.fn(); + const result = render( {}} onDefaultTableChange={onDefaultTableChange} />); + expect(result.container.firstChild).not.toBeNull(); + + const tableInput = result.getByLabelText(allLabels.components.Config.DefaultDatabaseTableConfig.table.label); + expect(tableInput).toBeInTheDocument(); + fireEvent.change(tableInput, { target: { value: 'test' } }); + fireEvent.blur(tableInput); + expect(onDefaultTableChange).toBeCalledTimes(1); + expect(onDefaultTableChange).toBeCalledWith(expect.any(Object)); + }); +}); diff --git a/src/components/configEditor/DefaultDatabaseTableConfig.tsx b/src/components/configEditor/DefaultDatabaseTableConfig.tsx index 07506e48..6e11286b 100644 --- a/src/components/configEditor/DefaultDatabaseTableConfig.tsx +++ b/src/components/configEditor/DefaultDatabaseTableConfig.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { ConfigSection } from '@grafana/experimental'; import { Input, Field } from '@grafana/ui'; +import allLabels from 'labels'; interface DefaultDatabaseTableConfigProps { defaultDatabase?: string; @@ -11,34 +12,38 @@ interface DefaultDatabaseTableConfigProps { export const DefaultDatabaseTableConfig = (props: DefaultDatabaseTableConfigProps) => { const { defaultDatabase, defaultTable, onDefaultDatabaseChange, onDefaultTableChange } = props; + const labels = allLabels.components.Config.DefaultDatabaseTableConfig; + return ( - + diff --git a/src/components/configEditor/LabeledInput.test.tsx b/src/components/configEditor/LabeledInput.test.tsx new file mode 100644 index 00000000..b2a7b5a1 --- /dev/null +++ b/src/components/configEditor/LabeledInput.test.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { LabeledInput } from './LabeledInput'; + +describe('LabeledInput', () => { + it('should render', () => { + const result = render( {}} />); + expect(result.container.firstChild).not.toBeNull(); + }); + + it('should call onChange when input is changed', async () => { + const onChange = jest.fn(); + const result = render(); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText('test'); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onChange).toBeCalledTimes(1); + expect(onChange).toBeCalledWith('changed'); + }); +}); diff --git a/src/components/configEditor/LogsConfig.test.tsx b/src/components/configEditor/LogsConfig.test.tsx new file mode 100644 index 00000000..b60ac7bb --- /dev/null +++ b/src/components/configEditor/LogsConfig.test.tsx @@ -0,0 +1,191 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { LogsConfig } from './LogsConfig'; +import allLabels from 'labels'; + +const labelToPlaceholder = (l: string) => l.toLowerCase().replace(/ /g, '_'); + +describe('LogsConfig', () => { + it('should render', () => { + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTimeColumnChange={() => {}} + onLevelColumnChange={() => {}} + onMessageColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + }); + + it('should call onDefaultDatabase when changed', () => { + const onDefaultDatabaseChange = jest.fn(); + const result = render( + {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTimeColumnChange={() => {}} + onLevelColumnChange={() => {}} + onMessageColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(allLabels.components.Config.LogsConfig.defaultDatabase.placeholder); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onDefaultDatabaseChange).toBeCalledTimes(1); + expect(onDefaultDatabaseChange).toBeCalledWith('changed'); + }); + + it('should call onDefaultTable when changed', () => { + const onDefaultTableChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={onDefaultTableChange} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTimeColumnChange={() => {}} + onLevelColumnChange={() => {}} + onMessageColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(allLabels.components.Config.LogsConfig.defaultTable.placeholder); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onDefaultTableChange).toBeCalledTimes(1); + expect(onDefaultTableChange).toBeCalledWith('changed'); + }); + + it('should call onOtelEnabled when changed', () => { + const onOtelEnabledChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={onOtelEnabledChange} + onOtelVersionChange={() => {}} + onTimeColumnChange={() => {}} + onLevelColumnChange={() => {}} + onMessageColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByRole('checkbox'); + expect(input).toBeInTheDocument(); + fireEvent.click(input); + expect(onOtelEnabledChange).toBeCalledTimes(1); + expect(onOtelEnabledChange).toBeCalledWith(true); + }); + + it('should call onOtelVersionChange when changed', () => { + const onOtelVersionChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={onOtelVersionChange} + onTimeColumnChange={() => {}} + onLevelColumnChange={() => {}} + onMessageColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const select = result.getByRole('combobox'); + expect(select).toBeInTheDocument(); + fireEvent.keyDown(select, { key: 'ArrowDown' }); + fireEvent.keyDown(select, { key: 'Enter' }); + expect(onOtelVersionChange).toBeCalledTimes(2); // 2 from hook + expect(onOtelVersionChange).toBeCalledWith(expect.any(String)); + }); + + it('should call onTimeColumnChange when changed', () => { + const onTimeColumnChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTimeColumnChange={onTimeColumnChange} + onLevelColumnChange={() => {}} + onMessageColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.LogsConfig.columns.time.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onTimeColumnChange).toBeCalledTimes(1); + expect(onTimeColumnChange).toBeCalledWith('changed'); + }); + + it('should call onLevelColumnChange when changed', () => { + const onLevelColumnChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTimeColumnChange={() => {}} + onLevelColumnChange={onLevelColumnChange} + onMessageColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.LogsConfig.columns.level.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onLevelColumnChange).toBeCalledTimes(1); + expect(onLevelColumnChange).toBeCalledWith('changed'); + }); + + it('should call onMessageColumnChange when changed', () => { + const onMessageColumnChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTimeColumnChange={() => {}} + onLevelColumnChange={() => {}} + onMessageColumnChange={onMessageColumnChange} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.LogsConfig.columns.message.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onMessageColumnChange).toBeCalledTimes(1); + expect(onMessageColumnChange).toBeCalledWith('changed'); + }); +}); diff --git a/src/components/configEditor/LogsConfig.tsx b/src/components/configEditor/LogsConfig.tsx index 38364645..95cd9f7d 100644 --- a/src/components/configEditor/LogsConfig.tsx +++ b/src/components/configEditor/LogsConfig.tsx @@ -5,31 +5,32 @@ import { OtelVersionSelect } from 'components/queryBuilder/OtelVersionSelect'; import { ColumnHint } from 'types/queryBuilder'; import { versions as otelVersions } from 'otel'; import { LabeledInput } from './LabeledInput'; +import { CHLogsConfig } from 'types/config'; +import allLabels from 'labels'; interface LogsConfigProps { - defaultDatabase?: string; - defaultTable?: string; + logsConfig?: CHLogsConfig; onDefaultDatabaseChange: (v: any) => void; onDefaultTableChange: (v: any) => void; - - otelEnabled?: boolean; - otelVersion?: string; onOtelEnabledChange: (v: any) => void; onOtelVersionChange: (v: any) => void; - - timeColumn?: string; - levelColumn?: string; - messageColumn?: string; onTimeColumnChange: (v: any) => void; onLevelColumnChange: (v: any) => void; onMessageColumnChange: (v: any) => void; } export const LogsConfig = (props: LogsConfigProps) => { - const { defaultDatabase, defaultTable, onDefaultDatabaseChange, onDefaultTableChange } = props; - const { otelEnabled, otelVersion, onOtelEnabledChange, onOtelVersionChange } = props; - const { onTimeColumnChange, onLevelColumnChange, onMessageColumnChange } = props; - let { timeColumn, levelColumn, messageColumn } = props; + const { + onDefaultDatabaseChange, onDefaultTableChange, + onOtelEnabledChange, onOtelVersionChange, + onTimeColumnChange, onLevelColumnChange, onMessageColumnChange + } = props; + let { + defaultDatabase, defaultTable, + otelEnabled, otelVersion, + timeColumn, levelColumn, messageColumn + } = (props.logsConfig || {}) as CHLogsConfig; + const labels = allLabels.components.Config.LogsConfig; const otelConfig = otelVersions.find(v => v.version === otelVersion); if (otelEnabled && otelConfig) { @@ -40,40 +41,40 @@ export const LogsConfig = (props: LogsConfigProps) => { return ( onDefaultDatabaseChange(e.currentTarget.value)} - label="Default logs database" - aria-label="Default logs database" - placeholder="default" + label={labels.defaultDatabase.label} + aria-label={labels.defaultDatabase.label} + placeholder={labels.defaultDatabase.placeholder} /> onDefaultTableChange(e.currentTarget.value)} - label="Default logs table" - aria-label="Default logs table" - placeholder="logs" + label={labels.defaultTable.label} + aria-label={labels.defaultTable.label} + placeholder={labels.defaultTable.placeholder} /> { /> diff --git a/src/components/configEditor/QuerySettingsConfig.test.tsx b/src/components/configEditor/QuerySettingsConfig.test.tsx new file mode 100644 index 00000000..10834ef7 --- /dev/null +++ b/src/components/configEditor/QuerySettingsConfig.test.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { QuerySettingsConfig } from './QuerySettingsConfig'; +import allLabels from 'labels'; + +describe('QuerySettingsConfig', () => { + it('should render', () => { + const result = render( + {}} + onQueryTimeoutChange={() => {}} + onValidateSqlChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + }); + + it('should call onDialTimeout when changed', () => { + const onDialTimeout = jest.fn(); + const result = render( + {}} + onValidateSqlChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(allLabels.components.Config.QuerySettingsConfig.dialTimeout.placeholder); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: '10' } }); + fireEvent.blur(input); + expect(onDialTimeout).toBeCalledTimes(1); + expect(onDialTimeout).toBeCalledWith(expect.any(Object)); + }); + + it('should call onQueryTimeout when changed', () => { + const onQueryTimeout = jest.fn(); + const result = render( + {}} + onQueryTimeoutChange={onQueryTimeout} + onValidateSqlChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(allLabels.components.Config.QuerySettingsConfig.queryTimeout.placeholder); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: '10' } }); + fireEvent.blur(input); + expect(onQueryTimeout).toBeCalledTimes(1); + expect(onQueryTimeout).toBeCalledWith(expect.any(Object)); + }); + + it('should call onValidateSqlChange when changed', () => { + const onValidateSqlChange = jest.fn(); + const result = render( + {}} + onQueryTimeoutChange={() => {}} + onValidateSqlChange={onValidateSqlChange} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByRole('checkbox'); + expect(input).toBeInTheDocument(); + fireEvent.click(input); + expect(onValidateSqlChange).toBeCalledTimes(1); + expect(onValidateSqlChange).toBeCalledWith(expect.any(Object)); + }); +}); diff --git a/src/components/configEditor/QuerySettingsConfig.tsx b/src/components/configEditor/QuerySettingsConfig.tsx index 16e9bf21..2a13b8d8 100644 --- a/src/components/configEditor/QuerySettingsConfig.tsx +++ b/src/components/configEditor/QuerySettingsConfig.tsx @@ -1,7 +1,7 @@ import React, { } from 'react'; import { Switch, Input, Field } from '@grafana/ui'; -import { Components } from 'selectors'; import { ConfigSection } from '@grafana/experimental'; +import allLabels from 'labels'; interface QuerySettingsConfigProps { dialTimeout?: string; @@ -14,40 +14,43 @@ interface QuerySettingsConfigProps { export const QuerySettingsConfig = (props: QuerySettingsConfigProps) => { const { dialTimeout, queryTimeout, validateSql, onDialTimeoutChange, onQueryTimeoutChange, onValidateSqlChange } = props; + const labels = allLabels.components.Config.QuerySettingsConfig; + return ( - - + + - + diff --git a/src/components/configEditor/TracesConfig.test.tsx b/src/components/configEditor/TracesConfig.test.tsx new file mode 100644 index 00000000..7de514be --- /dev/null +++ b/src/components/configEditor/TracesConfig.test.tsx @@ -0,0 +1,464 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { TracesConfig } from './TracesConfig'; +import allLabels from 'labels'; + +const labelToPlaceholder = (l: string) => l.toLowerCase().replace(/ /g, '_'); + +describe('TracesConfig', () => { + it('should render', () => { + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + }); + + it('should call onDefaultDatabase when changed', () => { + const onDefaultDatabaseChange = jest.fn(); + const result = render( + {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(allLabels.components.Config.TracesConfig.defaultDatabase.placeholder); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onDefaultDatabaseChange).toBeCalledTimes(1); + expect(onDefaultDatabaseChange).toBeCalledWith('changed'); + }); + + it('should call onDefaultTable when changed', () => { + const onDefaultTableChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={onDefaultTableChange} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(allLabels.components.Config.TracesConfig.defaultTable.placeholder); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onDefaultTableChange).toBeCalledTimes(1); + expect(onDefaultTableChange).toBeCalledWith('changed'); + }); + + it('should call onOtelEnabled when changed', () => { + const onOtelEnabledChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={onOtelEnabledChange} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByRole('checkbox'); + expect(input).toBeInTheDocument(); + fireEvent.click(input); + expect(onOtelEnabledChange).toBeCalledTimes(1); + expect(onOtelEnabledChange).toBeCalledWith(true); + }); + + it('should call onOtelVersionChange when changed', () => { + const onOtelVersionChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={onOtelVersionChange} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const select = result.getByRole('combobox'); + expect(select).toBeInTheDocument(); + fireEvent.keyDown(select, { key: 'ArrowDown' }); + fireEvent.keyDown(select, { key: 'Enter' }); + expect(onOtelVersionChange).toBeCalledTimes(2); // 2 from hook + expect(onOtelVersionChange).toBeCalledWith(expect.any(String)); + }); + + it('should call onTraceIdColumnChange when changed', () => { + const onTraceIdColumnChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={onTraceIdColumnChange} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.traceId.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onTraceIdColumnChange).toBeCalledTimes(1); + expect(onTraceIdColumnChange).toBeCalledWith('changed'); + }); + + it('should call onSpanIdColumnChange when changed', () => { + const onSpanIdColumnChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={onSpanIdColumnChange} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.spanId.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onSpanIdColumnChange).toBeCalledTimes(1); + expect(onSpanIdColumnChange).toBeCalledWith('changed'); + }); + + it('should call onOperationNameColumnChange when changed', () => { + const onOperationNameColumnChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={onOperationNameColumnChange} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.operationName.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onOperationNameColumnChange).toBeCalledTimes(1); + expect(onOperationNameColumnChange).toBeCalledWith('changed'); + }); + + it('should call onParentSpanIdColumnChange when changed', () => { + const onParentSpanIdColumnChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={onParentSpanIdColumnChange} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.parentSpanId.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onParentSpanIdColumnChange).toBeCalledTimes(1); + expect(onParentSpanIdColumnChange).toBeCalledWith('changed'); + }); + + it('should call onServiceNameColumnChange when changed', () => { + const onServiceNameColumnChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={onServiceNameColumnChange} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.serviceName.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onServiceNameColumnChange).toBeCalledTimes(1); + expect(onServiceNameColumnChange).toBeCalledWith('changed'); + }); + + it('should call onDurationColumnChange when changed', () => { + const onDurationColumnChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={onDurationColumnChange} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.durationTime.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onDurationColumnChange).toBeCalledTimes(1); + expect(onDurationColumnChange).toBeCalledWith('changed'); + }); + + it('should call onDurationUnitChange when changed', () => { + const onDurationUnitChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={onDurationUnitChange} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const select = result.getByRole('combobox'); + expect(select).toBeInTheDocument(); + fireEvent.keyDown(select, { key: 'ArrowDown' }); + fireEvent.keyDown(select, { key: 'Enter' }); + expect(onDurationUnitChange).toBeCalledTimes(1); + expect(onDurationUnitChange).toBeCalledWith(expect.any(String)); + }); + + it('should call onStartTimeColumnChange when changed', () => { + const onStartTimeColumnChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={onStartTimeColumnChange} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.startTime.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onStartTimeColumnChange).toBeCalledTimes(1); + expect(onStartTimeColumnChange).toBeCalledWith('changed'); + }); + + it('should call onTagsColumnChange when changed', () => { + const onTagsColumnChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={onTagsColumnChange} + onServiceTagsColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.tags.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onTagsColumnChange).toBeCalledTimes(1); + expect(onTagsColumnChange).toBeCalledWith('changed'); + }); + + it('should call onServiceTagsColumnChange when changed', () => { + const onServiceTagsColumnChange = jest.fn(); + const result = render( + {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={onServiceTagsColumnChange} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.serviceTags.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onServiceTagsColumnChange).toBeCalledTimes(1); + expect(onServiceTagsColumnChange).toBeCalledWith('changed'); + }); +}); diff --git a/src/components/configEditor/TracesConfig.tsx b/src/components/configEditor/TracesConfig.tsx index 3cf2da87..7019905a 100644 --- a/src/components/configEditor/TracesConfig.tsx +++ b/src/components/configEditor/TracesConfig.tsx @@ -7,28 +7,15 @@ import { ColumnHint, TimeUnit } from 'types/queryBuilder'; import { versions as otelVersions } from 'otel'; import { LabeledInput } from './LabeledInput'; import { DurationUnitSelect } from 'components/queryBuilder/DurationUnitSelect'; +import { CHTracesConfig } from 'types/config'; +import allLabels from 'labels'; interface TraceConfigProps { - defaultDatabase?: string; - defaultTable?: string; + tracesConfig?: CHTracesConfig; onDefaultDatabaseChange: (v: any) => void; onDefaultTableChange: (v: any) => void; - - otelEnabled?: boolean; - otelVersion?: string; onOtelEnabledChange: (v: any) => void; onOtelVersionChange: (v: any) => void; - - traceIdColumn?: string; - spanIdColumn?: string; - operationNameColumn?: string; - parentSpanIdColumn?: string; - serviceNameColumn?: string; - durationColumn?: string; - durationUnit?: string; - startTimeColumn?: string; - tagsColumn?: string; - serviceTagsColumn?: string; onTraceIdColumnChange: (v: any) => void; onSpanIdColumnChange: (v: any) => void; onOperationNameColumnChange: (v: any) => void; @@ -42,17 +29,20 @@ interface TraceConfigProps { } export const TracesConfig = (props: TraceConfigProps) => { - const { defaultDatabase, defaultTable, onDefaultDatabaseChange, onDefaultTableChange } = props; - const { otelEnabled, otelVersion, onOtelEnabledChange, onOtelVersionChange } = props; const { + onDefaultDatabaseChange, onDefaultTableChange, + onOtelEnabledChange, onOtelVersionChange, onTraceIdColumnChange, onSpanIdColumnChange, onOperationNameColumnChange, onParentSpanIdColumnChange, onServiceNameColumnChange, onDurationColumnChange, onDurationUnitChange, onStartTimeColumnChange, onTagsColumnChange, onServiceTagsColumnChange, } = props; let { + defaultDatabase, defaultTable, + otelEnabled, otelVersion, traceIdColumn, spanIdColumn, operationNameColumn, parentSpanIdColumn, serviceNameColumn, durationColumn, durationUnit, startTimeColumn, tagsColumn, serviceTagsColumn - } = props; + } = (props.tracesConfig || {}) as CHTracesConfig; + const labels = allLabels.components.Config.TracesConfig; const otelConfig = otelVersions.find(v => v.version === otelVersion); if (otelEnabled && otelConfig) { @@ -70,40 +60,40 @@ export const TracesConfig = (props: TraceConfigProps) => { return ( onDefaultDatabaseChange(e.currentTarget.value)} - label="Default trace database" - aria-label="Default trace database" - placeholder="default" + label={labels.defaultDatabase.label} + aria-label={labels.defaultDatabase.label} + placeholder={labels.defaultDatabase.placeholder} /> onDefaultTableChange(e.currentTarget.value)} - label="Default trace table" - aria-label="Default trace table" - placeholder="traces" + label={labels.defaultTable.label} + aria-label={labels.defaultTable.label} + placeholder={labels.defaultTable.placeholder} /> { /> @@ -162,22 +152,22 @@ export const TracesConfig = (props: TraceConfigProps) => { /> diff --git a/src/components/queryBuilder/OrderByEditor.tsx b/src/components/queryBuilder/OrderByEditor.tsx index cd539935..8fa56227 100644 --- a/src/components/queryBuilder/OrderByEditor.tsx +++ b/src/components/queryBuilder/OrderByEditor.tsx @@ -9,7 +9,7 @@ import { } from 'types/queryBuilder'; import allLabels from 'labels'; import { styles } from 'styles'; -import { isAggregateQuery } from './utils'; +import { isAggregateQuery } from 'data/sqlGenerator'; interface OrderByItemProps { columnOptions: Array>; diff --git a/src/components/queryBuilder/OtelVersionSelect.test.tsx b/src/components/queryBuilder/OtelVersionSelect.test.tsx index 23dfc9cc..b891ebbc 100644 --- a/src/components/queryBuilder/OtelVersionSelect.test.tsx +++ b/src/components/queryBuilder/OtelVersionSelect.test.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { render, fireEvent } from '@testing-library/react'; import { OtelVersionSelect } from './OtelVersionSelect'; +import { versions as allVersions } from 'otel'; describe('OtelVersionSelect', () => { - const testVersion = '1.0.0-test'; + const testVersion = allVersions[0]; + const testVersionName = allVersions[0].version + ' (latest)'; it('should render with empty properties', () => { const result = render( @@ -22,12 +24,12 @@ describe('OtelVersionSelect', () => { {}} - selectedVersion={testVersion} + selectedVersion={testVersion.version} onVersionChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); - expect(result.getByText(testVersion)).toBeInTheDocument(); + expect(result.getByText(testVersionName)).toBeInTheDocument(); }); it('should call onEnabledChange when the switch is enabled', () => { @@ -36,7 +38,7 @@ describe('OtelVersionSelect', () => { {}} /> ); @@ -55,7 +57,7 @@ describe('OtelVersionSelect', () => { {}} /> ); @@ -74,7 +76,7 @@ describe('OtelVersionSelect', () => { {}} - selectedVersion={testVersion} + selectedVersion={testVersion.version} onVersionChange={onVersionChange} /> ); @@ -93,7 +95,7 @@ describe('OtelVersionSelect', () => { {}} - selectedVersion={testVersion} + selectedVersion={testVersion.version} onVersionChange={() => {}} /> ); diff --git a/src/components/queryBuilder/OtelVersionSelect.tsx b/src/components/queryBuilder/OtelVersionSelect.tsx index 5c5de72e..de78b1cc 100644 --- a/src/components/queryBuilder/OtelVersionSelect.tsx +++ b/src/components/queryBuilder/OtelVersionSelect.tsx @@ -45,6 +45,7 @@ export const OtelVersionSelect = (props: OtelVersionSelectProps) => { className="gf-form" value={enabled} onChange={e => onEnabledChange(e.currentTarget.checked)} + role="checkbox" />
- + = (props) => { onLogsConfigChange('defaultDatabase', db)} onDefaultTableChange={table => onLogsConfigChange('defaultTable', table)} - - otelEnabled={jsonData.logs?.otelEnabled} - otelVersion={jsonData.logs?.otelVersion} onOtelEnabledChange={v => onLogsConfigChange('otelEnabled', v)} onOtelVersionChange={v => onLogsConfigChange('otelVersion', v)} - - timeColumn={jsonData.logs?.timeColumn} - levelColumn={jsonData.logs?.levelColumn} - messageColumn={jsonData.logs?.messageColumn} onTimeColumnChange={c => onLogsConfigChange('timeColumn', c)} onLevelColumnChange={c => onLogsConfigChange('levelColumn', c)} onMessageColumnChange={c => onLogsConfigChange('messageColumn', c)} @@ -360,26 +353,11 @@ export const ConfigEditor: React.FC = (props) => { onTracesConfigChange('defaultDatabase', db)} onDefaultTableChange={table => onTracesConfigChange('defaultTable', table)} - - otelEnabled={jsonData.traces?.otelEnabled} - otelVersion={jsonData.traces?.otelVersion} onOtelEnabledChange={v => onTracesConfigChange('otelEnabled', v)} onOtelVersionChange={v => onTracesConfigChange('otelVersion', v)} - - traceIdColumn={jsonData.traces?.traceIdColumn} - spanIdColumn={jsonData.traces?.spanIdColumn} - operationNameColumn={jsonData.traces?.operationNameColumn} - parentSpanIdColumn={jsonData.traces?.parentSpanIdColumn} - serviceNameColumn={jsonData.traces?.serviceNameColumn} - durationColumn={jsonData.traces?.durationColumn} - durationUnit={jsonData.traces?.durationUnit} - startTimeColumn={jsonData.traces?.startTimeColumn} - tagsColumn={jsonData.traces?.tagsColumn} - serviceTagsColumn={jsonData.traces?.serviceTagsColumn} onTraceIdColumnChange={c => onTracesConfigChange('traceIdColumn', c)} onSpanIdColumnChange={c => onTracesConfigChange('spanIdColumn', c)} onOperationNameColumnChange={c => onTracesConfigChange('operationNameColumn', c)} @@ -395,8 +373,8 @@ export const ConfigEditor: React.FC = (props) => { {config.featureToggles['secureSocksDSProxyEnabled'] && gte(config.buildInfo.version, '10.0.0') && ( Date: Thu, 16 Nov 2023 14:09:31 -0500 Subject: [PATCH 44/95] revert some field/test changes --- .../queryBuilder/FilterEditor.test.tsx | 10 +- src/components/queryBuilder/FilterEditor.tsx | 103 ++++++++++++------ src/components/queryBuilder/LimitEditor.tsx | 12 +- src/components/queryBuilder/SqlPreview.tsx | 13 ++- src/data/sqlGenerator.ts | 7 +- 5 files changed, 93 insertions(+), 52 deletions(-) diff --git a/src/components/queryBuilder/FilterEditor.test.tsx b/src/components/queryBuilder/FilterEditor.test.tsx index 7c092cdf..b87e8d66 100644 --- a/src/components/queryBuilder/FilterEditor.test.tsx +++ b/src/components/queryBuilder/FilterEditor.test.tsx @@ -38,7 +38,9 @@ describe('FilterEditor', () => { const result = render( {}} />); expect(result.container.firstChild).not.toBeNull(); expect(result.getAllByText(selectors.components.QueryEditor.QueryBuilder.WHERE.label).length).toBe(1); - expect(result.getByTestId('query-builder-filters-add-button')).toBeInTheDocument(); + expect(result.queryByTestId('query-builder-filters-add-button')).not.toBeInTheDocument(); + expect(result.getByTestId('query-builder-filters-inline-add-button')).toBeInTheDocument(); + expect(result.getAllByTestId('query-builder-filters-inline-add-button').length).toBe(1); expect(result.getAllByTestId('query-builder-filters-remove-button').length).toBe(filters.length); }); it('should call the onFiltersChange with correct args', async () => { @@ -62,9 +64,11 @@ describe('FilterEditor', () => { const result = render(); expect(result.container.firstChild).not.toBeNull(); expect(result.getAllByText(selectors.components.QueryEditor.QueryBuilder.WHERE.label).length).toBe(1); - expect(result.getByTestId('query-builder-filters-add-button')).toBeInTheDocument(); + expect(result.queryByTestId('query-builder-filters-add-button')).not.toBeInTheDocument(); + expect(result.getByTestId('query-builder-filters-inline-add-button')).toBeInTheDocument(); + expect(result.getAllByTestId('query-builder-filters-inline-add-button').length).toBe(1); expect(result.getAllByTestId('query-builder-filters-remove-button').length).toBe(filters.length); - await userEvent.click(result.getByTestId('query-builder-filters-add-button')); + await userEvent.click(result.getByTestId('query-builder-filters-inline-add-button')); expect(onFiltersChange).toBeCalledTimes(1); expect(onFiltersChange).toHaveBeenNthCalledWith(1, [...filters, defaultNewFilter]); await userEvent.click(result.getAllByTestId('query-builder-filters-remove-button')[0]); diff --git a/src/components/queryBuilder/FilterEditor.tsx b/src/components/queryBuilder/FilterEditor.tsx index 57edfca6..944b43f4 100644 --- a/src/components/queryBuilder/FilterEditor.tsx +++ b/src/components/queryBuilder/FilterEditor.tsx @@ -1,11 +1,10 @@ import React, { useState } from 'react'; import { SelectableValue } from '@grafana/data'; -import { Button, Input, MultiSelect, RadioButtonGroup, Select } from '@grafana/ui'; +import { Button, InlineFormLabel, Input, MultiSelect, RadioButtonGroup, Select } from '@grafana/ui'; import { Filter, FilterOperator, TableColumn, NullFilter } from 'types/queryBuilder'; import * as utils from 'components/queryBuilder/utils'; import labels from 'labels'; import { styles } from 'styles'; -import { EditorField, EditorFieldGroup } from '@grafana/experimental'; const boolValues: Array> = [ { value: true, label: 'True' }, @@ -67,7 +66,11 @@ const FilterValueNumberItem = (props: { value: number; onChange: (value: number) const FilterValueSingleStringItem = (props: { value: string; onChange: (value: string) => void }) => { return (
- props.onChange(e.currentTarget.value)} /> + props.onChange(e.currentTarget.value)} + />
); }; @@ -329,7 +332,7 @@ export const FilterEditor = (props: { onFilterChange(index, filter); }; return ( - + <> {index !== 0 && ( onFilterConditionChange(e!)} /> )} @@ -337,13 +340,15 @@ export const FilterEditor = (props: { disabled={Boolean(filter.hint)} placeholder={filter.hint || undefined} value={filter.key} - width={30} + width={40} + className={styles.Common.inlineSelect} options={getFields()} isOpen={isOpen} onOpenMenu={() => setIsOpen(true)} onCloseMenu={() => setIsOpen(false)} onChange={(e) => onFilterNameChange(e.value!)} allowCustomValue={true} + menuPlacement={'bottom'} /> { onChange={e => setLimit(e.currentTarget.valueAsNumber)} onBlur={() => props.onLimitChange(limit)} /> - + ); }; diff --git a/src/components/queryBuilder/SqlPreview.tsx b/src/components/queryBuilder/SqlPreview.tsx index e613d3b0..1bffd7e6 100644 --- a/src/components/queryBuilder/SqlPreview.tsx +++ b/src/components/queryBuilder/SqlPreview.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { EditorField, EditorRow } from '@grafana/experimental'; +import { InlineFormLabel } from '@grafana/ui'; import labels from 'labels'; interface SqlPreviewProps { @@ -11,10 +11,11 @@ export const SqlPreview = (props: SqlPreviewProps) => { const { label, tooltip } = labels.components.SqlPreview; return ( - - -
{sql}
-
-
+
+ + {label} + +
{sql}
+
); }; diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index 69a2b3eb..2d79d55a 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -234,11 +234,12 @@ const getTraceDurationSelectSql = (columnIdentifier: string, timeUnit?: TimeUnit } const getLimit = (limit?: number | undefined): string => { - if (limit === undefined) { - return ''; + limit = Math.max(0, limit || 0); + if (limit > 0) { + return 'LIMIT ' + limit; } - return 'LIMIT ' + Math.max(0, limit || 1000); + return ''; }; const getFilters = (options: QueryBuilderOptions): string => { From 375f8fa87cb052995343b2d7b792e0467a1d9a8b Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 20 Nov 2023 15:12:48 -0500 Subject: [PATCH 45/95] only default LIMIT 1000 for logs --- src/components/queryBuilder/utils.ts | 4 ++-- src/components/queryBuilder/views/TableQueryBuilder.tsx | 2 +- src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx | 2 +- src/types/sql.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/queryBuilder/utils.ts b/src/components/queryBuilder/utils.ts index 39fc1ab5..23c98f0d 100644 --- a/src/components/queryBuilder/utils.ts +++ b/src/components/queryBuilder/utils.ts @@ -226,8 +226,8 @@ export const getOrderBy = (orderBy?: OrderBy[], prefix = true): string => { : ''; }; -const getLimit = (limit?: number): string => { - return ` LIMIT ` + (limit || 1000); +const getLimit = (limit: number): string => { + return ` LIMIT ` + limit; }; export const getSqlFromQueryBuilderOptions = (options: QueryBuilderOptions): string => { diff --git a/src/components/queryBuilder/views/TableQueryBuilder.tsx b/src/components/queryBuilder/views/TableQueryBuilder.tsx index 83d7b827..c9d705f4 100644 --- a/src/components/queryBuilder/views/TableQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TableQueryBuilder.tsx @@ -38,7 +38,7 @@ export const TableQueryBuilder = (props: TableQueryBuilderProps) => { aggregates: builderOptions.aggregates || [], groupBy: builderOptions.groupBy || [], orderBy: builderOptions.orderBy || [], - limit: builderOptions.limit || 1000, + limit: builderOptions.limit || 0, filters: builderOptions.filters || [], }), [builderOptions]); diff --git a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx index 679030ce..f55a66c6 100644 --- a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -45,7 +45,7 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => { aggregates: builderOptions.aggregates || [], groupBy: builderOptions.groupBy || [], orderBy: builderOptions.orderBy || [], - limit: builderOptions.limit || 1000, + limit: builderOptions.limit || 0, filters: builderOptions.filters || [], }), [builderOptions]); diff --git a/src/types/sql.ts b/src/types/sql.ts index 7e488682..607b9b07 100644 --- a/src/types/sql.ts +++ b/src/types/sql.ts @@ -54,7 +54,7 @@ export const defaultCHBuilderQuery: Omit = { mode: BuilderMode.List, columns: [], meta: {}, - limit: 1000 + limit: 0 }, }; export const defaultCHSqlQuery: Omit = { From bd21e7735db1f223609f48550adf04cbe3dea481 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 20 Nov 2023 15:13:02 -0500 Subject: [PATCH 46/95] persist db/table when switching query type --- src/hooks/useBuilderOptionsState.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hooks/useBuilderOptionsState.ts b/src/hooks/useBuilderOptionsState.ts index 1056d572..c301e36f 100644 --- a/src/hooks/useBuilderOptionsState.ts +++ b/src/hooks/useBuilderOptionsState.ts @@ -66,6 +66,8 @@ const actions = new Map Date: Mon, 20 Nov 2023 15:16:34 -0500 Subject: [PATCH 47/95] fix syntax issues from merge --- pkg/plugin/settings.go | 1 + pkg/plugin/settings_test.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/plugin/settings.go b/pkg/plugin/settings.go index c1ccdaa4..c68df04a 100644 --- a/pkg/plugin/settings.go +++ b/pkg/plugin/settings.go @@ -85,6 +85,7 @@ func LoadSettings(config backend.DataSourceInstanceSettings) (settings Settings, } else { settings.Secure = jsonData["secure"].(bool) } + } if jsonData["path"] != nil { settings.Path = jsonData["path"].(string) } diff --git a/pkg/plugin/settings_test.go b/pkg/plugin/settings_test.go index f75c7b12..fcf619fe 100644 --- a/pkg/plugin/settings_test.go +++ b/pkg/plugin/settings_test.go @@ -100,7 +100,7 @@ func TestLoadSettings(t *testing.T) { wantErr error description string }{ - {jsonData: `{ "server": "", "port": 443 }`, password: "", wantErr: ErrorMessageInvalidServerName, description: "should capture empty server name"}, + {jsonData: `{ "server": "", "port": 443 }`, password: "", wantErr: ErrorMessageInvalidHost, description: "should capture empty server name"}, {jsonData: `{ "server": "foo" }`, password: "", wantErr: ErrorMessageInvalidPort, description: "should capture nil port"}, {jsonData: ` "server": "foo", "port": 443, "username" : "foo" }`, password: "", wantErr: ErrorMessageInvalidJSON, description: "should capture invalid json"}, } From d70e377fce1172b7ea67d73a730b570625565f8a Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 20 Nov 2023 15:41:46 -0500 Subject: [PATCH 48/95] allow v3 config field names, updated tests --- pkg/plugin/settings.go | 10 ++++++++++ pkg/plugin/settings_test.go | 30 +++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/pkg/plugin/settings.go b/pkg/plugin/settings.go index c68df04a..e6c19854 100644 --- a/pkg/plugin/settings.go +++ b/pkg/plugin/settings.go @@ -60,9 +60,14 @@ func LoadSettings(config backend.DataSourceInstanceSettings) (settings Settings, return settings, fmt.Errorf("%s: %w", err.Error(), ErrorMessageInvalidJSON) } + // Deprecated: Replaced with Host for v4. Deserializes "server" field for old v3 configs. + if jsonData["server"] != nil { + settings.Host = jsonData["server"].(string) + } if jsonData["host"] != nil { settings.Host = jsonData["host"].(string) } + if jsonData["port"] != nil { if port, ok := jsonData["port"].(string); ok { settings.Port, err = strconv.ParseInt(port, 0, 64) @@ -128,9 +133,14 @@ func LoadSettings(config backend.DataSourceInstanceSettings) (settings Settings, settings.DefaultDatabase = jsonData["defaultDatabase"].(string) } + // Deprecated: Replaced with DialTimeout for v4. Deserializes "timeout" field for old v3 configs. + if jsonData["timeout"] != nil { + settings.DialTimeout = jsonData["timeout"].(string) + } if jsonData["dialTimeout"] != nil { settings.DialTimeout = jsonData["dialTimeout"].(string) } + if jsonData["queryTimeout"] != nil { if val, ok := jsonData["queryTimeout"].(string); ok { settings.QueryTimeout = val diff --git a/pkg/plugin/settings_test.go b/pkg/plugin/settings_test.go index fcf619fe..6805f3b9 100644 --- a/pkg/plugin/settings_test.go +++ b/pkg/plugin/settings_test.go @@ -24,11 +24,11 @@ func TestLoadSettings(t *testing.T) { wantErr error }{ { - name: "should parse and set all the json fields correctly", + name: "should parse and set all json fields correctly", args: args{ config: backend.DataSourceInstanceSettings{ UID: "ds-uid", - JSONData: []byte(`{ "server": "foo", "port": 443, "path": "custom-path", "username": "baz", "defaultDatabase":"example", "tlsSkipVerify": true, "tlsAuth" : true, "tlsAuthWithCACert": true, "timeout": "10", "enableSecureSocksProxy": true}`), + JSONData: []byte(`{ "host": "foo", "port": 443, "path": "custom-path", "username": "baz", "defaultDatabase":"example", "tlsSkipVerify": true, "tlsAuth" : true, "tlsAuthWithCACert": true, "dialTimeout": "10", "enableSecureSocksProxy": true}`), DecryptedSecureJSONData: map[string]string{"password": "bar", "tlsCACert": "caCert", "tlsClientCert": "clientCert", "tlsClientKey": "clientKey", "secureSocksProxyPassword": "test"}, }, }, @@ -62,10 +62,10 @@ func TestLoadSettings(t *testing.T) { wantErr: nil, }, { - name: "should converting string values to the correct type)", + name: "should convert string values to the correct type", args: args{ config: backend.DataSourceInstanceSettings{ - JSONData: []byte(`{"server": "test", "port": "443", "path": "custom-path", "tlsSkipVerify": "true", "tlsAuth" : "true", "tlsAuthWithCACert": "true"}`), + JSONData: []byte(`{"host": "test", "port": "443", "path": "custom-path", "tlsSkipVerify": "true", "tlsAuth" : "true", "tlsAuthWithCACert": "true"}`), DecryptedSecureJSONData: map[string]string{}, }, }, @@ -82,6 +82,22 @@ func TestLoadSettings(t *testing.T) { }, wantErr: nil, }, + { + name: "should parse v3 config fields into new fields", + args: args{ + config: backend.DataSourceInstanceSettings{ + JSONData: []byte(`{"server": "test", "port": 443, "timeout": "10"}`), + DecryptedSecureJSONData: map[string]string{}, + }, + }, + wantSettings: Settings{ + Host: "test", + Port: 443, + DialTimeout: "10", + QueryTimeout: "60", + }, + wantErr: nil, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -100,9 +116,9 @@ func TestLoadSettings(t *testing.T) { wantErr error description string }{ - {jsonData: `{ "server": "", "port": 443 }`, password: "", wantErr: ErrorMessageInvalidHost, description: "should capture empty server name"}, - {jsonData: `{ "server": "foo" }`, password: "", wantErr: ErrorMessageInvalidPort, description: "should capture nil port"}, - {jsonData: ` "server": "foo", "port": 443, "username" : "foo" }`, password: "", wantErr: ErrorMessageInvalidJSON, description: "should capture invalid json"}, + {jsonData: `{ "host": "", "port": 443 }`, password: "", wantErr: ErrorMessageInvalidHost, description: "should capture empty server name"}, + {jsonData: `{ "host": "foo" }`, password: "", wantErr: ErrorMessageInvalidPort, description: "should capture nil port"}, + {jsonData: ` "host": "foo", "port": 443, "username" : "foo" }`, password: "", wantErr: ErrorMessageInvalidJSON, description: "should capture invalid json"}, } for i, tc := range tests { t.Run(fmt.Sprintf("[%v/%v] %s", i+1, len(tests), tc.description), func(t *testing.T) { From 9ce290851b5d41763ae5e49da52ce73854bcea58 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 20 Nov 2023 17:51:51 -0500 Subject: [PATCH 49/95] remove need for effect in SqlEditor --- src/components/SqlEditor.test.tsx | 12 +++--- src/components/SqlEditor.tsx | 38 ++++++++----------- .../queryBuilder/QueryTypeSwitcher.tsx | 2 +- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/components/SqlEditor.test.tsx b/src/components/SqlEditor.test.tsx index 9f63ea5e..032a38d3 100644 --- a/src/components/SqlEditor.test.tsx +++ b/src/components/SqlEditor.test.tsx @@ -36,20 +36,20 @@ describe('SQL Editor', () => { expect(screen.queryByText(rawSql)).toBeInTheDocument(); }); it('Should Expand Query', async () => { - const onChangeValue = jest.fn(); + const onChange = jest.fn(); const onRunQuery = jest.fn(); - await waitFor(() => + const result = await waitFor(() => render( )); - expect(screen.queryByText('test')).toBeInTheDocument(); - await userEvent.click(screen.getByTestId(Components.QueryEditor.CodeEditor.Expand)); - expect(onChangeValue).toHaveBeenCalledTimes(2); + expect(result.queryByText('test')).toBeInTheDocument(); + await userEvent.click(result.getByTestId(Components.QueryEditor.CodeEditor.Expand)); + expect(onChange).toHaveBeenCalledTimes(0); // TODO: codeEditor isn't mounting and wont call onChange }); }); diff --git a/src/components/SqlEditor.tsx b/src/components/SqlEditor.tsx index 0be4c324..9517055c 100644 --- a/src/components/SqlEditor.tsx +++ b/src/components/SqlEditor.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { CoreApp, QueryEditorProps } from '@grafana/data'; import { CodeEditor } from '@grafana/ui'; import { Datasource } from 'data/CHDatasource'; @@ -26,34 +26,26 @@ export const SqlEditor = (props: SqlEditorProps) => { const { app, query, onChange, datasource } = props; const sqlQuery = query as CHSqlQuery; const [codeEditor, setCodeEditor] = useState(); - const [queryType, setQueryType] = useState(QueryType.Table); - const [sql, setSql] = useState(''); - const [expand, setExpand] = useState({ + const [expand, _setExpand] = useState({ height: defaultHeight, icon: 'plus', on: sqlQuery.expand || false, }); + const queryType = sqlQuery.queryType || QueryType.Table; - useEffect(() => { - sqlQuery.queryType && setQueryType(sqlQuery.queryType); - setSql(sqlQuery.rawSql); - - // Run on load - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { + const saveChanges = (changes: Partial) => { onChange({ - ...query, + ...sqlQuery, editorType: EditorType.SQL, - queryType, - rawSql: sql, - expand: expand.on, - format: mapQueryTypeToGrafanaFormat(queryType), + format: mapQueryTypeToGrafanaFormat(changes.queryType || queryType), + ...changes }); + } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [queryType, sql, expand]) + const setExpand = (expand: Expand) => { + _setExpand(expand); + saveChanges({ expand: expand.on }); + } const onToggleExpand = () => { const on = !expand.on; @@ -127,7 +119,7 @@ export const SqlEditor = (props: SqlEditorProps) => { <> { app === CoreApp.Explore &&
- + saveChanges({ queryType })} sqlEditor />
}
@@ -143,10 +135,10 @@ export const SqlEditor = (props: SqlEditorProps) => { height={expand.height} language="sql" value={query.rawSql || ''} - onSave={setSql} + onSave={sql => saveChanges({ rawSql: sql })} showMiniMap={false} showLineNumbers={true} - onBlur={setSql} + onBlur={sql => saveChanges({ rawSql: sql })} onEditorDidMount={(editor: any) => handleMount(editor)} />
diff --git a/src/components/queryBuilder/QueryTypeSwitcher.tsx b/src/components/queryBuilder/QueryTypeSwitcher.tsx index 26b05fb5..f898556d 100644 --- a/src/components/queryBuilder/QueryTypeSwitcher.tsx +++ b/src/components/queryBuilder/QueryTypeSwitcher.tsx @@ -40,7 +40,7 @@ export const QueryTypeSwitcher = (props: QueryTypeSwitcherProps) => { {label} - onChange(v)} /> + ); }; From 1e754aea31e90dbf0bf26f7f12187dc5678442b8 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 20 Nov 2023 17:52:11 -0500 Subject: [PATCH 50/95] prevent overwriting state for raw sql editor --- src/views/CHQueryEditor.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/CHQueryEditor.tsx b/src/views/CHQueryEditor.tsx index 3daf6ac9..60ca3b47 100644 --- a/src/views/CHQueryEditor.tsx +++ b/src/views/CHQueryEditor.tsx @@ -42,7 +42,7 @@ const CHEditorByType = (props: CHQueryEditorProps) => { */ const queryKey = query.key || '' const lastKey = useRef(queryKey); - if (queryKey !== lastKey.current) { + if (queryKey !== lastKey.current && query.editorType === EditorType.Builder) { builderOptionsDispatch(setAllOptions((query as CHBuilderQuery).builderOptions || {})); lastKey.current = queryKey; } @@ -54,7 +54,7 @@ const CHEditorByType = (props: CHQueryEditorProps) => { } useEffect(() => { - if (shouldSkipChanges.current) { + if (shouldSkipChanges.current || query.editorType !== EditorType.Builder) { return; } From 733fb4797f702429ae9c93190dd5d7b0894d7222 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 21 Nov 2023 12:46:05 -0500 Subject: [PATCH 51/95] skip syncing query if using raw sql editor --- src/views/CHQueryEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/CHQueryEditor.tsx b/src/views/CHQueryEditor.tsx index 60ca3b47..9ef81c81 100644 --- a/src/views/CHQueryEditor.tsx +++ b/src/views/CHQueryEditor.tsx @@ -54,7 +54,7 @@ const CHEditorByType = (props: CHQueryEditorProps) => { } useEffect(() => { - if (shouldSkipChanges.current || query.editorType !== EditorType.Builder) { + if (shouldSkipChanges.current || query.editorType === EditorType.SQL) { return; } From f4b3c8d2acb6c640ef4aa25460fc55c7d506bd63 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 21 Nov 2023 12:46:52 -0500 Subject: [PATCH 52/95] fix EditorTypeSwitcher not responding to state --- src/components/queryBuilder/EditorTypeSwitcher.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/queryBuilder/EditorTypeSwitcher.tsx b/src/components/queryBuilder/EditorTypeSwitcher.tsx index f82e0631..0ef6cd8e 100644 --- a/src/components/queryBuilder/EditorTypeSwitcher.tsx +++ b/src/components/queryBuilder/EditorTypeSwitcher.tsx @@ -26,8 +26,7 @@ const options: Array> = [ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { const { query, onChange } = props; const { label, tooltip, switcher, cannotConvert } = labels.components.EditorTypeSwitcher; - const editorType: EditorType = query.editorType; - const [editor, setEditor] = useState(editorType || EditorType.Builder); + const editorType: EditorType = query.editorType || EditorType.Builder; const [confirmModalState, setConfirmModalState] = useState(false); const [cannotConvertModalState, setCannotConvertModalState] = useState(false); const [errorMessage, setErrorMessage] = useState(''); @@ -42,7 +41,6 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { setConfirmModalState(true); } } else { - setEditor(editorType); let builderOptions: QueryBuilderOptions; switch (query.editorType) { case EditorType.Builder: @@ -85,7 +83,7 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { {label} - onEditorTypeChange(e)} /> + onEditorTypeChange(e)} /> Date: Tue, 21 Nov 2023 13:41:49 -0500 Subject: [PATCH 53/95] default logs queries to LIMIT 1000 on initial load --- src/components/queryBuilder/views/LogsQueryBuilder.tsx | 2 +- src/hooks/useBuilderOptionsState.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index 633a0dec..062b01b9 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -56,7 +56,7 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { // liveView: builderOptions.meta?.liveView || false, filters: builderOptions.filters || [], orderBy: builderOptions.orderBy || [], - limit: builderOptions.limit || 1000, + limit: builderOptions.limit || 0, }), [builderOptions]); const showConfigWarning = datasource.getDefaultLogsColumns().size === 0; diff --git a/src/hooks/useBuilderOptionsState.ts b/src/hooks/useBuilderOptionsState.ts index c301e36f..4c4a8d54 100644 --- a/src/hooks/useBuilderOptionsState.ts +++ b/src/hooks/useBuilderOptionsState.ts @@ -126,6 +126,11 @@ const buildInitialState = (savedOptions?: Partial): QueryBu } }; + // TODO: make different defaultOptions constant per query type + if (savedOptions?.queryType === QueryType.Logs && savedOptions.limit === undefined) { + initialState.limit = 1000; + } + return initialState; }; From bece9bebd0a26af953c61d67817fbc11d00ea6ba Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 22 Nov 2023 17:34:37 -0500 Subject: [PATCH 54/95] dont throw error for missing time column --- src/components/queryBuilder/utils.test.ts | 23 +++++++++++------------ src/components/queryBuilder/utils.ts | 9 ++------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/components/queryBuilder/utils.test.ts b/src/components/queryBuilder/utils.test.ts index c51ed2ea..412bf5df 100644 --- a/src/components/queryBuilder/utils.test.ts +++ b/src/components/queryBuilder/utils.test.ts @@ -436,18 +436,17 @@ describe('Utils: getSqlFromQueryBuilderOptions and getQueryOptionsFromSql', () = false ); - it('timeseries function throws if "timeFieldType" not a DateType', () => { - expect(() => - getSqlFromQueryBuilderOptions({ - database: 'db', - table: 'foo', - queryType: QueryType.TimeSeries, - mode: BuilderMode.Trend, - columns: [{ name: 'time', type: 'boolean', hint: ColumnHint.Time }], - aggregates: [], - filters: [], - }) - ).toThrowErrorMatchingInlineSnapshot('"time column is expected to be a valid date type."'); + it('timeseries function returns empty $__timeInterval macro if time column missing', () => { + const sql = getSqlFromQueryBuilderOptions({ + database: 'db', + table: 'foo', + queryType: QueryType.TimeSeries, + mode: BuilderMode.Trend, + aggregates: [], + filters: [], + }); + + expect(sql).toContain('$__timeInterval()'); }); }); diff --git a/src/components/queryBuilder/utils.ts b/src/components/queryBuilder/utils.ts index 23c98f0d..79a11911 100644 --- a/src/components/queryBuilder/utils.ts +++ b/src/components/queryBuilder/utils.ts @@ -113,8 +113,7 @@ const getTrendByQuery = ( table = '', metrics: AggregateColumn[] = [], groupBy: string[] = [], - timeField = '', - timeFieldType = '' + timeField = '' ): string => { metrics = metrics && metrics.length > 0 ? metrics : []; @@ -245,16 +244,12 @@ export const getSqlFromQueryBuilderOptions = (options: QueryBuilderOptions): str break; case BuilderMode.Trend: const timeColumn = getColumnByHint(options, ColumnHint.Time); - if (!isDateType(timeColumn?.type || '')) { - throw new Error('time column is expected to be a valid date type.'); - } query += getTrendByQuery( database, table, options.aggregates, options.groupBy, - timeColumn?.name || '', - timeColumn?.type || '' + timeColumn?.name || '' ); const trendFilters = getFilters(options.filters || []); From 5c3d510fd7397a941205e3991cc015bbf27fb7cd Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 27 Nov 2023 16:26:38 -0500 Subject: [PATCH 55/95] default all query builder views to LIMIT 1000 --- src/hooks/useBuilderOptionsState.ts | 5 ----- src/types/sql.ts | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/hooks/useBuilderOptionsState.ts b/src/hooks/useBuilderOptionsState.ts index 4c4a8d54..c301e36f 100644 --- a/src/hooks/useBuilderOptionsState.ts +++ b/src/hooks/useBuilderOptionsState.ts @@ -126,11 +126,6 @@ const buildInitialState = (savedOptions?: Partial): QueryBu } }; - // TODO: make different defaultOptions constant per query type - if (savedOptions?.queryType === QueryType.Logs && savedOptions.limit === undefined) { - initialState.limit = 1000; - } - return initialState; }; diff --git a/src/types/sql.ts b/src/types/sql.ts index 607b9b07..7e488682 100644 --- a/src/types/sql.ts +++ b/src/types/sql.ts @@ -54,7 +54,7 @@ export const defaultCHBuilderQuery: Omit = { mode: BuilderMode.List, columns: [], meta: {}, - limit: 0 + limit: 1000 }, }; export const defaultCHSqlQuery: Omit = { From 3c78096080340a031ed7a0c29f1d1c1cd0b3718b Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 28 Nov 2023 10:54:00 -0500 Subject: [PATCH 56/95] add all column for aggregate editor --- src/components/queryBuilder/AggregateEditor.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/queryBuilder/AggregateEditor.tsx b/src/components/queryBuilder/AggregateEditor.tsx index 269359fe..26031f12 100644 --- a/src/components/queryBuilder/AggregateEditor.tsx +++ b/src/components/queryBuilder/AggregateEditor.tsx @@ -69,10 +69,14 @@ interface AggregateEditorProps { aggregates: AggregateColumn[]; onAggregatesChange: (aggregates: AggregateColumn[]) => void; } + +const allColumnName = '*'; + export const AggregateEditor = (props: AggregateEditorProps) => { const { allColumns, aggregates, onAggregatesChange } = props; - const columnOptions: Array> = allColumns.map(c => ({ label: c.name, value: c.name })); const { label, tooltip, addLabel } = labels.components.AggregatesEditor; + const columnOptions: Array> = allColumns.map(c => ({ label: c.name, value: c.name })); + columnOptions.push({ label: allColumnName, value: allColumnName }); const addAggregate = () => { const nextAggregates: AggregateColumn[] = aggregates.slice(); From 11eca7eca46ebbefee2f384571c995444a4496af Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 28 Nov 2023 15:28:33 -0500 Subject: [PATCH 57/95] v3 to v4 migration logic --- src/__mocks__/datasource.ts | 1 + src/components/SqlEditor.test.tsx | 4 +- src/components/SqlEditor.tsx | 2 + .../queryBuilder/EditorTypeSwitcher.test.tsx | 2 + src/data/CHDatasource.test.ts | 1 + src/data/CHDatasource.ts | 1 + src/data/migration.ts | 187 ++++++++++++++++++ src/data/utils.ts | 21 +- src/hooks/useMigratedQuery.ts | 7 + src/types/sql.ts | 3 + src/utils/version.ts | 3 + src/views/CHQueryEditor.test.tsx | 2 +- src/views/CHQueryEditor.tsx | 10 +- 13 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 src/data/migration.ts create mode 100644 src/hooks/useMigratedQuery.ts diff --git a/src/__mocks__/datasource.ts b/src/__mocks__/datasource.ts index 61ce8063..ca50ee13 100644 --- a/src/__mocks__/datasource.ts +++ b/src/__mocks__/datasource.ts @@ -43,6 +43,7 @@ export const mockDatasource = new Datasource({ }); mockDatasource.adHocFiltersStatus = 1; // most tests should skip checking the CH version. We will set ad hoc filters to enabled to avoid running the CH version check export const mockQuery: CHQuery = { + pluginVersion: '', rawSql: 'select * from foo', refId: '', editorType: EditorType.SQL, diff --git a/src/components/SqlEditor.test.tsx b/src/components/SqlEditor.test.tsx index 032a38d3..11fe682c 100644 --- a/src/components/SqlEditor.test.tsx +++ b/src/components/SqlEditor.test.tsx @@ -27,7 +27,7 @@ describe('SQL Editor', () => { const rawSql = 'foo'; render( { const result = await waitFor(() => render( ; @@ -36,6 +37,7 @@ export const SqlEditor = (props: SqlEditorProps) => { const saveChanges = (changes: Partial) => { onChange({ ...sqlQuery, + pluginVersion, editorType: EditorType.SQL, format: mapQueryTypeToGrafanaFormat(changes.queryType || queryType), ...changes diff --git a/src/components/queryBuilder/EditorTypeSwitcher.test.tsx b/src/components/queryBuilder/EditorTypeSwitcher.test.tsx index 5642f0d7..f8a6702d 100644 --- a/src/components/queryBuilder/EditorTypeSwitcher.test.tsx +++ b/src/components/queryBuilder/EditorTypeSwitcher.test.tsx @@ -40,6 +40,7 @@ describe('EditorTypeSwitcher', () => { const result = render( { const result = render( { describe('SupplementaryQueriesSupport', () => { const query: CHBuilderQuery = { + pluginVersion: '', refId: '42', editorType: EditorType.Builder, rawSql: 'SELECT * FROM system.numbers LIMIT 1', diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index 7e941218..3eef6e42 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -180,6 +180,7 @@ export class Datasource return { // format: Format.AUTO, // selectedFormat: Format.AUTO, + pluginVersion: '', editorType: EditorType.SQL, rawSql: logVolumeSupplementaryQuery, refId: '', diff --git a/src/data/migration.ts b/src/data/migration.ts new file mode 100644 index 00000000..745f573f --- /dev/null +++ b/src/data/migration.ts @@ -0,0 +1,187 @@ +import { ColumnHint, Filter, QueryBuilderOptions, QueryType, SelectedColumn } from "types/queryBuilder"; +import { CHBuilderQuery, CHQuery, CHSqlQuery, EditorType } from "types/sql"; +import { isVersionGtOrEq, pluginVersion } from "utils/version"; +import { mapGrafanaFormatToQueryType } from "./utils"; + +export type AnyCHQuery = Partial & {[k: string]: any}; +export type AnyQueryBuilderOptions = Partial & {[k: string]: any}; + +/** + * Takes a CHQuery and transforms it to the latest interface. + * Returns undefined if no migration is applied. + */ +export const migrateCHQuery = (savedQuery: CHQuery): CHQuery | undefined => { + if (isV3CHQuery(savedQuery)) { + return migrateV3CHQuery(savedQuery); + } + + return undefined; +}; + +/** + * Takes v3 CHQuery and returns a version compatible with the latest editor. + */ +const migrateV3CHQuery = (savedQuery: AnyCHQuery): CHQuery => { + // Builder Query + if (savedQuery['queryType'] === 'builder') { + const builderQuery: CHBuilderQuery = { + ...savedQuery, + pluginVersion, + editorType: EditorType.Builder, + builderOptions: migrateV3QueryBuilderOptions(savedQuery['builderOptions'] || {}), + rawSql: savedQuery.rawSql || '', + refId: savedQuery.refId || '', + format: savedQuery.format, + meta: {} + }; + + if (savedQuery?.meta?.timezone) { + builderQuery.meta!.timezone = savedQuery.meta.timezone; + } + + // delete unwanted properties from v3 + delete (builderQuery as any)['queryType']; + delete (builderQuery as any)['selectedFormat']; + + return builderQuery; + } + + // Raw SQL Query + const rawSqlQuery: CHSqlQuery = { + ...savedQuery, + pluginVersion, + editorType: EditorType.SQL, + rawSql: savedQuery.rawSql || '', + refId: savedQuery.refId || '', + format: savedQuery.format, + queryType: mapGrafanaFormatToQueryType(savedQuery.format), + meta: {} + }; + + if (savedQuery.expand) { + rawSqlQuery.expand = savedQuery.expand; + } + + if (savedQuery.meta) { + const meta = (savedQuery.meta as any); + if (meta.timezone) { + rawSqlQuery.meta!.timezone = meta.timezone; + } + + if (meta.builderOptions) { + rawSqlQuery.meta!.builderOptions = migrateV3QueryBuilderOptions(meta.builderOptions); + } + } + + // delete unwanted properties from v3 + delete (rawSqlQuery as any)['builderOptions']; + delete (rawSqlQuery as any)['selectedFormat']; + + return rawSqlQuery; +}; + +/** + * Takes v3 options and returns a version compatible with the latest builder. + */ +const migrateV3QueryBuilderOptions = (savedOptions: AnyQueryBuilderOptions): QueryBuilderOptions => { + const mapped: QueryBuilderOptions = { + database: savedOptions.database || '', + table: savedOptions.table || '', + queryType: getV3QueryType(savedOptions) + }; + + if (savedOptions.mode) { + mapped.mode = savedOptions.mode; + } + + if (savedOptions['fields'] || Array.isArray(savedOptions['fields'])) { + const oldColumns: string[] = savedOptions['fields']; + const timeField: string = savedOptions['timeField']; + const timeFieldType: string = savedOptions['timeFieldType']; + const logLevelField: string = savedOptions['logLevelField']; + + mapped.columns = oldColumns.map((colName: string) => { + const result: SelectedColumn = { + name: colName, + }; + + if (colName === timeField) { + result.hint = ColumnHint.Time; + if (timeFieldType) { + result.type = timeFieldType; + } + } else if (colName === logLevelField) { + result.hint = ColumnHint.LogLevel; + } + + return result; + }); + } + + if (savedOptions['metrics'] || Array.isArray(savedOptions['metrics'])) { + const oldAggregates: any[] = savedOptions['metrics']; + mapped.aggregates = oldAggregates.map(agg => ({ + aggregateType: agg['aggregation'], + column: agg['field'], + alias: agg['alias'] + })); + } + + if (savedOptions.filters || Array.isArray(savedOptions.filters)) { + const oldFilters: Filter[] = savedOptions.filters; + const timeField: string = savedOptions['timeField']; + const logLevelField: string = savedOptions['logLevelField']; + + mapped.filters = oldFilters.map((filter: Filter) => { + const result: Filter = { + ...filter + }; + + if (filter.key === timeField) { + result.hint = ColumnHint.Time; + } else if (filter.key === logLevelField) { + result.hint = ColumnHint.LogLevel; + } + + return result; + }); + } + + if (savedOptions.groupBy || Array.isArray(savedOptions.groupBy)) { + mapped.groupBy = savedOptions.groupBy; + } + + if (savedOptions.orderBy || Array.isArray(savedOptions.orderBy)) { + mapped.orderBy = savedOptions.orderBy; + } + + if (savedOptions.limit !== undefined && savedOptions.limit >= 0) { + mapped.limit = savedOptions.limit; + } + + return mapped; +}; + + +/** + * Checks if CHQuery is from <= v3 options. + */ +const isV3CHQuery = (savedQuery: AnyCHQuery): boolean => { + // pluginVersion was added in v4 + const oldPluginVersion = !savedQuery['pluginVersion'] || !isVersionGtOrEq(savedQuery.pluginVersion, '4.0.0'); + const oldQueryType = savedQuery['queryType'] === 'sql' || savedQuery['queryType'] === 'builder'; + return oldPluginVersion && oldQueryType; +}; + +/** + * Takes v3 options and returns the optimal QueryType. Defaults to QueryType.Table. + */ +const getV3QueryType = (savedOptions: AnyQueryBuilderOptions): QueryType => { + if (savedOptions['timeField']) { + return QueryType.TimeSeries; + } else if (savedOptions['logLevelField']) { + return QueryType.Logs; + } + + return QueryType.Table; +}; diff --git a/src/data/utils.ts b/src/data/utils.ts index a702af28..c7b14e78 100644 --- a/src/data/utils.ts +++ b/src/data/utils.ts @@ -17,7 +17,7 @@ export const isBuilderOptionsRunnable = (builderOptions: QueryBuilderOptions): b * Converts QueryType to Grafana format * src: https://github.com/grafana/sqlds/blob/main/query.go#L20 */ -export const mapQueryTypeToGrafanaFormat = (t: QueryType): number => { +export const mapQueryTypeToGrafanaFormat = (t?: QueryType): number => { switch (t) { case QueryType.Table: return 1; @@ -31,3 +31,22 @@ export const mapQueryTypeToGrafanaFormat = (t: QueryType): number => { return 1 << 8; // an unused u32, defaults to timeseries/graph on plugin backend. } }; + +/** + * Converts Grafana format to builder QueryType + * src: https://github.com/grafana/sqlds/blob/main/query.go#L20 + */ +export const mapGrafanaFormatToQueryType = (f?: number): QueryType => { + switch (f) { + case 0: + return QueryType.TimeSeries; + case 1: + return QueryType.Table; + case 2: + return QueryType.Logs; + case 3: + return QueryType.Traces; + default: + return QueryType.Table; + } +}; diff --git a/src/hooks/useMigratedQuery.ts b/src/hooks/useMigratedQuery.ts new file mode 100644 index 00000000..c941120c --- /dev/null +++ b/src/hooks/useMigratedQuery.ts @@ -0,0 +1,7 @@ +import { migrateCHQuery } from "data/migration"; +import { CHQuery } from "types/sql"; + +export default (currentQuery: CHQuery): CHQuery => { + const migratedQuery = migrateCHQuery(currentQuery); + return migratedQuery === undefined ? currentQuery : migratedQuery; +} diff --git a/src/types/sql.ts b/src/types/sql.ts index 7e488682..46a4c920 100644 --- a/src/types/sql.ts +++ b/src/types/sql.ts @@ -10,6 +10,7 @@ export enum EditorType { } export interface CHQueryBase extends DataQuery { + pluginVersion: string, editorType: EditorType; rawSql: string; @@ -45,6 +46,7 @@ export type CHQuery = CHSqlQuery | CHBuilderQuery; // TODO: these aren't really types export const defaultEditorType: EditorType = EditorType.Builder; export const defaultCHBuilderQuery: Omit = { + pluginVersion: '', editorType: EditorType.Builder, rawSql: '', builderOptions: { @@ -58,6 +60,7 @@ export const defaultCHBuilderQuery: Omit = { }, }; export const defaultCHSqlQuery: Omit = { + pluginVersion: '', editorType: EditorType.SQL, rawSql: '', expand: false, diff --git a/src/utils/version.ts b/src/utils/version.ts index f7e6fccb..f92c5421 100644 --- a/src/utils/version.ts +++ b/src/utils/version.ts @@ -1,5 +1,8 @@ +import pluginPackage from '../../package.json'; import { isNumber } from 'lodash'; +export const pluginVersion = pluginPackage.version; + const versionPattern = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z\.]+))?/; export class SemVersion { diff --git a/src/views/CHQueryEditor.test.tsx b/src/views/CHQueryEditor.test.tsx index 152519f2..d134cddd 100644 --- a/src/views/CHQueryEditor.test.tsx +++ b/src/views/CHQueryEditor.test.tsx @@ -25,7 +25,7 @@ describe('Query Editor', () => { const rawSql = 'foo'; render( ; @@ -18,15 +20,16 @@ export type CHQueryEditorProps = QueryEditorProps * Top level query editor component */ export const CHQueryEditor = (props: CHQueryEditorProps) => { - const { onRunQuery } = props; + const { query: savedQuery, onRunQuery } = props; + const query = useMigratedQuery(savedQuery); return ( <>
- +
- + ); }; @@ -61,6 +64,7 @@ const CHEditorByType = (props: CHQueryEditorProps) => { const sql = generateSql(builderOptions); onChange({ ...query, + pluginVersion, editorType: EditorType.Builder, rawSql: sql, builderOptions, From dc4e34a7e878098ded96afba3343172a4e4d03ec Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 28 Nov 2023 15:50:23 -0500 Subject: [PATCH 58/95] Closeable column config warnings --- src/components/queryBuilder/views/LogsQueryBuilder.tsx | 6 +++--- src/components/queryBuilder/views/TraceQueryBuilder.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index 062b01b9..85fa078c 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; import { Filter, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint, DateFilterWithoutValue, FilterOperator, OrderByDirection, TableColumn } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; @@ -58,7 +58,7 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { orderBy: builderOptions.orderBy || [], limit: builderOptions.limit || 0, }), [builderOptions]); - const showConfigWarning = datasource.getDefaultLogsColumns().size === 0; + const [showConfigWarning, setConfigWarningOpen] = useState(datasource.getDefaultLogsColumns().size === 0 && builderOptions.columns?.length === 0); const onOptionChange = useBuilderOptionChanges(next => { const nextColumns = next.selectedColumns.slice(); @@ -86,7 +86,7 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { useDefaultFilters(builderOptions.table, builderState.timeColumn, builderState.filters, builderState.orderBy, builderOptionsDispatch); const configWarning = showConfigWarning && ( - + setConfigWarningOpen(false)}>
{'To speed up your query building, enter your default logs configuration in your '} diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index ba26bf35..0308cff2 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -43,7 +43,7 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { const { datasource, builderOptions, builderOptionsDispatch } = props; const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); const isNewQuery = useIsNewQuery(builderOptions); - const showConfigWarning = datasource.getDefaultTraceColumns().size === 0; + const [showConfigWarning, setConfigWarningOpen] = useState(datasource.getDefaultTraceColumns().size === 0 && builderOptions.columns?.length === 0); const [isColumnsOpen, setColumnsOpen] = useState(showConfigWarning); // Toggle Columns collapsable section const [isFiltersOpen, setFiltersOpen] = useState(true); // Toggle Filters collapsable section const labels = allLabels.components.TraceQueryBuilder; @@ -93,7 +93,7 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { useOtelColumns(builderState.otelEnabled, builderState.otelVersion, builderOptionsDispatch); const configWarning = showConfigWarning && ( - + setConfigWarningOpen(false)}>
{'To speed up your query building, enter your default trace configuration in your '} From bb054c0844866c28bedb2274f47e4e5475304c78 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 28 Nov 2023 15:55:19 -0500 Subject: [PATCH 59/95] loosen check for v3 query data --- src/data/migration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/migration.ts b/src/data/migration.ts index 745f573f..02735e34 100644 --- a/src/data/migration.ts +++ b/src/data/migration.ts @@ -170,7 +170,7 @@ const isV3CHQuery = (savedQuery: AnyCHQuery): boolean => { // pluginVersion was added in v4 const oldPluginVersion = !savedQuery['pluginVersion'] || !isVersionGtOrEq(savedQuery.pluginVersion, '4.0.0'); const oldQueryType = savedQuery['queryType'] === 'sql' || savedQuery['queryType'] === 'builder'; - return oldPluginVersion && oldQueryType; + return oldPluginVersion || oldQueryType; }; /** From 62692dc78c14d3bf4b4c13637213253ab3c98a57 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 29 Nov 2023 10:06:49 -0500 Subject: [PATCH 60/95] rename setExpand state hook --- src/components/SqlEditor.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/SqlEditor.tsx b/src/components/SqlEditor.tsx index e16984fa..2bee3696 100644 --- a/src/components/SqlEditor.tsx +++ b/src/components/SqlEditor.tsx @@ -27,7 +27,7 @@ export const SqlEditor = (props: SqlEditorProps) => { const { app, query, onChange, datasource } = props; const sqlQuery = query as CHSqlQuery; const [codeEditor, setCodeEditor] = useState(); - const [expand, _setExpand] = useState({ + const [expand, setExpand] = useState({ height: defaultHeight, icon: 'plus', on: sqlQuery.expand || false, @@ -44,8 +44,8 @@ export const SqlEditor = (props: SqlEditorProps) => { }); } - const setExpand = (expand: Expand) => { - _setExpand(expand); + const updateExpand = (expand: Expand) => { + setExpand(expand); saveChanges({ expand: expand.on }); } @@ -59,12 +59,12 @@ export const SqlEditor = (props: SqlEditorProps) => { if (on) { codeEditor.expanded = true; const height = getEditorHeight(codeEditor); - setExpand({ height: `${height}px`, on, icon }); + updateExpand({ height: `${height}px`, on, icon }); return; } codeEditor.expanded = false; - setExpand({ height: defaultHeight, icon, on }); + updateExpand({ height: defaultHeight, icon, on }); }; const schema: Schema = { @@ -105,7 +105,7 @@ export const SqlEditor = (props: SqlEditorProps) => { editor.onDidChangeModelDecorations((a: any) => { if (editor.expanded) { const height = getEditorHeight(editor); - setExpand({ height: `${height}px`, on: true, icon: 'minus' }); + updateExpand({ height: `${height}px`, on: true, icon: 'minus' }); } }); editor.onKeyUp((e: any) => { From fb7dffb53fc7b8e2e45fbe2bfebaae80ef64e823 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 29 Nov 2023 10:12:07 -0500 Subject: [PATCH 61/95] don't migrate empty queries --- src/components/SqlEditor.tsx | 2 +- src/data/migration.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/SqlEditor.tsx b/src/components/SqlEditor.tsx index 2bee3696..d8ebcf53 100644 --- a/src/components/SqlEditor.tsx +++ b/src/components/SqlEditor.tsx @@ -136,7 +136,7 @@ export const SqlEditor = (props: SqlEditorProps) => { aria-label="SQL" height={expand.height} language="sql" - value={query.rawSql || ''} + value={query.rawSql} onSave={sql => saveChanges({ rawSql: sql })} showMiniMap={false} showLineNumbers={true} diff --git a/src/data/migration.ts b/src/data/migration.ts index 02735e34..7a736f8f 100644 --- a/src/data/migration.ts +++ b/src/data/migration.ts @@ -11,6 +11,10 @@ export type AnyQueryBuilderOptions = Partial & {[k: string] * Returns undefined if no migration is applied. */ export const migrateCHQuery = (savedQuery: CHQuery): CHQuery | undefined => { + if (savedQuery.rawSql === undefined) { + return undefined; + } + if (isV3CHQuery(savedQuery)) { return migrateV3CHQuery(savedQuery); } From 3a82594df38daf08007bd4aaa4e02ca4cafe781d Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 29 Nov 2023 10:17:46 -0500 Subject: [PATCH 62/95] update doc url to permalink --- src/types/sql.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/sql.ts b/src/types/sql.ts index 46a4c920..75106c90 100644 --- a/src/types/sql.ts +++ b/src/types/sql.ts @@ -17,7 +17,7 @@ export interface CHQueryBase extends DataQuery { /** * REQUIRED by backend for auto selecting preferredVisualisationType. * Only used in explore view. - * src: https://github.com/grafana/sqlds/blob/main/query.go#L36 + * src: https://github.com/grafana/sqlds/blob/dda2dc0a54b128961fc9f7885baabf555f3ddfdc/query.go#L36 */ format?: number; } From d95d0bb6cbe1de26bf1433e2cdc436adc52e52ba Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 29 Nov 2023 10:20:54 -0500 Subject: [PATCH 63/95] fix useTables useEffect cleanup function --- src/hooks/useTables.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useTables.ts b/src/hooks/useTables.ts index e3d184a4..cfdbd778 100644 --- a/src/hooks/useTables.ts +++ b/src/hooks/useTables.ts @@ -1,7 +1,7 @@ import { useState, useEffect, useRef } from 'react'; import { Datasource } from 'data/CHDatasource'; -export default (datasource: Datasource, database: string): string[] => { +export default (datasource: Datasource, database: string): readonly string[] => { const [tables, setTables] = useState([]); useEffect(() => { @@ -23,7 +23,7 @@ export default (datasource: Datasource, database: string): string[] => { }); return () => { - ignore = false; + ignore = true; }; }, [datasource, database]); From 8f1d50c08bf7856e985263b5ba131467931c37a4 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 29 Nov 2023 10:24:26 -0500 Subject: [PATCH 64/95] return readonly array on useDatabases hook --- src/hooks/useDatabases.test.ts | 4 ++-- src/hooks/useDatabases.ts | 2 +- src/hooks/useTables.test.ts | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hooks/useDatabases.test.ts b/src/hooks/useDatabases.test.ts index fc18c7d4..22c0168a 100644 --- a/src/hooks/useDatabases.test.ts +++ b/src/hooks/useDatabases.test.ts @@ -5,7 +5,7 @@ import useDatabases from './useDatabases'; describe('useDatabases', () => { it('should return empty array if invalid datasource is provided', async () => { - let result: { current: string[] }; + let result: { current: readonly string[] }; await act(async () => { const r = renderHook(() => useDatabases(undefined!)); result = r.result; @@ -18,7 +18,7 @@ describe('useDatabases', () => { const mockDs = {} as Datasource; mockDs.fetchDatabases = jest.fn(() => Promise.resolve(['a', 'b'])); - let result: { current: string[] }; + let result: { current: readonly string[] }; await act(async () => { const r = renderHook(() => useDatabases(mockDs)); result = r.result; diff --git a/src/hooks/useDatabases.ts b/src/hooks/useDatabases.ts index 2af17965..5d2af5c1 100644 --- a/src/hooks/useDatabases.ts +++ b/src/hooks/useDatabases.ts @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react'; import { Datasource } from 'data/CHDatasource'; -export default (datasource: Datasource): string[] => { +export default (datasource: Datasource): readonly string[] => { const [databases, setDatabases] = useState([]); useEffect(() => { diff --git a/src/hooks/useTables.test.ts b/src/hooks/useTables.test.ts index 51ea182e..f3fc4665 100644 --- a/src/hooks/useTables.test.ts +++ b/src/hooks/useTables.test.ts @@ -5,7 +5,7 @@ import useTables from './useTables'; describe('useTables', () => { it('should return empty array if invalid datasource is provided', async () => { - let result: { current: string[] }; + let result: { current: readonly string[] }; await act(async () => { const r = renderHook(() => useTables(undefined!, 'db')); result = r.result; @@ -18,7 +18,7 @@ describe('useTables', () => { const mockDs = {} as Datasource; mockDs.fetchTables = jest.fn((db: string) => Promise.resolve(['a', 'b'])); - let result: { current: string[] }; + let result: { current: readonly string[] }; await act(async () => { const r = renderHook(() => useTables(mockDs, '')); result = r.result; @@ -31,7 +31,7 @@ describe('useTables', () => { const mockDs = {} as Datasource; mockDs.fetchTables = jest.fn((db: string) => Promise.resolve(['a', 'b'])); - let result: { current: string[] }; + let result: { current: readonly string[] }; await act(async () => { const r = renderHook(() => useTables(mockDs, 'db')); result = r.result; From de26f5fd007cf42eae8142cc366a3f89bfef2abc Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Wed, 29 Nov 2023 10:25:53 -0500 Subject: [PATCH 65/95] move limit function in sqlGenerator --- src/data/sqlGenerator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index 2d79d55a..bb8d0a51 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -19,7 +19,6 @@ export const generateSql = (options: QueryBuilderOptions): string => { */ const generateTraceQuery = (options: QueryBuilderOptions): string => { const { database, table } = options; - const limit = getLimit(options.limit); const queryParts: string[] = []; @@ -98,6 +97,7 @@ const generateTraceQuery = (options: QueryBuilderOptions): string => { queryParts.push('ORDER BY startTime ASC'); } + const limit = getLimit(options.limit); if (limit !== '') { queryParts.push(limit); } @@ -114,7 +114,6 @@ const generateTraceQuery = (options: QueryBuilderOptions): string => { */ const generateLogsQuery = (options: QueryBuilderOptions): string => { const { database, table } = options; - const limit = getLimit(options.limit); const queryParts: string[] = []; @@ -162,6 +161,7 @@ const generateLogsQuery = (options: QueryBuilderOptions): string => { queryParts.push(getOrderBy(options.orderBy, false)); } + const limit = getLimit(options.limit); if (limit !== '') { queryParts.push(limit); } From 30b102c03ba718f33e974792082f79e696d5b617 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Fri, 1 Dec 2023 21:42:37 -0500 Subject: [PATCH 66/95] move query builder view hooks, add tests for hooks --- src/__mocks__/datasource.ts | 3 + .../queryBuilder/views/LogsQueryBuilder.tsx | 145 +----------- .../views/TimeSeriesQueryBuilder.tsx | 70 +----- .../queryBuilder/views/TraceQueryBuilder.tsx | 73 +----- .../views/logsQueryBuilderHooks.test.ts | 209 ++++++++++++++++++ .../views/logsQueryBuilderHooks.ts | 144 ++++++++++++ .../views/timeSeriesQueryBuilderHooks.test.ts | 104 +++++++++ .../views/timeSeriesQueryBuilderHooks.ts | 67 ++++++ .../views/traceQueryBuilderHooks.test.ts | 99 +++++++++ .../views/traceQueryBuilderHooks.ts | 76 +++++++ 10 files changed, 712 insertions(+), 278 deletions(-) create mode 100644 src/components/queryBuilder/views/logsQueryBuilderHooks.test.ts create mode 100644 src/components/queryBuilder/views/logsQueryBuilderHooks.ts create mode 100644 src/components/queryBuilder/views/timeSeriesQueryBuilderHooks.test.ts create mode 100644 src/components/queryBuilder/views/timeSeriesQueryBuilderHooks.ts create mode 100644 src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts create mode 100644 src/components/queryBuilder/views/traceQueryBuilderHooks.ts diff --git a/src/__mocks__/datasource.ts b/src/__mocks__/datasource.ts index ca50ee13..f0d48cb7 100644 --- a/src/__mocks__/datasource.ts +++ b/src/__mocks__/datasource.ts @@ -15,6 +15,7 @@ export const mockDatasource = new Datasource({ path: '', username: 'user', defaultDatabase: 'foo', + defaultTable: 'bar', protocol: Protocol.Native, }, readOnly: true, @@ -41,7 +42,9 @@ export const mockDatasource = new Datasource({ }, }, }); + mockDatasource.adHocFiltersStatus = 1; // most tests should skip checking the CH version. We will set ad hoc filters to enabled to avoid running the CH version check + export const mockQuery: CHQuery = { pluginVersion: '', rawSql: 'select * from foo', diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index 85fa078c..c191724e 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { Filter, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint, DateFilterWithoutValue, FilterOperator, OrderByDirection, TableColumn } from 'types/queryBuilder'; +import { Filter, OrderBy, QueryBuilderOptions, SelectedColumn, ColumnHint } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; import { OtelVersionSelect } from '../OtelVersionSelect'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; @@ -11,11 +11,11 @@ import { getColumnByHint } from 'data/sqlGenerator'; import { columnFilterDateTime, columnFilterString } from 'data/columnFilters'; import { Datasource } from 'data/CHDatasource'; import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; -import { versions as otelVersions } from 'otel'; import { Alert, VerticalGroup } from '@grafana/ui'; import useColumns from 'hooks/useColumns'; -import { BuilderOptionsReducerAction, setColumnByHint, setOptions, setOtelEnabled, setOtelVersion } from 'hooks/useBuilderOptionsState'; +import { BuilderOptionsReducerAction, setOptions, setOtelEnabled, setOtelVersion } from 'hooks/useBuilderOptionsState'; import useIsNewQuery from 'hooks/useIsNewQuery'; +import { useDefaultFilters, useDefaultTimeColumn, useLogDefaultsOnMount, useOtelColumns } from './logsQueryBuilderHooks'; interface LogsQueryBuilderProps { datasource: Datasource; @@ -171,140 +171,3 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => {
); } - -/** - * Loads the default configuration for new queries. (Only runs on new queries) - */ -const useLogDefaultsOnMount = (datasource: Datasource, isNewQuery: boolean, builderOptions: QueryBuilderOptions, builderOptionsDispatch: React.Dispatch) => { - const didSetDefaults = useRef(false); - useEffect(() => { - if (!isNewQuery || didSetDefaults.current) { - return; - } - - const defaultDb = datasource.getDefaultLogsDatabase() || datasource.getDefaultDatabase(); - const defaultTable = datasource.getDefaultLogsTable() || datasource.getDefaultTable(); - const otelVersion = datasource.getLogsOtelVersion(); - const defaultColumns = datasource.getDefaultLogsColumns(); - - const nextColumns: SelectedColumn[] = []; - for (let [hint, colName] of defaultColumns) { - nextColumns.push({ name: colName, hint }); - } - - builderOptionsDispatch(setOptions({ - database: defaultDb, - table: defaultTable || builderOptions.table, - columns: nextColumns, - meta: { - otelEnabled: Boolean(otelVersion), - otelVersion, - } - })); - didSetDefaults.current = true; - }, [builderOptions.columns, builderOptions.orderBy, builderOptions.table, builderOptionsDispatch, datasource, isNewQuery]); -}; - -/** - * Sets OTEL Logs columns automatically when OTEL is enabled - */ -const useOtelColumns = (otelEnabled: boolean, otelVersion: string, builderOptionsDispatch: React.Dispatch) => { - const didSetColumns = useRef(otelEnabled); - if (!otelEnabled) { - didSetColumns.current = false; - } - - useEffect(() => { - if (!otelEnabled || didSetColumns.current) { - return; - } - - const otelConfig = otelVersions.find(v => v.version === otelVersion); - const logColumnMap = otelConfig?.logColumnMap; - if (!otelConfig || !logColumnMap) { - return; - } - - const columns: SelectedColumn[] = []; - logColumnMap.forEach((name, hint) => { - columns.push({ name, hint }); - }); - - builderOptionsDispatch(setOptions({ columns })); - didSetColumns.current = true; - }, [otelEnabled, otelVersion, builderOptionsDispatch]); -}; - -// Finds and selects a default log time column, updates when table changes -const useDefaultTimeColumn = (datasource: Datasource, allColumns: readonly TableColumn[], table: string, timeColumn: SelectedColumn | undefined, otelEnabled: boolean, builderOptionsDispatch: React.Dispatch) => { - const hasDefaultColumnConfigured = useMemo(() => Boolean(datasource.getDefaultLogsTable()) && datasource.getDefaultLogsColumns().has(ColumnHint.Time), [datasource]); - const didSetDefaultTime = useRef(Boolean(timeColumn) || hasDefaultColumnConfigured); - const lastTable = useRef(table || ''); - if (table !== lastTable.current) { - didSetDefaultTime.current = false; - } - - if (Boolean(timeColumn) || otelEnabled) { - lastTable.current = table; - didSetDefaultTime.current = true; - } - - useEffect(() => { - if (didSetDefaultTime.current || allColumns.length === 0 || !table) { - return; - } - - const col = allColumns.filter(columnFilterDateTime)[0]; - if (!col) { - return; - } - - const timeColumn: SelectedColumn = { - name: col.name, - type: col.type, - hint: ColumnHint.Time - }; - - builderOptionsDispatch(setColumnByHint(timeColumn)); - lastTable.current = table; - didSetDefaultTime.current = true; - }, [datasource, allColumns, table, builderOptionsDispatch]); -}; - -// Apply default filters/orderBy on timeColumn change -const timeRangeFilterId = 'timeRange'; -const useDefaultFilters = (table: string, timeColumn: SelectedColumn | undefined, filters: Filter[], orderBy: OrderBy[], builderOptionsDispatch: React.Dispatch) => { - const lastTimeColumn = useRef(timeColumn?.name || ''); - const lastTable = useRef(table || ''); - if (!timeColumn || table !== lastTable.current) { - lastTimeColumn.current = ''; - } - - useEffect(() => { - if (!timeColumn || (timeColumn.name === lastTimeColumn.current) || !table) { - return; - } - - const nextFilters: Filter[] = filters.filter(f => f.id !== timeRangeFilterId); - const timeRangeFilter: DateFilterWithoutValue = { - type: 'datetime', - operator: FilterOperator.WithInGrafanaTimeRange, - filterType: 'custom', - key: timeColumn.name, - id: timeRangeFilterId, - condition: 'AND' - }; - nextFilters.unshift(timeRangeFilter); - - const nextOrderBy: OrderBy[] = orderBy.filter(o => !o.default); - const defaultOrderBy: OrderBy = { name: timeColumn?.name, dir: OrderByDirection.DESC, default: true }; - nextOrderBy.unshift(defaultOrderBy); - - lastTable.current = table; - lastTimeColumn.current = timeColumn.name; - builderOptionsDispatch(setOptions({ - filters: nextFilters, - orderBy: nextOrderBy - })); - }, [table, timeColumn, filters, orderBy, builderOptionsDispatch]); -}; diff --git a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx index f55a66c6..5dc69e18 100644 --- a/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TimeSeriesQueryBuilder.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useMemo, useRef } from 'react'; +import React, { useMemo } from 'react'; import { ColumnsEditor } from '../ColumnsEditor'; -import { AggregateColumn, BuilderMode, Filter, OrderBy, QueryBuilderOptions, ColumnHint, SelectedColumn, DateFilterWithoutValue, FilterOperator, TableColumn } from 'types/queryBuilder'; +import { AggregateColumn, BuilderMode, Filter, OrderBy, QueryBuilderOptions, ColumnHint, SelectedColumn } from 'types/queryBuilder'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; import { LimitEditor } from '../LimitEditor'; import { FiltersEditor } from '../FilterEditor'; @@ -14,7 +14,8 @@ import { columnFilterDateTime } from 'data/columnFilters'; import { Datasource } from 'data/CHDatasource'; import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; import useColumns from 'hooks/useColumns'; -import { BuilderOptionsReducerAction, setColumnByHint, setOptions } from 'hooks/useBuilderOptionsState'; +import { BuilderOptionsReducerAction, setOptions } from 'hooks/useBuilderOptionsState'; +import { useDefaultFilters, useDefaultTimeColumn } from './timeSeriesQueryBuilderHooks'; interface TimeSeriesQueryBuilderProps { datasource: Datasource; @@ -119,66 +120,3 @@ export const TimeSeriesQueryBuilder = (props: TimeSeriesQueryBuilderProps) => {
); } - -// Finds and selects a default log time column, updates when table changes -const useDefaultTimeColumn = (allColumns: readonly TableColumn[], table: string, timeColumn: SelectedColumn | undefined, builderOptionsDispatch: React.Dispatch) => { - const didSetDefaultTime = useRef(Boolean(timeColumn)); - const lastTable = useRef(table || ''); - if (table !== lastTable.current) { - didSetDefaultTime.current = false; - } - - useEffect(() => { - if (didSetDefaultTime.current || allColumns.length === 0 || !table) { - return; - } - - const col = allColumns.filter(columnFilterDateTime)[0]; - if (!col) { - return; - } - - const timeColumn: SelectedColumn = { - name: col.name, - type: col.type, - hint: ColumnHint.Time - }; - - builderOptionsDispatch(setColumnByHint(timeColumn)); - lastTable.current = table; - didSetDefaultTime.current = true; - }, [allColumns, table, builderOptionsDispatch]); -}; - -// Apply default filters/orderBy on timeColumn change -const timeRangeFilterId = 'timeRange'; -const useDefaultFilters = (table: string, timeColumn: SelectedColumn | undefined, filters: Filter[], builderOptionsDispatch: React.Dispatch) => { - const lastTimeColumn = useRef(timeColumn?.name || ''); - const lastTable = useRef(table || ''); - if (!timeColumn || table !== lastTable.current) { - lastTimeColumn.current = ''; - } - - useEffect(() => { - if (!timeColumn || (timeColumn.name === lastTimeColumn.current) || !table) { - return; - } - - const nextFilters: Filter[] = filters.filter(f => f.id !== timeRangeFilterId); - const timeRangeFilter: DateFilterWithoutValue = { - type: 'datetime', - operator: FilterOperator.WithInGrafanaTimeRange, - filterType: 'custom', - key: timeColumn.name, - id: timeRangeFilterId, - condition: 'AND' - }; - nextFilters.unshift(timeRangeFilter); - - lastTable.current = table; - lastTimeColumn.current = timeColumn.name; - builderOptionsDispatch(setOptions({ - filters: nextFilters - })); - }, [table, timeColumn, filters, builderOptionsDispatch]); -}; diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index 0308cff2..006c5aff 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { Filter, QueryBuilderOptions, SelectedColumn, ColumnHint, TimeUnit } from 'types/queryBuilder'; import { ColumnSelect } from '../ColumnSelect'; import { FiltersEditor } from '../FilterEditor'; @@ -13,7 +13,7 @@ import useColumns from 'hooks/useColumns'; import { BuilderOptionsReducerAction, setOptions, setOtelEnabled, setOtelVersion } from 'hooks/useBuilderOptionsState'; import useIsNewQuery from 'hooks/useIsNewQuery'; import { OtelVersionSelect } from '../OtelVersionSelect'; -import { versions as otelVersions } from 'otel'; +import { useOtelColumns, useTraceDefaultsOnMount } from './traceQueryBuilderHooks'; interface TraceQueryBuilderProps { datasource: Datasource; @@ -297,72 +297,3 @@ const TraceIdInput = (props: TraceIdInputProps) => { ) } - -/** - * Loads the default configuration for new queries. (Only runs on new queries) - */ -const useTraceDefaultsOnMount = (datasource: Datasource, isNewQuery: boolean, builderOptions: QueryBuilderOptions, builderOptionsDispatch: React.Dispatch) => { - const didSetDefaults = useRef(false); - useEffect(() => { - if (!isNewQuery || didSetDefaults.current) { - return; - } - - const defaultDb = datasource.getDefaultTraceDatabase() || datasource.getDefaultDatabase(); - const defaultTable = datasource.getDefaultTraceTable() || datasource.getDefaultTable(); - const defaultDurationUnit = datasource.getDefaultTraceDurationUnit(); - const otelVersion = datasource.getTraceOtelVersion(); - const defaultColumns = datasource.getDefaultTraceColumns(); - - const nextColumns: SelectedColumn[] = []; - for (let [hint, colName] of defaultColumns) { - nextColumns.push({ name: colName, hint }); - } - - builderOptionsDispatch(setOptions({ - database: defaultDb, - table: defaultTable || builderOptions.table, - columns: nextColumns, - meta: { - otelEnabled: Boolean(otelVersion), - otelVersion, - traceDurationUnit: defaultDurationUnit - } - })); - didSetDefaults.current = true; - }, [builderOptions.columns, builderOptions.orderBy, builderOptions.table, builderOptionsDispatch, datasource, isNewQuery]); -}; -/** - * Sets OTEL Trace columns automatically when OTEL is enabled - */ -const useOtelColumns = (otelEnabled: boolean, otelVersion: string, builderOptionsDispatch: React.Dispatch) => { - const didSetColumns = useRef(otelEnabled); - if (!otelEnabled) { - didSetColumns.current = false; - } - - useEffect(() => { - if (!otelEnabled || didSetColumns.current) { - return; - } - - const otelConfig = otelVersions.find(v => v.version === otelVersion); - const traceColumnMap = otelConfig?.traceColumnMap; - if (!otelConfig || !traceColumnMap) { - return; - } - - const columns: SelectedColumn[] = []; - traceColumnMap.forEach((name, hint) => { - columns.push({ name, hint }); - }); - - builderOptionsDispatch(setOptions({ - columns, - meta: { - traceDurationUnit: otelConfig.traceDurationUnit - } - })); - didSetColumns.current = true; - }, [otelEnabled, otelVersion, builderOptionsDispatch]); -}; diff --git a/src/components/queryBuilder/views/logsQueryBuilderHooks.test.ts b/src/components/queryBuilder/views/logsQueryBuilderHooks.test.ts new file mode 100644 index 00000000..7f94ada6 --- /dev/null +++ b/src/components/queryBuilder/views/logsQueryBuilderHooks.test.ts @@ -0,0 +1,209 @@ +import { renderHook } from '@testing-library/react'; +import { useDefaultFilters, useDefaultTimeColumn, useLogDefaultsOnMount, useOtelColumns } from './logsQueryBuilderHooks'; +import { mockDatasource } from '__mocks__/datasource'; +import { ColumnHint, Filter, OrderBy, QueryBuilderOptions, SelectedColumn, TableColumn } from 'types/queryBuilder'; +import { setColumnByHint, setOptions } from 'hooks/useBuilderOptionsState'; +import { versions as otelVersions } from 'otel'; + +describe('useLogDefaultsOnMount', () => { + it('should call builderOptionsDispatch with default log columms', async () => { + const builderOptionsDispatch = jest.fn(); + jest.spyOn(mockDatasource, 'getLogsOtelVersion').mockReturnValue(undefined); + jest.spyOn(mockDatasource, 'getDefaultLogsColumns').mockReturnValue(new Map([[ColumnHint.Time, 'timestamp']])); + + renderHook(() => useLogDefaultsOnMount(mockDatasource, true, {} as QueryBuilderOptions, builderOptionsDispatch)); + + const expectedOptions = { + database: expect.anything(), + table: expect.anything(), + columns: [{ name: 'timestamp', hint: ColumnHint.Time }], + meta: { + otelEnabled: expect.anything(), + otelVersion: undefined + } + }; + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions))); + }); + + it('should not call builderOptionsDispatch after defaults are set', async () => { + const builderOptions = {} as QueryBuilderOptions; + const builderOptionsDispatch = jest.fn(); + + const hook = renderHook(() => useLogDefaultsOnMount(mockDatasource, true, builderOptions, builderOptionsDispatch)); + hook.rerender(); + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + }); + + it('should not call builderOptionsDispatch for existing query', async () => { + const isNewQuery = false; // query already exists, is not new + const builderOptionsDispatch = jest.fn(); + renderHook(() => useLogDefaultsOnMount(mockDatasource, isNewQuery, {} as QueryBuilderOptions, builderOptionsDispatch)); + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(0); + }); +}); + +describe('useOtelColumns', () => { + const testOtelVersion = otelVersions[0]; // use latest version + + it('should not call builderOptionsDispatch if OTEL is already enabled', async () => { + const builderOptionsDispatch = jest.fn(); + renderHook(() => useOtelColumns(true, testOtelVersion.version, builderOptionsDispatch)); + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(0); + }); + + it('should not call builderOptionsDispatch if OTEL is disabled', async () => { + const builderOptionsDispatch = jest.fn(); + renderHook(() => useOtelColumns(true, testOtelVersion.version, builderOptionsDispatch)); + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(0); + }); + + it('should call builderOptionsDispatch with columns when OTEL is toggled on', async () => { + const builderOptionsDispatch = jest.fn(); + + let otelEnabled = false; + const hook = renderHook(enabled => useOtelColumns(enabled, testOtelVersion.version, builderOptionsDispatch), { initialProps: otelEnabled }); + otelEnabled = true; + hook.rerender(otelEnabled); + + const columns: SelectedColumn[] = []; + testOtelVersion.logColumnMap.forEach((v, k) => columns.push({ name: v, hint: k })); + const expectedOptions = { columns }; + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions))); + }); + + it('should not call builderOptionsDispatch after OTEL columns are set', async () => { + const builderOptionsDispatch = jest.fn(); + + let otelEnabled = false; // OTEL is off + const hook = renderHook(enabled => useOtelColumns(enabled, testOtelVersion.version, builderOptionsDispatch), { initialProps: otelEnabled }); + otelEnabled = true; + hook.rerender(otelEnabled); // OTEL is on, columns are set + hook.rerender(otelEnabled); // OTEL still on, should not set again + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + }); +}); + +describe('useDefaultTimeColumn', () => { + it('should call builderOptionsDispatch when there are no configured defaults', async () => { + const builderOptionsDispatch = jest.fn(); + const tableName = 'logs'; + const timeColumnName = 'time'; + jest.spyOn(mockDatasource, 'getDefaultLogsTable').mockReturnValue(undefined); + const allColumns: readonly TableColumn[] = [{ name: timeColumnName, type: 'DateTime', picklistValues: [] }]; + const timeColumn = undefined; + const otelEnabled = false; + + renderHook(() => useDefaultTimeColumn(mockDatasource, allColumns, tableName, timeColumn, otelEnabled, builderOptionsDispatch)); + + const expectedColumn: SelectedColumn = { name: timeColumnName, type: 'DateTime', hint: ColumnHint.Time }; + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setColumnByHint(expectedColumn))); + }); + + it('should not call builderOptionsDispatch when column is already set', async () => { + const builderOptionsDispatch = jest.fn(); + const tableName = 'logs'; + const timeColumnName = 'time'; + jest.spyOn(mockDatasource, 'getDefaultLogsTable').mockReturnValue(tableName); + jest.spyOn(mockDatasource, 'getDefaultLogsColumns').mockReturnValue(new Map([[ColumnHint.Time, timeColumnName]])); + const allColumns: readonly TableColumn[] = [{ name: timeColumnName, type: 'DateTime', picklistValues: [] }]; + const timeColumn: SelectedColumn = { name: timeColumnName, hint: ColumnHint.Time }; + const otelEnabled = false; + + renderHook(() => useDefaultTimeColumn(mockDatasource, allColumns, tableName, timeColumn, otelEnabled, builderOptionsDispatch)); + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(0); + }); + + it('should call builderOptionsDispatch when table changes', async () => { + const builderOptionsDispatch = jest.fn(); + const tableName = 'logs'; + const timeColumnName = 'time'; + jest.spyOn(mockDatasource, 'getDefaultLogsTable').mockReturnValue(tableName); + jest.spyOn(mockDatasource, 'getDefaultLogsColumns').mockReturnValue(new Map([[ColumnHint.Time, timeColumnName]])); + const allColumns: readonly TableColumn[] = [{ name: timeColumnName, type: 'DateTime', picklistValues: [] }]; + const timeColumn = undefined; + const otelEnabled = false; + + const hook = renderHook(table => + useDefaultTimeColumn( + mockDatasource, + allColumns, + table, + timeColumn, + otelEnabled, + builderOptionsDispatch + ), + { initialProps: tableName } + ); + hook.rerender('other_logs'); + + const expectedColumn: SelectedColumn = { name: timeColumnName, type: 'DateTime', hint: ColumnHint.Time }; + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setColumnByHint(expectedColumn))); + }); +}); + +describe('useDefaultFilters', () => { + it('should not call builderOptionsDispatch when column/table are present on initial load', async () => { + const builderOptionsDispatch = jest.fn(); + const tableName = 'logs'; + const timeColumn: SelectedColumn = { name: 'timestamp', hint: ColumnHint.Time }; + const filters: Filter[] = []; + const orderBy: OrderBy[] = []; + + renderHook(() => useDefaultFilters(tableName, timeColumn, filters, orderBy, builderOptionsDispatch)); + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(0); + }); + + it('should call builderOptionsDispatch when table changes', async () => { + const builderOptionsDispatch = jest.fn(); + const tableName = 'logs'; + const timeColumn: SelectedColumn = { name: 'timestamp', hint: ColumnHint.Time }; + const filters: Filter[] = []; + const orderBy: OrderBy[] = []; + + const hook = renderHook(table => + useDefaultFilters(table, timeColumn, filters, orderBy, builderOptionsDispatch), + { initialProps: tableName } + ); + hook.rerender('other_logs'); + + const expectedOptions = { + filters: [expect.anything()], + orderBy: [expect.anything()], + }; + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions))); + }); + + it('should call builderOptionsDispatch when time column changes', async () => { + const builderOptionsDispatch = jest.fn(); + const tableName = 'logs'; + const filters: Filter[] = []; + const orderBy: OrderBy[] = []; + + const hook = renderHook(timeColumn => + useDefaultFilters(tableName, timeColumn, filters, orderBy, builderOptionsDispatch), + { initialProps: { name: 'timestamp', hint: ColumnHint.Time } } + ); + hook.rerender({ name: 'other_timestamp', hint: ColumnHint.Time }); + + const expectedOptions = { + filters: [expect.anything()], + orderBy: [expect.anything()], + }; + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions))); + }); +}); diff --git a/src/components/queryBuilder/views/logsQueryBuilderHooks.ts b/src/components/queryBuilder/views/logsQueryBuilderHooks.ts new file mode 100644 index 00000000..f16f0816 --- /dev/null +++ b/src/components/queryBuilder/views/logsQueryBuilderHooks.ts @@ -0,0 +1,144 @@ +import { Datasource } from "data/CHDatasource"; +import { columnFilterDateTime } from "data/columnFilters"; +import { BuilderOptionsReducerAction, setColumnByHint, setOptions } from "hooks/useBuilderOptionsState"; +import { useEffect, useMemo, useRef } from "react"; +import { ColumnHint, DateFilterWithoutValue, Filter, FilterOperator, OrderBy, OrderByDirection, QueryBuilderOptions, SelectedColumn, TableColumn } from "types/queryBuilder"; +import { versions as otelVersions } from 'otel'; + +/** + * Loads the default configuration for new queries. (Only runs on new queries) + */ +export const useLogDefaultsOnMount = (datasource: Datasource, isNewQuery: boolean, builderOptions: QueryBuilderOptions, builderOptionsDispatch: React.Dispatch) => { + const didSetDefaults = useRef(false); + useEffect(() => { + if (!isNewQuery || didSetDefaults.current) { + return; + } + + const defaultDb = datasource.getDefaultLogsDatabase() || datasource.getDefaultDatabase(); + const defaultTable = datasource.getDefaultLogsTable() || datasource.getDefaultTable(); + const otelVersion = datasource.getLogsOtelVersion(); + const defaultColumns = datasource.getDefaultLogsColumns(); + + const nextColumns: SelectedColumn[] = []; + for (let [hint, colName] of defaultColumns) { + nextColumns.push({ name: colName, hint }); + } + + builderOptionsDispatch(setOptions({ + database: defaultDb, + table: defaultTable || builderOptions.table, + columns: nextColumns, + meta: { + otelEnabled: Boolean(otelVersion), + otelVersion, + } + })); + didSetDefaults.current = true; + }, [builderOptions.columns, builderOptions.orderBy, builderOptions.table, builderOptionsDispatch, datasource, isNewQuery]); +}; + +/** + * Sets OTEL Logs columns automatically when OTEL is enabled. + * Does not run if OTEL is already enabled, only when it's changed. + */ +export const useOtelColumns = (otelEnabled: boolean, otelVersion: string, builderOptionsDispatch: React.Dispatch) => { + const didSetColumns = useRef(otelEnabled); + if (!otelEnabled) { + didSetColumns.current = false; + } + + useEffect(() => { + if (!otelEnabled || didSetColumns.current) { + return; + } + + const otelConfig = otelVersions.find(v => v.version === otelVersion); + const logColumnMap = otelConfig?.logColumnMap; + if (!logColumnMap) { + return; + } + + const columns: SelectedColumn[] = []; + logColumnMap.forEach((name, hint) => { + columns.push({ name, hint }); + }); + + builderOptionsDispatch(setOptions({ columns })); + didSetColumns.current = true; + }, [otelEnabled, otelVersion, builderOptionsDispatch]); +}; + +// Finds and selects a default log time column, updates when table changes +export const useDefaultTimeColumn = (datasource: Datasource, allColumns: readonly TableColumn[], table: string, timeColumn: SelectedColumn | undefined, otelEnabled: boolean, builderOptionsDispatch: React.Dispatch) => { + const hasDefaultColumnConfigured = useMemo(() => Boolean(datasource.getDefaultLogsTable()) && datasource.getDefaultLogsColumns().has(ColumnHint.Time), [datasource]); + const didSetDefaultTime = useRef(Boolean(timeColumn) || hasDefaultColumnConfigured); + const lastTable = useRef(table || ''); + if (table !== lastTable.current) { + didSetDefaultTime.current = false; + } + + if (Boolean(timeColumn) || otelEnabled) { + lastTable.current = table; + didSetDefaultTime.current = true; + } + + useEffect(() => { + if (didSetDefaultTime.current || allColumns.length === 0 || !table) { + return; + } + + const col = allColumns.filter(columnFilterDateTime)[0]; + if (!col) { + return; + } + + const timeColumn: SelectedColumn = { + name: col.name, + type: col.type, + hint: ColumnHint.Time + }; + + builderOptionsDispatch(setColumnByHint(timeColumn)); + lastTable.current = table; + didSetDefaultTime.current = true; + }, [datasource, allColumns, table, builderOptionsDispatch]); +}; + +// Apply default filters/orderBy on timeColumn change +const timeRangeFilterId = 'timeRange'; +export const useDefaultFilters = (table: string, timeColumn: SelectedColumn | undefined, filters: Filter[], orderBy: OrderBy[], builderOptionsDispatch: React.Dispatch) => { + const lastTimeColumn = useRef(timeColumn?.name || ''); + const lastTable = useRef(table || ''); + if (!timeColumn || table !== lastTable.current) { + lastTimeColumn.current = ''; + } + + useEffect(() => { + if (!timeColumn || (timeColumn.name === lastTimeColumn.current) || !table) { + return; + } + + const nextFilters: Filter[] = filters.filter(f => f.id !== timeRangeFilterId); + const timeRangeFilter: DateFilterWithoutValue = { + type: 'datetime', + operator: FilterOperator.WithInGrafanaTimeRange, + filterType: 'custom', + key: timeColumn.name, + id: timeRangeFilterId, + condition: 'AND' + }; + nextFilters.unshift(timeRangeFilter); + + const nextOrderBy: OrderBy[] = orderBy.filter(o => !o.default); + const defaultOrderBy: OrderBy = { name: timeColumn?.name, dir: OrderByDirection.DESC, default: true }; + nextOrderBy.unshift(defaultOrderBy); + + lastTable.current = table; + lastTimeColumn.current = timeColumn.name; + builderOptionsDispatch(setOptions({ + filters: nextFilters, + orderBy: nextOrderBy + })); + }, [table, timeColumn, filters, orderBy, builderOptionsDispatch]); +}; diff --git a/src/components/queryBuilder/views/timeSeriesQueryBuilderHooks.test.ts b/src/components/queryBuilder/views/timeSeriesQueryBuilderHooks.test.ts new file mode 100644 index 00000000..3e281ff7 --- /dev/null +++ b/src/components/queryBuilder/views/timeSeriesQueryBuilderHooks.test.ts @@ -0,0 +1,104 @@ +import { renderHook } from '@testing-library/react'; +import { useDefaultFilters, useDefaultTimeColumn } from './timeSeriesQueryBuilderHooks'; +import { ColumnHint, Filter, SelectedColumn, TableColumn } from 'types/queryBuilder'; +import { setColumnByHint, setOptions } from 'hooks/useBuilderOptionsState'; + +describe('useDefaultTimeColumn', () => { + it('should call builderOptionsDispatch when time column is unset', async () => { + const builderOptionsDispatch = jest.fn(); + const tableName = 'timeseries'; + const timeColumnName = 'time'; + const allColumns: readonly TableColumn[] = [{ name: timeColumnName, type: 'DateTime', picklistValues: [] }]; + const timeColumn = undefined; + + renderHook(() => useDefaultTimeColumn(allColumns, tableName, timeColumn, builderOptionsDispatch)); + + const expectedColumn: SelectedColumn = { name: timeColumnName, type: 'DateTime', hint: ColumnHint.Time }; + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setColumnByHint(expectedColumn))); + }); + + it('should not call builderOptionsDispatch when time column is already set', async () => { + const builderOptionsDispatch = jest.fn(); + const tableName = 'timeseries'; + const timeColumnName = 'time'; + const allColumns: readonly TableColumn[] = [{ name: timeColumnName, type: 'DateTime', picklistValues: [] }]; + const timeColumn: SelectedColumn = { name: timeColumnName, hint: ColumnHint.Time }; + + renderHook(() => useDefaultTimeColumn(allColumns, tableName, timeColumn, builderOptionsDispatch)); + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(0); + }); + + it('should call builderOptionsDispatch when table changes', async () => { + const builderOptionsDispatch = jest.fn(); + const tableName = 'timeseries'; + const timeColumnName = 'time'; + const allColumns: readonly TableColumn[] = [{ name: timeColumnName, type: 'DateTime', picklistValues: [] }]; + const timeColumn = undefined; + + renderHook(table => + useDefaultTimeColumn( + allColumns, + table, + timeColumn, + builderOptionsDispatch + ), + { initialProps: tableName } + ); + + const expectedColumn: SelectedColumn = { name: timeColumnName, type: 'DateTime', hint: ColumnHint.Time }; + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setColumnByHint(expectedColumn))); + }); +}); + +describe('useDefaultFilters', () => { + it('should not call builderOptionsDispatch when column/table are present on initial load', async () => { + const builderOptionsDispatch = jest.fn(); + const tableName = 'timeseries'; + const timeColumn: SelectedColumn = { name: 'timestamp', hint: ColumnHint.Time }; + const filters: Filter[] = []; + + renderHook(() => useDefaultFilters(tableName, timeColumn, filters, builderOptionsDispatch)); + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(0); + }); + + it('should call builderOptionsDispatch when table changes', async () => { + const builderOptionsDispatch = jest.fn(); + const tableName = 'timeseries'; + const timeColumn: SelectedColumn = { name: 'timestamp', hint: ColumnHint.Time }; + const filters: Filter[] = []; + + const hook = renderHook(table => + useDefaultFilters(table, timeColumn, filters, builderOptionsDispatch), + { initialProps: tableName } + ); + hook.rerender('other_timeseries'); + + const expectedOptions = { + filters: [expect.anything()], + }; + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions))); + }); + + it('should call builderOptionsDispatch when time column changes', async () => { + const builderOptionsDispatch = jest.fn(); + const tableName = 'timeseries'; + const filters: Filter[] = []; + + const hook = renderHook(timeColumn => + useDefaultFilters(tableName, timeColumn, filters, builderOptionsDispatch), + { initialProps: { name: 'timestamp', hint: ColumnHint.Time } } + ); + hook.rerender({ name: 'other_timestamp', hint: ColumnHint.Time }); + + const expectedOptions = { + filters: [expect.anything()], + }; + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions))); + }); +}); \ No newline at end of file diff --git a/src/components/queryBuilder/views/timeSeriesQueryBuilderHooks.ts b/src/components/queryBuilder/views/timeSeriesQueryBuilderHooks.ts new file mode 100644 index 00000000..4302d3b0 --- /dev/null +++ b/src/components/queryBuilder/views/timeSeriesQueryBuilderHooks.ts @@ -0,0 +1,67 @@ +import { columnFilterDateTime } from 'data/columnFilters'; +import { BuilderOptionsReducerAction, setColumnByHint, setOptions } from 'hooks/useBuilderOptionsState'; +import React, { useEffect, useRef } from 'react'; +import { ColumnHint, DateFilterWithoutValue, Filter, FilterOperator, SelectedColumn, TableColumn } from 'types/queryBuilder'; + +// Finds and selects a default log time column, updates when table changes +export const useDefaultTimeColumn = (allColumns: readonly TableColumn[], table: string, timeColumn: SelectedColumn | undefined, builderOptionsDispatch: React.Dispatch) => { + const didSetDefaultTime = useRef(Boolean(timeColumn)); + const lastTable = useRef(table || ''); + if (table !== lastTable.current) { + didSetDefaultTime.current = false; + } + + useEffect(() => { + if (didSetDefaultTime.current || allColumns.length === 0 || !table) { + return; + } + + const col = allColumns.filter(columnFilterDateTime)[0]; + if (!col) { + return; + } + + const timeColumn: SelectedColumn = { + name: col.name, + type: col.type, + hint: ColumnHint.Time + }; + + builderOptionsDispatch(setColumnByHint(timeColumn)); + lastTable.current = table; + didSetDefaultTime.current = true; + }, [allColumns, table, builderOptionsDispatch]); +}; + +// Apply default filters/orderBy on timeColumn change +const timeRangeFilterId = 'timeRange'; +export const useDefaultFilters = (table: string, timeColumn: SelectedColumn | undefined, filters: Filter[], builderOptionsDispatch: React.Dispatch) => { + const lastTimeColumn = useRef(timeColumn?.name || ''); + const lastTable = useRef(table || ''); + if (!timeColumn || table !== lastTable.current) { + lastTimeColumn.current = ''; + } + + useEffect(() => { + if (!timeColumn || (timeColumn.name === lastTimeColumn.current) || !table) { + return; + } + + const nextFilters: Filter[] = filters.filter(f => f.id !== timeRangeFilterId); + const timeRangeFilter: DateFilterWithoutValue = { + type: 'datetime', + operator: FilterOperator.WithInGrafanaTimeRange, + filterType: 'custom', + key: timeColumn.name, + id: timeRangeFilterId, + condition: 'AND' + }; + nextFilters.unshift(timeRangeFilter); + + lastTable.current = table; + lastTimeColumn.current = timeColumn.name; + builderOptionsDispatch(setOptions({ + filters: nextFilters + })); + }, [table, timeColumn, filters, builderOptionsDispatch]); +}; diff --git a/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts b/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts new file mode 100644 index 00000000..2af7b94e --- /dev/null +++ b/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts @@ -0,0 +1,99 @@ +import { renderHook } from '@testing-library/react'; +import { useTraceDefaultsOnMount, useOtelColumns } from './traceQueryBuilderHooks'; +import { mockDatasource } from '__mocks__/datasource'; +import { ColumnHint, QueryBuilderOptions, SelectedColumn } from 'types/queryBuilder'; +import { setOptions } from 'hooks/useBuilderOptionsState'; +import { versions as otelVersions } from 'otel'; + +describe('useTraceDefaultsOnMount', () => { + it('should call builderOptionsDispatch with default trace columms', async () => { + const builderOptionsDispatch = jest.fn(); + jest.spyOn(mockDatasource, 'getTraceOtelVersion').mockReturnValue(undefined); + jest.spyOn(mockDatasource, 'getDefaultTraceColumns').mockReturnValue(new Map([[ColumnHint.Time, 'timestamp']])); + + renderHook(() => useTraceDefaultsOnMount(mockDatasource, true, {} as QueryBuilderOptions, builderOptionsDispatch)); + + const expectedOptions = { + database: expect.anything(), + table: expect.anything(), + columns: [{ name: 'timestamp', hint: ColumnHint.Time }], + meta: { + otelEnabled: expect.anything(), + otelVersion: undefined, + traceDurationUnit: expect.anything() + } + }; + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions))); + }); + + it('should not call builderOptionsDispatch after defaults are set', async () => { + const builderOptions = {} as QueryBuilderOptions; + const builderOptionsDispatch = jest.fn(); + + const hook = renderHook(() => useTraceDefaultsOnMount(mockDatasource, true, builderOptions, builderOptionsDispatch)); + hook.rerender(); + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + }); + + it('should not call builderOptionsDispatch for existing query', async () => { + const isNewQuery = false; // query already exists, is not new + const builderOptionsDispatch = jest.fn(); + renderHook(() => useTraceDefaultsOnMount(mockDatasource, isNewQuery, {} as QueryBuilderOptions, builderOptionsDispatch)); + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(0); + }); +}); + +describe('useOtelColumns', () => { + const testOtelVersion = otelVersions[0]; // use latest version + + it('should not call builderOptionsDispatch if OTEL is already enabled', async () => { + const builderOptionsDispatch = jest.fn(); + renderHook(() => useOtelColumns(true, testOtelVersion.version, builderOptionsDispatch)); + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(0); + }); + + it('should not call builderOptionsDispatch if OTEL is disabled', async () => { + const builderOptionsDispatch = jest.fn(); + renderHook(() => useOtelColumns(true, testOtelVersion.version, builderOptionsDispatch)); + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(0); + }); + + it('should call builderOptionsDispatch with columns when OTEL is toggled on', async () => { + const builderOptionsDispatch = jest.fn(); + + let otelEnabled = false; + const hook = renderHook(enabled => useOtelColumns(enabled, testOtelVersion.version, builderOptionsDispatch), { initialProps: otelEnabled }); + otelEnabled = true; + hook.rerender(otelEnabled); + + const columns: SelectedColumn[] = []; + testOtelVersion.traceColumnMap.forEach((v, k) => columns.push({ name: v, hint: k })); + const expectedOptions = { + columns, + meta: { + traceDurationUnit: expect.anything() + } + }; + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions))); + }); + + it('should not call builderOptionsDispatch after OTEL columns are set', async () => { + const builderOptionsDispatch = jest.fn(); + + let otelEnabled = false; // OTEL is off + const hook = renderHook(enabled => useOtelColumns(enabled, testOtelVersion.version, builderOptionsDispatch), { initialProps: otelEnabled }); + otelEnabled = true; + hook.rerender(otelEnabled); // OTEL is on, columns are set + hook.rerender(otelEnabled); // OTEL still on, should not set again + + expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/components/queryBuilder/views/traceQueryBuilderHooks.ts b/src/components/queryBuilder/views/traceQueryBuilderHooks.ts new file mode 100644 index 00000000..fdca6788 --- /dev/null +++ b/src/components/queryBuilder/views/traceQueryBuilderHooks.ts @@ -0,0 +1,76 @@ +import React, { useEffect, useRef } from 'react'; +import { Datasource } from 'data/CHDatasource'; +import { versions as otelVersions } from 'otel'; +import { QueryBuilderOptions, SelectedColumn } from 'types/queryBuilder'; +import { BuilderOptionsReducerAction, setOptions } from 'hooks/useBuilderOptionsState'; + +/** + * Loads the default configuration for new queries. (Only runs on new queries) + */ +export const useTraceDefaultsOnMount = (datasource: Datasource, isNewQuery: boolean, builderOptions: QueryBuilderOptions, builderOptionsDispatch: React.Dispatch) => { + const didSetDefaults = useRef(false); + useEffect(() => { + if (!isNewQuery || didSetDefaults.current) { + return; + } + + const defaultDb = datasource.getDefaultTraceDatabase() || datasource.getDefaultDatabase(); + const defaultTable = datasource.getDefaultTraceTable() || datasource.getDefaultTable(); + const defaultDurationUnit = datasource.getDefaultTraceDurationUnit(); + const otelVersion = datasource.getTraceOtelVersion(); + const defaultColumns = datasource.getDefaultTraceColumns(); + + const nextColumns: SelectedColumn[] = []; + for (let [hint, colName] of defaultColumns) { + nextColumns.push({ name: colName, hint }); + } + + builderOptionsDispatch(setOptions({ + database: defaultDb, + table: defaultTable || builderOptions.table, + columns: nextColumns, + meta: { + otelEnabled: Boolean(otelVersion), + otelVersion, + traceDurationUnit: defaultDurationUnit + } + })); + didSetDefaults.current = true; + }, [builderOptions.columns, builderOptions.orderBy, builderOptions.table, builderOptionsDispatch, datasource, isNewQuery]); +}; + +/** + * Sets OTEL Trace columns automatically when OTEL is enabled + * Does not run if OTEL is already enabled, only when it's changed. + */ +export const useOtelColumns = (otelEnabled: boolean, otelVersion: string, builderOptionsDispatch: React.Dispatch) => { + const didSetColumns = useRef(otelEnabled); + if (!otelEnabled) { + didSetColumns.current = false; + } + + useEffect(() => { + if (!otelEnabled || didSetColumns.current) { + return; + } + + const otelConfig = otelVersions.find(v => v.version === otelVersion); + const traceColumnMap = otelConfig?.traceColumnMap; + if (!traceColumnMap) { + return; + } + + const columns: SelectedColumn[] = []; + traceColumnMap.forEach((name, hint) => { + columns.push({ name, hint }); + }); + + builderOptionsDispatch(setOptions({ + columns, + meta: { + traceDurationUnit: otelConfig.traceDurationUnit + } + })); + didSetColumns.current = true; + }, [otelEnabled, otelVersion, builderOptionsDispatch]); +}; From 1e0de7df0a7824422d9938d14f7dcd3303d93f5c Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Sun, 3 Dec 2023 21:11:51 -0500 Subject: [PATCH 67/95] remove wrapper hook --- src/data/migration.ts | 10 +++++----- src/hooks/useMigratedQuery.ts | 7 ------- src/views/CHQueryEditor.tsx | 4 ++-- 3 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 src/hooks/useMigratedQuery.ts diff --git a/src/data/migration.ts b/src/data/migration.ts index 7a736f8f..15bb5474 100644 --- a/src/data/migration.ts +++ b/src/data/migration.ts @@ -8,18 +8,18 @@ export type AnyQueryBuilderOptions = Partial & {[k: string] /** * Takes a CHQuery and transforms it to the latest interface. - * Returns undefined if no migration is applied. */ -export const migrateCHQuery = (savedQuery: CHQuery): CHQuery | undefined => { - if (savedQuery.rawSql === undefined) { - return undefined; +export const migrateCHQuery = (savedQuery: CHQuery): CHQuery => { + const isGrafanaDefaultQuery = savedQuery.rawSql === undefined; + if (isGrafanaDefaultQuery) { + return savedQuery; } if (isV3CHQuery(savedQuery)) { return migrateV3CHQuery(savedQuery); } - return undefined; + return savedQuery; }; /** diff --git a/src/hooks/useMigratedQuery.ts b/src/hooks/useMigratedQuery.ts deleted file mode 100644 index c941120c..00000000 --- a/src/hooks/useMigratedQuery.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { migrateCHQuery } from "data/migration"; -import { CHQuery } from "types/sql"; - -export default (currentQuery: CHQuery): CHQuery => { - const migratedQuery = migrateCHQuery(currentQuery); - return migratedQuery === undefined ? currentQuery : migratedQuery; -} diff --git a/src/views/CHQueryEditor.tsx b/src/views/CHQueryEditor.tsx index b45d0274..e811adfa 100644 --- a/src/views/CHQueryEditor.tsx +++ b/src/views/CHQueryEditor.tsx @@ -11,8 +11,8 @@ import { generateSql } from 'data/sqlGenerator'; import { SqlEditor } from 'components/SqlEditor'; import { isBuilderOptionsRunnable, mapQueryTypeToGrafanaFormat } from 'data/utils'; import { setAllOptions, useBuilderOptionsState } from 'hooks/useBuilderOptionsState'; -import useMigratedQuery from 'hooks/useMigratedQuery'; import { pluginVersion } from 'utils/version'; +import { migrateCHQuery } from 'data/migration'; export type CHQueryEditorProps = QueryEditorProps; @@ -21,7 +21,7 @@ export type CHQueryEditorProps = QueryEditorProps */ export const CHQueryEditor = (props: CHQueryEditorProps) => { const { query: savedQuery, onRunQuery } = props; - const query = useMigratedQuery(savedQuery); + const query = migrateCHQuery(savedQuery); return ( <> From 04c9bb9cab7f699b675cf7c0793311d4f9ebceb5 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 4 Dec 2023 01:49:53 -0500 Subject: [PATCH 68/95] fix query migration, added more tests --- src/data/migration.test.ts | 368 +++++++++++++++++++++++++++++++++++++ src/data/migration.ts | 49 ++--- 2 files changed, 395 insertions(+), 22 deletions(-) create mode 100644 src/data/migration.test.ts diff --git a/src/data/migration.test.ts b/src/data/migration.test.ts new file mode 100644 index 00000000..9a98cd86 --- /dev/null +++ b/src/data/migration.test.ts @@ -0,0 +1,368 @@ +import { CHBuilderQuery, CHQuery, CHSqlQuery, EditorType } from "types/sql"; +import { migrateCHQuery } from "./migration"; +import { pluginVersion } from "utils/version"; +import { AggregateType, BuilderMode, ColumnHint, Filter, FilterOperator, OrderByDirection, QueryBuilderOptions, QueryType } from "types/queryBuilder"; +import { mapQueryTypeToGrafanaFormat } from "./utils"; + +describe('Query Editor Version Migration', () => { + it('does not apply migration for empty query', () => { + const query = {} as CHQuery; + + const migratedQuery = migrateCHQuery(query); + expect(migratedQuery).not.toBeUndefined(); + expect(migratedQuery).toEqual(query); + }); + + it('does not apply migration for default grafana query', () => { + const defaultGrafanaQuery = { + datasource: 'test-ds', + refId: 'A' + } as unknown as CHQuery; + + const migratedQuery = migrateCHQuery(defaultGrafanaQuery); + expect(migratedQuery).not.toBeUndefined(); + expect(migratedQuery).toEqual(defaultGrafanaQuery); + }); + + it('does not apply migration to latest query schema', () => { + const latestQuery: CHBuilderQuery = { + pluginVersion, + editorType: EditorType.Builder, + builderOptions: { + database: 'default', + table: 'test', + queryType: QueryType.Table, + mode: BuilderMode.List, + columns: [ + { name: 'a', type: 'String' }, + { name: 'b', type: 'String' }, + ], + aggregates: [ + { aggregateType: AggregateType.Count, column: '*', alias: 'c' } + ], + filters: [ + { + type: 'String', + operator: FilterOperator.Equals, + filterType: 'custom', + key: 'b', + condition: 'AND', + value: 'test' + } + ], + groupBy: ['a'], + orderBy: [ + { name: 'a', dir: OrderByDirection.ASC } + ], + limit: 250, + meta: { + otelEnabled: false, + otelVersion: 'test' + } + }, + rawSql: 'sql', + refId: 'A' + }; + + const migratedQuery = migrateCHQuery(latestQuery); + expect(migratedQuery).toBe(latestQuery); + expect(migratedQuery).toEqual(latestQuery); + }); + + it('apply migration for v3 builder query', () => { + const v3Query = { + refId: 'A', + datasource: { + type: 'ch-ds', + uid: 'test-uid' + }, + key: 'test-key', + queryType: 'builder', + rawSql: 'SELECT 1', + builderOptions: { + mode: 'list', + fields: [ + 'created_at', + 'level', + 'event' + ], + limit: 50, + database: 'default', + table: 'logs', + filters: [ + { + operator: 'WITH IN DASHBOARD TIME RANGE', + filterType: 'custom', + key: 'created_at', + type: 'datetime', + condition: 'AND', + restrictToFields: [ + { + name: 'created_at', + type: 'DateTime', + label: 'created_at', + picklistValues: [] + } + ] + }, + { + filterType: 'custom', + key: 'event', + type: 'String', + condition: 'AND', + operator: 'IS NOT NULL' + } + ], + metrics: [ + { field: 'level', aggregation: 'count', alias: 'c' } + ], + groupBy: ['c'], + orderBy: [ + { name: 'created_at', dir: 'DESC' } + ] + }, + format: 1, + selectedFormat: 1, + meta: { + timezone: 'tz' + } + } as unknown as CHQuery; + + const latestQuery: CHBuilderQuery = { + pluginVersion, + editorType: EditorType.Builder, + refId: 'A', + datasource: { + type: 'ch-ds', + uid: 'test-uid' + }, + key: 'test-key', + builderOptions: { + database: 'default', + table: 'logs', + queryType: QueryType.Table, + mode: BuilderMode.List, + columns: [ + { name: 'created_at' }, + { name: 'level' }, + { name: 'event' }, + ], + filters: [ + { + operator: FilterOperator.WithInGrafanaTimeRange, + filterType: 'custom', + key: 'created_at', + type: 'datetime', + condition: 'AND', + restrictToFields: [ + { + name: 'created_at', + type: 'DateTime', + label: 'created_at', + picklistValues: [] + } + ] + } as Filter, + { + filterType: 'custom', + key: 'event', + type: 'String', + condition: 'AND', + operator: FilterOperator.IsNotNull + } + ], + aggregates: [ + { aggregateType: AggregateType.Count, column: 'level', alias: 'c' } + ], + groupBy: ['c'], + orderBy: [ + { name: 'created_at', dir: OrderByDirection.DESC } + ], + limit: 50, + }, + rawSql: 'SELECT 1', + format: mapQueryTypeToGrafanaFormat(QueryType.Table), + meta: { + timezone: 'tz' + } + }; + + const migratedQuery = migrateCHQuery(v3Query); + expect(migratedQuery).toEqual(latestQuery); + }); + + it('apply migration for v3 sql query', () => { + const v3Query = { + refId: 'A', + datasource: { + type: 'ch-ds', + uid: 'test-uid' + }, + key: 'test-key', + queryType: 'sql', + rawSql: 'SELECT 1', + meta: { + timezone: 'tz', + builderOptions: { + fields: [ + 'created_at', + 'level', + 'event' + ] + } + }, + format: 1, + selectedFormat: 1, + expand: true + } as unknown as CHQuery; + + const latestQuery: CHSqlQuery = { + pluginVersion, + editorType: EditorType.SQL, + refId: 'A', + datasource: { + type: 'ch-ds', + uid: 'test-uid' + }, + key: 'test-key', + rawSql: 'SELECT 1', + queryType: QueryType.Table, + format: mapQueryTypeToGrafanaFormat(QueryType.Table), + expand: true, + meta: { + timezone: 'tz', + builderOptions: { + database: '', + table: '', + queryType: QueryType.Table, + columns: [ + { name: 'created_at' }, + { name: 'level' }, + { name: 'event' }, + ] + } as QueryBuilderOptions + } + }; + + const migratedQuery = migrateCHQuery(v3Query); + expect(migratedQuery).toEqual(latestQuery); + }); + + it('apply migration for partial v3 query', () => { + const v3Query = { + queryType: 'builder', + builderOptions: { + mode: 'list', + }, + rawSql: '' + } as unknown as CHQuery; + + const latestQuery: CHBuilderQuery = { + pluginVersion, + editorType: EditorType.Builder, + builderOptions: { + database: '', + table: '', + queryType: QueryType.Table, + mode: BuilderMode.List, + columns: [] + }, + rawSql: '', + refId: '' + }; + + const migratedQuery = migrateCHQuery(v3Query); + expect(migratedQuery).toEqual(latestQuery); + }); + + it('v3 migration maps hinted columns', () => { + const v3Query = { + queryType: 'builder', + builderOptions: { + timeField: 'timestamp', + timeFieldType: 'DateTime', + logLevelField: 'level' + }, + rawSql: '' + } as unknown as CHQuery; + + const latestQuery: CHBuilderQuery = { + pluginVersion, + editorType: EditorType.Builder, + builderOptions: { + database: '', + table: '', + queryType: QueryType.TimeSeries, // TimeSeries because v3 timeField is present + columns: [ + { name: 'timestamp', type: 'DateTime', hint: ColumnHint.Time }, + { name: 'level', hint: ColumnHint.LogLevel } + ] + }, + format: undefined, + rawSql: '', + refId: '' + }; + + const migratedQuery = migrateCHQuery(v3Query); + expect(migratedQuery).toEqual(latestQuery); + }); + + it('v3 migration detects QueryType.TimeSeries', () => { + const v3Query = { + queryType: 'builder', + builderOptions: { + timeField: 'timestamp', + timeFieldType: 'DateTime', + }, + rawSql: '' + } as unknown as CHQuery; + + const latestQuery: CHBuilderQuery = { + pluginVersion, + editorType: EditorType.Builder, + builderOptions: { + database: '', + table: '', + queryType: QueryType.TimeSeries, + columns: [ + { name: 'timestamp', type: 'DateTime', hint: ColumnHint.Time }, + ] + }, + format: undefined, + rawSql: '', + refId: '' + }; + + const migratedQuery = migrateCHQuery(v3Query); + expect(migratedQuery).toEqual(latestQuery); + }); + + it('v3 migration detects QueryType.Logs', () => { + const v3Query = { + queryType: 'builder', + builderOptions: { + logLevelField: 'level', + }, + rawSql: '' + } as unknown as CHQuery; + + const latestQuery: CHBuilderQuery = { + pluginVersion, + editorType: EditorType.Builder, + builderOptions: { + database: '', + table: '', + queryType: QueryType.Logs, + columns: [ + { name: 'level', hint: ColumnHint.LogLevel } + ] + }, + format: undefined, + rawSql: '', + refId: '' + }; + + const migratedQuery = migrateCHQuery(v3Query); + expect(migratedQuery).toEqual(latestQuery); + }); +}); diff --git a/src/data/migration.ts b/src/data/migration.ts index 15bb5474..ac497a7b 100644 --- a/src/data/migration.ts +++ b/src/data/migration.ts @@ -36,11 +36,12 @@ const migrateV3CHQuery = (savedQuery: AnyCHQuery): CHQuery => { rawSql: savedQuery.rawSql || '', refId: savedQuery.refId || '', format: savedQuery.format, - meta: {} }; if (savedQuery?.meta?.timezone) { - builderQuery.meta!.timezone = savedQuery.meta.timezone; + builderQuery.meta = { + timezone: savedQuery.meta.timezone + }; } // delete unwanted properties from v3 @@ -73,6 +74,7 @@ const migrateV3CHQuery = (savedQuery: AnyCHQuery): CHQuery => { } if (meta.builderOptions) { + // When chaning from builder to raw editor, the builder options are saved and also require migration rawSqlQuery.meta!.builderOptions = migrateV3QueryBuilderOptions(meta.builderOptions); } } @@ -91,7 +93,8 @@ const migrateV3QueryBuilderOptions = (savedOptions: AnyQueryBuilderOptions): Que const mapped: QueryBuilderOptions = { database: savedOptions.database || '', table: savedOptions.table || '', - queryType: getV3QueryType(savedOptions) + queryType: getV3QueryType(savedOptions), + columns: [] }; if (savedOptions.mode) { @@ -100,26 +103,30 @@ const migrateV3QueryBuilderOptions = (savedOptions: AnyQueryBuilderOptions): Que if (savedOptions['fields'] || Array.isArray(savedOptions['fields'])) { const oldColumns: string[] = savedOptions['fields']; - const timeField: string = savedOptions['timeField']; - const timeFieldType: string = savedOptions['timeFieldType']; - const logLevelField: string = savedOptions['logLevelField']; + mapped.columns = oldColumns.map((name: string) => ({ name })); + } - mapped.columns = oldColumns.map((colName: string) => { - const result: SelectedColumn = { - name: colName, - }; - if (colName === timeField) { - result.hint = ColumnHint.Time; - if (timeFieldType) { - result.type = timeFieldType; - } - } else if (colName === logLevelField) { - result.hint = ColumnHint.LogLevel; - } + const timeField: string = savedOptions['timeField']; + const timeFieldType: string = savedOptions['timeFieldType']; + if (timeField) { + const timeColumn: SelectedColumn = { + name: timeField, + type: timeFieldType, + hint: ColumnHint.Time + }; - return result; - }); + mapped.columns!.push(timeColumn); + } + + const logLevelField: string = savedOptions['logLevelField']; + if (logLevelField) { + const logLevelColumn: SelectedColumn = { + name: logLevelField, + hint: ColumnHint.LogLevel + }; + + mapped.columns!.push(logLevelColumn); } if (savedOptions['metrics'] || Array.isArray(savedOptions['metrics'])) { @@ -133,8 +140,6 @@ const migrateV3QueryBuilderOptions = (savedOptions: AnyQueryBuilderOptions): Que if (savedOptions.filters || Array.isArray(savedOptions.filters)) { const oldFilters: Filter[] = savedOptions.filters; - const timeField: string = savedOptions['timeField']; - const logLevelField: string = savedOptions['logLevelField']; mapped.filters = oldFilters.map((filter: Filter) => { const result: Filter = { From ffa5d51cd1c1dfab843a2b5cb692e1c1ca5ad69c Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 4 Dec 2023 01:59:29 -0500 Subject: [PATCH 69/95] add sql generator tests, test actual SQL output --- src/data/sqlGenerator.test.ts | 54 +++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/src/data/sqlGenerator.test.ts b/src/data/sqlGenerator.test.ts index d47d84b4..c2cb9c9c 100644 --- a/src/data/sqlGenerator.test.ts +++ b/src/data/sqlGenerator.test.ts @@ -16,11 +16,12 @@ describe('SQL Generator', () => { filters: [], orderBy: [] }; + const expectedSql = ( + 'SELECT timestamp as timestamp, message as body, level as level FROM "default"."logs" LIMIT 1000' + ); const sql = generateSql(opts); - expect(sql).not.toBeUndefined(); - expect(sql).not.toHaveLength(0); - expect(sql.length).toBeGreaterThan(0); + expect(sql).toEqual(expectedSql); }); it('generates trace sql', () => { @@ -43,11 +44,16 @@ describe('SQL Generator', () => { filters: [], orderBy: [] }; + const expectedSql = ( + 'SELECT "TraceId" as traceID, "SpanId" as spanID, "ParentSpanId" as parentSpanID, "ServiceName" as serviceName, ' + + '"SpanName" as operationName, "Timestamp" as startTime, "Duration" as duration, ' + + 'arrayMap(key -> map(\'key\', key, \'value\',"SpanAttributes"[key]), mapKeys("SpanAttributes")) as tags, ' + + 'arrayMap(key -> map(\'key\', key, \'value\',"ResourceAttributes"[key]), mapKeys("ResourceAttributes")) as serviceTags ' + + 'FROM "otel"."otel_traces" ORDER BY startTime ASC LIMIT 1000' + ); const sql = generateSql(opts); - expect(sql).not.toBeUndefined(); - expect(sql).not.toHaveLength(0); - expect(sql.length).toBeGreaterThan(0); + expect(sql).toEqual(expectedSql); }); it('generates other sql', () => { @@ -63,11 +69,41 @@ describe('SQL Generator', () => { filters: [], orderBy: [] }; + const expectedSql = ( + 'SELECT "timestamp", "text" FROM "default"."data" LIMIT 1000' + ); const sql = generateSql(opts); - expect(sql).not.toBeUndefined(); - expect(sql).not.toHaveLength(0); - expect(sql.length).toBeGreaterThan(0); + expect(sql).toEqual(expectedSql); + }); + + it('excludes LIMIT when limit is 0', () => { + const opts: QueryBuilderOptions = { + database: 'default', + table: 'data', + queryType: QueryType.Table, + limit: 0 + }; + const expectedSql = ( + 'SELECT FROM "default"."data"' + ); + + const sql = generateSql(opts); + expect(sql).toEqual(expectedSql); + }); + + it('excludes LIMIT when limit is excluded', () => { + const opts: QueryBuilderOptions = { + database: 'default', + table: 'data', + queryType: QueryType.Table + }; + const expectedSql = ( + 'SELECT FROM "default"."data"' + ); + + const sql = generateSql(opts); + expect(sql).toEqual(expectedSql); }); }); From 2ddd8a06a85ce08a7acc8cb49ae5a97513d38cb3 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 4 Dec 2023 02:20:13 -0500 Subject: [PATCH 70/95] move placeholder text func to utils, update tests --- .../configEditor/LabeledInput.test.tsx | 6 +-- src/components/configEditor/LabeledInput.tsx | 2 +- .../configEditor/LogsConfig.test.tsx | 37 +++++++++---------- src/components/configEditor/LogsConfig.tsx | 4 ++ .../configEditor/TracesConfig.test.tsx | 21 +++++------ src/components/configEditor/TracesConfig.tsx | 10 +++++ src/data/utils.test.ts | 9 +++++ src/data/utils.ts | 7 ++++ 8 files changed, 62 insertions(+), 34 deletions(-) create mode 100644 src/data/utils.test.ts diff --git a/src/components/configEditor/LabeledInput.test.tsx b/src/components/configEditor/LabeledInput.test.tsx index b2a7b5a1..b9bb03a1 100644 --- a/src/components/configEditor/LabeledInput.test.tsx +++ b/src/components/configEditor/LabeledInput.test.tsx @@ -10,14 +10,14 @@ describe('LabeledInput', () => { it('should call onChange when input is changed', async () => { const onChange = jest.fn(); - const result = render(); + const result = render(); expect(result.container.firstChild).not.toBeNull(); const input = result.getByPlaceholderText('test'); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onChange).toBeCalledTimes(1); - expect(onChange).toBeCalledWith('changed'); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith('changed'); }); }); diff --git a/src/components/configEditor/LabeledInput.tsx b/src/components/configEditor/LabeledInput.tsx index eeaf471a..e700565f 100644 --- a/src/components/configEditor/LabeledInput.tsx +++ b/src/components/configEditor/LabeledInput.tsx @@ -23,7 +23,7 @@ export function LabeledInput(props: LabeledInputProps) { width={30} value={value} onChange={e => onChange(e.currentTarget.value)} - placeholder={placeholder || label.toLowerCase().replace(/ /g, '_')} + placeholder={placeholder} /> ) diff --git a/src/components/configEditor/LogsConfig.test.tsx b/src/components/configEditor/LogsConfig.test.tsx index b60ac7bb..fe4392da 100644 --- a/src/components/configEditor/LogsConfig.test.tsx +++ b/src/components/configEditor/LogsConfig.test.tsx @@ -2,8 +2,7 @@ import React from 'react'; import { render, fireEvent } from '@testing-library/react'; import { LogsConfig } from './LogsConfig'; import allLabels from 'labels'; - -const labelToPlaceholder = (l: string) => l.toLowerCase().replace(/ /g, '_'); +import { columnLabelToPlaceholder } from 'data/utils'; describe('LogsConfig', () => { it('should render', () => { @@ -42,8 +41,8 @@ describe('LogsConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onDefaultDatabaseChange).toBeCalledTimes(1); - expect(onDefaultDatabaseChange).toBeCalledWith('changed'); + expect(onDefaultDatabaseChange).toHaveBeenCalledTimes(1); + expect(onDefaultDatabaseChange).toHaveBeenCalledWith('changed'); }); it('should call onDefaultTable when changed', () => { @@ -66,8 +65,8 @@ describe('LogsConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onDefaultTableChange).toBeCalledTimes(1); - expect(onDefaultTableChange).toBeCalledWith('changed'); + expect(onDefaultTableChange).toHaveBeenCalledTimes(1); + expect(onDefaultTableChange).toHaveBeenCalledWith('changed'); }); it('should call onOtelEnabled when changed', () => { @@ -89,8 +88,8 @@ describe('LogsConfig', () => { const input = result.getByRole('checkbox'); expect(input).toBeInTheDocument(); fireEvent.click(input); - expect(onOtelEnabledChange).toBeCalledTimes(1); - expect(onOtelEnabledChange).toBeCalledWith(true); + expect(onOtelEnabledChange).toHaveBeenCalledTimes(1); + expect(onOtelEnabledChange).toHaveBeenCalledWith(true); }); it('should call onOtelVersionChange when changed', () => { @@ -113,8 +112,8 @@ describe('LogsConfig', () => { expect(select).toBeInTheDocument(); fireEvent.keyDown(select, { key: 'ArrowDown' }); fireEvent.keyDown(select, { key: 'Enter' }); - expect(onOtelVersionChange).toBeCalledTimes(2); // 2 from hook - expect(onOtelVersionChange).toBeCalledWith(expect.any(String)); + expect(onOtelVersionChange).toHaveBeenCalledTimes(2); // 2 from hook + expect(onOtelVersionChange).toHaveBeenCalledWith(expect.any(String)); }); it('should call onTimeColumnChange when changed', () => { @@ -133,12 +132,12 @@ describe('LogsConfig', () => { ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.LogsConfig.columns.time.label)); + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.LogsConfig.columns.time.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onTimeColumnChange).toBeCalledTimes(1); - expect(onTimeColumnChange).toBeCalledWith('changed'); + expect(onTimeColumnChange).toHaveBeenCalledTimes(1); + expect(onTimeColumnChange).toHaveBeenCalledWith('changed'); }); it('should call onLevelColumnChange when changed', () => { @@ -157,12 +156,12 @@ describe('LogsConfig', () => { ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.LogsConfig.columns.level.label)); + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.LogsConfig.columns.level.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onLevelColumnChange).toBeCalledTimes(1); - expect(onLevelColumnChange).toBeCalledWith('changed'); + expect(onLevelColumnChange).toHaveBeenCalledTimes(1); + expect(onLevelColumnChange).toHaveBeenCalledWith('changed'); }); it('should call onMessageColumnChange when changed', () => { @@ -181,11 +180,11 @@ describe('LogsConfig', () => { ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.LogsConfig.columns.message.label)); + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.LogsConfig.columns.message.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onMessageColumnChange).toBeCalledTimes(1); - expect(onMessageColumnChange).toBeCalledWith('changed'); + expect(onMessageColumnChange).toHaveBeenCalledTimes(1); + expect(onMessageColumnChange).toHaveBeenCalledWith('changed'); }); }); diff --git a/src/components/configEditor/LogsConfig.tsx b/src/components/configEditor/LogsConfig.tsx index 95cd9f7d..aa94f963 100644 --- a/src/components/configEditor/LogsConfig.tsx +++ b/src/components/configEditor/LogsConfig.tsx @@ -7,6 +7,7 @@ import { versions as otelVersions } from 'otel'; import { LabeledInput } from './LabeledInput'; import { CHLogsConfig } from 'types/config'; import allLabels from 'labels'; +import { columnLabelToPlaceholder } from 'data/utils'; interface LogsConfigProps { logsConfig?: CHLogsConfig; @@ -87,6 +88,7 @@ export const LogsConfig = (props: LogsConfigProps) => { { { l.toLowerCase().replace(/ /g, '_'); +import { columnLabelToPlaceholder } from 'data/utils'; describe('TracesConfig', () => { it('should render', () => { @@ -175,7 +174,7 @@ describe('TracesConfig', () => { ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.traceId.label)); + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.traceId.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); @@ -206,7 +205,7 @@ describe('TracesConfig', () => { ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.spanId.label)); + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.spanId.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); @@ -237,7 +236,7 @@ describe('TracesConfig', () => { ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.operationName.label)); + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.operationName.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); @@ -268,7 +267,7 @@ describe('TracesConfig', () => { ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.parentSpanId.label)); + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.parentSpanId.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); @@ -299,7 +298,7 @@ describe('TracesConfig', () => { ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.serviceName.label)); + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.serviceName.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); @@ -330,7 +329,7 @@ describe('TracesConfig', () => { ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.durationTime.label)); + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.durationTime.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); @@ -392,7 +391,7 @@ describe('TracesConfig', () => { ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.startTime.label)); + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.startTime.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); @@ -423,7 +422,7 @@ describe('TracesConfig', () => { ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.tags.label)); + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.tags.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); @@ -454,7 +453,7 @@ describe('TracesConfig', () => { ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(labelToPlaceholder(allLabels.components.Config.TracesConfig.columns.serviceTags.label)); + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.serviceTags.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); diff --git a/src/components/configEditor/TracesConfig.tsx b/src/components/configEditor/TracesConfig.tsx index 7019905a..17208214 100644 --- a/src/components/configEditor/TracesConfig.tsx +++ b/src/components/configEditor/TracesConfig.tsx @@ -9,6 +9,7 @@ import { LabeledInput } from './LabeledInput'; import { DurationUnitSelect } from 'components/queryBuilder/DurationUnitSelect'; import { CHTracesConfig } from 'types/config'; import allLabels from 'labels'; +import { columnLabelToPlaceholder } from 'data/utils'; interface TraceConfigProps { tracesConfig?: CHTracesConfig; @@ -106,6 +107,7 @@ export const TracesConfig = (props: TraceConfigProps) => { { { { { { { { { { + it('converts to lowercase and removes multiple spaces', () => { + const expected = 'expected_test_output'; + const actual = columnLabelToPlaceholder('Expected TEST output'); + expect(actual).toEqual(expected); + }); +}); diff --git a/src/data/utils.ts b/src/data/utils.ts index c7b14e78..d64f46ed 100644 --- a/src/data/utils.ts +++ b/src/data/utils.ts @@ -50,3 +50,10 @@ export const mapGrafanaFormatToQueryType = (f?: number): QueryType => { return QueryType.Table; } }; + + +/** + * Converts label into sql-style column name. + * Example: "Test Column" -> "test_column" + */ +export const columnLabelToPlaceholder = (label: string) => label.toLowerCase().replace(/ /g, '_'); From 1051c175268fec2b4b73defdf2eaddffc77caff1 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 4 Dec 2023 02:24:06 -0500 Subject: [PATCH 71/95] add test for isBuilderOptionsRunnable func --- src/data/utils.test.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/data/utils.test.ts b/src/data/utils.test.ts index 01f6de15..8838f4d8 100644 --- a/src/data/utils.test.ts +++ b/src/data/utils.test.ts @@ -1,4 +1,32 @@ -import { columnLabelToPlaceholder } from "./utils"; +import { QueryBuilderOptions, QueryType } from "types/queryBuilder"; +import { columnLabelToPlaceholder, isBuilderOptionsRunnable } from "./utils"; + +describe('isBuilderOptionsRunnable', () => { + it('should return false for empty builder options', () => { + const opts: QueryBuilderOptions = { + database: 'default', + table: 'test', + queryType: QueryType.Table + }; + + const runnable = isBuilderOptionsRunnable(opts); + expect(runnable).toBe(false); + }); + + it('should return true for valid builder options', () => { + const opts: QueryBuilderOptions = { + database: 'default', + table: 'test', + queryType: QueryType.Table, + columns: [ + { name: 'valid_column' } + ] + }; + + const runnable = isBuilderOptionsRunnable(opts); + expect(runnable).toBe(true); + }); +}); describe('columnLabelToPlaceholder', () => { it('converts to lowercase and removes multiple spaces', () => { From 14274991eba73a72560c86849ef246af9c06583b Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 4 Dec 2023 02:31:42 -0500 Subject: [PATCH 72/95] update config components with correct types --- .../DefaultDatabaseTableConfig.test.tsx | 8 +-- .../DefaultDatabaseTableConfig.tsx | 6 +- src/components/configEditor/LogsConfig.tsx | 14 ++--- .../configEditor/QuerySettingsConfig.tsx | 8 +-- .../configEditor/TracesConfig.test.tsx | 56 +++++++++---------- src/components/configEditor/TracesConfig.tsx | 28 +++++----- src/views/CHConfigEditor.tsx | 4 +- 7 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/components/configEditor/DefaultDatabaseTableConfig.test.tsx b/src/components/configEditor/DefaultDatabaseTableConfig.test.tsx index 8dce985f..72f932f3 100644 --- a/src/components/configEditor/DefaultDatabaseTableConfig.test.tsx +++ b/src/components/configEditor/DefaultDatabaseTableConfig.test.tsx @@ -18,8 +18,8 @@ describe('DefaultDatabaseTableConfig', () => { expect(databaseInput).toBeInTheDocument(); fireEvent.change(databaseInput, { target: { value: 'test' } }); fireEvent.blur(databaseInput); - expect(onDefaultDatabaseChange).toBeCalledTimes(1); - expect(onDefaultDatabaseChange).toBeCalledWith(expect.any(Object)); + expect(onDefaultDatabaseChange).toHaveBeenCalledTimes(1); + expect(onDefaultDatabaseChange).toHaveBeenCalledWith(expect.any(Object)); }); it('should call onDefaultTableChange when default table is changed', () => { @@ -31,7 +31,7 @@ describe('DefaultDatabaseTableConfig', () => { expect(tableInput).toBeInTheDocument(); fireEvent.change(tableInput, { target: { value: 'test' } }); fireEvent.blur(tableInput); - expect(onDefaultTableChange).toBeCalledTimes(1); - expect(onDefaultTableChange).toBeCalledWith(expect.any(Object)); + expect(onDefaultTableChange).toHaveBeenCalledTimes(1); + expect(onDefaultTableChange).toHaveBeenCalledWith(expect.any(Object)); }); }); diff --git a/src/components/configEditor/DefaultDatabaseTableConfig.tsx b/src/components/configEditor/DefaultDatabaseTableConfig.tsx index 6e11286b..2ac2221b 100644 --- a/src/components/configEditor/DefaultDatabaseTableConfig.tsx +++ b/src/components/configEditor/DefaultDatabaseTableConfig.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { SyntheticEvent } from 'react'; import { ConfigSection } from '@grafana/experimental'; import { Input, Field } from '@grafana/ui'; import allLabels from 'labels'; @@ -6,8 +6,8 @@ import allLabels from 'labels'; interface DefaultDatabaseTableConfigProps { defaultDatabase?: string; defaultTable?: string; - onDefaultDatabaseChange: (e: any) => void; - onDefaultTableChange: (e: any) => void; + onDefaultDatabaseChange: (e: SyntheticEvent) => void; + onDefaultTableChange: (e: SyntheticEvent) => void; } export const DefaultDatabaseTableConfig = (props: DefaultDatabaseTableConfigProps) => { diff --git a/src/components/configEditor/LogsConfig.tsx b/src/components/configEditor/LogsConfig.tsx index aa94f963..c23193d0 100644 --- a/src/components/configEditor/LogsConfig.tsx +++ b/src/components/configEditor/LogsConfig.tsx @@ -11,13 +11,13 @@ import { columnLabelToPlaceholder } from 'data/utils'; interface LogsConfigProps { logsConfig?: CHLogsConfig; - onDefaultDatabaseChange: (v: any) => void; - onDefaultTableChange: (v: any) => void; - onOtelEnabledChange: (v: any) => void; - onOtelVersionChange: (v: any) => void; - onTimeColumnChange: (v: any) => void; - onLevelColumnChange: (v: any) => void; - onMessageColumnChange: (v: any) => void; + onDefaultDatabaseChange: (v: string) => void; + onDefaultTableChange: (v: string) => void; + onOtelEnabledChange: (v: boolean) => void; + onOtelVersionChange: (v: string) => void; + onTimeColumnChange: (v: string) => void; + onLevelColumnChange: (v: string) => void; + onMessageColumnChange: (v: string) => void; } export const LogsConfig = (props: LogsConfigProps) => { diff --git a/src/components/configEditor/QuerySettingsConfig.tsx b/src/components/configEditor/QuerySettingsConfig.tsx index 2a13b8d8..1a096965 100644 --- a/src/components/configEditor/QuerySettingsConfig.tsx +++ b/src/components/configEditor/QuerySettingsConfig.tsx @@ -1,4 +1,4 @@ -import React, { } from 'react'; +import React, { FormEvent } from 'react'; import { Switch, Input, Field } from '@grafana/ui'; import { ConfigSection } from '@grafana/experimental'; import allLabels from 'labels'; @@ -7,9 +7,9 @@ interface QuerySettingsConfigProps { dialTimeout?: string; queryTimeout?: string; validateSql?: boolean; - onDialTimeoutChange: (e: any) => void; - onQueryTimeoutChange: (e: any) => void; - onValidateSqlChange: (e: any) => void; + onDialTimeoutChange: (e: FormEvent) => void; + onQueryTimeoutChange: (e: FormEvent) => void; + onValidateSqlChange: (e: FormEvent) => void; } export const QuerySettingsConfig = (props: QuerySettingsConfigProps) => { diff --git a/src/components/configEditor/TracesConfig.test.tsx b/src/components/configEditor/TracesConfig.test.tsx index ae638fda..2f84bf01 100644 --- a/src/components/configEditor/TracesConfig.test.tsx +++ b/src/components/configEditor/TracesConfig.test.tsx @@ -55,8 +55,8 @@ describe('TracesConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onDefaultDatabaseChange).toBeCalledTimes(1); - expect(onDefaultDatabaseChange).toBeCalledWith('changed'); + expect(onDefaultDatabaseChange).toHaveBeenCalledTimes(1); + expect(onDefaultDatabaseChange).toHaveBeenCalledWith('changed'); }); it('should call onDefaultTable when changed', () => { @@ -86,8 +86,8 @@ describe('TracesConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onDefaultTableChange).toBeCalledTimes(1); - expect(onDefaultTableChange).toBeCalledWith('changed'); + expect(onDefaultTableChange).toHaveBeenCalledTimes(1); + expect(onDefaultTableChange).toHaveBeenCalledWith('changed'); }); it('should call onOtelEnabled when changed', () => { @@ -116,8 +116,8 @@ describe('TracesConfig', () => { const input = result.getByRole('checkbox'); expect(input).toBeInTheDocument(); fireEvent.click(input); - expect(onOtelEnabledChange).toBeCalledTimes(1); - expect(onOtelEnabledChange).toBeCalledWith(true); + expect(onOtelEnabledChange).toHaveBeenCalledTimes(1); + expect(onOtelEnabledChange).toHaveBeenCalledWith(true); }); it('should call onOtelVersionChange when changed', () => { @@ -147,8 +147,8 @@ describe('TracesConfig', () => { expect(select).toBeInTheDocument(); fireEvent.keyDown(select, { key: 'ArrowDown' }); fireEvent.keyDown(select, { key: 'Enter' }); - expect(onOtelVersionChange).toBeCalledTimes(2); // 2 from hook - expect(onOtelVersionChange).toBeCalledWith(expect.any(String)); + expect(onOtelVersionChange).toHaveBeenCalledTimes(2); // 2 from hook + expect(onOtelVersionChange).toHaveBeenCalledWith(expect.any(String)); }); it('should call onTraceIdColumnChange when changed', () => { @@ -178,8 +178,8 @@ describe('TracesConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onTraceIdColumnChange).toBeCalledTimes(1); - expect(onTraceIdColumnChange).toBeCalledWith('changed'); + expect(onTraceIdColumnChange).toHaveBeenCalledTimes(1); + expect(onTraceIdColumnChange).toHaveBeenCalledWith('changed'); }); it('should call onSpanIdColumnChange when changed', () => { @@ -209,8 +209,8 @@ describe('TracesConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onSpanIdColumnChange).toBeCalledTimes(1); - expect(onSpanIdColumnChange).toBeCalledWith('changed'); + expect(onSpanIdColumnChange).toHaveBeenCalledTimes(1); + expect(onSpanIdColumnChange).toHaveBeenCalledWith('changed'); }); it('should call onOperationNameColumnChange when changed', () => { @@ -240,8 +240,8 @@ describe('TracesConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onOperationNameColumnChange).toBeCalledTimes(1); - expect(onOperationNameColumnChange).toBeCalledWith('changed'); + expect(onOperationNameColumnChange).toHaveBeenCalledTimes(1); + expect(onOperationNameColumnChange).toHaveBeenCalledWith('changed'); }); it('should call onParentSpanIdColumnChange when changed', () => { @@ -271,8 +271,8 @@ describe('TracesConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onParentSpanIdColumnChange).toBeCalledTimes(1); - expect(onParentSpanIdColumnChange).toBeCalledWith('changed'); + expect(onParentSpanIdColumnChange).toHaveBeenCalledTimes(1); + expect(onParentSpanIdColumnChange).toHaveBeenCalledWith('changed'); }); it('should call onServiceNameColumnChange when changed', () => { @@ -302,8 +302,8 @@ describe('TracesConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onServiceNameColumnChange).toBeCalledTimes(1); - expect(onServiceNameColumnChange).toBeCalledWith('changed'); + expect(onServiceNameColumnChange).toHaveBeenCalledTimes(1); + expect(onServiceNameColumnChange).toHaveBeenCalledWith('changed'); }); it('should call onDurationColumnChange when changed', () => { @@ -333,8 +333,8 @@ describe('TracesConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onDurationColumnChange).toBeCalledTimes(1); - expect(onDurationColumnChange).toBeCalledWith('changed'); + expect(onDurationColumnChange).toHaveBeenCalledTimes(1); + expect(onDurationColumnChange).toHaveBeenCalledWith('changed'); }); it('should call onDurationUnitChange when changed', () => { @@ -364,8 +364,8 @@ describe('TracesConfig', () => { expect(select).toBeInTheDocument(); fireEvent.keyDown(select, { key: 'ArrowDown' }); fireEvent.keyDown(select, { key: 'Enter' }); - expect(onDurationUnitChange).toBeCalledTimes(1); - expect(onDurationUnitChange).toBeCalledWith(expect.any(String)); + expect(onDurationUnitChange).toHaveBeenCalledTimes(1); + expect(onDurationUnitChange).toHaveBeenCalledWith(expect.any(String)); }); it('should call onStartTimeColumnChange when changed', () => { @@ -395,8 +395,8 @@ describe('TracesConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onStartTimeColumnChange).toBeCalledTimes(1); - expect(onStartTimeColumnChange).toBeCalledWith('changed'); + expect(onStartTimeColumnChange).toHaveBeenCalledTimes(1); + expect(onStartTimeColumnChange).toHaveBeenCalledWith('changed'); }); it('should call onTagsColumnChange when changed', () => { @@ -426,8 +426,8 @@ describe('TracesConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onTagsColumnChange).toBeCalledTimes(1); - expect(onTagsColumnChange).toBeCalledWith('changed'); + expect(onTagsColumnChange).toHaveBeenCalledTimes(1); + expect(onTagsColumnChange).toHaveBeenCalledWith('changed'); }); it('should call onServiceTagsColumnChange when changed', () => { @@ -457,7 +457,7 @@ describe('TracesConfig', () => { expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onServiceTagsColumnChange).toBeCalledTimes(1); - expect(onServiceTagsColumnChange).toBeCalledWith('changed'); + expect(onServiceTagsColumnChange).toHaveBeenCalledTimes(1); + expect(onServiceTagsColumnChange).toHaveBeenCalledWith('changed'); }); }); diff --git a/src/components/configEditor/TracesConfig.tsx b/src/components/configEditor/TracesConfig.tsx index 17208214..371c4d2f 100644 --- a/src/components/configEditor/TracesConfig.tsx +++ b/src/components/configEditor/TracesConfig.tsx @@ -13,20 +13,20 @@ import { columnLabelToPlaceholder } from 'data/utils'; interface TraceConfigProps { tracesConfig?: CHTracesConfig; - onDefaultDatabaseChange: (v: any) => void; - onDefaultTableChange: (v: any) => void; - onOtelEnabledChange: (v: any) => void; - onOtelVersionChange: (v: any) => void; - onTraceIdColumnChange: (v: any) => void; - onSpanIdColumnChange: (v: any) => void; - onOperationNameColumnChange: (v: any) => void; - onParentSpanIdColumnChange: (v: any) => void; - onServiceNameColumnChange: (v: any) => void; - onDurationColumnChange: (v: any) => void; - onDurationUnitChange: (v: any) => void; - onStartTimeColumnChange: (v: any) => void; - onTagsColumnChange: (v: any) => void; - onServiceTagsColumnChange: (v: any) => void; + onDefaultDatabaseChange: (v: string) => void; + onDefaultTableChange: (v: string) => void; + onOtelEnabledChange: (v: boolean) => void; + onOtelVersionChange: (v: string) => void; + onTraceIdColumnChange: (v: string) => void; + onSpanIdColumnChange: (v: string) => void; + onOperationNameColumnChange: (v: string) => void; + onParentSpanIdColumnChange: (v: string) => void; + onServiceNameColumnChange: (v: string) => void; + onDurationColumnChange: (v: string) => void; + onDurationUnitChange: (v: TimeUnit) => void; + onStartTimeColumnChange: (v: string) => void; + onTagsColumnChange: (v: string) => void; + onServiceTagsColumnChange: (v: string) => void; } export const TracesConfig = (props: TraceConfigProps) => { diff --git a/src/views/CHConfigEditor.tsx b/src/views/CHConfigEditor.tsx index dd72a71f..931bfecf 100644 --- a/src/views/CHConfigEditor.tsx +++ b/src/views/CHConfigEditor.tsx @@ -120,7 +120,7 @@ export const ConfigEditor: React.FC = (props) => { }, }); }; - const onLogsConfigChange = (key: keyof CHLogsConfig, value: string) => { + const onLogsConfigChange = (key: keyof CHLogsConfig, value: string | boolean) => { onOptionsChange({ ...options, jsonData: { @@ -132,7 +132,7 @@ export const ConfigEditor: React.FC = (props) => { } }); }; - const onTracesConfigChange = (key: keyof CHTracesConfig, value: string) => { + const onTracesConfigChange = (key: keyof CHTracesConfig, value: string | boolean) => { onOptionsChange({ ...options, jsonData: { From 1e218ab932683f4169f27bdb99045f4d11f68972 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 4 Dec 2023 02:45:44 -0500 Subject: [PATCH 73/95] remove type --- src/components/configEditor/LogsConfig.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/configEditor/LogsConfig.tsx b/src/components/configEditor/LogsConfig.tsx index c23193d0..6d3fa2dd 100644 --- a/src/components/configEditor/LogsConfig.tsx +++ b/src/components/configEditor/LogsConfig.tsx @@ -30,7 +30,7 @@ export const LogsConfig = (props: LogsConfigProps) => { defaultDatabase, defaultTable, otelEnabled, otelVersion, timeColumn, levelColumn, messageColumn - } = (props.logsConfig || {}) as CHLogsConfig; + } = (props.logsConfig || {}); const labels = allLabels.components.Config.LogsConfig; const otelConfig = otelVersions.find(v => v.version === otelVersion); From e6ee675ad0398a6c27e8b2da30414d6d14c855d5 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 4 Dec 2023 02:48:54 -0500 Subject: [PATCH 74/95] remove defaultToLatest prop on otel selector --- src/components/configEditor/LogsConfig.tsx | 1 - src/components/configEditor/TracesConfig.tsx | 1 - src/components/queryBuilder/OtelVersionSelect.tsx | 7 +++---- src/components/queryBuilder/views/LogsQueryBuilder.tsx | 1 - src/components/queryBuilder/views/TraceQueryBuilder.tsx | 1 - 5 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/components/configEditor/LogsConfig.tsx b/src/components/configEditor/LogsConfig.tsx index 6d3fa2dd..0cb7484e 100644 --- a/src/components/configEditor/LogsConfig.tsx +++ b/src/components/configEditor/LogsConfig.tsx @@ -82,7 +82,6 @@ export const LogsConfig = (props: LogsConfigProps) => { selectedVersion={otelVersion || ''} onEnabledChange={onOtelEnabledChange} onVersionChange={onOtelVersionChange} - defaultToLatest wide /> { selectedVersion={otelVersion || ''} onEnabledChange={onOtelEnabledChange} onVersionChange={onOtelVersionChange} - defaultToLatest wide /> void, selectedVersion: string, onVersionChange: (version: string) => void, - defaultToLatest?: boolean, wide?: boolean, } export const OtelVersionSelect = (props: OtelVersionSelectProps) => { - const { enabled, onEnabledChange, selectedVersion, onVersionChange, defaultToLatest, wide } = props; + const { enabled, onEnabledChange, selectedVersion, onVersionChange, wide } = props; const { label, tooltip } = selectors.components.OtelVersionSelect; const options: SelectableValue[] = allVersions.map(v => ({ label: `${v.version}${v.name ? (` (${v.name})`) : ''}`, @@ -22,10 +21,10 @@ export const OtelVersionSelect = (props: OtelVersionSelectProps) => { })); useEffect(() => { - if (defaultToLatest && selectedVersion === '') { + if (selectedVersion === '') { onVersionChange(allVersions[0].version); } - }, [defaultToLatest, selectedVersion, onVersionChange]); + }, [selectedVersion, onVersionChange]); const theme = useTheme(); const switchContainerStyle: React.CSSProperties = { diff --git a/src/components/queryBuilder/views/LogsQueryBuilder.tsx b/src/components/queryBuilder/views/LogsQueryBuilder.tsx index c191724e..a890507d 100644 --- a/src/components/queryBuilder/views/LogsQueryBuilder.tsx +++ b/src/components/queryBuilder/views/LogsQueryBuilder.tsx @@ -104,7 +104,6 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => { onEnabledChange={e => builderOptionsDispatch(setOtelEnabled(e))} selectedVersion={builderState.otelVersion} onVersionChange={v => builderOptionsDispatch(setOtelVersion(v))} - defaultToLatest /> { onEnabledChange={e => builderOptionsDispatch(setOtelEnabled(e))} selectedVersion={builderState.otelVersion} onVersionChange={v => builderOptionsDispatch(setOtelVersion(v))} - defaultToLatest wide />
From 8d588907c745151d12034cf29d342807895116b4 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 4 Dec 2023 03:15:00 -0500 Subject: [PATCH 75/95] Update E2E test query --- .gitignore | 1 + e2e/e2ek6.test.js | 70 ++++++++++++++++++++++++----------------------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index 456d18d8..11098e0e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ artifacts/ work/ ci/ e2e-results/ +test_summary.json # Editor .idea diff --git a/e2e/e2ek6.test.js b/e2e/e2ek6.test.js index 7a5fdca5..2834489a 100644 --- a/e2e/e2ek6.test.js +++ b/e2e/e2ek6.test.js @@ -134,43 +134,45 @@ export async function configurePanel(page) { let queryData = { "queries": [ - { - "datasource": { - "type": "grafana-clickhouse-datasource", - "uid": `${datasourceUID}` - }, - "refId": "A", - "queryType": "builder", - "rawSql": "SELECT \"schema_name\" FROM \"INFORMATION_SCHEMA\".\"SCHEMATA\" LIMIT 100", - "meta": { - "builderOptions": { - "mode": "list", - "fields": [], - "limit": 100 - }, - "timezone": "America/Denver" - }, - "format": 1, - "selectedFormat": 4, - "builderOptions": { - "mode": "list", - "fields": [ - "schema_name" - ], - "limit": 100, - "database": "INFORMATION_SCHEMA", - "table": "SCHEMATA", - "filters": [], - "orderBy": [] - }, - "datasourceId": 3568, - "intervalMs": 30000, - "maxDataPoints": 631 - } + { + "datasource": { + "type": "grafana-clickhouse-datasource", + "uid": `${datasourceUID}` + }, + "pluginVersion": "4.0.0", + "editorType": "builder", + "rawSql": "SELECT \"schema_name\" FROM \"information_schema\".\"schemata\" LIMIT 1000", + "builderOptions": { + "database": "information_schema", + "table": "schemata", + "queryType": "table", + "mode": "list", + "columns": [ + { + "name": "schema_name", + "type": "String", + "custom": false + } + ], + "meta": {}, + "limit": 1000, + "aggregates": [], + "groupBy": [], + "filters": [], + "orderBy": [] + }, + "format": 1, + "meta": { + "timezone": "America/New_York" + }, + "datasourceId": 1, + "intervalMs": 10000, + "maxDataPoints": 1920 + } ], "from": "1695121104422", "to": "1695142704422" - } + }; // ensures user is an admin to the org http.post(`http://admin:admin@${GRAFANA_HOST}:3000/api/user/using/1`, null); From 3ed0a25a4f18ca4e045cb6445f08c7c7f1340a9c Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 4 Dec 2023 03:17:42 -0500 Subject: [PATCH 76/95] fix newline lint --- .../queryBuilder/views/timeSeriesQueryBuilderHooks.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/queryBuilder/views/timeSeriesQueryBuilderHooks.test.ts b/src/components/queryBuilder/views/timeSeriesQueryBuilderHooks.test.ts index 3e281ff7..57d2a6be 100644 --- a/src/components/queryBuilder/views/timeSeriesQueryBuilderHooks.test.ts +++ b/src/components/queryBuilder/views/timeSeriesQueryBuilderHooks.test.ts @@ -101,4 +101,4 @@ describe('useDefaultFilters', () => { expect(builderOptionsDispatch).toHaveBeenCalledTimes(1); expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions))); }); -}); \ No newline at end of file +}); From 1e196e91864cfd6bbf5c5025bb54ebcba3519e09 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 4 Dec 2023 03:27:11 -0500 Subject: [PATCH 77/95] update plugin version (fixes version-dependent migration logic) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d9cd10cd..644e0ff6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clickhouse-datasource", - "version": "3.3.0", + "version": "4.0.0", "description": "Clickhouse Datasource", "engines": { "node": ">=16" From f8b22c1dca51dcb5b573f69e936bc1c5ac0dcc54 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Mon, 4 Dec 2023 23:49:00 -0500 Subject: [PATCH 78/95] column filter tests, fix complex date types --- src/data/columnFilters.test.ts | 71 ++++++++++++++++++++++++++++++++++ src/data/columnFilters.ts | 9 +++-- 2 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 src/data/columnFilters.test.ts diff --git a/src/data/columnFilters.test.ts b/src/data/columnFilters.test.ts new file mode 100644 index 00000000..76e138ca --- /dev/null +++ b/src/data/columnFilters.test.ts @@ -0,0 +1,71 @@ +import { SelectedColumn } from "types/queryBuilder"; +import { columnFilterDateTime, columnFilterOr, columnFilterString } from "./columnFilters"; + +describe('columnFilterDateTime', () => { + it.each<{ col: SelectedColumn, expected: boolean }>([ + { col: { name: 't', type: 'Date' }, expected: true }, + { col: { name: 't', type: 'DateTime' }, expected: true }, + { col: { name: 't', type: 'Nullable(DateTime)' }, expected: true }, + { col: { name: 't', type: 'DateTime64' }, expected: true }, + { col: { name: 't', type: 'DateTime64(9)' }, expected: true }, + { col: { name: 't', type: 'date' }, expected: true }, + { col: { name: 't', type: 'datEtIME' }, expected: true }, + + { col: { name: 't', type: 'String' }, expected: false }, + { col: { name: 't', type: 'Int64' }, expected: false }, + { col: { name: 't', type: 'Dat' }, expected: false }, + { col: { name: 't', type: 'DaTme' }, expected: false }, + { col: { name: 't', type: 'nullaBLE(DaTme)' }, expected: false }, + ])('returns $expected for case $# ("$col.type")', ({ col, expected }) => { + expect(columnFilterDateTime(col)).toBe(expected); + }); +}); + +describe('columnFilterString', () => { + it.each<{ col: SelectedColumn, expected: boolean }>([ + { col: { name: 't', type: 'String' }, expected: true }, + { col: { name: 't', type: 'LowCardinality(String)' }, expected: true }, + { col: { name: 't', type: 'LowCardinality(Nullable(String))' }, expected: true }, + { col: { name: 't', type: 'newFeature(nullable(string))' }, expected: true }, + { col: { name: 't', type: 'string' }, expected: true }, + + { col: { name: 't', type: 'Int64' }, expected: false }, + { col: { name: 't', type: 'strin' }, expected: false }, + { col: { name: 't', type: 'Date' }, expected: false }, + { col: { name: 't', type: 'DateTime' }, expected: false }, + ])('returns $expected for case $# ("$col.type")', ({ col, expected }) => { + expect(columnFilterString(col)).toBe(expected); + }); +}); + +describe('columnFilterOr', () => { + it('matches no filters using logical OR operator', () => { + const col: SelectedColumn = { name: 't', type: 'invalid' }; + expect( + columnFilterOr(col, + columnFilterString, + columnFilterDateTime, + ) + ).toBe(false); + }); + + it('compares multiple filters using logical OR operator, matching first', () => { + const col: SelectedColumn = { name: 't', type: 'String' }; + expect( + columnFilterOr(col, + columnFilterString, + columnFilterDateTime, + ) + ).toBe(true); + }); + + it('compares multiple filters using logical OR operator, matching last', () => { + const col: SelectedColumn = { name: 't', type: 'String' }; + expect( + columnFilterOr(col, + columnFilterDateTime, + columnFilterString, + ) + ).toBe(true); + }); +}); diff --git a/src/data/columnFilters.ts b/src/data/columnFilters.ts index 268b5571..3abf6d10 100644 --- a/src/data/columnFilters.ts +++ b/src/data/columnFilters.ts @@ -1,13 +1,14 @@ import { SelectedColumn } from "types/queryBuilder"; -export const columnFilterDateTime = (s: SelectedColumn): boolean => (s.type || '').toLowerCase().startsWith('date'); +export const columnFilterDateTime = (s: SelectedColumn): boolean => (s.type || '').toLowerCase().includes('date'); export const columnFilterString = (s: SelectedColumn): boolean => (s.type || '').toLowerCase().includes('string') || (s.type || '').toLowerCase().includes('enum'); export const columnFilterOr = (s: SelectedColumn, ...filterFuncs: ReadonlyArray<(s: SelectedColumn) => boolean>): boolean => { for (let filterFn of filterFuncs) { - if (!filterFn(s)) { - return false; + if (filterFn(s)) { + return true; } } - return true; + + return false; }; From 9757085d8088507be3fcf144827fc6c839160ce1 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 00:27:16 -0500 Subject: [PATCH 79/95] isNewQuery hook tests --- src/hooks/useIsNewQuery.test.ts | 50 +++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/hooks/useIsNewQuery.test.ts diff --git a/src/hooks/useIsNewQuery.test.ts b/src/hooks/useIsNewQuery.test.ts new file mode 100644 index 00000000..e5319b84 --- /dev/null +++ b/src/hooks/useIsNewQuery.test.ts @@ -0,0 +1,50 @@ +import { renderHook } from "@testing-library/react"; +import useIsNewQuery from "./useIsNewQuery"; +import { QueryBuilderOptions, QueryType } from "types/queryBuilder"; + +describe('useIsNewQuery', () => { + const newQueryOpts: QueryBuilderOptions = { + database: 'default', + table: 'test', + queryType: QueryType.Table + }; + + const existingQueryOpts: QueryBuilderOptions = { + database: 'default', + table: 'test', + queryType: QueryType.Table, + columns: [ + { name: 'valid_column' } + ] + }; + + it('should return true when new query is provided', async () => { + const hook = renderHook(() => useIsNewQuery(newQueryOpts)); + expect(hook.result.current).toBe(true); + }); + + it('should return false when existing query is provided', async () => { + const hook = renderHook(() => useIsNewQuery(existingQueryOpts)); + expect(hook.result.current).toBe(false); + }); + + it('should continue to return true when new query is updated', async () => { + const hook = renderHook(opts => useIsNewQuery(opts), { initialProps: newQueryOpts }); + const firstResult = hook.result.current; + hook.rerender(existingQueryOpts); + const secondResult = hook.result.current; + + expect(firstResult).toBe(true); + expect(secondResult).toBe(true); + }); + + it('should continue to return false when existing query is updated', async () => { + const hook = renderHook(opts => useIsNewQuery(opts), { initialProps: existingQueryOpts }); + const firstResult = hook.result.current; + hook.rerender(existingQueryOpts); + const secondResult = hook.result.current; + + expect(firstResult).toBe(false); + expect(secondResult).toBe(false); + }); +}); From e2c61da12d995aafe44881db6f33247367894cd7 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 00:31:19 -0500 Subject: [PATCH 80/95] simplify useBuilderOptionChanges hook, add test --- src/hooks/useBuilderOptionChanges.test.ts | 26 +++++++++++++++++++++++ src/hooks/useBuilderOptionChanges.ts | 22 ++++++------------- 2 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 src/hooks/useBuilderOptionChanges.test.ts diff --git a/src/hooks/useBuilderOptionChanges.test.ts b/src/hooks/useBuilderOptionChanges.test.ts new file mode 100644 index 00000000..5d3fa9ab --- /dev/null +++ b/src/hooks/useBuilderOptionChanges.test.ts @@ -0,0 +1,26 @@ +import { renderHook } from "@testing-library/react"; +import { useBuilderOptionChanges } from "./useBuilderOptionChanges"; + +interface TestData { + x: number; + y: number; +} + +describe('useBuilderOptionChanges', () => { + it('calls onChange with merged object', async () => { + const onChange = jest.fn(); + const prevState: TestData = { + x: 1, + y: 2 + }; + const hook = renderHook(() => useBuilderOptionChanges(onChange, prevState)); + const applyChanges = hook.result.current; + + + expect(applyChanges).not.toBeUndefined(); + applyChanges('y')(3); + + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith({ x: 1, y: 3 }); + }); +}); diff --git a/src/hooks/useBuilderOptionChanges.ts b/src/hooks/useBuilderOptionChanges.ts index d6d8e2b3..c39d5174 100644 --- a/src/hooks/useBuilderOptionChanges.ts +++ b/src/hooks/useBuilderOptionChanges.ts @@ -1,32 +1,24 @@ import React from 'react'; -type onOptionChangeFn = (key: keyof T | Object) => (nextValue: React.SetStateAction) => void; +type onOptionChangeFn = (key: keyof T) => (nextValue: React.SetStateAction) => void; /** * Returns a function that can apply changes with an object or a specific key in an object. When called * will run another function with the changes applied. * - * (Does not deep clone the object, for now) + * Does not deep clone the object. This is used for top level fields on the QueryBuilderOptions type. * * @param onChange a function that receives the updated state from the change function * @param prevState the current (previous) state object * @returns a function used to apply changes to individual fields */ export function useBuilderOptionChanges(onChange: (nextState: T) => void, prevState: T): onOptionChangeFn { - return (key: keyof T | Object) => + return (key: keyof T) => (nextValue: React.SetStateAction) => { - let nextState: T; - if (typeof key === 'object') { - nextState = { - ...prevState, - ...key, - }; - } else { - nextState = { - ...prevState, - [key]: nextValue - }; - } + const nextState: T = { + ...prevState, + [key]: nextValue + }; onChange(nextState); }; From 2033a76e5f561b444d9f261f0f3dda07673df6ec Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 01:43:08 -0500 Subject: [PATCH 81/95] add tests for builderOptions state reducer --- src/hooks/useBuilderOptionsState.test.ts | 154 +++++++++++++++++++++++ src/hooks/useBuilderOptionsState.ts | 5 + 2 files changed, 159 insertions(+) create mode 100644 src/hooks/useBuilderOptionsState.test.ts diff --git a/src/hooks/useBuilderOptionsState.test.ts b/src/hooks/useBuilderOptionsState.test.ts new file mode 100644 index 00000000..f9a4f7c2 --- /dev/null +++ b/src/hooks/useBuilderOptionsState.test.ts @@ -0,0 +1,154 @@ +import { ColumnHint, QueryType } from "types/queryBuilder"; +import { setAllOptions, setColumnByHint, setDatabase, setOptions, setOtelEnabled, setOtelVersion, setQueryType, setTable, testFuncs } from "./useBuilderOptionsState"; +const { reducer, buildInitialState } = testFuncs; + +describe('reducer', () => { + it('applies SetOptions action', async () => { + const prevState = buildInitialState(); + const action = setOptions({ + limit: 100, + // Include meta to verify deep merge + meta: { + otelEnabled: true + } + }); + + const nextState = reducer(prevState, action); + expect(nextState.limit).toEqual(100); + expect(nextState.meta?.otelEnabled).toEqual(true); + }); + it('applies SetAllOptions action', async () => { + const prevState = buildInitialState({ + limit: 100 + }); + const action = setAllOptions({ + database: 'default', + table: 'test', + queryType: QueryType.Table + }); + + const nextState = reducer(prevState, action); + // SetAllOptions will overwrite with defaults + expect(nextState.limit).not.toEqual(100); + }); + it('run SetQueryType action with no changes', async () => { + const prevState = buildInitialState({ + queryType: QueryType.TimeSeries + }); + const action = setQueryType(QueryType.TimeSeries); + + const nextState = reducer(prevState, action); + expect(nextState.queryType).toEqual(QueryType.TimeSeries); + }); + it('applies SetQueryType to reset settings but preserve db/table', async () => { + const prevState = buildInitialState({ + database: 'prev_db', + table: 'prev_table', + queryType: QueryType.Table, + groupBy: ['will', 'be', 'reset'] + }); + const action = setQueryType(QueryType.Logs); + + const nextState = reducer(prevState, action); + expect(nextState.database).toEqual('prev_db'); + expect(nextState.table).toEqual('prev_table'); + expect(nextState.queryType).toEqual(QueryType.Logs); + expect(nextState.groupBy).toBeFalsy(); + }); + it('applies SetDatabase to reset settings but preserve query type', async () => { + const prevState = buildInitialState({ + database: 'prev_db', + table: 'prev_table', + queryType: QueryType.Logs, + groupBy: ['will', 'be', 'reset'] + }); + const action = setDatabase('next_db'); + + const nextState = reducer(prevState, action); + expect(nextState.database).toEqual('next_db'); + expect(nextState.table).toEqual(''); + expect(nextState.queryType).toEqual(QueryType.Logs); + expect(nextState.groupBy).toBeFalsy(); + }); + it('applies SetTable to reset settings but preserve db/queryType', async () => { + const prevState = buildInitialState({ + database: 'prev_db', + table: 'prev_table', + queryType: QueryType.Logs, + groupBy: ['will', 'be', 'reset'] + }); + const action = setTable('next_table'); + + const nextState = reducer(prevState, action); + expect(nextState.database).toEqual('prev_db'); + expect(nextState.table).toEqual('next_table'); + expect(nextState.queryType).toEqual(QueryType.Logs); + expect(nextState.groupBy).toBeFalsy(); + }); + it('applies SetOtelEnabled action', async () => { + const prevState = buildInitialState({ + limit: 50 + }); + const action = setOtelEnabled(true); + + const nextState = reducer(prevState, action); + expect(nextState.limit).toEqual(50); + expect(nextState.meta?.otelEnabled).toEqual(true); + }); + it('applies SetOtelVersion action', async () => { + const prevState = buildInitialState({ + limit: 50 + }); + const action = setOtelVersion('0.0.1'); + + const nextState = reducer(prevState, action); + expect(nextState.limit).toEqual(50); + expect(nextState.meta?.otelVersion).toEqual('0.0.1'); + }); + it('applies SetColumnByHint action, overwrites existing column', async () => { + const prevState = buildInitialState({ + columns: [ + { name: 'prev_timestamp', hint: ColumnHint.Time }, + { name: 'a' }, + { name: 'b' }, + { name: 'c' }, + ] + }); + const action = setColumnByHint({ name: 'next_timestamp', hint: ColumnHint.Time }); + + const nextState = reducer(prevState, action); + expect(nextState.columns).toHaveLength(4); + expect(nextState.columns![0].name).toEqual('a'); + expect(nextState.columns![1].name).toEqual('b'); + expect(nextState.columns![2].name).toEqual('c'); + // Updated column is filtered and pushed to end of array + expect(nextState.columns![3].name).toEqual('next_timestamp'); + }); +}); + + +describe('buildInitialState', () => { + it('builds initial state using defaults', async () => { + const state = buildInitialState(); + expect(state).not.toBeUndefined(); + expect(state.database).toEqual(''); + expect(state.table).toEqual(''); + expect(state.queryType).toEqual(QueryType.Table); + }); + + it('builds initial state and merge saved state', async () => { + const state = buildInitialState({ + table: 'saved_table', + limit: 50, + meta: { + otelEnabled: true + } + }); + expect(state).not.toBeUndefined(); + expect(state.database).toEqual(''); + expect(state.table).toEqual('saved_table'); + expect(state.limit).toEqual(50); + expect(state.queryType).toEqual(QueryType.Table); + expect(state.meta?.otelEnabled).toEqual(true); + }); +}); \ No newline at end of file diff --git a/src/hooks/useBuilderOptionsState.ts b/src/hooks/useBuilderOptionsState.ts index c301e36f..a5ee4a0a 100644 --- a/src/hooks/useBuilderOptionsState.ts +++ b/src/hooks/useBuilderOptionsState.ts @@ -144,3 +144,8 @@ export const useBuilderOptionsState = (savedOptions: QueryBuilderOptions): [Quer const [state, dispatch] = useReducer(reducer, savedOptions, buildInitialState); return [state as QueryBuilderOptions, dispatch]; }; + +export const testFuncs = { + reducer, + buildInitialState +}; From 70e82180db1b5a24ca14a46c39d654f930a98f03 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 02:01:23 -0500 Subject: [PATCH 82/95] test for content in ColumnSelect --- src/components/queryBuilder/ColumnSelect.test.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/queryBuilder/ColumnSelect.test.tsx b/src/components/queryBuilder/ColumnSelect.test.tsx index 71133174..9b93c005 100644 --- a/src/components/queryBuilder/ColumnSelect.test.tsx +++ b/src/components/queryBuilder/ColumnSelect.test.tsx @@ -33,6 +33,7 @@ describe('ColumnSelect', () => { /> ); expect(result.container.firstChild).not.toBeNull(); + expect(result.getByText('foo')).not.toBeUndefined(); }); it('should call onColumnChange when a new column is selected', () => { @@ -56,7 +57,7 @@ describe('ColumnSelect', () => { expect(multiSelect).toBeInTheDocument(); fireEvent.keyDown(multiSelect, { key: 'ArrowDown' }); fireEvent.keyDown(multiSelect, { key: 'Enter' }); - expect(onColumnChange).toBeCalledTimes(1); - expect(onColumnChange).toBeCalledWith(expect.any(Object)); + expect(onColumnChange).toHaveBeenCalledTimes(1); + expect(onColumnChange).toHaveBeenCalledWith(expect.any(Object)); }); }); From 267fb32c72358c42b918485db756c14b877570a6 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 02:08:14 -0500 Subject: [PATCH 83/95] move EditorTypeSwitcher warning msg to labels file --- src/components/queryBuilder/EditorTypeSwitcher.tsx | 2 +- src/labels.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/queryBuilder/EditorTypeSwitcher.tsx b/src/components/queryBuilder/EditorTypeSwitcher.tsx index 0ef6cd8e..20046547 100644 --- a/src/components/queryBuilder/EditorTypeSwitcher.tsx +++ b/src/components/queryBuilder/EditorTypeSwitcher.tsx @@ -96,7 +96,7 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { /> Date: Tue, 5 Dec 2023 02:10:20 -0500 Subject: [PATCH 84/95] remove unreachable code --- src/components/queryBuilder/EditorTypeSwitcher.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/queryBuilder/EditorTypeSwitcher.tsx b/src/components/queryBuilder/EditorTypeSwitcher.tsx index 20046547..ff079bd8 100644 --- a/src/components/queryBuilder/EditorTypeSwitcher.tsx +++ b/src/components/queryBuilder/EditorTypeSwitcher.tsx @@ -47,8 +47,7 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { builderOptions = query.builderOptions; break; case EditorType.SQL: - builderOptions = - (getQueryOptionsFromSql(query.rawSql) as QueryBuilderOptions) || defaultCHBuilderQuery.builderOptions; + builderOptions = getQueryOptionsFromSql(query.rawSql) as QueryBuilderOptions; break; default: builderOptions = defaultCHBuilderQuery.builderOptions; From 2d91ededbb52d5930df15b2f3fb586ac4ad5d756 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 02:14:16 -0500 Subject: [PATCH 85/95] rename confirmation function parameter --- src/components/queryBuilder/EditorTypeSwitcher.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/queryBuilder/EditorTypeSwitcher.tsx b/src/components/queryBuilder/EditorTypeSwitcher.tsx index ff079bd8..abc14c19 100644 --- a/src/components/queryBuilder/EditorTypeSwitcher.tsx +++ b/src/components/queryBuilder/EditorTypeSwitcher.tsx @@ -30,9 +30,9 @@ export const EditorTypeSwitcher = (props: CHEditorTypeSwitcherProps) => { const [confirmModalState, setConfirmModalState] = useState(false); const [cannotConvertModalState, setCannotConvertModalState] = useState(false); const [errorMessage, setErrorMessage] = useState(''); - const onEditorTypeChange = (editorType: EditorType, confirm = false) => { + const onEditorTypeChange = (editorType: EditorType, confirmed = false) => { // TODO: component state has updated, but not local state. - if (query.editorType === EditorType.SQL && editorType === EditorType.Builder && !confirm) { + if (query.editorType === EditorType.SQL && editorType === EditorType.Builder && !confirmed) { const queryOptionsFromSql = getQueryOptionsFromSql(query.rawSql); if (isString(queryOptionsFromSql)) { setCannotConvertModalState(true); From 082449c76ea361884c06db18e842aa17de80a177 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 12:02:23 -0500 Subject: [PATCH 86/95] add comment for sqleditor format selector --- src/components/SqlEditor.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/SqlEditor.tsx b/src/components/SqlEditor.tsx index d8ebcf53..cc5509f5 100644 --- a/src/components/SqlEditor.tsx +++ b/src/components/SqlEditor.tsx @@ -119,6 +119,7 @@ export const SqlEditor = (props: SqlEditorProps) => { return ( <> + {/* Only show in explore view where panel can't be manually selected. Dashboard view lets you change the panel. */} { app === CoreApp.Explore &&
saveChanges({ queryType })} sqlEditor /> From 76bf7cb8fa040337f95e94dd627d197eb352a449 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 12:08:30 -0500 Subject: [PATCH 87/95] remove import abbreviation --- src/components/SqlEditor.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/SqlEditor.tsx b/src/components/SqlEditor.tsx index cc5509f5..18c85f41 100644 --- a/src/components/SqlEditor.tsx +++ b/src/components/SqlEditor.tsx @@ -6,7 +6,7 @@ import { registerSQL, Range, Fetcher } from './sqlProvider'; import { CHConfig } from 'types/config'; import { CHQuery, EditorType, CHSqlQuery } from 'types/sql'; import { styles } from 'styles'; -import { fetchSuggestions as sugg, Schema } from './suggestions'; +import { fetchSuggestions, Schema } from './suggestions'; import { selectors } from 'selectors'; import { validate } from 'data/validate'; import { mapQueryTypeToGrafanaFormat } from 'data/utils'; @@ -74,8 +74,8 @@ export const SqlEditor = (props: SqlEditorProps) => { defaultDatabase: datasource.getDefaultDatabase(), }; - const fetchSuggestions: Fetcher = async (text: string, range: Range) => { - const suggestions = await sugg(text, schema, range); + const getSuggestions: Fetcher = async (text: string, range: Range) => { + const suggestions = await fetchSuggestions(text, schema, range); return Promise.resolve({ suggestions }); }; @@ -100,7 +100,7 @@ export const SqlEditor = (props: SqlEditorProps) => { }; const handleMount = (editor: any) => { - const me = registerSQL('chSql', editor, fetchSuggestions); + const me = registerSQL('chSql', editor, getSuggestions); editor.expanded = (query as CHSqlQuery).expand; editor.onDidChangeModelDecorations((a: any) => { if (editor.expanded) { From dedeb299440cd78d879ddbd3cd4a2a7c82493092 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 12:09:19 -0500 Subject: [PATCH 88/95] rename aria label on sql editor input --- src/components/SqlEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SqlEditor.tsx b/src/components/SqlEditor.tsx index 18c85f41..85226bc7 100644 --- a/src/components/SqlEditor.tsx +++ b/src/components/SqlEditor.tsx @@ -134,7 +134,7 @@ export const SqlEditor = (props: SqlEditorProps) => { Date: Tue, 5 Dec 2023 13:38:59 -0500 Subject: [PATCH 89/95] remove extra spaces in generated sql, add tests --- src/data/sqlGenerator.test.ts | 67 ++++++++++++++++++++++++++++++++--- src/data/sqlGenerator.ts | 30 +++++++++++++--- 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/src/data/sqlGenerator.test.ts b/src/data/sqlGenerator.test.ts index c2cb9c9c..95793f1c 100644 --- a/src/data/sqlGenerator.test.ts +++ b/src/data/sqlGenerator.test.ts @@ -1,4 +1,4 @@ -import { AggregateType, ColumnHint, QueryBuilderOptions, QueryType } from 'types/queryBuilder'; +import { AggregateType, ColumnHint, FilterOperator, QueryBuilderOptions, QueryType } from 'types/queryBuilder'; import { generateSql, getColumnByHint, getColumnIndexByHint, getColumnsByHints, isAggregateQuery } from './sqlGenerator'; describe('SQL Generator', () => { @@ -13,11 +13,20 @@ describe('SQL Generator', () => { { name: 'message', type: 'String', hint: ColumnHint.LogMessage }, ], limit: 1000, - filters: [], + filters: [ + { + filterType: 'custom', + key: 'message', + type: 'String', + condition: 'AND', + operator: FilterOperator.IsNotNull + } + ], orderBy: [] }; const expectedSql = ( - 'SELECT timestamp as timestamp, message as body, level as level FROM "default"."logs" LIMIT 1000' + 'SELECT timestamp as timestamp, message as body, level as level ' + + 'FROM "default"."logs" WHERE ( message IS NOT NULL ) LIMIT 1000' ); const sql = generateSql(opts); @@ -41,7 +50,17 @@ describe('SQL Generator', () => { { name: 'ResourceAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceServiceTags }, ], limit: 1000, - filters: [], + filters: [ + { + filterType: 'custom', + key: '', // hint property is used instead of column name + type: 'String', + condition: 'AND', + hint: ColumnHint.TraceId, + operator: FilterOperator.Equals, + value: '1234' + } + ], orderBy: [] }; const expectedSql = ( @@ -49,7 +68,7 @@ describe('SQL Generator', () => { '"SpanName" as operationName, "Timestamp" as startTime, "Duration" as duration, ' + 'arrayMap(key -> map(\'key\', key, \'value\',"SpanAttributes"[key]), mapKeys("SpanAttributes")) as tags, ' + 'arrayMap(key -> map(\'key\', key, \'value\',"ResourceAttributes"[key]), mapKeys("ResourceAttributes")) as serviceTags ' + - 'FROM "otel"."otel_traces" ORDER BY startTime ASC LIMIT 1000' + 'FROM "otel"."otel_traces" WHERE ( TraceId = \'1234\' ) ORDER BY startTime ASC LIMIT 1000' ); const sql = generateSql(opts); @@ -77,6 +96,44 @@ describe('SQL Generator', () => { expect(sql).toEqual(expectedSql); }); + it('generates other sql with filters', () => { + const opts: QueryBuilderOptions = { + database: 'default', + table: 'data', + queryType: QueryType.Table, + columns: [ + { name: 'timestamp', type: 'DateTime' }, + { name: 'text', type: 'String' }, + ], + limit: 1000, + filters: [ + { + operator: FilterOperator.WithInGrafanaTimeRange, + filterType: 'custom', + key: 'created_at', + type: 'datetime', + condition: 'AND' + }, + { + filterType: 'custom', + key: 'event', + type: 'String', + condition: 'AND', + operator: FilterOperator.IsNotNull + } + ], + orderBy: [] + }; + const expectedSql = ( + 'SELECT "timestamp", "text" FROM "default"."data" ' + + 'WHERE ( created_at >= $__fromTime AND created_at <= $__toTime ) AND ( event IS NOT NULL ) ' + + 'LIMIT 1000' + ); + + const sql = generateSql(opts); + expect(sql).toEqual(expectedSql); + }); + it('excludes LIMIT when limit is 0', () => { const opts: QueryBuilderOptions = { database: 'default', diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index bb8d0a51..3e655f89 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -1,7 +1,6 @@ import { getSqlFromQueryBuilderOptions, getOrderBy } from 'components/queryBuilder/utils'; import { BooleanFilter, ColumnHint, DateFilterWithValue, FilterOperator, MultiFilter, NumberFilter, QueryBuilderOptions, QueryType, SelectedColumn, StringFilter, TimeUnit } from 'types/queryBuilder'; - export const generateSql = (options: QueryBuilderOptions): string => { if (options.queryType === QueryType.Traces) { return generateTraceQuery(options); @@ -102,7 +101,7 @@ const generateTraceQuery = (options: QueryBuilderOptions): string => { queryParts.push(limit); } - return queryParts.join(' '); + return concatQueryParts(queryParts); } /** @@ -166,7 +165,7 @@ const generateLogsQuery = (options: QueryBuilderOptions): string => { queryParts.push(limit); } - return queryParts.join(' '); + return concatQueryParts(queryParts); } export const isAggregateQuery = (builder: QueryBuilderOptions): boolean => (builder.aggregates?.length || 0) > 0; @@ -233,6 +232,27 @@ const getTraceDurationSelectSql = (columnIdentifier: string, timeUnit?: TimeUnit } } +/** + * Concats query parts with no empty spaces. + */ +const concatQueryParts = (parts: readonly string[]): string => { + let query = ''; + for (let i = 0; i < parts.length; i++) { + const p = parts[i]; + if (!p) { + continue; + } + + query += p; + + if (i !== parts.length - 1) { + query += ' ' + } + } + + return query; +} + const getLimit = (limit?: number | undefined): string => { limit = Math.max(0, limit || 0); if (limit > 0) { @@ -328,11 +348,11 @@ const getFilters = (options: QueryBuilderOptions): string => { } filterParts.push(')'); - const builtFilter = filterParts.join(' '); + const builtFilter = concatQueryParts(filterParts); builtFilters.push(builtFilter); } - return builtFilters.join(' '); + return concatQueryParts(builtFilters); }; const isBooleanType = (type: string): boolean => (type?.toLowerCase().startsWith('boolean')); From ec71bffe3e7d2ba0495aa383963804df3df647ac Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 14:23:15 -0500 Subject: [PATCH 90/95] Client side migration for v3 config name changes --- src/views/CHConfigEditor.tsx | 8 +++- src/views/CHConfigEditorHooks.test.ts | 64 +++++++++++++++++++++++++++ src/views/CHConfigEditorHooks.ts | 37 ++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/views/CHConfigEditorHooks.test.ts create mode 100644 src/views/CHConfigEditorHooks.ts diff --git a/src/views/CHConfigEditor.tsx b/src/views/CHConfigEditor.tsx index 931bfecf..83c2b53a 100644 --- a/src/views/CHConfigEditor.tsx +++ b/src/views/CHConfigEditor.tsx @@ -8,7 +8,7 @@ import { RadioButtonGroup, Switch, Input, SecretInput, Button, Field, Horizontal import { CertificationKey } from '../components/ui/CertificationKey'; import { Components } from 'selectors'; import { CHConfig, CHCustomSetting, CHSecureConfig, CHLogsConfig, Protocol, CHTracesConfig } from 'types/config'; -import { gte } from 'semver'; +import { gte as versionGte } from 'semver'; import { ConfigSection, ConfigSubSection, DataSourceDescription } from '@grafana/experimental'; import { config } from '@grafana/runtime'; import { Divider } from 'components/Divider'; @@ -17,6 +17,7 @@ import { DefaultDatabaseTableConfig } from 'components/configEditor/DefaultDatab import { QuerySettingsConfig } from 'components/configEditor/QuerySettingsConfig'; import { LogsConfig } from 'components/configEditor/LogsConfig'; import { TracesConfig } from 'components/configEditor/TracesConfig'; +import { useMigrateV3Config } from './CHConfigEditorHooks'; export interface ConfigEditorProps extends DataSourcePluginOptionsEditorProps {} @@ -32,6 +33,9 @@ export const ConfigEditor: React.FC = (props) => { { label: 'Native', value: Protocol.Native }, { label: 'HTTP', value: Protocol.Http }, ]; + + useMigrateV3Config(options, onOptionsChange); + const onPortChange = (port: string) => { onOptionsChange({ ...options, @@ -382,7 +386,7 @@ export const ConfigEditor: React.FC = (props) => { /> - {config.featureToggles['secureSocksDSProxyEnabled'] && gte(config.buildInfo.version, '10.0.0') && ( + {config.featureToggles['secureSocksDSProxyEnabled'] && versionGte(config.buildInfo.version, '10.0.0') && ( { + it('should not call onOptionsChange if no v3 fields are present', async () => { + const onOptionsChange = jest.fn(); + const options = { + jsonData: { + host: 'new', + dialTimeout: '8' + } + } as any as DataSourceSettings; + + renderHook(opts => useMigrateV3Config(opts, onOptionsChange), { initialProps: options }); + + expect(onOptionsChange).toHaveBeenCalledTimes(0); + }); + + it('should rename v3 fields to latest config names', async () => { + const onOptionsChange = jest.fn(); + const options = { + jsonData: { + server: 'address', + timeout: '8' + } + } as any as DataSourceSettings; + + renderHook(opts => useMigrateV3Config(opts, onOptionsChange), { initialProps: options }); + + const expectedOptions = { + jsonData: { + host: 'address', + dialTimeout: '8' + } + }; + expect(onOptionsChange).toHaveBeenCalledTimes(1); + expect(onOptionsChange).toHaveBeenCalledWith(expect.objectContaining(expectedOptions)); + }); + + it('should remove v3 fields without overwriting latest config names', async () => { + const onOptionsChange = jest.fn(); + const options = { + jsonData: { + server: 'old', + host: 'new', + timeout: '6', + dialTimeout: '8' + } + } as any as DataSourceSettings; + + renderHook(opts => useMigrateV3Config(opts, onOptionsChange), { initialProps: options }); + + const expectedOptions = { + jsonData: { + host: 'new', + dialTimeout: '8' + } + }; + expect(onOptionsChange).toHaveBeenCalledTimes(1); + expect(onOptionsChange).toHaveBeenCalledWith(expect.objectContaining(expectedOptions)); + }); +}); diff --git a/src/views/CHConfigEditorHooks.ts b/src/views/CHConfigEditorHooks.ts new file mode 100644 index 00000000..eea4908c --- /dev/null +++ b/src/views/CHConfigEditorHooks.ts @@ -0,0 +1,37 @@ +import { DataSourceSettings } from "@grafana/data"; +import { useEffect, useRef } from "react"; +import { CHConfig } from "types/config"; + +/** + * Migrates v3 config to latest config schema. + * Copies and removes old "server" to "host" field + * Copies and removes old "timeout" to "dialTimeout" field + */ +export const useMigrateV3Config = (options: DataSourceSettings, onOptionsChange: (opts: DataSourceSettings) => void) => { + const { jsonData } = options; + const v3ServerField = (jsonData as any)['server']; + const v3TimeoutField = (jsonData as any)['timeout']; + + const migrated = useRef(!v3ServerField && !v3TimeoutField); + useEffect(() => { + if (!migrated.current) { + const nextJsonOptions = { ...options.jsonData }; + + if (v3ServerField && !nextJsonOptions.host) { + nextJsonOptions.host = v3ServerField; + } + delete (nextJsonOptions as any)['server']; + + if (v3TimeoutField && !nextJsonOptions.dialTimeout) { + nextJsonOptions.dialTimeout = v3TimeoutField; + } + delete (nextJsonOptions as any)['timeout']; + + onOptionsChange({ + ...options, + jsonData: nextJsonOptions, + }); + migrated.current = true; + } + }, [v3ServerField, v3TimeoutField, options, onOptionsChange]); +}; From 676ddf5597bacd233475aed8d96c246e89ed8c32 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 14:34:23 -0500 Subject: [PATCH 91/95] fix lint --- src/hooks/useBuilderOptionsState.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useBuilderOptionsState.test.ts b/src/hooks/useBuilderOptionsState.test.ts index f9a4f7c2..34e8eb8a 100644 --- a/src/hooks/useBuilderOptionsState.test.ts +++ b/src/hooks/useBuilderOptionsState.test.ts @@ -151,4 +151,4 @@ describe('buildInitialState', () => { expect(state.queryType).toEqual(QueryType.Table); expect(state.meta?.otelEnabled).toEqual(true); }); -}); \ No newline at end of file +}); From 30b8a064aaba2701c324867f128da655780c437f Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 15:22:39 -0500 Subject: [PATCH 92/95] fix spellcheck errors --- src/components/queryBuilder/ModeSwitch.tsx | 2 +- src/components/queryBuilder/OrderByEditor.test.tsx | 14 +++++++------- .../queryBuilder/views/TraceQueryBuilder.tsx | 4 ++-- .../views/logsQueryBuilderHooks.test.ts | 2 +- .../views/traceQueryBuilderHooks.test.ts | 2 +- src/data/columnFilters.test.ts | 2 +- src/data/migration.ts | 2 +- src/data/sqlGenerator.ts | 2 +- src/types/sql.ts | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/components/queryBuilder/ModeSwitch.tsx b/src/components/queryBuilder/ModeSwitch.tsx index 05a93856..23d2c070 100644 --- a/src/components/queryBuilder/ModeSwitch.tsx +++ b/src/components/queryBuilder/ModeSwitch.tsx @@ -11,7 +11,7 @@ export interface ModeSwitchProps { }; /** - * Component for switching between modes. Boxes are labled unlike regular Switch. + * Component for switching between modes. Boxes are labeled unlike regular Switch. */ export const ModeSwitch = (props: ModeSwitchProps) => { const { labelA, labelB, value, onChange, label, tooltip } = props; diff --git a/src/components/queryBuilder/OrderByEditor.test.tsx b/src/components/queryBuilder/OrderByEditor.test.tsx index 8301e079..101dfb3b 100644 --- a/src/components/queryBuilder/OrderByEditor.test.tsx +++ b/src/components/queryBuilder/OrderByEditor.test.tsx @@ -121,7 +121,7 @@ describe('OrderByEditor', () => { }); describe('getOrderByOptions', () => { - const allColumms: readonly TableColumn[] = [ + const allColumns: readonly TableColumn[] = [ { name: 'field1', type: 'string', @@ -153,7 +153,7 @@ describe('getOrderByOptions', () => { queryType: QueryType.Table, columns: [{ name: 'field1' }, { name: 'field3' }], }, - allColumms + allColumns ) ).toStrictEqual([ { @@ -184,7 +184,7 @@ describe('getOrderByOptions', () => { columns: [{ name: 'field1' }], aggregates: [{ column: 'field2', aggregateType: AggregateType.Max }], }, - allColumms + allColumns ) ).toStrictEqual([ { @@ -206,7 +206,7 @@ describe('getOrderByOptions', () => { queryType: QueryType.Table, aggregates: [{ column: 'field1', aggregateType: AggregateType.Max, alias: 'a' }], }, - allColumms + allColumns ) ).toStrictEqual([ { @@ -228,7 +228,7 @@ describe('getOrderByOptions', () => { ], groupBy: ['field2'] }, - allColumms + allColumns ) ).toStrictEqual([ { @@ -256,7 +256,7 @@ describe('getOrderByOptions', () => { aggregates: [], groupBy: ['field3', 'field1'], }, - allColumms + allColumns ) ).toStrictEqual([ { @@ -292,7 +292,7 @@ describe('getOrderByOptions', () => { ], groupBy: ['field3', 'field1'], }, - allColumms + allColumns ) ).toStrictEqual([ { diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index 22221367..9372af07 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -44,8 +44,8 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { const allColumns = useColumns(datasource, builderOptions.database, builderOptions.table); const isNewQuery = useIsNewQuery(builderOptions); const [showConfigWarning, setConfigWarningOpen] = useState(datasource.getDefaultTraceColumns().size === 0 && builderOptions.columns?.length === 0); - const [isColumnsOpen, setColumnsOpen] = useState(showConfigWarning); // Toggle Columns collapsable section - const [isFiltersOpen, setFiltersOpen] = useState(true); // Toggle Filters collapsable section + const [isColumnsOpen, setColumnsOpen] = useState(showConfigWarning); // Toggle Columns collapse section + const [isFiltersOpen, setFiltersOpen] = useState(true); // Toggle Filters collapse section const labels = allLabels.components.TraceQueryBuilder; const builderState: TraceQueryBuilderState = useMemo(() => ({ isSearchMode: builderOptions.meta?.isTraceSearchMode || false, diff --git a/src/components/queryBuilder/views/logsQueryBuilderHooks.test.ts b/src/components/queryBuilder/views/logsQueryBuilderHooks.test.ts index 7f94ada6..f92b1d67 100644 --- a/src/components/queryBuilder/views/logsQueryBuilderHooks.test.ts +++ b/src/components/queryBuilder/views/logsQueryBuilderHooks.test.ts @@ -6,7 +6,7 @@ import { setColumnByHint, setOptions } from 'hooks/useBuilderOptionsState'; import { versions as otelVersions } from 'otel'; describe('useLogDefaultsOnMount', () => { - it('should call builderOptionsDispatch with default log columms', async () => { + it('should call builderOptionsDispatch with default log columns', async () => { const builderOptionsDispatch = jest.fn(); jest.spyOn(mockDatasource, 'getLogsOtelVersion').mockReturnValue(undefined); jest.spyOn(mockDatasource, 'getDefaultLogsColumns').mockReturnValue(new Map([[ColumnHint.Time, 'timestamp']])); diff --git a/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts b/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts index 2af7b94e..7bbb7038 100644 --- a/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts +++ b/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts @@ -6,7 +6,7 @@ import { setOptions } from 'hooks/useBuilderOptionsState'; import { versions as otelVersions } from 'otel'; describe('useTraceDefaultsOnMount', () => { - it('should call builderOptionsDispatch with default trace columms', async () => { + it('should call builderOptionsDispatch with default trace columns', async () => { const builderOptionsDispatch = jest.fn(); jest.spyOn(mockDatasource, 'getTraceOtelVersion').mockReturnValue(undefined); jest.spyOn(mockDatasource, 'getDefaultTraceColumns').mockReturnValue(new Map([[ColumnHint.Time, 'timestamp']])); diff --git a/src/data/columnFilters.test.ts b/src/data/columnFilters.test.ts index 76e138ca..dc196233 100644 --- a/src/data/columnFilters.test.ts +++ b/src/data/columnFilters.test.ts @@ -30,7 +30,7 @@ describe('columnFilterString', () => { { col: { name: 't', type: 'string' }, expected: true }, { col: { name: 't', type: 'Int64' }, expected: false }, - { col: { name: 't', type: 'strin' }, expected: false }, + { col: { name: 't', type: 'str' }, expected: false }, { col: { name: 't', type: 'Date' }, expected: false }, { col: { name: 't', type: 'DateTime' }, expected: false }, ])('returns $expected for case $# ("$col.type")', ({ col, expected }) => { diff --git a/src/data/migration.ts b/src/data/migration.ts index ac497a7b..4f98a318 100644 --- a/src/data/migration.ts +++ b/src/data/migration.ts @@ -74,7 +74,7 @@ const migrateV3CHQuery = (savedQuery: AnyCHQuery): CHQuery => { } if (meta.builderOptions) { - // When chaning from builder to raw editor, the builder options are saved and also require migration + // When changing from builder to raw editor, the builder options are saved and also require migration rawSqlQuery.meta!.builderOptions = migrateV3QueryBuilderOptions(meta.builderOptions); } } diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index 3e655f89..6518ff8e 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -233,7 +233,7 @@ const getTraceDurationSelectSql = (columnIdentifier: string, timeUnit?: TimeUnit } /** - * Concats query parts with no empty spaces. + * Concatenates query parts with no empty spaces. */ const concatQueryParts = (parts: readonly string[]): string => { let query = ''; diff --git a/src/types/sql.ts b/src/types/sql.ts index 75106c90..f10ae3da 100644 --- a/src/types/sql.ts +++ b/src/types/sql.ts @@ -15,7 +15,7 @@ export interface CHQueryBase extends DataQuery { rawSql: string; /** - * REQUIRED by backend for auto selecting preferredVisualisationType. + * REQUIRED by backend for auto selecting preferredVisualizationType. * Only used in explore view. * src: https://github.com/grafana/sqlds/blob/dda2dc0a54b128961fc9f7885baabf555f3ddfdc/query.go#L36 */ From ceca61531ea5f7b9dcc0321cc0620bdc15117e80 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 15:26:06 -0500 Subject: [PATCH 93/95] Add version check test for 4.0.0-beta --- src/utils/version.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/version.test.ts b/src/utils/version.test.ts index 52691b3b..88ae761f 100644 --- a/src/utils/version.test.ts +++ b/src/utils/version.test.ts @@ -45,6 +45,7 @@ describe('SemVersion', () => { { values: ['3.4.5', '4'], expected: false }, { values: ['3.4.5', '3.5'], expected: false }, { values: ['6.0.0', '5.2.0'], expected: true }, + { values: ['3.1.1', '4.0.0-beta'], expected: false }, ]; cases.forEach((testCase) => { expect(isVersionGtOrEq(testCase.values[0], testCase.values[1])).toBe(testCase.expected); From e1b46d3f01d6dedf4825024b9142c0ca44f1a8e2 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 15:27:22 -0500 Subject: [PATCH 94/95] update version to '4.0.0-beta' in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 644e0ff6..5248f67c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clickhouse-datasource", - "version": "4.0.0", + "version": "4.0.0-beta", "description": "Clickhouse Datasource", "engines": { "node": ">=16" From 7ee626bda268751eb0a53ff0a6afa980bff2e2b6 Mon Sep 17 00:00:00 2001 From: Spencer Torres Date: Tue, 5 Dec 2023 15:40:58 -0500 Subject: [PATCH 95/95] allow limit input to be set to 0 --- src/components/queryBuilder/LimitEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/queryBuilder/LimitEditor.tsx b/src/components/queryBuilder/LimitEditor.tsx index fbe059e7..7d4da2c1 100644 --- a/src/components/queryBuilder/LimitEditor.tsx +++ b/src/components/queryBuilder/LimitEditor.tsx @@ -22,7 +22,7 @@ export const LimitEditor = (props: LimitEditorProps) => { width={10} value={limit} type="number" - min={1} + min={0} onChange={e => setLimit(e.currentTarget.valueAsNumber)} onBlur={() => props.onLimitChange(limit)} />