blob: 3d446a210e7a0c41b5d318b973f3277396d2e7b2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { IconName } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import { C, F, SqlExpression } from 'druid-query-toolkit';
import type { Filter } from 'react-table';
import { addOrUpdate, caseInsensitiveContains, filterMap } from '../utils';
export const DEFAULT_TABLE_CLASS_NAME = '-striped -highlight padded-header';
export const STANDARD_TABLE_PAGE_SIZE = 50;
export const STANDARD_TABLE_PAGE_SIZE_OPTIONS = [50, 100, 200];
export const SMALL_TABLE_PAGE_SIZE = 25;
export const SMALL_TABLE_PAGE_SIZE_OPTIONS = [25, 50, 100];
export type FilterMode = '~' | '=' | '!=' | '<' | '<=' | '>' | '>=';
export const FILTER_MODES: FilterMode[] = ['~', '=', '!=', '<', '<=', '>', '>='];
export const FILTER_MODES_NO_COMPARISON: FilterMode[] = ['~', '=', '!='];
export function filterModeToIcon(mode: FilterMode): IconName {
switch (mode) {
case '~':
return IconNames.SEARCH;
case '=':
return IconNames.EQUALS;
case '!=':
return IconNames.NOT_EQUAL_TO;
case '<':
return IconNames.LESS_THAN;
case '<=':
return IconNames.LESS_THAN_OR_EQUAL_TO;
case '>':
return IconNames.GREATER_THAN;
case '>=':
return IconNames.GREATER_THAN_OR_EQUAL_TO;
default:
return IconNames.BLANK;
}
}
export function filterModeToTitle(mode: FilterMode): string {
switch (mode) {
case '~':
return 'Search';
case '=':
return 'Equals';
case '!=':
return 'Not equals';
case '<':
return 'Less than';
case '<=':
return 'Less than or equal';
case '>':
return 'Greater than';
case '>=':
return 'Greater than or equal';
default:
return '?';
}
}
interface FilterModeAndNeedle {
mode: FilterMode;
needle: string;
needleParts: string[];
}
export function parseFilterModeAndNeedle(
filter: Filter,
loose = false,
): FilterModeAndNeedle | undefined {
const m = /^(~|=|!=|<(?!=)|<=|>(?!=)|>=)?(.*)$/.exec(String(filter.value));
if (!m) return;
if (!loose && !m[2]) return;
const mode = (m[1] as FilterMode) || '~';
const needle = m[2] || '';
return {
mode,
needle,
needleParts: needle.split('|'),
};
}
export function combineModeAndNeedle(mode: FilterMode, needle: string, cleanup = false): string {
if (cleanup && needle === '') return '';
return `${mode}${needle}`;
}
export function addOrUpdateFilter(filters: readonly Filter[], filter: Filter): Filter[] {
return addOrUpdate(filters, filter, f => f.id);
}
export function booleanCustomTableFilter(filter: Filter, value: unknown): boolean {
if (value == null) return false;
const modeAndNeedles = parseFilterModeAndNeedle(filter);
if (!modeAndNeedles) return true;
const { mode, needleParts } = modeAndNeedles;
const strValue = String(value);
switch (mode) {
case '=':
return needleParts.some(needle => strValue === needle);
case '!=':
return needleParts.every(needle => strValue !== needle);
case '<':
return needleParts.some(needle => strValue < needle);
case '<=':
return needleParts.some(needle => strValue <= needle);
case '>':
return needleParts.some(needle => strValue > needle);
case '>=':
return needleParts.some(needle => strValue >= needle);
default:
return needleParts.some(needle => caseInsensitiveContains(strValue, needle));
}
}
export function sqlQueryCustomTableFilter(filter: Filter): SqlExpression | undefined {
const modeAndNeedles = parseFilterModeAndNeedle(filter);
if (!modeAndNeedles) return;
const { mode, needleParts } = modeAndNeedles;
const column = C(filter.id);
switch (mode) {
case '=': {
return SqlExpression.or(...needleParts.map(needle => column.equal(needle)));
}
case '!=': {
return SqlExpression.and(...needleParts.map(needle => column.unequal(needle)));
}
case '<':
return SqlExpression.or(...needleParts.map(needle => column.lessThan(needle)));
case '<=':
return SqlExpression.or(...needleParts.map(needle => column.lessThanOrEqual(needle)));
case '>':
return SqlExpression.or(...needleParts.map(needle => column.greaterThan(needle)));
case '>=':
return SqlExpression.or(...needleParts.map(needle => column.greaterThanOrEqual(needle)));
default:
return SqlExpression.or(
...needleParts.map(needle => F('LOWER', column).like(`%${needle.toLowerCase()}%`)),
);
}
}
export function sqlQueryCustomTableFilters(filters: Filter[]): SqlExpression {
return SqlExpression.and(...filterMap(filters, sqlQueryCustomTableFilter));
}
export function tableFiltersToString(tableFilters: Filter[]): string {
return tableFilters
.map(({ id, value }) => `${id}${value.replace(/[&%/]/g, encodeURIComponent)}`)
.join('&');
}
export function stringToTableFilters(str: string | undefined): Filter[] {
if (!str) return [];
// '~' | '=' | '!=' | '<' | '<=' | '>' | '>=';
return filterMap(str.split('&'), clause => {
const m = /^(\w+)((?:~|=|!=|<(?!=)|<=|>(?!=)|>=).*)$/.exec(
clause.replace(/%2[56F]/g, decodeURIComponent),
);
if (!m) return;
return { id: m[1], value: m[2] };
});
}