Bump MobX version to 6.12 and simplify stores (#3561)

# What this PR does

- update 
  "mobx" to "6.12.0",
  "mobx-react" to "9.1.0",
- add `runInAction` when update observables after async operations
- update babel config and ts config according to the
[guide](https://mobx.js.org/migrating-from-4-or-5.html)
- add  `makeObservable(this);` to each model constructor

## Which issue(s) this PR fixes

https://github.com/grafana/oncall/issues/3453

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated
- [ ] Documentation added (or `pr:no public docs` PR label added if not
required)
- [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
This commit is contained in:
Maxim Mordasov 2024-01-03 14:37:01 +03:00 committed by GitHub
parent 44ec718d7c
commit 59a064e05a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 954 additions and 807 deletions

View file

@ -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)

View file

@ -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"
]

View file

@ -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",

View file

@ -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<EscalationPolicyProps, any
// @ts-ignore
value={wait_delay}
onChange={this._getOnSelectChangeHandler('wait_delay')}
options={waitDelays.map((waitDelay: WaitDelay) => ({
options={waitDelays.map((waitDelay: SelectOption) => ({
value: waitDelay.value,
label: waitDelay.display_name,
}))}

View file

@ -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<NotificationPolicyProps,
value={wait_delay}
disabled={disabled}
onChange={this._getOnChangeHandler('wait_delay')}
options={waitDelays.map((waitDelay: WaitDelay) => ({
options={waitDelays.map((waitDelay: SelectOption) => ({
label: waitDelay.display_name,
value: waitDelay.value,
}))}
@ -208,7 +207,7 @@ export class NotificationPolicy extends React.Component<NotificationPolicyProps,
value={notify_by}
disabled={disabled}
onChange={this._getOnChangeHandler('notify_by')}
options={notifyByOptions.map((notifyByOption: NotifyBy) => ({
options={notifyByOptions.map((notifyByOption: SelectOption) => ({
label: notifyByOption.display_name,
value: notifyByOption.value,
}))}

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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<AlertReceiveChannel> {
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<ChannelFilter>) {
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<AlertReceiveChannel>): SelectOption {
@ -338,13 +360,14 @@ export class AlertReceiveChannelStore extends BaseStore {
async saveAlertReceiveChannel(id: AlertReceiveChannel['id'], data: Partial<AlertReceiveChannel>) {
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<any> {
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;
}

View file

@ -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);
});
}
}

View file

@ -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<number, Alert>([...this.alerts, ...newAlerts]);
runInAction(() => {
// @ts-ignore
this.alerts = new Map<number, Alert>([...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<void> {
return await makeRequest('/alertgroup_table_settings/reset', { method: 'POST' }).catch(() =>
openErrorNotification('There was an error resetting the table settings')
);
}
@action
async loadLabelsKeys(): Promise<Array<ApiSchemas['LabelKey']>> {
return await makeRequest(`/alertgroups/labels/keys/`, {}).catch(() =>
openErrorNotification('There was an error processing your request')
);
}
@action
async loadValuesForLabelKey(
key: ApiSchemas['LabelKey']['id'],
search = ''

View file

@ -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';

View file

@ -1,5 +0,0 @@
export interface ApiTokenDTO {
pk: string;
token_name: string;
created_at: string;
}

View file

@ -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',
});

View file

@ -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;
}

View file

@ -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' });
}

View file

@ -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/';
}

View file

@ -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<EscalationChain> {
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<EscalationChain>) {
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;
}

View file

@ -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<User['pk']>;
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,
};
}

View file

@ -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(

View file

@ -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';

View file

@ -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;
}

View file

@ -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 = '') {

View file

@ -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<GrafanaTeam>) {
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<TeamItems>(
(acc, item) => ({
...acc,
[item.id]: item,
}),
{}
),
};
this.searchResult = result.map((item: GrafanaTeam) => item.id);
runInAction(() => {
this.items = {
...this.items,
...result.reduce<TeamItems>(
(acc, item) => ({
...acc,
[item.id]: item,
}),
{}
),
};
this.searchResult = result.map((item: GrafanaTeam) => item.id);
});
}
getSearchResult() {

View file

@ -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,
};
});
}
}

View file

@ -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}`, {

View file

@ -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;

View file

@ -1,4 +1,4 @@
import { User } from './user/user.types';
import { User } from 'models/user/user.types';
export interface NotificationPolicyType {
id: string;

View file

@ -1,4 +0,0 @@
export interface NotifyBy {
value: string;
display_name: string;
}

View file

@ -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<Organization>) {
this.currentOrganization = await makeRequest(this.path, {
method: 'PUT',

View file

@ -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<OutgoingWebhook> {
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

View file

@ -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);

View file

@ -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,
}

View file

@ -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<Schedule> {
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<Shift>) {
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<Shift>) {
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<Shift>) {
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<Shift>) {
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<ShiftSwap>) {
@ -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,
};
}
});
}
}

View file

@ -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<SlackSettings>) {
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;

View file

@ -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 = '') {

View file

@ -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 = '') {

View file

@ -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<User> {
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;
});
});
}

View file

@ -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 = '') {

View file

@ -1,4 +0,0 @@
export interface WaitDelay {
value: string;
display_name: string;
}

View file

@ -179,9 +179,7 @@ class Incidents extends React.Component<IncidentsPageProps, IncidentsPageState>
);
}
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<IncidentsPageProps, IncidentsPageState>
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: [],

View file

@ -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';

View file

@ -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';

View file

@ -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;

View file

@ -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

View file

@ -11,6 +11,7 @@
"strict": false,
"resolveJsonModule": true,
"noImplicitAny": false,
"skipLibCheck": true
"skipLibCheck": true,
"useDefineForClassFields": true
}
}

View file

@ -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"