diff options
Diffstat (limited to 'src/app/helpers')
-rw-r--r-- | src/app/helpers/filter-helpers.ts | 256 | ||||
-rw-r--r-- | src/app/helpers/form-validators.ts | 32 | ||||
-rw-r--r-- | src/app/helpers/helpers.ts | 76 | ||||
-rw-r--r-- | src/app/helpers/listing.ts | 90 | ||||
-rw-r--r-- | src/app/helpers/sorting-helpers.ts | 59 |
5 files changed, 513 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); +} diff --git a/src/app/helpers/form-validators.ts b/src/app/helpers/form-validators.ts new file mode 100644 index 0000000..6cd3acd --- /dev/null +++ b/src/app/helpers/form-validators.ts @@ -0,0 +1,32 @@ +/* + * 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 { AbstractControl, ValidationErrors } from '@angular/forms'; + +export class WhiteSpaceValidator { + static noWhiteSpace(control: AbstractControl): ValidationErrors | null { + if (control.value !== null) { + if ((control.value as string).indexOf(' ') >= 0) { + return { noWhiteSpace: true }; + } + return null; + } + return null; + } +} diff --git a/src/app/helpers/helpers.ts b/src/app/helpers/helpers.ts new file mode 100644 index 0000000..7c03dbd --- /dev/null +++ b/src/app/helpers/helpers.ts @@ -0,0 +1,76 @@ +/* + * 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 { FormArray, FormGroup } from '@angular/forms'; + +export function isNotUndefined<T>(val: T | undefined): val is T { + return val !== undefined; +} + +export function isNotNull<T>(val: T | null): val is T { + return val !== null; +} + +export function markAsDirtyAndValidate(formGroup: FormGroup): void { + Object.values(formGroup.controls).forEach(control => { + control.markAsDirty(); + control.updateValueAndValidity(); + }); +} + +export function isNullOrUndefined(val: any): boolean { + return val === null || val === undefined; +} + +export function isNotNullOrUndefined(val: any): boolean { + return val !== null && val !== undefined; +} + +export function isNullOrUndefinedOrEmptyString(val: any): boolean { + return val === null || val === undefined || val === ''; +} + +export function isEmptyArray(array: any[]):boolean { + return !array.length +} + +export function getRandomNumber(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +export function areFormControlsValid(form: FormGroup): boolean { + const formControls = Object.keys(form.controls) + .map(key => form.controls[key]) + .filter(control => !(control instanceof FormArray)); + return formControls.find(control => control.invalid && (control.dirty || control.touched)) === undefined; +} + +export function isString(value: any): boolean { + return typeof value === 'string' || value instanceof String; +} + +export function resetSelectDefaultValue(cssSelector: string): void { + setTimeout(() => { + const element = document.querySelector(cssSelector); + if (element) { + //@ts-ignore + document.querySelector(cssSelector)?.selectedIndex = -1; + } + }, 0); +} diff --git a/src/app/helpers/listing.ts b/src/app/helpers/listing.ts new file mode 100644 index 0000000..66d1ae0 --- /dev/null +++ b/src/app/helpers/listing.ts @@ -0,0 +1,90 @@ +/* + * 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 { Router } from '@angular/router'; +import { filterBuilder, FilterOperator } from 'src/app/helpers/filter-helpers'; + +/** + * A JSONPath filter. One or more `Filter`s can be passed to the `filter=` param of the alarm API. + */ +export interface Filter { + /** + * The field to compare against, i.e `$.type` + */ + parameter: string; + /** + * 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 `==` and `like_regex`: + * ``` + * $.id == 1 + * $.type like_regex "type_(a|b)" + * ``` + */ + operator: FilterOperator; + /** + * The value or pattern that the field should have, i.e `"type_(a|b)"` + */ + value: string | number; +} + +/** + * This represents one filter expression for one column. + * Use `undefined` to signal removal of the filter. + * + * Note that parameter needs to be the complete param (i.e `$.column`). + * That is necessary because this will be the key when parsing the filter expression into a map. + */ +export type ColumnFilter = { + parameter: string; + filter: Filter | undefined; +}; + +export function changePage(router: Router, page: number): void { + router.navigate([], { queryParams: { page }, queryParamsHandling: 'merge' }); +} + +export function changePageSize(router: Router, pageSize: number): void { + router.navigate([], { + queryParams: { page: 1, pageSize }, + queryParamsHandling: 'merge', + }); +} + +export function changeFilter(router: Router, value: string | undefined): void { + router.navigate([], { + queryParams: { filter: value ? value.trim() : undefined, page: 1 }, + queryParamsHandling: 'merge', + }); +} + +/** + * Format a JSONPath filter expression into the format required by the angular router. + * Add or update this filter param in the router. + * @param router the angular router + * @param filters the filter expression in JSONPath format + * @param path path for new router link + * @param params other queryParams + */ +export function changeFiltersInRouter(router: Router, filters: Map<string, Filter>, params?: { [key: string]: any }, path?: any[]): void { + const composedFilter = filters.size > 0 ? filterBuilder(filters) : undefined; + router.navigate(path ?? [], { + queryParams: { filter: composedFilter, ...params, page: 1 }, + queryParamsHandling: 'merge', + }); +} diff --git a/src/app/helpers/sorting-helpers.ts b/src/app/helpers/sorting-helpers.ts new file mode 100644 index 0000000..94ec9ef --- /dev/null +++ b/src/app/helpers/sorting-helpers.ts @@ -0,0 +1,59 @@ +/* + * 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 { Sort, SortDirection } from '../components/shared/sort/sort.component'; +import { Router } from '@angular/router'; + +export function parseSortStringToSort(str: string): Sort { + return str.startsWith('-') + ? { parameter: str.split('-')[1], direction: SortDirection.DESC } + : { parameter: str, direction: SortDirection.ASC }; +} +export function applyNewSorting(router: Router, sort: Sort) { + if (sort.direction === SortDirection.NONE) { + router.navigate([], { + queryParams: { page: 1, sort: null }, + queryParamsHandling: 'merge', + }); + } else { + router.navigate([], { + queryParams: { page: 1, sort: sort.direction === SortDirection.ASC ? sort.parameter : '-' + sort.parameter }, + queryParamsHandling: 'merge', + }); + } +} +export function parseSortToSortString(sort: Sort): string { + if (sort.direction === SortDirection.ASC) { + return sort.parameter; + } + if (sort.direction === SortDirection.DESC) { + return '-' + sort.parameter; + } + return ''; +} + +export function parseSortToSortJsonString(sort: Sort): string { + if (sort.direction === SortDirection.ASC) { + return '$.' + sort.parameter; + } + if (sort.direction === SortDirection.DESC) { + return '-$.' + sort.parameter; + } + return ''; +} |