aboutsummaryrefslogtreecommitdiffstats
path: root/src/app/helpers/filter-helpers.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/helpers/filter-helpers.ts')
-rw-r--r--src/app/helpers/filter-helpers.ts256
1 files changed, 256 insertions, 0 deletions
diff --git a/src/app/helpers/filter-helpers.ts b/src/app/helpers/filter-helpers.ts
new file mode 100644
index 0000000..cc9f13e
--- /dev/null
+++ b/src/app/helpers/filter-helpers.ts
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2022. Deutsche Telekom AG
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { Filter } from 'src/app/helpers/listing';
+
+/**
+ * The JSONPath Filter Operators that are used for comparison (subset of [these](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-SQLJSON-FILTER-EX-TABLE)).
+ * For instance:
+ * ```
+ * $.id == 1
+ * $.type like_regex "type_(a|b)"
+ * ```
+ */
+export enum FilterOperator {
+ EQUAL = ' == ',
+ NOT_EQUAL = ' != ',
+ LIKE_REGEX = ' like_regex ',
+ OR = ' || ',
+ EQUAL_IN = ' IN '
+}
+
+/**
+ * Turns a Filter that is not formatted into a valid JSONPath one
+ * @param filter the filter that is not in JSONPath format
+ * @param string pass string: true if you want a return type string
+ * @returns a Filter with formatted: true or a string when parameter string: true
+ */
+export function createJsonPathFilter(filter: Filter): Filter;
+export function createJsonPathFilter(filter: Filter, returnsString: true): string;
+export function createJsonPathFilter(filter: Filter, returnsString = false): Filter | string {
+ // like_regex only works with strings
+ if (filter.operator === FilterOperator.LIKE_REGEX) {
+ if (returnsString) {
+ return createFilter(addFieldPrefix(filter.parameter), filter.operator, toSearchRegex(filter.value.toString()));
+ }
+ return {
+ parameter: addFieldPrefix(filter.parameter),
+ operator: filter.operator,
+ value: toSearchRegex(filter.value.toString()),
+ };
+ }
+ if (typeof filter.value === 'number') {
+ if (returnsString) {
+ return createFilter(addFieldPrefix(filter.parameter), filter.operator, filter.value.toString());
+ }
+ return {
+ parameter: addFieldPrefix(filter.parameter),
+ operator: filter.operator,
+ value: filter.value,
+ };
+ }
+ if (returnsString) {
+ return createFilter(addFieldPrefix(filter.parameter), filter.operator, quote(filter.value));
+ }
+ return {
+ parameter: addFieldPrefix(filter.parameter),
+ operator: filter.operator,
+ value: quote(filter.value),
+ };
+}
+
+/**
+ * Concatenates the provided input into a string.
+ * @param parameter the field to filter
+ * @param value the value it should match
+ * @param operator the operator for comparison (`==`,`!=`,`like_regex`,...)
+ * @param forApi
+ * @returns a concatenated string
+ */
+export function createFilter(parameter: string, operator: FilterOperator, value: string | string[], forApi= false): string {
+ if (forApi && operator === FilterOperator.EQUAL_IN) {
+ const valueArray = Array.isArray(value) ? value : value.split(',');
+ return addParentheses(createOrCondition(composeFilterFromArray(valueArray,parameter,FilterOperator.EQUAL)));
+ }
+ return `${parameter}${operator}${value}`;
+}
+/**
+ * Create array of composed filter
+ * @param value array of strings e.g array of IDs for alarms
+ * @param parameter the field to filter e.g $.id
+ * @param operator the operator for comparison
+ * @returns a string[]
+ */
+function composeFilterFromArray(value: string[], parameter: string, operator: FilterOperator): string[] {
+ return value
+ .reduce((acc: string[], curr) => [...acc, `${parameter}${operator}${curr}`], [])
+}
+/**
+ * Join array of string using '||' operator
+ * @param value string[]
+ * @returns a string
+ */
+function createOrCondition(value: string[]) {
+ return value.join(FilterOperator.OR)
+}
+/**
+ * Wraps a string in parentheses `"string"` -> `("string")`
+ * @param value string to wrap in quotes
+ * @returns a string wrapped in quotes
+ */
+function addParentheses(value: string) {
+ return ` ( ${value} ) `
+}
+/**
+ * Wraps a string in quotes `"string"` -> `"'string'"`
+ * @param value string to wrap in quotes
+ * @returns a string wrapped in quotes
+ */
+export function quote(value: string): string {
+ return `"${value}"`;
+}
+
+/**
+ * Add a `$.` prefix to the provided field
+ * @param value the field to match against
+ * @returns the field with a `$.` prefix
+ */
+export function addFieldPrefix(value: string): string {
+ return `$.${value}`;
+}
+
+/**
+ * Concatenate a list of categories into a regex expression for alternative matches.
+ * I.e `[a,b,c]` -> `"^(a|b|c)$"`
+ * @param categories the alternative values that should be matched
+ * @returns a regex expression of alternative matches
+ */
+export function toCategoricalRegex(categories: string[]): string {
+ return `"^(${categories.join('|')})$"`;
+}
+
+/**
+ * Extracts the list of categories from a given regular expression.
+ * I.e `"^(a|b|c)$"` -> `[a,b,c]`
+ * @param regex a regex expression of alternative matches
+ * @returns the categories from the regex
+ */
+export function fromCategoricalRegex(regex: string): string[] {
+ let strippedRegex = regex.slice(3, regex.length - 3);
+ return strippedRegex.split('|');
+}
+
+/**
+ * Turns the given searchTerm into a case insensitive value to be used with like_regex
+ * I.e `term` -> `"term" flag "i"`
+ * @param searchTerm the search term
+ * @returns the term in the format to be used with like_regex
+ */
+export function toSearchRegex(searchTerm: string): string {
+ return `"${searchTerm}" + flag "i"`;
+}
+
+/**
+ * Strips the value of a like_regex filter off its formatting.
+ * I.e `"value" flag "i"` -> `value`
+ * @param regex the value for the `like_regex` operation
+ * @returns the value without quotes and flags
+ */
+export function fromSearchRegex(regex: string): string {
+ const rawValue = regex?.split(' ')[0];
+ if (rawValue.charAt(0) != '"' || rawValue.charAt(rawValue.length - 1) != '"') {
+ throw new Error('Error while extracting the value from the url. Is it in the correct format?');
+ }
+ return rawValue.substring(1, rawValue.length - 1);
+}
+
+/**
+ * Join the individual filters with `&&` into a long expression
+ * @param filters the array of filter strings
+ * @returns a long filter string chained by `&&`'s
+ */
+export function composeFilter(filters: string[]): string {
+ return filters.join('&&');
+}
+
+/**
+ * Turn the provided string of filters into a Map of filters
+ * @param filter the filter string
+ * @returns a Map of filters
+ */
+export function parseFilterFromUrl(filter: string | null): Map<string, Filter> {
+ if (!filter) {
+ return new Map<string, Filter>();
+ }
+ const filters = filter.split('&&');
+ return splitFilterByOperator(filters);
+}
+
+/**
+ * Parse a list of strings in filter format into a Map of Filters
+ * @param filters list of strings in format `['']`
+ * @returns a map of Filters
+ */
+function splitFilterByOperator(filters: string[]): Map<string, Filter> {
+ const mappedFilters = new Map<string, Filter>();
+ filters
+ .map(filter => {
+ if (filter.includes(FilterOperator.EQUAL_IN)) {
+ const [parameter, value] = filter.split(FilterOperator.EQUAL_IN);
+ return { parameter, value, operator: FilterOperator.EQUAL_IN };
+ }
+ if (filter.includes(FilterOperator.EQUAL)) {
+ const [parameter, value] = filter.split(FilterOperator.EQUAL);
+ return { parameter, value, operator: FilterOperator.EQUAL };
+ }
+ if (filter.includes(FilterOperator.NOT_EQUAL)) {
+ const [parameter, value] = filter.split(FilterOperator.NOT_EQUAL);
+ return { parameter, value, operator: FilterOperator.NOT_EQUAL };
+ }
+ if (filter.includes(FilterOperator.LIKE_REGEX)) {
+ const [parameter, value] = filter.split(FilterOperator.LIKE_REGEX);
+ return { parameter, value, operator: FilterOperator.LIKE_REGEX };
+ }
+ throw new Error('Unsupported operator');
+ })
+ .forEach(item => mappedFilters.set(item.parameter, item));
+ return mappedFilters;
+}
+
+/**
+ * Transforms the map of filters back into a JSONPATH filter string
+ * @param filters
+ * @param forApi
+ * @returns a string containing all filter expressions or undefined if size of the Filter Map is 0
+ */
+export function filterBuilder(filters: Map<string, Filter>, forApi=false): string | undefined {
+ if (filters.size === 0) {
+ return undefined;
+ }
+ const filtersString: string[] = [];
+ for (const [, filter] of filters.entries()) {
+ filtersString.push(
+ createFilter(
+ filter.parameter,
+ filter.operator,
+ filter.value.toString(),
+ forApi),
+ );
+ }
+ return composeFilter(filtersString);
+}