Skip to content

Commit c0d21c1

Browse files
wssheldonkevgliss
andauthored
Auto-run workflows associated with Signals and auto-parameterize with Entity Type values (Netflix#3128)
* Add WorkflowCreateDialog tied to SiganlDefinition and template out flow * Enhancement/signal api (Netflix#3131) * Improving signal api * rough outline of how we might map entity type values to params and auto-run * add alembic migration file * add alembic migration file * add Load More to BaseCombobox * Custom run flow for signal workflows that replaces entity types with assoc values * run automated workflows for signals and remove duped function * entity type name becomes parameter value for workflow * use id instead of name for externalRef * remove duplicate workflow code in signal flow * use signal id instead of name for externalRef * workflowInstances <-> Signals * 🐶 ruff ruff * attempt to resolve tests * use entity fixtures in signal filter tests --------- Co-authored-by: kevgliss <[email protected]>
1 parent 830e8d5 commit c0d21c1

19 files changed

+503
-65
lines changed

src/dispatch/case/models.py

+1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ class SignalRead(DispatchBase):
163163
variant: Optional[str]
164164
external_id: str
165165
external_url: Optional[str]
166+
workflow_instances: Optional[List[WorkflowInstanceRead]] = []
166167

167168

168169
class SignalInstanceRead(DispatchBase):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Associate WorkflowInstances with Signals
2+
3+
Revision ID: 1dafcb9ad889
4+
Revises: b48b245f9c4c
5+
Create Date: 2023-03-24 10:51:26.443920
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
# revision identifiers, used by Alembic.
12+
revision = "1dafcb9ad889"
13+
down_revision = "b48b245f9c4c"
14+
branch_labels = None
15+
depends_on = None
16+
17+
18+
def upgrade():
19+
# ### commands auto generated by Alembic - please adjust! ###
20+
op.add_column("workflow_instance", sa.Column("signal_id", sa.Integer(), nullable=True))
21+
op.create_foreign_key(
22+
None, "workflow_instance", "signal", ["signal_id"], ["id"], ondelete="CASCADE"
23+
)
24+
# ### end Alembic commands ###
25+
26+
27+
def downgrade():
28+
# ### commands auto generated by Alembic - please adjust! ###
29+
op.drop_constraint(None, "workflow_instance", type_="foreignkey")
30+
op.drop_column("workflow_instance", "signal_id")
31+
# ### end Alembic commands ###

src/dispatch/entity/service.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ def get_by_value_or_create(*, db_session, entity_in: EntityCreate) -> Entity:
9292
if entity_in.id:
9393
q = db_session.query(Entity).filter(Entity.id == entity_in.id)
9494
else:
95-
q = db_session.query(Entity).filter_by(value=entity_in.value)
95+
q = db_session.query(Entity).filter_by(
96+
value=entity_in.value, entity_type_id=entity_in.entity_type.id
97+
)
9698

9799
instance = q.first()
98100
if instance:

src/dispatch/entity_type/models.py

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class EntityTypeBase(DispatchBase):
4141

4242

4343
class EntityTypeCreate(EntityTypeBase):
44+
id: Optional[PrimaryKey]
4445
project: ProjectRead
4546

4647

src/dispatch/signal/flows.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from dispatch.project.models import Project
99
from dispatch.signal import service as signal_service
1010
from dispatch.signal.models import SignalInstanceCreate
11+
from dispatch.workflow import flows as workflow_flows
1112

1213

1314
@background_task
@@ -45,6 +46,17 @@ def signal_instance_create_flow(
4546

4647
signal_instance.case = case
4748
db_session.commit()
49+
50+
# run workflows if not duplicate or snoozed
51+
if workflows := signal_instance.signal.workflows:
52+
for workflow in workflows:
53+
workflow_flows.signal_workflow_run_flow(
54+
current_user=current_user,
55+
db_session=db_session,
56+
signal_instance=signal_instance,
57+
workflow=workflow,
58+
)
59+
4860
return case_flows.case_new_create_flow(
4961
db_session=db_session, organization_slug=None, case_id=case.id
5062
)
@@ -79,5 +91,7 @@ def create_signal_instance(
7991
)
8092

8193
return signal_instance_create_flow(
82-
db_session=db_session, signal_instance_id=signal_instance.id, organization_slug=None
94+
db_session=db_session,
95+
signal_instance_id=signal_instance.id,
96+
organization_slug=None,
8397
)

src/dispatch/signal/models.py

+4
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ class Signal(Base, TimeStampMixin, ProjectMixin):
145145
secondary=assoc_signal_workflows,
146146
backref="signals",
147147
)
148+
workflow_instances = relationship(
149+
"WorkflowInstance", backref="signal", cascade="all, delete-orphan"
150+
)
151+
148152
tags = relationship(
149153
"Tag",
150154
secondary=assoc_signal_tags,

src/dispatch/signal/service.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ def get_signal_filter(*, db_session: Session, signal_filter_id: int) -> SignalFi
121121
return db_session.query(SignalFilter).filter(SignalFilter.id == signal_filter_id).one_or_none()
122122

123123

124-
def get_signal_instance(*, db_session: Session, signal_instance_id: int | str):
124+
def get_signal_instance(
125+
*, db_session: Session, signal_instance_id: int | str
126+
) -> Optional[SignalInstance]:
125127
"""Gets a signal instance by it's UUID."""
126128
return (
127129
db_session.query(SignalInstance)

src/dispatch/static/dispatch/src/components/BaseCombobox.vue

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
v-model="selectedItems"
1616
>
1717
<slot name="selection" v-bind="{ attr, item, selected }"></slot>
18+
<template v-slot:append-item>
19+
<v-list-item v-if="more" @click="loadMore()">
20+
<v-list-item-content>
21+
<v-list-item-subtitle> Load More </v-list-item-subtitle>
22+
</v-list-item-content>
23+
</v-list-item>
24+
</template>
1825
</v-combobox>
1926
</template>
2027

src/dispatch/static/dispatch/src/signal/NewEditDialog.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,9 @@
219219
</v-app-bar>
220220
<v-card-text>
221221
<workflow-combobox
222-
v-model="selected.workflows"
222+
v-model="workflows"
223223
:project="project"
224+
:signalDefinition="selected"
224225
></workflow-combobox>
225226
</v-card-text>
226227
</v-card>

src/dispatch/static/dispatch/src/signal/store.js

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const getDefaultSelectedState = () => {
2020
entity_types: [],
2121
tags: [],
2222
signal_definition: null,
23+
workflow_instances: null,
24+
workflows: null,
2325
source: null,
2426
project: null,
2527
created_at: null,

src/dispatch/static/dispatch/src/workflow/WorkflowCombobox.vue

+74-43
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,74 @@
11
<template>
2-
<base-combobox
3-
:value="value"
4-
:label="label"
5-
:api="workflowApi"
6-
:project="project"
7-
v-bind="$attrs"
8-
v-on="$listeners"
9-
>
10-
<template #selection="{ attr, item, selected }">
11-
<v-menu bottom right transition="scale-transition" origin="top left">
12-
<template v-slot:activator="{ on }">
13-
<v-chip v-bind="attr" :input-value="selected" pill v-on="on">
14-
{{ item ? item.name : "Unknown" }}
15-
</v-chip>
2+
<v-row no-gutters align="center">
3+
<v-col cols="12" sm="11">
4+
<base-combobox
5+
:value="value"
6+
:label="label"
7+
:api="workflowApi"
8+
:project="project"
9+
v-bind="$attrs"
10+
v-on="$listeners"
11+
v-model="workflows"
12+
>
13+
<template #selection="{ attr, item, selected }">
14+
<v-menu bottom right transition="scale-transition" origin="top left">
15+
<template v-slot:activator="{ on }">
16+
<v-chip v-bind="attr" :input-value="selected" pill v-on="on">
17+
{{ item ? item.name : "Unknown" }}
18+
</v-chip>
19+
</template>
20+
<v-card>
21+
<v-list dark>
22+
<v-list-item>
23+
<v-list-item-avatar color="teal">
24+
<span class="white--text">{{ initials(item) }}</span>
25+
</v-list-item-avatar>
26+
<v-list-item-content>
27+
<v-list-item-title>{{ item ? item.name : "Unknown" }}</v-list-item-title>
28+
<v-list-item-subtitle>{{ item ? item.type : "Unknown" }}</v-list-item-subtitle>
29+
</v-list-item-content>
30+
<v-list-item-action>
31+
<v-btn icon>
32+
<v-icon>mdi-close-circle</v-icon>
33+
</v-btn>
34+
</v-list-item-action>
35+
</v-list-item>
36+
</v-list>
37+
<v-list>
38+
<v-list-item>
39+
<v-list-item-action>
40+
<v-icon>mdi-text-box</v-icon>
41+
</v-list-item-action>
42+
<v-list-item-subtitle>{{
43+
item ? item.description : "Unknown"
44+
}}</v-list-item-subtitle>
45+
</v-list-item>
46+
</v-list>
47+
</v-card>
48+
</v-menu>
1649
</template>
17-
<v-card>
18-
<v-list dark>
19-
<v-list-item>
20-
<v-list-item-avatar color="teal">
21-
<span class="white--text">{{ initials(item) }}</span>
22-
</v-list-item-avatar>
23-
<v-list-item-content>
24-
<v-list-item-title>{{ item ? item.name : "Unknown" }}</v-list-item-title>
25-
<v-list-item-subtitle>{{ item ? item.type : "Unknown" }}</v-list-item-subtitle>
26-
</v-list-item-content>
27-
<v-list-item-action>
28-
<v-btn icon>
29-
<v-icon>mdi-close-circle</v-icon>
30-
</v-btn>
31-
</v-list-item-action>
32-
</v-list-item>
33-
</v-list>
34-
<v-list>
35-
<v-list-item>
36-
<v-list-item-action>
37-
<v-icon>mdi-text-box</v-icon>
38-
</v-list-item-action>
39-
<v-list-item-subtitle>{{ item ? item.description : "Unknown" }}</v-list-item-subtitle>
40-
</v-list-item>
41-
</v-list>
42-
</v-card>
43-
</v-menu>
44-
</template>
45-
</base-combobox>
50+
</base-combobox>
51+
</v-col>
52+
<v-col cols="12" sm="1">
53+
<workflow-create-dialog
54+
v-model="createdItem"
55+
:project="project"
56+
:signalDefinition="signalDefinition"
57+
/>
58+
</v-col>
59+
</v-row>
4660
</template>
4761

4862
<script>
4963
import BaseCombobox from "@/components/BaseCombobox.vue"
5064
import WorkflowApi from "@/workflow/api"
65+
import WorkflowCreateDialog from "@/workflow/WorkflowCreateDialog.vue"
5166
5267
export default {
5368
name: "WorkflowCombobox",
5469
components: {
5570
BaseCombobox,
71+
WorkflowCreateDialog,
5672
},
5773
props: {
5874
value: {
@@ -67,12 +83,27 @@ export default {
6783
type: Object,
6884
required: true,
6985
},
86+
signalDefinition: {
87+
type: Object,
88+
required: false,
89+
},
7090
},
7191
data() {
7292
return {
7393
workflowApi: WorkflowApi,
94+
createdItem: null,
7495
}
7596
},
97+
computed: {
98+
workflows: {
99+
get() {
100+
return this.value
101+
},
102+
set(value) {
103+
this.$emit("input", value)
104+
},
105+
},
106+
},
76107
methods: {
77108
// ...
78109
initials(item) {

0 commit comments

Comments
 (0)