Azure: Improved column handling in logs query builder (#114667)
* Add parameter type field * Use parameterType to filter columns for aggregation funcs * Add tests for aggregate components
This commit is contained in:
+9
@@ -329,8 +329,17 @@ export enum BuilderQueryEditorOrderByOptions {
|
||||
Desc = 'desc',
|
||||
}
|
||||
|
||||
export enum BuilderQueryEditorReduceParameterTypes {
|
||||
Generic = 'generic',
|
||||
Numeric = 'numeric',
|
||||
}
|
||||
|
||||
export interface BuilderQueryEditorProperty {
|
||||
name: string;
|
||||
/**
|
||||
* Optional parameter type for function properties
|
||||
*/
|
||||
parameterType?: BuilderQueryEditorReduceParameterTypes;
|
||||
type: BuilderQueryEditorPropertyType;
|
||||
}
|
||||
|
||||
|
||||
@@ -223,6 +223,8 @@ func NewBuilderQueryEditorPropertyExpression() *BuilderQueryEditorPropertyExpres
|
||||
type BuilderQueryEditorProperty struct {
|
||||
Type BuilderQueryEditorPropertyType `json:"type"`
|
||||
Name string `json:"name"`
|
||||
// Optional parameter type for function properties
|
||||
ParameterType *BuilderQueryEditorReduceParameterTypes `json:"parameterType,omitempty"`
|
||||
}
|
||||
|
||||
// NewBuilderQueryEditorProperty creates a new BuilderQueryEditorProperty object.
|
||||
@@ -242,6 +244,13 @@ const (
|
||||
BuilderQueryEditorPropertyTypeInterval BuilderQueryEditorPropertyType = "interval"
|
||||
)
|
||||
|
||||
type BuilderQueryEditorReduceParameterTypes string
|
||||
|
||||
const (
|
||||
BuilderQueryEditorReduceParameterTypesGeneric BuilderQueryEditorReduceParameterTypes = "generic"
|
||||
BuilderQueryEditorReduceParameterTypesNumeric BuilderQueryEditorReduceParameterTypes = "numeric"
|
||||
)
|
||||
|
||||
type BuilderQueryEditorExpressionType string
|
||||
|
||||
const (
|
||||
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import {
|
||||
BuilderQueryEditorExpressionType,
|
||||
BuilderQueryEditorPropertyType,
|
||||
BuilderQueryEditorReduceExpression,
|
||||
BuilderQueryEditorReduceParameterTypes,
|
||||
} from '../../dataquery.gen';
|
||||
|
||||
import AggregateItem from './AggregateItem';
|
||||
|
||||
describe('AggregateItem', () => {
|
||||
const mockColumns = [
|
||||
{ label: 'TimeGenerated', value: 'TimeGenerated' },
|
||||
{ label: 'Level', value: 'Level' },
|
||||
{ label: 'Message', value: 'Message' },
|
||||
];
|
||||
|
||||
const mockTemplateVariables = { label: '$variable', value: '$variable' };
|
||||
|
||||
const defaultAggregate: BuilderQueryEditorReduceExpression = {
|
||||
reduce: {
|
||||
name: 'sum',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Numeric,
|
||||
},
|
||||
property: {
|
||||
name: 'TimeGenerated',
|
||||
type: BuilderQueryEditorPropertyType.String,
|
||||
},
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
aggregate: defaultAggregate,
|
||||
columns: mockColumns,
|
||||
onChange: jest.fn(),
|
||||
onDelete: jest.fn(),
|
||||
templateVariableOptions: mockTemplateVariables,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders aggregate function select (with column field)', () => {
|
||||
render(<AggregateItem {...defaultProps} />);
|
||||
expect(screen.getByLabelText('Aggregate function')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Column')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render column select for count aggregates', () => {
|
||||
const countAggregate = {
|
||||
...defaultAggregate,
|
||||
reduce: {
|
||||
name: 'count',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Generic,
|
||||
},
|
||||
};
|
||||
render(<AggregateItem {...defaultProps} aggregate={countAggregate} />);
|
||||
expect(screen.queryByLabelText('Column')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders percentile input and OF label for percentile aggregate', () => {
|
||||
const percentileAggregate: BuilderQueryEditorReduceExpression = {
|
||||
reduce: {
|
||||
name: 'percentile',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Numeric,
|
||||
},
|
||||
parameters: [
|
||||
{
|
||||
type: BuilderQueryEditorExpressionType.Function_parameter,
|
||||
fieldType: BuilderQueryEditorPropertyType.Number,
|
||||
value: '95',
|
||||
},
|
||||
{
|
||||
type: BuilderQueryEditorExpressionType.Function_parameter,
|
||||
fieldType: BuilderQueryEditorPropertyType.String,
|
||||
value: 'TimeGenerated',
|
||||
},
|
||||
],
|
||||
property: {
|
||||
name: 'TimeGenerated',
|
||||
type: BuilderQueryEditorPropertyType.String,
|
||||
},
|
||||
};
|
||||
render(<AggregateItem {...defaultProps} aggregate={percentileAggregate} />);
|
||||
expect(screen.getByDisplayValue('95')).toBeInTheDocument();
|
||||
expect(screen.getByText('OF')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onChange when aggregate function changes', async () => {
|
||||
render(<AggregateItem {...defaultProps} />);
|
||||
|
||||
const select = screen.getByLabelText('Aggregate function');
|
||||
await userEvent.click(select);
|
||||
|
||||
const avgOption = await screen.findByText('avg');
|
||||
await userEvent.click(avgOption);
|
||||
|
||||
expect(defaultProps.onChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reduce: expect.objectContaining({
|
||||
name: 'avg',
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Numeric,
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('calls onChange when column changes', async () => {
|
||||
render(<AggregateItem {...defaultProps} />);
|
||||
|
||||
const columnSelect = screen.getByLabelText('Column');
|
||||
await userEvent.click(columnSelect);
|
||||
const levelOption = await screen.findByText('Level');
|
||||
await userEvent.click(levelOption);
|
||||
|
||||
expect(defaultProps.onChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
property: expect.objectContaining({
|
||||
name: 'Level',
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('calls onChange when percentile value changes', async () => {
|
||||
const percentileAggregate: BuilderQueryEditorReduceExpression = {
|
||||
reduce: {
|
||||
name: 'percentile',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Numeric,
|
||||
},
|
||||
parameters: [
|
||||
{
|
||||
type: BuilderQueryEditorExpressionType.Function_parameter,
|
||||
fieldType: BuilderQueryEditorPropertyType.Number,
|
||||
value: '95',
|
||||
},
|
||||
{
|
||||
type: BuilderQueryEditorExpressionType.Function_parameter,
|
||||
fieldType: BuilderQueryEditorPropertyType.String,
|
||||
value: 'TimeGenerated',
|
||||
},
|
||||
],
|
||||
};
|
||||
render(<AggregateItem {...defaultProps} aggregate={percentileAggregate} />);
|
||||
|
||||
const percentileInput = screen.getByDisplayValue('95');
|
||||
await userEvent.clear(percentileInput);
|
||||
await userEvent.type(percentileInput, '99');
|
||||
|
||||
expect(defaultProps.onChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
parameters: expect.arrayContaining([expect.objectContaining({ value: '99' })]),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('calls onDelete when delete button clicked', async () => {
|
||||
render(<AggregateItem {...defaultProps} />);
|
||||
|
||||
const deleteButton = screen.getByLabelText('Remove');
|
||||
await userEvent.click(deleteButton);
|
||||
expect(defaultProps.onDelete).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('includes template variables in column options', async () => {
|
||||
render(<AggregateItem {...defaultProps} />);
|
||||
|
||||
const columnSelect = screen.getByLabelText('Column');
|
||||
await userEvent.click(columnSelect);
|
||||
expect(await screen.findByText('$variable')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles array of template variables', async () => {
|
||||
const arrayTemplateVars = [
|
||||
{ label: '$var1', value: '$var1' },
|
||||
{ label: '$var2', value: '$var2' },
|
||||
];
|
||||
render(<AggregateItem {...defaultProps} templateVariableOptions={arrayTemplateVars[0]} />);
|
||||
|
||||
const columnSelect = screen.getByLabelText('Column');
|
||||
await userEvent.click(columnSelect);
|
||||
|
||||
expect(await screen.findByText('$var1')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+10
-1
@@ -9,6 +9,7 @@ import {
|
||||
BuilderQueryEditorExpressionType,
|
||||
BuilderQueryEditorPropertyType,
|
||||
BuilderQueryEditorReduceExpression,
|
||||
BuilderQueryEditorReduceParameterTypes,
|
||||
} from '../../dataquery.gen';
|
||||
|
||||
import { aggregateOptions, inputFieldSize } from './utils';
|
||||
@@ -65,8 +66,16 @@ const AggregateItem: React.FC<AggregateItemProps> = ({
|
||||
};
|
||||
|
||||
const handleAggregateChange = (funcName?: string) => {
|
||||
const functionParameterType =
|
||||
aggregateOptions.find((option) => option.value === (funcName || ''))?.parameterType ||
|
||||
BuilderQueryEditorReduceParameterTypes.Generic;
|
||||
|
||||
updateAggregate({
|
||||
reduce: { name: funcName || '', type: BuilderQueryEditorPropertyType.Function },
|
||||
reduce: {
|
||||
name: funcName || '',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: functionParameterType,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import {
|
||||
AzureQueryType,
|
||||
BuilderQueryEditorExpressionType,
|
||||
BuilderQueryEditorPropertyType,
|
||||
BuilderQueryEditorReduceExpression,
|
||||
BuilderQueryEditorReduceParameterTypes,
|
||||
} from '../../dataquery.gen';
|
||||
import { AzureMonitorQuery } from '../../types/query';
|
||||
|
||||
import { AggregateSection } from './AggregationSection';
|
||||
|
||||
describe('AggregationSection', () => {
|
||||
const mockAllColumns = [
|
||||
{ name: 'TimeGenerated', type: 'datetime' },
|
||||
{ name: 'Level', type: 'string' },
|
||||
{ name: 'Count', type: 'int' },
|
||||
{ name: 'Duration', type: 'real' },
|
||||
];
|
||||
|
||||
const mockTemplateVariables = { label: '$variable', value: '$variable' };
|
||||
|
||||
const createMockQuery = (reduce?: BuilderQueryEditorReduceExpression[]): AzureMonitorQuery => ({
|
||||
refId: 'A',
|
||||
queryType: AzureQueryType.LogAnalytics,
|
||||
azureLogAnalytics: {
|
||||
builderQuery: {
|
||||
from: {
|
||||
type: BuilderQueryEditorExpressionType.Property,
|
||||
property: { type: BuilderQueryEditorPropertyType.String, name: 'AppRequests' },
|
||||
},
|
||||
columns: {
|
||||
type: BuilderQueryEditorExpressionType.Property,
|
||||
columns: [],
|
||||
},
|
||||
reduce: {
|
||||
type: BuilderQueryEditorExpressionType.Reduce,
|
||||
expressions: reduce || [],
|
||||
},
|
||||
where: {
|
||||
type: BuilderQueryEditorExpressionType.And,
|
||||
expressions: [],
|
||||
},
|
||||
groupBy: {
|
||||
type: BuilderQueryEditorExpressionType.Group_by,
|
||||
expressions: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
query: createMockQuery(),
|
||||
allColumns: mockAllColumns,
|
||||
templateVariableOptions: mockTemplateVariables,
|
||||
buildAndUpdateQuery: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders the aggregate section', () => {
|
||||
render(<AggregateSection {...defaultProps} />);
|
||||
expect(screen.getByTestId('aggregate-section')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders empty list when no aggregates exist', () => {
|
||||
render(<AggregateSection {...defaultProps} />);
|
||||
const addButton = screen.getByRole('button', { name: /add/i });
|
||||
expect(addButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders existing aggregates', () => {
|
||||
const existingAggregates: BuilderQueryEditorReduceExpression[] = [
|
||||
{
|
||||
reduce: {
|
||||
name: 'sum',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Numeric,
|
||||
},
|
||||
property: {
|
||||
name: 'Count',
|
||||
type: BuilderQueryEditorPropertyType.String,
|
||||
},
|
||||
},
|
||||
{
|
||||
reduce: {
|
||||
name: 'avg',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Numeric,
|
||||
},
|
||||
property: {
|
||||
name: 'Duration',
|
||||
type: BuilderQueryEditorPropertyType.String,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const queryWithAggregates = createMockQuery(existingAggregates);
|
||||
render(<AggregateSection {...defaultProps} query={queryWithAggregates} />);
|
||||
|
||||
expect(screen.getAllByLabelText('Aggregate function')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('calls buildAndUpdateQuery when aggregate is added', async () => {
|
||||
render(<AggregateSection {...defaultProps} />);
|
||||
|
||||
const addButton = screen.getByRole('button', { name: /add/i });
|
||||
await userEvent.click(addButton);
|
||||
|
||||
expect(defaultProps.buildAndUpdateQuery).toHaveBeenCalledWith({
|
||||
reduce: expect.arrayContaining([expect.objectContaining({})]),
|
||||
});
|
||||
});
|
||||
|
||||
it('calls buildAndUpdateQuery when aggregate is deleted', async () => {
|
||||
const existingAggregates: BuilderQueryEditorReduceExpression[] = [
|
||||
{
|
||||
reduce: {
|
||||
name: 'sum',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Numeric,
|
||||
},
|
||||
property: {
|
||||
name: 'Count',
|
||||
type: BuilderQueryEditorPropertyType.String,
|
||||
},
|
||||
},
|
||||
];
|
||||
const avgAggregate = {
|
||||
reduce: {
|
||||
name: 'avg',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Numeric,
|
||||
},
|
||||
property: {
|
||||
name: 'Duration',
|
||||
type: BuilderQueryEditorPropertyType.String,
|
||||
},
|
||||
};
|
||||
existingAggregates.push(avgAggregate);
|
||||
|
||||
const queryWithAggregates = createMockQuery(existingAggregates);
|
||||
render(<AggregateSection {...defaultProps} query={queryWithAggregates} />);
|
||||
|
||||
const deleteButton = (await screen.findAllByLabelText('Remove'))[0];
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
expect(defaultProps.buildAndUpdateQuery).toHaveBeenCalledWith({
|
||||
reduce: [avgAggregate],
|
||||
});
|
||||
expect(screen.getAllByLabelText('Aggregate function')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('provides numeric columns for numeric aggregate functions', async () => {
|
||||
const numericAggregate: BuilderQueryEditorReduceExpression[] = [
|
||||
{
|
||||
reduce: {
|
||||
name: 'sum',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Numeric,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const queryWithAggregate = createMockQuery(numericAggregate);
|
||||
render(<AggregateSection {...defaultProps} query={queryWithAggregate} />);
|
||||
|
||||
const columnSelect = screen.getByLabelText('Column');
|
||||
await userEvent.click(columnSelect);
|
||||
|
||||
expect(await screen.getByText('Count')).toBeInTheDocument();
|
||||
expect(await screen.getByText('Duration')).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByText('Level')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('provides all columns for generic aggregate functions', async () => {
|
||||
const genericAggregate: BuilderQueryEditorReduceExpression[] = [
|
||||
{
|
||||
reduce: {
|
||||
name: 'min',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Generic,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const queryWithAggregate = createMockQuery(genericAggregate);
|
||||
render(<AggregateSection {...defaultProps} query={queryWithAggregate} />);
|
||||
|
||||
const columnSelect = screen.getByLabelText('Column');
|
||||
await userEvent.click(columnSelect);
|
||||
|
||||
expect(await screen.getByText('TimeGenerated')).toBeInTheDocument();
|
||||
expect(await screen.getByText('Level')).toBeInTheDocument();
|
||||
expect(await screen.getByText('Count')).toBeInTheDocument();
|
||||
expect(await screen.getByText('Duration')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('resets aggregates when table changes', () => {
|
||||
const existingAggregates: BuilderQueryEditorReduceExpression[] = [
|
||||
{
|
||||
reduce: {
|
||||
name: 'sum',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Numeric,
|
||||
},
|
||||
property: {
|
||||
name: 'Count',
|
||||
type: BuilderQueryEditorPropertyType.String,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const queryWithAggregates = createMockQuery(existingAggregates);
|
||||
const { rerender } = render(<AggregateSection {...defaultProps} query={queryWithAggregates} />);
|
||||
|
||||
const newQuery = createMockQuery(existingAggregates);
|
||||
newQuery.azureLogAnalytics!.builderQuery!.from!.property.name = 'AppEvents';
|
||||
|
||||
rerender(<AggregateSection {...defaultProps} query={newQuery} />);
|
||||
|
||||
const addButton = screen.getByRole('button', { name: /add/i });
|
||||
expect(addButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('uses selected columns when available', async () => {
|
||||
const aggregate: BuilderQueryEditorReduceExpression[] = [
|
||||
{
|
||||
reduce: {
|
||||
name: 'sum',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Generic,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const query = createMockQuery(aggregate);
|
||||
query.azureLogAnalytics!.builderQuery = {
|
||||
...query.azureLogAnalytics!.builderQuery,
|
||||
columns: {
|
||||
columns: ['TimeGenerated', 'Level'],
|
||||
type: BuilderQueryEditorExpressionType.Property,
|
||||
},
|
||||
};
|
||||
|
||||
render(<AggregateSection {...defaultProps} query={query} />);
|
||||
|
||||
const columnSelect = screen.getByLabelText('Column');
|
||||
await userEvent.click(columnSelect);
|
||||
|
||||
expect(await screen.getByText('TimeGenerated')).toBeInTheDocument();
|
||||
expect(await screen.getByText('Level')).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByText('Count')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Duration')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('falls back to all columns when no columns selected', async () => {
|
||||
const aggregate: BuilderQueryEditorReduceExpression[] = [
|
||||
{
|
||||
reduce: {
|
||||
name: 'sum',
|
||||
type: BuilderQueryEditorPropertyType.Function,
|
||||
parameterType: BuilderQueryEditorReduceParameterTypes.Generic,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const query = createMockQuery(aggregate);
|
||||
query.azureLogAnalytics!.builderQuery = {
|
||||
...query.azureLogAnalytics!.builderQuery,
|
||||
columns: {
|
||||
columns: [],
|
||||
type: BuilderQueryEditorExpressionType.Property,
|
||||
},
|
||||
};
|
||||
|
||||
render(<AggregateSection {...defaultProps} query={query} />);
|
||||
|
||||
const columnSelect = screen.getByLabelText('Column');
|
||||
await userEvent.click(columnSelect);
|
||||
|
||||
expect(await screen.getByText('TimeGenerated')).toBeInTheDocument();
|
||||
expect(await screen.getByText('Level')).toBeInTheDocument();
|
||||
expect(await screen.getByText('Count')).toBeInTheDocument();
|
||||
expect(await screen.getByText('Duration')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+18
-4
@@ -4,12 +4,12 @@ import { SelectableValue } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { EditorField, EditorFieldGroup, EditorList, EditorRow } from '@grafana/plugin-ui';
|
||||
|
||||
import { BuilderQueryEditorReduceExpression } from '../../dataquery.gen';
|
||||
import { BuilderQueryEditorReduceExpression, BuilderQueryEditorReduceParameterTypes } from '../../dataquery.gen';
|
||||
import { AzureLogAnalyticsMetadataColumn } from '../../types/logAnalyticsMetadata';
|
||||
import { AzureMonitorQuery } from '../../types/query';
|
||||
|
||||
import AggregateItem from './AggregateItem';
|
||||
import { BuildAndUpdateOptions } from './utils';
|
||||
import { BuildAndUpdateOptions, isNumericColumn } from './utils';
|
||||
|
||||
interface AggregateSectionProps {
|
||||
query: AzureMonitorQuery;
|
||||
@@ -43,6 +43,10 @@ export const AggregateSection: React.FC<AggregateSectionProps> = ({
|
||||
const availableColumns: Array<SelectableValue<string>> = builderQuery?.columns?.columns?.length
|
||||
? builderQuery.columns.columns.map((col) => ({ label: col, value: col }))
|
||||
: allColumns.map((col) => ({ label: col.name, value: col.name }));
|
||||
const numericColumns: Array<SelectableValue<string>> = allColumns.filter(isNumericColumn).map((col) => ({
|
||||
label: col.name,
|
||||
value: col.name,
|
||||
}));
|
||||
|
||||
const onChange = (newItems: Array<Partial<BuilderQueryEditorReduceExpression>>) => {
|
||||
setAggregates(newItems);
|
||||
@@ -82,7 +86,12 @@ export const AggregateSection: React.FC<AggregateSectionProps> = ({
|
||||
<EditorList
|
||||
items={aggregates}
|
||||
onChange={onChange}
|
||||
renderItem={makeRenderAggregate(availableColumns, onDeleteAggregate, templateVariableOptions)}
|
||||
renderItem={makeRenderAggregate(
|
||||
availableColumns,
|
||||
numericColumns,
|
||||
onDeleteAggregate,
|
||||
templateVariableOptions
|
||||
)}
|
||||
/>
|
||||
</EditorField>
|
||||
</EditorFieldGroup>
|
||||
@@ -93,6 +102,7 @@ export const AggregateSection: React.FC<AggregateSectionProps> = ({
|
||||
|
||||
function makeRenderAggregate(
|
||||
availableColumns: Array<SelectableValue<string>>,
|
||||
numericColumns: Array<SelectableValue<string>>,
|
||||
onDeleteAggregate: (aggregate: BuilderQueryEditorReduceExpression) => void,
|
||||
templateVariableOptions: SelectableValue<string>
|
||||
) {
|
||||
@@ -105,7 +115,11 @@ function makeRenderAggregate(
|
||||
aggregate={item}
|
||||
onChange={onChange}
|
||||
onDelete={() => onDeleteAggregate(item)}
|
||||
columns={availableColumns}
|
||||
columns={
|
||||
item.reduce?.name && item.reduce.parameterType === BuilderQueryEditorReduceParameterTypes.Numeric
|
||||
? numericColumns
|
||||
: availableColumns
|
||||
}
|
||||
templateVariableOptions={templateVariableOptions}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
BuilderQueryEditorPropertyExpression,
|
||||
BuilderQueryEditorPropertyType,
|
||||
BuilderQueryEditorReduceExpression,
|
||||
BuilderQueryEditorReduceParameterTypes,
|
||||
BuilderQueryEditorWhereExpression,
|
||||
BuilderQueryExpression,
|
||||
} from '../../dataquery.gen';
|
||||
@@ -93,12 +94,17 @@ export interface BuildAndUpdateOptions {
|
||||
}
|
||||
|
||||
export const aggregateOptions = [
|
||||
{ label: 'sum', value: 'sum' },
|
||||
{ label: 'avg', value: 'avg' },
|
||||
{ label: 'percentile', value: 'percentile' },
|
||||
{ label: 'count', value: 'count' },
|
||||
{ label: 'min', value: 'min' },
|
||||
{ label: 'max', value: 'max' },
|
||||
{ label: 'dcount', value: 'dcount' },
|
||||
{ label: 'stdev', value: 'stdev' },
|
||||
{ label: 'sum', value: 'sum', parameterType: BuilderQueryEditorReduceParameterTypes.Numeric },
|
||||
{ label: 'avg', value: 'avg', parameterType: BuilderQueryEditorReduceParameterTypes.Numeric },
|
||||
{ label: 'percentile', value: 'percentile', parameterType: BuilderQueryEditorReduceParameterTypes.Numeric },
|
||||
{ label: 'stdev', value: 'stdev', parameterType: BuilderQueryEditorReduceParameterTypes.Numeric },
|
||||
{ label: 'min', value: 'min', parameterType: BuilderQueryEditorReduceParameterTypes.Generic },
|
||||
{ label: 'max', value: 'max', parameterType: BuilderQueryEditorReduceParameterTypes.Generic },
|
||||
{ label: 'count', value: 'count', parameterType: BuilderQueryEditorReduceParameterTypes.Generic },
|
||||
{ label: 'dcount', value: 'dcount', parameterType: BuilderQueryEditorReduceParameterTypes.Generic },
|
||||
];
|
||||
|
||||
export const isNumericColumn = (column: AzureLogAnalyticsMetadataColumn): boolean => {
|
||||
const numericTypes = ['decimal', 'int', 'long', 'real'];
|
||||
return numericTypes.includes(column.type);
|
||||
};
|
||||
|
||||
@@ -175,10 +175,13 @@ composableKinds: DataQuery: {
|
||||
#BuilderQueryEditorExpressionType: "property" | "operator" | "reduce" | "function_parameter" | "group_by" | "or" | "and" | "order_by" @cuetsy(kind="enum", memberNames:"Property|Operator|Reduce|FunctionParameter|GroupBy|Or|And|OrderBy")
|
||||
#BuilderQueryEditorPropertyType: "number" | "string" | "boolean" | "datetime" | "time_span" | "function" | "interval" @cuetsy(kind="enum", memberNames:"Number|String|Boolean|Datetime|TimeSpan|Function|Interval")
|
||||
#BuilderQueryEditorOrderByOptions: "asc" | "desc" @cuetsy(kind="enum", memberNames:"Asc|Desc")
|
||||
#BuilderQueryEditorReduceParameterTypes: "generic" | "numeric" @cuetsy(kind="enum", memberNames:"Asc|Desc")
|
||||
|
||||
#BuilderQueryEditorProperty: {
|
||||
type: #BuilderQueryEditorPropertyType
|
||||
name: string
|
||||
// Optional parameter type for function properties
|
||||
parameterType?: #BuilderQueryEditorReduceParameterTypes
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
#BuilderQueryEditorPropertyExpression: {
|
||||
|
||||
@@ -327,8 +327,17 @@ export enum BuilderQueryEditorOrderByOptions {
|
||||
Desc = 'desc',
|
||||
}
|
||||
|
||||
export enum BuilderQueryEditorReduceParameterTypes {
|
||||
Generic = 'generic',
|
||||
Numeric = 'numeric',
|
||||
}
|
||||
|
||||
export interface BuilderQueryEditorProperty {
|
||||
name: string;
|
||||
/**
|
||||
* Optional parameter type for function properties
|
||||
*/
|
||||
parameterType?: BuilderQueryEditorReduceParameterTypes;
|
||||
type: BuilderQueryEditorPropertyType;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user