Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: VariablesTable, VariablesDelete, filters, bulk and count routes #1296

Merged
merged 5 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/components/VariablesDeleteButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<template>
<p-button v-if="variableIds.length > 0" danger icon="TrashIcon" @click="open" />
<ConfirmDeleteModal
v-model:showModal="showModal"
:name="localization.info.selectedVariables"
:label="localization.info.variables"
@delete="deleteVariables(variableIds)"
/>
</template>

<script lang="ts" setup>
import { showToast } from '@prefecthq/prefect-design'
import { computed } from 'vue'
import ConfirmDeleteModal from '@/components/ConfirmDeleteModal.vue'
import { useShowModal, useWorkspaceApi } from '@/compositions'
import { localization } from '@/localization'
import { toPluralString } from '@/utilities'

defineProps<{
variableIds: string[],
}>()

const emit = defineEmits<{
(event: 'delete'): string[],
}>()

const { showModal, open, close } = useShowModal()

const api = useWorkspaceApi()

const deleteVariables = async (variableIds: string[]): Promise<void> => {
try {
const variableDeletePromises = variableIds.map(api.variables.deleteVariable)
await Promise.all(variableDeletePromises)
Comment on lines +33 to +34
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this feature is being actively built. Any chance we can get an api endpoint for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jakekaplan and i talked about this yesterday (great minds etc) - we can but we don't really need it since there's a fairly low cap on the number of variables one can create


const successMessage = localization.success.delete(`${variableIds.length} ${toPluralString(localization.info.variable, variableIds.length)}`)
showToast(successMessage, 'success')
emit('delete')
} catch (error) {
showToast(localization.error.delete(localization.info.variables), 'error')
} finally {
close()
}
}
</script>
189 changes: 189 additions & 0 deletions src/components/VariablesTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<template>
<div class="variables-table">
<p-layout-table sticky>
<template #header-start>
<div class="variables-table__header-start">
<ResultsCount v-if="selectedVariables.length == 0" :label="localization.info.variable" :count="variablesCount" />
<SelectedCount v-else :count="selectedVariables.length" />

<FlowsDeleteButton v-if="can.delete.variable" :selected="selectedVariables" @delete="deleteVariables" />
</div>
</template>

<template #header-end>
<div class="variables-table__header-end">
<SearchInput v-model="variableLike" :placeholder="localization.info.variablesSearch" :label="localization.info.variablesSearch" />
<p-select v-model="filter.sort" :options="variableSortOptions" />
<p-tags-input v-model="filter.variables.tags.name" :empty-message="localization.info.tags" class="variables-table__tags" />
</div>
</template>

<p-table :data="variables" :columns="columns">
<template #selection-heading>
<p-checkbox v-model="model" @update:model-value="selectAllVariables" />
</template>

<template #selection="{ row }">
<p-checkbox v-model="selectedVariables" :value="row.id" />
</template>

<template #name="{ row }">
<span>{{ row.name }}</span>
</template>

<template #updated="{ row }">
{{ formatDateTimeNumeric(row.updated) }}
</template>

<template #action-heading>
<span />
</template>

<template #action="{ row }">
<div class="variables-table__action">
<VariableMenu :variable="row" size="xs" @delete="refresh" />
</div>
</template>

<template #empty-state>
<PEmptyResults>
<template #message>
{{ localization.info.noVariables }}
</template>
<template v-if="isCustomFilter" #actions>
<p-button size="sm" secondary @click="clear">
Clear Filters
</p-button>
</template>
</PEmptyResults>
</template>
</p-table>

<template #footer-end>
<p-pager v-if="variables.length" v-model:page="offset" :pages="variablesCount" />
</template>
</p-layout-table>
</div>
</template>

<script lang="ts" setup>
import { PTable, PEmptyResults, CheckboxModel } from '@prefecthq/prefect-design'
import { useDebouncedRef, useSubscription } from '@prefecthq/vue-compositions'
import { computed, ref } from 'vue'
import { FlowsDeleteButton, VariableMenu, ResultsCount, SearchInput, SelectedCount } from '@/components'
import { useCan, useVariablesFilter, useWorkspaceApi } from '@/compositions'
import { localization } from '@/localization'
import { VariablesFilter } from '@/models/Filters'
import { variableSortOptions } from '@/types'
import { formatDateTimeNumeric } from '@/utilities/dates'

