diff --git a/CHANGELOG.md b/CHANGELOG.md index b23b8560..a998ad9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Handle message to reply to not found in Telegram send log ([#3587](https://github.com/grafana/oncall/pull/3587)) +- Upgrade mobx lib to the latest version 6.12.0 ([#3453](https://github.com/grafana/oncall/issues/3453)) ## v1.3.81 (2023-12-28) diff --git a/grafana-plugin/babel.config.json b/grafana-plugin/babel.config.json index 2cba8024..6699239a 100644 --- a/grafana-plugin/babel.config.json +++ b/grafana-plugin/babel.config.json @@ -8,7 +8,7 @@ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-transform-destructuring", { "useBuiltIns": true }], "@babel/plugin-transform-runtime", - "@babel/proposal-class-properties", + ["@babel/plugin-proposal-class-properties", { "loose": false }], "@babel/transform-regenerator", "@babel/plugin-transform-template-literals" ] diff --git a/grafana-plugin/package.json b/grafana-plugin/package.json index 719b6c57..5aed5207 100644 --- a/grafana-plugin/package.json +++ b/grafana-plugin/package.json @@ -133,8 +133,8 @@ "dayjs": "^1.11.5", "eslint-plugin-import": "^2.25.4", "immutability-helper": "^3.1.1", - "mobx": "5.13.0", - "mobx-react": "6.1.1", + "mobx": "6.12.0", + "mobx-react": "9.1.0", "object-hash": "^3.0.0", "openapi-fetch": "^0.8.1", "prettier": "^2.8.2", diff --git a/grafana-plugin/src/components/Policy/EscalationPolicy.tsx b/grafana-plugin/src/components/Policy/EscalationPolicy.tsx index 83587c02..2eeab636 100644 --- a/grafana-plugin/src/components/Policy/EscalationPolicy.tsx +++ b/grafana-plugin/src/components/Policy/EscalationPolicy.tsx @@ -23,7 +23,6 @@ import { import { GrafanaTeamStore } from 'models/grafana_team/grafana_team'; import { OutgoingWebhookStore } from 'models/outgoing_webhook/outgoing_webhook'; import { ScheduleStore } from 'models/schedule/schedule'; -import { WaitDelay } from 'models/wait_delay'; import { SelectOption } from 'state/types'; import { getVar } from 'utils/DOM'; import { UserActions } from 'utils/authorization'; @@ -255,7 +254,7 @@ export class EscalationPolicy extends React.Component ({ + options={waitDelays.map((waitDelay: SelectOption) => ({ value: waitDelay.value, label: waitDelay.display_name, }))} diff --git a/grafana-plugin/src/components/Policy/NotificationPolicy.tsx b/grafana-plugin/src/components/Policy/NotificationPolicy.tsx index 7f541056..3d8d909c 100644 --- a/grafana-plugin/src/components/Policy/NotificationPolicy.tsx +++ b/grafana-plugin/src/components/Policy/NotificationPolicy.tsx @@ -8,13 +8,12 @@ import { SortableElement } from 'react-sortable-hoc'; import PluginLink from 'components/PluginLink/PluginLink'; import Timeline from 'components/Timeline/Timeline'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; -import { Channel } from 'models/channel'; -import { NotificationPolicyType, prepareNotificationPolicy } from 'models/notification_policy'; -import { NotifyBy } from 'models/notify_by'; +import { Channel } from 'models/channel/channel'; +import { NotificationPolicyType, prepareNotificationPolicy } from 'models/notification_policy/notification_policy'; import { User } from 'models/user/user.types'; -import { WaitDelay } from 'models/wait_delay'; import { RootStore } from 'state'; import { AppFeature } from 'state/features'; +import { SelectOption } from 'state/types'; import { UserAction } from 'utils/authorization'; import DragHandle from './DragHandle'; @@ -34,8 +33,8 @@ export interface NotificationPolicyProps { onDelete: (id: string) => void; notificationChoices: any[]; channels?: any[]; - waitDelays?: WaitDelay[]; - notifyByOptions?: NotifyBy[]; + waitDelays?: SelectOption[]; + notifyByOptions?: SelectOption[]; telegramVerified: boolean; phoneStatus: number; isMobileAppConnected: boolean; @@ -185,7 +184,7 @@ export class NotificationPolicy extends React.Component ({ + options={waitDelays.map((waitDelay: SelectOption) => ({ label: waitDelay.display_name, value: waitDelay.value, }))} @@ -208,7 +207,7 @@ export class NotificationPolicy extends React.Component ({ + options={notifyByOptions.map((notifyByOption: SelectOption) => ({ label: notifyByOption.display_name, value: notifyByOption.value, }))} diff --git a/grafana-plugin/src/containers/AlertRules/parts/index.tsx b/grafana-plugin/src/containers/AlertRules/parts/index.tsx index 00f9c97d..051373e9 100644 --- a/grafana-plugin/src/containers/AlertRules/parts/index.tsx +++ b/grafana-plugin/src/containers/AlertRules/parts/index.tsx @@ -5,7 +5,7 @@ import { VerticalGroup } from '@grafana/ui'; import Timeline from 'components/Timeline/Timeline'; import SlackConnector from 'containers/AlertRules/parts/connectors/SlackConnector'; import TelegramConnector from 'containers/AlertRules/parts/connectors/TelegramConnector'; -import { ChannelFilter } from 'models/channel_filter'; +import { ChannelFilter } from 'models/channel_filter/channel_filter.types'; import { AppFeature } from 'state/features'; import { useStore } from 'state/useStore'; import { getVar } from 'utils/DOM'; diff --git a/grafana-plugin/src/containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay.tsx b/grafana-plugin/src/containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay.tsx index 04665e91..07cf5893 100644 --- a/grafana-plugin/src/containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay.tsx +++ b/grafana-plugin/src/containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay.tsx @@ -11,7 +11,7 @@ import TooltipBadge from 'components/TooltipBadge/TooltipBadge'; import styles from 'containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay.module.scss'; import { RouteButtonsDisplay } from 'containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay'; import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; -import { ChannelFilter } from 'models/channel_filter'; +import { ChannelFilter } from 'models/channel_filter/channel_filter.types'; import CommonIntegrationHelper from 'pages/integration/CommonIntegration.helper'; import IntegrationHelper from 'pages/integration/Integration.helper'; import { useStore } from 'state/useStore'; diff --git a/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx index 2646c04b..12e83668 100644 --- a/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx +++ b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx @@ -30,7 +30,7 @@ import styles from 'containers/IntegrationContainers/ExpandedIntegrationRouteDis import TeamName from 'containers/TeamName/TeamName'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; -import { AlertTemplatesDTO } from 'models/alert_templates'; +import { AlertTemplatesDTO } from 'models/alert_templates/alert_templates'; import { ChannelFilter } from 'models/channel_filter/channel_filter.types'; import { EscalationChain } from 'models/escalation_chain/escalation_chain.types'; import CommonIntegrationHelper from 'pages/integration/CommonIntegration.helper'; diff --git a/grafana-plugin/src/containers/IntegrationContainers/IntegrationTemplatesList.tsx b/grafana-plugin/src/containers/IntegrationContainers/IntegrationTemplatesList.tsx index 4b1a4ba3..32b89682 100644 --- a/grafana-plugin/src/containers/IntegrationContainers/IntegrationTemplatesList.tsx +++ b/grafana-plugin/src/containers/IntegrationContainers/IntegrationTemplatesList.tsx @@ -10,7 +10,7 @@ import { MONACO_READONLY_CONFIG } from 'components/MonacoEditor/MonacoEditor.con import Text from 'components/Text/Text'; import { templatesToRender } from 'containers/IntegrationContainers/IntegrationTemplatesList.config'; import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; -import { AlertTemplatesDTO } from 'models/alert_templates'; +import { AlertTemplatesDTO } from 'models/alert_templates/alert_templates'; import IntegrationHelper from 'pages/integration/Integration.helper'; import styles from 'pages/integration/Integration.module.scss'; import { MONACO_INPUT_HEIGHT_TALL } from 'pages/integration/IntegrationCommon.config'; diff --git a/grafana-plugin/src/containers/IntegrationTemplate/IntegrationTemplate.tsx b/grafana-plugin/src/containers/IntegrationTemplate/IntegrationTemplate.tsx index c747be2a..87d43367 100644 --- a/grafana-plugin/src/containers/IntegrationTemplate/IntegrationTemplate.tsx +++ b/grafana-plugin/src/containers/IntegrationTemplate/IntegrationTemplate.tsx @@ -19,7 +19,7 @@ import TemplateResult from 'containers/TemplateResult/TemplateResult'; import TemplatesAlertGroupsList, { TEMPLATE_PAGE } from 'containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; -import { AlertTemplatesDTO } from 'models/alert_templates'; +import { AlertTemplatesDTO } from 'models/alert_templates/alert_templates'; import { Alert } from 'models/alertgroup/alertgroup.types'; import { ChannelFilter } from 'models/channel_filter/channel_filter.types'; import { TemplateOptions } from 'pages/integration/Integration.config'; diff --git a/grafana-plugin/src/containers/PersonalNotificationSettings/PersonalNotificationSettings.tsx b/grafana-plugin/src/containers/PersonalNotificationSettings/PersonalNotificationSettings.tsx index fef69a4f..b939a7fa 100644 --- a/grafana-plugin/src/containers/PersonalNotificationSettings/PersonalNotificationSettings.tsx +++ b/grafana-plugin/src/containers/PersonalNotificationSettings/PersonalNotificationSettings.tsx @@ -10,7 +10,7 @@ import SortableList from 'components/SortableList/SortableList'; import Text from 'components/Text/Text'; import Timeline from 'components/Timeline/Timeline'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; -import { NotificationPolicyType } from 'models/notification_policy'; +import { NotificationPolicyType } from 'models/notification_policy/notification_policy'; import { User as UserType } from 'models/user/user.types'; import { AppFeature } from 'state/features'; import { useStore } from 'state/useStore'; diff --git a/grafana-plugin/src/containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList.tsx b/grafana-plugin/src/containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList.tsx index 9f15792c..8fccc235 100644 --- a/grafana-plugin/src/containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList.tsx +++ b/grafana-plugin/src/containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList.tsx @@ -9,7 +9,7 @@ import { MONACO_EDITABLE_CONFIG } from 'components/MonacoEditor/MonacoEditor.con import Text from 'components/Text/Text'; import TooltipBadge from 'components/TooltipBadge/TooltipBadge'; import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; -import { AlertTemplatesDTO } from 'models/alert_templates'; +import { AlertTemplatesDTO } from 'models/alert_templates/alert_templates'; import { Alert } from 'models/alertgroup/alertgroup.types'; import { OutgoingWebhook, OutgoingWebhookResponse } from 'models/outgoing_webhook/outgoing_webhook.types'; import { useStore } from 'state/useStore'; diff --git a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts index 0c08b1d3..87516749 100644 --- a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts +++ b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts @@ -1,7 +1,7 @@ import { omit } from 'lodash-es'; -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; -import { AlertTemplatesDTO } from 'models/alert_templates'; +import { AlertTemplatesDTO } from 'models/alert_templates/alert_templates'; import { Alert } from 'models/alertgroup/alertgroup.types'; import BaseStore from 'models/base_store'; import { ChannelFilter } from 'models/channel_filter/channel_filter.types'; @@ -9,7 +9,6 @@ import { GrafanaTeam } from 'models/grafana_team/grafana_team.types'; import { Heartbeat } from 'models/heartbeat/heartbeat.types'; import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types'; import { makeRequest } from 'network'; -import { Mixpanel } from 'services/mixpanel'; import { RootStore } from 'state'; import { move } from 'state/helpers'; import { SelectOption } from 'state/types'; @@ -64,6 +63,8 @@ export class AlertReceiveChannelStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/alert_receive_channels/'; } @@ -95,11 +96,13 @@ export class AlertReceiveChannelStore extends BaseStore { async loadItem(id: AlertReceiveChannel['id'], skipErrorHandling = false): Promise { const alertReceiveChannel = await this.getById(id, skipErrorHandling); - // @ts-ignore - this.items = { - ...this.items, - [id]: omit(alertReceiveChannel, 'heartbeat'), - }; + runInAction(() => { + // @ts-ignore + this.items = { + ...this.items, + [id]: omit(alertReceiveChannel, 'heartbeat'), + }; + }); this.populateHearbeats([alertReceiveChannel]); @@ -112,20 +115,24 @@ export class AlertReceiveChannelStore extends BaseStore { const { results } = await makeRequest(this.path, { params }); - this.items = { - ...this.items, - ...results.reduce( - (acc: { [key: number]: AlertReceiveChannel }, item: AlertReceiveChannel) => ({ - ...acc, - [item.id]: omit(item, 'heartbeat'), - }), - {} - ), - }; + runInAction(() => { + this.items = { + ...this.items, + ...results.reduce( + (acc: { [key: number]: AlertReceiveChannel }, item: AlertReceiveChannel) => ({ + ...acc, + [item.id]: omit(item, 'heartbeat'), + }), + {} + ), + }; + }); this.populateHearbeats(results); - this.searchResult = results.map((item: AlertReceiveChannel) => item.id); + runInAction(() => { + this.searchResult = results.map((item: AlertReceiveChannel) => item.id); + }); this.updateCounters(); @@ -149,24 +156,28 @@ export class AlertReceiveChannelStore extends BaseStore { return undefined; } - this.items = { - ...this.items, - ...results.reduce( - (acc: { [key: number]: AlertReceiveChannel }, item: AlertReceiveChannel) => ({ - ...acc, - [item.id]: omit(item, 'heartbeat'), - }), - {} - ), - }; + runInAction(() => { + this.items = { + ...this.items, + ...results.reduce( + (acc: { [key: number]: AlertReceiveChannel }, item: AlertReceiveChannel) => ({ + ...acc, + [item.id]: omit(item, 'heartbeat'), + }), + {} + ), + }; + }); this.populateHearbeats(results); - this.paginatedSearchResult = { - count, - results: results.map((item: AlertReceiveChannel) => item.id), - page_size, - }; + runInAction(() => { + this.paginatedSearchResult = { + count, + results: results.map((item: AlertReceiveChannel) => item.id), + page_size, + }; + }); if (updateCounters) { this.updateCounters(); @@ -184,10 +195,12 @@ export class AlertReceiveChannelStore extends BaseStore { return acc; }, {}); - this.rootStore.heartbeatStore.items = { - ...this.rootStore.heartbeatStore.items, - ...heartbeats, - }; + runInAction(() => { + this.rootStore.heartbeatStore.items = { + ...this.rootStore.heartbeatStore.items, + ...heartbeats, + }; + }); const alertReceiveChannelToHeartbeat = alertReceiveChannels.reduce( (acc: any, alertReceiveChannel: AlertReceiveChannel) => { @@ -200,10 +213,12 @@ export class AlertReceiveChannelStore extends BaseStore { {} ); - this.alertReceiveChannelToHeartbeat = { - ...this.alertReceiveChannelToHeartbeat, - ...alertReceiveChannelToHeartbeat, - }; + runInAction(() => { + this.alertReceiveChannelToHeartbeat = { + ...this.alertReceiveChannelToHeartbeat, + ...alertReceiveChannelToHeartbeat, + }; + }); } @action @@ -220,42 +235,48 @@ export class AlertReceiveChannelStore extends BaseStore { {} ); - this.channelFilters = { - ...this.channelFilters, - ...channelFilters, - }; - - if (isOverwrite) { - // This is needed because on Move Up/Down/Removal the store no longer reflects the correct state + runInAction(() => { this.channelFilters = { + ...this.channelFilters, ...channelFilters, }; + }); + + if (isOverwrite) { + runInAction(() => { + // This is needed because on Move Up/Down/Removal the store no longer reflects the correct state + this.channelFilters = { + ...channelFilters, + }; + }); } - this.channelFilterIds = { - ...this.channelFilterIds, - [alertReceiveChannelId]: response.map((channelFilter: ChannelFilter) => channelFilter.id), - }; + runInAction(() => { + this.channelFilterIds = { + ...this.channelFilterIds, + [alertReceiveChannelId]: response.map((channelFilter: ChannelFilter) => channelFilter.id), + }; + }); } @action async updateChannelFilter(channelFilterId: ChannelFilter['id']) { const response = await makeRequest(`/channel_filters/${channelFilterId}/`, {}); - this.channelFilters = { - ...this.channelFilters, - [channelFilterId]: response, - }; + runInAction(() => { + this.channelFilters = { + ...this.channelFilters, + [channelFilterId]: response, + }; + }); } - @action async migrateChannel(id: AlertReceiveChannel['id']) { return await makeRequest(`/alert_receive_channels/${id}/migrate`, { method: 'POST', }); } - @action async createChannelFilter(data: Partial) { return await makeRequest('/channel_filters/', { method: 'POST', @@ -270,10 +291,12 @@ export class AlertReceiveChannelStore extends BaseStore { data, }); - this.channelFilters = { - ...this.channelFilters, - [response.id]: response, - }; + runInAction(() => { + this.channelFilters = { + ...this.channelFilters, + [response.id]: response, + }; + }); return response; } @@ -284,8 +307,6 @@ export class AlertReceiveChannelStore extends BaseStore { oldIndex: number, newIndex: number ) { - Mixpanel.track('Move ChannelFilter', null); - const channelFilterId = this.channelFilterIds[alertReceiveChannelId][oldIndex]; this.channelFilterIds[alertReceiveChannelId] = move( @@ -301,8 +322,6 @@ export class AlertReceiveChannelStore extends BaseStore { @action async deleteChannelFilter(channelFilterId: ChannelFilter['id']) { - Mixpanel.track('Delete ChannelFilter', null); - const channelFilter = this.channelFilters[channelFilterId]; this.channelFilterIds[channelFilter.alert_receive_channel].splice( @@ -320,7 +339,10 @@ export class AlertReceiveChannelStore extends BaseStore { @action.bound async updateAlertReceiveChannelOptions() { const response = await makeRequest(`/alert_receive_channels/integration_options/`, {}); - this.alertReceiveChannelOptions = response; + + runInAction(() => { + this.alertReceiveChannelOptions = response; + }); } getIntegration(alertReceiveChannel: Partial): SelectOption { @@ -338,13 +360,14 @@ export class AlertReceiveChannelStore extends BaseStore { async saveAlertReceiveChannel(id: AlertReceiveChannel['id'], data: Partial) { const item = await this.update(id, data, undefined, true); - this.items = { - ...this.items, - [id]: item, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: item, + }; + }); } - @action async deleteAlertReceiveChannel(id: AlertReceiveChannel['id']) { return await this.delete(id); } @@ -356,20 +379,24 @@ export class AlertReceiveChannelStore extends BaseStore { withCredentials: true, }); - this.templates = { - ...this.templates, - [alertReceiveChannelId]: response, - }; + runInAction(() => { + this.templates = { + ...this.templates, + [alertReceiveChannelId]: response, + }; + }); } @action async updateItem(id: AlertReceiveChannel['id']) { const item = await this.getById(id); - this.items = { - ...this.items, - [id]: item, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: item, + }; + }); } @action @@ -380,10 +407,12 @@ export class AlertReceiveChannelStore extends BaseStore { withCredentials: true, }); - this.templates = { - ...this.templates, - [alertReceiveChannelId]: response, - }; + runInAction(() => { + this.templates = { + ...this.templates, + [alertReceiveChannelId]: response, + }; + }); } async getGrafanaAlertingContactPoints() { @@ -394,22 +423,24 @@ export class AlertReceiveChannelStore extends BaseStore { async updateConnectedContactPoints(alertReceiveChannelId: AlertReceiveChannel['id']) { const response = await makeRequest(`${this.path}${alertReceiveChannelId}/connected_contact_points `, {}); - this.connectedContactPoints = { - ...this.connectedContactPoints, + runInAction(() => { + this.connectedContactPoints = { + ...this.connectedContactPoints, - [alertReceiveChannelId]: response.reduce((list: ContactPoint[], payload) => { - payload.contact_points.forEach((contactPoint: { name: string; notification_connected: boolean }) => { - list.push({ - dataSourceName: payload.name, - dataSourceId: payload.uid, - contactPoint: contactPoint.name, - notificationConnected: contactPoint.notification_connected, - } as ContactPoint); - }); + [alertReceiveChannelId]: response.reduce((list: ContactPoint[], payload) => { + payload.contact_points.forEach((contactPoint: { name: string; notification_connected: boolean }) => { + list.push({ + dataSourceName: payload.name, + dataSourceId: payload.uid, + contactPoint: contactPoint.name, + notificationConnected: contactPoint.notification_connected, + } as ContactPoint); + }); - return list; - }, []), - }; + return list; + }, []), + }; + }); } async connectContactPoint( @@ -460,13 +491,6 @@ export class AlertReceiveChannelStore extends BaseStore { return integration_log; } - async installSentry(sentry_payload: string) { - return await makeRequest('/sentry_complete_install/', { - method: 'POST', - params: { sentry_payload }, - }); - } - async sendDemoAlert(id: AlertReceiveChannel['id'], payload: string = undefined) { const requestConfig: any = { method: 'POST', @@ -479,8 +503,6 @@ export class AlertReceiveChannelStore extends BaseStore { } await makeRequest(`${this.path}${id}/send_demo_alert/`, requestConfig).catch(showApiError); - - Mixpanel.track('Send Demo Incident', null); } async sendDemoAlertToParticularRoute(id: ChannelFilter['id']) { @@ -508,25 +530,31 @@ export class AlertReceiveChannelStore extends BaseStore { }); } + @action async updateCounters() { const counters = await makeRequest(`${this.path}counters`, { method: 'GET', }); - this.counters = counters; + runInAction(() => { + this.counters = counters; + }); } + @action async updateCountersForIntegration(id: AlertReceiveChannel['id']): Promise { const counters = await makeRequest(`${this.path}${id}/counters`, { method: 'GET', }); - this.counters = { - ...this.counters, - [id]: { - ...counters[id], - }, - }; + runInAction(() => { + this.counters = { + ...this.counters, + [id]: { + ...counters[id], + }, + }; + }); return counters; } diff --git a/grafana-plugin/src/models/alert_receive_channel_filters/alert_receive_channel_filters.ts b/grafana-plugin/src/models/alert_receive_channel_filters/alert_receive_channel_filters.ts index 39f4cd78..98ae190c 100644 --- a/grafana-plugin/src/models/alert_receive_channel_filters/alert_receive_channel_filters.ts +++ b/grafana-plugin/src/models/alert_receive_channel_filters/alert_receive_channel_filters.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { makeRequest } from 'network'; @@ -15,6 +15,8 @@ export class AlertReceiveChannelFiltersStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/alert_receive_channels/'; } @@ -32,17 +34,19 @@ export class AlertReceiveChannelFiltersStore extends BaseStore { params: { search: query, filters: true }, }); - this.items = { - ...this.items, - ...results.reduce( - (acc: { [key: string]: SelectOption }, item: SelectOption) => ({ - ...acc, - [item.value]: item, - }), - {} - ), - }; + runInAction(() => { + this.items = { + ...this.items, + ...results.reduce( + (acc: { [key: string]: SelectOption }, item: SelectOption) => ({ + ...acc, + [item.value]: item, + }), + {} + ), + }; - this.searchResult = results.map((item: SelectOption) => item.value); + this.searchResult = results.map((item: SelectOption) => item.value); + }); } } diff --git a/grafana-plugin/src/models/alert_templates.ts b/grafana-plugin/src/models/alert_templates/alert_templates.ts similarity index 100% rename from grafana-plugin/src/models/alert_templates.ts rename to grafana-plugin/src/models/alert_templates/alert_templates.ts diff --git a/grafana-plugin/src/models/alertgroup/alertgroup.ts b/grafana-plugin/src/models/alertgroup/alertgroup.ts index 93c29049..073ad5f2 100644 --- a/grafana-plugin/src/models/alertgroup/alertgroup.ts +++ b/grafana-plugin/src/models/alertgroup/alertgroup.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import qs from 'query-string'; import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; @@ -7,7 +7,6 @@ import { ActionKey } from 'models/loader/action-keys'; import { User } from 'models/user/user.types'; import { makeRequest } from 'network'; import { ApiSchemas } from 'network/oncall-api/api.types'; -import { Mixpanel } from 'services/mixpanel'; import { RootStore } from 'state'; import { SelectOption } from 'state/types'; import { openErrorNotification, refreshPageError, showApiError } from 'utils'; @@ -80,6 +79,8 @@ export class AlertGroupStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/alertgroups/'; } @@ -96,13 +97,16 @@ export class AlertGroupStore extends BaseStore { }).catch(showApiError); } + @action async updateItem(id: Alert['pk']) { const item = await this.getById(id); - this.items = { - ...this.items, - [item.id]: item, - }; + runInAction(() => { + this.items = { + ...this.items, + [item.id]: item, + }; + }); } getSearchResult(query = '') { @@ -124,12 +128,14 @@ export class AlertGroupStore extends BaseStore { return await makeRequest(`${this.path}${pk}`, {}); } - @action async updateSilenceOptions() { - this.silenceOptions = await makeRequest(`${this.path}silence_options/`, {}); + const result = await makeRequest(`${this.path}silence_options/`, {}); + + runInAction(() => { + this.silenceOptions = result; + }); } - @action async resolve(id: Alert['pk'], delay: number) { await makeRequest(`${this.path}${id}/silence/`, { method: 'POST', @@ -137,28 +143,24 @@ export class AlertGroupStore extends BaseStore { }); } - @action async unresolve(id: Alert['pk']) { await makeRequest(`${this.path}${id}/unresolve/`, { method: 'POST', }); } - @action async acknowledge(id: Alert['pk']) { await makeRequest(`${this.path}${id}/acknowledge/`, { method: 'POST', }); } - @action async unacknowledge(id: Alert['pk']) { await makeRequest(`${this.path}${id}/unacknowledge/`, { method: 'POST', }); } - @action async silence(id: Alert['pk'], delay: number) { await makeRequest(`${this.path}${id}/silence/`, { method: 'POST', @@ -166,7 +168,6 @@ export class AlertGroupStore extends BaseStore { }); } - @action async unsilence(id: Alert['pk']) { await makeRequest(`${this.path}${id}/unsilence/`, { method: 'POST', @@ -177,13 +178,15 @@ export class AlertGroupStore extends BaseStore { async updateBulkActions() { const response = await makeRequest(`${this.path}bulk_action_options/`, {}); - this.bulkActions = response.reduce( - (acc: { [key: string]: boolean }, item: SelectOption) => ({ - ...acc, - [item.value]: true, - }), - {} - ); + runInAction(() => { + this.bulkActions = response.reduce( + (acc: { [key: string]: boolean }, item: SelectOption) => ({ + ...acc, + [item.value]: true, + }), + {} + ); + }); } async bulkAction(data: any) { @@ -202,7 +205,6 @@ export class AlertGroupStore extends BaseStore { // methods were moved from rootBaseStore. // TODO check if methods are dublicating existing ones - @action async updateIncidents() { await Promise.all([ this.getNewIncidentsStats(), @@ -212,7 +214,12 @@ export class AlertGroupStore extends BaseStore { this.updateAlertGroups(), ]); - this.liveUpdatesPaused = false; + this.setLiveUpdatesPaused(false); + } + + @action + setLiveUpdatesPaused(value: boolean) { + this.liveUpdatesPaused = value; } @action @@ -276,17 +283,19 @@ export class AlertGroupStore extends BaseStore { }) ); - // @ts-ignore - this.alerts = new Map([...this.alerts, ...newAlerts]); + runInAction(() => { + // @ts-ignore + this.alerts = new Map([...this.alerts, ...newAlerts]); - this.alertsSearchResult['default'] = { - prev: prevCursor, - next: nextCursor, - results: results.map((alert: Alert) => alert.pk), - page_size, - }; + this.alertsSearchResult['default'] = { + prev: prevCursor, + next: nextCursor, + results: results.map((alert: Alert) => alert.pk), + page_size, + }; - this.alertGroupsLoading = false; + this.alertGroupsLoading = false; + }); } getAlertSearchResult(query: string) { @@ -303,10 +312,11 @@ export class AlertGroupStore extends BaseStore { }; } - @action async getAlert(pk: Alert['pk']) { return await makeRequest(`${this.path}${pk}`, {}).then((alert: Alert) => { - this.alerts.set(pk, alert); + runInAction(() => { + this.alerts.set(pk, alert); + }); return alert; }); @@ -324,7 +334,10 @@ export class AlertGroupStore extends BaseStore { status: [IncidentStatus.Firing], }, }); - this.newIncidents = result; + + runInAction(() => { + this.newIncidents = result; + }); } @action @@ -336,7 +349,9 @@ export class AlertGroupStore extends BaseStore { }, }); - this.acknowledgedIncidents = result; + runInAction(() => { + this.acknowledgedIncidents = result; + }); } @action @@ -348,7 +363,9 @@ export class AlertGroupStore extends BaseStore { }, }); - this.resolvedIncidents = result; + runInAction(() => { + this.resolvedIncidents = result; + }); } @action @@ -360,7 +377,9 @@ export class AlertGroupStore extends BaseStore { }, }); - this.silencedIncidents = result; + runInAction(() => { + this.silencedIncidents = result; + }); } @action @@ -371,32 +390,26 @@ export class AlertGroupStore extends BaseStore { if (!isUndo) { switch (action) { case AlertAction.Acknowledge: - Mixpanel.track('Acknowledge Incident', null); undoAction = AlertAction.unAcknowledge; break; case AlertAction.unAcknowledge: - Mixpanel.track('Unacknowledge Incident', null); undoAction = AlertAction.Acknowledge; break; case AlertAction.Resolve: - Mixpanel.track('Resolve Incident', null); undoAction = AlertAction.unResolve; break; case AlertAction.unResolve: - Mixpanel.track('Unresolve Incident', null); undoAction = AlertAction.Resolve; break; case AlertAction.Silence: - Mixpanel.track('Silence Incident', null); undoAction = AlertAction.unSilence; break; case AlertAction.unSilence: - Mixpanel.track('Unsilence Incident', null); undoAction = AlertAction.Silence; break; } - this.liveUpdatesPaused = true; + this.setLiveUpdatesPaused(true); } try { @@ -424,11 +437,6 @@ export class AlertGroupStore extends BaseStore { }); } - @action - toggleLiveUpdate(value: boolean) { - this.liveUpdatesEnabled = value; - } - async unpageUser(alertId: Alert['pk'], userId: User['pk']) { return await makeRequest(`${this.path}${alertId}/unpage_user`, { method: 'POST', @@ -442,11 +450,13 @@ export class AlertGroupStore extends BaseStore { const { hidden, visible, default: isDefaultOrder } = tableSettings; - this.isDefaultColumnOrder = isDefaultOrder; - this.columns = [ - ...visible.map((item: AlertGroupColumn): AlertGroupColumn => ({ ...item, isVisible: true })), - ...hidden.map((item: AlertGroupColumn): AlertGroupColumn => ({ ...item, isVisible: false })), - ]; + runInAction(() => { + this.isDefaultColumnOrder = isDefaultOrder; + this.columns = [ + ...visible.map((item: AlertGroupColumn): AlertGroupColumn => ({ ...item, isVisible: true })), + ...hidden.map((item: AlertGroupColumn): AlertGroupColumn => ({ ...item, isVisible: false })), + ]; + }); } @action @@ -462,24 +472,23 @@ export class AlertGroupStore extends BaseStore { data: { ...columns }, }); - this.isDefaultColumnOrder = isDefaultOrder; + runInAction(() => { + this.isDefaultColumnOrder = isDefaultOrder; + }); } - @action async resetTableSettings(): Promise { return await makeRequest('/alertgroup_table_settings/reset', { method: 'POST' }).catch(() => openErrorNotification('There was an error resetting the table settings') ); } - @action async loadLabelsKeys(): Promise> { return await makeRequest(`/alertgroups/labels/keys/`, {}).catch(() => openErrorNotification('There was an error processing your request') ); } - @action async loadValuesForLabelKey( key: ApiSchemas['LabelKey']['id'], search = '' diff --git a/grafana-plugin/src/models/alertgroup/alertgroup.types.ts b/grafana-plugin/src/models/alertgroup/alertgroup.types.ts index 72374249..8a3b4122 100644 --- a/grafana-plugin/src/models/alertgroup/alertgroup.types.ts +++ b/grafana-plugin/src/models/alertgroup/alertgroup.types.ts @@ -1,5 +1,5 @@ import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; -import { Channel } from 'models/channel'; +import { Channel } from 'models/channel/channel'; import { GrafanaTeam } from 'models/grafana_team/grafana_team.types'; import { LabelKeyValue } from 'models/label/label.types'; import { PagedUser, User } from 'models/user/user.types'; diff --git a/grafana-plugin/src/models/api_key.ts b/grafana-plugin/src/models/api_key.ts deleted file mode 100644 index 51c241b2..00000000 --- a/grafana-plugin/src/models/api_key.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ApiTokenDTO { - pk: string; - token_name: string; - created_at: string; -} diff --git a/grafana-plugin/src/models/api_token/api_token.ts b/grafana-plugin/src/models/api_token/api_token.ts index e607453c..51f3e8f2 100644 --- a/grafana-plugin/src/models/api_token/api_token.ts +++ b/grafana-plugin/src/models/api_token/api_token.ts @@ -1,8 +1,7 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { makeRequest } from 'network'; -import { Mixpanel } from 'services/mixpanel'; import { RootStore } from 'state'; import { ApiToken } from './api_token.types'; @@ -17,6 +16,8 @@ export class ApiTokenStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/tokens/'; } @@ -26,21 +27,23 @@ export class ApiTokenStore extends BaseStore { params: { search: query }, }); - this.items = { - ...this.items, - ...results.reduce( - (acc: { [key: number]: ApiToken }, item: ApiToken) => ({ - ...acc, - [item.id]: item, - }), - {} - ), - }; + runInAction(() => { + this.items = { + ...this.items, + ...results.reduce( + (acc: { [key: number]: ApiToken }, item: ApiToken) => ({ + ...acc, + [item.id]: item, + }), + {} + ), + }; - this.searchResult = { - ...this.searchResult, - [query]: results.map((item: ApiToken) => item.id), - }; + this.searchResult = { + ...this.searchResult, + [query]: results.map((item: ApiToken) => item.id), + }; + }); } getSearchResult(query = '') { @@ -52,8 +55,6 @@ export class ApiTokenStore extends BaseStore { } async revokeApiToken(id: ApiToken['id']) { - Mixpanel.track('Revoke ApiToken', null); - return await makeRequest(`${this.path}${id}/`, { method: 'DELETE', }); diff --git a/grafana-plugin/src/models/channel.ts b/grafana-plugin/src/models/channel/channel.ts similarity index 100% rename from grafana-plugin/src/models/channel.ts rename to grafana-plugin/src/models/channel/channel.ts diff --git a/grafana-plugin/src/models/channel_filter.ts b/grafana-plugin/src/models/channel_filter.ts deleted file mode 100644 index 34ac528f..00000000 --- a/grafana-plugin/src/models/channel_filter.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; -import { SlackChannel } from 'models/slack_channel/slack_channel.types'; -import { TelegramChannel } from 'models/telegram_channel/telegram_channel.types'; - -export interface ChannelFilter { - id: string; - alert_receive_channel: AlertReceiveChannel['id']; - slack_channel_id?: SlackChannel['id']; - telegram_channel?: TelegramChannel['id']; - escalation_chain?: string; - created_at: string; - filtering_term: string; - is_default: boolean; -} diff --git a/grafana-plugin/src/models/cloud/cloud.ts b/grafana-plugin/src/models/cloud/cloud.ts index 5c8f5e99..7b4814a8 100644 --- a/grafana-plugin/src/models/cloud/cloud.ts +++ b/grafana-plugin/src/models/cloud/cloud.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { makeRequest } from 'network'; @@ -19,6 +19,8 @@ export class CloudStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/cloud_users/'; } @@ -28,21 +30,23 @@ export class CloudStore extends BaseStore { params: { page }, }); - this.items = { - ...this.items, - ...results.reduce( - (acc: { [key: number]: Cloud }, item: Cloud) => ({ - ...acc, - [item.id]: item, - }), - {} - ), - }; + runInAction(() => { + this.items = { + ...this.items, + ...results.reduce( + (acc: { [key: number]: Cloud }, item: Cloud) => ({ + ...acc, + [item.id]: item, + }), + {} + ), + }; - this.searchResult = { - matched_users_count, - results: results.map((item: Cloud) => item.id), - }; + this.searchResult = { + matched_users_count, + results: results.map((item: Cloud) => item.id), + }; + }); } getSearchResult() { @@ -70,14 +74,17 @@ export class CloudStore extends BaseStore { @action.bound async loadCloudConnectionStatus() { - this.cloudConnectionStatus = await this.getCloudConnectionStatus(); + const result = await this.getCloudConnectionStatus(); + + runInAction(() => { + this.cloudConnectionStatus = result; + }); } async getCloudConnectionStatus() { return await makeRequest(`/cloud_connection/`, { method: 'GET' }); } - @action async disconnectToCloud() { return await makeRequest(`/cloud_connection/`, { method: 'DELETE' }); } diff --git a/grafana-plugin/src/models/direct_paging/direct_paging.ts b/grafana-plugin/src/models/direct_paging/direct_paging.ts index 19057689..b722b208 100644 --- a/grafana-plugin/src/models/direct_paging/direct_paging.ts +++ b/grafana-plugin/src/models/direct_paging/direct_paging.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable } from 'mobx'; import { UserResponders } from 'containers/AddResponders/AddResponders.types'; import { Alert } from 'models/alertgroup/alertgroup.types'; @@ -24,6 +24,8 @@ export class DirectPagingStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/direct_paging/'; } diff --git a/grafana-plugin/src/models/escalation_chain/escalation_chain.ts b/grafana-plugin/src/models/escalation_chain/escalation_chain.ts index 443aedd8..a1f0db81 100644 --- a/grafana-plugin/src/models/escalation_chain/escalation_chain.ts +++ b/grafana-plugin/src/models/escalation_chain/escalation_chain.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { makeRequest } from 'network'; @@ -25,6 +25,8 @@ export class EscalationChainStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/escalation_chains/'; } @@ -32,10 +34,12 @@ export class EscalationChainStore extends BaseStore { async loadItem(id: EscalationChain['id'], skipErrorHandling = false): Promise { const escalationChain = await this.getById(id, skipErrorHandling); - this.items = { - ...this.items, - [id]: escalationChain, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: escalationChain, + }; + }); return escalationChain; } @@ -44,30 +48,36 @@ export class EscalationChainStore extends BaseStore { async updateById(id: EscalationChain['id']) { const response = await this.getById(id); - this.items = { - ...this.items, - [id]: response, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: response, + }; + }); } @action async save(id: EscalationChain['id'], data: Partial) { const response = await super.update(id, data); - this.items = { - ...this.items, - [id]: response, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: response, + }; + }); } @action async updateEscalationChainDetails(id: EscalationChain['id']) { const response = await makeRequest(`${this.path}${id}/details/`, {}); - this.details = { - ...this.details, - [id]: response, - }; + runInAction(() => { + this.details = { + ...this.details, + [id]: response, + }; + }); } @action @@ -86,10 +96,12 @@ export class EscalationChainStore extends BaseStore { } if (escalationChain) { - this.items = { - ...this.items, - [id]: escalationChain, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: escalationChain, + }; + }); } return escalationChain; @@ -105,23 +117,25 @@ export class EscalationChainStore extends BaseStore { params, }); - this.items = { - ...this.items, - ...results.reduce( - (acc: { [key: number]: EscalationChain }, item: EscalationChain) => ({ - ...acc, - [item.id]: item, - }), - {} - ), - }; + runInAction(() => { + this.items = { + ...this.items, + ...results.reduce( + (acc: { [key: number]: EscalationChain }, item: EscalationChain) => ({ + ...acc, + [item.id]: item, + }), + {} + ), + }; - const key = typeof query === 'string' ? query : ''; + const key = typeof query === 'string' ? query : ''; - this.searchResult = { - ...this.searchResult, - [key]: results.map((item: EscalationChain) => item.id), - }; + this.searchResult = { + ...this.searchResult, + [key]: results.map((item: EscalationChain) => item.id), + }; + }); this.loading = false; } diff --git a/grafana-plugin/src/models/escalation_policy.ts b/grafana-plugin/src/models/escalation_policy.ts deleted file mode 100644 index 9df6ecc7..00000000 --- a/grafana-plugin/src/models/escalation_policy.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Channel } from 'models/channel'; -import { Schedule } from 'models/schedule/schedule.types'; -import { UserGroup } from 'models/user_group/user_group.types'; - -import { ChannelFilter } from './channel_filter'; -import { ScheduleDTO } from './schedule'; -import { User } from './user/user.types'; - -export interface EscalationPolicyType { - id: string; - notify_to_user: User['pk'] | null; - // it's option value from api/internal/v1/escalation_policies/escalation_options/ - step: number; - wait_delay: string | null; - is_final: boolean; - channel_filter: ChannelFilter['id']; - notify_to_users_queue: Array; - from_time: string | null; - to_time: string | null; - notify_to_schedule: ScheduleDTO['id'] | null; - notify_to_channel: Channel['id'] | null; - notify_to_group: UserGroup['id']; - notify_schedule: Schedule['id']; -} - -export function prepareEscalationPolicy(value: EscalationPolicyType): EscalationPolicyType { - return { - ...value, - notify_to_user: null, - wait_delay: null, - notify_to_users_queue: [], - from_time: null, - to_time: null, - notify_to_schedule: null, - }; -} diff --git a/grafana-plugin/src/models/escalation_policy/escalation_policy.ts b/grafana-plugin/src/models/escalation_policy/escalation_policy.ts index 3872f8af..8e15f756 100644 --- a/grafana-plugin/src/models/escalation_policy/escalation_policy.ts +++ b/grafana-plugin/src/models/escalation_policy/escalation_policy.ts @@ -1,11 +1,10 @@ import { get } from 'lodash-es'; -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { EscalationChain } from 'models/escalation_chain/escalation_chain.types'; import { EscalationPolicy } from 'models/escalation_policy/escalation_policy.types'; import { makeRequest } from 'network'; -import { Mixpanel } from 'services/mixpanel'; import { RootStore } from 'state'; import { move } from 'state/helpers'; import { SelectOption } from 'state/types'; @@ -31,13 +30,18 @@ export class EscalationPolicyStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/escalation_policies/'; } @action.bound async updateWebEscalationPolicyOptions() { const response = await makeRequest('/escalation_policies/escalation_options/', {}); - this.webEscalationChoices = response; + + runInAction(() => { + this.webEscalationChoices = response; + }); } @action.bound @@ -45,13 +49,19 @@ export class EscalationPolicyStore extends BaseStore { const response = await makeRequest('/escalation_policies/', { method: 'OPTIONS', }); - this.escalationChoices = get(response, 'actions.POST', []); + + runInAction(() => { + this.escalationChoices = get(response, 'actions.POST', []); + }); } @action.bound async updateNumMinutesInWindowOptions() { const response = await makeRequest('/escalation_policies/num_minutes_in_window_options/', {}); - this.numMinutesInWindowOptions = response; + + runInAction(() => { + this.numMinutesInWindowOptions = response; + }); } @action @@ -68,15 +78,17 @@ export class EscalationPolicyStore extends BaseStore { {} ); - this.items = { - ...this.items, - ...escalationPolicies, - }; + runInAction(() => { + this.items = { + ...this.items, + ...escalationPolicies, + }; - this.escalationChainToEscalationPolicy = { - ...this.escalationChainToEscalationPolicy, - [escalationChainId]: response.map((escalationPolicy: EscalationPolicy) => escalationPolicy.id), - }; + this.escalationChainToEscalationPolicy = { + ...this.escalationChainToEscalationPolicy, + [escalationChainId]: response.map((escalationPolicy: EscalationPolicy) => escalationPolicy.id), + }; + }); } @action @@ -103,8 +115,6 @@ export class EscalationPolicyStore extends BaseStore { @action async moveEscalationPolicyToPosition(oldIndex: any, newIndex: any, escalationChainId: EscalationChain['id']) { - Mixpanel.track('Move EscalationPolicy', null); - const escalationPolicyId = this.escalationChainToEscalationPolicy[escalationChainId][oldIndex]; this.escalationChainToEscalationPolicy[escalationChainId] = move( diff --git a/grafana-plugin/src/models/escalation_policy/escalation_policy.types.ts b/grafana-plugin/src/models/escalation_policy/escalation_policy.types.ts index a4918d0f..93c8a9d0 100644 --- a/grafana-plugin/src/models/escalation_policy/escalation_policy.types.ts +++ b/grafana-plugin/src/models/escalation_policy/escalation_policy.types.ts @@ -1,4 +1,4 @@ -import { Channel } from 'models/channel'; +import { Channel } from 'models/channel/channel'; import { EscalationChain } from 'models/escalation_chain/escalation_chain.types'; import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types'; import { Schedule } from 'models/schedule/schedule.types'; diff --git a/grafana-plugin/src/models/filters/filters.ts b/grafana-plugin/src/models/filters/filters.ts index 16f76283..081c0b41 100644 --- a/grafana-plugin/src/models/filters/filters.ts +++ b/grafana-plugin/src/models/filters/filters.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { LabelKeyValue } from 'models/label/label.types'; @@ -31,6 +31,8 @@ export class FiltersStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + const savedFilters = getItem(LOCAL_STORAGE_FILTERS_KEY); if (savedFilters) { this._globalValues = { ...savedFilters }; @@ -61,10 +63,12 @@ export class FiltersStore extends BaseStore { result.unshift({ name: 'search', type: 'search' }); } - this.options = { - ...this.options, - [page]: result, - }; + runInAction(() => { + this.options = { + ...this.options, + [page]: result, + }; + }); return result; } diff --git a/grafana-plugin/src/models/global_setting/global_setting.ts b/grafana-plugin/src/models/global_setting/global_setting.ts index f1f6d3f6..a7b9d1d7 100644 --- a/grafana-plugin/src/models/global_setting/global_setting.ts +++ b/grafana-plugin/src/models/global_setting/global_setting.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { RootStore } from 'state'; @@ -15,6 +15,8 @@ export class GlobalSettingStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/live_settings/'; } @@ -22,31 +24,35 @@ export class GlobalSettingStore extends BaseStore { async updateById(id: GlobalSetting['id']) { const response = await this.getById(id); - this.items = { - ...this.items, - [id]: response, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: response, + }; + }); } @action async updateItems(query = '') { const results = await this.getAll(); - this.items = { - ...this.items, - ...results.reduce( - (acc: { [key: number]: GlobalSetting }, item: GlobalSetting) => ({ - ...acc, - [item.id]: item, - }), - {} - ), - }; + runInAction(() => { + this.items = { + ...this.items, + ...results.reduce( + (acc: { [key: number]: GlobalSetting }, item: GlobalSetting) => ({ + ...acc, + [item.id]: item, + }), + {} + ), + }; - this.searchResult = { - ...this.searchResult, - [query]: results.map((item: GlobalSetting) => item.id), - }; + this.searchResult = { + ...this.searchResult, + [query]: results.map((item: GlobalSetting) => item.id), + }; + }); } getSearchResult(query = '') { diff --git a/grafana-plugin/src/models/grafana_team/grafana_team.ts b/grafana-plugin/src/models/grafana_team/grafana_team.ts index 04dd55a8..d5d6e2fa 100644 --- a/grafana-plugin/src/models/grafana_team/grafana_team.ts +++ b/grafana-plugin/src/models/grafana_team/grafana_team.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { GrafanaTeam } from 'models/grafana_team/grafana_team.types'; @@ -17,6 +17,8 @@ export class GrafanaTeamStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/teams/'; } @@ -24,10 +26,12 @@ export class GrafanaTeamStore extends BaseStore { async updateTeam(id: GrafanaTeam['id'], data: Partial) { const result = await this.update(id, data); - this.items = { - ...this.items, - [id]: result, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: result, + }; + }); } @action.bound @@ -40,18 +44,21 @@ export class GrafanaTeamStore extends BaseStore { only_include_notifiable_teams: onlyIncludeNotifiableTeams ? 'true' : 'false', }, }); - this.items = { - ...this.items, - ...result.reduce( - (acc, item) => ({ - ...acc, - [item.id]: item, - }), - {} - ), - }; - this.searchResult = result.map((item: GrafanaTeam) => item.id); + runInAction(() => { + this.items = { + ...this.items, + ...result.reduce( + (acc, item) => ({ + ...acc, + [item.id]: item, + }), + {} + ), + }; + + this.searchResult = result.map((item: GrafanaTeam) => item.id); + }); } getSearchResult() { diff --git a/grafana-plugin/src/models/heartbeat/heartbeat.ts b/grafana-plugin/src/models/heartbeat/heartbeat.ts index 04d5f117..fe046d7e 100644 --- a/grafana-plugin/src/models/heartbeat/heartbeat.ts +++ b/grafana-plugin/src/models/heartbeat/heartbeat.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; import BaseStore from 'models/base_store'; @@ -17,12 +17,18 @@ export class HeartbeatStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/heartbeats/'; } @action async updateTimeoutOptions() { - this.timeoutOptions = await makeRequest(`${this.path}timeout_options/`, {}); + const result = await makeRequest(`${this.path}timeout_options/`, {}); + + runInAction(() => { + this.timeoutOptions = result; + }); } @action @@ -33,10 +39,12 @@ export class HeartbeatStore extends BaseStore { return; } - this.items = { - ...this.items, - [response.id]: response, - }; + runInAction(() => { + this.items = { + ...this.items, + [response.id]: response, + }; + }); } @action @@ -50,14 +58,16 @@ export class HeartbeatStore extends BaseStore { return; } - this.rootStore.alertReceiveChannelStore.alertReceiveChannelToHeartbeat = { - ...this.rootStore.alertReceiveChannelStore.alertReceiveChannelToHeartbeat, - [alertReceiveChannelId]: response.id, - }; + runInAction(() => { + this.rootStore.alertReceiveChannelStore.alertReceiveChannelToHeartbeat = { + ...this.rootStore.alertReceiveChannelStore.alertReceiveChannelToHeartbeat, + [alertReceiveChannelId]: response.id, + }; - this.items = { - ...this.items, - [response.id]: response, - }; + this.items = { + ...this.items, + [response.id]: response, + }; + }); } } diff --git a/grafana-plugin/src/models/label/label.ts b/grafana-plugin/src/models/label/label.ts index 1138550e..fc41e4cc 100644 --- a/grafana-plugin/src/models/label/label.ts +++ b/grafana-plugin/src/models/label/label.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { makeRequest } from 'network'; @@ -17,13 +17,18 @@ export class LabelStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/labels/'; } @action.bound public async loadKeys() { const { data } = await onCallApi.GET('/labels/keys/', undefined); - this.keys = data; + + runInAction(() => { + this.keys = data; + }); return data; } @@ -40,15 +45,16 @@ export class LabelStore extends BaseStore { const filteredValues = result.values.filter((v) => v.name.toLowerCase().includes(search.toLowerCase())); // TODO remove after backend search implementation - this.values = { - ...this.values, - [key]: filteredValues, - }; + runInAction(() => { + this.values = { + ...this.values, + [key]: filteredValues, + }; + }); return { ...result, values: filteredValues }; } - @action.bound @WithGlobalNotification({ success: 'New key has been added', failure: 'Failed to add new key' }) async createKey(name: string) { const data = await makeRequest(`${this.path}`, { @@ -58,7 +64,6 @@ export class LabelStore extends BaseStore { return data.key; } - @action.bound @WithGlobalNotification({ success: 'New value has been added', failure: 'Failed to add new value' }) async createValue(keyId: ApiSchemas['LabelKey']['id'], value: string) { const result = await makeRequest(`${this.path}id/${keyId}/values`, { @@ -68,7 +73,6 @@ export class LabelStore extends BaseStore { return result.values.find((v) => v.name === value); // TODO remove after backend API change } - @action.bound @WithGlobalNotification({ success: 'Key has been renamed', failure: 'Failed to rename key' }) async updateKey(keyId: ApiSchemas['LabelKey']['id'], name: string) { const result = await makeRequest(`${this.path}id/${keyId}`, { @@ -78,7 +82,6 @@ export class LabelStore extends BaseStore { return result.key; } - @action.bound @WithGlobalNotification({ success: 'Value has been renamed', failure: 'Failed to rename value' }) async updateKeyValue(keyId: ApiSchemas['LabelKey']['id'], valueId: ApiSchemas['LabelValue']['id'], name: string) { const result = await makeRequest(`${this.path}id/${keyId}/values/${valueId}`, { diff --git a/grafana-plugin/src/models/loader/loader.ts b/grafana-plugin/src/models/loader/loader.ts index 4606bb45..0338f435 100644 --- a/grafana-plugin/src/models/loader/loader.ts +++ b/grafana-plugin/src/models/loader/loader.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable } from 'mobx'; interface LoadingResult { [key: string]: boolean; @@ -8,6 +8,10 @@ class LoaderStoreClass { @observable items: LoadingResult = {}; + constructor() { + makeObservable(this); + } + @action setLoadingAction(actionKey: string, isLoading: boolean) { this.items[actionKey] = isLoading; diff --git a/grafana-plugin/src/models/notification_policy.ts b/grafana-plugin/src/models/notification_policy/notification_policy.ts similarity index 88% rename from grafana-plugin/src/models/notification_policy.ts rename to grafana-plugin/src/models/notification_policy/notification_policy.ts index 79f7b6b4..802d2d22 100644 --- a/grafana-plugin/src/models/notification_policy.ts +++ b/grafana-plugin/src/models/notification_policy/notification_policy.ts @@ -1,4 +1,4 @@ -import { User } from './user/user.types'; +import { User } from 'models/user/user.types'; export interface NotificationPolicyType { id: string; diff --git a/grafana-plugin/src/models/notify_by.ts b/grafana-plugin/src/models/notify_by.ts deleted file mode 100644 index dbaa88f7..00000000 --- a/grafana-plugin/src/models/notify_by.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface NotifyBy { - value: string; - display_name: string; -} diff --git a/grafana-plugin/src/models/organization/organization.ts b/grafana-plugin/src/models/organization/organization.ts index 024cf392..b7aad03d 100644 --- a/grafana-plugin/src/models/organization/organization.ts +++ b/grafana-plugin/src/models/organization/organization.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { makeRequest } from 'network'; @@ -12,16 +12,19 @@ export class OrganizationStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); this.path = '/organization/'; } @action.bound async loadCurrentOrganization() { const organization = await makeRequest(this.path, {}); - this.currentOrganization = organization; + + runInAction(() => { + this.currentOrganization = organization; + }); } - @action async saveCurrentOrganization(data: Partial) { this.currentOrganization = await makeRequest(this.path, { method: 'PUT', diff --git a/grafana-plugin/src/models/outgoing_webhook/outgoing_webhook.ts b/grafana-plugin/src/models/outgoing_webhook/outgoing_webhook.ts index 770c2d5d..7251fcfd 100644 --- a/grafana-plugin/src/models/outgoing_webhook/outgoing_webhook.ts +++ b/grafana-plugin/src/models/outgoing_webhook/outgoing_webhook.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { LabelsErrors } from 'models/label/label.types'; @@ -23,6 +23,8 @@ export class OutgoingWebhookStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/webhooks/'; } @@ -30,10 +32,12 @@ export class OutgoingWebhookStore extends BaseStore { async loadItem(id: OutgoingWebhook['id'], skipErrorHandling = false): Promise { const outgoingWebhook = await this.getById(id, skipErrorHandling); - this.items = { - ...this.items, - [id]: outgoingWebhook, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: outgoingWebhook, + }; + }); return outgoingWebhook; } @@ -42,19 +46,24 @@ export class OutgoingWebhookStore extends BaseStore { async updateById(id: OutgoingWebhook['id']) { const response = await this.getById(id); - this.items = { - ...this.items, - [id]: response, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: response, + }; + }); } @action async updateItem(id: OutgoingWebhook['id'], fromOrganization = false) { const response = await this.getById(id, false, fromOrganization); - this.items = { - ...this.items, - [id]: response, - }; + + runInAction(() => { + this.items = { + ...this.items, + [id]: response, + }; + }); } @action @@ -65,23 +74,25 @@ export class OutgoingWebhookStore extends BaseStore { params, }); - this.items = { - ...this.items, - ...results.reduce( - (acc: { [key: number]: OutgoingWebhook }, item: OutgoingWebhook) => ({ - ...acc, - [item.id]: item, - }), - {} - ), - }; + runInAction(() => { + this.items = { + ...this.items, + ...results.reduce( + (acc: { [key: number]: OutgoingWebhook }, item: OutgoingWebhook) => ({ + ...acc, + [item.id]: item, + }), + {} + ), + }; - const key = typeof query === 'string' ? query : ''; + const key = typeof query === 'string' ? query : ''; - this.searchResult = { - ...this.searchResult, - [key]: results.map((item: OutgoingWebhook) => item.id), - }; + this.searchResult = { + ...this.searchResult, + [key]: results.map((item: OutgoingWebhook) => item.id), + }; + }); } getSearchResult(query = '') { @@ -108,7 +119,10 @@ export class OutgoingWebhookStore extends BaseStore { @action.bound async updateOutgoingWebhookPresetsOptions() { const response = await makeRequest(`/webhooks/preset_options/`, {}); - this.outgoingWebhookPresets = response; + + runInAction(() => { + this.outgoingWebhookPresets = response; + }); } @action.bound diff --git a/grafana-plugin/src/models/resolution_note/resolution_note.ts b/grafana-plugin/src/models/resolution_note/resolution_note.ts index 9dcaeb2a..2c23bb19 100644 --- a/grafana-plugin/src/models/resolution_note/resolution_note.ts +++ b/grafana-plugin/src/models/resolution_note/resolution_note.ts @@ -1,16 +1,9 @@ -import { observable } from 'mobx'; - import { Alert } from 'models/alertgroup/alertgroup.types'; import BaseStore from 'models/base_store'; import { makeRequest } from 'network'; import { RootStore } from 'state'; -import { ResolutionNote } from './resolution_note.types'; - export class ResolutionNotesStore extends BaseStore { - @observable.shallow - resolutionNotes: { [id: string]: ResolutionNote[] } = {}; - constructor(rootStore: RootStore) { super(rootStore); diff --git a/grafana-plugin/src/models/schedule.ts b/grafana-plugin/src/models/schedule.ts deleted file mode 100644 index 1313cda2..00000000 --- a/grafana-plugin/src/models/schedule.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface ScheduleDTO { - id: string; - name: string; - ical_url_primary: string; - ical_url_overrides: string; - type: ScheduleType; - channel_name: string; - channel: string; -} - -export enum ScheduleType { - CalendarSchedule, - IcalSchedule, -} diff --git a/grafana-plugin/src/models/schedule/schedule.ts b/grafana-plugin/src/models/schedule/schedule.ts index f4e4a55e..f1505b37 100644 --- a/grafana-plugin/src/models/schedule/schedule.ts +++ b/grafana-plugin/src/models/schedule/schedule.ts @@ -1,5 +1,5 @@ import dayjs from 'dayjs'; -import { action, observable } from 'mobx'; +import { action, makeObservable, observable, runInAction } from 'mobx'; import { RemoteFiltersType } from 'containers/RemoteFilters/RemoteFilters.types'; import BaseStore from 'models/base_store'; @@ -118,6 +118,8 @@ export class ScheduleStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/schedules/'; } @@ -125,10 +127,12 @@ export class ScheduleStore extends BaseStore { async loadItem(id: Schedule['id'], skipErrorHandling = false): Promise { const schedule = await this.getById(id, skipErrorHandling); - this.items = { - ...this.items, - [id]: schedule, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: schedule, + }; + }); return schedule; } @@ -149,23 +153,26 @@ export class ScheduleStore extends BaseStore { return; } - this.items = { - ...this.items, - ...results.reduce( - (acc: { [key: number]: Schedule }, item: Schedule) => ({ - ...acc, - [item.id]: item, - }), - {} - ), - }; - this.searchResult = { - page_size, - count, - results: results.map((item: Schedule) => item.id), - }; + runInAction(() => { + this.items = { + ...this.items, + ...results.reduce( + (acc: { [key: number]: Schedule }, item: Schedule) => ({ + ...acc, + [item.id]: item, + }), + {} + ), + }; + this.searchResult = { + page_size, + count, + results: results.map((item: Schedule) => item.id), + }; + }); } + @action async updateItem(id: Schedule['id'], fromOrganization = false) { if (id) { let schedule; @@ -182,10 +189,12 @@ export class ScheduleStore extends BaseStore { } if (schedule) { - this.items = { - ...this.items, - [id]: schedule, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: schedule, + }; + }); } return schedule; @@ -204,7 +213,6 @@ export class ScheduleStore extends BaseStore { return await makeRequest(`/schedules/${scheduleId}/quality`, { method: 'GET' }); } - @action async reloadIcal(scheduleId: Schedule['id']) { await makeRequest(`/schedules/${scheduleId}/reload_ical/`, { method: 'POST', @@ -231,6 +239,7 @@ export class ScheduleStore extends BaseStore { // ------- NEW SCHEDULES API ENDPOINTS --------- + @action async createRotation(scheduleId: Schedule['id'], isOverride: boolean, params: Partial) { const type = isOverride ? 3 : 2; @@ -239,10 +248,12 @@ export class ScheduleStore extends BaseStore { method: 'POST', }); - this.shifts = { - ...this.shifts, - [response.id]: response, - }; + runInAction(() => { + this.shifts = { + ...this.shifts, + [response.id]: response, + }; + }); return response; } @@ -270,26 +281,28 @@ export class ScheduleStore extends BaseStore { method: 'POST', }); - if (isOverride) { - const overridePreview = enrichOverrides( - [...(this.events[scheduleId]?.['override']?.[fromString] as Array<{ shiftId: string; events: Event[] }>)], - response.rotation, - shiftId - ); + runInAction(() => { + if (isOverride) { + const overridePreview = enrichOverrides( + [...(this.events[scheduleId]?.['override']?.[fromString] as Array<{ shiftId: string; events: Event[] }>)], + response.rotation, + shiftId + ); - this.overridePreview = { ...this.overridePreview, [fromString]: overridePreview }; - } else { - const layers = enrichLayers( - [...(this.events[scheduleId]?.['rotation']?.[fromString] as Layer[])], - response.rotation, - shiftId, - params.priority_level - ); + this.overridePreview = { ...this.overridePreview, [fromString]: overridePreview }; + } else { + const layers = enrichLayers( + [...(this.events[scheduleId]?.['rotation']?.[fromString] as Layer[])], + response.rotation, + shiftId, + params.priority_level + ); - this.rotationPreview = { ...this.rotationPreview, [fromString]: layers }; - } + this.rotationPreview = { ...this.rotationPreview, [fromString]: layers }; + } - this.finalPreview = { ...this.finalPreview, [fromString]: fillGapsInShifts(splitToShifts(response.final)) }; + this.finalPreview = { ...this.finalPreview, [fromString]: fillGapsInShifts(splitToShifts(response.final)) }; + }); } @action @@ -310,10 +323,12 @@ export class ScheduleStore extends BaseStore { const shiftEventsListFlattened = flattenShiftEvents([...existingShiftEventsList, newShiftEvents]); - this.shiftSwapsPreview = { - ...this.shiftSwapsPreview, - [fromString]: shiftEventsListFlattened, - }; + runInAction(() => { + this.shiftSwapsPreview = { + ...this.shiftSwapsPreview, + [fromString]: shiftEventsListFlattened, + }; + }); } @action @@ -325,6 +340,7 @@ export class ScheduleStore extends BaseStore { this.rotationFormLiveParams = undefined; } + @action async updateRotation(shiftId: Shift['id'], params: Partial) { const response = await makeRequest(`/oncall_shifts/${shiftId}`, { params: { force: true }, @@ -332,54 +348,66 @@ export class ScheduleStore extends BaseStore { method: 'PUT', }); - this.shifts = { - ...this.shifts, - [response.id]: response, - }; + runInAction(() => { + this.shifts = { + ...this.shifts, + [response.id]: response, + }; + }); return response; } + @action async updateRotationAsNew(shiftId: Shift['id'], params: Partial) { const response = await makeRequest(`/oncall_shifts/${shiftId}`, { data: { ...params }, method: 'PUT', }); - this.shifts = { - ...this.shifts, - [response.id]: response, - }; + runInAction(() => { + this.shifts = { + ...this.shifts, + [response.id]: response, + }; + }); return response; } + @action updateRelatedEscalationChains = async (id: Schedule['id']) => { const response = await makeRequest(`/schedules/${id}/related_escalation_chains`, { method: 'GET', }); - this.relatedEscalationChains = { - ...this.relatedEscalationChains, - [id]: response, - }; + runInAction(() => { + this.relatedEscalationChains = { + ...this.relatedEscalationChains, + [id]: response, + }; + }); return response; }; + @action updateRelatedUsers = async (id: Schedule['id']) => { const { users } = await makeRequest(`/schedules/${id}/next_shifts_per_user`, { method: 'GET', }); - this.relatedUsers = { - ...this.relatedUsers, - [id]: users, - }; + runInAction(() => { + this.relatedUsers = { + ...this.relatedUsers, + [id]: users, + }; + }); return users; }; + @action async updateOncallShifts(scheduleId: Schedule['id']) { const { results } = await makeRequest(`/oncall_shifts/`, { params: { @@ -388,16 +416,18 @@ export class ScheduleStore extends BaseStore { method: 'GET', }); - this.shifts = { - ...this.shifts, - ...results.reduce( - (acc: { [key: number]: Shift }, item: Shift) => ({ - ...acc, - [item.id]: item, - }), - {} - ), - }; + runInAction(() => { + this.shifts = { + ...this.shifts, + ...results.reduce( + (acc: { [key: number]: Shift }, item: Shift) => ({ + ...acc, + [item.id]: item, + }), + {} + ), + }; + }); } @action @@ -410,10 +440,12 @@ export class ScheduleStore extends BaseStore { const response = await makeRequest(`/oncall_shifts/${shiftId}`, {}); - this.shifts = { - ...this.shifts, - [shiftId]: response, - }; + runInAction(() => { + this.shifts = { + ...this.shifts, + [shiftId]: response, + }; + }); delete this.shiftsCurrentlyUpdating[shiftId]; @@ -424,10 +456,12 @@ export class ScheduleStore extends BaseStore { async saveOncallShift(shiftId: Shift['id'], data: Partial) { const response = await makeRequest(`/oncall_shifts/${shiftId}`, { method: 'PUT', data }); - this.shifts = { - ...this.shifts, - [shiftId]: response, - }; + runInAction(() => { + this.shifts = { + ...this.shifts, + [shiftId]: response, + }; + }); return response; } @@ -439,6 +473,7 @@ export class ScheduleStore extends BaseStore { }).catch(this.onApiError); } + @action async updateEvents(scheduleId: Schedule['id'], startMoment: dayjs.Dayjs, type: RotationType = 'rotation', days = 9) { const dayBefore = startMoment.subtract(1, 'day'); @@ -457,16 +492,18 @@ export class ScheduleStore extends BaseStore { const shifts = fillGapsInShifts(shiftsUnflattened); const layers = type === 'rotation' ? splitToLayers(shifts) : undefined; - this.events = { - ...this.events, - [scheduleId]: { - ...this.events[scheduleId], - [type]: { - ...this.events[scheduleId]?.[type], - [fromString]: layers ? layers : shifts, + runInAction(() => { + this.events = { + ...this.events, + [scheduleId]: { + ...this.events[scheduleId], + [type]: { + ...this.events[scheduleId]?.[type], + [fromString]: layers ? layers : shifts, + }, }, - }, - }; + }; + }); } async updateFrequencyOptions() { @@ -475,10 +512,15 @@ export class ScheduleStore extends BaseStore { }); } + @action async updateDaysOptions() { - this.byDayOptions = await makeRequest(`/oncall_shifts/days_options/`, { + const result = await makeRequest(`/oncall_shifts/days_options/`, { method: 'GET', }); + + runInAction(() => { + this.byDayOptions = result; + }); } async createShiftSwap(params: Partial) { @@ -493,14 +535,18 @@ export class ScheduleStore extends BaseStore { return await makeRequest(`/shift_swaps/${shiftSwapId}/take`, { method: 'POST' }).catch(this.onApiError); } + @action async loadShiftSwap(id: ShiftSwap['id']) { const result = await makeRequest(`/shift_swaps/${id}`, { params: { expand_users: true } }); - this.shiftSwaps = { ...this.shiftSwaps, [id]: result }; + runInAction(() => { + this.shiftSwaps = { ...this.shiftSwaps, [id]: result }; + }); return result; } + @action async updateShiftSwaps(scheduleId: Schedule['id'], startMoment: dayjs.Dayjs, days = 9) { const fromString = getFromString(startMoment); @@ -522,23 +568,26 @@ export class ScheduleStore extends BaseStore { const shiftEventsListFlattened = flattenShiftEvents(shiftEventsList); - this.shiftSwaps = result.shift_swaps.reduce( - (memo, shiftSwap) => ({ - ...memo, - [shiftSwap.id]: shiftSwap, - }), - this.shiftSwaps - ); + runInAction(() => { + this.shiftSwaps = result.shift_swaps.reduce( + (memo, shiftSwap) => ({ + ...memo, + [shiftSwap.id]: shiftSwap, + }), + this.shiftSwaps + ); - this.scheduleAndDateToShiftSwaps = { - ...this.scheduleAndDateToShiftSwaps, - [scheduleId]: { - ...this.scheduleAndDateToShiftSwaps[scheduleId], - [fromString]: shiftEventsListFlattened, - }, - }; + this.scheduleAndDateToShiftSwaps = { + ...this.scheduleAndDateToShiftSwaps, + [scheduleId]: { + ...this.scheduleAndDateToShiftSwaps[scheduleId], + [fromString]: shiftEventsListFlattened, + }, + }; + }); } + @action async updatePersonalEvents(userPk: User['pk'], startMoment: dayjs.Dayjs, days = 9, isUpdateOnCallNow = false) { const fromString = getFromString(startMoment); @@ -558,20 +607,22 @@ export class ScheduleStore extends BaseStore { const shiftEventsListFlattened = flattenShiftEvents(shiftEventsList); - this.personalEvents = { - ...this.personalEvents, - [userPk]: { - ...this.personalEvents[userPk], - [fromString]: shiftEventsListFlattened, - }, - }; - - if (isUpdateOnCallNow) { - // since current endpoint works incorrectly we are waiting for https://github.com/grafana/oncall/issues/3164 - this.onCallNow = { - ...this.onCallNow, - [userPk]: is_oncall, + runInAction(() => { + this.personalEvents = { + ...this.personalEvents, + [userPk]: { + ...this.personalEvents[userPk], + [fromString]: shiftEventsListFlattened, + }, }; - } + + if (isUpdateOnCallNow) { + // since current endpoint works incorrectly we are waiting for https://github.com/grafana/oncall/issues/3164 + this.onCallNow = { + ...this.onCallNow, + [userPk]: is_oncall, + }; + } + }); } } diff --git a/grafana-plugin/src/models/slack/slack.ts b/grafana-plugin/src/models/slack/slack.ts index 95ae7735..ffda73f0 100644 --- a/grafana-plugin/src/models/slack/slack.ts +++ b/grafana-plugin/src/models/slack/slack.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { SlackChannel } from 'models/slack_channel/slack_channel.types'; @@ -16,19 +16,28 @@ export class SlackStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); } @action async updateSlackSettings() { - this.slackSettings = await makeRequest('/slack_settings/', {}); + const result = await makeRequest('/slack_settings/', {}); + + runInAction(() => { + this.slackSettings = result; + }); } @action async saveSlackSettings(data: Partial) { - this.slackSettings = await makeRequest('/slack_settings/', { + const result = await makeRequest('/slack_settings/', { data, method: 'PUT', }); + + runInAction(() => { + this.slackSettings = result; + }); } @action @@ -41,12 +50,17 @@ export class SlackStore extends BaseStore { @action async updateSlackIntegrationData(slack_id: string) { - return (this.slackIntegrationData = await makeRequest('/slack_integration/', { + const result = await makeRequest('/slack_integration/', { params: { slack_id }, - })); + }); + + runInAction(() => { + this.slackIntegrationData = result; + }); + + return result; } - @action async reinstallSlackIntegration(slack_id: string) { return await makeRequest('/slack_integration/', { validateStatus: function (status) { @@ -57,7 +71,6 @@ export class SlackStore extends BaseStore { }).catch(this.onApiError); } - @action async slackLogin() { const url_for_redirect = await makeRequest('/login/slack-login/', {}); window.location = url_for_redirect; diff --git a/grafana-plugin/src/models/slack_channel/slack_channel.ts b/grafana-plugin/src/models/slack_channel/slack_channel.ts index 389b0924..1ee8f75b 100644 --- a/grafana-plugin/src/models/slack_channel/slack_channel.ts +++ b/grafana-plugin/src/models/slack_channel/slack_channel.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { makeRequest } from 'network'; @@ -16,6 +16,8 @@ export class SlackChannelStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/slack_channels/'; } @@ -23,20 +25,24 @@ export class SlackChannelStore extends BaseStore { async updateById(id: SlackChannel['id']) { const response = await this.getById(id); - this.items = { - ...this.items, - [id]: response, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: response, + }; + }); } @action async updateItem(id: SlackChannel['id']) { const response = await this.getById(id); - this.items = { - ...this.items, - [id]: response, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: response, + }; + }); } @action @@ -45,21 +51,23 @@ export class SlackChannelStore extends BaseStore { params: { search: query }, }); - this.items = { - ...this.items, - ...results.reduce( - (acc: { [key: number]: SlackChannel }, item: SlackChannel) => ({ - ...acc, - [item.id]: item, - }), - {} - ), - }; + runInAction(() => { + this.items = { + ...this.items, + ...results.reduce( + (acc: { [key: number]: SlackChannel }, item: SlackChannel) => ({ + ...acc, + [item.id]: item, + }), + {} + ), + }; - this.searchResult = { - ...this.searchResult, - [query]: results.map((item: SlackChannel) => item.id), - }; + this.searchResult = { + ...this.searchResult, + [query]: results.map((item: SlackChannel) => item.id), + }; + }); } getSearchResult(query = '') { diff --git a/grafana-plugin/src/models/telegram_channel/telegram_channel.ts b/grafana-plugin/src/models/telegram_channel/telegram_channel.ts index 6b72151c..9a94bd5c 100644 --- a/grafana-plugin/src/models/telegram_channel/telegram_channel.ts +++ b/grafana-plugin/src/models/telegram_channel/telegram_channel.ts @@ -1,4 +1,4 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { makeRequest } from 'network'; @@ -21,6 +21,8 @@ export class TelegramChannelStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/telegram_channels/'; } @@ -36,43 +38,49 @@ export class TelegramChannelStore extends BaseStore { {} ); - this.items = { - ...this.items, - ...items, - }; + runInAction(() => { + this.items = { + ...this.items, + ...items, + }; - this.currentTeamToTelegramChannel = response.map((telegramChannel: TelegramChannel) => telegramChannel.id); + this.currentTeamToTelegramChannel = response.map((telegramChannel: TelegramChannel) => telegramChannel.id); + }); } @action async updateById(id: TelegramChannel['id']) { const response = await this.getById(id); - this.items = { - ...this.items, - [id]: response, - }; + runInAction(() => { + this.items = { + ...this.items, + [id]: response, + }; + }); } @action async updateItems(query = '') { const result = await this.getAll(); - this.items = { - ...this.items, - ...result.reduce( - (acc: { [key: number]: TelegramChannel }, item: TelegramChannel) => ({ - ...acc, - [item.id]: item, - }), - {} - ), - }; + runInAction(() => { + this.items = { + ...this.items, + ...result.reduce( + (acc: { [key: number]: TelegramChannel }, item: TelegramChannel) => ({ + ...acc, + [item.id]: item, + }), + {} + ), + }; - this.searchResult = { - ...this.searchResult, - [query]: result.map((item: TelegramChannel) => item.id), - }; + this.searchResult = { + ...this.searchResult, + [query]: result.map((item: TelegramChannel) => item.id), + }; + }); } getSearchResult(query = '') { diff --git a/grafana-plugin/src/models/user/user.ts b/grafana-plugin/src/models/user/user.ts index 1d6f18f2..4400d0f0 100644 --- a/grafana-plugin/src/models/user/user.ts +++ b/grafana-plugin/src/models/user/user.ts @@ -1,12 +1,11 @@ import { config } from '@grafana/runtime'; import dayjs from 'dayjs'; import { get } from 'lodash-es'; -import { action, computed, observable } from 'mobx'; +import { action, computed, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; -import { NotificationPolicyType } from 'models/notification_policy'; +import { NotificationPolicyType } from 'models/notification_policy/notification_policy'; import { makeRequest } from 'network'; -import { Mixpanel } from 'services/mixpanel'; import { RootStore } from 'state'; import { move } from 'state/helpers'; import { throttlingError } from 'utils'; @@ -48,6 +47,8 @@ export class UserStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/users/'; } @@ -64,11 +65,13 @@ export class UserStore extends BaseStore { const response = await makeRequest('/user/', {}); const timezone = await this.refreshTimezone(response.pk); - this.items = { - ...this.items, - [response.pk]: { ...response, timezone }, - }; - this.currentUserPk = response.pk; + runInAction(() => { + this.items = { + ...this.items, + [response.pk]: { ...response, timezone }, + }; + this.currentUserPk = response.pk; + }); } @action @@ -88,10 +91,12 @@ export class UserStore extends BaseStore { async loadUser(userPk: User['pk'], skipErrorHandling = false): Promise { const user = await this.getById(userPk, skipErrorHandling); - this.items = { - ...this.items, - [user.pk]: { ...user, timezone: getTimezone(user) }, - }; + runInAction(() => { + this.items = { + ...this.items, + [user.pk]: { ...user, timezone: getTimezone(user) }, + }; + }); return user; } @@ -106,10 +111,12 @@ export class UserStore extends BaseStore { const user = await this.getById(userPk); - this.items = { - ...this.items, - [user.pk]: { ...user, timezone: getTimezone(user) }, - }; + runInAction(() => { + this.items = { + ...this.items, + [user.pk]: { ...user, timezone: getTimezone(user) }, + }; + }); delete this.itemsCurrentlyUpdating[userPk]; } @@ -135,25 +142,27 @@ export class UserStore extends BaseStore { const { count, results, page_size } = response; - this.items = { - ...this.items, - ...results.reduce( - (acc: { [key: number]: User }, item: User) => ({ - ...acc, - [item.pk]: { - ...item, - timezone: getTimezone(item), - }, - }), - {} - ), - }; + runInAction(() => { + this.items = { + ...this.items, + ...results.reduce( + (acc: { [key: number]: User }, item: User) => ({ + ...acc, + [item.pk]: { + ...item, + timezone: getTimezone(item), + }, + }), + {} + ), + }; - this.searchResult = { - count, - page_size, - results: results.map((item: User) => item.pk), - }; + this.searchResult = { + count, + page_size, + results: results.map((item: User) => item.pk), + }; + }); return response; } @@ -178,10 +187,12 @@ export class UserStore extends BaseStore { const user = await this.getById(userPk); - this.items = { - ...this.items, - [user.pk]: user, - }; + runInAction(() => { + this.items = { + ...this.items, + [user.pk]: user, + }; + }); }; @action @@ -192,10 +203,12 @@ export class UserStore extends BaseStore { const user = await this.getById(userPk); - this.items = { - ...this.items, - [user.pk]: user, - }; + runInAction(() => { + this.items = { + ...this.items, + [user.pk]: user, + }; + }); }; sendBackendConfirmationCode = (userPk: User['pk'], backend: string) => @@ -216,10 +229,12 @@ export class UserStore extends BaseStore { async createUser(data: any) { const user = await this.create(data); - this.items = { - ...this.items, - [user.pk]: user, - }; + runInAction(() => { + this.items = { + ...this.items, + [user.pk]: user, + }; + }); return user; } @@ -238,10 +253,12 @@ export class UserStore extends BaseStore { this.rootStore.userStore.loadCurrentUser(); } - this.items = { - ...this.items, - [data.pk as User['pk']]: user, - }; + runInAction(() => { + this.items = { + ...this.items, + [data.pk as User['pk']]: user, + }; + }); } @action @@ -254,10 +271,12 @@ export class UserStore extends BaseStore { }, }); - this.items = { - ...this.items, - [this.currentUserPk as User['pk']]: user, - }; + runInAction(() => { + this.items = { + ...this.items, + [this.currentUserPk as User['pk']]: user, + }; + }); } @action @@ -300,15 +319,16 @@ export class UserStore extends BaseStore { params: { user: id, important: false }, }); - this.notificationPolicies = { - ...this.notificationPolicies, - [id]: [...nonImportantEPs, ...importantEPs], - }; + runInAction(() => { + this.notificationPolicies = { + ...this.notificationPolicies, + [id]: [...nonImportantEPs, ...importantEPs], + }; + }); } @action async moveNotificationPolicyToPosition(userPk: User['pk'], oldIndex: number, newIndex: number, offset: number) { - Mixpanel.track('Move NotificationPolicy', null); const notificationPolicy = this.notificationPolicies[userPk][oldIndex + offset]; this.notificationPolicies[userPk] = move(this.notificationPolicies[userPk], oldIndex + offset, newIndex + offset); @@ -348,20 +368,20 @@ export class UserStore extends BaseStore { data: value, }); - this.notificationPolicies = { - ...this.notificationPolicies, - [userPk]: this.notificationPolicies[userPk].map((policy: NotificationPolicyType) => - id === policy.id ? { ...policy, ...notificationPolicy } : policy - ), - }; + runInAction(() => { + this.notificationPolicies = { + ...this.notificationPolicies, + [userPk]: this.notificationPolicies[userPk].map((policy: NotificationPolicyType) => + id === policy.id ? { ...policy, ...notificationPolicy } : policy + ), + }; + }); this.updateItem(userPk); // to update notification_chain_verbal } @action async deleteNotificationPolicy(userPk: User['pk'], id: NotificationPolicyType['id']) { - Mixpanel.track('Delete NotificationPolicy', null); - await makeRequest(`/notification_policies/${id}`, { method: 'DELETE' }).catch(this.onApiError); this.updateNotificationPolicies(userPk); @@ -374,7 +394,10 @@ export class UserStore extends BaseStore { const response = await makeRequest('/notification_policies/', { method: 'OPTIONS', }); - this.notificationChoices = get(response, 'actions.POST', []); + + runInAction(() => { + this.notificationChoices = get(response, 'actions.POST', []); + }); } @action @@ -390,9 +413,13 @@ export class UserStore extends BaseStore { @action.bound async updateNotifyByOptions() { const response = await makeRequest('/notification_policies/notify_by_options/', {}); - this.notifyByOptions = response; + + runInAction(() => { + this.notifyByOptions = response; + }); } + @action async makeTestCall(userPk: User['pk']) { this.isTestCallInProgress = true; @@ -401,7 +428,9 @@ export class UserStore extends BaseStore { }) .catch(this.onApiError) .finally(() => { - this.isTestCallInProgress = false; + runInAction(() => { + this.isTestCallInProgress = false; + }); }); } diff --git a/grafana-plugin/src/models/user_group/user_group.ts b/grafana-plugin/src/models/user_group/user_group.ts index 659d028a..8e20b7d4 100644 --- a/grafana-plugin/src/models/user_group/user_group.ts +++ b/grafana-plugin/src/models/user_group/user_group.ts @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, makeObservable, runInAction } from 'mobx'; import BaseStore from 'models/base_store'; import { makeRequest } from 'network'; @@ -16,6 +16,8 @@ export class UserGroupStore extends BaseStore { constructor(rootStore: RootStore) { super(rootStore); + makeObservable(this); + this.path = '/user_groups/'; } @@ -25,21 +27,23 @@ export class UserGroupStore extends BaseStore { params: { search: query }, }); - this.items = { - ...this.items, - ...result.reduce( - (acc: { [key: number]: UserGroup }, item: UserGroup) => ({ - ...acc, - [item.id]: item, - }), - {} - ), - }; + runInAction(() => { + this.items = { + ...this.items, + ...result.reduce( + (acc: { [key: number]: UserGroup }, item: UserGroup) => ({ + ...acc, + [item.id]: item, + }), + {} + ), + }; - this.searchResult = { - ...(this.searchResult || {}), - [query]: result.map((item: UserGroup) => item.id), - }; + this.searchResult = { + ...(this.searchResult || {}), + [query]: result.map((item: UserGroup) => item.id), + }; + }); } getSearchResult(query = '') { diff --git a/grafana-plugin/src/models/wait_delay.ts b/grafana-plugin/src/models/wait_delay.ts deleted file mode 100644 index 203fd7d9..00000000 --- a/grafana-plugin/src/models/wait_delay.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface WaitDelay { - value: string; - display_name: string; -} diff --git a/grafana-plugin/src/pages/incidents/Incidents.tsx b/grafana-plugin/src/pages/incidents/Incidents.tsx index 71dfbb97..6c6c80d2 100644 --- a/grafana-plugin/src/pages/incidents/Incidents.tsx +++ b/grafana-plugin/src/pages/incidents/Incidents.tsx @@ -179,9 +179,7 @@ class Incidents extends React.Component ); } - renderCards(filtersState, setFiltersState, filtersOnFiltersValueChange) { - const { store } = this.props; - + renderCards(filtersState, setFiltersState, filtersOnFiltersValueChange, store) { const { values } = filtersState; const { newIncidents, acknowledgedIncidents, resolvedIncidents, silencedIncidents } = store.alertGroupStore; @@ -301,7 +299,9 @@ class Incidents extends React.Component query={query} page={PAGE.Incidents} onChange={this.handleFiltersChange} - extraFilters={this.renderCards.bind(this)} + extraFilters={(...args) => { + return this.renderCards(...args, store); + }} grafanaTeamStore={store.grafanaTeamStore} defaultFilters={{ team: [], diff --git a/grafana-plugin/src/pages/integration/Integration.tsx b/grafana-plugin/src/pages/integration/Integration.tsx index 3a220645..c02ceb59 100644 --- a/grafana-plugin/src/pages/integration/Integration.tsx +++ b/grafana-plugin/src/pages/integration/Integration.tsx @@ -56,8 +56,8 @@ import { AlertReceiveChannel, AlertReceiveChannelCounters, } from 'models/alert_receive_channel/alert_receive_channel.types'; -import { AlertTemplatesDTO } from 'models/alert_templates'; -import { ChannelFilter } from 'models/channel_filter'; +import { AlertTemplatesDTO } from 'models/alert_templates/alert_templates'; +import { ChannelFilter } from 'models/channel_filter/channel_filter.types'; import { INTEGRATION_TEMPLATES_LIST } from 'pages/integration/Integration.config'; import IntegrationHelper from 'pages/integration/Integration.helper'; import styles from 'pages/integration/Integration.module.scss'; diff --git a/grafana-plugin/src/pages/settings/tabs/LiveSettings/LiveSettingsPage.tsx b/grafana-plugin/src/pages/settings/tabs/LiveSettings/LiveSettingsPage.tsx index e6f6da03..b66223ca 100644 --- a/grafana-plugin/src/pages/settings/tabs/LiveSettings/LiveSettingsPage.tsx +++ b/grafana-plugin/src/pages/settings/tabs/LiveSettings/LiveSettingsPage.tsx @@ -2,9 +2,8 @@ import React from 'react'; import { Button, Checkbox, HorizontalGroup, Icon } from '@grafana/ui'; import cn from 'classnames/bind'; -import { observe } from 'mobx'; +import { Lambda, observe } from 'mobx'; import { observer } from 'mobx-react'; -import { Lambda } from 'mobx/lib/internal'; import GTable from 'components/GTable/GTable'; import Text from 'components/Text/Text'; diff --git a/grafana-plugin/src/services/mixpanel.ts b/grafana-plugin/src/services/mixpanel.ts deleted file mode 100644 index 30762550..00000000 --- a/grafana-plugin/src/services/mixpanel.ts +++ /dev/null @@ -1,28 +0,0 @@ -const mixpanel = window.mixpanel; - -let actions = { - identify: (id: any) => { - if (mixpanel) { - mixpanel.identify(id); - } - }, - alias: (id: any) => { - if (mixpanel) { - mixpanel.alias(id); - } - }, - track: (name: any, props: any) => { - if (mixpanel) { - mixpanel.track(name, props); - } - }, - people: { - set: (props: any) => { - if (mixpanel) { - mixpanel.people.set(props); - } - }, - }, -}; - -export let Mixpanel = actions; diff --git a/grafana-plugin/src/state/rootBaseStore/index.ts b/grafana-plugin/src/state/rootBaseStore/index.ts index bc4e903b..7599a3dc 100644 --- a/grafana-plugin/src/state/rootBaseStore/index.ts +++ b/grafana-plugin/src/state/rootBaseStore/index.ts @@ -1,6 +1,6 @@ import { locationService } from '@grafana/runtime'; import { contextSrv } from 'grafana/app/core/core'; -import { action, observable } from 'mobx'; +import { action, makeObservable, observable, runInAction } from 'mobx'; import moment from 'moment-timezone'; import qs from 'query-string'; import { OnCallAppPluginMeta } from 'types'; @@ -113,8 +113,11 @@ export class RootBaseStore { labelsStore = new LabelStore(this); loaderStore = LoaderStore; + constructor() { + makeObservable(this); + } @action.bound - async loadBasicData() { + loadBasicData = async () => { const updateFeatures = async () => { await this.updateFeatures(); @@ -131,18 +134,24 @@ export class RootBaseStore { () => this.grafanaTeamStore.updateItems(), () => updateFeatures(), ]); - this.isBasicDataLoaded = true; - } + this.setIsBasicDataLoaded(true); + }; - @action.bound - async loadMasterData() { + @action + loadMasterData = async () => { Promise.all([ this.userStore.updateNotificationPolicyOptions(), this.userStore.updateNotifyByOptions(), this.alertReceiveChannelStore.updateAlertReceiveChannelOptions(), ]); + }; + + @action + setIsBasicDataLoaded(value: boolean) { + this.isBasicDataLoaded = value; } + @action setupPluginError(errorMsg: string) { this.initializationError = errorMsg; } @@ -167,7 +176,7 @@ export class RootBaseStore { * Finally, try to load the current user from the OnCall backend */ async setupPlugin(meta: OnCallAppPluginMeta) { - this.initializationError = null; + this.setupPluginError(null); this.onCallApiUrl = getOnCallApiUrl(meta); if (!FaroHelper.faro) { @@ -245,9 +254,11 @@ export class RootBaseStore { } } else { // everything is all synced successfully at this point.. - this.backendVersion = pluginConnectionStatus.version; - this.backendLicense = pluginConnectionStatus.license; - this.recaptchaSiteKey = pluginConnectionStatus.recaptcha_site_key; + runInAction(() => { + this.backendVersion = pluginConnectionStatus.version; + this.backendLicense = pluginConnectionStatus.license; + this.recaptchaSiteKey = pluginConnectionStatus.recaptcha_site_key; + }); } if (!this.userStore.currentUser) { @@ -292,13 +303,16 @@ export class RootBaseStore { @action.bound async updateFeatures() { const response = await makeRequest('/features/', {}); - this.features = response.reduce( - (acc: any, key: string) => ({ - ...acc, - [key]: true, - }), - {} - ); + + runInAction(() => { + this.features = response.reduce( + (acc: any, key: string) => ({ + ...acc, + [key]: true, + }), + {} + ); + }); } @action diff --git a/grafana-plugin/tsconfig.json b/grafana-plugin/tsconfig.json index 676efe28..d568c4a5 100644 --- a/grafana-plugin/tsconfig.json +++ b/grafana-plugin/tsconfig.json @@ -11,6 +11,7 @@ "strict": false, "resolveJsonModule": true, "noImplicitAny": false, - "skipLibCheck": true + "skipLibCheck": true, + "useDefineForClassFields": true } } diff --git a/grafana-plugin/yarn.lock b/grafana-plugin/yarn.lock index 2c791915..e76fe020 100644 --- a/grafana-plugin/yarn.lock +++ b/grafana-plugin/yarn.lock @@ -11101,22 +11101,24 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mobx-react-lite@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-1.4.0.tgz#193beb5fdddf17ae61542f65ff951d84db402351" - integrity sha512-5xCuus+QITQpzKOjAOIQ/YxNhOl/En+PlNJF+5QU4Qxn9gnNMJBbweAdEW3HnuVQbfqDYEUnkGs5hmkIIStehg== - -mobx-react@6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-6.1.1.tgz#24a2c8a3393890fa732b4efd34cc6dcccf6e0e7a" - integrity sha512-hjACWCTpxZf9Sv1YgWF/r6HS6Nsly1SYF22qBJeUE3j+FMfoptgjf8Zmcx2d6uzA07Cezwap5Cobq9QYa0MKUw== +mobx-react-lite@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-4.0.5.tgz#e2cb98f813e118917bcc463638f5bf6ea053a67b" + integrity sha512-StfB2wxE8imKj1f6T8WWPf4lVMx3cYH9Iy60bbKXEs21+HQ4tvvfIBZfSmMXgQAefi8xYEwQIz4GN9s0d2h7dg== dependencies: - mobx-react-lite "1.4.0" + use-sync-external-store "^1.2.0" -mobx@5.13.0: - version "5.13.0" - resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.13.0.tgz#0fd68f10aa5ff2d146a4ed9e145b53337cfbca59" - integrity sha512-eSAntMSMNj0PFL705rgv+aB/z1RjNqDnFEpBe18yQVreXTWiVgIrmBUXzjnJfuba+eo4eAk6zi+/gXQkSUea8A== +mobx-react@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-9.1.0.tgz#5e54919ca27ffad5f2c0d835148a1f681cebdbc1" + integrity sha512-DeDRTYw4AlgHw8xEXtiZdKKEnp+c5/jeUgTbTQXEqnAzfkrgYRWP3p3Nv3Whc2CEcM/mDycbDWGjxKokQdlffg== + dependencies: + mobx-react-lite "^4.0.4" + +mobx@6.12.0: + version "6.12.0" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.12.0.tgz#72b2685ca5af031aaa49e77a4d76ed67fcbf9135" + integrity sha512-Mn6CN6meXEnMa0a5u6a5+RKrqRedHBhZGd15AWLk9O6uFY4KYHzImdt8JI8WODo1bjTSRnwXhJox+FCUZhCKCQ== module-details-from-path@^1.0.3: version "1.0.3" @@ -15686,6 +15688,11 @@ use-memo-one@^1.1.1: resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99" integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ== +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"