diff --git a/grafana-plugin/src/containers/RemoteFilters/RemoteFilters.tsx b/grafana-plugin/src/containers/RemoteFilters/RemoteFilters.tsx index a7ec0582c9..e79edd3568 100644 --- a/grafana-plugin/src/containers/RemoteFilters/RemoteFilters.tsx +++ b/grafana-plugin/src/containers/RemoteFilters/RemoteFilters.tsx @@ -48,7 +48,7 @@ interface RemoteFiltersProps extends WithStoreProps { grafanaTeamStore: GrafanaTeamStore; skipFilterOptionFn?: (filterOption: FilterOption) => boolean; } -interface RemoteFiltersState { +export interface RemoteFiltersState { filterOptions?: FilterOption[]; filters: FilterOption[]; values: Record; diff --git a/grafana-plugin/src/models/alertgroup/alertgroup.helpers.ts b/grafana-plugin/src/models/alertgroup/alertgroup.helpers.ts index 0aa4c69283..542d6f2fe1 100644 --- a/grafana-plugin/src/models/alertgroup/alertgroup.helpers.ts +++ b/grafana-plugin/src/models/alertgroup/alertgroup.helpers.ts @@ -1,6 +1,8 @@ +import { ActionKey } from 'models/loader/action-keys'; import { makeRequest } from 'network/network'; import { ApiSchemas } from 'network/oncall-api/api.types'; import { onCallApi } from 'network/oncall-api/http-client'; +import { AutoLoadingState } from 'utils/decorators'; import { AlertGroupStore } from './alertgroup'; @@ -33,6 +35,26 @@ export class AlertGroupHelper { return (await onCallApi().POST('/alertgroups/bulk_action/', { params: {}, body: data })).data; } + @AutoLoadingState(ActionKey.INCIDENTS_BULK_UPDATE) + static async updateBulkActionAndRefresh( + data: ApiSchemas['AlertGroupBulkActionRequest'], + alertGroupStore: AlertGroupStore, + onFinally?: () => void + ) { + try { + alertGroupStore.setLiveUpdatesPaused(true); + + await AlertGroupHelper.bulkAction(data); + + // pull new data + await alertGroupStore.fetchAlertGroups(); + } finally { + alertGroupStore.setLiveUpdatesPaused(false); + + onFinally?.(); + } + } + static async renderPreview(id: ApiSchemas['AlertGroup']['pk'], template_name: string, template_body: string) { return ( await onCallApi().POST('/alertgroups/{id}/preview_template/', { diff --git a/grafana-plugin/src/models/alertgroup/alertgroup.ts b/grafana-plugin/src/models/alertgroup/alertgroup.ts index f3c9397202..d050eec30b 100644 --- a/grafana-plugin/src/models/alertgroup/alertgroup.ts +++ b/grafana-plugin/src/models/alertgroup/alertgroup.ts @@ -79,7 +79,7 @@ export class AlertGroupStore { const newAlerts = new Map( results.map((alert: ApiSchemas['AlertGroup']) => { const oldAlert = this.alerts.get(alert.pk) || {}; - const mergedAlertData = { ...oldAlert, ...alert, undoAction: alert.undoAction }; + const mergedAlertData = { ...oldAlert, ...alert }; return [alert.pk, mergedAlertData]; }) ); @@ -114,7 +114,8 @@ export class AlertGroupStore { this.rootStore.setPageTitle(`#${alertGroup.inside_organization_number} ${alertGroup.render_for_web.title}`); } - async fetchIncidentsAndStats(isPollingJob = false) { + @AutoLoadingState(ActionKey.FETCH_INCIDENTS_AND_STATS) + async fetchIncidentsAndStats(isPollingJob = false): Promise { await Promise.all([ this.fetchStats(IncidentStatus.Firing), this.fetchStats(IncidentStatus.Acknowledged), @@ -218,13 +219,11 @@ export class AlertGroupStore { composeFailureMessageFn, }) async resolve(id: ApiSchemas['AlertGroup']['pk']) { - this.setLiveUpdatesPaused(true); const { data } = await onCallApi({ skipErrorHandling: true }).POST('/alertgroups/{id}/resolve/', { params: { path: { id } }, }); this.updateAlert(id, { ...data, - undoAction: AlertAction.Resolve, }); } @@ -233,11 +232,9 @@ export class AlertGroupStore { composeFailureMessageFn, }) async unresolve(id: ApiSchemas['AlertGroup']['pk']) { - this.setLiveUpdatesPaused(true); const { data } = await onCallApi().POST('/alertgroups/{id}/unresolve/', { params: { path: { id } } }); this.updateAlert(id, { ...data, - undoAction: AlertAction.unResolve, }); } @@ -246,11 +243,9 @@ export class AlertGroupStore { composeFailureMessageFn, }) async acknowledge(id: ApiSchemas['AlertGroup']['pk']) { - this.setLiveUpdatesPaused(true); const { data } = await onCallApi().POST('/alertgroups/{id}/acknowledge/', { params: { path: { id } } }); this.updateAlert(id, { ...data, - undoAction: AlertAction.Acknowledge, }); } @@ -259,11 +254,9 @@ export class AlertGroupStore { composeFailureMessageFn, }) async unacknowledge(id: ApiSchemas['AlertGroup']['pk']) { - this.setLiveUpdatesPaused(true); const { data } = await onCallApi().POST('/alertgroups/{id}/unacknowledge/', { params: { path: { id } } }); this.updateAlert(id, { ...data, - undoAction: AlertAction.unAcknowledge, }); } @@ -272,14 +265,12 @@ export class AlertGroupStore { composeFailureMessageFn, }) async silence(id: ApiSchemas['AlertGroup']['pk'], delay: number) { - this.setLiveUpdatesPaused(true); const { data } = await onCallApi().POST('/alertgroups/{id}/silence/', { params: { path: { id } }, body: { delay }, }); this.updateAlert(id, { ...data, - undoAction: AlertAction.Silence, }); } @@ -288,11 +279,9 @@ export class AlertGroupStore { composeFailureMessageFn, }) async unsilence(id: ApiSchemas['AlertGroup']['pk']) { - this.setLiveUpdatesPaused(true); const { data } = await onCallApi().POST('/alertgroups/{id}/unsilence/', { params: { path: { id } } }); this.updateAlert(id, { ...data, - undoAction: AlertAction.unSilence, }); } @@ -305,8 +294,14 @@ export class AlertGroupStore { [AlertAction.Resolve]: this.resolve, [AlertAction.unResolve]: this.unresolve, }; + if (actionToMethodMap[action]) { - await actionToMethodMap[action](id, delay); + try { + this.setLiveUpdatesPaused(true); + await actionToMethodMap[action](id, delay); + } finally { + this.setLiveUpdatesPaused(false); + } } } diff --git a/grafana-plugin/src/models/loader/action-keys.ts b/grafana-plugin/src/models/loader/action-keys.ts index 0b093adb53..ba8ac1760f 100644 --- a/grafana-plugin/src/models/loader/action-keys.ts +++ b/grafana-plugin/src/models/loader/action-keys.ts @@ -8,6 +8,8 @@ export enum ActionKey { FETCH_INCIDENTS = 'FETCH_INCIDENTS', FETCH_INCIDENTS_POLLING = 'FETCH_INCIDENTS_POLLING', FETCH_INCIDENTS_AND_STATS = 'FETCH_INCIDENTS_AND_STATS', + INCIDENTS_BULK_UPDATE = 'INCIDENTS_BULK_UPDATE', + UPDATE_FILTERS_AND_FETCH_INCIDENTS = 'UPDATE_FILTERS_AND_FETCH_INCIDENTS', UPDATE_SERVICENOW_TOKEN = 'UPDATE_SERVICENOW_TOKEN', FETCH_INTEGRATIONS = 'FETCH_INTEGRATIONS', diff --git a/grafana-plugin/src/network/oncall-api/types-generator/custom-schemas.ts b/grafana-plugin/src/network/oncall-api/types-generator/custom-schemas.ts index 193d65b86a..67eb8858e9 100644 --- a/grafana-plugin/src/network/oncall-api/types-generator/custom-schemas.ts +++ b/grafana-plugin/src/network/oncall-api/types-generator/custom-schemas.ts @@ -1,5 +1,3 @@ -import { AlertAction } from 'models/alertgroup/alertgroup.types'; - // Custom properties not exposed by OpenAPI schema should be defined here export type CustomApiSchemas = { Webhook: { @@ -15,7 +13,6 @@ export type CustomApiSchemas = { }; }; AlertGroup: { - undoAction: AlertAction; loading?: boolean; created_at?: string; }; diff --git a/grafana-plugin/src/pages/incidents/Incidents.tsx b/grafana-plugin/src/pages/incidents/Incidents.tsx index 1924e014c4..2416392bb0 100644 --- a/grafana-plugin/src/pages/incidents/Incidents.tsx +++ b/grafana-plugin/src/pages/incidents/Incidents.tsx @@ -34,7 +34,7 @@ import { Tutorial } from 'components/Tutorial/Tutorial'; import { TutorialStep } from 'components/Tutorial/Tutorial.types'; import { ColumnsSelectorWrapper } from 'containers/ColumnsSelectorWrapper/ColumnsSelectorWrapper'; import { IncidentsFiltersType } from 'containers/IncidentsFilters/IncidentFilters.types'; -import { RemoteFilters } from 'containers/RemoteFilters/RemoteFilters'; +import { RemoteFilters, RemoteFiltersState } from 'containers/RemoteFilters/RemoteFilters'; import { TeamName } from 'containers/TeamName/TeamName'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; import { AlertReceiveChannelHelper } from 'models/alert_receive_channel/alert_receive_channel.helpers'; @@ -73,7 +73,6 @@ interface IncidentsPageProps extends WithStoreProps, PageProps, RouteComponentPr interface IncidentsPageState { selectedIncidentIds: Array; - affectedRows: { [key: string]: boolean }; filters?: Record; pagination: Pagination; showAddAlertGroupForm: boolean; @@ -128,7 +127,6 @@ class _IncidentsPage extends React.Component void) => void, + filtersOnFiltersValueChange: (status: string, newStatuses: string[]) => void, + store: RootStore, + theme: GrafanaTheme2 + ) { const { values } = filtersState; const { stats } = store.alertGroupStore; @@ -220,7 +224,7 @@ class _IncidentsPage extends React.Component @@ -234,7 +238,7 @@ class _IncidentsPage extends React.Component @@ -248,7 +252,7 @@ class _IncidentsPage extends React.Component @@ -262,7 +266,7 @@ class _IncidentsPage extends React.Component @@ -273,13 +277,12 @@ class _IncidentsPage extends React.Component void) => void, + filtersOnFiltersValueChange: (status: string, newStatuses: string[]) => void ) => { return (selected: boolean) => { const { values } = filtersState; - const { status: statusFilter = [] } = values; let newStatuses = [...statusFilter]; @@ -348,7 +351,6 @@ class _IncidentsPage extends React.Component { - const { selectedIncidentIds, affectedRows, isHorizontalScrolling } = this.state; + const { selectedIncidentIds, isHorizontalScrolling } = this.state; const { store, theme } = this.props; if (!store.alertGroupStore.bulkActions) { return null; } - const { results } = AlertGroupHelper.getAlertSearchResult(store.alertGroupStore); - const hasSelected = selectedIncidentIds.length > 0; - const isLoading = LoaderHelper.isLoading(store.loaderStore, ActionKey.FETCH_INCIDENTS); - const hasInvalidatedAlert = Boolean( - (results && results.some((alert: ApiSchemas['AlertGroup']) => alert.undoAction)) || - Object.keys(affectedRows).length - ); + const isLoading = LoaderHelper.isLoading(store.loaderStore, [ + ActionKey.FETCH_INCIDENTS, + ActionKey.FETCH_INCIDENTS_POLLING, + ActionKey.FETCH_INCIDENTS_AND_STATS, + ActionKey.INCIDENTS_BULK_UPDATE, + ]); const styles = getStyles(theme); + const isBulkUpdate = LoaderHelper.isLoading(store.loaderStore, ActionKey.INCIDENTS_BULK_UPDATE); return (
@@ -453,9 +455,9 @@ class _IncidentsPage extends React.Component @@ -464,9 +466,9 @@ class _IncidentsPage extends React.Component @@ -475,9 +477,9 @@ class _IncidentsPage extends React.Component @@ -486,8 +488,8 @@ class _IncidentsPage extends React.Component this.getBulkActionClickHandler('silence', ev)} + disabled={!hasSelected || isBulkUpdate} + onSelect={(ev) => this.onBulkActionClick('silence', ev)} /> )} @@ -499,15 +501,6 @@ class _IncidentsPage extends React.Component
- - - Results out of date - - - - @@ -915,10 +908,11 @@ class _IncidentsPage extends React.Component Promise) => { const { store } = this.props; - return (e: SyntheticEvent) => { + return async (e: SyntheticEvent) => { e.stopPropagation(); - return store.alertGroupStore.doIncidentAction(incidentId, action); + await store.alertGroupStore.doIncidentAction(incidentId, action); + await store.alertGroupStore.fetchIncidentsAndStats(); }; }; @@ -940,72 +934,57 @@ class _IncidentsPage extends React.Component { - const { selectedIncidentIds, affectedRows } = this.state; - const { store } = this.props; - - this.setPollingInterval(); + onBulkActionClick = async (action: ApiSchemas['AlertGroupBulkActionRequest']['action'], event?: any) => { + const { selectedIncidentIds } = this.state; + const { alertGroupStore } = this.props.store; - store.alertGroupStore.setLiveUpdatesPaused(true); const delay = typeof event === 'number' ? event : 0; - this.setState( + await AlertGroupHelper.updateBulkActionAndRefresh( { - selectedIncidentIds: [], - affectedRows: selectedIncidentIds.reduce( - (acc, incidentId: ApiSchemas['AlertGroup']['pk']) => ({ - ...acc, - [incidentId]: true, - }), - affectedRows - ), + action, + alert_group_pks: selectedIncidentIds, + delay, }, - () => { - AlertGroupHelper.bulkAction({ - action, - alert_group_pks: selectedIncidentIds, - delay, - }); + alertGroupStore, + async () => { + // clear selected incident on finally and update incident stats + this.setState({ selectedIncidentIds: [] }); + this.setPollingInterval(); + + await alertGroupStore.fetchIncidentsAndStats(); } ); }; - onIncidentsUpdateClick = () => { - const { store } = this.props; - - this.setState({ affectedRows: {} }, () => { - store.alertGroupStore.fetchIncidentsAndStats(); - }); - }; - clearPollingInterval() { clearInterval(this.pollingIntervalId); this.pollingIntervalId = null; } setPollingInterval() { - const startPolling = (delayed = false) => { - this.pollingIntervalId = setTimeout( - async () => { - const isBrowserWindowInactive = document.hidden; - if ( - !isBrowserWindowInactive && - !LoaderHelper.isLoading(this.props.store.loaderStore, [ - ActionKey.FETCH_INCIDENTS, - ActionKey.FETCH_INCIDENTS_POLLING, - ]) && - !this.props.store.alertGroupStore.liveUpdatesPaused - ) { - await this.props.store.alertGroupStore.fetchIncidentsAndStats(true); - } + const startPolling = () => { + this.pollingIntervalId = setTimeout(async () => { + const isBrowserWindowInactive = document.hidden; + const { liveUpdatesPaused } = this.props.store.alertGroupStore; + + if ( + !liveUpdatesPaused && + !isBrowserWindowInactive && + !LoaderHelper.isLoading(this.props.store.loaderStore, [ + ActionKey.FETCH_INCIDENTS, + ActionKey.FETCH_INCIDENTS_POLLING, + ]) + ) { + await this.props.store.alertGroupStore.fetchIncidentsAndStats(true); + } - if (this.pollingIntervalId === null) { - return; - } - startPolling(isBrowserWindowInactive); - }, - delayed ? 60 * 1000 : POLLING_NUM_SECONDS * 1000 - ); + if (this.pollingIntervalId === null) { + return; + } + + startPolling(); + }, POLLING_NUM_SECONDS * 1000); }; startPolling(); diff --git a/grafana-plugin/src/pages/incidents/parts/IncidentSilenceModal.tsx b/grafana-plugin/src/pages/incidents/parts/IncidentSilenceModal.tsx index c959d2663e..96b3a196a1 100644 --- a/grafana-plugin/src/pages/incidents/parts/IncidentSilenceModal.tsx +++ b/grafana-plugin/src/pages/incidents/parts/IncidentSilenceModal.tsx @@ -66,7 +66,7 @@ const IncidentSilenceModal: React.FC = ({ type="primary" className={cx(utilStyles.overflowChild, bem(utilStyles.overflowChild, 'line-1'))} > - Silence alert group #${alertGroupID} ${alertGroupName} + Silence alert group #{alertGroupID} ${alertGroupName} } className={styles.root}