const props = defineProps<{
filter?: VariablesFilter,
}>()

const api = useWorkspaceApi()
const can = useCan()

const variableLike = ref<string>()
const variableLikeDebounced = useDebouncedRef(variableLike, 1000)
const offset = ref(0)

const { filter, isCustomFilter, clear } = useVariablesFilter({
...props.filter,
variables: {
...props.filter?.variables,
nameLike: variableLikeDebounced,
valueLike: variableLikeDebounced,
},
offset,
})

const columns = [
{
label: 'selection',
width: '20px',
visible: can.delete.variable,
},
{
property: 'name',
label: 'Name',
width: '125px',
},
{
property: 'value',
label: 'Value',
width: '125px',
},
{
property: 'updated',
label: 'Updated',
width: '125px',
},
{
label: 'Action',
width: '42px',
},
]

const selectedVariables = ref<string[]>([])
const selectAllVariables = (allVariablesSelected: CheckboxModel): string[] => {
if (allVariablesSelected) {
return selectedVariables.value = [...variables.value.map(variable => variable.id)]
}
return selectedVariables.value = []
}

const model = computed({
get() {
return selectedVariables.value.length === variables.value.length
},
set(value: boolean) {
selectAllVariables(value)
},
})
Comment on lines +128 to +143
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make a select all component for this. Takes an array v-model and an array of all the possible values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yea good thought, we're doing this in a few places. I'll open a ticket 👍🏻

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


const variablesSubscription = useSubscription(api.variables.getVariables, [filter])
const variables = computed(() => variablesSubscription.response ?? [])

const variablesCountSubscription = useSubscription(api.variables.getVariablesCount, [filter])
const variablesCount = computed(() => variablesCountSubscription.response)

function refresh(): void {
variablesSubscription.refresh()
variablesCountSubscription.refresh()
}

const emit = defineEmits<{
(event: 'delete'): void,
}>()

const deleteVariables = (): void => {
selectedVariables.value = []
refresh()
emit('delete')
}
</script>

<style>
.variables-table__header-start { @apply
grow
whitespace-nowrap
}

.variables-table__header-end { @apply
flex
flex-wrap
pl-2
ml-auto
shrink
gap-2
}

.variables-table__tags {
min-width: 128px;
}

.variables-table__action { @apply
text-right
}
</style>
8 changes: 6 additions & 2 deletions src/compositions/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { FlowRunSortValuesSortParam } from '@/formatters/FlowRunSortValuesSortPa
import { FlowSortValuesSortParam } from '@/formatters/FlowSortValuesSortParam'
import { OperatorRouteParam } from '@/formatters/OperatorRouteParam'
import { TaskRunSortValuesSortParam } from '@/formatters/TaskRunSortValuesSortParam'
import { BlockDocumentFilter, BlockDocumentsFilter, BlockSchemaFilter, BlockSchemasFilter, BlockTypeFilter, BlockTypesFilter, DeploymentFilter, DeploymentsFilter, FlowFilter, FlowRunFilter, FlowRunsFilter, FlowRunsHistoryFilter, FlowsFilter, StateFilter, TagFilter, TaskRunFilter, TaskRunsFilter, UnionFilter, UnionFilterSort, WorkPoolFilter, WorkPoolQueueFilter, WorkPoolsFilter } from '@/models/Filters'
import { defaultDeploymentSort, defaultFlowRunSort, defaultFlowSort, defaultTaskRunSort } from '@/types'
import { BlockDocumentFilter, BlockDocumentsFilter, BlockSchemaFilter, BlockSchemasFilter, BlockTypeFilter, BlockTypesFilter, DeploymentFilter, DeploymentsFilter, FlowFilter, FlowRunFilter, FlowRunsFilter, FlowRunsHistoryFilter, FlowsFilter, StateFilter, TagFilter, TaskRunFilter, TaskRunsFilter, UnionFilter, UnionFilterSort, VariablesFilter, WorkPoolFilter, WorkPoolQueueFilter, WorkPoolsFilter } from '@/models/Filters'
import { defaultDeploymentSort, defaultFlowRunSort, defaultFlowSort, defaultTaskRunSort, defaultVariableSort } from '@/types'
import { AnyRecord } from '@/types/any'
import { MaybeReactive } from '@/types/reactivity'
import { merge } from '@/utilities/object'
Expand Down Expand Up @@ -515,6 +515,10 @@ export function useDeploymentsFilter(defaultValue: MaybeReactive<DeploymentsFilt
return useUnionFilter<DeploymentsFilter>(defaultValue, defaultDeploymentSort)
}

