Reviewed By: LukeDefeo Differential Revision: D51202685 fbshipit-source-id: 74d0a3d7ed956f3fafc393f180b30cd2fcc55384
389 lines
12 KiB
TypeScript
389 lines
12 KiB
TypeScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
import dayjs from 'dayjs';
|
|
import {OperatorConfig} from '../PowerSearch';
|
|
import {
|
|
EnumLabels,
|
|
FloatOperatorConfig,
|
|
} from '../PowerSearch/PowerSearchConfig';
|
|
|
|
export type PowerSearchOperatorProcessor = (
|
|
powerSearchOperatorConfig: OperatorConfig,
|
|
searchValue: any,
|
|
value: any,
|
|
) => boolean;
|
|
|
|
export const dataTablePowerSearchOperators = {
|
|
string_matches_regex: () => ({
|
|
label: 'matches regex',
|
|
key: 'string_matches_regex',
|
|
valueType: 'STRING',
|
|
}),
|
|
string_contains: () => ({
|
|
label: 'contains',
|
|
key: 'string_contains',
|
|
valueType: 'STRING',
|
|
}),
|
|
string_not_contains: () => ({
|
|
label: 'does not contain',
|
|
key: 'string_not_contains',
|
|
valueType: 'STRING',
|
|
}),
|
|
string_matches_exactly: () => ({
|
|
label: 'is',
|
|
key: 'string_matches_exactly',
|
|
valueType: 'STRING',
|
|
}),
|
|
string_not_matches_exactly: () => ({
|
|
label: 'is not',
|
|
key: 'string_not_matches_exactly',
|
|
valueType: 'STRING',
|
|
}),
|
|
searializable_object_contains: () => ({
|
|
label: 'contains',
|
|
key: 'searializable_object_contains',
|
|
valueType: 'STRING',
|
|
}),
|
|
searializable_object_matches_regex: () => ({
|
|
label: 'matches regex',
|
|
key: 'searializable_object_matches_regex',
|
|
valueType: 'STRING',
|
|
}),
|
|
searializable_object_not_contains: () => ({
|
|
label: 'does not contain',
|
|
key: 'searializable_object_not_contains',
|
|
valueType: 'STRING',
|
|
}),
|
|
string_set_contains_any_of: () => ({
|
|
label: 'contains any of',
|
|
key: 'string_set_contains_any_of',
|
|
valueType: 'STRING_SET',
|
|
}),
|
|
string_set_contains_none_of: () => ({
|
|
label: 'contains none of',
|
|
key: 'string_set_contains_none_of',
|
|
valueType: 'STRING_SET',
|
|
}),
|
|
int_equals: () => ({
|
|
label: '=',
|
|
key: 'int_equals',
|
|
valueType: 'INTEGER',
|
|
}),
|
|
int_greater_than: () => ({
|
|
label: '>',
|
|
key: 'int_greater_than',
|
|
valueType: 'INTEGER',
|
|
}),
|
|
int_greater_or_equal: () => ({
|
|
label: '>=',
|
|
key: 'int_greater_or_equal',
|
|
valueType: 'INTEGER',
|
|
}),
|
|
int_less_than: () => ({
|
|
label: '<',
|
|
key: 'int_less_than',
|
|
valueType: 'INTEGER',
|
|
}),
|
|
int_less_or_equal: () => ({
|
|
label: '<=',
|
|
key: 'int_less_or_equal',
|
|
valueType: 'INTEGER',
|
|
}),
|
|
float_equals: (precision?: number) => ({
|
|
label: '=',
|
|
key: 'float_equals',
|
|
valueType: 'FLOAT',
|
|
precision,
|
|
}),
|
|
float_greater_than: () => ({
|
|
label: '>',
|
|
key: 'float_greater_than',
|
|
valueType: 'FLOAT',
|
|
}),
|
|
float_greater_or_equal: () => ({
|
|
label: '>=',
|
|
key: 'float_greater_or_equal',
|
|
valueType: 'FLOAT',
|
|
}),
|
|
float_less_than: () => ({
|
|
label: '<',
|
|
key: 'float_less_than',
|
|
valueType: 'FLOAT',
|
|
}),
|
|
float_less_or_equal: () => ({
|
|
label: '<=',
|
|
key: 'float_less_or_equal',
|
|
valueType: 'FLOAT',
|
|
}),
|
|
// { [enumValue]: enumLabel }
|
|
enum_is: (enumLabels: EnumLabels) => ({
|
|
label: 'is',
|
|
key: 'enum_is',
|
|
valueType: 'ENUM',
|
|
enumLabels,
|
|
}),
|
|
enum_is_nullish_or: (enumLabels: EnumLabels) => ({
|
|
label: 'is nullish or',
|
|
key: 'enum_is_nullish_or',
|
|
valueType: 'ENUM',
|
|
enumLabels,
|
|
}),
|
|
enum_is_not: (enumLabels: EnumLabels) => ({
|
|
label: 'is not',
|
|
key: 'enum_is_not',
|
|
valueType: 'ENUM',
|
|
enumLabels,
|
|
}),
|
|
// TODO: Support logical operations (AND, OR, NOT) to combine primitive operators instead of adding new complex operators!
|
|
enum_set_is_nullish_or_any_of: (enumLabels: EnumLabels) => ({
|
|
label: 'is nullish or any of',
|
|
key: 'enum_set_is_nullish_or_any_of',
|
|
valueType: 'ENUM_SET',
|
|
enumLabels,
|
|
}),
|
|
enum_set_is_any_of: (enumLabels: EnumLabels) => ({
|
|
label: 'is any of',
|
|
key: 'enum_set_is_any_of',
|
|
valueType: 'ENUM_SET',
|
|
enumLabels,
|
|
}),
|
|
enum_set_is_none_of: (enumLabels: EnumLabels) => ({
|
|
label: 'is none of',
|
|
key: 'enum_set_is_none_of',
|
|
valueType: 'ENUM_SET',
|
|
enumLabels,
|
|
}),
|
|
is_nullish: () => ({
|
|
label: 'is nullish',
|
|
key: 'is_nullish',
|
|
valueType: 'NO_VALUE',
|
|
}),
|
|
newer_than_absolute_date: () => ({
|
|
key: 'newer_than_absolute_date',
|
|
label: 'is after',
|
|
valueType: 'ABSOLUTE_DATE',
|
|
dateOnly: false,
|
|
}),
|
|
newer_than_absolute_date_no_time: () => ({
|
|
key: 'newer_than_absolute_date_no_time',
|
|
label: 'is after the day',
|
|
valueType: 'ABSOLUTE_DATE',
|
|
dateOnly: true,
|
|
}),
|
|
older_than_absolute_date: () => ({
|
|
key: 'older_than_absolute_date',
|
|
label: 'is before',
|
|
valueType: 'ABSOLUTE_DATE',
|
|
dateOnly: false,
|
|
}),
|
|
older_than_absolute_date_no_time: () => ({
|
|
key: 'older_than_absolute_date_no_time',
|
|
label: 'is before the day',
|
|
valueType: 'ABSOLUTE_DATE',
|
|
dateOnly: true,
|
|
}),
|
|
same_as_absolute_date_no_time: () => ({
|
|
key: 'same_as_absolute_date_no_time',
|
|
label: 'is',
|
|
valueType: 'ABSOLUTE_DATE',
|
|
dateOnly: true,
|
|
}),
|
|
} satisfies {
|
|
[key: string]: (...args: any[]) => OperatorConfig;
|
|
};
|
|
|
|
export type PowerSearchOperatorProcessorConfig = {
|
|
[K in keyof typeof dataTablePowerSearchOperators]: PowerSearchOperatorProcessor;
|
|
};
|
|
|
|
const tryConvertingUnknownToString = (value: unknown): string | null => {
|
|
try {
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (typeof value === 'object') {
|
|
return JSON.stringify(value);
|
|
}
|
|
if (typeof value === 'number') {
|
|
return value.toString();
|
|
}
|
|
if (typeof value === 'string') {
|
|
return value;
|
|
}
|
|
throw value;
|
|
} catch (e) {
|
|
console.warn(
|
|
'tryConvertingUnknownToString -> you tried to use power search for some weird data type. Please, configure your MasterDetail component to handle it correctly. See https://fburl.com/workplace/i2n0z6sm',
|
|
e,
|
|
);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const regexCache: Record<string, RegExp> = {};
|
|
function safeCreateRegExp(source: string): RegExp | undefined {
|
|
try {
|
|
if (!regexCache[source]) {
|
|
regexCache[source] = new RegExp(source);
|
|
}
|
|
return regexCache[source];
|
|
} catch (_e) {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
const enumPredicateForWhenValueCouldBeAStringifiedNullish = (
|
|
// searchValue is typed as a string here, but originally it could have been an undefined or a null and we stringified them during inference (search for `inferEnumOptionsFromData`)
|
|
searchValue: string,
|
|
value: string | null | undefined,
|
|
): boolean => {
|
|
if (searchValue === value) {
|
|
return true;
|
|
}
|
|
if (value === null && searchValue === 'null') {
|
|
return true;
|
|
}
|
|
if (value === undefined && searchValue === 'undefined') {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
export const dataTablePowerSearchOperatorProcessorConfig = {
|
|
string_matches_regex: (_operator, searchValue: string, value: string) =>
|
|
!!safeCreateRegExp(searchValue)?.test(
|
|
tryConvertingUnknownToString(value) ?? '',
|
|
),
|
|
string_contains: (_operator, searchValue: string, value: string) =>
|
|
!!tryConvertingUnknownToString(value)
|
|
?.toLowerCase()
|
|
.includes(searchValue.toLowerCase()),
|
|
string_not_contains: (_operator, searchValue: string, value: string) =>
|
|
!tryConvertingUnknownToString(value)
|
|
?.toLowerCase()
|
|
.includes(searchValue.toLowerCase()),
|
|
searializable_object_matches_regex: (
|
|
_operator,
|
|
searchValue: string,
|
|
value: object,
|
|
) => !!safeCreateRegExp(searchValue)?.test(JSON.stringify(value)),
|
|
searializable_object_contains: (
|
|
_operator,
|
|
searchValue: string,
|
|
value: object,
|
|
) => JSON.stringify(value).toLowerCase().includes(searchValue.toLowerCase()),
|
|
searializable_object_not_contains: (
|
|
_operator,
|
|
searchValue: string,
|
|
value: object,
|
|
) => !JSON.stringify(value).toLowerCase().includes(searchValue.toLowerCase()),
|
|
string_matches_exactly: (_operator, searchValue: string, value: string) =>
|
|
tryConvertingUnknownToString(value) === searchValue,
|
|
string_not_matches_exactly: (_operator, searchValue: string, value: string) =>
|
|
tryConvertingUnknownToString(value) !== searchValue,
|
|
// See PowerSearchStringSetTerm
|
|
string_set_contains_any_of: (
|
|
_operator,
|
|
searchValue: string[],
|
|
value: string,
|
|
) =>
|
|
searchValue.some((item) =>
|
|
tryConvertingUnknownToString(value)
|
|
?.toLowerCase()
|
|
.includes(item.toLowerCase()),
|
|
),
|
|
string_set_contains_none_of: (
|
|
_operator,
|
|
searchValue: string[],
|
|
value: string,
|
|
) =>
|
|
!searchValue.some((item) =>
|
|
tryConvertingUnknownToString(value)
|
|
?.toLowerCase()
|
|
.includes(item.toLowerCase()),
|
|
),
|
|
int_equals: (_operator, searchValue: number, value: number) =>
|
|
value === searchValue,
|
|
int_greater_than: (_operator, searchValue: number, value: number) =>
|
|
value > searchValue,
|
|
int_greater_or_equal: (_operator, searchValue: number, value: number) =>
|
|
value >= searchValue,
|
|
int_less_than: (_operator, searchValue: number, value: number) =>
|
|
value < searchValue,
|
|
int_less_or_equal: (_operator, searchValue: number, value: number) =>
|
|
value <= searchValue,
|
|
float_equals: (operator, searchValue: number, value: number) => {
|
|
const precision = (operator as FloatOperatorConfig).precision ?? 0.01;
|
|
return value <= searchValue + precision && value >= searchValue - precision;
|
|
},
|
|
float_greater_than: (_operator, searchValue: number, value: number) =>
|
|
value > searchValue,
|
|
float_greater_or_equal: (_operator, searchValue: number, value: number) =>
|
|
value >= searchValue,
|
|
float_less_than: (_operator, searchValue: number, value: number) =>
|
|
value < searchValue,
|
|
float_less_or_equal: (_operator, searchValue: number, value: number) =>
|
|
value <= searchValue,
|
|
enum_is: (_operator, searchValue: string, value: string) =>
|
|
enumPredicateForWhenValueCouldBeAStringifiedNullish(searchValue, value),
|
|
enum_is_nullish_or: (_operator, searchValue: string, value?: string | null) =>
|
|
value == null ||
|
|
enumPredicateForWhenValueCouldBeAStringifiedNullish(searchValue, value),
|
|
enum_is_not: (_operator, searchValue: string, value: string) =>
|
|
!enumPredicateForWhenValueCouldBeAStringifiedNullish(searchValue, value),
|
|
enum_set_is_nullish_or_any_of: (
|
|
_operator,
|
|
searchValue: string[],
|
|
value?: string | null,
|
|
) =>
|
|
value == null ||
|
|
searchValue.some((item) =>
|
|
enumPredicateForWhenValueCouldBeAStringifiedNullish(item, value),
|
|
),
|
|
enum_set_is_any_of: (_operator, searchValue: string[], value: string) =>
|
|
searchValue.some((item) =>
|
|
enumPredicateForWhenValueCouldBeAStringifiedNullish(item, value),
|
|
),
|
|
enum_set_is_none_of: (_operator, searchValue: string[], value: string) =>
|
|
!searchValue.some((item) =>
|
|
enumPredicateForWhenValueCouldBeAStringifiedNullish(item, value),
|
|
),
|
|
is_nullish: (_operator, _searchValue, value) => value == null,
|
|
// See PowerSearchAbsoluteDateTerm
|
|
newer_than_absolute_date: (_operator, searchValue: Date, value: any) => {
|
|
const valueNormalized = dayjs(value);
|
|
return valueNormalized.isAfter(searchValue);
|
|
},
|
|
newer_than_absolute_date_no_time: (
|
|
_operator,
|
|
searchValue: Date,
|
|
value: any,
|
|
) => {
|
|
const valueNormalized = dayjs(value);
|
|
return valueNormalized.isAfter(searchValue);
|
|
},
|
|
older_than_absolute_date: (_operator, searchValue: Date, value: any) => {
|
|
const valueNormalized = dayjs(value);
|
|
return valueNormalized.isBefore(searchValue);
|
|
},
|
|
older_than_absolute_date_no_time: (
|
|
_operator,
|
|
searchValue: Date,
|
|
value: any,
|
|
) => {
|
|
const valueNormalized = dayjs(value);
|
|
return valueNormalized.isBefore(searchValue);
|
|
},
|
|
same_as_absolute_date_no_time: (_operator, searchValue: Date, value: any) => {
|
|
const valueNormalized = dayjs(value);
|
|
return valueNormalized.isSame(searchValue, 'day');
|
|
},
|
|
} satisfies PowerSearchOperatorProcessorConfig;
|