Skip to content

Commit

Permalink
Merge pull request #4672 from grafana/dev
Browse files Browse the repository at this point in the history
v1.8.4
  • Loading branch information
brojd authored Jul 15, 2024
2 parents d211b71 + 851ca2e commit b81f3b4
Show file tree
Hide file tree
Showing 17 changed files with 232 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ def _make_button(text, action_id_step_class_name, action_id_scenario_step="distr
"action_id": ScenarioStep.get_step("declare_incident", "DeclareIncidentStep").routing_uid(),
}

show_timeline_button = _make_button(
":blue_book: Show Timeline", "OpenAlertGroupTimelineDialogStep", "alertgroup_timeline"
)

buttons = []
if not alert_group.is_maintenance_incident:
if not alert_group.resolved:
Expand Down Expand Up @@ -282,6 +286,8 @@ def _make_button(text, action_id_step_class_name, action_id_scenario_step="distr
if not alert_group.resolved:
buttons.append(resolve_button)

buttons.append(show_timeline_button)

return [{"type": "actions", "elements": buttons}]

def _get_invitation_attachment(self):
Expand Down
78 changes: 78 additions & 0 deletions engine/apps/slack/scenarios/alertgroup_timeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import typing

from apps.api.permissions import RBACPermission
from apps.slack.chatops_proxy_routing import make_private_metadata
from apps.slack.scenarios import scenario_step
from apps.slack.scenarios.slack_renderer import AlertGroupLogSlackRenderer
from apps.slack.types import (
Block,
BlockActionType,
EventPayload,
InteractiveMessageActionType,
ModalView,
PayloadType,
ScenarioRoute,
)

from .step_mixins import AlertGroupActionsMixin

if typing.TYPE_CHECKING:
from apps.slack.models import SlackTeamIdentity, SlackUserIdentity


class OpenAlertGroupTimelineDialogStep(AlertGroupActionsMixin, scenario_step.ScenarioStep):
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]

def process_scenario(
self,
slack_user_identity: "SlackUserIdentity",
slack_team_identity: "SlackTeamIdentity",
payload: EventPayload,
) -> None:
alert_group = self.get_alert_group(slack_team_identity, payload)
if not self.is_authorized(alert_group):
self.open_unauthorized_warning(payload)
return

private_metadata = {
"organization_id": self.organization.pk,
"alert_group_pk": alert_group.pk,
"message_ts": payload.get("message_ts") or payload["container"]["message_ts"],
}

alert_receive_channel = alert_group.channel
past_log_report = AlertGroupLogSlackRenderer.render_alert_group_past_log_report_text(alert_group)
future_log_report = AlertGroupLogSlackRenderer.render_alert_group_future_log_report_text(alert_group)
blocks: typing.List[Block.Section] = []
if past_log_report:
blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": past_log_report}})
if future_log_report:
blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": future_log_report}})

view: ModalView = {
"blocks": blocks,
"type": "modal",
"title": {
"type": "plain_text",
"text": "Alert group log",
},
"private_metadata": make_private_metadata(private_metadata, alert_receive_channel.organization),
}

self._slack_client.views_open(trigger_id=payload["trigger_id"], view=view)


STEPS_ROUTING: ScenarioRoute.RoutingSteps = [
{
"payload_type": PayloadType.INTERACTIVE_MESSAGE,
"action_type": InteractiveMessageActionType.BUTTON,
"action_name": OpenAlertGroupTimelineDialogStep.routing_uid(),
"step": OpenAlertGroupTimelineDialogStep,
},
{
"payload_type": PayloadType.BLOCK_ACTIONS,
"block_action_type": BlockActionType.BUTTON,
"block_action_id": OpenAlertGroupTimelineDialogStep.routing_uid(),
"step": OpenAlertGroupTimelineDialogStep,
},
]
28 changes: 15 additions & 13 deletions engine/apps/slack/scenarios/slack_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,26 @@

class AlertGroupLogSlackRenderer:
@staticmethod
def render_incident_log_report_for_slack(alert_group: "AlertGroup"):
def render_alert_group_past_log_report_text(alert_group: "AlertGroup"):
from apps.alerts.models import AlertGroupLogRecord
from apps.base.models import UserNotificationPolicyLogRecord

log_builder = IncidentLogBuilder(alert_group)
all_log_records = log_builder.get_log_records_list()

attachments = []

# get rendered logs
result = ""
for log_record in all_log_records: # list of AlertGroupLogRecord and UserNotificationPolicyLogRecord logs
if type(log_record) is AlertGroupLogRecord:
result += f"{log_record.rendered_incident_log_line(for_slack=True)}\n"
elif type(log_record) is UserNotificationPolicyLogRecord:
result += f"{log_record.rendered_notification_log_line(for_slack=True)}\n"

attachments.append(
{
"text": result,
}
)
result = ""
return result

