From 0a551b1736a5d4c47a5c17f564097be674374aae Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Tue, 25 Jan 2022 14:46:55 -0600 Subject: [PATCH 1/2] pulled out orion-design parts of extract-filters --- .../orion-design/src/services/Filter.ts | 164 ++++++++++++ orion-ui/src/plugins/api.ts | 61 +++++ orion-ui/src/store/getters.ts | 6 + orion-ui/src/types/states.ts | 3 + orion-ui/src/typings/filters.d.ts | 243 +++--------------- orion-ui/src/typings/global.d.ts | 59 ++--- orion-ui/src/utilities/arrays.ts | 6 + orion-ui/src/utilities/dates.ts | 45 ++++ orion-ui/src/utilities/states.ts | 6 +- orion-ui/src/utilities/timeFrame.ts | 37 +++ 10 files changed, 389 insertions(+), 241 deletions(-) create mode 100644 orion-ui/packages/orion-design/src/services/Filter.ts create mode 100644 orion-ui/src/utilities/timeFrame.ts diff --git a/orion-ui/packages/orion-design/src/services/Filter.ts b/orion-ui/packages/orion-design/src/services/Filter.ts new file mode 100644 index 000000000000..dbee6dfda913 --- /dev/null +++ b/orion-ui/packages/orion-design/src/services/Filter.ts @@ -0,0 +1,164 @@ +import { Filter, FlowFilter, FlowRunFilter, TaskRunFilter, DeploymentFilter, StateFilter, TimeFrameFilter } from '@/typings/filters' +import { BaseFilter, GlobalFilter, RunState, RunTimeFrame } from '@/typings/global' +import { isNonEmptyArray } from '@/utilities/arrays' +import { isSubState } from '@/utilities/states' +import { calculateEnd, calculateStart, isValidTimeFrame } from '@/utilities/timeFrame' + +interface Sortable { + sort?: [keyof T], +} + +export type DeploymentsFilter = { deployments?: DeploymentFilter } & Sortable +export type FlowsFilter = { flows?: FlowFilter } & Sortable +export type TaskRunsFilter = { task_runs?: TaskRunFilter } & Sortable +export type FlowRunsFilter = { flow_runs?: FlowRunFilter } & Sortable + +export type UnionFilters = + & FlowsFilter + & DeploymentsFilter + & FlowRunsFilter + & TaskRunsFilter + +interface Historical { + history_start: string, + history_end: string, + history_interval_seconds: number, +} + +export type TaskRunsHistoryFilter = { task_runs?: TaskRunFilter } & Historical +export type FlowRunsHistoryFilter = { flow_runs?: FlowRunFilter } & Historical + + +function buildBaseFilter(baseFilter: BaseFilter): Filter { + const filter: Filter = {} + + if (isNonEmptyArray(baseFilter.ids)) { + filter.id = { any_: baseFilter.ids } + } + + if (isNonEmptyArray(baseFilter.names)) { + filter.name = { any_: baseFilter.names } + } + + if (isNonEmptyArray(baseFilter.tags)) { + filter.tags = { all_: baseFilter.tags } + } + + return filter +} + +function buildTimeFrameFilter(timeFrame: RunTimeFrame | undefined): TimeFrameFilter | undefined { + if (!timeFrame || !isValidTimeFrame(timeFrame.from) && !isValidTimeFrame(timeFrame.from)) { + return undefined + } + + let filter: TimeFrameFilter = {} + + if (isValidTimeFrame(timeFrame.from)) { + filter = { ...filter, after_: calculateStart(timeFrame.from)!.toISOString() } + } + + if (isValidTimeFrame(timeFrame.to)) { + filter = { ...filter, before_: calculateEnd(timeFrame.to)!.toISOString() } + } + + return filter +} + +function buildStateFilter(states: RunState[] | undefined): StateFilter | undefined { + if (!states || !isNonEmptyArray(states)) { + return undefined + } + + let filter: StateFilter = {} + + const [stateNames, stateTypes] = states.reduce<[string[], string[]]>(([stateNames, stateTypes], state) => { + if (isSubState(state.name)) { + stateNames.push(state.name) + } else { + stateTypes.push(state.type) + } + + return [stateNames, stateTypes] + }, [[], []]) + + if (stateNames.length > 0) { + filter = { ...filter, name: { any_: stateNames } } + } + + if (stateTypes.length > 0) { + filter = { ...filter, type: { any_: stateTypes } } + } + + return filter +} + +function buildDeploymentFilter(globalFilter: GlobalFilter): DeploymentFilter { + const filter: DeploymentFilter = buildBaseFilter(globalFilter.deployments) + + return filter +} + +function buildFlowFilter(globalFilter: GlobalFilter): FlowFilter { + const filter: FlowFilter = buildBaseFilter(globalFilter.flows) + + return filter +} + +function buildFlowRunFilter(globalFilter: GlobalFilter): FlowRunFilter { + const filter: FlowRunFilter = buildBaseFilter(globalFilter.flow_runs) + + const startTime = buildTimeFrameFilter(globalFilter.flow_runs.timeframe) + if (startTime) { + filter.expected_start_time = startTime + } + + const state = buildStateFilter(globalFilter.flow_runs.states) + if (state) { + filter.state = state + } + + return filter +} + +function buildTaskRunFilter(globalFilter: GlobalFilter): TaskRunFilter { + const filter: TaskRunFilter = buildBaseFilter(globalFilter.task_runs) + + const startTime = buildTimeFrameFilter(globalFilter.task_runs.timeframe) + if (startTime) { + filter.start_time = startTime + } + + const state = buildStateFilter(globalFilter.task_runs.states) + if (state) { + filter.state = state + } + + return filter +} + +export function buildFilter(globalFilter: GlobalFilter): UnionFilters { + const filters: UnionFilters = {} + + const deployments = buildDeploymentFilter(globalFilter) + if (Object.keys(deployments).length > 0) { + filters.deployments = deployments + } + + const flows = buildFlowFilter(globalFilter) + if (Object.keys(flows).length > 0) { + filters.flows = flows + } + + const flowRuns = buildFlowRunFilter(globalFilter) + if (Object.keys(flowRuns).length > 0) { + filters.flow_runs = flowRuns + } + + const taskRuns = buildTaskRunFilter(globalFilter) + if (Object.keys(taskRuns).length > 0) { + filters.task_runs = taskRuns + } + + return filters +} \ No newline at end of file diff --git a/orion-ui/src/plugins/api.ts b/orion-ui/src/plugins/api.ts index 82fc8fbad508..1c504442a1f2 100644 --- a/orion-ui/src/plugins/api.ts +++ b/orion-ui/src/plugins/api.ts @@ -1,4 +1,65 @@ import { App, Plugin, ref, ComputedRef, watch, WatchStopHandle } from 'vue' +import { + FlowFilter, + DeploymentFilter, + FlowRunFilter, + TaskRunFilter +} from '@/typings/filters' + +interface Endpoint { + method: 'POST' | 'GET' | 'DELETE' | 'PUT' + url: string + interpolate?: boolean +} + +interface CreateFlowRunBody { + name?: string + flow_id: string + deployment_id?: string + flow_version?: string + parameters?: { [key: string]: any } + idempotency_key?: string + context?: { [key: string]: any } + tags?: string[] + parent_task_run_id?: string + state?: { + type: string + name?: string + message?: string + data?: any + state_details?: { + flow_run_id?: string + task_run_id?: string + child_flow_run_id?: string + scheduled_time?: string + cache_key?: string + cache_expiration?: string + } + } +} + +interface CreateDeploymentFlowRunBody { + id: string + name?: string + parameters?: { [key: string]: any } + idempotency_key?: string + context?: { [key: string]: any } + tags?: string[] + state?: { + type: string + name?: string + message?: string + data?: any + state_details?: { + flow_run_id?: string + task_run_id?: string + child_flow_run_id?: string + scheduled_time?: string + cache_key?: string + cache_expiration?: string + } + } +} export interface BaseFilter { flows?: FlowFilter diff --git a/orion-ui/src/store/getters.ts b/orion-ui/src/store/getters.ts index 73bcdb663dfc..81a4d0c253f4 100644 --- a/orion-ui/src/store/getters.ts +++ b/orion-ui/src/store/getters.ts @@ -5,6 +5,12 @@ import { FlowRunsFilter, TaskRunsFilter } from '@/plugins/api' +import { + FlowFilter, + DeploymentFilter, + FlowRunFilter, + TaskRunFilter +} from '@/typings/filters' import { State } from '.' import { GetterTree } from 'vuex' diff --git a/orion-ui/src/types/states.ts b/orion-ui/src/types/states.ts index da15e4c802bb..aee2f39837a9 100644 --- a/orion-ui/src/types/states.ts +++ b/orion-ui/src/types/states.ts @@ -19,6 +19,9 @@ export type StateDirection = 1 | -1 export type StateIcon = `pi-${Lowercase}` export type StateColor = `var(--${Lowercase})` +export const SubStates = ['Late', 'Crashed'] as const +export type SubState = typeof SubStates[number] + export class States { public static readonly COMPLETED = 'COMPLETED' public static readonly RUNNING = 'RUNNING' diff --git a/orion-ui/src/typings/filters.d.ts b/orion-ui/src/typings/filters.d.ts index c93c87c5cd0c..6642368eea98 100644 --- a/orion-ui/src/typings/filters.d.ts +++ b/orion-ui/src/typings/filters.d.ts @@ -1,26 +1,26 @@ /** A list where results will be returned only if they match all the values in the list */ -type all_ = string[] +type all_ = { all_?: string[] } /** A list where results will be returned if any of the values are included in the list */ -type any_ = string[] +type any_ = { any_?: string[] } /** A list where results will be returned if values don't match any in the list */ -type not_any_ = string[] +type not_any_ = { not_any_?: string[] } /** Matches on boolean equality */ -type eq_ = boolean +type eq_ = { eq_?: boolean } /** Matches on boolean equality */ -type exists_ = boolean +type exists_ = { exists_?: boolean } /** If true, returns results whose key is null */ -type is_null_ = boolean +type is_null_ = { is_null_?: boolean } /** A date-time string to include results starting at or before this time */ -type before_ = string +type before_ = { before_?: string } /** A date-time string to include results starting at or after this time */ -type after_ = string +type after_ = { after_?: string } /** * Max: 200 @@ -35,214 +35,45 @@ type limit = number */ type offset = number -declare interface DeploymentFilter { - id?: { - /** - * A list of ids - * Example: [ "abc-123", "def-456" ] - */ - any_?: any_ - } - name?: { - /** - * A list of names - * Example: [ "my-flow-1", "my-flow-2" ] - */ - any_?: any_ - } - tags?: { - /** - * Results will be returned only if their tags are a superset of the list. - */ - all_?: any_ - /** - * If true, only include flows without tags - */ - is_null_?: is_null_ - } - is_schedule_active?: { - eq_: eq_ - } +export interface Filter { + id?: any_ + name?: any_ + tags?: all_ & is_null_ } -declare interface FlowFilter { - id?: { - /** - * A list of ids - * Example: [ "abc-123", "def-456" ] - */ - any_?: any_ - } - name?: { - /** - * A list of names - * Example: [ "my-flow-1", "my-flow-2" ] - */ - any_?: any_ - } - tags?: { - /** - * Results will be returned only if their tags are a superset of the list. - */ - all_?: any_ - /** - * If true, only include flows without tags - */ - is_null_?: is_null_ - } +export interface DeploymentFilter extends Filter { + is_schedule_active?: eq_ } -declare interface FlowRunFilter { - id?: { - any_?: any_ - not_any_?: not_any_ - } - name?: { - /** - * A list of names - * Example: [ "my-flow-1", "my-flow-2" ] - */ - any_?: any_ - } - tags?: { - /** - * Results will be returned only if their tags are a superset of the list. - */ - all_?: all_ - /** - * If true, only include flow runs without tags - */ - is_null_?: is_null_ - } - deployment_id?: { - any_?: any_ - is_null_?: is_null_ - } - state?: { - type?: { - any_?: any_ - } - name?: { - any_?: any_ - } - } - flow_version?: { - any_: any_ - } +export type FlowFilter = Filter + +export type StateFilter = { + type?: any_ + name?: any_ +} + +export type TimeFrameFilter = before_ & after_ + +export interface FlowRunFilter extends Filter { + id?: any_ & not_any_ + deployment_id?: any_ & is_null_ + state?: StateFilter + flow_version?: any_ /** * Flow run actual starts */ - start_time?: { - before_?: before_ - after_?: after_ - } + start_time?: TimeFrameFilter /** * Flow run scheduled starts */ - expected_start_time?: { - before_?: before_ - after_?: after_ - } - next_scheduled_start_time?: { - before_?: before_ - after_?: after_ - } - parent_task_run_id?: { - any_?: any_ - is_null_?: is_null_ - } -} - -declare interface TaskRunFilter { - id?: { - any_?: any_ - not_any_?: not_any_ - } - name?: { - /** - * A list of names - * Example: [ "my-flow-1", "my-flow-2" ] - */ - any_?: any_ - } - tags?: { - /** - * Results will be returned only if their tags are a superset of the list. - */ - all_?: all_ - /** - * If true, only include flow runs without tags - */ - is_null_?: is_null_ - } - state?: { - type?: { - any_: any_ - } - name?: { - any_: any_ - } - } - start_time?: { - before_?: before_ - after_?: after_ - } - subflow_runs?: { - exists_: exists_ - } -} - -declare interface Endpoint { - method: 'POST' | 'GET' | 'DELETE' | 'PUT' - url: string - interpolate?: boolean = false -} - -declare interface CreateFlowRunBody { - name?: string - flow_id: string - deployment_id?: string - flow_version?: string - parameters?: { [key: string]: any } - idempotency_key?: string - context?: { [key: string]: any } - tags?: string[] - parent_task_run_id?: string - state?: { - type: string - name?: string - message?: string - data?: any - state_details?: { - flow_run_id?: string - task_run_id?: string - child_flow_run_id?: string - scheduled_time?: string - cache_key?: string - cache_expiration?: string - } - } + expected_start_time?: TimeFrameFilter + next_scheduled_start_time?: TimeFrameFilter + parent_task_run_id?: any_ & is_null_ } -declare interface CreateDeploymentFlowRunBody { - id: string - name?: string - parameters?: { [key: string]: any } - idempotency_key?: string - context?: { [key: string]: any } - tags?: string[] - state?: { - type: string - name?: string - message?: string - data?: any - state_details?: { - flow_run_id?: string - task_run_id?: string - child_flow_run_id?: string - scheduled_time?: string - cache_key?: string - cache_expiration?: string - } - } +export interface TaskRunFilter extends Filter { + id?: any_ & not_any_ + state?: StateFilter + start_time?: TimeFrameFilter + subflow_runs?: exists_ } diff --git a/orion-ui/src/typings/global.d.ts b/orion-ui/src/typings/global.d.ts index 88b243e7a9f2..889a7fe7bc59 100644 --- a/orion-ui/src/typings/global.d.ts +++ b/orion-ui/src/typings/global.d.ts @@ -3,43 +3,34 @@ export interface RunState { type: string } +export type TimeUnit = 'minutes' | 'hours' | 'days' + +export type TimeFrame = { + timestamp?: Date + unit?: TimeUnit + value?: number +} + export interface RunTimeFrame { dynamic: boolean - from: { - timestamp?: Date - unit?: 'minutes' | 'hours' | 'days' - value?: number - } - to: { - timestamp?: Date - unit?: 'minutes' | 'hours' | 'days' - value?: number - } + from: TimeFrame + to: TimeFrame +} + +export interface BaseFilter { + ids?: string[] + names?: string[] + tags?: string[] +} + +export interface StatesFilter extends BaseFilter { + states?: RunState[] + timeframe?: RunTimeFrame } export interface GlobalFilter { - flows: { - ids?: string[] - names?: string[] - tags?: string[] - } - deployments: { - ids?: string[] - names?: string[] - tags?: string[] - } - flow_runs: { - ids?: string[] - names?: string[] - tags?: string[] - states?: RunState[] - timeframe?: RunTimeFrame - } - task_runs: { - ids?: string[] - names?: string[] - tags?: string[] - states?: RunState[] - timeframe?: RunTimeFrame - } + flows: BaseFilter + deployments: BaseFilter + flow_runs: StatesFilter + task_runs: StatesFilter } diff --git a/orion-ui/src/utilities/arrays.ts b/orion-ui/src/utilities/arrays.ts index 31db27241de1..1933c7b7d1cc 100644 --- a/orion-ui/src/utilities/arrays.ts +++ b/orion-ui/src/utilities/arrays.ts @@ -1,3 +1,9 @@ export function unique(array: T): T { return array.filter((v, i, a) => a.indexOf(v) === i) as T } + +export function isNonEmptyArray( + array: T | undefined +): array is T { + return array !== undefined && array.length > 0 +} diff --git a/orion-ui/src/utilities/dates.ts b/orion-ui/src/utilities/dates.ts index 3969782c3d14..83865f8f44ea 100644 --- a/orion-ui/src/utilities/dates.ts +++ b/orion-ui/src/utilities/dates.ts @@ -1,3 +1,4 @@ +import { TimeUnit } from '@/typings/global' import { format } from 'date-fns' export function formatDateTimeNumeric(date: Date | string): string { @@ -5,3 +6,47 @@ export function formatDateTimeNumeric(date: Date | string): string { return format(parsed, 'yyyy/MM/dd hh:mm:ss a') } + +export function subtractTimeUnitValue( + unit: TimeUnit, + value: number, + date: Date = new Date() +): Date { + switch (unit) { + case 'minutes': + date.setMinutes(date.getMinutes() - value) + break + case 'hours': + date.setHours(date.getHours() - value) + break + case 'days': + date.setDate(date.getDate() - value) + break + default: + break + } + + return date +} + +export function addTimeUnitValue( + unit: TimeUnit, + value: number, + date: Date = new Date() +): Date { + switch (unit) { + case 'minutes': + date.setMinutes(date.getMinutes() + value) + break + case 'hours': + date.setHours(date.getHours() + value) + break + case 'days': + date.setDate(date.getDate() + value) + break + default: + break + } + + return date +} diff --git a/orion-ui/src/utilities/states.ts b/orion-ui/src/utilities/states.ts index 9db4dc08e9ca..c568bd17a670 100644 --- a/orion-ui/src/utilities/states.ts +++ b/orion-ui/src/utilities/states.ts @@ -1,5 +1,9 @@ -import { State, States } from '@/types/states' +import { SubState, SubStates, State, States } from '@/types/states' export function isState(value: unknown): value is State { return Object.values(States).includes(value) } + +export function isSubState(value: unknown): value is SubState { + return SubStates.includes(value as SubState) +} diff --git a/orion-ui/src/utilities/timeFrame.ts b/orion-ui/src/utilities/timeFrame.ts new file mode 100644 index 000000000000..c6ea7d15364b --- /dev/null +++ b/orion-ui/src/utilities/timeFrame.ts @@ -0,0 +1,37 @@ +import { TimeFrame } from '@/typings/global' +import { addTimeUnitValue, subtractTimeUnitValue } from '@/utilities/dates' + +function isValidTimeFrame( + timeFrame: TimeFrame | undefined +): timeFrame is TimeFrame { + return ( + timeFrame?.timestamp != undefined || + (timeFrame?.value != undefined && timeFrame.unit != undefined) + ) +} + +function calculateStart(timeFrame: TimeFrame): Date | null { + if (timeFrame.timestamp) { + return timeFrame.timestamp + } + + if (timeFrame.unit && timeFrame.value) { + return subtractTimeUnitValue(timeFrame.unit, timeFrame.value) + } + + return null +} + +function calculateEnd(timeFrame: TimeFrame): Date | null { + if (timeFrame.timestamp) { + return timeFrame.timestamp + } + + if (timeFrame.unit && timeFrame.value) { + return addTimeUnitValue(timeFrame.unit, timeFrame.value) + } + + return null +} + +export { isValidTimeFrame, calculateStart, calculateEnd } From 90b55063498bceb63b3d0e8ec10d0db131f44628 Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Tue, 25 Jan 2022 15:02:27 -0600 Subject: [PATCH 2/2] increment package json --- orion-ui/packages/orion-design/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orion-ui/packages/orion-design/package.json b/orion-ui/packages/orion-design/package.json index 61611260a68e..ee8c48d1e842 100644 --- a/orion-ui/packages/orion-design/package.json +++ b/orion-ui/packages/orion-design/package.json @@ -1,6 +1,6 @@ { "name": "@prefecthq/orion-design", - "version": "0.1.4", + "version": "0.1.5", "private": false, "keywords": [ "prefect",