src/lib/service/search/search.service.ts
Collection of Search Services See SearchQuery for Query Definition
Methods |
|
constructor(system: SystemService, backend: BackendService, config: Config, translate: TranslateService)
|
|||||||||||||||
Defined in src/lib/service/search/search.service.ts:29
|
|||||||||||||||
Parameters :
|
Public autocomplete | ||||||||
autocomplete(term: string)
|
||||||||
Defined in src/lib/service/search/search.service.ts:317
|
||||||||
Executes a search for fetching suggestions on a given search term.
Parameters :
Returns :
any
|
Public buildQuery | ||||||
buildQuery(query: any)
|
||||||
Defined in src/lib/service/search/search.service.ts:67
|
||||||
Creates a fromated search Query
Parameters :
Returns :
SearchQuery
SearchQuery |
Public createResultFromResponse | ||||||
createResultFromResponse(backendResponse: )
|
||||||
Defined in src/lib/service/search/search.service.ts:494
|
||||||
Creates a SearchResult from the response sent by the server
Parameters :
Returns :
SearchResult
|
Public downloadCsvFromQuery | ||||||||||||
downloadCsvFromQuery(queryJson: object, withUrl: boolean)
|
||||||||||||
Defined in src/lib/service/search/search.service.ts:288
|
||||||||||||
Downloads a csv-file of the results of a given search-query
Parameters :
Returns :
Observable<any>
Observable |
Public executeQuery | ||||||||||||||||
executeQuery(queryJson: any, aggregateSearch?: boolean, customSize?: number)
|
||||||||||||||||
Defined in src/lib/service/search/search.service.ts:254
|
||||||||||||||||
Executes a query
Parameters :
Returns :
Observable<any>
Observable |
fetchResultFieldDefinition | ||||||||||||||||
fetchResultFieldDefinition(type: ObjectType | null, contextType: ObjectType | null, mode: )
|
||||||||||||||||
Defined in src/lib/service/search/search.service.ts:571
|
||||||||||||||||
Fetches the result field definition for a given object type USER: (default) the result list configuration saved by the current user DEFAULT: the default result list configuration (also returned when there is no user configuration) ALL: all available result fields for the given type (and context)
Parameters :
Returns :
any
Observable |
filterToElementValue | ||||||||||||
filterToElementValue(filter: SearchFilter, formElementType: string)
|
||||||||||||
Defined in src/lib/service/search/search.service.ts:664
|
||||||||||||
Gets the value to be added to a form element by extracting it from a search filter. The kind of value differs based on the form elements type.
Parameters :
Returns :
any
the extracted value |
Public getChunkedResult | ||||||||||||||||
getChunkedResult(query: SearchQuery, fromIndex: number, chunkSize: number)
|
||||||||||||||||
Defined in src/lib/service/search/search.service.ts:230
|
||||||||||||||||
Fetches a part of a queries result. This can be used for paging of results or lazy loading large results.
Parameters :
Returns :
Observable<SearchResult>
Observable |
getReferenceTree |
getReferenceTree(targetid: string, targettype: string)
|
Defined in src/lib/service/search/search.service.ts:538
|
Provides the information about all the fields that are referencing to the given target id and target type
Returns :
Observable<any>
Observable |
getResultFieldDefinitionConfig | ||||||
getResultFieldDefinitionConfig(query: SearchQuery)
|
||||||
Defined in src/lib/service/search/search.service.ts:522
|
||||||
Provides the Resul tField Definition Config
Parameters :
Returns :
{ type: any; contextType: any; }
({type: ObjectType; contextType: ObjectType}) |
Public getSearchFilter | ||||||||||||
getSearchFilter(targetTypes: ObjectType[], formElementName: string, elementValue: any)
|
||||||||||||
Defined in src/lib/service/search/search.service.ts:334
|
||||||||||||
Use this method to create a SearchFilter for a form element. Based on the target types it'll create the right kind of filter that you then can add to a SearchQuery
Parameters :
Returns :
SearchFilter[]
SearchFilter[] An array of search filters most of the time containing only one entry, but for table elements this could be more |
Public getSearchState | ||||||
getSearchState(query: any)
|
||||||
Defined in src/lib/service/search/search.service.ts:171
|
||||||
Retrieve Search results
Parameters :
Returns :
Observable<SearchState>
|
removeResultFieldDefinition | |||||||||
removeResultFieldDefinition(type: ObjectType | null, contextType: ObjectType | null)
|
|||||||||
Defined in src/lib/service/search/search.service.ts:642
|
|||||||||
Removes a column definition.
Parameters :
Returns :
any
Observable |
saveResultFieldDefinition | ||||||||||||||||||||
saveResultFieldDefinition(type: ObjectType | null, contextType: ObjectType | null, data: FieldDefinition, common: )
|
||||||||||||||||||||
Defined in src/lib/service/search/search.service.ts:616
|
||||||||||||||||||||
Saves a column definition as result or common result.
Parameters :
Returns :
any
Observable |
Public search | ||||||||
search(query: SearchQuery)
|
||||||||
Defined in src/lib/service/search/search.service.ts:162
|
||||||||
Executes a query.
Parameters :
Returns :
Observable<SearchResult>
Observable |
tableFiltersToElementValue | ||||||||||||
tableFiltersToElementValue(filters: SearchFilter[], tableElements: )
|
||||||||||||
Defined in src/lib/service/search/search.service.ts:698
|
||||||||||||
Creates a table value by extracting the data from the corresponding search filters.
Parameters :
Returns :
{}
the table value extracted from all filters |
Static toRangeValue | ||||||||
toRangeValue(value: any)
|
||||||||
Defined in src/lib/service/search/search.service.ts:43
|
||||||||
Creates a RangeValue instance from the given value object.
Parameters :
Returns :
RangeValue
RangeValue |
import { HttpResponse } from '@angular/common/http';
import {Injectable} from '@angular/core';
import * as FileSaver from 'file-saver';
import {Observable} from 'rxjs';
import {map, tap} from 'rxjs/operators';
import {ObjectType} from '../../model/object-type.model';
import {RangeValue} from '../../model/range-value.model';
import {Utils} from '../../util/utils';
import {BackendService} from '../backend/backend.service';
import {Config} from '../config/config.service';
import {SystemService} from '../system/system.service';
import {SearchFilterType} from './search-filter-type.interface';
import {
FieldDefinition,
SearchFilter,
SearchQuery,
SearchResult,
SearchState
} from './search-query.model';
import {TranslateService} from "@ngx-translate/core";
/**
* Collection of Search Services
* See {@link SearchQuery} for Query Definition
*/
@Injectable({
providedIn: 'root'
})
export class SearchService {
constructor(
private system: SystemService,
private backend: BackendService,
private config: Config,
private translate: TranslateService
) {}
/**
* Creates a RangeValue instance from the given value object.
*
* @param value The object to be
* @returns RangeValue
*/
public static toRangeValue(value: any): RangeValue {
if (value) {
if (value instanceof RangeValue) {
return value;
} else if (
value.hasOwnProperty('operator') &&
value.hasOwnProperty('firstValue')
) {
return new RangeValue(
value.operator,
value.firstValue,
value.secondValue
);
}
}
return value;
}
/**
* Creates a fromated search Query
*
* @param query
* @returns SearchQuery
*/
public buildQuery(query: any): SearchQuery {
let q: SearchQuery;
if (query instanceof SearchQuery) {
q = query;
} else {
let qObj;
/**
* if we got a string, we'll assume that it's a JSON
*/
if (typeof query === 'string') {
qObj = JSON.parse(query);
} else {
qObj = query;
}
q = new SearchQuery();
q.term = qObj.term;
if (qObj.filters) {
Object.keys(qObj.filters).forEach(k => {
let operator = qObj.filters[k].o;
if(qObj.filters[k].useNot){
operator = qObj.filters[k].actualOperator;
}
q.filters.push(
new SearchFilter(
k,
operator,
qObj.filters[k].v1,
qObj.filters[k].v2,
qObj.filters[k].transformDate,
qObj.filters[k].usenot,
qObj.filters[k].actualOperator,
)
);
});
}
if (qObj.types) {
qObj.types.forEach(t => {
if (this.system.getObjectType(t)) {
q.types.push(this.system.getObjectType(t));
} else {
q.types.push(t);
}
});
}
if (qObj.contextfoldertypes) {
qObj.contextfoldertypes.forEach(t => {
if (t === 'sysroot') {
// 'sysroot' is not a real object type, so we have to create a fake type.
const sysrootFakeObjectType = new ObjectType({
id: 'a',
folder: true,
name: 'sysroot',
qname: 'sysroot',
label: 'sysroot',
iscontextfolder: true,
abstract: false,
elements: [],
description: '',
shareable: false,
minfiles: 0,
maxfiles: 1
});
q.contextFolderTypes.push(sysrootFakeObjectType);
} else {
q.contextFolderTypes.push(this.system.getObjectType(t));
}
});
}
q.fields = qObj.fields;
if (qObj.options) {
q.suggest = qObj.options.suggest || false;
q.expertMode = qObj.options.expertmode || false;
q.scope = qObj.options.scope;
// "||" does not work for recognizing "false" with priority over "default true"
q.resolveReference = (qObj.options.resolvereference !== null) ? qObj.options.resolvereference :
( qObj.options.resolveReference !== null ? qObj.options.resolveReference : true);
// withContext was old and "wrong" name of parameter
q.withContext = qObj.options.withcontext || qObj.options.withContext || false;
q.searchMode =
qObj.options.searchmode || SearchQuery.DEFAULT_SEARCH_MODE;
}
}
return q;
}
/**
* Executes a query.
*
* @param query The query to be executed
* @returns Observable<SearchResult>
*/
public search(query: SearchQuery): Observable<SearchResult> {
return this.executeQuery(query.getQueryJson()).pipe(
map(res => this.createResultFromResponse(res))
);
}
/**
* Retrieve Search results
*/
public getSearchState(query: any): Observable<SearchState> {
if (query instanceof SearchQuery) {
query = query.getQueryJson();
}
delete query.name;
return this.executeQuery(query, true).pipe(
map((res: any) => {
const state = new SearchState();
state.count = res.hits.total;
Object.keys(SearchQuery.BASE_PARAMS).forEach(k => {
const baseParam = SearchQuery.BASE_PARAMS[k];
const aggregations = res.aggregations[baseParam]
? res.aggregations[baseParam].buckets
: [];
let aggs = {};
aggregations.forEach(aggregation => {
let {key, doc_count, contextfoldertype} = aggregation;
if (baseParam === 'filesize') {
doc_count = aggregation['filter#documents'].doc_count;
}
state.addAggregations(baseParam, key, doc_count, aggregation);
/**
* type aggregations may also contain buckets for their context types
*/
if (
baseParam === SearchQuery.BASE_PARAMS.TYPE &&
contextfoldertype
) {
contextfoldertype.buckets.forEach(ctxAgg => {
aggs[ctxAgg.key] = aggs[ctxAgg.key] ? aggs[ctxAgg.key] + ctxAgg.doc_count : ctxAgg.doc_count;
});
}
});
if (baseParam === SearchQuery.BASE_PARAMS.TYPE) {
Object.keys(aggs).forEach((key) => {
state.addAggregations(
'contexttype',
key,
aggs[key]
);
});
}
});
return state;
})
);
}
/**
* Fetches a part of a queries result. This can be used for paging of results
* or lazy loading large results.
*
* @param query The query to be executed
* @param fromIndex The index from where to fetch results
* @param chunkSize Size of the chunk to be fetched
* @returns Observable<SearchResult>
*/
public getChunkedResult(
query: SearchQuery,
fromIndex: number,
chunkSize: number
): Observable<SearchResult> {
const q = query.getQueryJson();
q.from = fromIndex;
const queryUri = `search!chunk:${chunkSize}!${JSON.stringify(q)}`;
return this.backend.getViaTempCache(queryUri, () =>
this.executeQuery(q, false, chunkSize).pipe(
map(res => this.createResultFromResponse(res))
)
);
}
/**
* Executes a query
*
* @param queryJson The query JSON to be executed
* @param aggregateSearch Flag indicating whether or not to execute an aggregation search (no actual results)
* @param customSize Custom number of results to be fetched, if not set the default size will be applied
* @returns Observable<any>
*/
public executeQuery(
queryJson: any,
aggregateSearch?: boolean,
customSize?: number
): Observable<any> {
let uri: string;
let query = queryJson;
if (aggregateSearch) {
uri = `/aggregate`;
} else {
const size = customSize || this.config.getRaw('search.limit');
uri = `/?size=${size}`;
}
query.options = {...query.options, tracktotalhits: this.config.getRaw('search.trackTotalHits') || false, timezone: Utils.getTimezoneOffset()};
query = this.removeTemporaryFilters(query);
query = this.refineFilters(query);
return this.backend.getViaTempCache(uri + JSON.stringify(query),
() => this.backend.post(uri, query, this.backend.getSearchBase()).pipe(map(res => {
res?.config?.elements?.forEach(el => {
if (el.baseparameter) {
el.label = this.translate.instant('eo.global.baseparam.' + el.name);
}
});
return res;
})));
}
/**
* Downloads a csv-file of the results of a given search-query
*
* @param queryJson The query JSON to be executed
* @param withUrl request for the CSV file is sent with Url
* @returns Observable<any>
*/
public downloadCsvFromQuery(queryJson: object, withUrl: boolean): Observable<any> {
let uri = withUrl ? `/export?withurl=${withUrl}` : '/export';
return this.backend
.post(uri, queryJson, this.backend.getSearchBase(), {
observe: 'response',
responseType: 'blob'
})
.pipe(
tap(res => {
const blob = new Blob([res.body], {
type: 'text/csv;charset=utf-8'
});
FileSaver.saveAs(blob, this.getFileNameFromHttpResponse(res), {autoBom: true});
})
);
}
private getFileNameFromHttpResponse(res: HttpResponse<any>) {
const contentDispositionHeader =
res.headers.get('Content-Disposition') || '';
return contentDispositionHeader.split('filename=')[1];
}
/**
* Executes a search for fetching suggestions on a given search term.
*
* @param term The search term
* @return
*/
public autocomplete(term: string) {
return this.backend.getJson(
`/autocomplete?prefix=${encodeURIComponent(term)}`,
this.backend.getSearchBase()
);
}
/**
* Use this method to create a SearchFilter for a form element. Based on the target types
* it'll create the right kind of filter that you then can add to a SearchQuery
*
* @param targetTypes
* @param formElementName
* @param elementValue
* @returns SearchFilter[] An array of search filters most of the time containing only one entry, but
* for table elements this could be more
*/
public getSearchFilter(
targetTypes: ObjectType[],
formElementName: string,
elementValue: any
): SearchFilter[] {
const filter: SearchFilter[] = [];
const filterType: SearchFilterType = this.identifySearchFilter(
targetTypes,
formElementName,
elementValue?.operator
);
const formElement = targetTypes[0]?.elements.find(el => el.qname === formElementName);
if (elementValue === null) {
filter.push(
new SearchFilter(formElementName, SearchFilter.OPERATOR.EQUAL, null)
);
} else if (formElement && formElement.classification === 'path') {
filter.push(
new SearchFilter(
formElementName,
SearchFilter.OPERATOR.MATCH,
elementValue
)
);
} else if (elementValue instanceof RangeValue) {
/**
* create filter for elements that support ranges like DATETIME and NUMBER
*/
if (
elementValue.firstValue !== null &&
elementValue.firstValue !== undefined
) {
filter.push(
new SearchFilter(
formElementName,
elementValue.operator,
elementValue.firstValue,
elementValue.secondValue,
filterType.datetime ? {time: filterType.withtime} : undefined,
elementValue?.useNot,
elementValue?.actualOperator
)
);
}
} else if (filterType.multiselect && Array.isArray(elementValue) && elementValue.length &&!filterType.table) {
/**
* create filter for multiselect elements like CODESYSTEM and ORGANIZATION
*/
filter.push(
new SearchFilter(
formElementName,
SearchFilter.OPERATOR.IN,
elementValue
)
);
} else if (filterType.table) {
/**
* create filter(s) for table elements
*/
const queryRow = elementValue[0];
Object.keys(queryRow).forEach(function (key) {
if(queryRow[key] !== undefined){
const propertyName = `${formElementName}.${key}`;
if (queryRow[key] instanceof RangeValue) {
filter.push(
new SearchFilter(
propertyName,
queryRow[key].operator,
queryRow[key].firstValue,
queryRow[key].secondValue
)
);
} else {
filter.push(
new SearchFilter(
propertyName,
Array.isArray(queryRow[key]) ? SearchFilter.OPERATOR.IN : SearchFilter.OPERATOR.EQUAL,
queryRow[key]
)
);
}
}
});
} else {
/**
* default will use equality operator
*/
filter.push(
new SearchFilter(
formElementName,
elementValue?.operator ? elementValue?.operator : Array.isArray(elementValue) ? SearchFilter.OPERATOR.IN : SearchFilter.OPERATOR.EQUAL,
elementValue?.value || elementValue,
null,
null,
elementValue?.useNot,
elementValue?.actualOperator || null
)
);
}
return filter;
}
/**
* identifies form elements that are enabled to contain multiple values
* try to find the form element inside of the provided object types to get its type
*
* @param ObjectType[] targetTypes
* @param string formElementName
* @param string operator
* @return SearchFilterType
*/
private identifySearchFilter(
targetTypes: ObjectType[],
formElementName: string,
operator: string
): SearchFilterType {
let i = 0;
let match;
while (!match && i < targetTypes.length) {
match = targetTypes[i].elements.find(
element => element.qname === formElementName
);
i++;
}
return {
multiselect: this.isMultiselectSearchFilter(match),
table: match && match.type === 'TABLE',
datetime: match && match.type === 'DATETIME',
withtime: match && match.withtime && operator !== 'eq'
};
}
/**
* identifies form elements that are enabled to contain multiple values
*
* @param formElement
* @return boolean
*/
private isMultiselectSearchFilter(formElement: any): boolean {
return (
formElement &&
(formElement.type === 'ORGANIZATION' ||
formElement.type === 'CODESYSTEM' ||
// ...
(formElement.type === 'STRING' &&
(formElement.classification === 'selector' || formElement?.autocompleteurl?.trim().length > 0)) ||
// multiselect reference fields
(formElement.type === 'STRING' && formElement.multiselect))
);
}
/**
* Creates a SearchResult from the response sent by the server
*
* @param backendResponse Server response
* @returnThe SearchResult SearchResult object created from the server response
*/
public createResultFromResponse(backendResponse): SearchResult {
const result = new SearchResult();
result.suggests = backendResponse.suggest;
result.fields = backendResponse.config;
/**
* use hits _id field as id property, because otherwise empty result hits would not contain an id
*/
const hits = backendResponse.hits.hits.map(h => ({
...h._source,
id: h._id,
...(h.snippets && {snippets: h.snippets}),
// having requested a resolved reference (options) we also pass `_reference` in the result item
...(h.references && {_references: h.references}),
...(h.context && {context: h.context})
}));
result.count = backendResponse.hits.total;
result.hits = hits || [];
return result;
}
/**
* Provides the Resul tField Definition Config
*
* @param SearchQuery query
* @return ({type: ObjectType; contextType: ObjectType})
*/
getResultFieldDefinitionConfig(query: SearchQuery) {
const type = (query.types || []).length === 1 ? query.types[0] : null;
const contextType =
(query.contextFolderTypes || []).length === 1
? query.contextFolderTypes[0]
: null;
return {type, contextType};
}
/**
* Provides the information about all the fields that are referencing to the given target id and target type
*
* @param targetid
* @param targettype
* @return Observable<any>
*/
getReferenceTree(targetid: string, targettype: string): Observable<any> {
const params = {
targetid: targetid,
targettype: targettype,
resolveobjects: true,
mode: "tree",
resolvereferences: true,
forcedefaultdefinition: true
}
return this.backend.post('/referenceview', params, '/structure').pipe(map(res => res.folder));
}
private resolveConfigTypeNames(
type: ObjectType | null,
contextType: ObjectType | null
) {
return {
typeName: type ? type.qname : 'sysobject',
contextTypeName: type && contextType ? contextType.qname : undefined
};
}
/**
* Fetches the result field definition for a given object type
* USER: (default) the result list configuration saved by the current user
* DEFAULT: the default result list configuration (also returned when there is no user configuration)
* ALL: all available result fields for the given type (and context)
*
* @param ObjectType | null type The object type
* @param ObjectType | null contextType Object type target types allowed context type
* @param mode The result lists configuration mode:
* @return Observable<FieldDefinition>
*/
fetchResultFieldDefinition(
type: ObjectType | null,
contextType: ObjectType | null,
mode
) {
const {typeName, contextTypeName} = this.resolveConfigTypeNames(
type,
contextType
);
const url = Utils.buildUri(`/user/config/result/${typeName}`, {
contexttype: contextTypeName,
mode
});
return this.backend
.getJson(url)
.pipe(
map(
(res: any) => {
res.elements.forEach(el => {
if (el.baseparameter) {
el.label = this.translate.instant('eo.global.baseparam.' + el.name);
}
});
return new FieldDefinition(
res.elements,
res.sortorder,
res.grouporder,
res.pinned,
res.alignmentx,
res.mode
);
}
)
);
}
/**
* Saves a column definition as result or common result.
*
* @param ObjectType | null type The object type
* @param ObjectType | null contextType Object type target types allowed context type
* @param FieldDefinition data The result list configuration to be persisted
* @param boolean common Flag enables to set config as default / common result
* @return Observable<any>
*/
saveResultFieldDefinition(
type: ObjectType | null,
contextType: ObjectType | null,
data: FieldDefinition,
common = false
) {
const baseUri = common
? '/user/config/commonresult/'
: '/user/config/result/';
const {typeName, contextTypeName} = this.resolveConfigTypeNames(
type,
contextType
);
const url = Utils.buildUri(baseUri + typeName, {
contexttype: contextTypeName
});
return this.backend.post(url, data);
}
/**
* Removes a column definition.
*
* @param ObjectType | null type The object type
* @param ObjectType | null contextType Object type target types allowed context type
* @return Observable<any>
*/
removeResultFieldDefinition(
type: ObjectType | null,
contextType: ObjectType | null
) {
const {typeName, contextTypeName} = this.resolveConfigTypeNames(
type,
contextType
);
const url = Utils.buildUri(`/user/config/result/${typeName}`, {
contexttype: contextTypeName
});
return this.backend.del(url);
}
/**
* Gets the value to be added to a form element by extracting it from a
* search filter. The kind of value differs based on the form elements type.
*
* @param filter The filter to extract value from
* @param formElementType Form elements type ('STRING', 'DATE', 'CODESYSTEM', ...)
* @returns the extracted value
*/
filterToElementValue(filter: SearchFilter, formElementType: string) {
let value = undefined;
if (filter) {
// types that support ranges
if (filter?.firstValue) {
if (formElementType === 'NUMBER' || formElementType === 'DATETIME') {
value = new RangeValue(
filter.operator,
filter.firstValue,
filter.secondValue
);
} else if (formElementType === 'CODESYSTEM' || (formElementType === 'STRING' && filter?.actualOperator)) {
value = {
value: filter.firstValue,
operator: filter.operator,
actualOperator: filter.actualOperator,
useNot: filter.useNot,
};
} else {
value = filter.firstValue;
}
}
}
return value;
}
/**
* Creates a table value by extracting the data from the corresponding
* search filters.
*
* @param filters The filters to extract value from
* @param tableElements the form elements of the table
* @returns the table value extracted from all filters
*/
tableFiltersToElementValue(filters: SearchFilter[], tableElements){
let value = {};
filters.forEach(f => {
let key = tableElements.find(e => e.qname === f.property).name;
let type = tableElements.find(e => e.qname === f.property).type;
value[key] = this.filterToElementValue(f, type);
});
return [value];
}
/**
* Removes temporary filters from the search query.
* Now, specifically, it removes the 'actualOperator' property from each filter in the query.
*
* @param {SearchQuery} query - The search query object containing filters.
* @returns {SearchQuery} - The modified search query with temporary filters removed.
*/
private removeTemporaryFilters(query: SearchQuery): SearchQuery {
const searchQuery = query;
if (searchQuery.filters) {
for (const key in searchQuery.filters) {
if (searchQuery.filters[key]) {
if (searchQuery.filters[key]['v1']?.hasOwnProperty("actualOperator")) {
delete searchQuery.filters[key]['v1'].actualOperator;
} else if (searchQuery.filters[key].hasOwnProperty("actualOperator")) {
delete searchQuery.filters[key].actualOperator;
}
}
}
}
return searchQuery;
}
/**
* Refines the filters in the given search query.
* If a filter's v1 property is an object containing useNot, operator, and an array value,
* it updates the filter to use these properties.
*
* @param query The search query to refine.
* @returns The refined search query.
*/
private refineFilters(query: SearchQuery): SearchQuery {
const updatedQuery = {...query};
if (updatedQuery.filters) {
Object.entries(updatedQuery.filters).forEach(([key, filter]) => {
const v1 = (filter as any).v1;
if (
v1 &&
typeof v1 === 'object' &&
'useNot' in v1 &&
'operator' in v1 &&
Array.isArray(v1.value)
) {
updatedQuery.filters[key] = {
o: v1.operator,
v1: v1.value,
useNot: v1.useNot,
};
}
});
}
return updatedQuery as SearchQuery;
}
}