File

src/lib/service/search/search-query.model.ts

Index

Properties
Methods

Constructor

constructor(property: string, operator: string, firstValue: any, secondValue?: any, transformDate?: literal type, useNot?: boolean, actualOperator?: string)

Constructor for creating a new SearchFilter.

Parameters :
Name Type Optional Description
property string no

The qualified name of the field this filter should apply to.

operator string no

Operator indicating how to handle the filters value(s). See SearchFilter.OPERATOR for available operators.

firstValue any no

The filters value

secondValue any yes

Optional second value for filters that for example define ranges of values

transformDate literal type yes

Flag to transform date hours and minutes based on operator

useNot boolean yes
actualOperator string yes

Properties

Public Optional actualOperator
actualOperator: string
Type : string
Public firstValue
firstValue: any
Type : any
The filters value
Public operator
operator: string
Type : string
Operator indicating how to handle the filters value(s). See SearchFilter.OPERATOR for available operators.
Static OPERATOR
OPERATOR: object
Type : object
Default value : { /** equal */ EQUAL: 'eq', /** match (for string with classification "path6666") */ MATCH: 'match', /** match at least one of the provided values (value has to be an array) */ IN: 'in', /** greater than */ GREATER_THAN: 'gt', /** greater than or equal */ GREATER_OR_EQUAL: 'gte', // LESS_THAN: 'lt', // less than LESS_OR_EQUAL: 'lte', // less than or equal INTERVAL: 'gtlt', // interval INTERVAL_INCLUDE_BOTH: 'gtelte', // interval include left and right INTERVAL_INCLUDE_TO: 'gtlte', // interval include right INTERVAL_INCLUDE_FROM: 'gtelt', // interval include left RANGE: 'rg', // aggegation ranges MORE: 'mr', USE_NOT: 'usenot' }

available operators for a search filter

Public property
property: string
Type : string
The qualified name of the field this filter should apply to.
Public Optional secondValue
secondValue: any
Type : any
Optional second value for filters that for example define ranges of values
Public Optional transformDate
transformDate: literal type
Type : literal type
Flag to transform date hours and minutes based on operator
Public Optional useNot
useNot: boolean
Type : boolean

Methods

formatDate
formatDate(value: any)
Parameters :
Name Type Optional
value any no
Returns : any
toDateTimeQuery
toDateTimeQuery()
Returns : { usenot: boolean; actualOperator: string; v2: any; o: string; v1: any; } | { v2: any; o: string;...
toQuery
toQuery()
Returns : { usenot: boolean; actualOperator: string; v2: any; o: string; v1: any; }
import {ObjectType} from '../../model/object-type.model';
import {DatePipe} from '@angular/common';

//export type QueryScope = 'indexdate' | 'content' | 'all';

export enum QueryScope {
  ALL = 'all',
  CONTENT = 'content',
  INDEX_DATA = 'indexdata'
}

/**
 * The SearchQuery Class
 *
 * See {@link SearchService} for Usage
 */
export class SearchQuery {
  /**
   * Base Parameters for a Search Query
   */
  public static BASE_PARAMS = {
    TYPE: 'type',
    CREATOR: 'creator',
    CREATED: 'created',
    MODIFIER: 'modifier',
    MODIFIED: 'modified',
    FILESIZE: 'filesize',
    FILENAME: 'filename',
    MIMETYPEGROUP: 'mimetypegroup',
    FOLDER: 'folder'
  };

  /**
   * Time period buckets property names
   */
  public static TIME_PERIOD_BUCKETS = {
    TODAY: 'today',
    YESTERDAY: 'yesterday',
    THIS_WEEK: 'thisweek',
    LAST_WEEK: 'lastweek',
    THIS_MONTH: 'thismonth',
    LAST_MONTH: 'lastmonth',
    THIS_YEAR: 'thisyear',
    LAST_YEAR: 'lastyear'
  };

  /**
 * Relative date buckets property names
 */
  public static RELATIVE_DATE_BUCKETS = {
    TODAY: 'today',
    YESTERDAY: 'yesterday',
    TOMORROW: 'tomorrow',
    THIS_WEEK: 'thisweek',
    LAST_WEEK: 'lastweek',
    NEXT_WEEK: 'nextweek',
    THIS_MONTH: 'thismonth',
    LAST_MONTH: 'lastmonth',
    NEXT_MONTH: 'nextmonth',
    THIS_QUARTER: 'thisquarter',
    LAST_QUARTER: 'lastquarter',
    NEXT_QUARTER: 'nextquarter',
    THIS_YEAR: 'thisyear',
    LAST_YEAR: 'lastyear',
    NEXT_YEAR: 'nextyear'
  };


  /**
   * mimetype buckets property names
   */
  public static MIMETYPEGROUP_BUCKETS = {
    PDF: 'pdf',
    MAIL: 'mail',
    WORD: 'word',
    IMAGE: 'image',
    OCTET_STREAM: 'octet-stream',
    TXT: 'txt',
    EXCEL: 'excel',
    POWERPOINT: 'powerpoint',
    VIDEO: 'video',
    HTML: 'html',
    AUDIO: 'audio',
    ZIP: 'zip'
  };

  /**
   * filesize buckets property names
   */
  public static FILE_SIZE_BUCKETS = {
    NONE: 'none',
    LT1MB: 'lt1MB',
    LT10MB: 'lt10MB',
    LT100MB: 'lt100MB',
    GT100MB: 'gt100MB'
  };

  // list of things that may have changed the current query
  public static UPDATE_CAUSE = {
    TOGGLED_TYPE: 'cause.type.toggled',
    TOGGLED_EXPERTMODE: 'cause.expertmode.toggled',
    TERM_SET: 'cause.term.set',
    TYPES_SET: 'cause.types.set',
    CONTEXT_FOLDER_SET: 'cause.contextfolder.set',
    TOGGLED_FILTER: 'cause.filter.toggled',
    ADDED_FILTER: 'cause.filter.added',
    REMOVED_FILTER: 'cause.filter.removed',
  };
  /**
   * Detault Search mode Index
   */
  public static DEFAULT_SEARCH_MODE = 'idxs';
  /**
   * Search mode for Full test Search
   */
  public static FULLTEXT_SEARCH_MODE = 'fts';
  /**
   * fulltext search term
   */
  public term: string;
  /**
   * Array of fields to request from the search service, leaving this blank will return the fields specified in the result field configuration
   */
  public fields: string[];
  /**
   * List of object types to be fetched by the query. Leaving this blank will return results from any type
   */
  public types: ObjectType[] = [];
  /**
   * List of context folder types
   */
  public contextFolderTypes: ObjectType[] = [];
  /**
   * List of filters to define the query
   */
  public filters: SearchFilter[] = [];
  /**
   * Whether or not to retrieve suggestions
   */
  public suggest: boolean;
  /**
   * Whether or not the term is an expert mode query
   */
  public expertMode = false;
  /**
   * The scope of the query
   */
  public scope: QueryScope;
  /**
   * The search mode to be used (SearchQuery.DEFAULT_SEARCH_MODE or SearchQuery.FULLTEXT_SEARCH_MODE)
   */
  public searchMode: string = SearchQuery.DEFAULT_SEARCH_MODE;
  /**
   * Additional options to be applied to the query
   */
  public sortOptions: SortOption[] = [];
  /**
   * Resolve references with their title instead of their actual value
   */
  public resolveReference = true;
  /**
   * The state of the current query
   */
  public state: SearchState = new SearchState();

  public highlighting: boolean = false;
  public withContext: boolean = false;

  public name?: string;

  // Indicator what caused the update of a SearchQuery object.
  // Usefull for components that subscribed to a query, and need to
  // track the kind of changes made
  __updateCause: string;

  constructor() {}

  /**
   * Adds a new target type to the query
   *
   * @param type Object type to be added
   */
  public addType(type: ObjectType) {
    if (!this.types.find(t => t.id === type.id)) {
      this.types.push(type);
    }
  }

  /**
   * Removes a type from the target types list
   *
   * @param type The object type to be removed
   */
  public removeType(type: ObjectType) {
    this.types = this.types.filter(t => t.id !== type.id);
  }

  /**
   * Adds or removes the given type based on the current settings
   *
   * @param type The object type to be toggled
   */
  public toggleType(type: ObjectType) {
    if (this.types.find(t => t.id === type.id)) {
      this.removeType(type);
    } else {
      this.types.push(type);
    }
  }

  /**
   * Adds a new filter to the query. If there already is a filter set for the given property, it will be overridden.
   *
   * @param filter The filter to be added
   */
  public addFilter(filter: SearchFilter) {
    if (!filter) {
      return;
    }
    if (!this.getFilter(filter.property)) {
      this.filters.push(filter);
    } else {
      // if there already is a filter set for the target property
      // we'll just override it with the new value
      this.removeFilter(filter.property);
      this.filters.push(filter);
    }
  }

  /**
   * Removes a filter from the query.
   *
   * @param filterPropertyName The filter (its property name) to be removed
   */
  public removeFilter(filterPropertyName: string) {
    this.filters = this.filters.filter(f => f.property !== filterPropertyName);
  }

  /**
   * Adds or removes the given filter based on the current settings
   *
   * @param filter The filter to be toggled
   */
  public toggleFilter(filter: SearchFilter, override?: boolean) {
    if (this.getFilter(filter.property)) {
      this.removeFilter(filter.property);
      if (override) {
        this.filters.push(filter);
      }
    } else {
      this.filters.push(filter);
    }
  }

  /**
   * Retrieves a filter by its property name.
   *
   * @param propertyName The filters property name (qname of form element)
   * @returns Search Filter Object
   */
  public getFilter(propertyName: string): SearchFilter {
    return this.filters.find(f => f.property === propertyName);
  }

  public getTableFilters(propertyName: string): SearchFilter[] {
    return this.filters.filter(f => f.property.startsWith(propertyName + '.'));
  }

  /**
   * Adding new Sort Options
   * Sort Options are only added if they are not already present
   *
   * @param name
   * @param order
   * @param missing
   */
  public addSortOption(name: string, order: string, missing?: string) {
    if (!this.sortOptions.find(s => s.field === name)) {
      this.sortOptions.push(new SortOption(name, order, missing));
    }
  }

  /**
   *
   * @param name
   */
  public removeSortOption(name: string) {
    this.sortOptions = this.sortOptions.filter(s => s.field !== name);
  }

  /**
   * Generate a query JSON from the current SearchQuery that can be send to the search service.
   *
   * @param resolveReference in case of csv export the resolving of references is set by configuration
   */
  public getQueryJson(resolveReference?: boolean) {
    // Convert the current query object to a query JSON that can be consumed by the search service backend.
    // To keep it short, we'll only add properties that are set and differ from default values
    const queryJson: any = {};

    // add type filters
    if (this.types.length > 0) {
      queryJson.types = this.types.map(t =>
        typeof t === 'string' ? t : t.name
      );
    }

    // add context folder type filters
    if (this.contextFolderTypes.length > 0) {
      queryJson.contextfoldertypes = this.contextFolderTypes.map(t => t.name);
    }

    // filters
    if (this.filters.length > 0) {
      queryJson.filters = {};
      this.filters.forEach(f => {
        queryJson.filters[f.property] = f.toQuery();
      });
    }
    if (this.term) {
      queryJson.term = this.term;
    }
    // search fields
    if (this.fields) {
      queryJson.fields = this.fields;
    }
    if (this.name) {
      queryJson.name = this.name;
    }
    // add options when they differ from the default values
    if (this.applyOptions()) {
      queryJson.options = {};
      queryJson.options.scope = this.scope;
      if (this.suggest) {
        queryJson.options.suggest = true;
      }
      if (this.expertMode) {
        queryJson.options.expertmode = true;
      }

      queryJson.options.resolvereference = resolveReference !== undefined ? resolveReference : true;

      if (this.searchMode !== SearchQuery.DEFAULT_SEARCH_MODE) {
        queryJson.options.searchmode = this.searchMode;
      }
      if (this.sortOptions && this.sortOptions.length) {
        queryJson.options.sort = {};
        this.sortOptions.forEach((sortOption: SortOption) => {
          if (sortOption.order) {
            queryJson.options.sort[sortOption.field] = {
              order: sortOption.order
            };
          }
        });
      }

      if (this.highlighting) {
        queryJson.options.highlighting = this.highlighting;
      }
      if (this.withContext) {
        queryJson.options.withcontext = this.withContext;
      }
    }
    return queryJson;
  }

  // determines whether or not the query send to the search service should contain options
  private applyOptions(): boolean {
    return (
      this.expertMode ||
      !!this.scope ||
      this.suggest ||
      this.highlighting ||
      this.resolveReference ||
      this.withContext ||
      this.searchMode !== SearchQuery.DEFAULT_SEARCH_MODE ||
      this.sortOptions.length > 0
    );
  }
}

/**
 * @ignore
 * Creates the Model of Sort Options
 */
export class SortOption {
  constructor(
    public field: string,
    public order: string,
    public missing?: string
  ) {}
}

/**
 * @ignore
 * Creates the Model of Field Definitions
 */
export class FieldDefinition {
  constructor(
    public elements: any[] = [],
    public sortorder: any[] = [],
    public grouporder: any[] = [],
    public pinned: any[] = [],
    public alignmentx: {qname: string, value: string}[] = [],
    public mode?: string
  ) {}
}

/**
 * @ignore
 * Class representing the state of a query
 */
export class SearchState {
  public count: {value: number, relation?: string};
  public aggregations;
  public lastChange?: string;

  /**
   * @ignore
   */
  constructor() {
    this.aggregations = {
      type: new Map<string, number>(),
      contextType: new Map<string, number>(),
      created: [],
      modified: [],
      mimetypegroup: [],
      filesize: []
    };
  }

  get totalCount() {
    return Array.from(this.aggregations.type.values()).reduce((acc: number, cur: number) => acc + cur, 0) as number;
  }

  get isEmpty() {
    return !(this.count && this.count.value);
  }

  addAggregations(type: string, key: string, value: number, agg?) {
    switch (type) {
      case 'contexttype': {
        this.aggregations.contextType.set(key, value);
        break;
      }
      case SearchQuery.BASE_PARAMS.TYPE: {
        this.aggregations.type.set(key, value);
        break;
      }
      case SearchQuery.BASE_PARAMS.CREATED: {
        this.aggregations.created.push({
          key: key,
          value: value
        });
        break;
      }
      case SearchQuery.BASE_PARAMS.MODIFIED: {
        this.aggregations.modified.push({
          key: key,
          value: value
        });
        break;
      }
      case SearchQuery.BASE_PARAMS.MIMETYPEGROUP: {
        this.aggregations.mimetypegroup.push({
          key: key,
          value: value
        });
        break;
      }
      case SearchQuery.BASE_PARAMS.FILESIZE: {
        this.aggregations.filesize.push({
          key: key,
          value: value,
          from: agg.from,
          to: agg.to || Infinity,
          label: key.replace('gt', '> ').replace('lt', '< ')
        });
        break;
      }
    }
  }
}

/**
 * @ignore
 * Creates the Model of Search Results
 */
export class SearchResult {
  public count: {value: number, relation?: string};
  public suggests: string[];
  public hits: any[];
  public typeName: string;
  public fields: FieldDefinition;

  constructor() {}
}

export class SearchFilter {
  /**
   * available operators for a search filter
   */
  public static OPERATOR = {
    /** equal */
    EQUAL: 'eq',
    /** match (for string with classification "path6666") */
    MATCH: 'match',
    /** match at least one of the provided values (value has to be an array)  */
    IN: 'in',
    /** greater than */
    GREATER_THAN: 'gt',
    /** greater than or equal */
    GREATER_OR_EQUAL: 'gte', //
    LESS_THAN: 'lt', // less than
    LESS_OR_EQUAL: 'lte', // less than or equal
    INTERVAL: 'gtlt', // interval
    INTERVAL_INCLUDE_BOTH: 'gtelte', // interval include left and right
    INTERVAL_INCLUDE_TO: 'gtlte', // interval include right
    INTERVAL_INCLUDE_FROM: 'gtelt', // interval include left
    RANGE: 'rg', // aggegation ranges
    MORE: 'mr',
    USE_NOT: 'usenot'
  };

  /**
   * Constructor for creating a new SearchFilter.
   *
   * @param property The qualified name of the field this filter should apply to.
   * @param operator Operator indicating how to handle the filters value(s). See SearchFilter.OPERATOR for available operators.
   * @param firstValue The filters value
   * @param secondValue Optional second value for filters that for example define ranges of values
   * @param transformDate Flag to transform date hours and minutes based on operator
   */
  constructor(
    public property: string,
    public operator: string,
    public firstValue: any,
    public secondValue?: any,
    public transformDate?: {time?: boolean; timezone?: boolean},
    public useNot?: boolean,
    public actualOperator?: string
  ) { }

  formatDate(value: any) {
    return this.transformDate && value
      ? this.transformDate.timezone
        ? new DatePipe('en').transform(value, 'yyyy-MM-ddTZ')
        : this.transformDate.time
          ? new Date(value).toISOString().slice(0, 16) + 'Z'
          : new DatePipe('en').transform(value, 'yyyy-MM-dd')
      : value;
  }

  toQuery() {
    const v1 = this.operator === 'mr' ? this.firstValue : this.formatDate(this.firstValue);
    const o = this.operator === 'mr' ? 'rg' : (this.useNot ? this.actualOperator : this.operator);
    return {
      o,
      v1,
      ...(this.secondValue && {v2: this.formatDate(this.secondValue)}),
      ...(this.useNot && { usenot: true, actualOperator: this.actualOperator })
    };
  }

  toDateTimeQuery() {
    if (this.transformDate && this.transformDate.timezone && this.firstValue) {
      let firstValue = this.firstValue;
      let secondValue = this.secondValue;
      const endDate = new Date(secondValue || firstValue);
      endDate.setHours(23, 59, 59, 999);
      if (this.operator === SearchFilter.OPERATOR.LESS_OR_EQUAL) {
        firstValue = endDate.toISOString();
      } else {
        const startDate = new Date(firstValue);
        if (
          this.operator === SearchFilter.OPERATOR.GREATER_THAN ||
          this.operator === SearchFilter.OPERATOR.INTERVAL
        ) {
          startDate.setHours(23, 59, 59, 999);
        } else {
          startDate.setHours(0, 0, 0, 0);
        }
        firstValue = startDate.toISOString();
      }
      if (
        this.operator === SearchFilter.OPERATOR.INTERVAL_INCLUDE_BOTH ||
        this.operator === SearchFilter.OPERATOR.EQUAL
      ) {
        secondValue = endDate.toISOString();
      }
      return {
        o:
          this.operator === SearchFilter.OPERATOR.EQUAL
            ? SearchFilter.OPERATOR.INTERVAL_INCLUDE_BOTH
            : this.operator,
        v1: firstValue,
        ...(secondValue && {v2: secondValue})
      };
    } else {
      return this.toQuery();
    }
  }

  /**
   * @ignore
   */
  match(
    property: string,
    operator: string,
    firstValue: any,
    secondValue?: any
  ) {
    return (
      this.property === property &&
      this.operator === operator &&
      this.secondValue === secondValue &&
      (this.firstValue instanceof Array
        ? !!this.firstValue.find(v => v === firstValue)
        : this.firstValue === firstValue)
    );
  }
}

results matching ""

    No results matching ""