Skip to content

Commit

Permalink
Merge pull request #942 from PrefectHQ/filters-services
Browse files Browse the repository at this point in the history
UI: Filters Stringify and Parse Services
  • Loading branch information
pleek91 authored Feb 7, 2022
2 parents fd81cc0 + 9bb6123 commit 861208d
Show file tree
Hide file tree
Showing 11 changed files with 396 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -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 '...'
}
}
}
137 changes: 137 additions & 0 deletions orion-ui/packages/orion-design/src/services/FilterParseService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import {
Filter,
FilterObject,
FilterOperation,
FilterProperty,
FilterType,
FilterValue,
ObjectStringFilter
} from '../types'
import { parseDateTimeNumeric } from '../utilities/dates'

export class FilterParseService {

private static readonly dateParsers: ((input: string) => Date)[] = [
parseDateTimeNumeric,
(input: string) => new Date(input),
]

public static parseFilterStrings(inputs: string[]): Required<Filter>[] {
return inputs.map(input => this.parseFilterString(input))
}

public static parseFilterString(input: string): Required<Filter> {
const [prefix, ...rest] = input.split(':')
const value = rest.join(':')

switch (prefix) {
case 'd':
case '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.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 static stringFilter(object: FilterObject, property: FilterProperty, input: string): Required<Filter> {
const exactOperationRegex = /^"(.*)"$/
const match = input.match(exactOperationRegex)

let value: ObjectStringFilter['value'] = input
let operation: ObjectStringFilter['operation'] = 'contains'

if (match) {
[, value] = match
operation = 'equals'
}

return this.filter(object, property, 'string', operation, value)
}

// eslint-disable-next-line max-params
private static dateFilter(object: FilterObject, property: FilterProperty, operation: FilterOperation, input: string): Required<Filter> {
const value = this.parseDateValue(input)

return this.filter(object, property, 'date', operation, value)
}

private static tagFilter(object: FilterObject, input: string): Required<Filter> {
return this.filter(object, 'tag', 'tag', 'and', input.split(','))
}

private static stateFilter(object: FilterObject, property: FilterProperty, input: string): Required<Filter> {
return this.filter(object, property, 'state', 'or', input.split('|'))
}

private static 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 static isValidDate(input: Date): boolean {
return !isNaN(input.getTime())
}


// eslint-disable-next-line max-params
private static filter(object: FilterObject, property: FilterProperty, type: FilterType, operation: FilterOperation, value: FilterValue): Required<Filter> {
return {
object,
property,
type,
operation,
value,
} as Required<Filter>
}

}
30 changes: 30 additions & 0 deletions orion-ui/packages/orion-design/src/services/FilterService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint-disable no-dupe-class-members */
import { Filter } from '../types/filters'
import { FilterDescriptionService } from './FilterDescriptionService'
import { FilterParseService, FilterStringifyService } from '.'

export class FilterService {
public static stringify(filter: Required<Filter>): string
public static stringify(filters: Required<Filter>[]): string[]
public static stringify(filterOrFilters: Required<Filter> | Required<Filter>[]): string | string[] {
if (Array.isArray(filterOrFilters)) {
return FilterStringifyService.convertFiltersToTags(filterOrFilters)
}

return FilterStringifyService.convertFilterToTag(filterOrFilters)
}

public static parse(filter: string): Required<Filter>
public static parse(filters: string[]): Required<Filter>[]
public static parse(filterOrFilters: string | string[]): Required<Filter> | Required<Filter>[] {
if (Array.isArray(filterOrFilters)) {
return FilterParseService.parseFilterStrings(filterOrFilters)
}

return FilterParseService.parseFilterString(filterOrFilters)
}

public static describe(filter: Filter): string {
return FilterDescriptionService.describe(filter)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* eslint-disable default-case */
import {
Filter,
FilterTagPrefix,
ObjectDateFilter,
ObjectStateFilter,
ObjectStringFilter,
ObjectTagFilter,
ObjectTagPrefixDictionary,
ObjectRelativeDateFilter
} from '../types/filters'
import { formatDateTimeNumeric } from '../utilities/dates'

export class FilterStringifyService {
public static convertFiltersToTags(filters: Required<Filter>[]): string[] {
return filters.map(filter => this.convertFilterToTag(filter))
}

public static convertFilterToTag(filter: Required<Filter>): string {
const tagPrefix = this.createTagPrefix(filter)
const tagSuffix = this.createTagSuffix(filter)
const tagValue = this.createTagValue(filter)

return `${tagPrefix}${tagSuffix}:${tagValue}`
}

private static createObjectStringFilterValue(filter: ObjectStringFilter): string {
switch (filter.operation) {
case 'contains':
return filter.value
case 'equals':
return `"${filter.value}"`
}
}

private static createObjectDateFilterValue(filter: ObjectDateFilter): string {
return formatDateTimeNumeric(filter.value)
}

private static createObjectTimeFilterValue(filter: ObjectRelativeDateFilter): string {
return filter.value
}

private static createObjectStateFilterValue(filter: ObjectStateFilter): string {
return filter.value.join('|')
}

private static createObjectTagFilterValue(filter: ObjectTagFilter): string {
return filter.value.join(',')
}

private static createTagPrefix(filter: Required<Filter>): FilterTagPrefix {
return ObjectTagPrefixDictionary[filter.object]
}

private static createTagSuffix(filter: Required<Filter>): string {
switch (filter.type) {
case 'string':
return ''
case 'state':
case 'tag':
return filter.type[0]
case 'date':
case 'time':
return filter.operation[0]
}
}

private static createTagValue(filter: Required<Filter>): 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)
}
}

}
4 changes: 4 additions & 0 deletions orion-ui/packages/orion-design/src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
export * from './Api'
export * from './Filter'
export * from './FilterParseService'
export * from './FilterStringifyService'
export * from './LogsApi'
export * from './Mocker'
export * from './SimpleIdManager'
export * from './StatesApi'
export * from './TaskRunsApi'
11 changes: 8 additions & 3 deletions orion-ui/packages/orion-design/src/types/filters/flowRuns.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ObjectStringFilter, ObjectDateFilter, ObjectTagFilter, ObjectStateFilter } from '.'
import { ObjectStringFilter, ObjectDateFilter, ObjectTagFilter, ObjectStateFilter, ObjectRelativeDateFilter } from '.'

export type FlowRunFilter = {
object: 'flow_run',
} & Partial<(FlowRunStringFilter | FlowRunDateFilter | FlowRunTagFilter | FlowRunStateFilter)>
} & Partial<(FlowRunStringFilter | FlowRunDateFilter | FlowRunTimeFilter | FlowRunTagFilter | FlowRunStateFilter)>

export type FlowRunStringFilter = {
object: 'flow_run',
Expand All @@ -11,9 +11,14 @@ export type FlowRunStringFilter = {

export type FlowRunDateFilter = {
object: 'flow_run',
property: 'start_date' | 'end_date',
property: 'start_date',
} & Partial<ObjectDateFilter>

export type FlowRunTimeFilter = {
object: 'flow_run',
property: 'start_date',
} & Partial<ObjectRelativeDateFilter>

export type FlowRunTagFilter = {
object: 'flow_run',
property: 'tag',
Expand Down
Loading

0 comments on commit 861208d

Please sign in to comment.