Skip to content

Commit

Permalink
fix: Allow custom filter values for fields with Map type in query bui…
Browse files Browse the repository at this point in the history
…lder (#246)
  • Loading branch information
cletter7 authored Nov 30, 2022
1 parent c3db6b5 commit 47a8161
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 35 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 2.0.4

**Fix** - Query builder: allow custom filter values for fields with [`Map`](https://clickhouse.com/docs/en/sql-reference/data-types/map/) type

## 2.0.3

**Chore** - Backend binaries compiled with latest go version 1.19.3
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "clickhouse-datasource",
"version": "2.0.3",
"version": "2.0.4",
"description": "Clickhouse Datasource",
"scripts": {
"spellcheck": "cspell -c cspell.config.json \"**/*.{ts,tsx,js,go,md,mdx,yml,yaml,json,scss,css}\"",
Expand Down
81 changes: 81 additions & 0 deletions src/components/queryBuilder/Filters.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,87 @@ describe('FiltersEditor', () => {
);
expect(result.container.firstChild).not.toBeNull();
});
it('should have all provided fields in the select', () => {
const result = render(
<FilterEditor
fieldsList={[
{ label: 'col1', name: 'col1', type: 'string', picklistValues: [] },
{ label: 'col2', name: 'col2', type: 'string', picklistValues: [] },
{ label: 'col3', name: 'col3', type: 'string', picklistValues: [] },
]}
filter={{
key: 'foo',
operator: FilterOperator.IsNotNull,
type: 'boolean',
condition: 'AND',
filterType: 'custom',
}}
index={0}
onFilterChange={() => {}}
/>
);

// expand the `fieldName` select box
userEvent.type(result.getAllByRole('combobox')[0], '{ArrowDown}');

expect(result.getByText('col1')).toBeInTheDocument();
expect(result.getByText('col2')).toBeInTheDocument();
expect(result.getByText('col3')).toBeInTheDocument();
});
it('should call onFilterChange when user adds correct custom filter for the field with Map type', () => {
const onFilterChange = jest.fn();
const result = render(
<FilterEditor
fieldsList={[{ label: 'colName', name: 'colName', type: 'Map(String, UInt64)', picklistValues: [] }]}
filter={{
key: 'foo',
type: 'boolean',
operator: FilterOperator.IsNotNull,
condition: 'AND',
filterType: 'custom',
}}
index={0}
onFilterChange={onFilterChange}
/>
);

// type into the `fieldName` select box
userEvent.type(result.getAllByRole('combobox')[0], `colName[['keyName']`);
userEvent.keyboard('{Enter}');

const expectedFilter: Filter = {
key: `colName['keyName']`,
type: 'UInt64',
operator: FilterOperator.IsNotNull,
condition: 'AND',
filterType: 'custom',
};

expect(onFilterChange).toHaveBeenCalledWith(0, expectedFilter);
});
it('should not call onFilterChange when user adds incorrect custom filter', async () => {
const onFilterChange = jest.fn();
const result = render(
<FilterEditor
fieldsList={[{ label: 'mapField', name: 'mapField', type: 'Map(String, UInt64)', picklistValues: [] }]}
filter={{
key: 'foo',
type: 'boolean',
operator: FilterOperator.IsNotNull,
condition: 'AND',
filterType: 'custom',
}}
index={0}
onFilterChange={onFilterChange}
/>
);

// type into the `fieldName` select box
userEvent.type(result.getAllByRole('combobox')[0], `mapField__key`);
userEvent.keyboard('{Enter}');

expect(onFilterChange).not.toHaveBeenCalled();
});
});
describe('FilterValueEditor', () => {
it('should render nothing for null operator', () => {
Expand Down
99 changes: 65 additions & 34 deletions src/components/queryBuilder/Filters.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { SelectableValue } from '@grafana/data';
import { Button, InlineFormLabel, RadioButtonGroup, Select, Input, MultiSelect } from '@grafana/ui';
import { Filter, FullField, FilterOperator, NullFilter, BooleanFilter, DateFilter } from './../../types';
import { Filter, FullField, FilterOperator, NullFilter } from './../../types';
import * as utils from './utils';
import { selectors } from './../../selectors';
import { styles } from '../../styles';
Expand Down Expand Up @@ -227,41 +227,72 @@ export const FilterEditor = (props: {
};
const onFilterNameChange = (fieldName: string) => {
setIsOpen(false);
const matchingFiled = fieldsList.find((f) => f.name === fieldName);
if (matchingFiled) {
var newFilter: Filter;
if (utils.isBooleanType(matchingFiled.type)) {
let boolFilter: BooleanFilter = {
filterType: 'custom',
key: matchingFiled.name,
type: 'boolean',
condition: filter.condition || 'AND',
operator: FilterOperator.Equals,
value: false,
};
newFilter = boolFilter;
} else if (utils.isDateType(matchingFiled.type)) {
let timeFilter: DateFilter = {
filterType: 'custom',
key: matchingFiled.name,
type: matchingFiled.type as 'date',
condition: filter.condition || 'AND',
operator: FilterOperator.Equals,
value: 'TODAY',
};
newFilter = timeFilter;
} else {
let nullFilter: NullFilter = {
filterType: 'custom',
key: matchingFiled.name,
type: matchingFiled.type,
condition: filter.condition || 'AND',
operator: FilterOperator.IsNotNull,
};
newFilter = nullFilter;
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,
};
}
}
onFilterChange(index, newFilter);
}

if (!filterData) {
return;
}

let newFilter: Filter;
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;
Expand Down

0 comments on commit 47a8161

Please sign in to comment.