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

Associate Signals w/ Workflows and add UI elements to bind them in Definition and run in Instance tab #3116

Merged
merged 13 commits into from
Mar 16, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""empty message

Revision ID: b48b245f9c4c
Revises: 7db13bf5c5d7
Create Date: 2023-03-14 16:56:11.844768

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "b48b245f9c4c"
down_revision = "7db13bf5c5d7"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"assoc_signal_workflows",
sa.Column("signal_id", sa.Integer(), nullable=False),
sa.Column("workflow_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(["signal_id"], ["signal.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["workflow_id"], ["workflow.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("signal_id", "workflow_id"),
)
op.create_table(
"assoc_signal_instance_tags",
sa.Column("signal_instance_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("tag_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(["signal_instance_id"], ["signal_instance.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tag_id"], ["tag.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("signal_instance_id", "tag_id"),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("assoc_signal_instance_tags")
op.drop_table("assoc_signal_workflows")
# ### end Alembic commands ###
17 changes: 17 additions & 0 deletions src/dispatch/signal/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
ProjectMixin,
TimeStampMixin,
)
from dispatch.workflow.models import WorkflowRead


class RuleMode(DispatchEnum):
Expand Down Expand Up @@ -96,6 +97,14 @@ class RuleMode(DispatchEnum):
PrimaryKeyConstraint("signal_id", "entity_type_id"),
)

assoc_signal_workflows = Table(
"assoc_signal_workflows",
Base.metadata,
Column("signal_id", Integer, ForeignKey("signal.id", ondelete="CASCADE")),
Column("workflow_id", Integer, ForeignKey("workflow.id", ondelete="CASCADE")),
PrimaryKeyConstraint("signal_id", "workflow_id"),
)


class SignalFilterMode(DispatchEnum):
active = "active"
Expand Down Expand Up @@ -131,6 +140,11 @@ class Signal(Base, TimeStampMixin, ProjectMixin):
secondary=assoc_signal_entity_types,
backref="signals",
)
workflows = relationship(
"Workflow",
secondary=assoc_signal_workflows,
backref="signals",
)
tags = relationship(
"Tag",
secondary=assoc_signal_tags,
Expand Down Expand Up @@ -224,20 +238,23 @@ class SignalBase(DispatchBase):
class SignalCreate(SignalBase):
filters: Optional[List[SignalFilterRead]] = []
entity_types: Optional[List[EntityTypeRead]] = []
workflows: Optional[List[WorkflowRead]] = []
tags: Optional[List[TagRead]] = []


class SignalUpdate(SignalBase):
id: PrimaryKey
filters: Optional[List[SignalFilterRead]] = []
entity_types: Optional[List[EntityTypeRead]] = []
workflows: Optional[List[WorkflowRead]] = []
tags: Optional[List[TagRead]] = []


class SignalRead(SignalBase):
id: PrimaryKey
entity_types: Optional[List[EntityTypeRead]] = []
filters: Optional[List[SignalFilterRead]] = []
workflows: Optional[List[WorkflowRead]] = []
tags: Optional[List[TagRead]] = []


Expand Down
20 changes: 20 additions & 0 deletions src/dispatch/signal/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from dispatch.exceptions import NotFoundError
from dispatch.project import service as project_service
from dispatch.tag import service as tag_service
from dispatch.workflow import service as workflow_service

from .models import (
Signal,
Expand Down Expand Up @@ -184,6 +185,7 @@ def create(*, db_session: Session, signal_in: SignalCreate) -> Signal:
"filters",
"tags",
"entity_types",
"workflows",
}
),
project=project,
Expand Down Expand Up @@ -212,6 +214,15 @@ def create(*, db_session: Session, signal_in: SignalCreate) -> Signal:

signal.filters = filters

workflows = []
for w in signal_in.workflows:
workflow = workflow_service.get_by_name_or_raise(
db_session=db_session, project_id=signal.project.id, workflow_in=w
)
workflows.append(workflow)

signal.workflows = workflows

if signal_in.case_priority:
case_priority = case_priority_service.get_by_name_or_default(
db_session=db_session, project_id=project.id, case_priority_in=signal_in.case_priority
Expand Down Expand Up @@ -272,6 +283,15 @@ def update(*, db_session: Session, signal: Signal, signal_in: SignalUpdate) -> S

signal.filters = filters

workflows = []
for w in signal_in.workflows:
workflow = workflow_service.get_by_name_or_raise(
db_session=db_session, project_id=signal.project.id, workflow_in=w
)
workflows.append(workflow)

signal.workflows = workflows

if signal_in.case_priority:
case_priority = case_priority_service.get_by_name_or_default(
db_session=db_session,
Expand Down
2 changes: 1 addition & 1 deletion src/dispatch/static/dispatch/src/case/EditSheet.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<v-tab key="timeline"> Timeline </v-tab>
<v-tab key="workflows"> Workflows </v-tab>
<v-tab key="entities"> Entities </v-tab>
<v-tab key="signals">Signals</v-tab>
<v-tab key="signals"> Signals </v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item key="details">
Expand Down
138 changes: 138 additions & 0 deletions src/dispatch/static/dispatch/src/components/BaseCombobox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<template>
<v-combobox
:items="items"
:label="label"
:loading="loading"
:search-input.sync="search"
@update:search-input="getFilteredData()"
chips
clearable
hide-selected
:item-text="getItemName"
item-value="id"
multiple
no-filter
v-model="selectedItems"
>
<slot name="selection" v-bind="{ attr, item, selected }"></slot>
</v-combobox>
</template>

<script>
import { debounce } from "lodash"

import SearchUtils from "@/search/utils"

export default {
name: "BaseCombobox",

props: {
value: {
type: Array,
default: () => [],
},
label: {
type: String,
default: "Add Items",
},
api: {
type: Object,
required: true,
},
project: {
type: Object,
required: true,
},
},

data() {
return {
loading: false,
items: [],
more: false,
numItems: 5,
createdItem: null,
search: null,
attr: {},
item: null,
selected: null,
}
},

computed: {
selectedItems: {
get() {
return this.value
},
set(value) {
this.$emit("input", value)
},
},
},

created() {
this.fetchData()
this.$watch(
(vm) => [vm.project],
() => {
this.fetchData()
}
)
},

watch: {
createdItem: function (newVal) {
this.items.push(newVal)
this.selectedItems = [newVal]
},
},

methods: {
loadMore() {
this.numItems = this.numItems + 5
this.fetchData()
},
getItemName(item) {
if (item === null) {
return "Unknown"
}
return item.name
},
fetchData() {
this.error = null
this.loading = "error"

let filterOptions = {
q: this.search,
itemsPerPage: this.numItems,
}

if (this.project) {
filterOptions = {
...filterOptions,
filters: {
project: [this.project],
},
}
filterOptions = SearchUtils.createParametersFromTableOptions({ ...filterOptions })
}

this.api.getAll(filterOptions).then((response) => {
this.items = response.data.items
this.total = response.data.total

if (this.items.length < this.total) {
this.more = true
} else {
this.more = false
}

this.loading = false
})
},
getFilteredData: debounce(function () {
this.fetchData()
}, 500),
},
}
</script>
25 changes: 25 additions & 0 deletions src/dispatch/static/dispatch/src/signal/NewEditDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,26 @@
</v-card-text>
</v-card>
</v-col>
<v-col cols="12">
<v-card flat tile>
<v-app-bar color="white" flat>
<v-toolbar-title class="subtitle-2"> Workflow(s) </v-toolbar-title>
<v-spacer></v-spacer>
<v-tooltip max-width="250px" bottom>
<template v-slot:activator="{ on, attrs }">
<v-icon v-bind="attrs" v-on="on"> help_outline </v-icon>
</template>
Defines a workflow.
</v-tooltip>
</v-app-bar>
<v-card-text>
<workflow-combobox
v-model="selected.workflows"
:project="project"
></workflow-combobox>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-navigation-drawer>
</ValidationObserver>
Expand All @@ -217,6 +237,8 @@ import CasePrioritySelect from "@/case/priority/CasePrioritySelect.vue"
import EntityTypeFilterCombobox from "@/entity_type/EntityTypeFilterCombobox.vue"
import SignalFilterCombobox from "@/signal/filter/SignalFilterCombobox.vue"
import TagFilterAutoComplete from "@/tag/TagFilterAutoComplete.vue"
import WorkflowSelect from "@/workflow/WorkflowSelect.vue"
import WorkflowCombobox from "@/workflow/WorkflowCombobox.vue"

extend("required", {
...required,
Expand All @@ -233,6 +255,8 @@ export default {
SignalFilterCombobox,
EntityTypeFilterCombobox,
TagFilterAutoComplete,
WorkflowSelect,
WorkflowCombobox,
},

computed: {
Expand All @@ -254,6 +278,7 @@ export default {
"selected.signal_definition",
"selected.source",
"selected.tags",
"selected.workflows",
"selected.project",
"selected.loading",
]),
Expand Down
11 changes: 11 additions & 0 deletions src/dispatch/static/dispatch/src/signal/SignalInstanceTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,29 @@
</v-tooltip>
</template>
<template v-slot:item.data-table-actions="{ item }">
<v-btn icon @click="showRun({ type: 'signal', data: item })">
<v-icon>mdi-play-circle-outline</v-icon>
</v-btn>
<workflow-run-modal />
<raw-signal-viewer v-model="item.raw" />
</template>
</v-data-table>
</template>

<script>
import { mapFields } from "vuex-map-fields"
import { mapActions } from "vuex"

import SignalPopover from "@/signal/SignalPopover.vue"
import RawSignalViewer from "@/signal/RawSignalViewer.vue"
import WorkflowRunModal from "@/workflow/RunModal.vue"

export default {
name: "SignalInstanceTab",
components: {
SignalPopover,
RawSignalViewer,
WorkflowRunModal,
},
props: {
inputSignalInstances: {
Expand All @@ -51,6 +58,7 @@ export default {
data() {
return {
menu: false,
workflowRunDialog: false,
headers: [
{ text: "Signal", value: "signal", sortable: false },
{ text: "Entities", value: "entities", sortable: false },
Expand All @@ -68,5 +76,8 @@ export default {
return this.signal_instances
},
},
methods: {
...mapActions("workflow", ["showRun"]),
},
}
</script>
Loading