# check if escalation or invitation active
@staticmethod
def render_alert_group_future_log_report_text(alert_group: "AlertGroup"):
log_builder = IncidentLogBuilder(alert_group)
result = ""
if not (alert_group.resolved or alert_group.wiped_at or alert_group.root_alert_group):
escalation_policies_plan = log_builder.get_incident_escalation_plan(for_slack=True)
if escalation_policies_plan:
Expand All @@ -43,11 +38,18 @@ def render_incident_log_report_for_slack(alert_group: "AlertGroup"):
for time in sorted(escalation_policies_plan):
for plan_line in escalation_policies_plan[time]:
result += f"*{humanize.naturaldelta(time)}:* {plan_line}\n"
return result

if len(result) > 0:
@staticmethod
def render_incident_log_report_for_slack(alert_group: "AlertGroup"):
attachments = []
past = AlertGroupLogSlackRenderer.render_alert_group_past_log_report_text(alert_group)
future = AlertGroupLogSlackRenderer.render_alert_group_future_log_report_text(alert_group)
text = past + future
if len(text) > 0:
attachments.append(
{
"text": result,
"text": text,
}
)
return attachments
2 changes: 2 additions & 0 deletions engine/apps/slack/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from apps.slack.client import SlackClient
from apps.slack.errors import SlackAPIError
from apps.slack.scenarios.alertgroup_appearance import STEPS_ROUTING as ALERTGROUP_APPEARANCE_ROUTING
from apps.slack.scenarios.alertgroup_timeline import STEPS_ROUTING as ALERTGROUP_TIMELINE_ROUTING

