import { ESearchFilterConstraintType, IContainsFilterValue, IContainsInListFilterValue, IContainsLikeTextFitlerValue, IExactMatchFilterValue, IGreaterThanFilterValue, IInListFilterValue, ILikeTextFilterValue, INotContainsInListFilterValue, INotInListFilterValue, IRangeFilterValue, ISearchFilter, ITemporalDateRangeFilterValue, SearchFilterType } from "bf-types"; import { StringToDate } from "./utils/dates"; import { toJS } from "mobx"; type TInputObject = Record; type ISearchFilterTyped = ISearchFilter & { field_value: T }; type TCheckSearchFilter = ( obj: TInputObject, searchFilter: ISearchFilterTyped ) => boolean; type TFiltersObject = { [P in SearchFilterType]: TCheckSearchFilter

}; function getObjectField(obj: TInputObject, path: string) { const parts = path.split("."); const partCount = parts.length; if (partCount === 1) { return obj[parts[0]]; } let currentField = obj[parts[0]]; for (let i = 1; i < partCount; i++) { currentField = currentField[parts[i]]; if (!currentField) { return undefined; } } return currentField; } const filters: TFiltersObject = { CONTAINS( obj: TInputObject, searchFilter: ISearchFilterTyped ) { const { constraint, value } = searchFilter.field_value as IContainsFilterValue; const objField = getObjectField(obj, searchFilter.field_name); if (!Array.isArray(objField)) { return false; } const itemCount = objField.length; if (constraint) { for (let i = 0; i < itemCount; i++) { if (getObjectField(objField[i], constraint.key) === value) { return true; } } } else { for (let i = 0; i < itemCount; i++) { if (objField[i] === value) { return true; } } } return false; }, CONTAINS_LIKE_TEXT( obj: TInputObject, searchFilter: ISearchFilterTyped ) { const { constraint, value } = searchFilter.field_value as IContainsLikeTextFitlerValue; const objField = getObjectField(obj, searchFilter.field_name); if (!Array.isArray(objField)) { return false; } const regex = new RegExp(value, "i"); const itemCount = objField.length; if (constraint) { for (let i = 0; i < itemCount; i++) { if (regex.test(getObjectField(objField[i], constraint.key))) { return true; } } } else { for (let i = 0; i < itemCount; i++) { if (regex.test(objField[i])) { return true; } } } return false; }, CONTAINS_IN_LIST( obj: TInputObject, searchFilter: ISearchFilterTyped ) { const { constraint, values } = searchFilter.field_value as IContainsInListFilterValue; const objField = getObjectField(obj, searchFilter.field_name); if (!Array.isArray(objField)) { return false; } const itemCount = objField.length; if (constraint) { for (let i = 0; i < itemCount; i++) { if (values.includes(getObjectField(objField[i], constraint.key))) { return true; } } } else { for (let i = 0; i < itemCount; i++) { if (values.includes(objField[i])) { return true; } } } return false; }, EXACT_MATCH( obj: TInputObject, searchFilter: ISearchFilterTyped ) { return ( getObjectField(obj, searchFilter.field_name) === (searchFilter.field_value as IExactMatchFilterValue).value ); }, LIKE_TEXT( obj: TInputObject, searchFilter: ISearchFilterTyped ) { const field_value = searchFilter.field_value as ILikeTextFilterValue; const value = getObjectField(obj, searchFilter.field_name); return new RegExp(field_value.value, "i").test(value); }, TEMPORAL_DATE_RANGE( obj: TInputObject, searchFilter: ISearchFilterTyped ) { const time = new Date(obj[searchFilter.field_name]).getTime(); const value = searchFilter.field_value as ITemporalDateRangeFilterValue; const from = (value.start as string) || StringToDate(value.temporal_constraint.key); const to = (value.end as string) || (value.temporal_constraint.type === ESearchFilterConstraintType.META ? StringToDate(value.temporal_constraint.value) : false); if (!from || !to) { return false; } const fromDate = new Date(from).getTime(); const toDate = new Date(to).getTime(); return fromDate < time && time < toDate; }, IN_LIST( obj: TInputObject, searchFilter: ISearchFilterTyped ) { const fieldValue = searchFilter.field_value as IInListFilterValue; return fieldValue.values.includes( getObjectField(obj, searchFilter.field_name) ); }, NOT_CONTAINS_IN_LIST( obj: TInputObject, searchFilter: ISearchFilterTyped ) { const { constraint, values } = searchFilter.field_value as INotContainsInListFilterValue; const objField = getObjectField(obj, searchFilter.field_name); if (!Array.isArray(objField)) { return false; } const itemCount = objField.length; if (constraint) { for (let i = 0; i < itemCount; i++) { if (values.includes(getObjectField(objField[i], constraint.key))) { return false; } } } else { for (let i = 0; i < itemCount; i++) { if (values.includes(objField[i])) { return false; } } } return true; }, NOT_EQUAL( obj: TInputObject, searchFilter: ISearchFilterTyped ) { return ( getObjectField(obj, searchFilter.field_name) !== (searchFilter.field_value as IExactMatchFilterValue).value ); }, NOT_IN_LIST( obj: TInputObject, searchFilter: ISearchFilterTyped ) { const fieldValue = searchFilter.field_value as INotInListFilterValue; return !fieldValue.values.includes( getObjectField(obj, searchFilter.field_name) ); }, RANGE( obj: TInputObject, searchFilter: ISearchFilterTyped ) { const field_value = searchFilter.field_value as IRangeFilterValue; const value = getObjectField(obj, searchFilter.field_name); if (field_value.start !== null && field_value.end !== null) { return value >= field_value.start && value <= field_value.end; } if (field_value.start !== null) { return value >= field_value.start; } if (field_value.end !== null) { return value <= field_value.end; } return false; }, GREATER_THAN( obj: TInputObject, searchFilter: ISearchFilterTyped ) { const field_value = searchFilter.field_value as IGreaterThanFilterValue; const value = getObjectField(obj, searchFilter.field_name); if (field_value.value === null) { if (!value || value) { return false; } return true; } if (!value || value === null) { return false; } return value > field_value.value; }, GREATER_THAN_OR_EQUAL( obj: TInputObject, searchFilter: ISearchFilterTyped ) { // TODO: Implement this console.error( "SearchFiltersMatch: Greater Than Or Equal has not been implemented yet." ); return false; }, LESS_THAN( obj: TInputObject, searchFilter: ISearchFilterTyped ) { // TODO: Implement this console.error( "SearchFiltersMatch: Greater Than Or Equal has not been implemented yet." ); return false; }, LESS_THAN_OR_EQUAL( obj: TInputObject, searchFilter: ISearchFilterTyped ) { // TODO: Implement this console.error( "SearchFiltersMatch: Less Than has not been implemented yet." ); return false; } }; export function SearchFilterMatches( mobxObj: TInputObject, searchFilter: ISearchFilter ) { const obj = toJS(mobxObj); switch (searchFilter.field_value.type) { case SearchFilterType.CONTAINS: return filters.CONTAINS(obj, searchFilter as ISearchFilterTyped< SearchFilterType.CONTAINS >); case SearchFilterType.CONTAINS_LIKE_TEXT: return filters.CONTAINS_LIKE_TEXT(obj, searchFilter as ISearchFilterTyped< SearchFilterType.CONTAINS_LIKE_TEXT >); case SearchFilterType.CONTAINS_IN_LIST: return filters.CONTAINS_IN_LIST(obj, searchFilter as ISearchFilterTyped< SearchFilterType.CONTAINS_IN_LIST >); case SearchFilterType.EXACT_MATCH: return filters.EXACT_MATCH(obj, searchFilter as ISearchFilterTyped< SearchFilterType.EXACT_MATCH >); case SearchFilterType.LIKE_TEXT: return filters.LIKE_TEXT(obj, searchFilter as ISearchFilterTyped< SearchFilterType.LIKE_TEXT >); case SearchFilterType.TEMPORAL_DATE_RANGE: return filters.TEMPORAL_DATE_RANGE( obj, searchFilter as ISearchFilterTyped ); case SearchFilterType.IN_LIST: return filters.IN_LIST(obj, searchFilter as ISearchFilterTyped< SearchFilterType.IN_LIST >); case SearchFilterType.NOT_CONTAINS_IN_LIST: return filters.NOT_CONTAINS_IN_LIST( obj, searchFilter as ISearchFilterTyped< SearchFilterType.NOT_CONTAINS_IN_LIST > ); case SearchFilterType.NOT_EQUAL: return filters.NOT_EQUAL(obj, searchFilter as ISearchFilterTyped< SearchFilterType.NOT_EQUAL >); case SearchFilterType.NOT_IN_LIST: return filters.NOT_IN_LIST(obj, searchFilter as ISearchFilterTyped< SearchFilterType.NOT_IN_LIST >); case SearchFilterType.RANGE: return filters.RANGE(obj, searchFilter as ISearchFilterTyped< SearchFilterType.RANGE >); case SearchFilterType.GREATER_THAN: return filters.GREATER_THAN(obj, searchFilter as ISearchFilterTyped< SearchFilterType.GREATER_THAN >); case SearchFilterType.GREATER_THAN_OR_EQUAL: return filters.GREATER_THAN_OR_EQUAL( obj, searchFilter as ISearchFilterTyped< SearchFilterType.GREATER_THAN_OR_EQUAL > ); case SearchFilterType.LESS_THAN: return filters.LESS_THAN(obj, searchFilter as ISearchFilterTyped< SearchFilterType.LESS_THAN >); case SearchFilterType.LESS_THAN_OR_EQUAL: return filters.LESS_THAN_OR_EQUAL(obj, searchFilter as ISearchFilterTyped< SearchFilterType.LESS_THAN_OR_EQUAL >); } } export function SearchFilterCheck(searchFilters: ISearchFilter[]) { const filters = toJS(searchFilters); const searchFiltersMapped = filters.map(CheckSearchFilter); return { test: (item: Record) => TestResult(searchFilters, searchFiltersMapped, item) }; } function CheckSearchFilter(sf: ISearchFilter): ISearchFilter { if (sf.field_value.type === SearchFilterType.TEMPORAL_DATE_RANGE) { const value = sf.field_value; if (!value.start) { sf.field_value.start = StringToDate(value.temporal_constraint.key); } if ( !value.end && value.temporal_constraint.type === ESearchFilterConstraintType.META ) { sf.field_value.end = StringToDate(value.temporal_constraint.value); } } return sf; } function TestResult( searchFilters: ISearchFilter[], searchFiltersMapped: ISearchFilter[], item: Record ) { return searchFilters.length === 0 ? false : searchFiltersMapped.every(filter => SearchFilterMatches(item, filter)); } export function DataMatchesFilters(data: any, searchFilters: ISearchFilter[]) { const filterCount = searchFilters.length; for (let i = 0; i < filterCount; i++) { if (!SearchFilterMatches(data, searchFilters[i])) { return false; } } return true; }