Switch to async/await Promises handling across the codebase (#4191)

# What this PR does

Use async/await across the frontend codebase for Promises handling

## Which issue(s) this PR closes

Closes https://github.com/grafana/oncall/issues/3736

<!--
*Note*: if you have more than one GitHub issue that this PR closes, be
sure to preface
each issue link with a [closing
keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue).
This ensures that the issue(s) are auto-closed once the PR has been
merged.
-->

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
This commit is contained in:
Dominik Broj 2024-04-15 13:55:50 +02:00 committed by GitHub
parent 28ed2ddf61
commit 8187dfb595
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 780 additions and 758 deletions

View file

@ -3,7 +3,7 @@ rulesDirPlugin.RULES_DIR = 'tools/eslint-rules';
module.exports = {
extends: ['./.config/.eslintrc'],
plugins: ['rulesdir', 'import', 'unused-imports'],
plugins: ['rulesdir', 'import', 'unused-imports', 'promise'],
settings: {
'import/internal-regex':
'^assets|^components|^containers|^contexts|^icons|^models|^network|^pages|^services|^state|^utils|^plugin',
@ -71,5 +71,6 @@ module.exports = {
'react-hooks/exhaustive-deps': 'off',
'rulesdir/no-relative-import-paths': ['error', { allowSameFolder: true }],
'@typescript-eslint/explicit-member-accessibility': 'off',
'promise/prefer-await-to-then': 'error',
},
};

View file

@ -85,6 +85,7 @@
"eslint": "^8.25.0",
"eslint-plugin-deprecation": "^2.0.0",
"eslint-plugin-jsdoc": "^44.2.4",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.31.10",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-rulesdir": "^0.2.1",

View file

@ -26,6 +26,7 @@ import { ContactPoint } from 'models/alert_receive_channel/alert_receive_channel
import { ApiSchemas } from 'network/oncall-api/api.types';
import styles from 'pages/integration/Integration.module.scss';
import { useStore } from 'state/useStore';
import { GENERIC_ERROR } from 'utils/consts';
import { openErrorNotification, openNotification } from 'utils/utils';
const cx = cn.bind(styles);
@ -250,6 +251,17 @@ export const IntegrationContactPoint: React.FC<{
}
function renderActions(item: ContactPoint) {
const onDisconnect = async () => {
try {
await AlertReceiveChannelHelper.disconnectContactPoint(id, item.dataSourceId, item.contactPoint);
closeDrawer();
openNotification('Contact point has been removed');
alertReceiveChannelStore.fetchConnectedContactPoints(id);
} catch (_err) {
openErrorNotification(GENERIC_ERROR);
}
};
return (
<HorizontalGroup spacing="md">
<IconButton
@ -274,19 +286,7 @@ export const IntegrationContactPoint: React.FC<{
</VerticalGroup>
}
>
<IconButton
aria-label="Disconnect Contact Point"
name="trash-alt"
onClick={() => {
AlertReceiveChannelHelper.disconnectContactPoint(id, item.dataSourceId, item.contactPoint)
.then(() => {
closeDrawer();
openNotification('Contact point has been removed');
alertReceiveChannelStore.fetchConnectedContactPoints(id);
})
.catch(() => openErrorNotification('An error has occurred. Please try again.'));
}}
/>
<IconButton aria-label="Disconnect Contact Point" name="trash-alt" onClick={onDisconnect} />
</WithConfirm>
</HorizontalGroup>
);
@ -330,23 +330,21 @@ export const IntegrationContactPoint: React.FC<{
});
}
function onContactPointConnect() {
async function onContactPointConnect() {
setState({ isLoading: true });
(isExistingContactPoint
? AlertReceiveChannelHelper.connectContactPoint(id, selectedAlertManager, selectedContactPoint)
: AlertReceiveChannelHelper.createContactPoint(id, selectedAlertManager, selectedContactPoint)
)
.then(() => {
closeDrawer();
openNotification('A new contact point has been connected to your integration');
alertReceiveChannelStore.fetchConnectedContactPoints(id);
})
.catch((ex) => {
const error = ex.response?.data?.detail ?? 'An error has occurred. Please try again.';
openErrorNotification(error);
})
.finally(() => setState({ isLoading: false }));
try {
await (isExistingContactPoint
? AlertReceiveChannelHelper.connectContactPoint(id, selectedAlertManager, selectedContactPoint)
: AlertReceiveChannelHelper.createContactPoint(id, selectedAlertManager, selectedContactPoint));
closeDrawer();
openNotification('A new contact point has been connected to your integration');
alertReceiveChannelStore.fetchConnectedContactPoints(id);
} catch (ex) {
const error = ex.response?.data?.detail ?? GENERIC_ERROR;
openErrorNotification(error);
} finally {
setState({ isLoading: false });
}
}
function onAlertManagerChange(option: SelectableValue<string>) {

View file

@ -101,17 +101,16 @@ export const IntegrationSendDemoAlertModal: React.FC<IntegrationSendDemoPayloadM
setDemoPayload(value);
}
function onSendAlert() {
async function onSendAlert() {
let parsedPayload = undefined;
try {
parsedPayload = JSON.parse(demoPayload);
} catch (ex) {}
AlertReceiveChannelHelper.sendDemoAlert(alertReceiveChannel.id, parsedPayload).then(() => {
alertReceiveChannelStore.fetchCounters();
openNotification(<DemoNotification />);
onHideOrCancel();
});
await AlertReceiveChannelHelper.sendDemoAlert(alertReceiveChannel.id, parsedPayload);
alertReceiveChannelStore.fetchCounters();
openNotification(<DemoNotification />);
onHideOrCancel();
}
function getCurlText() {

View file

@ -24,14 +24,13 @@ export const LabelsFilterComponent: FC<LabelsFilterProps> = (props) => {
onChange(value.map((v) => v.data));
}, []);
const handleLoadOptions = (search) => {
return onLoadOptions(search).then((options) =>
options.map((v) => ({
label: `${v.key[FieldName]} : ${v.value[FieldName]}`,
value: `${v.key[FieldName]} : ${v.value[FieldName]}`,
data: v,
}))
);
const handleLoadOptions = async (search) => {
const options = await onLoadOptions(search);
return options.map((v) => ({
label: `${v.key[FieldName]} : ${v.value[FieldName]}`,
value: `${v.key[FieldName]} : ${v.value[FieldName]}`,
data: v,
}));
};
const value = useMemo(

View file

@ -7,23 +7,23 @@ type PluginId = SupportedPlugin | string;
const pluginCache = new Map<string, PluginMeta>();
export function getPluginSettings(pluginId: PluginId, options?: Partial<BackendSrvRequest>): Promise<PluginMeta> {
export async function getPluginSettings(pluginId: PluginId, options?: Partial<BackendSrvRequest>): Promise<PluginMeta> {
const pluginMetadata = pluginCache.get(pluginId);
if (pluginMetadata) {
return Promise.resolve(pluginMetadata);
}
return (
getBackendSrv()
.get(`/api/plugins/${pluginId}/settings`, undefined, undefined, options)
.then((settings: PluginMeta) => {
pluginCache.set(pluginId, settings);
return settings;
})
// TODO this error handling could be better
.catch((err: unknown) => {
return Promise.reject(new Error('Unknown Plugin' + err));
})
);
try {
const settings = await getBackendSrv().get<PluginMeta>(
`/api/plugins/${pluginId}/settings`,
undefined,
undefined,
options
);
pluginCache.set(pluginId, settings);
return settings;
} catch (err) {
throw new Error('Unknown Plugin' + err);
}
}

View file

@ -7,7 +7,6 @@ import { observer } from 'mobx-react';
import CopyToClipboard from 'react-copy-to-clipboard';
import { SourceCode } from 'components/SourceCode/SourceCode';
import { ApiToken } from 'models/api_token/api_token.types';
import { useStore } from 'state/useStore';
import { openErrorNotification, openNotification } from 'utils/utils';
@ -28,14 +27,14 @@ export const ApiTokenForm = observer((props: TokenCreationModalProps) => {
const store = useStore();
const onCreateTokenCallback = useCallback(() => {
store.apiTokenStore
.create({ name })
.then((data: ApiToken) => {
setToken(data.token);
onUpdate();
})
.catch((error) => openErrorNotification(get(error, 'response.data.detail', 'error creating token')));
const onCreateTokenCallback = useCallback(async () => {
try {
const data = await store.apiTokenStore.create({ name });
setToken(data.token);
onUpdate();
} catch (error) {
openErrorNotification(get(error, 'response.data.detail', 'error creating token'));
}
}, [name]);
const handleNameChange = useCallback((event) => {

View file

@ -142,10 +142,9 @@ class _ApiTokenSettings extends React.Component<ApiTokensProps, any> {
store: { apiTokenStore },
} = this.props;
return () => {
apiTokenStore.revokeApiToken(id).then(() => {
apiTokenStore.updateItems();
});
return async () => {
await apiTokenStore.revokeApiToken(id);
apiTokenStore.updateItems();
};
};

View file

@ -50,11 +50,10 @@ export const AttachIncidentForm = observer(({ id, onUpdate, onHide }: AttachInci
setSelected(value);
}, []);
const handleLinkClick = useCallback(() => {
AlertGroupHelper.attachAlert(id, selected).then(() => {
onHide();
onUpdate();
});
const handleLinkClick = useCallback(async () => {
await AlertGroupHelper.attachAlert(id, selected);
onHide();
onUpdate();
}, [selected, alertGroupStore, id, onHide, onUpdate]);
return (

View file

@ -59,20 +59,14 @@ export const EditRegexpRouteTemplateModal = observer((props: EditRegexpRouteTemp
}
}, [regexpTemplateBody]);
const handleConvertToJinja2 = useCallback(() => {
AlertReceiveChannelHelper.convertRegexpTemplateToJinja2Template(channelFilterId).then((response) => {
alertReceiveChannelStore
.saveChannelFilter(channelFilterId, {
filtering_term: response?.filtering_term_as_jinja2,
filtering_term_type: 1,
})
.then(() => {
alertReceiveChannelStore.fetchChannelFilters(alertReceiveChannelId, true).then(() => {
onOpenEditIntegrationTemplate('route_template', channelFilterId);
});
});
const handleConvertToJinja2 = useCallback(async () => {
const response = await AlertReceiveChannelHelper.convertRegexpTemplateToJinja2Template(channelFilterId);
await alertReceiveChannelStore.saveChannelFilter(channelFilterId, {
filtering_term: response?.filtering_term_as_jinja2,
filtering_term_type: 1,
});
await alertReceiveChannelStore.fetchChannelFilters(alertReceiveChannelId, true);
onOpenEditIntegrationTemplate('route_template', channelFilterId);
onHide();
}, []);

View file

@ -96,23 +96,23 @@ export const GSelect = observer(<Item,>(props: GSelectProps<Item>) => {
[propItems, onChange]
);
const loadOptions = useDebouncedCallback((query: string, cb) => {
fetchItemsFn(query).then(() => {
const searchResult = getSearchResult(query);
// TODO: we need to unify interface of search results to get rid of ts-ignore
// @ts-ignore
let items = Array.isArray(searchResult.results) ? searchResult.results : searchResult;
if (filterOptions) {
items = items.filter((opt: any) => filterOptions(opt[valueField]));
}
const options = items.map((item: any) => ({
value: item[valueField],
label: get(item, displayField),
imgUrl: item.avatar_url,
description: getDescription && getDescription(item),
}));
cb(options);
});
const loadOptions = useDebouncedCallback(async (query: string, cb) => {
await fetchItemsFn(query);
const searchResult = getSearchResult(query);
// TODO: we need to unify interface of search results to get rid of ts-ignore
// @ts-ignore
let items = Array.isArray(searchResult.results) ? searchResult.results : searchResult;
if (filterOptions) {
items = items.filter((opt: any) => filterOptions(opt[valueField]));
}
const options = items.map((item: any) => ({
value: item[valueField],
label: get(item, displayField),
imgUrl: item.avatar_url,
description: getDescription && getDescription(item),
}));
cb(options);
}, 250);
const getValues = () => {

View file

@ -99,9 +99,10 @@ export const ExpandedIntegrationRouteDisplay: React.FC<ExpandedIntegrationRouteD
useEffect(() => {
setIsLoading(true);
Promise.all([escalationChainStore.updateItems(), telegramChannelStore.updateTelegramChannels()]).then(() =>
setIsLoading(false)
);
(async () => {
await Promise.all([escalationChainStore.updateItems(), telegramChannelStore.updateTelegramChannels()]);
setIsLoading(false);
})();
}, []);
const channelFilter = alertReceiveChannelStore.channelFilters[channelFilterId];
@ -340,15 +341,12 @@ export const ExpandedIntegrationRouteDisplay: React.FC<ExpandedIntegrationRouteD
onRouteDelete(routeIdForDeletion);
}
function onEscalationChainChange({ id }) {
alertReceiveChannelStore
.saveChannelFilter(channelFilterId, {
escalation_chain: id,
})
.then(() => {
escalationChainStore.updateItems(); // to update number_of_integrations and number_of_routes
escalationPolicyStore.updateEscalationPolicies(id);
});
async function onEscalationChainChange({ id }) {
await alertReceiveChannelStore.saveChannelFilter(channelFilterId, {
escalation_chain: id,
});
escalationChainStore.updateItems(); // to update number_of_integrations and number_of_routes
escalationPolicyStore.updateEscalationPolicies(id);
}
async function onEscalationChainsRefresh() {

View file

@ -41,15 +41,12 @@ export const IntegrationTemplateList: React.FC<IntegrationTemplateListProps> = o
const [templateRestoreName, setTemplateRestoreName] = useState<string>(undefined);
const [autoresolveValue, setAutoresolveValue] = useState(alertReceiveChannelAllowSourceBasedResolving);
const handleSaveClick = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const handleSaveClick = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
setAutoresolveValue(event.target.checked);
alertReceiveChannelStore
.saveAlertReceiveChannel(alertReceiveChannelId, {
allow_source_based_resolving: event.target.checked,
})
.then(() => {
openNotification('Autoresolve ' + (event.target.checked ? 'enabled' : 'disabled'));
});
await alertReceiveChannelStore.saveAlertReceiveChannel(alertReceiveChannelId, {
allow_source_based_resolving: event.target.checked,
});
openNotification('Autoresolve ' + (event.target.checked ? 'enabled' : 'disabled'));
}, []);
const templatesToRender = getTemplatesToRender(features);
@ -128,22 +125,20 @@ export const IntegrationTemplateList: React.FC<IntegrationTemplateListProps> = o
return templateName === 'grouping_id_template';
}
function onResetTemplate(templateName: string) {
async function onResetTemplate(templateName: string) {
setTemplateRestoreName(undefined);
setIsRestoringTemplate(true);
alertReceiveChannelStore
.saveTemplates(alertReceiveChannelId, { [templateName]: '' })
.then(() => {
openNotification('The Alert template has been updated');
})
.catch((err) => {
if (err.response?.data?.length > 0) {
openErrorNotification(err.response.data);
} else {
openErrorNotification(err.message);
}
});
try {
await alertReceiveChannelStore.saveTemplates(alertReceiveChannelId, { [templateName]: '' });
openNotification('The Alert template has been updated');
} catch (err) {
if (err.response?.data?.length > 0) {
openErrorNotification(err.response.data);
} else {
openErrorNotification(err.message);
}
}
}
}
);

View file

@ -26,7 +26,7 @@ import { LabelsErrors } from 'models/label/label.types';
import { ApiSchemas } from 'network/oncall-api/api.types';
import { LabelTemplateOptions } from 'pages/integration/IntegrationCommon.config';
import { useStore } from 'state/useStore';
import { DOCS_ROOT } from 'utils/consts';
import { DOCS_ROOT, GENERIC_ERROR } from 'utils/consts';
import { openErrorNotification } from 'utils/utils';
import { getIsAddBtnDisabled, getIsTooManyLabelsWarningVisible } from './IntegrationLabelsForm.helpers';
@ -338,7 +338,7 @@ const CustomLabels = (props: CustomLabelsProps) => {
if (res?.response?.status === 409) {
openErrorNotification(`Duplicate values are not allowed`);
} else {
openErrorNotification('An error has occurred. Please try again');
openErrorNotification(GENERIC_ERROR);
}
}}
renderValue={(option, index, renderValueDefault) => {

View file

@ -8,6 +8,7 @@ import { observer } from 'mobx-react';
import { splitToGroups } from 'models/label/label.helpers';
import { LabelKeyValue } from 'models/label/label.types';
import { useStore } from 'state/useStore';
import { GENERIC_ERROR } from 'utils/consts';
import { openErrorNotification } from 'utils/utils';
export interface LabelsProps {
@ -128,7 +129,7 @@ function onUpdateError(res) {
if (res?.response?.status === 409) {
openErrorNotification(`Duplicate values are not allowed`);
} else {
openErrorNotification('An error has occurred. Please try again');
openErrorNotification(GENERIC_ERROR);
}
}

View file

@ -27,44 +27,42 @@ export const LabelsFilter = observer((props: LabelsFilterProps) => {
filterType === 'alert_group_labels' ? AlertGroupHelper.loadValuesForLabelKey : labelsStore.loadValuesForKey;
useEffect(() => {
loadKeys().then(setKeys);
(async () => {
const keys = await loadKeys();
setKeys(keys);
})();
}, []);
useEffect(() => {
const keyValuePairs = (propsValue || []).map((k) => k.split(':'));
const promises = keyValuePairs.map(([keyId]) => loadValuesForKey(keyId));
const fetchKeyValues = async () => await Promise.all(promises);
fetchKeyValues().then((list) => {
(async () => {
const list = await Promise.all(promises);
const value = list.map(({ key, values }, index) => ({
key,
value: values.find((v) => v.id === keyValuePairs[index][1]) || {},
}));
setValue(value);
});
})();
}, [propsValue, keys]);
const handleLoadOptions = (search) => {
const handleLoadOptions = async (search) => {
if (!search) {
return Promise.resolve([]);
return [];
}
return new Promise((resolve) => {
const keysFiltered = keys.filter((k) => k.name.toLowerCase().includes(search.toLowerCase()));
const keysFiltered = keys.filter((k) => k.name.toLowerCase().includes(search.toLowerCase()));
const promises = keysFiltered.map((key) => loadValuesForKey(key.id));
const promises = keysFiltered.map((key) => loadValuesForKey(key.id));
Promise.all(promises).then((list) => {
const options = list.reduce((memo, { key, values }) => {
const options = values.map((value) => ({ key, value }));
const list = await Promise.all(promises);
const options = list.reduce((memo, { key, values }) => {
const options = values.map((value) => ({ key, value }));
return [...memo, ...options];
}, []);
return [...memo, ...options];
}, []);
resolve(options);
});
});
return options;
};
return (

View file

@ -28,13 +28,13 @@ export const MSTeamsInstructions: FC<MSTeamsInstructionsProps> = observer((props
const { onCallisAdded, showInfoBox, personalSettings, onHide = () => {}, verificationCode } = props;
const { msteamsChannelStore } = useStore();
const handleMSTeamsGetChannels = () => {
msteamsChannelStore.updateItems().then(() => {
const connectedChannels = msteamsChannelStore.getSearchResult();
if (!connectedChannels?.length) {
openWarningNotification('No MS Teams channels found');
}
});
const handleMSTeamsGetChannels = async () => {
await msteamsChannelStore.updateItems();
const connectedChannels = msteamsChannelStore.getSearchResult();
if (!connectedChannels?.length) {
openWarningNotification('No MS Teams channels found');
}
onHide();
};

View file

@ -60,9 +60,10 @@ const MSTeamsModal = (props: MSTeamsModalProps) => {
const [verificationCode, setVerificationCode] = useState<string>();
const store = useStore();
useEffect(() => {
store.msteamsChannelStore.getMSTeamsChannelVerificationCode().then((res) => {
(async () => {
const res = await store.msteamsChannelStore.getMSTeamsChannelVerificationCode();
setVerificationCode(res);
});
})();
}, []);
return (

View file

@ -144,8 +144,9 @@ export const RotationForm = observer((props: RotationFormProps) => {
}, [showActiveOnSelectedDays]);
useEffect(() => {
if (isOpen) {
waitForElement(`#layer${shiftId === 'new' ? layerPriority : shift?.priority_level}`).then((elm) => {
(async () => {
if (isOpen) {
const elm = await waitForElement(`#layer${shiftId === 'new' ? layerPriority : shift?.priority_level}`);
const modal = document.querySelector(`.${cx('draggable')}`) as HTMLDivElement;
const coords = getCoords(elm);
@ -155,8 +156,8 @@ export const RotationForm = observer((props: RotationFormProps) => {
);
setOffsetTop(offsetTop);
});
}
}
})();
}, [isOpen]);
const handleChangeEndless = useCallback(
@ -166,10 +167,9 @@ export const RotationForm = observer((props: RotationFormProps) => {
[endLess]
);
const handleDeleteClick = useCallback((force: boolean) => {
store.scheduleStore.deleteOncallShift(shiftId, force).then(() => {
onDelete();
});
const handleDeleteClick = useCallback(async (force: boolean) => {
await store.scheduleStore.deleteOncallShift(shiftId, force);
onDelete();
}, []);
useEffect(() => {
@ -184,15 +184,22 @@ export const RotationForm = observer((props: RotationFormProps) => {
}
}, []);
const updatePreview = () => {
const updatePreview = async () => {
setErrors({});
store.scheduleStore
.updateRotationPreview(scheduleId, shiftId, store.timezoneStore.calendarStartDate, false, params)
.catch(onError)
.finally(() => {
setIsOpen(true);
});
try {
await store.scheduleStore.updateRotationPreview(
scheduleId,
shiftId,
store.timezoneStore.calendarStartDate,
false,
params
);
} catch (err) {
onError(err);
} finally {
setIsOpen(true);
}
};
const onError = useCallback((error) => {
@ -241,31 +248,31 @@ export const RotationForm = observer((props: RotationFormProps) => {
useEffect(handleChange, [params, store.timezoneStore.calendarStartDate]);
const create = useCallback(() => {
store.scheduleStore
.createRotation(scheduleId, false, { ...params, name: rotationName })
.then(() => {
onCreate();
})
.catch(onError);
const create = useCallback(async () => {
try {
await store.scheduleStore.createRotation(scheduleId, false, { ...params, name: rotationName });
onCreate();
} catch (err) {
onError(err);
}
}, [scheduleId, shiftId, params]);
const update = useCallback(() => {
store.scheduleStore
.updateRotation(shiftId, params)
.then(() => {
onUpdate();
})
.catch(onError);
const update = useCallback(async () => {
try {
await store.scheduleStore.updateRotation(shiftId, params);
onUpdate();
} catch (err) {
onError(err);
}
}, [shiftId, params]);
const updateAsNew = useCallback(() => {
store.scheduleStore
.updateRotationAsNew(shiftId, params)
.then(() => {
onUpdate();
})
.catch(onError);
const updateAsNew = useCallback(async () => {
try {
await store.scheduleStore.updateRotationAsNew(shiftId, params);
onUpdate();
} catch (err) {
onError(err);
}
}, [shiftId, params]);
const handleEditNewerRotationClick = useCallback(() => {

View file

@ -75,20 +75,19 @@ export const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
);
useEffect(() => {
if (isOpen) {
waitForElement('#overrides-list').then((elm) => {
(async () => {
if (isOpen) {
const elm = await waitForElement('#overrides-list');
const modal = document.querySelector(`.${cx('draggable')}`) as HTMLDivElement;
const coords = getCoords(elm);
const offsetTop = Math.min(
Math.max(coords.top - modal?.offsetHeight - 10, GRAFANA_HEADER_HEIGHT + 10),
document.body.offsetHeight - modal?.offsetHeight - 10
);
setOffsetTop(offsetTop);
});
}
}
})();
}, [isOpen]);
const [userGroups, setUserGroups] = useState([[]]);
@ -130,29 +129,23 @@ export const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
[shiftId, params, shift]
);
const handleDeleteClick = useCallback(() => {
store.scheduleStore.deleteOncallShift(shiftId).then(() => {
onHide();
onDelete();
});
const handleDeleteClick = useCallback(async () => {
await store.scheduleStore.deleteOncallShift(shiftId);
onHide();
onDelete();
}, []);
const handleCreate = useCallback(() => {
if (shiftId === 'new') {
store.scheduleStore
.createRotation(scheduleId, true, params)
.then(() => {
onCreate();
})
.catch(onError);
} else {
store.scheduleStore
.updateRotation(shiftId, params)
.then(() => {
onUpdate();
})
.catch(onError);
const handleCreate = useCallback(async () => {
try {
if (shiftId === 'new') {
await store.scheduleStore.createRotation(scheduleId, true, params);
onCreate();
} else {
await store.scheduleStore.updateRotation(shiftId, params);
onUpdate();
}
} catch (err) {
onError(err);
}
}, [scheduleId, shiftId, params]);
@ -162,15 +155,22 @@ export const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
}
}, []);
const updatePreview = () => {
const updatePreview = async () => {
setErrors({});
store.scheduleStore
.updateRotationPreview(scheduleId, shiftId, store.timezoneStore.calendarStartDate, true, params)
.catch(onError)
.finally(() => {
setIsOpen(true);
});
try {
await store.scheduleStore.updateRotationPreview(
scheduleId,
shiftId,
store.timezoneStore.calendarStartDate,
true,
params
);
} catch (err) {
onError(err);
} finally {
setIsOpen(true);
}
};
const onError = useCallback((error) => {

View file

@ -43,9 +43,12 @@ export const ShiftSwapForm = (props: ShiftSwapFormProps) => {
} = store;
useEffect(() => {
if (id !== 'new') {
scheduleStore.loadShiftSwap(id).then(setShiftSwap);
}
(async () => {
if (id !== 'new') {
const shiftSwap = await scheduleStore.loadShiftSwap(id);
setShiftSwap(shiftSwap);
}
})();
}, [id]);
const handleHide = useCallback(() => {

View file

@ -23,27 +23,26 @@ export const ScheduleICalSettings: FC<ScheduleICalSettingsProps> = observer((pro
const store = useStore();
const [ICalLink, setICalLink] = useState<string>(undefined);
const [isiCalLinkExist, setIsICalLinkExist] = useState<boolean>(false);
const [isICalLinkLoading, setIsICalLinkLoading] = useState<boolean>(true);
const [isiCalLinkExist, setIsICalLinkExist] = useState(false);
const [isICalLinkLoading, setIsICalLinkLoading] = useState(true);
useEffect(() => {
store.scheduleStore
.getICalLink(id)
.then(() => {
(async () => {
try {
await store.scheduleStore.getICalLink(id);
setIsICalLinkExist(true);
setIsICalLinkLoading(false);
})
.catch(() => {
} catch (_err) {
setIsICalLinkExist(false);
setIsICalLinkLoading(false);
});
}
})();
}, []);
const handleCreateICalLink = async () => {
setIsICalLinkExist(true);
await store.scheduleStore
.createICalLink(id)
.then((res: CreateScheduleExportTokenResponse) => setICalLink(res?.export_url));
const res: CreateScheduleExportTokenResponse = await store.scheduleStore.createICalLink(id);
setICalLink(res?.export_url);
};
const handleRevokeICalLink = async () => {

View file

@ -143,10 +143,11 @@ export const TeamModal = ({ teamId, onHide }: TeamModalProps) => {
String(Number(team.is_sharing_resources_to_all))
);
const handleSubmit = useCallback(() => {
Promise.all([
const handleSubmit = useCallback(async () => {
await Promise.all([
grafanaTeamStore.updateTeam(teamId, { is_sharing_resources_to_all: Boolean(Number(shareResourceToAll)) }),
]).then(onHide);
]);
onHide();
}, [shareResourceToAll]);
return (

View file

@ -67,10 +67,11 @@ const TelegramModal = (props: TelegramModalProps) => {
const [botLink, setBotLink] = useState<string>();
useEffect(() => {
telegramChannelStore.getTelegramVerificationCode().then((res) => {
(async () => {
const res = await telegramChannelStore.getTelegramVerificationCode();
setVerificationCode(res.telegram_code);
setBotLink(res.bot_link);
});
})();
}, []);
return (

View file

@ -60,30 +60,29 @@ export const TemplatePreview = observer((props: TemplatePreviewProps) => {
const store = useStore();
const { outgoingWebhookStore } = store;
const handleTemplateBodyChange = useDebouncedCallback(() => {
(templatePage === TEMPLATE_PAGE.Webhooks
? outgoingWebhookStore.renderPreview(outgoingWebhookId, templateName, templateBody, payload)
: alertGroupId
? AlertGroupHelper.renderPreview(alertGroupId, templateName, templateBody)
: AlertReceiveChannelHelper.renderPreview(alertReceiveChannelId, templateName, templateBody, payload)
)
.then((data) => {
setResult(data);
if (data?.preview === 'True') {
setConditionalResult({ isResult: true, value: 'True' });
} else if (templateType === 'boolean') {
setConditionalResult({ isResult: true, value: 'False' });
} else {
setConditionalResult({ isResult: false, value: undefined });
}
})
.catch((err) => {
if (err.response?.data?.length > 0) {
openErrorNotification(err.response.data);
} else {
openErrorNotification(err.message);
}
});
const handleTemplateBodyChange = useDebouncedCallback(async () => {
try {
const data = await (templatePage === TEMPLATE_PAGE.Webhooks
? outgoingWebhookStore.renderPreview(outgoingWebhookId, templateName, templateBody, payload)
: alertGroupId
? AlertGroupHelper.renderPreview(alertGroupId, templateName, templateBody)
: AlertReceiveChannelHelper.renderPreview(alertReceiveChannelId, templateName, templateBody, payload));
setResult(data);
if (data?.preview === 'True') {
setConditionalResult({ isResult: true, value: 'True' });
} else if (templateType === 'boolean') {
setConditionalResult({ isResult: true, value: 'False' });
} else {
setConditionalResult({ isResult: false, value: undefined });
}
} catch (err) {
if (err.response?.data?.length > 0) {
openErrorNotification(err.response.data);
} else {
openErrorNotification(err.message);
}
}
}, 1000);
useEffect(handleTemplateBodyChange, [templateBody, payload]);

View file

@ -57,16 +57,18 @@ export const TemplatesAlertGroupsList = (props: TemplatesAlertGroupsListProps) =
const [isEditMode, setIsEditMode] = useState(false);
useEffect(() => {
if (templatePage === TEMPLATE_PAGE.Webhooks) {
if (outgoingwebhookId !== 'new') {
store.outgoingWebhookStore.getLastResponses(outgoingwebhookId).then(setOutgoingWebhookLastResponses);
}
} else if (templatePage === TEMPLATE_PAGE.Integrations) {
AlertGroupHelper.getAlertGroupsForIntegration(alertReceiveChannelId).then((result) => {
(async () => {
if (templatePage === TEMPLATE_PAGE.Webhooks) {
if (outgoingwebhookId !== 'new') {
const res = await store.outgoingWebhookStore.getLastResponses(outgoingwebhookId);
setOutgoingWebhookLastResponses(res);
}
} else if (templatePage === TEMPLATE_PAGE.Integrations) {
const result = await AlertGroupHelper.getAlertGroupsForIntegration(alertReceiveChannelId);
setAlertGroupsList(result.slice(0, 30));
onLoadAlertGroupsList(result.length > 0);
});
}
}
})();
}, []);
const getChangeHandler = () => {

View file

@ -24,20 +24,22 @@ export const ICalConnector = (props: ICalConnectorProps) => {
const [iCalLoading, setiCalLoading] = useState<boolean>(true);
useEffect(() => {
UserHelper.getiCalLink(id)
.then((_res) => {
(async () => {
try {
await UserHelper.getiCalLink(id);
setIsiCalLinkExisting(true);
setiCalLoading(false);
})
.catch((_res) => {
} catch (_err) {
setIsiCalLinkExisting(false);
setiCalLoading(false);
});
}
})();
}, []);
const handleCreateiCalLink = async () => {
setIsiCalLinkExisting(true);
await UserHelper.createiCalLink(id).then((res) => setShowiCalLink(res?.export_url));
const res = await UserHelper.createiCalLink(id);
setShowiCalLink(res?.export_url);
};
const handleRevokeiCalLink = async () => {

View file

@ -26,10 +26,11 @@ const GoogleCalendar: React.FC<{ id: ApiSchemas['User']['pk'] }> = observer(({ i
const [showSchedulesDropdown, setShowSchedulesDropdown] = useState<boolean>();
useEffect(() => {
userStore.fetchItemById({ userPk: id }).then((user) => {
(async () => {
const user = await userStore.fetchItemById({ userPk: id });
setGoogleCalendarSettings(user.google_calendar_settings);
setShowSchedulesDropdown(user.google_calendar_settings.oncall_schedules_to_consider_for_shift_swaps?.length > 0);
});
})();
}, []);
const handleShowSchedulesDropdownChange = (event: React.ChangeEvent<HTMLInputElement>) => {

View file

@ -19,13 +19,17 @@ export const MSTeamsInfo = observer(() => {
const [onCallisAdded, setOnCallisAdded] = useState(false);
useEffect(() => {
UserHelper.fetchBackendConfirmationCode(userStore.currentUserPk, 'MSTEAMS').then(setVerificationCode);
msteamsChannelStore.updateItems().then(() => {
(async () => {
const res = await UserHelper.fetchBackendConfirmationCode(userStore.currentUserPk, 'MSTEAMS');
setVerificationCode(res);
await msteamsChannelStore.updateItems();
const connectedChannels = msteamsChannelStore.getSearchResult();
if (connectedChannels?.length) {
setOnCallisAdded(true);
}
});
})();
}, []);
useEffect(() => {

View file

@ -94,11 +94,10 @@ export const PhoneVerification = observer((props: PhoneVerificationProps) => {
userStore.sendTestSms(userPk);
}, [userPk, userStore.sendTestSms]);
const handleForgetNumberClick = useCallback(() => {
UserHelper.forgetPhone(userPk).then(async () => {
await userStore.fetchItemById({ userPk });
setState({ phone: '', showForgetScreen: false, isCodeSent: false, isPhoneCallInitiated: false });
});
const handleForgetNumberClick = useCallback(async () => {
await UserHelper.forgetPhone(userPk);
await userStore.fetchItemById({ userPk });
setState({ phone: '', showForgetScreen: false, isCodeSent: false, isPhoneCallInitiated: false });
}, [userPk, UserHelper.forgetPhone, userStore.fetchItemById]);
const onSubmitCallback = useCallback(
@ -108,39 +107,35 @@ export const PhoneVerification = observer((props: PhoneVerificationProps) => {
codeVerification = isPhoneCallInitiated;
}
if (codeVerification) {
UserHelper.verifyPhone(userPk, code).then(() => {
userStore.fetchItemById({ userPk });
});
await UserHelper.verifyPhone(userPk, code);
userStore.fetchItemById({ userPk });
} else {
window.grecaptcha.ready(function () {
window.grecaptcha
.execute(rootStore.recaptchaSiteKey, { action: 'mobile_verification_code' })
.then(async function (token) {
await userStore.updateUser({
pk: userPk,
email: user.email,
unverified_phone_number: phone,
});
window.grecaptcha.ready(async function () {
const token = await window.grecaptcha.execute(rootStore.recaptchaSiteKey, {
action: 'mobile_verification_code',
});
await userStore.updateUser({
pk: userPk,
email: user.email,
unverified_phone_number: phone,
});
switch (type) {
case 'verification_call':
UserHelper.fetchVerificationCall(userPk, token).then(() => {
setState({ isPhoneCallInitiated: true });
if (codeInputRef.current) {
codeInputRef.current.focus();
}
});
break;
case 'verification_sms':
UserHelper.fetchVerificationCode(userPk, token).then(() => {
setState({ isCodeSent: true });
if (codeInputRef.current) {
codeInputRef.current.focus();
}
});
break;
switch (type) {
case 'verification_call':
await UserHelper.fetchVerificationCall(userPk, token);
setState({ isPhoneCallInitiated: true });
if (codeInputRef.current) {
codeInputRef.current.focus();
}
});
break;
case 'verification_sms':
await UserHelper.fetchVerificationCode(userPk, token);
setState({ isCodeSent: true });
if (codeInputRef.current) {
codeInputRef.current.focus();
}
break;
}
});
}
},
@ -157,9 +152,8 @@ export const PhoneVerification = observer((props: PhoneVerificationProps) => {
);
const onVerifyCallback = useCallback(async () => {
UserHelper.verifyPhone(userPk, code).then(() => {
userStore.fetchItemById({ userPk });
});
await UserHelper.verifyPhone(userPk, code);
userStore.fetchItemById({ userPk });
}, [code, userPk, UserHelper.verifyPhone, userStore.fetchItemById]);
const providerConfiguration = organizationStore.currentOrganization?.env_status.phone_provider;

View file

@ -33,10 +33,11 @@ export const TelegramInfo = observer((_props: TelegramInfoProps) => {
const telegramConfigured = organizationStore.currentOrganization?.env_status.telegram_configured;
useEffect(() => {
UserHelper.fetchTelegramConfirmationCode(userStore.currentUserPk).then((res) => {
(async () => {
const res = await UserHelper.fetchTelegramConfirmationCode(userStore.currentUserPk);
setVerificationCode(res.telegram_code);
setBotLink(res.bot_link);
});
})();
}, []);
return (

View file

@ -219,14 +219,19 @@ export class AlertReceiveChannelHelper {
}
static async sendDemoAlertToParticularRoute(id: ChannelFilter['id']) {
await makeRequest(`/channel_filters/${id}/send_demo_alert/`, { method: 'POST' }).catch(showApiError);
try {
await makeRequest(`/channel_filters/${id}/send_demo_alert/`, { method: 'POST' });
} catch (err) {
showApiError(err);
}
}
static async convertRegexpTemplateToJinja2Template(id: ChannelFilter['id']) {
const result = await makeRequest(`/channel_filters/${id}/convert_from_regex_to_jinja2/`, { method: 'POST' }).catch(
showApiError
);
return result;
try {
return await makeRequest(`/channel_filters/${id}/convert_from_regex_to_jinja2/`, { method: 'POST' });
} catch (err) {
showApiError(err);
}
}
static async createChannelFilter(data: Partial<ChannelFilter>) {

View file

@ -44,53 +44,68 @@ export class BaseStore {
@action.bound
async getAll(query = '') {
return await makeRequest(`${this.path}`, {
params: { search: query },
method: 'GET',
}).catch(this.onApiError);
try {
return await makeRequest(`${this.path}`, {
params: { search: query },
method: 'GET',
});
} catch (err) {
this.onApiError(err);
}
}
@action.bound
async getById(id: string, skipErrorHandling = false, fromOrganization = false) {
return await makeRequest(`${this.path}${id}`, {
method: 'GET',
params: { from_organization: fromOrganization },
}).catch((error) => this.onApiError(error, skipErrorHandling));
try {
return await makeRequest(`${this.path}${id}`, {
method: 'GET',
params: { from_organization: fromOrganization },
});
} catch (error) {
this.onApiError(error, skipErrorHandling);
}
}
@action.bound
async create<RT = any>(data: any, skipErrorHandling = false): Promise<RT | void> {
return await makeRequest<RT>(this.path, {
method: 'POST',
data,
}).catch((error) => {
try {
return await makeRequest<RT>(this.path, {
method: 'POST',
data,
});
} catch (error) {
this.onApiError(error, skipErrorHandling);
});
}
}
@action.bound
async update<RT = any>(id: any, data: any, params: any = null, skipErrorHandling = false): Promise<RT | void> {
const result = await makeRequest<RT>(`${this.path}${id}/`, {
method: 'PUT',
data,
params: params,
}).catch((error) => {
this.onApiError(error, skipErrorHandling);
});
try {
const result = await makeRequest<RT>(`${this.path}${id}/`, {
method: 'PUT',
data,
params: params,
});
// Update env_status field for current team
await this.rootStore.organizationStore.loadCurrentOrganization();
return result;
// Update env_status field for current team
await this.rootStore.organizationStore.loadCurrentOrganization();
return result;
} catch (error) {
this.onApiError(error, skipErrorHandling);
}
}
@action.bound
async delete(id: any) {
const result = await makeRequest(`${this.path}${id}/`, {
method: 'DELETE',
}).catch(this.onApiError);
// Update env_status field for current team
await this.rootStore.organizationStore.loadCurrentOrganization();
return result;
try {
const result = await makeRequest(`${this.path}${id}/`, {
method: 'DELETE',
});
// Update env_status field for current team
await this.rootStore.organizationStore.loadCurrentOrganization();
return result;
} catch (error) {
this.onApiError(error);
}
}
}

View file

@ -75,22 +75,30 @@ export class DirectPagingStore extends BaseStore {
}
async createManualAlertRule(data: ManualAlertGroupPayload): Promise<DirectPagingResponse | void> {
return await makeRequest<DirectPagingResponse>(this.path, {
method: 'POST',
data,
}).catch(this.onApiError);
try {
return await makeRequest<DirectPagingResponse>(this.path, {
method: 'POST',
data,
});
} catch (err) {
this.onApiError(err);
}
}
async updateAlertGroup(
alertId: ApiSchemas['AlertGroup']['pk'],
data: ManualAlertGroupPayload
): Promise<DirectPagingResponse | void> {
return await makeRequest<DirectPagingResponse>(this.path, {
method: 'POST',
data: {
alert_group_id: alertId,
...data,
},
}).catch(this.onApiError);
try {
return await makeRequest<DirectPagingResponse>(this.path, {
method: 'POST',
data: {
alert_group_id: alertId,
...data,
},
});
} catch (err) {
this.onApiError(err);
}
}
}

View file

@ -486,10 +486,14 @@ export class ScheduleStore extends BaseStore {
}
async deleteOncallShift(shiftId: Shift['id'], force?: boolean) {
return await makeRequest(`/oncall_shifts/${shiftId}`, {
method: 'DELETE',
params: { force },
}).catch(this.onApiError);
try {
return await makeRequest(`/oncall_shifts/${shiftId}`, {
method: 'DELETE',
params: { force },
});
} catch (err) {
this.onApiError(err);
}
}
@action.bound
@ -566,15 +570,27 @@ export class ScheduleStore extends BaseStore {
}
async createShiftSwap(params: Partial<ShiftSwap>) {
return await makeRequest(`/shift_swaps/`, { method: 'POST', data: params }).catch(this.onApiError);
try {
return await makeRequest(`/shift_swaps/`, { method: 'POST', data: params });
} catch (err) {
this.onApiError(err);
}
}
async deleteShiftSwap(shiftSwapId: ShiftSwap['id']) {
return await makeRequest(`/shift_swaps/${shiftSwapId}`, { method: 'DELETE' }).catch(this.onApiError);
try {
return await makeRequest(`/shift_swaps/${shiftSwapId}`, { method: 'DELETE' });
} catch (err) {
this.onApiError(err);
}
}
async takeShiftSwap(shiftSwapId: ShiftSwap['id']) {
return await makeRequest(`/shift_swaps/${shiftSwapId}/take`, { method: 'POST' }).catch(this.onApiError);
try {
return await makeRequest(`/shift_swaps/${shiftSwapId}/take`, { method: 'POST' });
} catch (err) {
this.onApiError(err);
}
}
@action.bound

View file

@ -62,13 +62,17 @@ export class SlackStore extends BaseStore {
}
async reinstallSlackIntegration(slack_id: string) {
return await makeRequest('/slack_integration/', {
validateStatus: function (status) {
return status === 200 || status === 403;
},
method: 'POST',
params: { slack_id },
}).catch(this.onApiError);
try {
return await makeRequest('/slack_integration/', {
validateStatus: function (status) {
return status === 200 || status === 403;
},
method: 'POST',
params: { slack_id },
});
} catch (err) {
this.onApiError(err);
}
}
async slackLogin() {

View file

@ -89,7 +89,11 @@ export class UserHelper {
}
static async getiCalLink(userPk: ApiSchemas['User']['pk']) {
return (await onCallApi().GET('/users/{id}/export_token/', { params: { path: { id: userPk } } })).data;
return (
await onCallApi({ skipErrorHandling: true }).GET('/users/{id}/export_token/', {
params: { path: { id: userPk } },
})
).data;
}
static async createiCalLink(userPk: ApiSchemas['User']['pk']) {

View file

@ -53,11 +53,11 @@ export const makeRequest = async <RT = any>(path: string, config: RequestConfig)
span.setAttribute('page_url', document.URL.split('//')[1]);
}
return new Promise<RT>((resolve, reject) => {
otel.context.with(otel.trace.setSpan(otel.context.active(), span), async () => {
FaroHelper.faro.api.pushEvent('Sending request', { url });
return otel.context.with(otel.trace.setSpan(otel.context.active(), span), async () => {
FaroHelper.faro.api.pushEvent('Sending request', { url });
instance({
try {
const response = await instance({
method,
url,
params,
@ -75,37 +75,34 @@ export const makeRequest = async <RT = any>(path: string, config: RequestConfig)
*/
'X-Idempotency-Key': `${Date.now()}-${Math.random()}`,
},
})
.then((response) => {
FaroHelper.faro.api.pushEvent('Request completed', { url });
span.end();
resolve(response.data as RT);
})
.catch((ex) => {
FaroHelper.faro.api.pushEvent('Request failed', { url });
FaroHelper.faro.api.pushError(ex);
span.end();
reject(ex);
});
});
});
FaroHelper.faro.api.pushEvent('Request completed', { url });
span.end();
return response.data as RT;
} catch (ex) {
FaroHelper.faro.api.pushEvent('Request failed', { url });
FaroHelper.faro.api.pushError(ex);
span.end();
throw ex;
}
});
}
return instance({
method,
url,
params,
data,
validateStatus,
headers,
})
.then((response) => {
FaroHelper.faro?.api.pushEvent('Request completed', { url });
return response.data as RT;
})
.catch((ex) => {
FaroHelper.faro?.api.pushEvent('Request failed', { url });
FaroHelper.faro?.api.pushError(ex);
return Promise.reject(ex);
try {
const response = await instance({
method,
url,
params,
data,
validateStatus,
headers,
});
FaroHelper.faro?.api.pushEvent('Request completed', { url });
return response.data as RT;
} catch (ex) {
FaroHelper.faro?.api.pushEvent('Request failed', { url });
FaroHelper.faro?.api.pushError(ex);
throw ex;
}
};

View file

@ -9,9 +9,9 @@ import { paths } from './autogenerated-api.types';
export const API_PROXY_PREFIX = 'api/plugin-proxy/grafana-oncall-app';
export const API_PATH_PREFIX = '/api/internal/v1';
const showApiError = (errorResponse: Response) => {
if (errorResponse.status >= 400 && errorResponse.status < 500) {
const text = formatBackendError(errorResponse.statusText);
const showApiError = (status: number, errorData: string | Record<string, unknown>) => {
if (status >= 400 && status < 500) {
const text = formatBackendError(errorData);
if (text) {
openErrorNotification(text);
}
@ -60,7 +60,7 @@ export const getCustomFetchFn =
faro.api.pushError(errorData);
span.end();
if (withGlobalErrorHandler) {
showApiError(res);
showApiError(res.status, errorData);
}
reject(res);
}
@ -76,7 +76,7 @@ export const getCustomFetchFn =
faro?.api.pushEvent('Request failed', { url });
faro?.api.pushError(errorData);
if (withGlobalErrorHandler) {
showApiError(res);
showApiError(res.status, errorData);
}
throw res;

View file

@ -73,9 +73,13 @@ class _EscalationChainsPage extends React.Component<EscalationChainsPageProps, E
modeToShowEscalationChainForm: EscalationChainFormMode.Create,
});
} else if (id) {
let escalationChain = await escalationChainStore
.loadItem(id, true)
.catch((error) => this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } }));
let escalationChain: EscalationChain;
try {
escalationChain = await escalationChainStore.loadItem(id, true);
} catch (error) {
this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } });
}
await escalationChainStore.updateEscalationChainDetails(id);
if (!escalationChain) {
@ -250,11 +254,12 @@ class _EscalationChainsPage extends React.Component<EscalationChainsPageProps, E
},
} = this.props;
this.setState({ escalationChainsFilters: filters, extraEscalationChains: undefined }, () => {
this.setState({ escalationChainsFilters: filters, extraEscalationChains: undefined }, async () => {
await this.applyFilters();
if (isOnMount && id) {
this.applyFilters().then(this.parseQueryParams);
this.parseQueryParams();
} else {
this.applyFilters().then(this.autoSelectEscalationChain);
this.autoSelectEscalationChain();
}
});
};
@ -412,9 +417,13 @@ class _EscalationChainsPage extends React.Component<EscalationChainsPageProps, E
(!extraEscalationChains ||
(extraEscalationChains && !extraEscalationChains.some((escalationChain) => escalationChain.id === id)))
) {
let escalationChain = await escalationChainStore
.loadItem(id, true)
.catch((error) => this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } }));
let escalationChain: EscalationChain;
try {
escalationChain = await escalationChainStore.loadItem(id, true);
} catch (error) {
this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } });
}
if (escalationChain) {
this.setState({ extraEscalationChains: [...(this.state.extraEscalationChains || []), escalationChain] }, () => {
@ -426,7 +435,7 @@ class _EscalationChainsPage extends React.Component<EscalationChainsPageProps, E
}
};
handleDeleteEscalationChain = () => {
handleDeleteEscalationChain = async () => {
const { store, history } = this.props;
const { escalationChainStore } = store;
const { selectedEscalationChain, extraEscalationChains } = this.state;
@ -435,24 +444,22 @@ class _EscalationChainsPage extends React.Component<EscalationChainsPageProps, E
.getSearchResult()
.findIndex((escalationChain: EscalationChain) => escalationChain.id === selectedEscalationChain);
escalationChainStore
.delete(selectedEscalationChain)
.then(this.applyFilters)
.then(() => {
if (extraEscalationChains) {
const newExtraEscalationChains = extraEscalationChains.filter(
(scalationChain) => scalationChain.id !== selectedEscalationChain
);
await escalationChainStore.delete(selectedEscalationChain);
await this.applyFilters();
this.setState({ extraEscalationChains: newExtraEscalationChains });
}
if (extraEscalationChains) {
const newExtraEscalationChains = extraEscalationChains.filter(
(scalationChain) => scalationChain.id !== selectedEscalationChain
);
const escalationChains = escalationChainStore.getSearchResult();
this.setState({ extraEscalationChains: newExtraEscalationChains });
}
const newSelected = escalationChains[index - 1] || escalationChains[0];
const escalationChains = escalationChainStore.getSearchResult();
history.push(`${PLUGIN_ROOT}/escalations/${newSelected?.id || ''}${window.location.search}`);
});
const newSelected = escalationChains[index - 1] || escalationChains[0];
history.push(`${PLUGIN_ROOT}/escalations/${newSelected?.id || ''}${window.location.search}`);
};
handleEscalationChainNameChange = (value: string) => {

View file

@ -101,7 +101,7 @@ class _IncidentPage extends React.Component<IncidentPageProps, IncidentPageState
}
}
update = () => {
update = async () => {
this.setState({ errorData: initErrorDataState() }); // reset wrong team error to false
const {
@ -111,9 +111,11 @@ class _IncidentPage extends React.Component<IncidentPageProps, IncidentPageState
},
} = this.props;
store.alertGroupStore
.getAlert(id)
.catch((error) => this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } }));
try {
await store.alertGroupStore.getAlert(id);
} catch (error) {
this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } });
}
};
render() {
@ -481,8 +483,10 @@ class _IncidentPage extends React.Component<IncidentPageProps, IncidentPageState
this.setState({ showAttachIncidentForm: true });
};
getUnattachClickHandler = (pk: ApiSchemas['AlertGroup']['pk']) =>
AlertGroupHelper.unattachAlert(pk).then(this.update);
getUnattachClickHandler = async (pk: ApiSchemas['AlertGroup']['pk']) => {
await AlertGroupHelper.unattachAlert(pk);
this.update();
};
renderTimeline = () => {
const {
@ -585,7 +589,7 @@ class _IncidentPage extends React.Component<IncidentPageProps, IncidentPageState
}
};
handleCreateResolutionNote = () => {
handleCreateResolutionNote = async () => {
const {
store,
match: {
@ -594,12 +598,10 @@ class _IncidentPage extends React.Component<IncidentPageProps, IncidentPageState
} = this.props;
const { resolutionNoteText } = this.state;
store.resolutionNotesStore
.createResolutionNote(id, resolutionNoteText)
.then(() => {
this.setState({ resolutionNoteText: '' });
})
.then(this.update);
await store.resolutionNotesStore.createResolutionNote(id, resolutionNoteText);
this.setState({ resolutionNoteText: '' });
await this.update();
};
getPlaceholderReplaceFn = (entity: any, history) => {

View file

@ -71,7 +71,7 @@ export const IncidentDropdown: FC<{
const [currentLoadingAction, setCurrentActionLoading] = useState<IncidentStatus>(undefined);
const [forcedOpenAction, setForcedOpenAction] = useState<string>(undefined);
const onClickFn = (
const onClickFn = async (
ev: React.SyntheticEvent<HTMLDivElement>,
actionName: string,
action: (value: SyntheticEvent | number) => Promise<void>,
@ -83,15 +83,12 @@ export const IncidentDropdown: FC<{
// set them to forcedOpen so that they do not close
setForcedOpenAction(actionName);
action(ev)
.then(() => {
// network request is done and succesful, close them
setForcedOpenAction(undefined);
})
.finally(() => {
// hide loading/disabled state
setIsLoading(false);
});
await action(ev);
// network request is done and succesful, close them
setForcedOpenAction(undefined);
// hide loading/disabled state
setIsLoading(false);
};
if (alert.status === IncidentStatus.Resolved) {
@ -199,15 +196,16 @@ export const IncidentDropdown: FC<{
placeholder={
currentLoadingAction === IncidentStatus.Silenced && isLoading ? 'Loading...' : 'Silence for'
}
onSelect={(value) => {
onSelect={async (value) => {
setIsLoading(true);
setForcedOpenAction(AlertAction.unResolve);
setCurrentActionLoading(IncidentStatus.Silenced);
onSilence(value).finally(() => {
setIsLoading(false);
setForcedOpenAction(undefined);
setCurrentActionLoading(undefined);
});
await onSilence(value);
setIsLoading(false);
setForcedOpenAction(undefined);
setCurrentActionLoading(undefined);
}}
/>
</div>

View file

@ -67,7 +67,7 @@ import { useStore } from 'state/useStore';
import { withMobXProviderContext } from 'state/withStore';
import { LocationHelper } from 'utils/LocationHelper';
import { UserActions } from 'utils/authorization/authorization';
import { PLUGIN_ROOT } from 'utils/consts';
import { GENERIC_ERROR, PLUGIN_ROOT } from 'utils/consts';
import { withDrawer } from 'utils/hoc';
import { useDrawer } from 'utils/hooks';
import { getItem, setItem } from 'utils/localStorage';
@ -588,31 +588,28 @@ class _IntegrationPage extends React.Component<IntegrationProps, IntegrationStat
{
isAddingRoute: true,
},
() => {
AlertReceiveChannelHelper.createChannelFilter({
alert_receive_channel: id,
filtering_term: NEW_ROUTE_DEFAULT,
filtering_term_type: 1, // non-regex
})
.then(async (channelFilter: ChannelFilter) => {
await alertReceiveChannelStore.fetchChannelFilters(id);
this.setState(
(prevState) => ({
isAddingRoute: false,
openRoutes: prevState.openRoutes.concat(channelFilter.id),
}),
() => this.forceUpdate()
);
openNotification('A new route has been added');
})
.catch((err) => {
const errors = get(err, 'response.data');
if (errors?.non_field_errors) {
openErrorNotification(errors.non_field_errors);
}
async () => {
try {
const channelFilter: ChannelFilter = await AlertReceiveChannelHelper.createChannelFilter({
alert_receive_channel: id,
filtering_term: NEW_ROUTE_DEFAULT,
filtering_term_type: 1, // non-regex
});
await alertReceiveChannelStore.fetchChannelFilters(id);
this.setState(
(prevState) => ({
isAddingRoute: false,
openRoutes: prevState.openRoutes.concat(channelFilter.id),
}),
() => this.forceUpdate()
);
openNotification('A new route has been added');
} catch (err) {
const errors = get(err, 'response.data');
if (errors?.non_field_errors) {
openErrorNotification(errors.non_field_errors);
}
}
}
);
};
@ -631,7 +628,8 @@ class _IntegrationPage extends React.Component<IntegrationProps, IntegrationStat
const channelFilterIds = alertReceiveChannelStore.channelFilterIds[id];
const onRouteDelete = async (routeId: string) => {
await alertReceiveChannelStore.deleteChannelFilter(routeId).then(() => this.forceUpdate());
await alertReceiveChannelStore.deleteChannelFilter(routeId);
this.forceUpdate();
openNotification('Route has been deleted');
};
@ -682,7 +680,7 @@ class _IntegrationPage extends React.Component<IntegrationProps, IntegrationStat
this.setState({ isEditRegexpRouteTemplateModalOpen: true, channelFilterIdForEdit: channelFilterId });
};
onUpdateRoutesCallback = (
onUpdateRoutesCallback = async (
{ route_template }: { route_template: string },
channelFilterId: ChannelFilter['id'],
filteringTermType?: number
@ -692,29 +690,26 @@ class _IntegrationPage extends React.Component<IntegrationProps, IntegrationStat
params: { id },
} = this.props.match;
alertReceiveChannelStore
.saveChannelFilter(channelFilterId, {
try {
const channelFilter: ChannelFilter = await alertReceiveChannelStore.saveChannelFilter(channelFilterId, {
filtering_term: route_template,
filtering_term_type: filteringTermType,
})
.then((channelFilter: ChannelFilter) => {
alertReceiveChannelStore.fetchChannelFilters(id, true).then(() => {
escalationPolicyStore.updateEscalationPolicies(channelFilter.escalation_chain);
});
this.setState({
isEditTemplateModalOpen: undefined,
});
LocationHelper.update({ template: undefined, routeId: undefined }, 'partial');
})
.catch((err) => {
const errors = get(err, 'response.data');
if (errors?.non_field_errors) {
openErrorNotification(errors.non_field_errors);
}
});
await alertReceiveChannelStore.fetchChannelFilters(id, true);
escalationPolicyStore.updateEscalationPolicies(channelFilter.escalation_chain);
this.setState({
isEditTemplateModalOpen: undefined,
});
LocationHelper.update({ template: undefined, routeId: undefined }, 'partial');
} catch (err) {
const errors = get(err, 'response.data');
if (errors?.non_field_errors) {
openErrorNotification(errors.non_field_errors);
}
}
};
onUpdateTemplatesCallback = (data) => {
onUpdateTemplatesCallback = async (data) => {
const {
store,
match: {
@ -722,21 +717,19 @@ class _IntegrationPage extends React.Component<IntegrationProps, IntegrationStat
},
} = this.props;
store.alertReceiveChannelStore
.saveTemplates(id, data)
.then(() => {
openNotification('The Alert templates have been updated');
this.setState({ isEditTemplateModalOpen: undefined });
this.setState({ isTemplateSettingsOpen: true });
LocationHelper.update({ template: undefined, routeId: undefined }, 'partial');
})
.catch((err) => {
if (err.response?.data?.length > 0) {
openErrorNotification(err.response.data);
} else {
openErrorNotification('Template is not valid. Please check your template and try again');
}
});
try {
await store.alertReceiveChannelStore.saveTemplates(id, data);
openNotification('The Alert templates have been updated');
this.setState({ isEditTemplateModalOpen: undefined });
this.setState({ isTemplateSettingsOpen: true });
LocationHelper.update({ template: undefined, routeId: undefined }, 'partial');
} catch (err) {
if (err.response?.data?.length > 0) {
openErrorNotification(err.response.data);
} else {
openErrorNotification('Template is not valid. Please check your template and try again');
}
}
};
openEditTemplateModal = (templateName, channelFilterId?: ChannelFilter['id']) => {
@ -758,10 +751,9 @@ class _IntegrationPage extends React.Component<IntegrationProps, IntegrationStat
}
};
onRemovalFn = (id: ApiSchemas['AlertReceiveChannel']['id']) => {
AlertReceiveChannelHelper.deleteAlertReceiveChannel(id).then(() =>
this.props.history.push(`${PLUGIN_ROOT}/integrations/`)
);
onRemovalFn = async (id: ApiSchemas['AlertReceiveChannel']['id']) => {
await AlertReceiveChannelHelper.deleteAlertReceiveChannel(id);
this.props.history.push(`${PLUGIN_ROOT}/integrations/`);
};
async loadData() {
@ -773,10 +765,15 @@ class _IntegrationPage extends React.Component<IntegrationProps, IntegrationStat
history,
} = this.props;
const promises = [];
const promises: Array<Promise<void | { [key: string]: { alerts_count: number; alert_groups_count: number } }>> = [];
const fetchItemAndLoadExtraData = async () => {
await alertReceiveChannelStore.fetchItemById(id);
await this.loadExtraData(id);
};
if (!alertReceiveChannelStore.items[id]) {
promises.push(alertReceiveChannelStore.fetchItemById(id).then(() => this.loadExtraData(id)));
promises.push(fetchItemAndLoadExtraData());
} else {
promises.push(this.loadExtraData(id));
}
@ -791,14 +788,16 @@ class _IntegrationPage extends React.Component<IntegrationProps, IntegrationStat
}
promises.push(alertReceiveChannelStore.fetchCountersForIntegration(id));
await Promise.all(promises)
.catch(() => {
if (!alertReceiveChannelStore.items[id]) {
// failed fetching the integration (most likely it's not existent)
history.push(`${PLUGIN_ROOT}/integrations`);
}
})
.finally(() => this.setState({ isLoading: false }));
try {
await Promise.all(promises);
} catch (_err) {
if (!alertReceiveChannelStore.items[id]) {
// failed fetching the integration (most likely it's not existent)
history.push(`${PLUGIN_ROOT}/integrations`);
}
} finally {
this.setState({ isLoading: false });
}
}
async loadExtraData(id: ApiSchemas['AlertReceiveChannel']['id']) {
@ -1102,30 +1101,32 @@ const IntegrationActions: React.FC<IntegrationActionsProps> = ({
}
}
function onIntegrationMigrate() {
AlertReceiveChannelHelper.migrateChannel(alertReceiveChannel.id)
.then(() => {
setConfirmModal(undefined);
openNotification('Integration has been successfully migrated.');
})
.then(() =>
Promise.all([
alertReceiveChannelStore.fetchItemById(alertReceiveChannel.id),
alertReceiveChannelStore.fetchTemplates(alertReceiveChannel.id),
])
)
.catch(() => openErrorNotification('An error has occurred. Please try again.'));
async function onIntegrationMigrate() {
try {
await AlertReceiveChannelHelper.migrateChannel(alertReceiveChannel.id);
setConfirmModal(undefined);
openNotification('Integration has been successfully migrated.');
await Promise.all([
alertReceiveChannelStore.fetchItemById(alertReceiveChannel.id),
alertReceiveChannelStore.fetchTemplates(alertReceiveChannel.id),
]);
} catch (_err) {
openErrorNotification(GENERIC_ERROR);
}
}
function showHeartbeatSettings() {
return alertReceiveChannel.is_available_for_integration_heartbeat;
}
function deleteIntegration() {
AlertReceiveChannelHelper.deleteAlertReceiveChannel(alertReceiveChannel.id)
.then(() => history.push(`${PLUGIN_ROOT}/integrations`))
.then(() => openNotification('Integration has been succesfully deleted.'))
.catch(() => openErrorNotification('An error has occurred. Please try again.'));
async function deleteIntegration() {
try {
await AlertReceiveChannelHelper.deleteAlertReceiveChannel(alertReceiveChannel.id);
history.push(`${PLUGIN_ROOT}/integrations`);
openNotification('Integration has been succesfully deleted.');
} catch (_err) {
openErrorNotification(GENERIC_ERROR);
}
}
function openIntegrationSettings() {

View file

@ -147,9 +147,11 @@ class _IntegrationsPage extends React.Component<IntegrationsProps, IntegrationsS
const isNewAlertReceiveChannel = id === 'new';
if (!isNewAlertReceiveChannel) {
alertReceiveChannel = await store.alertReceiveChannelStore
.fetchItemById(id, true)
.catch((error) => this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } }));
try {
alertReceiveChannel = await store.alertReceiveChannelStore.fetchItemById(id, true);
} catch (error) {
this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } });
}
}
if (alertReceiveChannel || isNewAlertReceiveChannel) {
@ -650,8 +652,9 @@ class _IntegrationsPage extends React.Component<IntegrationsProps, IntegrationsS
this.setState({ alertReceiveChannelIdToShowLabels: id });
};
handleDeleteAlertReceiveChannel = (alertReceiveChannelId: ApiSchemas['AlertReceiveChannel']['id']) => {
AlertReceiveChannelHelper.deleteAlertReceiveChannel(alertReceiveChannelId).then(this.applyFilters);
handleDeleteAlertReceiveChannel = async (alertReceiveChannelId: ApiSchemas['AlertReceiveChannel']['id']) => {
await AlertReceiveChannelHelper.deleteAlertReceiveChannel(alertReceiveChannelId);
this.applyFilters(false);
this.setState({ confirmationModal: undefined });
};
@ -667,17 +670,15 @@ class _IntegrationsPage extends React.Component<IntegrationsProps, IntegrationsS
const { alertReceiveChannelStore } = store;
const newPage = isOnMount ? store.filtersStore.currentTablePageNum[PAGE.Integrations] : 1;
return alertReceiveChannelStore
.fetchPaginatedItems({
filters: this.getFiltersBasedOnCurrentTab(),
page: newPage,
shouldFetchCounters: false,
invalidateFn: () => this.invalidateRequestFn(newPage),
})
.then(() => {
store.filtersStore.currentTablePageNum[PAGE.Integrations] = newPage;
LocationHelper.update({ p: newPage }, 'partial');
});
await alertReceiveChannelStore.fetchPaginatedItems({
filters: this.getFiltersBasedOnCurrentTab(),
page: newPage,
shouldFetchCounters: false,
invalidateFn: () => this.invalidateRequestFn(newPage),
});
store.filtersStore.currentTablePageNum[PAGE.Integrations] = newPage;
LocationHelper.update({ p: newPage }, 'partial');
};
debouncedUpdateIntegrations = debounce(this.applyFilters, FILTERS_DEBOUNCE_MS);

View file

@ -87,11 +87,11 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
if (isNewWebhook) {
this.setState({ outgoingWebhookId: id, outgoingWebhookAction: WebhookFormActionType.NEW });
} else if (id) {
await store.outgoingWebhookStore
.loadItem(id, true)
.catch((error) =>
this.setState({ errorData: { ...getWrongTeamResponseInfo(error) }, outgoingWebhookAction: undefined })
);
try {
await store.outgoingWebhookStore.loadItem(id, true);
} catch (error) {
this.setState({ errorData: { ...getWrongTeamResponseInfo(error) }, outgoingWebhookAction: undefined });
}
}
};
@ -229,11 +229,10 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
action={outgoingWebhookAction}
onUpdate={this.update}
onHide={this.handleOutgoingWebhookFormHide}
onDelete={() => {
this.onDeleteClick(outgoingWebhookId).then(() => {
this.setState({ outgoingWebhookId: undefined, outgoingWebhookAction: undefined });
history.push(`${PLUGIN_ROOT}/outgoing_webhooks`);
});
onDelete={async () => {
await this.onDeleteClick(outgoingWebhookId);
this.setState({ outgoingWebhookId: undefined, outgoingWebhookAction: undefined });
history.push(`${PLUGIN_ROOT}/outgoing_webhooks`);
}}
/>
)}
@ -257,16 +256,15 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
);
}
handleFiltersChange = (filters: FiltersValues, isOnMount: boolean) => {
handleFiltersChange = async (filters: FiltersValues, isOnMount: boolean) => {
const { store } = this.props;
const { outgoingWebhookStore } = store;
outgoingWebhookStore.updateItems(filters).then(() => {
if (isOnMount) {
this.parseQueryParams();
}
});
await outgoingWebhookStore.updateItems(filters);
if (isOnMount) {
this.parseQueryParams();
}
};
renderTeam(record: ApiSchemas['Webhook'], teams: any) {
@ -352,14 +350,17 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
);
}
onDeleteClick = (id: ApiSchemas['Webhook']['id']): Promise<void> => {
onDeleteClick = async (id: ApiSchemas['Webhook']['id']): Promise<void> => {
const { store } = this.props;
return store.outgoingWebhookStore
.delete(id)
.then(this.update)
.then(() => openNotification('Webhook has been removed'))
.catch(() => openNotification('Webook could not been removed'))
.finally(() => this.setState({ confirmationModal: undefined }));
try {
await store.outgoingWebhookStore.delete(id);
await this.update();
openNotification('Webhook has been removed');
} catch (_err) {
openNotification('Webook could not been removed');
} finally {
this.setState({ confirmationModal: undefined });
}
};
onEditClick = (id: ApiSchemas['Webhook']['id']) => {
@ -378,7 +379,7 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
);
};
onDisableWebhook = (id: ApiSchemas['Webhook']['id'], isEnabled: boolean) => {
onDisableWebhook = async (id: ApiSchemas['Webhook']['id'], isEnabled: boolean) => {
const {
store: { outgoingWebhookStore },
} = this.props;
@ -391,12 +392,14 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
// don't pass trigger_type to backend as it's not editable
delete data.trigger_type;
outgoingWebhookStore
.update(id, data)
.then(() => this.update())
.then(() => openNotification(`Webhook has been ${isEnabled ? 'enabled' : 'disabled'}`))
.catch(() => openErrorNotification('Webhook could not been updated'))
.finally(() => this.setState({ confirmationModal: undefined }));
try {
await outgoingWebhookStore.update(id, data);
await this.update();
openNotification(`Webhook has been ${isEnabled ? 'enabled' : 'disabled'}`);
} catch (_err) {
openErrorNotification('Webhook could not been updated');
}
this.setState({ confirmationModal: undefined });
};
onLastRunClick = (id: ApiSchemas['Webhook']['id']) => {

View file

@ -278,9 +278,9 @@ class _SchedulePage extends React.Component<SchedulePageProps, SchedulePageState
/>
<Rotations
scheduleId={scheduleId}
onCreate={this.handleCreateRotation}
onUpdate={this.handleUpdateRotation}
onDelete={this.handleDeleteRotation}
onCreate={this.refreshEventsAndClearPreview}
onUpdate={this.refreshEventsAndClearPreview}
onDelete={this.refreshEventsAndClearPreview}
shiftIdToShowRotationForm={shiftIdToShowRotationForm}
onShowRotationForm={this.handleShowRotationForm}
onShowOverrideForm={this.handleShowOverridesForm}
@ -291,9 +291,9 @@ class _SchedulePage extends React.Component<SchedulePageProps, SchedulePageState
/>
<ScheduleOverrides
scheduleId={scheduleId}
onCreate={this.handleCreateOverride}
onUpdate={this.handleUpdateOverride}
onDelete={this.handleDeleteOverride}
onCreate={this.refreshEventsAndClearPreview}
onUpdate={this.refreshEventsAndClearPreview}
onDelete={this.refreshEventsAndClearPreview}
shiftIdToShowRotationForm={shiftIdToShowOverridesForm}
onShowRotationForm={this.handleShowOverridesForm}
disabled={disabledOverrideForm}
@ -332,7 +332,7 @@ class _SchedulePage extends React.Component<SchedulePageProps, SchedulePageState
scheduleId={scheduleId}
params={shiftSwapParamsToShowForm}
onHide={this.handleHideShiftSwapForm}
onUpdate={this.handleUpdateShiftSwaps}
onUpdate={this.refreshEventsAndClearPreview}
/>
)}
</>
@ -341,7 +341,7 @@ class _SchedulePage extends React.Component<SchedulePageProps, SchedulePageState
);
}
update = () => {
update = async () => {
const {
store,
match: {
@ -351,9 +351,8 @@ class _SchedulePage extends React.Component<SchedulePageProps, SchedulePageState
const { scheduleStore } = store;
return scheduleStore.loadItem(scheduleId).then((schedule) => {
store.setPageTitle(schedule?.name);
});
const schedule = await scheduleStore.loadItem(scheduleId);
store.setPageTitle(schedule?.name);
};
handleShowRotationForm = (shiftId: Shift['id'] | 'new') => {
@ -368,7 +367,7 @@ class _SchedulePage extends React.Component<SchedulePageProps, SchedulePageState
});
};
handleNameChange = (value: string) => {
handleNameChange = async (value: string) => {
const {
store,
match: {
@ -378,68 +377,16 @@ class _SchedulePage extends React.Component<SchedulePageProps, SchedulePageState
const schedule = store.scheduleStore.items[scheduleId];
store.scheduleStore
.update(scheduleId, { type: schedule.type, name: value })
.then(() => store.scheduleStore.loadItem(scheduleId))
.then((schedule) => {
store.setPageTitle(schedule?.name);
});
await store.scheduleStore.update(scheduleId, { type: schedule.type, name: value });
const loadedSchedule = await store.scheduleStore.loadItem(scheduleId);
store.setPageTitle(loadedSchedule?.name);
};
handleCreateRotation = () => {
refreshEventsAndClearPreview = async () => {
const { store } = this.props;
store.scheduleStore.refreshEvents(this.scheduleId).then(() => {
store.scheduleStore.clearPreview();
});
};
handleCreateOverride = () => {
const { store } = this.props;
store.scheduleStore.refreshEvents(this.scheduleId).then(() => {
store.scheduleStore.clearPreview();
});
};
handleUpdateRotation = () => {
const { store } = this.props;
store.scheduleStore.refreshEvents(this.scheduleId).then(() => {
store.scheduleStore.clearPreview();
});
};
handleUpdateShiftSwaps = () => {
const { store } = this.props;
store.scheduleStore.refreshEvents(this.scheduleId).then(() => {
store.scheduleStore.clearPreview();
});
};
handleDeleteRotation = () => {
const { store } = this.props;
store.scheduleStore.refreshEvents(this.scheduleId).then(() => {
store.scheduleStore.clearPreview();
});
};
handleDeleteOverride = () => {
const { store } = this.props;
store.scheduleStore.refreshEvents(this.scheduleId).then(() => {
store.scheduleStore.clearPreview();
});
};
handleUpdateOverride = () => {
const { store } = this.props;
store.scheduleStore.refreshEvents(this.scheduleId).then(() => {
store.scheduleStore.clearPreview();
});
await store.scheduleStore.refreshEvents(this.scheduleId);
store.scheduleStore.clearPreview();
};
handleShedulePeriodTypeChange = (value: string) => {
@ -494,7 +441,7 @@ class _SchedulePage extends React.Component<SchedulePageProps, SchedulePageState
};
};
handleDelete = () => {
handleDelete = async () => {
const {
store,
match: {
@ -503,7 +450,8 @@ class _SchedulePage extends React.Component<SchedulePageProps, SchedulePageState
history,
} = this.props;
store.scheduleStore.delete(id).then(() => history.replace(`${PLUGIN_ROOT}/schedules`));
await store.scheduleStore.delete(id);
history.replace(`${PLUGIN_ROOT}/schedules`);
};
handleShowShiftSwapForm = (id: ShiftSwap['id'], params: Partial<ShiftSwap>) => {

View file

@ -330,8 +330,9 @@ class _SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSt
const { store } = this.props;
const { scheduleStore } = store;
return () => {
scheduleStore.delete(id).then(() => this.update());
return async () => {
await scheduleStore.delete(id);
this.update();
};
};
@ -376,13 +377,13 @@ class _SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSt
};
getUpdateRelatedEscalationChainsHandler = (scheduleId: Schedule['id']) => {
const { store } = this.props;
const { scheduleStore } = store;
const {
store: { scheduleStore },
} = this.props;
return () => {
scheduleStore.updateRelatedEscalationChains(scheduleId).then(() => {
this.forceUpdate();
});
return async () => {
await scheduleStore.updateRelatedEscalationChains(scheduleId);
this.forceUpdate();
};
};

View file

@ -33,11 +33,10 @@ class MSTeamsSettings extends Component<MSTeamsProps, MSTeamsState> {
this.update();
}
update = () => {
update = async () => {
const { store } = this.props;
store.msteamsChannelStore.getMSTeamsChannelVerificationCode().then((data) => {
this.setState({ verificationCode: data });
});
const data = await store.msteamsChannelStore.getMSTeamsChannelVerificationCode();
this.setState({ verificationCode: data });
store.msteamsChannelStore.updateItems();
};

View file

@ -48,19 +48,24 @@ class _SlackSettings extends Component<SlackProps, SlackState> {
};
componentDidMount() {
const { store } = this.props;
if (store.hasFeature(AppFeature.LiveSettings)) {
this.getSlackLiveSettings().then(() => {
this.update();
});
} else {
this.update();
}
this.onDidMount();
}
handleOpenSlackInstructions = () => {
onDidMount = async () => {
const { store } = this.props;
store.slackStore.installSlackIntegration().catch(showApiError);
if (store.hasFeature(AppFeature.LiveSettings)) {
await this.getSlackLiveSettings();
}
this.update();
};
handleOpenSlackInstructions = async () => {
const { store } = this.props;
try {
await store.slackStore.installSlackIntegration();
} catch (err) {
showApiError(err);
}
};
update = () => {
@ -228,14 +233,14 @@ class _SlackSettings extends Component<SlackProps, SlackState> {
);
};
removeSlackIntegration = () => {
removeSlackIntegration = async () => {
const { store } = this.props;
store.slackStore
.removeSlackIntegration()
.then(() => {
store.organizationStore.loadCurrentOrganization();
})
.catch(showApiError);
try {
await store.slackStore.removeSlackIntegration();
store.organizationStore.loadCurrentOrganization();
} catch (err) {
showApiError(err);
}
};
getSlackSettingsChangeHandler = (field: string) => {

View file

@ -40,13 +40,14 @@ const _CloudPage = observer((props: CloudPageProps) => {
const { history } = props;
useEffect(() => {
store.cloudStore.updateItems(page);
store.cloudStore.getCloudConnectionStatus().then((cloudStatus) => {
(async () => {
store.cloudStore.updateItems(page);
const cloudStatus = await store.cloudStore.getCloudConnectionStatus();
setCloudIsConnected(cloudStatus.cloud_connection_status);
setheartbeatEnabled(cloudStatus.cloud_heartbeat_enabled);
setheartbeatLink(cloudStatus.cloud_heartbeat_link);
setCloudNotificationsEnabled(cloudStatus.cloud_notifications_enabled);
});
})();
}, [cloudIsConnected, page, store.cloudStore]);
const { matched_users_count, results } = store.cloudStore.getSearchResult();
@ -70,21 +71,22 @@ const _CloudPage = observer((props: CloudPageProps) => {
const connectToCloud = async () => {
setShowConfirmationModal(false);
const globalSettingItem = await store.globalSettingStore.getGlobalSettingItemByName('GRAFANA_CLOUD_ONCALL_TOKEN');
store.globalSettingStore
.update(globalSettingItem?.id, { name: 'GRAFANA_CLOUD_ONCALL_TOKEN', value: cloudApiKey }, { sync_users: false })
.then(async (response) => {
if (response.error) {
setCloudIsConnected(false);
setApiKeyError(true);
openErrorNotification(response.error);
} else {
setCloudIsConnected(true);
syncUsers();
const heartbeatData: { link: string } = await store.cloudStore.getCloudHeartbeat();
setheartbeatLink(heartbeatData?.link);
}
await store.cloudStore.loadCloudConnectionStatus();
});
const response = await store.globalSettingStore.update(
globalSettingItem?.id,
{ name: 'GRAFANA_CLOUD_ONCALL_TOKEN', value: cloudApiKey },
{ sync_users: false }
);
if (response.error) {
setCloudIsConnected(false);
setApiKeyError(true);
openErrorNotification(response.error);
} else {
setCloudIsConnected(true);
syncUsers();
const heartbeatData: { link: string } = await store.cloudStore.getCloudHeartbeat();
setheartbeatLink(heartbeatData?.link);
}
await store.cloudStore.loadCloudConnectionStatus();
};
const syncUsers = async () => {

View file

@ -216,8 +216,9 @@ class LiveSettings extends React.Component<LiveSettingsProps, LiveSettingsState>
const { store } = this.props;
const { globalSettingStore } = store;
return (value: string | boolean) => {
globalSettingStore.update(id, { name, value: prepareForUpdate(value) }).then(this.update);
return async (value: string | boolean) => {
await globalSettingStore.update(id, { name, value: prepareForUpdate(value) });
this.update();
};
};
@ -239,8 +240,9 @@ class LiveSettings extends React.Component<LiveSettingsProps, LiveSettingsState>
const { store } = this.props;
const { globalSettingStore } = store;
return () => {
globalSettingStore.delete(item.id).then(this.update);
return async () => {
await globalSettingStore.delete(item.id);
this.update();
};
};
}

View file

@ -110,10 +110,13 @@ class Users extends React.Component<UsersProps, UsersState> {
} = this.props;
if (id) {
await (id === 'me'
? store.userStore.loadCurrentUser()
: store.userStore.fetchItemById({ userPk: String(id), skipErrorHandling: true })
).catch((error) => this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } }));
try {
await (id === 'me'
? store.userStore.loadCurrentUser()
: store.userStore.fetchItemById({ userPk: String(id), skipErrorHandling: true }));
} catch (error) {
this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } });
}
const userPkToEdit = String(id === 'me' ? store.userStore.currentUserPk : id);

View file

@ -6563,6 +6563,11 @@ eslint-plugin-jsdoc@^44.2.4:
semver "^7.5.1"
spdx-expression-parse "^3.0.1"
eslint-plugin-promise@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz#269a3e2772f62875661220631bd4dafcb4083816"
integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==
eslint-plugin-react-hooks@4.6.0, eslint-plugin-react-hooks@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3"