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:
parent
28ed2ddf61
commit
8187dfb595
53 changed files with 780 additions and 758 deletions
|
|
@ -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',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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>) => {
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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']) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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']) => {
|
||||
|
|
|
|||
|
|
@ -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>) => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue