Skip to content

Commit

Permalink
Merge branch 'master' into enhancement/timeline_export
Browse files Browse the repository at this point in the history
  • Loading branch information
whitdog47 authored Dec 20, 2023
2 parents 79cbe0a + 879d29f commit 6539d5e
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 105 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
],
"[python]": {
"editor.codeActionsOnSave": {
"source.organizeImports": false
"source.organizeImports": "never"
}
},
}
54 changes: 27 additions & 27 deletions src/dispatch/plugins/dispatch_slack/case/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,34 +191,34 @@ def create_signal_messages(case_id: int, channel_id: str, db_session: Session) -
channel_id=channel_id,
).json()

signal_metadata_blocks = [
Actions(
elements=[
Button(
text="Raw Data",
action_id=SignalNotificationActions.view,
value=button_metadata,
),
Button(
text="Snooze",
action_id=SignalNotificationActions.snooze,
value=button_metadata,
),
]
# The button URL must have at least one character.
# Otherwise, Slack will raise a Validation error.
# external_url is not a required field. If it's empty, an empty list is added,
# which effectively doesn't add anything to the elements list.
+ [
Button(
text="Response Plan",
action_id="button-link",
url=first_instance_signal.external_url,
)
]
if first_instance_signal.external_url
else []
# Define the initial elements with "Raw Data" and "Snooze" buttons
elements = [
Button(
text="Raw Data",
action_id=SignalNotificationActions.view,
value=button_metadata,
),
Button(
text="Snooze",
action_id=SignalNotificationActions.snooze,
value=button_metadata,
),
]

# Check if `first_instance_signal.external_url` is not empty
if first_instance_signal.external_url:
# If `first_instance_signal.external_url` is not empty, add the "Response Plan" button
elements.append(
Button(
text="Response Plan",
action_id="button-link",
url=first_instance_signal.external_url,
)
)

# Create the Actions block with the elements
signal_metadata_blocks = [
Actions(elements=elements),
Section(text="*Alerts*"),
Divider(),
Section(text=f"{num_of_instances} alerts observed in this case."),
Expand Down
171 changes: 103 additions & 68 deletions src/dispatch/signal/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,100 +568,135 @@ def update_instance(
return signal_instance


def filter_signal(*, db_session: Session, signal_instance: SignalInstance) -> bool:
"""
Apply filter actions to the signal instance.
The function first checks if the signal instance is snoozed. If not snoozed,
it checks for a deduplication rule set on the signal instance. If no
deduplication rule is set, a default deduplication rule is applied,
grouping all signal instances together for a 1-hour window, regardless of
the entities in the signal instance.
def filter_snooze(*, db_session: Session, signal_instance: SignalInstance) -> SignalInstance:
"""Filters a signal instance for snoozing.
Args:
db_session (Session): Database session.
signal_instance (SignalInstance): Signal instance to be filtered.
Returns:
bool: True if the signal instance is filtered, False otherwise.
SignalInstance: The filtered signal instance.
"""

filtered = False
for f in signal_instance.signal.filters:
if f.mode != SignalFilterMode.active:
continue

if f.action != SignalFilterAction.snooze:
continue

if f.expiration.replace(tzinfo=timezone.utc) <= datetime.now(timezone.utc):
continue

query = db_session.query(SignalInstance).filter(
SignalInstance.signal_id == signal_instance.signal_id
)
query = apply_filter_specific_joins(SignalInstance, f.expression, query)
query = apply_filters(query, f.expression)
# an expression is not required for snoozing, if absent we snooze regardless of entity
if f.expression:
instances = query.filter(SignalInstance.id == signal_instance.id).all()

# order matters, check for snooze before deduplication
# we check to see if the current instances match's it's signals snooze filter
if f.action == SignalFilterAction.snooze:
if f.expiration.replace(tzinfo=timezone.utc) <= datetime.now(timezone.utc):
continue

# an expression is not required for snoozing, if absent we snooze regardless of entity
if f.expression:
instances = query.filter(SignalInstance.id == signal_instance.id).all()

if instances:
signal_instance.filter_action = SignalFilterAction.snooze
filtered = True
break
else:
if instances:
signal_instance.filter_action = SignalFilterAction.snooze
filtered = True
break
else:
signal_instance.filter_action = SignalFilterAction.snooze
break

elif f.action == SignalFilterAction.deduplicate:
window = datetime.now(timezone.utc) - timedelta(minutes=f.window)
query = query.filter(SignalInstance.created_at >= window)
query = query.join(SignalInstance.entities).filter(
Entity.id.in_([e.id for e in signal_instance.entities])
)
query = query.filter(SignalInstance.id != signal_instance.id)
return signal_instance

# get the earliest instance
query = query.order_by(asc(SignalInstance.created_at))
instances = query.all()

if instances:
# associate with existing case
signal_instance.case_id = instances[0].case_id
signal_instance.filter_action = SignalFilterAction.deduplicate
filtered = True
break
else:
# Check if there's a deduplication rule set on the signal
has_dedup_filter = any(
f.action == SignalFilterAction.deduplicate for f in signal_instance.signal.filters
def filter_dedup(*, db_session: Session, signal_instance: SignalInstance) -> SignalInstance:
"""Filters a signal instance for deduplication.
Args:
db_session (Session): Database session.
signal_instance (SignalInstance): Signal instance to be filtered.
Returns:
SignalInstance: The filtered signal instance.
"""
for f in signal_instance.signal.filters:
if f.mode != SignalFilterMode.active:
continue

if f.action != SignalFilterAction.deduplicate:
continue

query = db_session.query(SignalInstance).filter(
SignalInstance.signal_id == signal_instance.signal_id
)
# Apply the default deduplication rule if there's no deduplication rule set on the signal
# and the signal instance is not snoozed
if not has_dedup_filter and not filtered:
default_dedup_window = datetime.now(timezone.utc) - timedelta(hours=1)
instance = (
db_session.query(SignalInstance)
.filter(
SignalInstance.signal_id == signal_instance.signal_id,
SignalInstance.created_at >= default_dedup_window,
SignalInstance.id != signal_instance.id,
SignalInstance.case_id.isnot(None), # noqa
)
.with_entities(SignalInstance.case_id)
.order_by(desc(SignalInstance.created_at))
.first()
query = apply_filter_specific_joins(SignalInstance, f.expression, query)
query = apply_filters(query, f.expression)

window = datetime.now(timezone.utc) - timedelta(minutes=f.window)
query = query.filter(SignalInstance.created_at >= window)
query = query.join(SignalInstance.entities).filter(
Entity.id.in_([e.id for e in signal_instance.entities])
)
query = query.filter(SignalInstance.id != signal_instance.id)

# get the earliest instance
query = query.order_by(asc(SignalInstance.created_at))
instances = query.all()

if instances:
# associate with existing case
signal_instance.case_id = instances[0].case_id
signal_instance.filter_action = SignalFilterAction.deduplicate
break
# apply default deduplication rule
else:
default_dedup_window = datetime.now(timezone.utc) - timedelta(hours=1)
instance = (
db_session.query(SignalInstance)
.filter(
SignalInstance.signal_id == signal_instance.signal_id,
SignalInstance.created_at >= default_dedup_window,
SignalInstance.id != signal_instance.id,
SignalInstance.case_id.isnot(None), # noqa
)
if instance:
signal_instance.case_id = instance.case_id
signal_instance.filter_action = SignalFilterAction.deduplicate
filtered = True
.with_entities(SignalInstance.case_id)
.order_by(desc(SignalInstance.created_at))
.first()
)
if instance:
signal_instance.case_id = instance.case_id
signal_instance.filter_action = SignalFilterAction.deduplicate

return signal_instance

if not filtered:

def filter_signal(*, db_session: Session, signal_instance: SignalInstance) -> bool:
"""
Apply filter actions to the signal instance.
The function first checks if the signal instance is snoozed. If not snoozed,
it checks for a deduplication rule set on the signal instance. If no
deduplication rule is set, a default deduplication rule is applied,
grouping all signal instances together for a 1-hour window, regardless of
the entities in the signal instance.
Args:
db_session (Session): Database session.
signal_instance (SignalInstance): Signal instance to be filtered.
Returns:
bool: True if the signal instance is filtered, False otherwise.
"""
filtered = False

signal_instance = filter_snooze(db_session=db_session, signal_instance=signal_instance)

# we only dedupe if we haven't been snoozed
if not signal_instance.filter_action:
signal_instance = filter_dedup(db_session=db_session, signal_instance=signal_instance)

if not signal_instance.filter_action:
signal_instance.filter_action = SignalFilterAction.none
else:
filtered = True

db_session.commit()
return filtered
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,14 @@ export default {
}
if (props.project) {
if (Array.isArray(props.project) && props.project.length > 0) {
filterOptions = {
filters: {
project: props.project,
},
...filterOptions,
if (Array.isArray(props.project)) {
if (props.project.length > 0) {
filterOptions = {
filters: {
project: props.project,
},
...filterOptions,
}
}
} else {
filterOptions = {
Expand Down
29 changes: 26 additions & 3 deletions src/dispatch/static/dispatch/src/search/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,22 @@ export default {
return options, queryParams
},
createParametersFromTableOptions(options, model, rawFilters) {
let [sortBy, descending] = this.createSortExpression(options.sortBy, model)
let expression = this.createFilterExpression(options.filters, model)
delete options.filters
delete options.sortBy

if (!expression.length) {
if (rawFilters != null && typeof rawFilters[Symbol.iterator] === "function") {
expression = { and: [...rawFilters] }
return { ...options, filter: JSON.stringify(expression) }
return {
...options,
sortBy: sortBy,
descending: descending,
filter: JSON.stringify(expression),
}
} else {
return options
return { ...options, sortBy: sortBy, descending: descending }
}
}

Expand All @@ -45,7 +52,23 @@ export default {
expression = { and: expression }
}

return { ...options, filter: JSON.stringify(expression) }
return {
...options,
sortBy: sortBy,
descending: descending,
filter: JSON.stringify(expression),
}
},
createSortExpression(sort) {
let sortBy = []
let descending = []
each(sort, function (value) {
if (value.key) {
sortBy.push(value.key)
descending.push(value.order == "desc" ? true : false)
}
})
return [sortBy, descending]
},
/**
* Create a filter expression for searching for items in a database
Expand Down
Loading

0 comments on commit 6539d5e

Please sign in to comment.