# Importing routes from scenarios
from apps.slack.scenarios.declare_incident import STEPS_ROUTING as DECLARE_INCIDENT_ROUTING
Expand Down Expand Up @@ -52,6 +53,7 @@
SCENARIOS_ROUTES.extend(SHIFT_SWAP_REQUESTS_ROUTING)
SCENARIOS_ROUTES.extend(SLACK_CHANNEL_INTEGRATION_ROUTING)
SCENARIOS_ROUTES.extend(ALERTGROUP_APPEARANCE_ROUTING)
SCENARIOS_ROUTES.extend(ALERTGROUP_TIMELINE_ROUTING)
SCENARIOS_ROUTES.extend(RESOLUTION_NOTE_ROUTING)
SCENARIOS_ROUTES.extend(SLACK_USERGROUP_UPDATE_ROUTING)
SCENARIOS_ROUTES.extend(CHANNEL_ROUTING)
Expand Down
44 changes: 33 additions & 11 deletions grafana-plugin/src/components/Policy/EscalationPolicy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,11 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
data,
isDisabled,
theme,
store: { userStore },
store: {
userStore,
// dereferencing items is needed to rerender GSelect
userStore: { items: userItems },
},
} = this.props;
const { notify_to_users_queue } = data;
const styles = getEscalationPolicyStyles(theme);
Expand All @@ -187,7 +191,7 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
onChange={this.getOnChangeHandler('notify_to_users_queue')}
getOptionLabel={({ value }: SelectableValue) => <UserTooltip id={value} />}
width={'auto'}
items={userStore.items}
items={userItems}
fetchItemsFn={userStore.fetchItems}
fetchItemFn={async (id) => await userStore.fetchItemById({ userPk: id, skipIfAlreadyPending: true })}
getSearchResult={() => UserHelper.getSearchResult(userStore)}
Expand Down Expand Up @@ -352,7 +356,12 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
data,
theme,
isDisabled,
store: { grafanaTeamStore, scheduleStore },
store: {
scheduleStore,
// dereferencing items is needed to rerender GSelect
grafanaTeamStore: { items: grafanaTeamItems },
scheduleStore: { items: scheduleItems },
},
} = this.props;
const { notify_schedule } = data;
const styles = getEscalationPolicyStyles(theme);
Expand All @@ -362,7 +371,7 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
<GSelect<Schedule>
allowClear
disabled={isDisabled}
items={scheduleStore.items}
items={scheduleItems}
fetchItemsFn={scheduleStore.updateItems}
fetchItemFn={scheduleStore.updateItem}
getSearchResult={scheduleStore.getSearchResult}
Expand All @@ -373,7 +382,7 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
value={notify_schedule}
onChange={this.getOnChangeHandler('notify_schedule')}
getOptionLabel={(item: SelectableValue) => {
const team = grafanaTeamStore.items[scheduleStore.items[item.value].team];
const team = grafanaTeamItems[scheduleStore.items[item.value].team];
return (
<>
<Text>{item.label} </Text>
Expand All @@ -391,7 +400,11 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
data,
theme,
isDisabled,
store: { userGroupStore },
store: {
userGroupStore,
// dereferencing items is needed to rerender GSelect
userGroupStore: { items: userGroupItems },
},
} = this.props;

const { notify_to_group } = data;
Expand All @@ -402,7 +415,7 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
<GSelect<UserGroup>
allowClear
disabled={isDisabled}
items={userGroupStore.items}
items={userGroupItems}
fetchItemsFn={userGroupStore.updateItems}
fetchItemFn={userGroupStore.fetchItemById}
getSearchResult={userGroupStore.getSearchResult}
Expand All @@ -423,7 +436,12 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
data,
theme,
isDisabled,
store: { grafanaTeamStore, outgoingWebhookStore },
store: {
grafanaTeamStore,
outgoingWebhookStore,
// dereferencing items is needed to rerender GSelect
outgoingWebhookStore: { items: outgoingWebhookItems },
},
} = this.props;

const { custom_webhook } = data;
Expand All @@ -433,7 +451,7 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
<WithPermissionControlTooltip key="custom-webhook" userAction={UserActions.EscalationChainsWrite}>
<GSelect<ApiSchemas['Webhook']>
disabled={isDisabled}
items={outgoingWebhookStore.items}
items={outgoingWebhookItems}
fetchItemsFn={outgoingWebhookStore.updateItems}
fetchItemFn={outgoingWebhookStore.updateItem}
getSearchResult={outgoingWebhookStore.getSearchResult}
Expand Down Expand Up @@ -466,15 +484,19 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
const {
data,
isDisabled,
store: { grafanaTeamStore },
store: {
grafanaTeamStore,
// dereferencing items is needed to rerender GSelect
grafanaTeamStore: { items: grafanaTeamItems },
},
} = this.props;
const { notify_to_team_members } = data;

return (
<WithPermissionControlTooltip key="notify_to_team_members" userAction={UserActions.EscalationChainsWrite}>
<GSelect<GrafanaTeam>
disabled={isDisabled}
items={grafanaTeamStore.items}
items={grafanaTeamItems}
fetchItemsFn={grafanaTeamStore.updateItems}
fetchItemFn={grafanaTeamStore.fetchItemById}
getSearchResult={grafanaTeamStore.getSearchResult}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useCallback } from 'react';

import { HorizontalGroup, InlineSwitch } from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';

import { GSelect } from 'containers/GSelect/GSelect';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
Expand All @@ -18,11 +19,16 @@ interface MSTeamsConnectorProps {
channelFilterId: ChannelFilter['id'];
}

export const MSTeamsConnector = (props: MSTeamsConnectorProps) => {
export const MSTeamsConnector = observer((props: MSTeamsConnectorProps) => {
const { channelFilterId } = props;

const store = useStore();
const { alertReceiveChannelStore, msteamsChannelStore } = store;
const {
alertReceiveChannelStore,
msteamsChannelStore,
// dereferencing items is needed to rerender GSelect
msteamsChannelStore: { items: msteamsChannelItems },
} = store;

const channelFilter = store.alertReceiveChannelStore.channelFilters[channelFilterId];

Expand Down Expand Up @@ -57,7 +63,7 @@ export const MSTeamsConnector = (props: MSTeamsConnectorProps) => {
<GSelect<MSTeamsChannel>
allowClear
className={cx('select', 'control')}
items={msteamsChannelStore.items}
items={msteamsChannelItems}
fetchItemsFn={msteamsChannelStore.updateItems}
fetchItemFn={msteamsChannelStore.updateById}
getSearchResult={msteamsChannelStore.getSearchResult}
Expand All @@ -71,4 +77,4 @@ export const MSTeamsConnector = (props: MSTeamsConnectorProps) => {
</HorizontalGroup>
</div>
);
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useCallback } from 'react';

import { HorizontalGroup, InlineSwitch } from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';

import { GSelect } from 'containers/GSelect/GSelect';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
Expand All @@ -19,14 +20,16 @@ interface SlackConnectorProps {
channelFilterId: ChannelFilter['id'];
}

export const SlackConnector = (props: SlackConnectorProps) => {
export const SlackConnector = observer((props: SlackConnectorProps) => {
const { channelFilterId } = props;

const store = useStore();
const {
organizationStore: { currentOrganization },
alertReceiveChannelStore,
slackChannelStore,
// dereferencing items is needed to rerender GSelect
slackChannelStore: { items: slackChannelItems },
} = store;

const channelFilter = store.alertReceiveChannelStore.channelFilters[channelFilterId];
Expand Down Expand Up @@ -57,7 +60,7 @@ export const SlackConnector = (props: SlackConnectorProps) => {
<GSelect<SlackChannel>
allowClear
className={cx('select', 'control')}
items={slackChannelStore.items}
items={slackChannelItems}
fetchItemsFn={slackChannelStore.updateItems}
fetchItemFn={slackChannelStore.updateItem}
getSearchResult={getSearchResult}
Expand Down Expand Up @@ -94,4 +97,4 @@ export const SlackConnector = (props: SlackConnectorProps) => {

return results;
}
};
});
Loading

0 comments on commit b81f3b4

Please sign in to comment.