-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create and manage automations in the UI (#13342)
- Loading branch information
Showing
27 changed files
with
1,018 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<template> | ||
<p-select v-model="type" :options="options" class="automation-action-type-select" /> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { SelectOption } from '@prefecthq/prefect-design' | ||
import { AutomationActionType, automationActionTypeLabels, automationActionTypes } from '@prefecthq/prefect-ui-library' | ||
import { computed } from 'vue' | ||
const type = defineModel<AutomationActionType | null>('type', { required: true }) | ||
const options = computed<SelectOption[]>(() => { | ||
const allOptions = automationActionTypes.map(type => { | ||
const label = automationActionTypeLabels[type] | ||
return { | ||
label, | ||
value: type, | ||
} | ||
}) | ||
if (type.value === 'do-nothing') { | ||
return allOptions | ||
} | ||
return allOptions.filter(option => option.value !== 'do-nothing') | ||
}) | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
<template> | ||
<p-card class="automation-card"> | ||
<p-content> | ||
<p-content secondary> | ||
<div class="automation-card__header"> | ||
<p-link class="automation-card__name" :to="routes.automation(automation.id)"> | ||
{{ automation.name }} | ||
</p-link> | ||
<div class="automation-card__header-actions"> | ||
<AutomationToggle :automation="automation" @update="emit('update')" /> | ||
<AutomationMenu :automation="automation" @delete="emit('update')" /> | ||
</div> | ||
</div> | ||
<template v-if="automation.description"> | ||
<p class="automation-card__description"> | ||
{{ automation.description }} | ||
</p> | ||
</template> | ||
</p-content> | ||
|
||
<p-content secondary> | ||
<span class="automation-card__label">Trigger</span> | ||
<AutomationTriggerDescription :trigger="automation.trigger" /> | ||
</p-content> | ||
|
||
<p-content secondary> | ||
<span class="automation-card__label">{{ toPluralString('Action', automation.actions.length) }}</span> | ||
<template v-for="action in automation.actions" :key="action.id"> | ||
<p-card><AutomationActionDescription :action="action" /></p-card> | ||
</template> | ||
</p-content> | ||
</p-content> | ||
</p-card> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { toPluralString } from '@prefecthq/prefect-design' | ||
import { AutomationMenu, AutomationToggle, AutomationTriggerDescription, AutomationActionDescription, useWorkspaceRoutes } from '@prefecthq/prefect-ui-library' | ||
import { Automation } from '@/types/automation' | ||
defineProps<{ | ||
automation: Automation, | ||
}>() | ||
const emit = defineEmits<{ | ||
(event: 'update'): void, | ||
}>() | ||
const routes = useWorkspaceRoutes() | ||
</script> | ||
|
||
<style> | ||
.automation-card__header { @apply | ||
flex | ||
gap-2 | ||
items-center | ||
justify-between | ||
} | ||
.automation-card__header-actions { @apply | ||
flex | ||
gap-2 | ||
items-center | ||
} | ||
.automation-card__name { @apply | ||
text-lg | ||
} | ||
.automation-card__description { @apply | ||
text-sm | ||
} | ||
.automation-card__label { @apply | ||
font-medium | ||
mr-2 | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<template> | ||
<p-message info> | ||
Custom triggers allow advanced configuration of the conditions on which a trigger executes its actions. | ||
|
||
<template #action> | ||
<DocumentationButton :to="localization.docs.automationTriggers" small /> | ||
</template> | ||
</p-message> | ||
|
||
<p-label label="Trigger" :state="state" :message="error"> | ||
<JsonInput v-model="model" class="automation-trigger-json-input__json-input" show-format-button :state="state" /> | ||
</p-label> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { DocumentationButton, JsonInput, isEmptyArray, isEmptyString, isInvalidDate, isNullish, localization } from '@prefecthq/prefect-ui-library' | ||
import { ValidationRule, useValidation } from '@prefecthq/vue-compositions' | ||
import { mapper } from '@/services/mapper' | ||
const model = defineModel<string>({ required: true }) | ||
const isMappableAutomationTriggerJson: ValidationRule<string> = (value) => { | ||
try { | ||
const json = JSON.parse(value) | ||
mapper.map('AutomationTriggerResponse', json, 'AutomationTrigger') | ||
} catch (error) { | ||
return false | ||
} | ||
return true | ||
} | ||
const isRequired: ValidationRule<unknown> = (value, name) => { | ||
if (isNullish(value) || isEmptyArray(value) || isEmptyString(value) || isInvalidDate(value)) { | ||
return `${name} is required` | ||
} | ||
return true | ||
} | ||
const isJson: ValidationRule<string> = (value, name) => { | ||
try { | ||
JSON.parse(value) | ||
} catch { | ||
return `${name} must be valid JSON` | ||
} | ||
return true | ||
} | ||
const { state, error } = useValidation(model, 'Trigger', [isRequired, isJson, isMappableAutomationTriggerJson]) | ||
</script> | ||
|
||
<style> | ||
.automation-trigger-json-input__json-input { | ||
min-height: 400px; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<template> | ||
<p-select v-model="template" empty-message="Select template" :options="options" class="automation-trigger-template-select" /> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { SelectOptionNormalized } from '@prefecthq/prefect-design' | ||
import { AutomationTriggerTemplate, automationTriggerTemplates, getAutomationTriggerTemplateLabel } from '@prefecthq/prefect-ui-library' | ||
import { computed } from 'vue' | ||
const template = defineModel<AutomationTriggerTemplate | null>('template', { required: true }) | ||
/* | ||
* Currently OSS doesn't have support for enabled/disabled trigger templates like cloud does. | ||
* Only because it wasn't needed at the time of porting automations to OSS. | ||
*/ | ||
const options = computed<SelectOptionNormalized[]>(() => automationTriggerTemplates.map(type => { | ||
return { | ||
label: getAutomationTriggerTemplateLabel(type), | ||
value: type, | ||
} | ||
})) | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<template> | ||
<p-wizard | ||
ref="wizardRef" | ||
class="automation-wizard" | ||
:steps="steps" | ||
:last-step-text="lastStepText" | ||
show-cancel | ||
:nonlinear="editing" | ||
:show-save-and-exit="editing" | ||
@cancel="cancel" | ||
@submit="submit" | ||
> | ||
<template #trigger-step> | ||
<AutomationWizardStepTrigger v-model:automation="automation" /> | ||
</template> | ||
<template #actions-step> | ||
<template v-if="isAutomationActionFormValues(automation)"> | ||
<AutomationWizardStepActions v-model:automation="automation" /> | ||
</template> | ||
</template> | ||
<template #details-step> | ||
<AutomationWizardStepDetails v-model:automation="automation" /> | ||
</template> | ||
</p-wizard> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { PWizard, WizardStep } from '@prefecthq/prefect-design' | ||
import { isAutomationTriggerEvent } from '@prefecthq/prefect-ui-library' | ||
import { computed, ref } from 'vue' | ||
import { useRouter } from 'vue-router' | ||
import AutomationWizardStepActions from '@/components/AutomationWizardStepActions.vue' | ||
import AutomationWizardStepDetails from '@/components/AutomationWizardStepDetails.vue' | ||
import AutomationWizardStepTrigger from '@/components/AutomationWizardStepTrigger.vue' | ||
import { mapper } from '@/services/mapper' | ||
import { Automation, AutomationFormValues, IAutomation, isAutomationActionFormValues } from '@/types/automation' | ||
const props = defineProps<{ | ||
automation?: Partial<Automation>, | ||
editing?: boolean, | ||
}>() | ||
const automation = ref<AutomationFormValues>(props.automation ?? {}) | ||
const emit = defineEmits<{ | ||
(event: 'submit', value: Automation): void, | ||
}>() | ||
const router = useRouter() | ||
const lastStepText = computed(() => props.automation ? 'Save' : 'Create') | ||
const steps: WizardStep[] = [ | ||
{ title: 'Trigger', key: 'trigger-step' }, | ||
{ title: 'Actions', key: 'actions-step' }, | ||
{ title: 'Details', key: 'details-step' }, | ||
] | ||
const wizardRef = ref<InstanceType<typeof PWizard>>() | ||
function submit(): void { | ||
emit('submit', new Automation(automation.value as IAutomation)) | ||
} | ||
function cancel(): void { | ||
router.back() | ||
} | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
<template> | ||
<div class="automation-wizard-action"> | ||
<div class="automation-wizard-action__header"> | ||
<span class="automation-wizard-action__heading">Action {{ index + 1 }}</span> | ||
<p-button size="sm" icon="TrashIcon" @click="emit('delete')" /> | ||
</div> | ||
|
||
<p-content> | ||
<p-label label="Action Type" :state :message> | ||
<template #default="{ id }"> | ||
<AutomationActionTypeSelect :id v-model:type="type" :state /> | ||
</template> | ||
</p-label> | ||
|
||
|
||
<template v-if="input"> | ||
<component :is="input.component" v-bind="input.props" @update:action="updateAction" /> | ||
</template> | ||
</p-content> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { withProps, AutomationActionInput, AutomationAction, isNullish, getAutomationTriggerTemplate, getDefaultValueForAction } from '@prefecthq/prefect-ui-library' | ||
import { useValidation } from '@prefecthq/vue-compositions' | ||
import { computed } from 'vue' | ||
import AutomationActionTypeSelect from '@/components/AutomationActionTypeSelect.vue' | ||
import { AutomationActionFormValues } from '@/types/automation' | ||
const props = defineProps<{ | ||
index: number, | ||
action: Partial<AutomationAction>, | ||
automation: AutomationActionFormValues, | ||
}>() | ||
const emit = defineEmits<{ | ||
(event: 'delete'): void, | ||
(event: 'update:action', value: Partial<AutomationAction>): void, | ||
}>() | ||
const type = computed({ | ||
get() { | ||
return props.action.type ?? null | ||
}, | ||
set(value) { | ||
if (isNullish(value)) { | ||
emit('update:action', {}) | ||
return | ||
} | ||
const template = getAutomationTriggerTemplate(props.automation.trigger) | ||
const action = getDefaultValueForAction(value, template) | ||
emit('update:action', action) | ||
}, | ||
}) | ||
const { state, error: message } = useValidation(type, 'Action Type', value => !!value) | ||
const input = computed(() => { | ||
if (!props.action.type) { | ||
return null | ||
} | ||
return withProps(AutomationActionInput, { | ||
action: props.action, | ||
'onUpdate:action': value => emit('update:action', value), | ||
}) | ||
}) | ||
function updateAction(action: Partial<AutomationAction>): void { | ||
emit('update:action', action) | ||
} | ||
</script> | ||
|
||
<style> | ||
.automation-wizard-action { @apply | ||
grid | ||
gap-1 | ||
} | ||
.automation-wizard-action__header { @apply | ||
flex | ||
items-center | ||
justify-between | ||
} | ||
.automation-wizard-action__heading { @apply | ||
font-bold | ||
} | ||
</style> |
Oops, something went wrong.