From 124d9099c8e01aa26e3161ea8ce442e5d0d3b557 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Wed, 2 Feb 2022 10:47:15 -0600 Subject: [PATCH 01/14] Add a time filter and use Required for certain types. Add isComplete type guard --- .../src/types/filters/flowRuns.ts | 9 +++++++-- .../orion-design/src/types/filters/index.ts | 20 ++++++++++++------- .../src/types/filters/taskRuns.ts | 9 +++++++-- .../orion-design/src/utilities/filters.ts | 4 ++++ 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/orion-ui/packages/orion-design/src/types/filters/flowRuns.ts b/orion-ui/packages/orion-design/src/types/filters/flowRuns.ts index 14ba10ff2dcc..7e549e906b47 100644 --- a/orion-ui/packages/orion-design/src/types/filters/flowRuns.ts +++ b/orion-ui/packages/orion-design/src/types/filters/flowRuns.ts @@ -1,8 +1,8 @@ -import { ObjectStringFilter, ObjectDateFilter, ObjectTagFilter, ObjectStateFilter } from '.' +import { ObjectStringFilter, ObjectDateFilter, ObjectTagFilter, ObjectStateFilter, ObjectTimeFilter } from '.' export type FlowRunFilter = { object: 'flow_run', -} & Partial<(FlowRunStringFilter | FlowRunDateFilter | FlowRunTagFilter | FlowRunStateFilter)> +} & Partial<(FlowRunStringFilter | FlowRunDateFilter | FlowRunTimeFilter | FlowRunTagFilter | FlowRunStateFilter)> export type FlowRunStringFilter = { object: 'flow_run', @@ -14,6 +14,11 @@ export type FlowRunDateFilter = { property: 'start_date' | 'end_date', } & Partial +export type FlowRunTimeFilter = { + object: 'flow_run', + property: 'start_date' | 'end_date', +} & Partial + export type FlowRunTagFilter = { object: 'flow_run', property: 'tag', diff --git a/orion-ui/packages/orion-design/src/types/filters/index.ts b/orion-ui/packages/orion-design/src/types/filters/index.ts index 87543b8e2d78..e3b2262b8f89 100644 --- a/orion-ui/packages/orion-design/src/types/filters/index.ts +++ b/orion-ui/packages/orion-design/src/types/filters/index.ts @@ -12,8 +12,14 @@ export type ObjectStringFilter = { export type ObjectDateFilter = { type: 'date', - operation: 'after' | 'before' | 'older' | 'newer' | 'between', - value: Date | Date[], + operation: 'after' | 'before', + value: Date, +} + +export type ObjectTimeFilter = { + type: 'time', + operation: 'newer' | 'older', + value: `${number}h` | `${number}d` | `${number}w` | `${number}m` | `${number}y`, } export type ObjectTagFilter = { @@ -35,11 +41,11 @@ export type ObjectNumberFilter = { } export type Filter = FlowFilter | DeploymentFilter | FlowRunFilter | TaskRunFilter | TagFilter -export type FilterEntities = Filter['object'] -export type FilterOperations = Filter['operation'] -export type FilterTypes = Filter['type'] -export type FilterValues = Filter['value'] -export type ObjectFilter = Pick +export type FilterObjects = Filter['object'] +export type FilterOperations = Required['operation'] +export type FilterTypes = Required['type'] +export type FilterValues = Required['value'] +export type ObjectFilter = Pick, 'type' | 'operation' | 'value'> export * from './deployments' export * from './flowRuns' diff --git a/orion-ui/packages/orion-design/src/types/filters/taskRuns.ts b/orion-ui/packages/orion-design/src/types/filters/taskRuns.ts index bfacf6e1a3b5..80a6679789fb 100644 --- a/orion-ui/packages/orion-design/src/types/filters/taskRuns.ts +++ b/orion-ui/packages/orion-design/src/types/filters/taskRuns.ts @@ -1,8 +1,8 @@ -import { ObjectStringFilter, ObjectDateFilter, ObjectTagFilter, ObjectStateFilter } from '.' +import { ObjectStringFilter, ObjectDateFilter, ObjectTagFilter, ObjectStateFilter, ObjectTimeFilter } from '.' export type TaskRunFilter = { object: 'task_run', -} & Partial<(TaskRunStringFilter | TaskRunDateFilter | TaskRunTagFilter | TaskRunStateFilter)> +} & Partial<(TaskRunStringFilter | TaskRunDateFilter | TaskRunTimeFilter | TaskRunTagFilter | TaskRunStateFilter)> export type TaskRunStringFilter = { object: 'task_run', @@ -14,6 +14,11 @@ export type TaskRunDateFilter = { property: 'start_date' | 'end_date', } & Partial +export type TaskRunTimeFilter = { + object: 'task_run', + property: 'start_date' | 'end_date', +} & Partial + export type TaskRunTagFilter = { object: 'task_run', property: 'tag', diff --git a/orion-ui/packages/orion-design/src/utilities/filters.ts b/orion-ui/packages/orion-design/src/utilities/filters.ts index bde6aa9e4bb9..af9485f7d391 100644 --- a/orion-ui/packages/orion-design/src/utilities/filters.ts +++ b/orion-ui/packages/orion-design/src/utilities/filters.ts @@ -24,6 +24,10 @@ import { ObjectTagFilter } from '../types/filters' +export function isCompleteFilter(filter: Filter): filter is Required { + return !!(filter.operation && filter.property && filter.type && filter.value) +} + export function isFlowFilter(filter: Filter): filter is FlowFilter { return filter.object == 'flow' } From 80ffa5abeb053c9d8ee7e89e00f52a5b84330d45 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Wed, 2 Feb 2022 10:50:19 -0600 Subject: [PATCH 02/14] Add a service for converting filters to their string tag versions --- .../src/services/FilterTagService.ts | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 orion-ui/packages/orion-design/src/services/FilterTagService.ts diff --git a/orion-ui/packages/orion-design/src/services/FilterTagService.ts b/orion-ui/packages/orion-design/src/services/FilterTagService.ts new file mode 100644 index 000000000000..8c50a8d4ada1 --- /dev/null +++ b/orion-ui/packages/orion-design/src/services/FilterTagService.ts @@ -0,0 +1,86 @@ +/* eslint-disable default-case */ +import { Filter, ObjectDateFilter, ObjectStateFilter, ObjectStringFilter, ObjectTagFilter, ObjectTimeFilter } from '../types/filters' +import { formatDateTimeNumeric } from '../utilities/dates' + +export class FilterTagService { + public convertFiltersToTags(filters: Required[]): string[] { + return filters.map(filter => this.convertFilterToTag(filter)) + } + + public convertFilterToTag(filter: Required): string { + const tagPrefix = this.createTagPrefix(filter) + const tagSuffix = this.createTagSuffix(filter) + const tagValue = this.createTagValue(filter) + + return `${tagPrefix}${tagSuffix}:${tagValue}` + } + + private createObjectStringFilterValue(filter: ObjectStringFilter): string { + switch (filter.operation) { + case 'contains': + return filter.value + case 'equals': + return `"${filter.value}"` + } + } + + private createObjectDateFilterValue(filter: ObjectDateFilter): string { + return formatDateTimeNumeric(filter.value) + } + + private createObjectTimeFilterValue(filter: ObjectTimeFilter): string { + return filter.value + } + + private createObjectStateFilterValue(filter: ObjectStateFilter): string { + return filter.value.join('|') + } + + private createObjectTagFilterValue(filter: ObjectTagFilter): string { + return filter.value.join(',') + } + + private createTagPrefix(filter: Required): string { + switch (filter.object) { + case 'flow': + return 'f' + case 'deployment': + return 'd' + case 'flow_run': + return 'fr' + case 'task_run': + return 'tr' + case 'tag': + return 't' + } + } + + private createTagSuffix(filter: Required): string { + switch (filter.type) { + case 'string': + return '' + case 'state': + case 'tag': + return filter.type[0] + case 'date': + case 'time': + return filter.operation[0] + } + } + + private createTagValue(filter: Required): string { + switch (filter.type) { + case 'string': + return this.createObjectStringFilterValue(filter) + case 'state': + return this.createObjectStateFilterValue(filter) + case 'tag': + return this.createObjectTagFilterValue(filter) + case 'date': + return this.createObjectDateFilterValue(filter) + case 'time': + return this.createObjectTimeFilterValue(filter) + } + } + +} \ No newline at end of file From 9b699ac3e2137257283001d3fe398318ca8d2aaa Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Wed, 2 Feb 2022 11:19:04 -0600 Subject: [PATCH 03/14] update the services barrel --- orion-ui/packages/orion-design/src/services/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/orion-ui/packages/orion-design/src/services/index.ts b/orion-ui/packages/orion-design/src/services/index.ts index 0d50458d55c4..bfbbab62cbbe 100644 --- a/orion-ui/packages/orion-design/src/services/index.ts +++ b/orion-ui/packages/orion-design/src/services/index.ts @@ -1,5 +1,8 @@ export * from './Api' export * from './Filter' +export * from './FilterTagService' export * from './LogsApi' export * from './Mocker' +export * from './SimpleIdManager' +export * from './StatesApi' export * from './TaskRunsApi' From afb240626ddf9ccdf14ff628558e48fb0db0abd0 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Wed, 2 Feb 2022 16:31:24 -0600 Subject: [PATCH 04/14] Work in progress --- .../src/services/FilterParseTagService.ts | 210 ++++++++++++++++++ .../src/services/FilterTagService.ts | 26 +-- .../src/services/KeyValueStore.ts | 58 +++++ .../src/types/filters/flowRuns.ts | 4 +- .../orion-design/src/types/filters/index.ts | 24 +- .../src/types/filters/taskRuns.ts | 4 +- .../orion-design/src/utilities/object.ts | 11 + 7 files changed, 315 insertions(+), 22 deletions(-) create mode 100644 orion-ui/packages/orion-design/src/services/FilterParseTagService.ts create mode 100644 orion-ui/packages/orion-design/src/services/KeyValueStore.ts create mode 100644 orion-ui/packages/orion-design/src/utilities/object.ts diff --git a/orion-ui/packages/orion-design/src/services/FilterParseTagService.ts b/orion-ui/packages/orion-design/src/services/FilterParseTagService.ts new file mode 100644 index 000000000000..e98de2559e2b --- /dev/null +++ b/orion-ui/packages/orion-design/src/services/FilterParseTagService.ts @@ -0,0 +1,210 @@ +/* eslint-disable default-case */ +import { isCompleteFilter } from '..' +import { DeploymentFilter, Filter, FilterObject, FilterOperation, FilterProperty, FilterType, FilterValue, FlowFilter, FlowRunFilter, ObjectTagPrefixDictionary, ObjectTagPrefixes, TagFilter, TagPrefixObjectDictionary, TaskRunFilter } from '../types' + + +export class FilterParseTagService { + public convertTagsToFilters(tags: string[]): Required[] { + return tags.map(tag => this.convertTagToFilter(tag)) + } + + // null shouldn't be necessary + public convertTagToFilter(tag: string): Required | null { + const filter: Filter = { + object: this.parseObject(tag), + } + + filter.property = this.parseProperty(filter.object, tag) + filter.type = this.parseType(filter.object, filter.property, tag) + + if (!isCompleteFilter(filter)) { + return null + } + + return filter + } + + private parseObject(tag: string): FilterObject { + const prefix = ObjectTagPrefixes.find(prefix => tag.startsWith(prefix)) + + if (prefix === undefined) { + throw 'tag does not start with a valid prefix' + } + + return TagPrefixObjectDictionary[prefix] + } + + private parseProperty(object: FilterObject, tag: string): FilterProperty { + const prefix = ObjectTagPrefixDictionary[object] + const regex = new RegExp(`${prefix}(.*):.*`) + const match = tag.match(regex) + + if (match === null) { + throw 'tag does not contain a suffix' + } + + // can suffix be type safe? + const [, suffix] = match + + switch (object) { + case 'flow': + return this.getFlowPropertyFromSuffix(suffix) + case 'deployment': + return this.getDeploymentPropertyFromSuffix(suffix) + case 'flow_run': + return this.getFlowRunPropertyFromSuffix(suffix) + case 'task_run': + return this.getTaskRunPropertyFromSuffix(suffix) + case 'tag': + return this.getTagPropertyFromSuffix(suffix) + } + } + + private isFlowObject(object: FilterObject): object is 'flow' { + return object === 'flow' + } + + // type test = Extract, { object: 'flow' }>['property'] + private parseType, { object: T }>['property']>(object: T, property: P): FilterType { + switch (object) { + case 'flow': + return this.getFlowTypeFromProperty(property) + case 'deployment': + return this.getDeploymentTypeFromProperty(property) + case 'flow_run': + return this.getFlowRunTypeFromProperty(property) + case 'task_run': + return this.getTaskRunTypeFromProperty(property) + case 'tag': + return this.getTagTypeFromProperty(property) + } + + } + + private parseOperation(tag: string): FilterOperation { + + } + + private parseValue(tag: string): FilterValue { + + } + + private getDeploymentPropertyFromSuffix(suffix: string): Required['property'] { + switch (suffix) { + case '': + return 'name' + case 't': + return 'tag' + default: + throw 'deployment filter not have a valid suffix' + } + } + + private getFlowPropertyFromSuffix(suffix: string): Required['property'] { + switch (suffix) { + case '': + return 'name' + case 't': + return 'tag' + default: + throw 'flow filter not have a valid suffix' + } + } + + private getFlowRunPropertyFromSuffix(suffix: string): Required['property'] { + switch (suffix) { + case '': + return 'name' + case 't': + return 'tag' + case 's': + return 'state' + case 'ra': + case 'rb': + case 'rn': + case 'ro': + return 'start_date' + default: + throw 'flow run tag does not have a valid suffix' + } + } + + private getTaskRunPropertyFromSuffix(suffix: string): Required['property'] { + switch (suffix) { + case '': + return 'name' + case 't': + return 'tag' + case 's': + return 'state' + case 'ra': + case 'rb': + case 'rn': + case 'ro': + return 'start_date' + default: + throw 'task run filter not have a valid suffix' + } + } + + private getTagPropertyFromSuffix(suffix: string): Required['property'] { + switch (suffix) { + case '': + return 'name' + default: + throw 'tag filter not have a valid suffix' + } + } + + private getDeploymentTypeFromProperty(property: Required['property']): Required['type'] { + switch (property) { + case 'tag': + return 'tag' + case 'name': + return 'string' + } + } + + private getFlowTypeFromProperty(property: Required['property']): Required['type'] { + switch (property) { + case 'tag': + return 'tag' + case 'name': + return 'string' + } + } + + private getFlowRunTypeFromProperty(property: Required['property']): Required['type'] { + switch (property) { + case 'tag': + return 'tag' + case 'name': + return 'string' + case 'start_date': + return 'date' // or time + case 'state': + return 'state' + } + } + + private getTaskRunTypeFromProperty(property: Required['property']): Required['type'] { + switch (property) { + case 'tag': + return 'tag' + case 'name': + return 'string' + case 'start_date': + return 'date' // or time + case 'state': + return 'state' + } + } + + private getTagTypeFromProperty(property: Required['property']): Required['type'] { + switch (property) { + case 'name': + return 'string' + } + } + +} \ No newline at end of file diff --git a/orion-ui/packages/orion-design/src/services/FilterTagService.ts b/orion-ui/packages/orion-design/src/services/FilterTagService.ts index 8c50a8d4ada1..1c1d800f5844 100644 --- a/orion-ui/packages/orion-design/src/services/FilterTagService.ts +++ b/orion-ui/packages/orion-design/src/services/FilterTagService.ts @@ -1,5 +1,14 @@ /* eslint-disable default-case */ -import { Filter, ObjectDateFilter, ObjectStateFilter, ObjectStringFilter, ObjectTagFilter, ObjectTimeFilter } from '../types/filters' +import { + Filter, + FilterTagPrefix, + ObjectDateFilter, + ObjectStateFilter, + ObjectStringFilter, + ObjectTagFilter, + ObjectTagPrefixDictionary, + ObjectTimeFilter +} from '../types/filters' import { formatDateTimeNumeric } from '../utilities/dates' export class FilterTagService { @@ -40,19 +49,8 @@ export class FilterTagService { return filter.value.join(',') } - private createTagPrefix(filter: Required): string { - switch (filter.object) { - case 'flow': - return 'f' - case 'deployment': - return 'd' - case 'flow_run': - return 'fr' - case 'task_run': - return 'tr' - case 'tag': - return 't' - } + private createTagPrefix(filter: Required): FilterTagPrefix { + return ObjectTagPrefixDictionary[filter.object] } private createTagSuffix(filter: Required): string { diff --git a/orion-ui/packages/orion-design/src/services/KeyValueStore.ts b/orion-ui/packages/orion-design/src/services/KeyValueStore.ts new file mode 100644 index 000000000000..153bdbb8f37e --- /dev/null +++ b/orion-ui/packages/orion-design/src/services/KeyValueStore.ts @@ -0,0 +1,58 @@ +import { FilterObject } from '..' + +export class KeyValueStore { + private readonly keyMap = new Map() + private readonly valueMap = new Map() + + public constructor(entries?: readonly (readonly [K, V])[] | null) { + entries?.forEach(([key, value]) => { + this.set(key, value) + }) + } + + public getValue(key: K): V | undefined { + return this.keyMap.get(key) + } + + public getKey(value: V): K | undefined { + return this.valueMap.get(value) + } + + public has(key: K, value: V): boolean { + return this.keyMap.has(key) && this.valueMap.has(value) + } + + public hasKey(key: K): boolean { + return this.keyMap.has(key) + } + + public hasValue(value: V): boolean { + return this.valueMap.has(value) + } + + public set(key: K, value: V): this { + this.keyMap.set(key, value) + this.valueMap.set(value, key) + + return this + } + + public delete(key: K, value: V): boolean { + this.keyMap.delete(key) + this.valueMap.delete(value) + + return true + } + + public clear(): boolean { + this.keyMap.clear() + this.valueMap.clear() + + return true + } +} + +const store = new KeyValueStore([['flow', 'f']]) + +const test = store.getKey('f') +const test2 = store.getValue('flow') \ No newline at end of file diff --git a/orion-ui/packages/orion-design/src/types/filters/flowRuns.ts b/orion-ui/packages/orion-design/src/types/filters/flowRuns.ts index 7e549e906b47..f3e5c47e96cc 100644 --- a/orion-ui/packages/orion-design/src/types/filters/flowRuns.ts +++ b/orion-ui/packages/orion-design/src/types/filters/flowRuns.ts @@ -11,12 +11,12 @@ export type FlowRunStringFilter = { export type FlowRunDateFilter = { object: 'flow_run', - property: 'start_date' | 'end_date', + property: 'start_date', } & Partial export type FlowRunTimeFilter = { object: 'flow_run', - property: 'start_date' | 'end_date', + property: 'start_date', } & Partial export type FlowRunTagFilter = { diff --git a/orion-ui/packages/orion-design/src/types/filters/index.ts b/orion-ui/packages/orion-design/src/types/filters/index.ts index e3b2262b8f89..a7a6bcc0617b 100644 --- a/orion-ui/packages/orion-design/src/types/filters/index.ts +++ b/orion-ui/packages/orion-design/src/types/filters/index.ts @@ -1,3 +1,4 @@ +import { flip } from '../../utilities/object' import { DeploymentFilter } from './deployments' import { FlowRunFilter } from './flowRuns' import { FlowFilter } from './flows' @@ -41,12 +42,27 @@ export type ObjectNumberFilter = { } export type Filter = FlowFilter | DeploymentFilter | FlowRunFilter | TaskRunFilter | TagFilter -export type FilterObjects = Filter['object'] -export type FilterOperations = Required['operation'] -export type FilterTypes = Required['type'] -export type FilterValues = Required['value'] +export type FilterObject = Filter['object'] +export type FilterProperty = Required['property'] +export type FilterOperation = Required['operation'] +export type FilterType = Required['type'] +export type FilterValue = Required['value'] export type ObjectFilter = Pick, 'type' | 'operation' | 'value'> +const ObjectTagPrefixDictionaryData = { + 'deployment': 'd', + 'flow': 'f', + 'flow_run': 'fr', + 'task_run': 'tr', + 'tag': 't', +} as const + +export type FilterTagPrefix = typeof ObjectTagPrefixDictionaryData[FilterObject] + +export const ObjectTagPrefixes = Object.values(ObjectTagPrefixDictionaryData) +export const TagPrefixObjectDictionary = flip(ObjectTagPrefixDictionaryData) +export const ObjectTagPrefixDictionary = flip(TagPrefixObjectDictionary) + export * from './deployments' export * from './flowRuns' export * from './flows' diff --git a/orion-ui/packages/orion-design/src/types/filters/taskRuns.ts b/orion-ui/packages/orion-design/src/types/filters/taskRuns.ts index 80a6679789fb..46defbef0cf4 100644 --- a/orion-ui/packages/orion-design/src/types/filters/taskRuns.ts +++ b/orion-ui/packages/orion-design/src/types/filters/taskRuns.ts @@ -11,12 +11,12 @@ export type TaskRunStringFilter = { export type TaskRunDateFilter = { object: 'task_run', - property: 'start_date' | 'end_date', + property: 'start_date', } & Partial export type TaskRunTimeFilter = { object: 'task_run', - property: 'start_date' | 'end_date', + property: 'start_date', } & Partial export type TaskRunTagFilter = { diff --git a/orion-ui/packages/orion-design/src/utilities/object.ts b/orion-ui/packages/orion-design/src/utilities/object.ts new file mode 100644 index 000000000000..910a8336823c --- /dev/null +++ b/orion-ui/packages/orion-design/src/utilities/object.ts @@ -0,0 +1,11 @@ +export function flip(obj: Record): Record { + const result = {} as Record + + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + result[obj[key]] = key + } + } + + return result +} \ No newline at end of file From 3dec2096fe3425d56a57ea84baffcc57eb672777 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Thu, 3 Feb 2022 10:44:16 -0600 Subject: [PATCH 05/14] Rename services --- .../src/services/FilterParseService.ts | 218 ++++++++++++++++++ .../src/services/FilterParseTagService.ts | 210 ----------------- ...agService.ts => FilterStringifyService.ts} | 2 +- .../orion-design/src/types/filters/index.ts | 12 + 4 files changed, 231 insertions(+), 211 deletions(-) create mode 100644 orion-ui/packages/orion-design/src/services/FilterParseService.ts delete mode 100644 orion-ui/packages/orion-design/src/services/FilterParseTagService.ts rename orion-ui/packages/orion-design/src/services/{FilterTagService.ts => FilterStringifyService.ts} (98%) diff --git a/orion-ui/packages/orion-design/src/services/FilterParseService.ts b/orion-ui/packages/orion-design/src/services/FilterParseService.ts new file mode 100644 index 000000000000..9de39c38bf2a --- /dev/null +++ b/orion-ui/packages/orion-design/src/services/FilterParseService.ts @@ -0,0 +1,218 @@ +/* eslint-disable default-case */ +import { + Filter, + FilterObject, + FilterOperation, + FilterProperty, + FilterTagSuffix, + FilterType, + FilterValue, + ObjectDateFilter, + ObjectFilterTagSuffix, + ObjectStateFilter, + ObjectStringFilter, + ObjectTagFilter, + ObjectTagPrefixDictionary, + ObjectTagPrefixes, + ObjectTagSuffixes, + ObjectTimeFilter, + TagPrefixObjectDictionary +} from '../types' + +export class FilterParseService { + public convertTagsToFilters(tags: string[]): Required[] { + return tags.map(tag => this.convertTagToFilter(tag)) + } + + public convertTagToFilter(tag: string): Required { + // if(isLongTag) { + // return this.parseLongTag(tag) + // } + + return this.parseShortTag(tag) + } + + private parseShortTag(tag: string): Required { + const object = this.parseObject(tag) + const suffix = this.parseSuffix(object, tag) + const { type, property } = this.parseTypeAndProperty(object, suffix) + const { operation, value } = this.parseOperationAndValue(type, tag, suffix) + + return { + object, + property, + type, + operation, + value, + } as Required + } + + // private parseLongTag(tag: string): any {} + + private parseObject(tag: string): FilterObject { + const prefix = ObjectTagPrefixes.find(prefix => tag.startsWith(prefix)) + + if (prefix === undefined) { + throw 'tag does not start with a valid prefix' + } + + return TagPrefixObjectDictionary[prefix] + } + + private parseSuffix(object: FilterObject, tag: string): FilterTagSuffix { + const prefix = ObjectTagPrefixDictionary[object] + const regex = new RegExp(`${prefix}(.*):.*`) + const match = tag.match(regex) + + if (match === null) { + throw 'tag does not contain a suffix' + } + + const [, suffix] = match + + if (!this.isSuffix(suffix)) { + throw 'tag does not contain a valid suffix' + } + + return suffix + } + + private parseTypeAndProperty(object: FilterObject, suffix: FilterTagSuffix): { type: FilterType, property: FilterProperty } { + switch (object) { + case 'flow': + return this.parseFlowTypeAndProperty(suffix as ObjectFilterTagSuffix<'flow'>) + case 'deployment': + return this.parseDeploymentTypeAndProperty(suffix as ObjectFilterTagSuffix<'deployment'>) + case 'flow_run': + case 'task_run': + return this.parseRunTypeAndProperty(suffix as ObjectFilterTagSuffix<'flow_run' | 'task_run'>) + case 'tag': + return this.parseTagTypeAndProperty(suffix as ObjectFilterTagSuffix<'tag'>) + } + } + + private parseFlowTypeAndProperty(suffix: ObjectFilterTagSuffix<'flow'>): { type: FilterType, property: FilterProperty } { + switch (suffix) { + case '': + return { type: 'string', property: 'name' } + case 't': + return { type: 'tag', property: 'tag' } + } + } + + private parseRunTypeAndProperty(suffix: ObjectFilterTagSuffix<'flow_run' | 'task_run'>): { type: FilterType, property: FilterProperty } { + switch (suffix) { + case '': + return { type: 'string', property: 'name' } + case 't': + return { type: 'tag', property: 'tag' } + case 'a': + case 'b': + return { type: 'date', property: 'start_date' } + case 'n': + case 'o': + return { type: 'time', property: 'start_date' } + } + } + + private parseDeploymentTypeAndProperty(suffix: ObjectFilterTagSuffix<'deployment'>): { type: FilterType, property: FilterProperty } { + switch (suffix) { + case '': + return { type: 'string', property: 'name' } + case 't': + return { type: 'tag', property: 'tag' } + } + } + + private parseTagTypeAndProperty(suffix: ObjectFilterTagSuffix<'tag'>): { type: FilterType, property: FilterProperty } { + switch (suffix) { + case '': + return { type: 'string', property: 'name' } + } + } + + private parseOperationAndValue(type: FilterType, tag: string, suffix: FilterTagSuffix): { operation: FilterOperation, value: FilterValue } { + const [, input] = tag.split(':') + + switch (type) { + case 'string': + return this.parseStringOperationAndValue(input) + case 'tag': + return this.parseTagOperationAndValue(input) + case 'state': + return this.parseStateOperationAndValue(input) + case 'date': + return this.parseDateOperationAndValue(input, suffix as 'a' | 'b') + case 'time': + return this.parseTimeOperationAndValue(input, suffix as 'n' | 'o') + } + } + + private parseStringOperationAndValue(input: string): { operation: ObjectStringFilter['operation'], value: ObjectStringFilter['value'] } { + const exactOperationRegex = /^"(.*)"$/ + const match = input.match(exactOperationRegex) + + if (match) { + const [, value] = match + + return { + operation: 'equals', + value, + } + } + + return { + operation: 'contains', + value: input, + } + } + + private parseDateOperationAndValue(input: string, suffix: 'a' | 'b'): { operation: ObjectDateFilter['operation'], value: ObjectDateFilter['value'] } { + const value = new Date(input) + + switch (suffix) { + case 'a': + return { operation: 'after', value } + case 'b': + return { operation: 'before', value } + } + } + + private parseTimeOperationAndValue(input: string, suffix: 'n' | 'o'): { operation: ObjectTimeFilter['operation'], value: ObjectTimeFilter['value'] } { + if (!this.isTime(input)) { + throw 'invalid time value' + } + + switch (suffix) { + case 'n': + return { operation: 'newer', value: input } + case 'o': + return { operation: 'older', value: input } + } + } + + private parseTagOperationAndValue(input: string): { operation: ObjectTagFilter['operation'], value: ObjectTagFilter['value'] } { + return { + operation: 'and', + value: input.split(','), + } + } + + private parseStateOperationAndValue(input: string): { operation: ObjectStateFilter['operation'], value: ObjectStateFilter['value'] } { + return { + operation: 'or', + value: input.split(','), + } + } + + // using any here because typescript doesn't like string type... + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private isSuffix(value: any): value is FilterTagSuffix { + return ObjectTagSuffixes.includes(value) + } + + private isTime(value: string): value is ObjectTimeFilter['value'] { + return /^[0-1]+[h,d,w,m,w]$/.test(value) + } + +} \ No newline at end of file diff --git a/orion-ui/packages/orion-design/src/services/FilterParseTagService.ts b/orion-ui/packages/orion-design/src/services/FilterParseTagService.ts deleted file mode 100644 index e98de2559e2b..000000000000 --- a/orion-ui/packages/orion-design/src/services/FilterParseTagService.ts +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable default-case */ -import { isCompleteFilter } from '..' -import { DeploymentFilter, Filter, FilterObject, FilterOperation, FilterProperty, FilterType, FilterValue, FlowFilter, FlowRunFilter, ObjectTagPrefixDictionary, ObjectTagPrefixes, TagFilter, TagPrefixObjectDictionary, TaskRunFilter } from '../types' - - -export class FilterParseTagService { - public convertTagsToFilters(tags: string[]): Required[] { - return tags.map(tag => this.convertTagToFilter(tag)) - } - - // null shouldn't be necessary - public convertTagToFilter(tag: string): Required | null { - const filter: Filter = { - object: this.parseObject(tag), - } - - filter.property = this.parseProperty(filter.object, tag) - filter.type = this.parseType(filter.object, filter.property, tag) - - if (!isCompleteFilter(filter)) { - return null - } - - return filter - } - - private parseObject(tag: string): FilterObject { - const prefix = ObjectTagPrefixes.find(prefix => tag.startsWith(prefix)) - - if (prefix === undefined) { - throw 'tag does not start with a valid prefix' - } - - return TagPrefixObjectDictionary[prefix] - } - - private parseProperty(object: FilterObject, tag: string): FilterProperty { - const prefix = ObjectTagPrefixDictionary[object] - const regex = new RegExp(`${prefix}(.*):.*`) - const match = tag.match(regex) - - if (match === null) { - throw 'tag does not contain a suffix' - } - - // can suffix be type safe? - const [, suffix] = match - - switch (object) { - case 'flow': - return this.getFlowPropertyFromSuffix(suffix) - case 'deployment': - return this.getDeploymentPropertyFromSuffix(suffix) - case 'flow_run': - return this.getFlowRunPropertyFromSuffix(suffix) - case 'task_run': - return this.getTaskRunPropertyFromSuffix(suffix) - case 'tag': - return this.getTagPropertyFromSuffix(suffix) - } - } - - private isFlowObject(object: FilterObject): object is 'flow' { - return object === 'flow' - } - - // type test = Extract, { object: 'flow' }>['property'] - private parseType, { object: T }>['property']>(object: T, property: P): FilterType { - switch (object) { - case 'flow': - return this.getFlowTypeFromProperty(property) - case 'deployment': - return this.getDeploymentTypeFromProperty(property) - case 'flow_run': - return this.getFlowRunTypeFromProperty(property) - case 'task_run': - return this.getTaskRunTypeFromProperty(property) - case 'tag': - return this.getTagTypeFromProperty(property) - } - - } - - private parseOperation(tag: string): FilterOperation { - - } - - private parseValue(tag: string): FilterValue { - - } - - private getDeploymentPropertyFromSuffix(suffix: string): Required['property'] { - switch (suffix) { - case '': - return 'name' - case 't': - return 'tag' - default: - throw 'deployment filter not have a valid suffix' - } - } - - private getFlowPropertyFromSuffix(suffix: string): Required['property'] { - switch (suffix) { - case '': - return 'name' - case 't': - return 'tag' - default: - throw 'flow filter not have a valid suffix' - } - } - - private getFlowRunPropertyFromSuffix(suffix: string): Required['property'] { - switch (suffix) { - case '': - return 'name' - case 't': - return 'tag' - case 's': - return 'state' - case 'ra': - case 'rb': - case 'rn': - case 'ro': - return 'start_date' - default: - throw 'flow run tag does not have a valid suffix' - } - } - - private getTaskRunPropertyFromSuffix(suffix: string): Required['property'] { - switch (suffix) { - case '': - return 'name' - case 't': - return 'tag' - case 's': - return 'state' - case 'ra': - case 'rb': - case 'rn': - case 'ro': - return 'start_date' - default: - throw 'task run filter not have a valid suffix' - } - } - - private getTagPropertyFromSuffix(suffix: string): Required['property'] { - switch (suffix) { - case '': - return 'name' - default: - throw 'tag filter not have a valid suffix' - } - } - - private getDeploymentTypeFromProperty(property: Required['property']): Required['type'] { - switch (property) { - case 'tag': - return 'tag' - case 'name': - return 'string' - } - } - - private getFlowTypeFromProperty(property: Required['property']): Required['type'] { - switch (property) { - case 'tag': - return 'tag' - case 'name': - return 'string' - } - } - - private getFlowRunTypeFromProperty(property: Required['property']): Required['type'] { - switch (property) { - case 'tag': - return 'tag' - case 'name': - return 'string' - case 'start_date': - return 'date' // or time - case 'state': - return 'state' - } - } - - private getTaskRunTypeFromProperty(property: Required['property']): Required['type'] { - switch (property) { - case 'tag': - return 'tag' - case 'name': - return 'string' - case 'start_date': - return 'date' // or time - case 'state': - return 'state' - } - } - - private getTagTypeFromProperty(property: Required['property']): Required['type'] { - switch (property) { - case 'name': - return 'string' - } - } - -} \ No newline at end of file diff --git a/orion-ui/packages/orion-design/src/services/FilterTagService.ts b/orion-ui/packages/orion-design/src/services/FilterStringifyService.ts similarity index 98% rename from orion-ui/packages/orion-design/src/services/FilterTagService.ts rename to orion-ui/packages/orion-design/src/services/FilterStringifyService.ts index 1c1d800f5844..0453c98397a1 100644 --- a/orion-ui/packages/orion-design/src/services/FilterTagService.ts +++ b/orion-ui/packages/orion-design/src/services/FilterStringifyService.ts @@ -11,7 +11,7 @@ import { } from '../types/filters' import { formatDateTimeNumeric } from '../utilities/dates' -export class FilterTagService { +export class FilterStringifyService { public convertFiltersToTags(filters: Required[]): string[] { return filters.map(filter => this.convertFilterToTag(filter)) } diff --git a/orion-ui/packages/orion-design/src/types/filters/index.ts b/orion-ui/packages/orion-design/src/types/filters/index.ts index a7a6bcc0617b..d79b4ef00bd2 100644 --- a/orion-ui/packages/orion-design/src/types/filters/index.ts +++ b/orion-ui/packages/orion-design/src/types/filters/index.ts @@ -57,9 +57,21 @@ const ObjectTagPrefixDictionaryData = { 'tag': 't', } as const +const ObjectTagSuffixDictionaryData = { + 'deployment': ['', 't'], + 'flow': ['', 't'], + 'flow_run': ['', 't', 'a', 'b', 'n', 'o'], + 'task_run': ['', 't', 'a', 'b', 'n', 'o'], + 'tag': [''], +} as const + export type FilterTagPrefix = typeof ObjectTagPrefixDictionaryData[FilterObject] +export type FilterTagSuffix = typeof ObjectTagSuffixDictionaryData[FilterObject][number] + +export type ObjectFilterTagSuffix = typeof ObjectTagSuffixDictionaryData[T][number] export const ObjectTagPrefixes = Object.values(ObjectTagPrefixDictionaryData) +export const ObjectTagSuffixes = Object.values(ObjectTagSuffixDictionaryData).flat() export const TagPrefixObjectDictionary = flip(ObjectTagPrefixDictionaryData) export const ObjectTagPrefixDictionary = flip(TagPrefixObjectDictionary) From 679fba145c82cd4d423da9716f0afb6f6fdc63be Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Thu, 3 Feb 2022 10:44:54 -0600 Subject: [PATCH 06/14] Little clean up --- .../src/services/KeyValueStore.ts | 58 ------------------- .../orion-design/src/services/index.ts | 2 +- 2 files changed, 1 insertion(+), 59 deletions(-) delete mode 100644 orion-ui/packages/orion-design/src/services/KeyValueStore.ts diff --git a/orion-ui/packages/orion-design/src/services/KeyValueStore.ts b/orion-ui/packages/orion-design/src/services/KeyValueStore.ts deleted file mode 100644 index 153bdbb8f37e..000000000000 --- a/orion-ui/packages/orion-design/src/services/KeyValueStore.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { FilterObject } from '..' - -export class KeyValueStore { - private readonly keyMap = new Map() - private readonly valueMap = new Map() - - public constructor(entries?: readonly (readonly [K, V])[] | null) { - entries?.forEach(([key, value]) => { - this.set(key, value) - }) - } - - public getValue(key: K): V | undefined { - return this.keyMap.get(key) - } - - public getKey(value: V): K | undefined { - return this.valueMap.get(value) - } - - public has(key: K, value: V): boolean { - return this.keyMap.has(key) && this.valueMap.has(value) - } - - public hasKey(key: K): boolean { - return this.keyMap.has(key) - } - - public hasValue(value: V): boolean { - return this.valueMap.has(value) - } - - public set(key: K, value: V): this { - this.keyMap.set(key, value) - this.valueMap.set(value, key) - - return this - } - - public delete(key: K, value: V): boolean { - this.keyMap.delete(key) - this.valueMap.delete(value) - - return true - } - - public clear(): boolean { - this.keyMap.clear() - this.valueMap.clear() - - return true - } -} - -const store = new KeyValueStore([['flow', 'f']]) - -const test = store.getKey('f') -const test2 = store.getValue('flow') \ No newline at end of file diff --git a/orion-ui/packages/orion-design/src/services/index.ts b/orion-ui/packages/orion-design/src/services/index.ts index bfbbab62cbbe..1961c6e77e2d 100644 --- a/orion-ui/packages/orion-design/src/services/index.ts +++ b/orion-ui/packages/orion-design/src/services/index.ts @@ -1,6 +1,6 @@ export * from './Api' export * from './Filter' -export * from './FilterTagService' +export * from './FilterStringifyService' export * from './LogsApi' export * from './Mocker' export * from './SimpleIdManager' From 867d6d8027f22c12da36f6fed41d888ac60fc2e5 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Thu, 3 Feb 2022 13:45:07 -0600 Subject: [PATCH 07/14] Working FilterParseService --- .../src/services/FilterParseService.ts | 47 +++++++++++++++++-- .../orion-design/src/services/index.ts | 1 + .../orion-design/src/types/filters/index.ts | 4 +- .../orion-design/src/utilities/dates.ts | 27 +++++++++-- 4 files changed, 68 insertions(+), 11 deletions(-) diff --git a/orion-ui/packages/orion-design/src/services/FilterParseService.ts b/orion-ui/packages/orion-design/src/services/FilterParseService.ts index 9de39c38bf2a..f062c94d08eb 100644 --- a/orion-ui/packages/orion-design/src/services/FilterParseService.ts +++ b/orion-ui/packages/orion-design/src/services/FilterParseService.ts @@ -4,6 +4,7 @@ import { FilterObject, FilterOperation, FilterProperty, + FilterTagPrefix, FilterTagSuffix, FilterType, FilterValue, @@ -18,8 +19,12 @@ import { ObjectTimeFilter, TagPrefixObjectDictionary } from '../types' +import { parseDateTimeNumeric } from '../utilities/dates' export class FilterParseService { + + private readonly dateParsers: ((input: string) => Date)[] = [parseDateTimeNumeric, (input: string) => new Date(input)] + public convertTagsToFilters(tags: string[]): Required[] { return tags.map(tag => this.convertTagToFilter(tag)) } @@ -50,7 +55,7 @@ export class FilterParseService { // private parseLongTag(tag: string): any {} private parseObject(tag: string): FilterObject { - const prefix = ObjectTagPrefixes.find(prefix => tag.startsWith(prefix)) + const prefix = ObjectTagPrefixes.sort((a, b) => b.length - a.length).find(prefix => tag.startsWith(prefix)) if (prefix === undefined) { throw 'tag does not start with a valid prefix' @@ -61,7 +66,7 @@ export class FilterParseService { private parseSuffix(object: FilterObject, tag: string): FilterTagSuffix { const prefix = ObjectTagPrefixDictionary[object] - const regex = new RegExp(`${prefix}(.*):.*`) + const regex = new RegExp(`^${prefix}([^:]*):.*$`) const match = tag.match(regex) if (match === null) { @@ -70,6 +75,8 @@ export class FilterParseService { const [, suffix] = match + console.log({ suffix }) + if (!this.isSuffix(suffix)) { throw 'tag does not contain a valid suffix' } @@ -112,6 +119,8 @@ export class FilterParseService { case 'n': case 'o': return { type: 'time', property: 'start_date' } + case 's': + return { type: 'state', property: 'state' } } } @@ -132,7 +141,8 @@ export class FilterParseService { } private parseOperationAndValue(type: FilterType, tag: string, suffix: FilterTagSuffix): { operation: FilterOperation, value: FilterValue } { - const [, input] = tag.split(':') + const [, ...rest] = tag.split(':') + const input = rest.join(':') switch (type) { case 'string': @@ -168,7 +178,7 @@ export class FilterParseService { } private parseDateOperationAndValue(input: string, suffix: 'a' | 'b'): { operation: ObjectDateFilter['operation'], value: ObjectDateFilter['value'] } { - const value = new Date(input) + const value = this.parseDateValue(input) switch (suffix) { case 'a': @@ -201,10 +211,16 @@ export class FilterParseService { private parseStateOperationAndValue(input: string): { operation: ObjectStateFilter['operation'], value: ObjectStateFilter['value'] } { return { operation: 'or', - value: input.split(','), + value: input.split('|'), } } + // using any here because typescript doesn't like string type... + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private isPrefix(value: any): value is FilterTagPrefix { + return ObjectTagPrefixes.includes(value) + } + // using any here because typescript doesn't like string type... // eslint-disable-next-line @typescript-eslint/no-explicit-any private isSuffix(value: any): value is FilterTagSuffix { @@ -215,4 +231,25 @@ export class FilterParseService { return /^[0-1]+[h,d,w,m,w]$/.test(value) } + private parseDateValue(input: string): Date { + console.log({ input }) + let value: Date + const parsers = [...this.dateParsers] + + do { + const parser = parsers.pop()! + value = parser(input) + } while (!this.isValidDate(value) && parsers.length) + + if (!this.isValidDate(value)) { + throw 'filter date value is invalid' + } + + return value + } + + private isValidDate(input: Date): boolean { + return !isNaN(input.getTime()) + } + } \ No newline at end of file diff --git a/orion-ui/packages/orion-design/src/services/index.ts b/orion-ui/packages/orion-design/src/services/index.ts index 1961c6e77e2d..3e1c8f84af2b 100644 --- a/orion-ui/packages/orion-design/src/services/index.ts +++ b/orion-ui/packages/orion-design/src/services/index.ts @@ -1,5 +1,6 @@ export * from './Api' export * from './Filter' +export * from './FilterParseService' export * from './FilterStringifyService' export * from './LogsApi' export * from './Mocker' diff --git a/orion-ui/packages/orion-design/src/types/filters/index.ts b/orion-ui/packages/orion-design/src/types/filters/index.ts index d79b4ef00bd2..e6b3c0a206e2 100644 --- a/orion-ui/packages/orion-design/src/types/filters/index.ts +++ b/orion-ui/packages/orion-design/src/types/filters/index.ts @@ -60,8 +60,8 @@ const ObjectTagPrefixDictionaryData = { const ObjectTagSuffixDictionaryData = { 'deployment': ['', 't'], 'flow': ['', 't'], - 'flow_run': ['', 't', 'a', 'b', 'n', 'o'], - 'task_run': ['', 't', 'a', 'b', 'n', 'o'], + 'flow_run': ['', 't', 's', 'a', 'b', 'n', 'o'], + 'task_run': ['', 't', 's', 'a', 'b', 'n', 'o'], 'tag': [''], } as const diff --git a/orion-ui/packages/orion-design/src/utilities/dates.ts b/orion-ui/packages/orion-design/src/utilities/dates.ts index ebf9976f3451..aab972267e29 100644 --- a/orion-ui/packages/orion-design/src/utilities/dates.ts +++ b/orion-ui/packages/orion-design/src/utilities/dates.ts @@ -1,19 +1,38 @@ -import { format } from 'date-fns' +// duplicate imports are necessary for datefns tree shaking +/* eslint-disable import/no-duplicates */ +import format from 'date-fns/format' +import parse from 'date-fns/parse' + +const dateTimeNumericFormat = 'yyyy/MM/dd hh:mm:ss a' +const timeNumericFormat = 'hh:mm:ss a' +const dateFormat = 'MMM do, yyyy' export function formatDateTimeNumeric(date: Date | string): string { const parsed = new Date(date) - return format(parsed, 'yyyy/MM/dd hh:mm:ss a') + return format(parsed, dateTimeNumericFormat) +} + +export function parseDateTimeNumeric(input: string, reference: Date = new Date()): Date { + return parse(input, dateTimeNumericFormat, reference) } export function formatTimeNumeric(date: Date | string): string { const parsed = new Date(date) - return format(parsed, 'hh:mm:ss a') + return format(parsed, timeNumericFormat) +} + +export function parseTimeNumeric(input: string, reference: Date = new Date()): Date { + return parse(input, timeNumericFormat, reference) } export function formatDate(date: Date | string): string { const parsed = new Date(date) - return format(parsed, 'MMM do, yyyy') + return format(parsed, dateFormat) +} + +export function parseDate(input: string, reference: Date = new Date()): Date { + return parse(input, dateFormat, reference) } From f433e6be7408857e9f97bcb5a11633786da9e3c9 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Thu, 3 Feb 2022 16:17:43 -0600 Subject: [PATCH 08/14] Commit that has both parser versions for history --- .../src/services/FilterParseService.ts | 490 +++++++++++------- 1 file changed, 305 insertions(+), 185 deletions(-) diff --git a/orion-ui/packages/orion-design/src/services/FilterParseService.ts b/orion-ui/packages/orion-design/src/services/FilterParseService.ts index f062c94d08eb..592aa1c79991 100644 --- a/orion-ui/packages/orion-design/src/services/FilterParseService.ts +++ b/orion-ui/packages/orion-design/src/services/FilterParseService.ts @@ -21,218 +21,326 @@ import { } from '../types' import { parseDateTimeNumeric } from '../utilities/dates' -export class FilterParseService { - - private readonly dateParsers: ((input: string) => Date)[] = [parseDateTimeNumeric, (input: string) => new Date(input)] - - public convertTagsToFilters(tags: string[]): Required[] { - return tags.map(tag => this.convertTagToFilter(tag)) - } - - public convertTagToFilter(tag: string): Required { - // if(isLongTag) { - // return this.parseLongTag(tag) - // } - - return this.parseShortTag(tag) - } - - private parseShortTag(tag: string): Required { - const object = this.parseObject(tag) - const suffix = this.parseSuffix(object, tag) - const { type, property } = this.parseTypeAndProperty(object, suffix) - const { operation, value } = this.parseOperationAndValue(type, tag, suffix) - - return { - object, - property, - type, - operation, - value, - } as Required - } - - // private parseLongTag(tag: string): any {} +// export class FilterParseService { + +// private readonly dateParsers: ((input: string) => Date)[] = [parseDateTimeNumeric, (input: string) => new Date(input)] + +// public convertTagsToFilters(tags: string[]): Required[] { +// return tags.map(tag => this.convertTagToFilter(tag)) +// } + +// public convertTagToFilter(tag: string): Required { +// // if(isLongTag) { +// // return this.parseLongTag(tag) +// // } + +// return this.parseShortTag(tag) +// } + +// private parseShortTag(tag: string): Required { +// const object = this.parseObject(tag) +// const suffix = this.parseSuffix(object, tag) +// const { type, property } = this.parseTypeAndProperty(object, suffix) +// const { operation, value } = this.parseOperationAndValue(type, tag, suffix) + +// return { +// object, +// property, +// type, +// operation, +// value, +// } as Required +// } + +// // private parseLongTag(tag: string): any {} + +// private parseObject(tag: string): FilterObject { +// const prefix = ObjectTagPrefixes.sort((a, b) => b.length - a.length).find(prefix => tag.startsWith(prefix)) + +// if (prefix === undefined) { +// throw 'tag does not start with a valid prefix' +// } + +// return TagPrefixObjectDictionary[prefix] +// } + +// private parseSuffix(object: FilterObject, tag: string): FilterTagSuffix { +// const prefix = ObjectTagPrefixDictionary[object] +// const regex = new RegExp(`^${prefix}([^:]*):.*$`) +// const match = tag.match(regex) + +// if (match === null) { +// throw 'tag does not contain a suffix' +// } + +// const [, suffix] = match + +// if (!this.isSuffix(suffix)) { +// throw 'tag does not contain a valid suffix' +// } + +// return suffix +// } + +// private parseTypeAndProperty(object: FilterObject, suffix: FilterTagSuffix): { type: FilterType, property: FilterProperty } { +// switch (object) { +// case 'flow': +// return this.parseFlowTypeAndProperty(suffix as ObjectFilterTagSuffix<'flow'>) +// case 'deployment': +// return this.parseDeploymentTypeAndProperty(suffix as ObjectFilterTagSuffix<'deployment'>) +// case 'flow_run': +// case 'task_run': +// return this.parseRunTypeAndProperty(suffix as ObjectFilterTagSuffix<'flow_run' | 'task_run'>) +// case 'tag': +// return this.parseTagTypeAndProperty(suffix as ObjectFilterTagSuffix<'tag'>) +// } +// } + +// private parseFlowTypeAndProperty(suffix: ObjectFilterTagSuffix<'flow'>): { type: FilterType, property: FilterProperty } { +// switch (suffix) { +// case '': +// return { type: 'string', property: 'name' } +// case 't': +// return { type: 'tag', property: 'tag' } +// } +// } + +// private parseRunTypeAndProperty(suffix: ObjectFilterTagSuffix<'flow_run' | 'task_run'>): { type: FilterType, property: FilterProperty } { +// switch (suffix) { +// case '': +// return { type: 'string', property: 'name' } +// case 't': +// return { type: 'tag', property: 'tag' } +// case 'a': +// case 'b': +// return { type: 'date', property: 'start_date' } +// case 'n': +// case 'o': +// return { type: 'time', property: 'start_date' } +// case 's': +// return { type: 'state', property: 'state' } +// } +// } + +// private parseDeploymentTypeAndProperty(suffix: ObjectFilterTagSuffix<'deployment'>): { type: FilterType, property: FilterProperty } { +// switch (suffix) { +// case '': +// return { type: 'string', property: 'name' } +// case 't': +// return { type: 'tag', property: 'tag' } +// } +// } + +// private parseTagTypeAndProperty(suffix: ObjectFilterTagSuffix<'tag'>): { type: FilterType, property: FilterProperty } { +// switch (suffix) { +// case '': +// return { type: 'string', property: 'name' } +// } +// } + +// private parseOperationAndValue(type: FilterType, tag: string, suffix: FilterTagSuffix): { operation: FilterOperation, value: FilterValue } { +// const [, ...rest] = tag.split(':') +// const input = rest.join(':') + +// switch (type) { +// case 'string': +// return this.parseStringOperationAndValue(input) +// case 'tag': +// return this.parseTagOperationAndValue(input) +// case 'state': +// return this.parseStateOperationAndValue(input) +// case 'date': +// return this.parseDateOperationAndValue(input, suffix as 'a' | 'b') +// case 'time': +// return this.parseTimeOperationAndValue(input, suffix as 'n' | 'o') +// } +// } + +// private parseStringOperationAndValue(input: string): { operation: ObjectStringFilter['operation'], value: ObjectStringFilter['value'] } { +// const exactOperationRegex = /^"(.*)"$/ +// const match = input.match(exactOperationRegex) + +// if (match) { +// const [, value] = match + +// return { +// operation: 'equals', +// value, +// } +// } + +// return { +// operation: 'contains', +// value: input, +// } +// } + +// private parseDateOperationAndValue(input: string, suffix: 'a' | 'b'): { operation: ObjectDateFilter['operation'], value: ObjectDateFilter['value'] } { +// const value = this.parseDateValue(input) + +// switch (suffix) { +// case 'a': +// return { operation: 'after', value } +// case 'b': +// return { operation: 'before', value } +// } +// } + +// private parseTimeOperationAndValue(input: string, suffix: 'n' | 'o'): { operation: ObjectTimeFilter['operation'], value: ObjectTimeFilter['value'] } { +// if (!this.isTime(input)) { +// throw 'invalid time value' +// } + +// switch (suffix) { +// case 'n': +// return { operation: 'newer', value: input } +// case 'o': +// return { operation: 'older', value: input } +// } +// } + +// private parseTagOperationAndValue(input: string): { operation: ObjectTagFilter['operation'], value: ObjectTagFilter['value'] } { +// return { +// operation: 'and', +// value: input.split(','), +// } +// } + +// private parseStateOperationAndValue(input: string): { operation: ObjectStateFilter['operation'], value: ObjectStateFilter['value'] } { +// return { +// operation: 'or', +// value: input.split('|'), +// } +// } + +// // using any here because typescript doesn't like string type... +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// private isPrefix(value: any): value is FilterTagPrefix { +// return ObjectTagPrefixes.includes(value) +// } + +// // using any here because typescript doesn't like string type... +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// private isSuffix(value: any): value is FilterTagSuffix { +// return ObjectTagSuffixes.includes(value) +// } + +// private isTime(value: string): value is ObjectTimeFilter['value'] { +// return /^[0-1]+[h,d,w,m,w]$/.test(value) +// } + +// private parseDateValue(input: string): Date { +// let value: Date +// const parsers = [...this.dateParsers] + +// do { +// const parser = parsers.pop()! +// value = parser(input) +// } while (!this.isValidDate(value) && parsers.length) + +// if (!this.isValidDate(value)) { +// throw 'filter date value is invalid' +// } + +// return value +// } + +// private isValidDate(input: Date): boolean { +// return !isNaN(input.getTime()) +// } + +// } - private parseObject(tag: string): FilterObject { - const prefix = ObjectTagPrefixes.sort((a, b) => b.length - a.length).find(prefix => tag.startsWith(prefix)) - - if (prefix === undefined) { - throw 'tag does not start with a valid prefix' - } - - return TagPrefixObjectDictionary[prefix] - } - - private parseSuffix(object: FilterObject, tag: string): FilterTagSuffix { - const prefix = ObjectTagPrefixDictionary[object] - const regex = new RegExp(`^${prefix}([^:]*):.*$`) - const match = tag.match(regex) - - if (match === null) { - throw 'tag does not contain a suffix' - } - - const [, suffix] = match +export class FilterParseService { - console.log({ suffix }) + private readonly dateParsers: ((input: string) => Date)[] = [ + parseDateTimeNumeric, + (input: string) => new Date(input), + ] - if (!this.isSuffix(suffix)) { - throw 'tag does not contain a valid suffix' - } - - return suffix - } + public parseFilterString(input: string): Required { + const [prefix, ...rest] = input.split(':') + const value = rest.join(':') - private parseTypeAndProperty(object: FilterObject, suffix: FilterTagSuffix): { type: FilterType, property: FilterProperty } { - switch (object) { - case 'flow': - return this.parseFlowTypeAndProperty(suffix as ObjectFilterTagSuffix<'flow'>) + switch (prefix) { + case 'd': case 'deployment': - return this.parseDeploymentTypeAndProperty(suffix as ObjectFilterTagSuffix<'deployment'>) + return this.stringFilter('deployment', 'name', value) + case 'dt': + case 'deployment_tag': + return this.tagFilter('deployment', value) + case 'f': + case 'flow': + return this.stringFilter('flow', 'name', value) + case 'ft': + case 'flow_tag': + return this.tagFilter('flow', value) + case 'fr': case 'flow_run': + return this.stringFilter('flow_run', 'name', value) + case 'fra': + case 'flow_run_after': + return this.dateFilter('flow_run', 'start_date', 'after', value) + case 'frb': + case 'flow_run_before': + return this.dateFilter('flow_run', 'start_date', 'before', value) + case 'frn': + case 'flow_run_newer': + return this.filter('flow_run', 'start_date', 'time', 'newer', value) + case 'frs': + case 'flow_run_state': + return this.stateFilter('flow_run', 'state', value) + case 'tr': case 'task_run': - return this.parseRunTypeAndProperty(suffix as ObjectFilterTagSuffix<'flow_run' | 'task_run'>) - case 'tag': - return this.parseTagTypeAndProperty(suffix as ObjectFilterTagSuffix<'tag'>) - } - } - - private parseFlowTypeAndProperty(suffix: ObjectFilterTagSuffix<'flow'>): { type: FilterType, property: FilterProperty } { - switch (suffix) { - case '': - return { type: 'string', property: 'name' } - case 't': - return { type: 'tag', property: 'tag' } + return this.stringFilter('task_run', 'name', value) + case 'tra': + case 'task_run_after': + return this.dateFilter('task_run', 'start_date', 'after', value) + case 'trb': + case 'task_run_before': + return this.dateFilter('task_run', 'start_date', 'before', value) + case 'trn': + case 'task_run_newer': + return this.filter('task_run', 'start_date', 'time', 'newer', value) + case 'trs': + case 'task_run_state': + return this.stateFilter('task_run', 'state', value) + default: + throw 'filter has an invalid prefix' } } - private parseRunTypeAndProperty(suffix: ObjectFilterTagSuffix<'flow_run' | 'task_run'>): { type: FilterType, property: FilterProperty } { - switch (suffix) { - case '': - return { type: 'string', property: 'name' } - case 't': - return { type: 'tag', property: 'tag' } - case 'a': - case 'b': - return { type: 'date', property: 'start_date' } - case 'n': - case 'o': - return { type: 'time', property: 'start_date' } - case 's': - return { type: 'state', property: 'state' } - } - } - - private parseDeploymentTypeAndProperty(suffix: ObjectFilterTagSuffix<'deployment'>): { type: FilterType, property: FilterProperty } { - switch (suffix) { - case '': - return { type: 'string', property: 'name' } - case 't': - return { type: 'tag', property: 'tag' } - } - } - - private parseTagTypeAndProperty(suffix: ObjectFilterTagSuffix<'tag'>): { type: FilterType, property: FilterProperty } { - switch (suffix) { - case '': - return { type: 'string', property: 'name' } - } - } - - private parseOperationAndValue(type: FilterType, tag: string, suffix: FilterTagSuffix): { operation: FilterOperation, value: FilterValue } { - const [, ...rest] = tag.split(':') - const input = rest.join(':') - - switch (type) { - case 'string': - return this.parseStringOperationAndValue(input) - case 'tag': - return this.parseTagOperationAndValue(input) - case 'state': - return this.parseStateOperationAndValue(input) - case 'date': - return this.parseDateOperationAndValue(input, suffix as 'a' | 'b') - case 'time': - return this.parseTimeOperationAndValue(input, suffix as 'n' | 'o') - } - } - - private parseStringOperationAndValue(input: string): { operation: ObjectStringFilter['operation'], value: ObjectStringFilter['value'] } { + private stringFilter(object: FilterObject, property: FilterProperty, input: string): Required { const exactOperationRegex = /^"(.*)"$/ const match = input.match(exactOperationRegex) - if (match) { - const [, value] = match + let value: ObjectStringFilter['value'] = input + let operation: ObjectStringFilter['operation'] = 'contains' - return { - operation: 'equals', - value, - } + if (match) { + [, value] = match + operation = 'equals' } - return { - operation: 'contains', - value: input, - } + return this.filter(object, property, 'string', operation, value) } - private parseDateOperationAndValue(input: string, suffix: 'a' | 'b'): { operation: ObjectDateFilter['operation'], value: ObjectDateFilter['value'] } { + // eslint-disable-next-line max-params + private dateFilter(object: FilterObject, property: FilterProperty, operation: FilterOperation, input: string): Required { const value = this.parseDateValue(input) - switch (suffix) { - case 'a': - return { operation: 'after', value } - case 'b': - return { operation: 'before', value } - } - } - - private parseTimeOperationAndValue(input: string, suffix: 'n' | 'o'): { operation: ObjectTimeFilter['operation'], value: ObjectTimeFilter['value'] } { - if (!this.isTime(input)) { - throw 'invalid time value' - } - - switch (suffix) { - case 'n': - return { operation: 'newer', value: input } - case 'o': - return { operation: 'older', value: input } - } - } - - private parseTagOperationAndValue(input: string): { operation: ObjectTagFilter['operation'], value: ObjectTagFilter['value'] } { - return { - operation: 'and', - value: input.split(','), - } - } - - private parseStateOperationAndValue(input: string): { operation: ObjectStateFilter['operation'], value: ObjectStateFilter['value'] } { - return { - operation: 'or', - value: input.split('|'), - } + return this.filter(object, property, 'date', operation, value) } - // using any here because typescript doesn't like string type... - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private isPrefix(value: any): value is FilterTagPrefix { - return ObjectTagPrefixes.includes(value) + private tagFilter(object: FilterObject, input: string): Required { + return this.filter(object, 'tag', 'tag', 'and', input.split(',')) } - // using any here because typescript doesn't like string type... - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private isSuffix(value: any): value is FilterTagSuffix { - return ObjectTagSuffixes.includes(value) - } - - private isTime(value: string): value is ObjectTimeFilter['value'] { - return /^[0-1]+[h,d,w,m,w]$/.test(value) + private stateFilter(object: FilterObject, property: FilterProperty, input: string): Required { + return this.filter(object, property, 'state', 'or', input.split('|')) } private parseDateValue(input: string): Date { - console.log({ input }) let value: Date const parsers = [...this.dateParsers] @@ -252,4 +360,16 @@ export class FilterParseService { return !isNaN(input.getTime()) } + + // eslint-disable-next-line max-params + private filter(object: FilterObject, property: FilterProperty, type: FilterType, operation: FilterOperation, value: FilterValue): Required { + return { + object, + property, + type, + operation, + value, + } as Required + } + } \ No newline at end of file From 0305e4bc658b50cfea5b24da076a2ee5b437e0b2 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Thu, 3 Feb 2022 16:18:19 -0600 Subject: [PATCH 09/14] Parser that just uses a switch --- .../src/services/FilterParseService.ts | 244 +----------------- 1 file changed, 1 insertion(+), 243 deletions(-) diff --git a/orion-ui/packages/orion-design/src/services/FilterParseService.ts b/orion-ui/packages/orion-design/src/services/FilterParseService.ts index 592aa1c79991..0bc6fbcfa5a3 100644 --- a/orion-ui/packages/orion-design/src/services/FilterParseService.ts +++ b/orion-ui/packages/orion-design/src/services/FilterParseService.ts @@ -1,256 +1,14 @@ -/* eslint-disable default-case */ import { Filter, FilterObject, FilterOperation, FilterProperty, - FilterTagPrefix, - FilterTagSuffix, FilterType, FilterValue, - ObjectDateFilter, - ObjectFilterTagSuffix, - ObjectStateFilter, - ObjectStringFilter, - ObjectTagFilter, - ObjectTagPrefixDictionary, - ObjectTagPrefixes, - ObjectTagSuffixes, - ObjectTimeFilter, - TagPrefixObjectDictionary + ObjectStringFilter } from '../types' import { parseDateTimeNumeric } from '../utilities/dates' -// export class FilterParseService { - -// private readonly dateParsers: ((input: string) => Date)[] = [parseDateTimeNumeric, (input: string) => new Date(input)] - -// public convertTagsToFilters(tags: string[]): Required[] { -// return tags.map(tag => this.convertTagToFilter(tag)) -// } - -// public convertTagToFilter(tag: string): Required { -// // if(isLongTag) { -// // return this.parseLongTag(tag) -// // } - -// return this.parseShortTag(tag) -// } - -// private parseShortTag(tag: string): Required { -// const object = this.parseObject(tag) -// const suffix = this.parseSuffix(object, tag) -// const { type, property } = this.parseTypeAndProperty(object, suffix) -// const { operation, value } = this.parseOperationAndValue(type, tag, suffix) - -// return { -// object, -// property, -// type, -// operation, -// value, -// } as Required -// } - -// // private parseLongTag(tag: string): any {} - -// private parseObject(tag: string): FilterObject { -// const prefix = ObjectTagPrefixes.sort((a, b) => b.length - a.length).find(prefix => tag.startsWith(prefix)) - -// if (prefix === undefined) { -// throw 'tag does not start with a valid prefix' -// } - -// return TagPrefixObjectDictionary[prefix] -// } - -// private parseSuffix(object: FilterObject, tag: string): FilterTagSuffix { -// const prefix = ObjectTagPrefixDictionary[object] -// const regex = new RegExp(`^${prefix}([^:]*):.*$`) -// const match = tag.match(regex) - -// if (match === null) { -// throw 'tag does not contain a suffix' -// } - -// const [, suffix] = match - -// if (!this.isSuffix(suffix)) { -// throw 'tag does not contain a valid suffix' -// } - -// return suffix -// } - -// private parseTypeAndProperty(object: FilterObject, suffix: FilterTagSuffix): { type: FilterType, property: FilterProperty } { -// switch (object) { -// case 'flow': -// return this.parseFlowTypeAndProperty(suffix as ObjectFilterTagSuffix<'flow'>) -// case 'deployment': -// return this.parseDeploymentTypeAndProperty(suffix as ObjectFilterTagSuffix<'deployment'>) -// case 'flow_run': -// case 'task_run': -// return this.parseRunTypeAndProperty(suffix as ObjectFilterTagSuffix<'flow_run' | 'task_run'>) -// case 'tag': -// return this.parseTagTypeAndProperty(suffix as ObjectFilterTagSuffix<'tag'>) -// } -// } - -// private parseFlowTypeAndProperty(suffix: ObjectFilterTagSuffix<'flow'>): { type: FilterType, property: FilterProperty } { -// switch (suffix) { -// case '': -// return { type: 'string', property: 'name' } -// case 't': -// return { type: 'tag', property: 'tag' } -// } -// } - -// private parseRunTypeAndProperty(suffix: ObjectFilterTagSuffix<'flow_run' | 'task_run'>): { type: FilterType, property: FilterProperty } { -// switch (suffix) { -// case '': -// return { type: 'string', property: 'name' } -// case 't': -// return { type: 'tag', property: 'tag' } -// case 'a': -// case 'b': -// return { type: 'date', property: 'start_date' } -// case 'n': -// case 'o': -// return { type: 'time', property: 'start_date' } -// case 's': -// return { type: 'state', property: 'state' } -// } -// } - -// private parseDeploymentTypeAndProperty(suffix: ObjectFilterTagSuffix<'deployment'>): { type: FilterType, property: FilterProperty } { -// switch (suffix) { -// case '': -// return { type: 'string', property: 'name' } -// case 't': -// return { type: 'tag', property: 'tag' } -// } -// } - -// private parseTagTypeAndProperty(suffix: ObjectFilterTagSuffix<'tag'>): { type: FilterType, property: FilterProperty } { -// switch (suffix) { -// case '': -// return { type: 'string', property: 'name' } -// } -// } - -// private parseOperationAndValue(type: FilterType, tag: string, suffix: FilterTagSuffix): { operation: FilterOperation, value: FilterValue } { -// const [, ...rest] = tag.split(':') -// const input = rest.join(':') - -// switch (type) { -// case 'string': -// return this.parseStringOperationAndValue(input) -// case 'tag': -// return this.parseTagOperationAndValue(input) -// case 'state': -// return this.parseStateOperationAndValue(input) -// case 'date': -// return this.parseDateOperationAndValue(input, suffix as 'a' | 'b') -// case 'time': -// return this.parseTimeOperationAndValue(input, suffix as 'n' | 'o') -// } -// } - -// private parseStringOperationAndValue(input: string): { operation: ObjectStringFilter['operation'], value: ObjectStringFilter['value'] } { -// const exactOperationRegex = /^"(.*)"$/ -// const match = input.match(exactOperationRegex) - -// if (match) { -// const [, value] = match - -// return { -// operation: 'equals', -// value, -// } -// } - -// return { -// operation: 'contains', -// value: input, -// } -// } - -// private parseDateOperationAndValue(input: string, suffix: 'a' | 'b'): { operation: ObjectDateFilter['operation'], value: ObjectDateFilter['value'] } { -// const value = this.parseDateValue(input) - -// switch (suffix) { -// case 'a': -// return { operation: 'after', value } -// case 'b': -// return { operation: 'before', value } -// } -// } - -// private parseTimeOperationAndValue(input: string, suffix: 'n' | 'o'): { operation: ObjectTimeFilter['operation'], value: ObjectTimeFilter['value'] } { -// if (!this.isTime(input)) { -// throw 'invalid time value' -// } - -// switch (suffix) { -// case 'n': -// return { operation: 'newer', value: input } -// case 'o': -// return { operation: 'older', value: input } -// } -// } - -// private parseTagOperationAndValue(input: string): { operation: ObjectTagFilter['operation'], value: ObjectTagFilter['value'] } { -// return { -// operation: 'and', -// value: input.split(','), -// } -// } - -// private parseStateOperationAndValue(input: string): { operation: ObjectStateFilter['operation'], value: ObjectStateFilter['value'] } { -// return { -// operation: 'or', -// value: input.split('|'), -// } -// } - -// // using any here because typescript doesn't like string type... -// // eslint-disable-next-line @typescript-eslint/no-explicit-any -// private isPrefix(value: any): value is FilterTagPrefix { -// return ObjectTagPrefixes.includes(value) -// } - -// // using any here because typescript doesn't like string type... -// // eslint-disable-next-line @typescript-eslint/no-explicit-any -// private isSuffix(value: any): value is FilterTagSuffix { -// return ObjectTagSuffixes.includes(value) -// } - -// private isTime(value: string): value is ObjectTimeFilter['value'] { -// return /^[0-1]+[h,d,w,m,w]$/.test(value) -// } - -// private parseDateValue(input: string): Date { -// let value: Date -// const parsers = [...this.dateParsers] - -// do { -// const parser = parsers.pop()! -// value = parser(input) -// } while (!this.isValidDate(value) && parsers.length) - -// if (!this.isValidDate(value)) { -// throw 'filter date value is invalid' -// } - -// return value -// } - -// private isValidDate(input: Date): boolean { -// return !isNaN(input.getTime()) -// } - -// } - export class FilterParseService { private readonly dateParsers: ((input: string) => Date)[] = [ From 44bf3ecb25e0610b3b387cdd60a012377feb7174 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Thu, 3 Feb 2022 16:23:14 -0600 Subject: [PATCH 10/14] Add a method that takes an array of strings and parses them --- .../packages/orion-design/src/services/FilterParseService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/orion-ui/packages/orion-design/src/services/FilterParseService.ts b/orion-ui/packages/orion-design/src/services/FilterParseService.ts index 0bc6fbcfa5a3..caa857eadca2 100644 --- a/orion-ui/packages/orion-design/src/services/FilterParseService.ts +++ b/orion-ui/packages/orion-design/src/services/FilterParseService.ts @@ -16,6 +16,10 @@ export class FilterParseService { (input: string) => new Date(input), ] + public parseFilterStrings(inputs: string[]): Required[] { + return inputs.map(input => this.parseFilterString(input)) + } + public parseFilterString(input: string): Required { const [prefix, ...rest] = input.split(':') const value = rest.join(':') From cbea2fe4a9106077f7dab64ce93885572e1a19d3 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Fri, 4 Feb 2022 09:27:48 -0600 Subject: [PATCH 11/14] Make filter services static since they have no state --- .../src/services/FilterParseService.ts | 20 +++++++++---------- .../src/services/FilterStringifyService.ts | 20 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/orion-ui/packages/orion-design/src/services/FilterParseService.ts b/orion-ui/packages/orion-design/src/services/FilterParseService.ts index caa857eadca2..6958acdd5b67 100644 --- a/orion-ui/packages/orion-design/src/services/FilterParseService.ts +++ b/orion-ui/packages/orion-design/src/services/FilterParseService.ts @@ -11,16 +11,16 @@ import { parseDateTimeNumeric } from '../utilities/dates' export class FilterParseService { - private readonly dateParsers: ((input: string) => Date)[] = [ + private static readonly dateParsers: ((input: string) => Date)[] = [ parseDateTimeNumeric, (input: string) => new Date(input), ] - public parseFilterStrings(inputs: string[]): Required[] { + public static parseFilterStrings(inputs: string[]): Required[] { return inputs.map(input => this.parseFilterString(input)) } - public parseFilterString(input: string): Required { + public static parseFilterString(input: string): Required { const [prefix, ...rest] = input.split(':') const value = rest.join(':') @@ -72,7 +72,7 @@ export class FilterParseService { } } - private stringFilter(object: FilterObject, property: FilterProperty, input: string): Required { + private static stringFilter(object: FilterObject, property: FilterProperty, input: string): Required { const exactOperationRegex = /^"(.*)"$/ const match = input.match(exactOperationRegex) @@ -88,21 +88,21 @@ export class FilterParseService { } // eslint-disable-next-line max-params - private dateFilter(object: FilterObject, property: FilterProperty, operation: FilterOperation, input: string): Required { + private static dateFilter(object: FilterObject, property: FilterProperty, operation: FilterOperation, input: string): Required { const value = this.parseDateValue(input) return this.filter(object, property, 'date', operation, value) } - private tagFilter(object: FilterObject, input: string): Required { + private static tagFilter(object: FilterObject, input: string): Required { return this.filter(object, 'tag', 'tag', 'and', input.split(',')) } - private stateFilter(object: FilterObject, property: FilterProperty, input: string): Required { + private static stateFilter(object: FilterObject, property: FilterProperty, input: string): Required { return this.filter(object, property, 'state', 'or', input.split('|')) } - private parseDateValue(input: string): Date { + private static parseDateValue(input: string): Date { let value: Date const parsers = [...this.dateParsers] @@ -118,13 +118,13 @@ export class FilterParseService { return value } - private isValidDate(input: Date): boolean { + private static isValidDate(input: Date): boolean { return !isNaN(input.getTime()) } // eslint-disable-next-line max-params - private filter(object: FilterObject, property: FilterProperty, type: FilterType, operation: FilterOperation, value: FilterValue): Required { + private static filter(object: FilterObject, property: FilterProperty, type: FilterType, operation: FilterOperation, value: FilterValue): Required { return { object, property, diff --git a/orion-ui/packages/orion-design/src/services/FilterStringifyService.ts b/orion-ui/packages/orion-design/src/services/FilterStringifyService.ts index 0453c98397a1..428e68b02525 100644 --- a/orion-ui/packages/orion-design/src/services/FilterStringifyService.ts +++ b/orion-ui/packages/orion-design/src/services/FilterStringifyService.ts @@ -12,11 +12,11 @@ import { import { formatDateTimeNumeric } from '../utilities/dates' export class FilterStringifyService { - public convertFiltersToTags(filters: Required[]): string[] { + public static convertFiltersToTags(filters: Required[]): string[] { return filters.map(filter => this.convertFilterToTag(filter)) } - public convertFilterToTag(filter: Required): string { + public static convertFilterToTag(filter: Required): string { const tagPrefix = this.createTagPrefix(filter) const tagSuffix = this.createTagSuffix(filter) const tagValue = this.createTagValue(filter) @@ -24,7 +24,7 @@ export class FilterStringifyService { return `${tagPrefix}${tagSuffix}:${tagValue}` } - private createObjectStringFilterValue(filter: ObjectStringFilter): string { + private static createObjectStringFilterValue(filter: ObjectStringFilter): string { switch (filter.operation) { case 'contains': return filter.value @@ -33,27 +33,27 @@ export class FilterStringifyService { } } - private createObjectDateFilterValue(filter: ObjectDateFilter): string { + private static createObjectDateFilterValue(filter: ObjectDateFilter): string { return formatDateTimeNumeric(filter.value) } - private createObjectTimeFilterValue(filter: ObjectTimeFilter): string { + private static createObjectTimeFilterValue(filter: ObjectTimeFilter): string { return filter.value } - private createObjectStateFilterValue(filter: ObjectStateFilter): string { + private static createObjectStateFilterValue(filter: ObjectStateFilter): string { return filter.value.join('|') } - private createObjectTagFilterValue(filter: ObjectTagFilter): string { + private static createObjectTagFilterValue(filter: ObjectTagFilter): string { return filter.value.join(',') } - private createTagPrefix(filter: Required): FilterTagPrefix { + private static createTagPrefix(filter: Required): FilterTagPrefix { return ObjectTagPrefixDictionary[filter.object] } - private createTagSuffix(filter: Required): string { + private static createTagSuffix(filter: Required): string { switch (filter.type) { case 'string': return '' @@ -66,7 +66,7 @@ export class FilterStringifyService { } } - private createTagValue(filter: Required): string { + private static createTagValue(filter: Required): string { switch (filter.type) { case 'string': return this.createObjectStringFilterValue(filter) From 4c8b7a01aedc74c3f2633130bff8b241364e6ebd Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Fri, 4 Feb 2022 09:36:13 -0600 Subject: [PATCH 12/14] Single FilterService to make a single entry point --- .../src/services/FilterService.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 orion-ui/packages/orion-design/src/services/FilterService.ts diff --git a/orion-ui/packages/orion-design/src/services/FilterService.ts b/orion-ui/packages/orion-design/src/services/FilterService.ts new file mode 100644 index 000000000000..a29b41968413 --- /dev/null +++ b/orion-ui/packages/orion-design/src/services/FilterService.ts @@ -0,0 +1,25 @@ +/* eslint-disable no-dupe-class-members */ +import { Filter } from '../types/filters' +import { FilterParseService, FilterStringifyService } from '.' + +export class FilterService { + public static stringify(filter: Required): string + public static stringify(filters: Required[]): string[] + public static stringify(filterOrFilters: Required | Required[]): string | string[] { + if (Array.isArray(filterOrFilters)) { + return FilterStringifyService.convertFiltersToTags(filterOrFilters) + } + + return FilterStringifyService.convertFilterToTag(filterOrFilters) + } + + public static parse(filter: string): Required + public static parse(filters: string[]): Required[] + public static parse(filterOrFilters: string | string[]): Required | Required[] { + if (Array.isArray(filterOrFilters)) { + return FilterParseService.parseFilterStrings(filterOrFilters) + } + + return FilterParseService.parseFilterString(filterOrFilters) + } +} \ No newline at end of file From f1f70077167f01ffef8a0a7ab209572cd727613f Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Fri, 4 Feb 2022 12:52:47 -0600 Subject: [PATCH 13/14] Rename ObjectTimeFilter to ObjectRelativeDateFilter --- .../orion-design/src/services/FilterStringifyService.ts | 4 ++-- orion-ui/packages/orion-design/src/types/filters/flowRuns.ts | 4 ++-- orion-ui/packages/orion-design/src/types/filters/index.ts | 2 +- orion-ui/packages/orion-design/src/types/filters/taskRuns.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/orion-ui/packages/orion-design/src/services/FilterStringifyService.ts b/orion-ui/packages/orion-design/src/services/FilterStringifyService.ts index 428e68b02525..a1cc83d0353d 100644 --- a/orion-ui/packages/orion-design/src/services/FilterStringifyService.ts +++ b/orion-ui/packages/orion-design/src/services/FilterStringifyService.ts @@ -7,7 +7,7 @@ import { ObjectStringFilter, ObjectTagFilter, ObjectTagPrefixDictionary, - ObjectTimeFilter + ObjectRelativeDateFilter } from '../types/filters' import { formatDateTimeNumeric } from '../utilities/dates' @@ -37,7 +37,7 @@ export class FilterStringifyService { return formatDateTimeNumeric(filter.value) } - private static createObjectTimeFilterValue(filter: ObjectTimeFilter): string { + private static createObjectTimeFilterValue(filter: ObjectRelativeDateFilter): string { return filter.value } diff --git a/orion-ui/packages/orion-design/src/types/filters/flowRuns.ts b/orion-ui/packages/orion-design/src/types/filters/flowRuns.ts index f3e5c47e96cc..0bf42c044afb 100644 --- a/orion-ui/packages/orion-design/src/types/filters/flowRuns.ts +++ b/orion-ui/packages/orion-design/src/types/filters/flowRuns.ts @@ -1,4 +1,4 @@ -import { ObjectStringFilter, ObjectDateFilter, ObjectTagFilter, ObjectStateFilter, ObjectTimeFilter } from '.' +import { ObjectStringFilter, ObjectDateFilter, ObjectTagFilter, ObjectStateFilter, ObjectRelativeDateFilter } from '.' export type FlowRunFilter = { object: 'flow_run', @@ -17,7 +17,7 @@ export type FlowRunDateFilter = { export type FlowRunTimeFilter = { object: 'flow_run', property: 'start_date', -} & Partial +} & Partial export type FlowRunTagFilter = { object: 'flow_run', diff --git a/orion-ui/packages/orion-design/src/types/filters/index.ts b/orion-ui/packages/orion-design/src/types/filters/index.ts index e6b3c0a206e2..e3835bdaea2c 100644 --- a/orion-ui/packages/orion-design/src/types/filters/index.ts +++ b/orion-ui/packages/orion-design/src/types/filters/index.ts @@ -17,7 +17,7 @@ export type ObjectDateFilter = { value: Date, } -export type ObjectTimeFilter = { +export type ObjectRelativeDateFilter = { type: 'time', operation: 'newer' | 'older', value: `${number}h` | `${number}d` | `${number}w` | `${number}m` | `${number}y`, diff --git a/orion-ui/packages/orion-design/src/types/filters/taskRuns.ts b/orion-ui/packages/orion-design/src/types/filters/taskRuns.ts index 46defbef0cf4..1ef4e1ef4e08 100644 --- a/orion-ui/packages/orion-design/src/types/filters/taskRuns.ts +++ b/orion-ui/packages/orion-design/src/types/filters/taskRuns.ts @@ -1,4 +1,4 @@ -import { ObjectStringFilter, ObjectDateFilter, ObjectTagFilter, ObjectStateFilter, ObjectTimeFilter } from '.' +import { ObjectStringFilter, ObjectDateFilter, ObjectTagFilter, ObjectStateFilter, ObjectRelativeDateFilter } from '.' export type TaskRunFilter = { object: 'task_run', @@ -17,7 +17,7 @@ export type TaskRunDateFilter = { export type TaskRunTimeFilter = { object: 'task_run', property: 'start_date', -} & Partial +} & Partial export type TaskRunTagFilter = { object: 'task_run', From 9bb6123428f71715d3325ef8cf9fffcd2e48b794 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Fri, 4 Feb 2022 12:56:40 -0600 Subject: [PATCH 14/14] Add a filter description service and add as a dependency to the FilterService --- .../src/services/FilterDescriptionService.ts | 46 +++++++++++++++++++ .../src/services/FilterService.ts | 5 ++ 2 files changed, 51 insertions(+) create mode 100644 orion-ui/packages/orion-design/src/services/FilterDescriptionService.ts diff --git a/orion-ui/packages/orion-design/src/services/FilterDescriptionService.ts b/orion-ui/packages/orion-design/src/services/FilterDescriptionService.ts new file mode 100644 index 000000000000..5724113986ed --- /dev/null +++ b/orion-ui/packages/orion-design/src/services/FilterDescriptionService.ts @@ -0,0 +1,46 @@ +/* eslint-disable default-case */ +import { Filter, FilterObject, FilterProperty } from '../types/filters' + +export class FilterDescriptionService { + public static describe(filter: Filter): string { + const description: string[] = [ + 'Show', + this.object(filter.object), + 'by', + this.property(filter.property), + ] + + + return description.join(' ').trim() + } + + private static object(object: FilterObject): string { + switch (object) { + case 'flow': + return 'flows' + case 'deployment': + return 'deployments' + case 'flow_run': + return 'flow runs' + case 'task_run': + return 'task runs' + case 'tag': + return 'tags' + } + } + + private static property(property: FilterProperty | undefined): string { + switch (property) { + case 'tag': + return 'tag' + case 'name': + return 'name' + case 'start_date': + return 'start date' + case 'state': + return 'state' + case undefined: + return '...' + } + } +} \ No newline at end of file diff --git a/orion-ui/packages/orion-design/src/services/FilterService.ts b/orion-ui/packages/orion-design/src/services/FilterService.ts index a29b41968413..8ea5f6eb262e 100644 --- a/orion-ui/packages/orion-design/src/services/FilterService.ts +++ b/orion-ui/packages/orion-design/src/services/FilterService.ts @@ -1,5 +1,6 @@ /* eslint-disable no-dupe-class-members */ import { Filter } from '../types/filters' +import { FilterDescriptionService } from './FilterDescriptionService' import { FilterParseService, FilterStringifyService } from '.' export class FilterService { @@ -22,4 +23,8 @@ export class FilterService { return FilterParseService.parseFilterString(filterOrFilters) } + + public static describe(filter: Filter): string { + return FilterDescriptionService.describe(filter) + } } \ No newline at end of file