Skip to content

Commit

Permalink
Update query builder/sqlGenerator with new state/config changes
Browse files Browse the repository at this point in the history
  • Loading branch information
SpencerTorres committed Oct 25, 2023
1 parent b9fb80f commit b7ee093
Show file tree
Hide file tree
Showing 23 changed files with 661 additions and 377 deletions.
15 changes: 15 additions & 0 deletions src/components/Divider.tsx
Original file line number Diff line number Diff line change
@@ -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') ? (
<GrafanaDivider />
) : (
<div
style={{ borderTop: `1px solid ${theme.colors.border.weak}`, margin: theme.spacing(2, 0), width: '100%' }}
></div>
);
}
2 changes: 1 addition & 1 deletion src/components/SqlEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
31 changes: 25 additions & 6 deletions src/components/queryBuilder/ColumnSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,47 @@ 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;
}

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<SelectableValue<string>> = allColumns.
filter(columnFilterFn || defaultFilterFn).
map(c => ({ label: c.name, value: c.name }));

const onChange = (selected: SelectableValue<string>) => {
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<string | undefined>) => {
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
});
}
Expand All @@ -43,13 +59,16 @@ export const ColumnSelect = (props: ColumnSelectProps) => {
<InlineFormLabel width={wide ? 12 : 8} className={labelStyle} tooltip={tooltip}>
{label}
</InlineFormLabel>
<Select<string>
<Select<string | undefined>
disabled={disabled}
invalid={invalid || staleOption}
options={columns}
value={selectedColumnName}
placeholder={selectedColumnName || undefined}
onChange={onChange}
width={wide ? 25 : 20}
menuPlacement={'bottom'}
isClearable
/>
</div>
);
Expand Down
13 changes: 12 additions & 1 deletion src/components/queryBuilder/ColumnsEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface ColumnsEditorProps {
selectedColumns: SelectedColumn[];
onSelectedColumnsChange: (selectedColumns: SelectedColumn[]) => void;
disabled?: boolean;
showAllOption?: boolean;
}

function getCustomColumns(columnNames: string[], allColumns: readonly TableColumn[]): Array<SelectableValue<string>> {
Expand All @@ -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<Array<SelectableValue<string>>>([]);
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;

Expand All @@ -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);

Expand Down
16 changes: 8 additions & 8 deletions src/components/queryBuilder/DatabaseTableSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 (
<>
Expand Down
41 changes: 41 additions & 0 deletions src/components/queryBuilder/DurationUnitSelect.tsx
Original file line number Diff line number Diff line change
@@ -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<SelectableValue<TimeUnit>> = [
{ 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 (
<div className="gf-form">
<InlineFormLabel width={12} className={`query-keyword ${inline ? styles.QueryEditor.inlineField : ''}`} tooltip={tooltip}>
{label}
</InlineFormLabel>
<Select<TimeUnit>
disabled={disabled}
options={durationUnitOptions as Array<SelectableValue<TimeUnit>>}
value={unit}
onChange={v => onChange(v.value!)}
width={inline ? 25 : 30}
menuPlacement={'bottom'}
/>
</div>
);
};
4 changes: 2 additions & 2 deletions src/components/queryBuilder/FilterEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ const standardTimeOptions: Array<SelectableValue<string>> = [
export const defaultNewFilter: NullFilter = {
filterType: 'custom',
condition: 'AND',
key: 'Id',
type: 'id',
key: '',
type: '',
operator: FilterOperator.IsNotNull,
};
export interface PredefinedFilter {
Expand Down
27 changes: 11 additions & 16 deletions src/components/queryBuilder/OtelVersionSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -34,15 +35,9 @@ export const OtelVersionSelect = (props: OtelVersionSelectProps) => {
alignItems: 'center',
};

useEffect(() => {
if (defaultToLatest && selectedVersion === '') {
onVersionChange(allVersions[0].version);
}
}, [selectedVersion, onVersionChange, defaultToLatest])

return (
<div className="gf-form">
<InlineFormLabel width={8} className="query-keyword" tooltip={tooltip}>
<InlineFormLabel width={wide ? 12 : 8} className="query-keyword" tooltip={tooltip}>
{label}
</InlineFormLabel>
<div style={switchContainerStyle}>
Expand Down
16 changes: 8 additions & 8 deletions src/components/queryBuilder/QueryBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { TraceQueryBuilder } from './views/TraceQueryBuilder';
interface QueryBuilderProps {
app: CoreApp | undefined;
builderOptions: QueryBuilderOptions;
onBuilderOptionsChange: (builderOptions: QueryBuilderOptions) => void;
onBuilderOptionsChange: (nextBuilderOptions: Partial<QueryBuilderOptions>) => void;
datasource: Datasource;
generatedSql: string;
}
Expand All @@ -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 (
<div data-testid="query-editor-section-builder">
Expand All @@ -41,10 +41,10 @@ export const QueryBuilder = (props: QueryBuilderProps) => {
<QueryTypeSwitcher queryType={builderOptions.queryType} onChange={onQueryTypeChange} />
</div>

{ builderOptions.queryType === QueryType.Table && <TableQueryBuilder allColumns={allColumns} builderOptions={builderOptions} onBuilderOptionsChange={onBuilderOptionsChange} /> }
{ builderOptions.queryType === QueryType.Logs && <LogsQueryBuilder allColumns={allColumns} builderOptions={builderOptions} onBuilderOptionsChange={onBuilderOptionsChange} /> }
{ builderOptions.queryType === QueryType.TimeSeries && <TimeSeriesQueryBuilder allColumns={allColumns} builderOptions={builderOptions} onBuilderOptionsChange={onBuilderOptionsChange} /> }
{ builderOptions.queryType === QueryType.Traces && <TraceQueryBuilder allColumns={allColumns} builderOptions={builderOptions} onBuilderOptionsChange={onBuilderOptionsChange} /> }
{ builderOptions.queryType === QueryType.Table && <TableQueryBuilder datasource={datasource} allColumns={allColumns} builderOptions={builderOptions} onBuilderOptionsChange={onBuilderOptionsChange} /> }
{ builderOptions.queryType === QueryType.Logs && <LogsQueryBuilder datasource={datasource} allColumns={allColumns} builderOptions={builderOptions} onBuilderOptionsChange={onBuilderOptionsChange} /> }
{ builderOptions.queryType === QueryType.TimeSeries && <TimeSeriesQueryBuilder datasource={datasource} allColumns={allColumns} builderOptions={builderOptions} onBuilderOptionsChange={onBuilderOptionsChange} /> }
{ builderOptions.queryType === QueryType.Traces && <TraceQueryBuilder datasource={datasource} allColumns={allColumns} builderOptions={builderOptions} onBuilderOptionsChange={onBuilderOptionsChange} /> }

<SqlPreview sql={generatedSql} />
</div>
Expand Down
8 changes: 1 addition & 7 deletions src/components/queryBuilder/QueryTypeSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 (
<span>
<InlineFormLabel width={8} className="query-keyword" tooltip={sqlEditor ? sqlTooltip : tooltip}>
Expand Down
Loading

0 comments on commit b7ee093

Please sign in to comment.