export function useVariablesFilter(defaultValue: MaybeReactive<VariablesFilter> = {}): UseFilter<VariablesFilter> {
return useUnionFilter<VariablesFilter>(defaultValue, defaultVariableSort)
}

const unionFilterSchema: Omit<RouteQueryParamsSchema<UnionFilter>, 'sort'> = {
flows: flowFilterSchema,
flowRuns: flowRunFilterSchema,
Expand Down
4 changes: 4 additions & 0 deletions src/localization/locale/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,20 @@ export const en = {
artifact: 'Artifact',
artifacts: 'Artifacts',
artifactSearch: 'Search artifacts',
variablesSearch: 'Search variables',
artifactCreated: (key: string) => `Created __${key}__`,
artifactTypeChanged: (type: string) => `Changed to \`${type}\` artifact`,
newVariable: 'New variable',
editVariable: (name: string) => `Edit \`${name}\``,
close: 'Close',
save: 'Save',
name: 'Name',
selectedVariables: 'Selected variables',
value: 'Value',
latest: 'Latest',
item: 'Item',
noData: 'No data',
noVariables: 'No variables',
copyId: 'Copy ID',
copyName: 'Copy name',
copyValue: 'Copy value',
Expand All @@ -119,6 +122,7 @@ export const en = {
taskRun: 'Task run',
taskRuns: 'Task runs',
variable: 'Variable',
variables: 'Variables',
created: 'Created',
create: 'Create',
lastUpdated: 'Last Updated',
Expand Down
28 changes: 26 additions & 2 deletions src/maps/filters.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable camelcase */
import { asArray } from '@prefecthq/prefect-design'
import { Any, Like, All, IsNull, OperatorRequest, TagFilterRequest, FlowFilterRequest, FlowRunFilterRequest, NotAny, StateFilterRequest, Before, After, TaskRunFilterRequest, Exists, DeploymentFilterRequest, Equals, FlowsFilterRequest, FlowRunsFilterRequest, TaskRunsFilterRequest, DeploymentsFilterRequest, BlockTypeFilterRequest, BlockSchemaFilterRequest, BlockDocumentFilterRequest, NotificationsFilterRequest, SavedSearchesFilterRequest, LogsFilterRequest, GreaterThan, LessThan, ConcurrencyLimitsFilterRequest, BlockTypesFilterRequest, BlockSchemasFilterRequest, BlockDocumentsFilterRequest, WorkQueuesFilterRequest, StartsWith, WorkPoolFilterRequest, WorkPoolsFilterRequest, WorkPoolQueueFilterRequest, FlowRunsHistoryFilterRequest, WorkPoolWorkersFilterRequest, WorkPoolQueuesFilterRequest, ArtifactsFilterRequest, ArtifactFilterRequest, NullableEquals, Latest } from '@/models/api/Filters'
import { FlowFilter, FlowRunFilter, Operation, StateFilter, TagFilter, TaskRunFilter, DeploymentFilter, FlowsFilter, FlowRunsFilter, TaskRunsFilter, DeploymentsFilter, BlockTypeFilter, BlockSchemaFilter, BlockDocumentFilter, NotificationsFilter, SavedSearchesFilter, LogsFilter, ConcurrencyLimitsFilter, BlockTypesFilter, BlockSchemasFilter, BlockDocumentsFilter, WorkQueuesFilter, WorkPoolFilter, WorkPoolsFilter, WorkPoolQueueFilter, FlowRunsHistoryFilter, WorkPoolWorkersFilter, WorkPoolQueuesFilter, ArtifactsFilter, ArtifactFilter } from '@/models/Filters'
import { Any, Like, All, IsNull, OperatorRequest, TagFilterRequest, FlowFilterRequest, FlowRunFilterRequest, NotAny, StateFilterRequest, Before, After, TaskRunFilterRequest, Exists, DeploymentFilterRequest, Equals, FlowsFilterRequest, FlowRunsFilterRequest, TaskRunsFilterRequest, DeploymentsFilterRequest, BlockTypeFilterRequest, BlockSchemaFilterRequest, BlockDocumentFilterRequest, NotificationsFilterRequest, SavedSearchesFilterRequest, LogsFilterRequest, GreaterThan, LessThan, ConcurrencyLimitsFilterRequest, BlockTypesFilterRequest, BlockSchemasFilterRequest, BlockDocumentsFilterRequest, WorkQueuesFilterRequest, StartsWith, WorkPoolFilterRequest, WorkPoolsFilterRequest, WorkPoolQueueFilterRequest, FlowRunsHistoryFilterRequest, WorkPoolWorkersFilterRequest, WorkPoolQueuesFilterRequest, ArtifactsFilterRequest, ArtifactFilterRequest, NullableEquals, Latest, VariablesFilterRequest, VariableFilterRequest } from '@/models/api/Filters'
import { FlowFilter, FlowRunFilter, Operation, StateFilter, TagFilter, TaskRunFilter, DeploymentFilter, FlowsFilter, FlowRunsFilter, TaskRunsFilter, DeploymentsFilter, BlockTypeFilter, BlockSchemaFilter, BlockDocumentFilter, NotificationsFilter, SavedSearchesFilter, LogsFilter, ConcurrencyLimitsFilter, BlockTypesFilter, BlockSchemasFilter, BlockDocumentsFilter, WorkQueuesFilter, WorkPoolFilter, WorkPoolsFilter, WorkPoolQueueFilter, FlowRunsHistoryFilter, WorkPoolWorkersFilter, WorkPoolQueuesFilter, ArtifactsFilter, ArtifactFilter, VariablesFilter, VariableFilter } from '@/models/Filters'
import { MapFunction } from '@/services'
import { removeEmptyObjects } from '@/utilities'

Expand Down Expand Up @@ -289,6 +289,30 @@ export const mapArtifactsFilter: MapFunction<ArtifactsFilter, ArtifactsFilterReq
}
}

export const mapVariableFilter: MapFunction<VariableFilter, VariableFilterRequest> = function(source) {
return {
id: toAny(source.id),
name: {
...toAny(source.name),
...toLike(source.nameLike),
},
value: {
...toAny(source.value),
...toLike(source.valueLike),
},
tags: this.map('TagFilter', source.tags, 'TagFilterRequest'),
}
}

export const mapVariablesFilter: MapFunction<VariablesFilter, VariablesFilterRequest> = function(source) {
return {
variables: this.map('VariableFilter', source.variables, 'VariableFilterRequest'),
sort: source.sort,
limit: source.limit,
offset: source.offset,
}
}

export const mapFlowsFilter: MapFunction<FlowsFilter, FlowsFilterRequest> = function(source) {
return removeEmptyObjects({
flows: this.map('FlowFilter', source.flows, 'FlowFilterRequest'),
Expand Down
4 changes: 3 additions & 1 deletion src/maps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { mapStringToDate, mapDateToString } from '@/maps/date'
import { mapDeploymentResponseToDeployment, mapDeploymentUpdateToDeploymentUpdateRequest, mapDeploymentFlowRunCreateToDeploymentFlowRunRequest } from '@/maps/deployment'
import { mapRunHistoryToDivergingBarChartItem } from '@/maps/divergingBarChartItem'
import { mapEmpiricalPolicyToEmpiricalPolicyResponse, mapEmpiricalPolicyResponseToEmpiricalPolicy, mapEmpiricalPolicyToEmpiricalPolicyRequest } from '@/maps/empiricalPolicy'
import { mapFlowFilter, mapDeploymentFilter, mapFlowRunFilter, mapStateFilter, mapFlowsFilter, mapDeploymentsFilter, mapFlowRunsFilter, mapTagFilter, mapTaskRunFilter, mapTaskRunsFilter, mapBlockDocumentFilter, mapBlockSchemaFilter, mapBlockTypeFilter, mapBlockDocumentsFilter, mapBlockSchemasFilter, mapBlockTypesFilter, mapWorkPoolsFilter, mapWorkPoolFilter, mapWorkPoolQueueFilter, mapFlowRunsHistoryFilter, mapLogsFilter, mapNotificationsFilter, mapSavedSearchesFilter, mapWorkQueuesFilter, mapWorkPoolWorkersFilter, mapWorkPoolQueuesFilter, mapArtifactFilter, mapArtifactsFilter } from '@/maps/filters'
import { mapFlowFilter, mapDeploymentFilter, mapFlowRunFilter, mapStateFilter, mapFlowsFilter, mapDeploymentsFilter, mapFlowRunsFilter, mapTagFilter, mapTaskRunFilter, mapTaskRunsFilter, mapBlockDocumentFilter, mapBlockSchemaFilter, mapBlockTypeFilter, mapBlockDocumentsFilter, mapBlockSchemasFilter, mapBlockTypesFilter, mapWorkPoolsFilter, mapWorkPoolFilter, mapWorkPoolQueueFilter, mapFlowRunsHistoryFilter, mapLogsFilter, mapNotificationsFilter, mapSavedSearchesFilter, mapWorkQueuesFilter, mapWorkPoolWorkersFilter, mapWorkPoolQueuesFilter, mapArtifactFilter, mapArtifactsFilter, mapVariablesFilter, mapVariableFilter } from '@/maps/filters'
import { mapFlowToFlowResponse, mapFlowResponseToFlow } from '@/maps/flow'
import { mapFlowRunResponseToFlowRun } from '@/maps/flowRun'
import { mapSavedSearchFilterToFlowRunFilters } from '@/maps/flowRunFilter'
Expand Down Expand Up @@ -140,6 +140,8 @@ export const maps = {
UiFlowRunHistoryResponse: { UiFlowRunHistory: mapUiFlowRunHistoryResponseToUiFlowRunHistory },
VariableCreate: { VariableCreateRequest: mapVariableCreateToVariableCreateRequest },
VariableEdit: { VariableEditRequest: mapVariableEditToVariableEditRequest },
VariableFilter: { VariableFilterRequest: mapVariableFilter },
VariablesFilter: { VariablesFilterRequest: mapVariablesFilter },
VariableResponse: { Variable: mapVariableResponseToVariable },
WorkerScheduledFlowRunResponse: { WorkerScheduledFlowRun: mapWorkerScheduledFlowRunResponseToWorkerScheduledFlowRun },
WorkerScheduledFlowRuns: { WorkerScheduledFlowRunsRequest: mapWorkerScheduledFlowRunsToWorkerScheduledFlowRunsRequest },
Expand Down
18 changes: 17 additions & 1 deletion src/models/Filters.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ArtifactSortValues, FlowSortValues, FlowRunSortValues, TaskRunSortValues, DeploymentSortValues, LogSortValues } from '@/types'
import { ArtifactSortValues, FlowSortValues, FlowRunSortValues, TaskRunSortValues, DeploymentSortValues, LogSortValues, VariableSortValues } from '@/types'

export type Operation = 'and' | 'or'

Expand Down Expand Up @@ -87,6 +87,22 @@ export type ArtifactsFilter = {
offset?: number,
}

export type VariableFilter = {
id?: string[],
name?: string[],
nameLike?: string,
value?: string[],
valueLike?: string,
tags?: TagFilter,
}

export type VariablesFilter = {
variables?: VariableFilter,
sort?: VariableSortValues,
limit?: number,
offset?: number,
}

export type DeploymentFilter = {
id?: string[],
isScheduleActive?: boolean,
Expand Down
Loading