diff --git a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py index 7f2eded8f5..c5b5570cb2 100644 --- a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py @@ -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: @@ -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): diff --git a/engine/apps/slack/scenarios/alertgroup_timeline.py b/engine/apps/slack/scenarios/alertgroup_timeline.py new file mode 100644 index 0000000000..87976b493c --- /dev/null +++ b/engine/apps/slack/scenarios/alertgroup_timeline.py @@ -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, + }, +] diff --git a/engine/apps/slack/scenarios/slack_renderer.py b/engine/apps/slack/scenarios/slack_renderer.py index 982671a788..ee5de69c22 100644 --- a/engine/apps/slack/scenarios/slack_renderer.py +++ b/engine/apps/slack/scenarios/slack_renderer.py @@ -10,16 +10,13 @@ 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: @@ -27,14 +24,12 @@ def render_incident_log_report_for_slack(alert_group: "AlertGroup"): 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: @@ -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 diff --git a/engine/apps/slack/views.py b/engine/apps/slack/views.py index 9578bbc43d..7f23b70c4b 100644 --- a/engine/apps/slack/views.py +++ b/engine/apps/slack/views.py @@ -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 @@ -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) diff --git a/grafana-plugin/src/components/Policy/EscalationPolicy.tsx b/grafana-plugin/src/components/Policy/EscalationPolicy.tsx index c91700e98a..592f19d934 100644 --- a/grafana-plugin/src/components/Policy/EscalationPolicy.tsx +++ b/grafana-plugin/src/components/Policy/EscalationPolicy.tsx @@ -168,7 +168,11 @@ class _EscalationPolicy extends React.Component { 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); @@ -187,7 +191,7 @@ class _EscalationPolicy extends React.Component { onChange={this.getOnChangeHandler('notify_to_users_queue')} getOptionLabel={({ value }: SelectableValue) => } width={'auto'} - items={userStore.items} + items={userItems} fetchItemsFn={userStore.fetchItems} fetchItemFn={async (id) => await userStore.fetchItemById({ userPk: id, skipIfAlreadyPending: true })} getSearchResult={() => UserHelper.getSearchResult(userStore)} @@ -352,7 +356,12 @@ class _EscalationPolicy extends React.Component { 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); @@ -362,7 +371,7 @@ class _EscalationPolicy extends React.Component { allowClear disabled={isDisabled} - items={scheduleStore.items} + items={scheduleItems} fetchItemsFn={scheduleStore.updateItems} fetchItemFn={scheduleStore.updateItem} getSearchResult={scheduleStore.getSearchResult} @@ -373,7 +382,7 @@ class _EscalationPolicy extends React.Component { 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 ( <> {item.label} @@ -391,7 +400,11 @@ class _EscalationPolicy extends React.Component { data, theme, isDisabled, - store: { userGroupStore }, + store: { + userGroupStore, + // dereferencing items is needed to rerender GSelect + userGroupStore: { items: userGroupItems }, + }, } = this.props; const { notify_to_group } = data; @@ -402,7 +415,7 @@ class _EscalationPolicy extends React.Component { allowClear disabled={isDisabled} - items={userGroupStore.items} + items={userGroupItems} fetchItemsFn={userGroupStore.updateItems} fetchItemFn={userGroupStore.fetchItemById} getSearchResult={userGroupStore.getSearchResult} @@ -423,7 +436,12 @@ class _EscalationPolicy extends React.Component { 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; @@ -433,7 +451,7 @@ class _EscalationPolicy extends React.Component { disabled={isDisabled} - items={outgoingWebhookStore.items} + items={outgoingWebhookItems} fetchItemsFn={outgoingWebhookStore.updateItems} fetchItemFn={outgoingWebhookStore.updateItem} getSearchResult={outgoingWebhookStore.getSearchResult} @@ -466,7 +484,11 @@ class _EscalationPolicy extends React.Component { 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; @@ -474,7 +496,7 @@ class _EscalationPolicy extends React.Component { disabled={isDisabled} - items={grafanaTeamStore.items} + items={grafanaTeamItems} fetchItemsFn={grafanaTeamStore.updateItems} fetchItemFn={grafanaTeamStore.fetchItemById} getSearchResult={grafanaTeamStore.getSearchResult} diff --git a/grafana-plugin/src/containers/AlertRules/parts/connectors/MSTeamsConnector.tsx b/grafana-plugin/src/containers/AlertRules/parts/connectors/MSTeamsConnector.tsx index 58ba0805dd..138c3d1f68 100644 --- a/grafana-plugin/src/containers/AlertRules/parts/connectors/MSTeamsConnector.tsx +++ b/grafana-plugin/src/containers/AlertRules/parts/connectors/MSTeamsConnector.tsx @@ -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'; @@ -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]; @@ -57,7 +63,7 @@ export const MSTeamsConnector = (props: MSTeamsConnectorProps) => { allowClear className={cx('select', 'control')} - items={msteamsChannelStore.items} + items={msteamsChannelItems} fetchItemsFn={msteamsChannelStore.updateItems} fetchItemFn={msteamsChannelStore.updateById} getSearchResult={msteamsChannelStore.getSearchResult} @@ -71,4 +77,4 @@ export const MSTeamsConnector = (props: MSTeamsConnectorProps) => { ); -}; +}); diff --git a/grafana-plugin/src/containers/AlertRules/parts/connectors/SlackConnector.tsx b/grafana-plugin/src/containers/AlertRules/parts/connectors/SlackConnector.tsx index 3c2713dddd..ba7abdeb81 100644 --- a/grafana-plugin/src/containers/AlertRules/parts/connectors/SlackConnector.tsx +++ b/grafana-plugin/src/containers/AlertRules/parts/connectors/SlackConnector.tsx @@ -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'; @@ -19,7 +20,7 @@ interface SlackConnectorProps { channelFilterId: ChannelFilter['id']; } -export const SlackConnector = (props: SlackConnectorProps) => { +export const SlackConnector = observer((props: SlackConnectorProps) => { const { channelFilterId } = props; const store = useStore(); @@ -27,6 +28,8 @@ export const SlackConnector = (props: SlackConnectorProps) => { organizationStore: { currentOrganization }, alertReceiveChannelStore, slackChannelStore, + // dereferencing items is needed to rerender GSelect + slackChannelStore: { items: slackChannelItems }, } = store; const channelFilter = store.alertReceiveChannelStore.channelFilters[channelFilterId]; @@ -57,7 +60,7 @@ export const SlackConnector = (props: SlackConnectorProps) => { allowClear className={cx('select', 'control')} - items={slackChannelStore.items} + items={slackChannelItems} fetchItemsFn={slackChannelStore.updateItems} fetchItemFn={slackChannelStore.updateItem} getSearchResult={getSearchResult} @@ -94,4 +97,4 @@ export const SlackConnector = (props: SlackConnectorProps) => { return results; } -}; +}); diff --git a/grafana-plugin/src/containers/AlertRules/parts/connectors/TelegramConnector.tsx b/grafana-plugin/src/containers/AlertRules/parts/connectors/TelegramConnector.tsx index 73d15e8fd4..1d5db0dbad 100644 --- a/grafana-plugin/src/containers/AlertRules/parts/connectors/TelegramConnector.tsx +++ b/grafana-plugin/src/containers/AlertRules/parts/connectors/TelegramConnector.tsx @@ -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'; @@ -18,9 +19,14 @@ interface TelegramConnectorProps { channelFilterId: ChannelFilter['id']; } -export const TelegramConnector = ({ channelFilterId }: TelegramConnectorProps) => { +export const TelegramConnector = observer(({ channelFilterId }: TelegramConnectorProps) => { const store = useStore(); - const { alertReceiveChannelStore, telegramChannelStore } = store; + const { + alertReceiveChannelStore, + telegramChannelStore, + // dereferencing items is needed to rerender GSelect + telegramChannelStore: { items: telegramChannelItems }, + } = store; const channelFilter = store.alertReceiveChannelStore.channelFilters[channelFilterId]; @@ -49,7 +55,7 @@ export const TelegramConnector = ({ channelFilterId }: TelegramConnectorProps) = allowClear className={cx('select', 'control')} - items={telegramChannelStore.items} + items={telegramChannelItems} fetchItemsFn={telegramChannelStore.updateItems} fetchItemFn={telegramChannelStore.updateById} getSearchResult={telegramChannelStore.getSearchResult} @@ -63,4 +69,4 @@ export const TelegramConnector = ({ channelFilterId }: TelegramConnectorProps) = ); -}; +}); diff --git a/grafana-plugin/src/containers/AttachIncidentForm/AttachIncidentForm.tsx b/grafana-plugin/src/containers/AttachIncidentForm/AttachIncidentForm.tsx index a894a8bf54..508a299a13 100644 --- a/grafana-plugin/src/containers/AttachIncidentForm/AttachIncidentForm.tsx +++ b/grafana-plugin/src/containers/AttachIncidentForm/AttachIncidentForm.tsx @@ -42,7 +42,11 @@ const GroupedAlertNumber = observer(({ value }: GroupedAlertNumberProps) => { export const AttachIncidentForm = observer(({ id, onUpdate, onHide }: AttachIncidentFormProps) => { const store = useStore(); - const { alertGroupStore } = store; + const { + alertGroupStore, + // dereferencing alerts is needed to rerender GSelect + alertGroupStore: { alerts: alertGroupAlerts }, + } = store; const [selected, setSelected] = useState(undefined); @@ -75,7 +79,7 @@ export const AttachIncidentForm = observer(({ id, onUpdate, onHide }: AttachInci > - items={Object.fromEntries(alertGroupStore.alerts)} + items={Object.fromEntries(alertGroupAlerts)} fetchItemsFn={async (query: string) => { await alertGroupStore.fetchAlertGroups(false, query); }} diff --git a/grafana-plugin/src/containers/EscalationChainForm/EscalationChainForm.tsx b/grafana-plugin/src/containers/EscalationChainForm/EscalationChainForm.tsx index dc248f8159..ec694f7b0f 100644 --- a/grafana-plugin/src/containers/EscalationChainForm/EscalationChainForm.tsx +++ b/grafana-plugin/src/containers/EscalationChainForm/EscalationChainForm.tsx @@ -2,6 +2,7 @@ import React, { ChangeEvent, FC, useCallback, useState } from 'react'; import { Button, Field, HorizontalGroup, Input, Modal } from '@grafana/ui'; import cn from 'classnames/bind'; +import { observer } from 'mobx-react'; import { GSelect } from 'containers/GSelect/GSelect'; import { EscalationChain } from 'models/escalation_chain/escalation_chain.types'; @@ -26,11 +27,17 @@ interface EscalationChainFormProps { const cx = cn.bind(styles); -export const EscalationChainForm: FC = (props) => { +export const EscalationChainForm: FC = observer((props) => { const { escalationChainId, onHide, onSubmit: onSubmitProp, mode } = props; const store = useStore(); - const { escalationChainStore, userStore, grafanaTeamStore } = store; + const { + escalationChainStore, + userStore, + grafanaTeamStore, + // dereferencing items is needed to rerender GSelect + grafanaTeamStore: { items: grafanaTeamItems }, + } = store; const user = userStore.currentUser; @@ -93,7 +100,7 @@ export const EscalationChainForm: FC = (props) => {
- items={grafanaTeamStore.items} + items={grafanaTeamItems} fetchItemsFn={grafanaTeamStore.updateItems} fetchItemFn={grafanaTeamStore.fetchItemById} getSearchResult={grafanaTeamStore.getSearchResult} @@ -125,4 +132,4 @@ export const EscalationChainForm: FC = (props) => {
); -}; +}); diff --git a/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx b/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx index b1d480959a..bc8dbc381b 100644 --- a/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx +++ b/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx @@ -25,7 +25,12 @@ export const GrafanaTeamSelect = observer( ({ onSelect, onHide, withoutModal, defaultValue }: GrafanaTeamSelectProps) => { const store = useStore(); - const { userStore, grafanaTeamStore } = store; + const { + userStore, + grafanaTeamStore, + // dereferencing items is needed to rerender GSelect + grafanaTeamStore: { items: grafanaTeamItems }, + } = store; const user = userStore.currentUser; const [selectedTeam, setSelectedTeam] = useState(defaultValue); @@ -53,7 +58,7 @@ export const GrafanaTeamSelect = observer( const select = ( - items={grafanaTeamStore.items} + items={grafanaTeamItems} fetchItemsFn={grafanaTeamStore.updateItems} fetchItemFn={grafanaTeamStore.fetchItemById} getSearchResult={grafanaTeamStore.getSearchResult} diff --git a/grafana-plugin/src/containers/IntegrationForm/IntegrationForm.tsx b/grafana-plugin/src/containers/IntegrationForm/IntegrationForm.tsx index 80964db24d..39c58f6e88 100644 --- a/grafana-plugin/src/containers/IntegrationForm/IntegrationForm.tsx +++ b/grafana-plugin/src/containers/IntegrationForm/IntegrationForm.tsx @@ -97,7 +97,13 @@ export const IntegrationForm = observer( const history = useHistory(); const styles = useStyles2(getIntegrationFormStyles); const isNew = id === 'new'; - const { userStore, grafanaTeamStore, alertReceiveChannelStore } = store; + const { + userStore, + grafanaTeamStore, + // dereferencing items is needed to rerender GSelect + grafanaTeamStore: { items: grafanaTeamItems }, + alertReceiveChannelStore, + } = store; const data: Partial = isNew ? { @@ -234,7 +240,7 @@ export const IntegrationForm = observer( placeholder="Assign to team" {...field} {...{ - items: grafanaTeamStore.items, + items: grafanaTeamItems, fetchItemsFn: grafanaTeamStore.updateItems, fetchItemFn: grafanaTeamStore.fetchItemById, getSearchResult: grafanaTeamStore.getSearchResult, diff --git a/grafana-plugin/src/containers/MaintenanceForm/MaintenanceForm.tsx b/grafana-plugin/src/containers/MaintenanceForm/MaintenanceForm.tsx index 54ec54eee0..d8d30084f2 100644 --- a/grafana-plugin/src/containers/MaintenanceForm/MaintenanceForm.tsx +++ b/grafana-plugin/src/containers/MaintenanceForm/MaintenanceForm.tsx @@ -37,7 +37,11 @@ interface FormFields { export const MaintenanceForm = observer((props: MaintenanceFormProps) => { const { onUpdate, onHide, initialData = {} } = props; - const { alertReceiveChannelStore } = useStore(); + const { + alertReceiveChannelStore, + // dereferencing items is needed to rerender GSelect + alertReceiveChannelStore: { items: alertReceiveChannelItems }, + } = useStore(); const onSubmit = useCallback(async (data) => { try { @@ -87,7 +91,7 @@ export const MaintenanceForm = observer((props: MaintenanceFormProps) => { > disabled - items={alertReceiveChannelStore.items} + items={alertReceiveChannelItems} fetchItemsFn={alertReceiveChannelStore.fetchItems} fetchItemFn={alertReceiveChannelStore.fetchItemById} getSearchResult={() => AlertReceiveChannelHelper.getSearchResult(alertReceiveChannelStore)} diff --git a/grafana-plugin/src/containers/OutgoingWebhookForm/OutgoingWebhookFormFields.tsx b/grafana-plugin/src/containers/OutgoingWebhookForm/OutgoingWebhookFormFields.tsx index 041d620012..aa8111bdf6 100644 --- a/grafana-plugin/src/containers/OutgoingWebhookForm/OutgoingWebhookFormFields.tsx +++ b/grafana-plugin/src/containers/OutgoingWebhookForm/OutgoingWebhookFormFields.tsx @@ -48,7 +48,12 @@ const FORWARD_RADIO_OPTIONS = [ export const OutgoingWebhookFormFields: React.FC = observer( ({ preset, hasLabelsFeature, onTemplateEditClick }) => { - const { grafanaTeamStore, alertReceiveChannelStore } = useStore(); + const { + grafanaTeamStore, + // dereferencing items is needed to rerender GSelect + grafanaTeamStore: { items: grafanaTeamItems }, + alertReceiveChannelStore, + } = useStore(); const { items, fetchItems, fetchItemById } = alertReceiveChannelStore; const { control, @@ -98,7 +103,7 @@ export const OutgoingWebhookFormFields: React.FC > allowClear - items={grafanaTeamStore.items} + items={grafanaTeamItems} fetchItemsFn={grafanaTeamStore.updateItems} fetchItemFn={grafanaTeamStore.fetchItemById} getSearchResult={grafanaTeamStore.getSearchResult} diff --git a/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.tsx b/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.tsx index f3248a9e09..0dae9b81af 100644 --- a/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.tsx +++ b/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.tsx @@ -199,7 +199,11 @@ const FormFields = ({ scheduleType }: { scheduleType: Schedule['type'] }) => { }; const ScheduleCommonFields = () => { - const { grafanaTeamStore } = useStore(); + const { + grafanaTeamStore, + // dereferencing items is needed to rerender GSelect + grafanaTeamStore: { items: grafanaTeamItems }, + } = useStore(); const { control, formState } = useFormContext(); const { errors } = formState; @@ -222,7 +226,7 @@ const ScheduleCommonFields = () => { render={({ field }) => ( - items={grafanaTeamStore.items} + items={grafanaTeamItems} fetchItemsFn={grafanaTeamStore.updateItems} fetchItemFn={grafanaTeamStore.fetchItemById} getSearchResult={grafanaTeamStore.getSearchResult} @@ -239,12 +243,18 @@ const ScheduleCommonFields = () => { ); }; -const ScheduleNotificationSettingsFields = () => { +const ScheduleNotificationSettingsFields = observer(() => { const store = useStore(); const styles = useStyles2(getStyles); - const { slackChannelStore, userGroupStore } = store; + const { + slackChannelStore, + userGroupStore, + // dereferencing items is needed to rerender GSelect + slackChannelStore: { items: slackChannelItems }, + userGroupStore: { items: userGroupItems }, + } = store; const { control, @@ -265,7 +275,7 @@ const ScheduleNotificationSettingsFields = () => { > allowClear - items={slackChannelStore.items} + items={slackChannelItems} fetchItemsFn={slackChannelStore.updateItems} fetchItemFn={slackChannelStore.updateItem} getSearchResult={slackChannelStore.getSearchResult} @@ -291,7 +301,7 @@ const ScheduleNotificationSettingsFields = () => { > allowClear - items={userGroupStore.items} + items={userGroupItems} fetchItemsFn={userGroupStore.updateItems} fetchItemFn={userGroupStore.fetchItemById} getSearchResult={userGroupStore.getSearchResult} @@ -395,7 +405,7 @@ const ScheduleNotificationSettingsFields = () => { /> ); -}; +}); export const getStyles = () => ({ collapse: css` diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/GoogleCalendar/GoogleCalendar.tsx b/grafana-plugin/src/containers/UserSettings/parts/tabs/GoogleCalendar/GoogleCalendar.tsx index 84c06db2cf..9a9dfd536d 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/GoogleCalendar/GoogleCalendar.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/GoogleCalendar/GoogleCalendar.tsx @@ -19,7 +19,12 @@ import { UserActions } from 'utils/authorization/authorization'; import { DOCS_ROOT } from 'utils/consts'; const GoogleCalendar: React.FC<{ id: ApiSchemas['User']['pk'] }> = observer(({ id }) => { - const { userStore, scheduleStore } = useStore(); + const { + userStore, + scheduleStore, + // dereferencing items is needed to rerender GSelect + scheduleStore: { items: scheduleItems }, + } = useStore(); const utils = useStyles2(getUtilStyles); @@ -92,7 +97,7 @@ const GoogleCalendar: React.FC<{ id: ApiSchemas['User']['pk'] }> = observer(({ i isMulti allowClear disabled={false} - items={scheduleStore.items} + items={scheduleItems} fetchItemsFn={scheduleStore.updateItems} fetchItemFn={scheduleStore.updateItem} getSearchResult={scheduleStore.getSearchResult} diff --git a/grafana-plugin/src/pages/settings/tabs/ChatOps/tabs/SlackSettings/SlackSettings.tsx b/grafana-plugin/src/pages/settings/tabs/ChatOps/tabs/SlackSettings/SlackSettings.tsx index 13915572f6..1933359ff4 100644 --- a/grafana-plugin/src/pages/settings/tabs/ChatOps/tabs/SlackSettings/SlackSettings.tsx +++ b/grafana-plugin/src/pages/settings/tabs/ChatOps/tabs/SlackSettings/SlackSettings.tsx @@ -112,6 +112,8 @@ class _SlackSettings extends Component { organizationStore: { currentOrganization }, slackStore, slackChannelStore, + // dereferencing items is needed to rerender GSelect + slackChannelStore: { items: slackChannelItems }, } = store; return ( @@ -126,7 +128,7 @@ class _SlackSettings extends Component { > - items={slackChannelStore.items} + items={slackChannelItems} fetchItemsFn={slackChannelStore.updateItems} fetchItemFn={slackChannelStore.updateItem} getSearchResult={slackChannelStore.getSearchResult}