File

src/lib/service/search/search.service.ts

Description

Collection of Search Services See SearchQuery for Query Definition

Index

Methods

Constructor

constructor(system: SystemService, backend: BackendService, config: Config, translate: TranslateService)
Parameters :
Name Type Optional
system SystemService no
backend BackendService no
config Config no
translate TranslateService no

Methods

Public autocomplete
autocomplete(term: string)

Executes a search for fetching suggestions on a given search term.

Parameters :
Name Type Optional Description
term string no

The search term

Returns : any
Public buildQuery
buildQuery(query: any)

Creates a fromated search Query

Parameters :
Name Type Optional
query any no
Returns : SearchQuery

SearchQuery

Public createResultFromResponse
createResultFromResponse(backendResponse: )

Creates a SearchResult from the response sent by the server

Parameters :
Name Optional Description
backendResponse no

Server response

Returns : SearchResult
Public downloadCsvFromQuery
downloadCsvFromQuery(queryJson: object, withUrl: boolean)

Downloads a csv-file of the results of a given search-query

Parameters :
Name Type Optional Description
queryJson object no

The query JSON to be executed

withUrl boolean no

request for the CSV file is sent with Url

Returns : Observable<any>

Observable

Public executeQuery
executeQuery(queryJson: any, aggregateSearch?: boolean, customSize?: number)

Executes a query

Parameters :
Name Type Optional Description
queryJson any no

The query JSON to be executed

aggregateSearch boolean yes

Flag indicating whether or not to execute an aggregation search (no actual results)

customSize number yes

Custom number of results to be fetched, if not set the default size will be applied

Returns : Observable<any>

Observable

fetchResultFieldDefinition
fetchResultFieldDefinition(type: ObjectType | null, contextType: ObjectType | null, mode: )

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 :
Name Type Optional Description
type ObjectType | null no
contextType ObjectType | null no
mode no

The result lists configuration mode:

Returns : any

Observable

filterToElementValue
filterToElementValue(filter: SearchFilter, formElementType: string)

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 :
Name Type Optional Description
filter SearchFilter no

The filter to extract value from

formElementType string no

Form elements type ('STRING', 'DATE', 'CODESYSTEM', ...)

Returns : any

the extracted value

Public getChunkedResult
getChunkedResult(query: SearchQuery, fromIndex: number, chunkSize: number)

Fetches a part of a queries result. This can be used for paging of results or lazy loading large results.

Parameters :
Name Type Optional Description
query SearchQuery no

The query to be executed

fromIndex number no

The index from where to fetch results

chunkSize number no

Size of the chunk to be fetched

Returns : Observable<SearchResult>

Observable

getReferenceTree
getReferenceTree(targetid: string, targettype: string)

Provides the information about all the fields that are referencing to the given target id and target type

Parameters :
Name Type Optional
targetid string no
targettype string no
Returns : Observable<any>

Observable

getResultFieldDefinitionConfig
getResultFieldDefinitionConfig(query: SearchQuery)

Provides the Resul tField Definition Config

Parameters :
Name Type Optional
query SearchQuery no
Returns : { type: any; contextType: any; }

({type: ObjectType; contextType: ObjectType})

Public getSearchFilter
getSearchFilter(targetTypes: ObjectType[], formElementName: string, elementValue: any)

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 :
Name Type Optional
targetTypes ObjectType[] no
formElementName string no
elementValue any no
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)

Retrieve Search results

Parameters :
Name Type Optional
query any no
Returns : Observable<SearchState>
removeResultFieldDefinition
removeResultFieldDefinition(type: ObjectType | null, contextType: ObjectType | null)

Removes a column definition.

Parameters :
Name Type Optional
type ObjectType | null no
contextType ObjectType | null no
Returns : any

Observable

saveResultFieldDefinition
saveResultFieldDefinition(type: ObjectType | null, contextType: ObjectType | null, data: FieldDefinition, common: )

Saves a column definition as result or common result.

Parameters :
Name Type Optional Default value
type ObjectType | null no
contextType ObjectType | null no
data FieldDefinition no
common no false
Returns : any

Observable

Public search
search(query: SearchQuery)

Executes a query.

Parameters :
Name Type Optional Description
query SearchQuery no

The query to be executed

Returns : Observable<SearchResult>

Observable

tableFiltersToElementValue
tableFiltersToElementValue(filters: SearchFilter[], tableElements: )

Creates a table value by extracting the data from the corresponding search filters.

Parameters :
Name Type Optional Description
filters SearchFilter[] no

The filters to extract value from

tableElements no

the form elements of the table

Returns : {}

the table value extracted from all filters

Static toRangeValue
toRangeValue(value: any)

Creates a RangeValue instance from the given value object.

Parameters :
Name Type Optional Description
value any no

The object to be

Returns : RangeValue

RangeValue

search.service

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;
  }
}

results matching ""

    No results matching ""