-
{m.moment.format('ddd D MMM')}
+
+ {m.moment.format('ddd D MMM')}
+
{m.moments.map((mm, j) => (
@@ -69,7 +73,7 @@ const TimelineMarks: FC = (props) => {
'weekday-time-title__hidden': i === 0 && j === 0,
})}
>
- {mm.format('HH:mm')}
+ {mm.format('HH:mm')}
))}
diff --git a/grafana-plugin/src/components/UserGroups/UserGroups.module.css b/grafana-plugin/src/components/UserGroups/UserGroups.module.css
index 178e3412..d48834b8 100644
--- a/grafana-plugin/src/components/UserGroups/UserGroups.module.css
+++ b/grafana-plugin/src/components/UserGroups/UserGroups.module.css
@@ -13,7 +13,6 @@
font-size: 12px;
line-height: 16px;
text-align: center;
- color: rgba(204, 204, 220, 0.4);
margin: 4px 0;
display: flex;
align-items: center;
@@ -27,7 +26,7 @@
display: block;
content: "";
flex-grow: 1;
- border-bottom: 1px solid rgba(204, 204, 220, 0.15);
+ border-bottom: var(--border-medium);
height: 0;
margin-right: 5px;
}
@@ -36,7 +35,7 @@
display: block;
content: "";
flex-grow: 1;
- border-bottom: 1px solid rgba(204, 204, 220, 0.15);
+ border-bottom: var(--border-medium);
height: 0;
margin-left: 5px;
}
@@ -69,9 +68,13 @@
background: var(--hover-selected-hardcoded);
}
-.delete-icon {
- /* display: none; */
+.icon {
display: block;
+ color: var(--always-gray);
+}
+
+.icon:hover {
+ color: white;
}
.user:hover .delete-icon {
diff --git a/grafana-plugin/src/components/UserGroups/UserGroups.tsx b/grafana-plugin/src/components/UserGroups/UserGroups.tsx
index affdad35..e7953645 100644
--- a/grafana-plugin/src/components/UserGroups/UserGroups.tsx
+++ b/grafana-plugin/src/components/UserGroups/UserGroups.tsx
@@ -5,6 +5,7 @@ import { arrayMoveImmutable } from 'array-move';
import cn from 'classnames/bind';
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
+import Text from 'components/Text/Text';
import RemoteSelect from 'containers/RemoteSelect/RemoteSelect';
import { User } from 'models/user/user.types';
@@ -23,7 +24,7 @@ interface UserGroupsProps {
const cx = cn.bind(styles);
-const DragHandle = () =>
;
+const DragHandle = () =>
;
const SortableHandleHoc = SortableHandle(DragHandle);
@@ -94,7 +95,7 @@ const UserGroups = (props: UserGroupsProps) => {
{renderUser(item.data)}
-
+
@@ -155,14 +156,16 @@ const SortableList = SortableContainer
(({ items, handleAddGro
) : isMultipleGroups ? (
- {item.data.name}
+
+ {item.data.name}
+
) : null
)}
{isMultipleGroups && items[items.length - 1]?.type === 'item' && (
- Add user group +
+ Add user group +
)}
diff --git a/grafana-plugin/src/components/UserTimezoneSelect/UserTimezoneSelect.tsx b/grafana-plugin/src/components/UserTimezoneSelect/UserTimezoneSelect.tsx
index aa3fabf7..83a4882f 100644
--- a/grafana-plugin/src/components/UserTimezoneSelect/UserTimezoneSelect.tsx
+++ b/grafana-plugin/src/components/UserTimezoneSelect/UserTimezoneSelect.tsx
@@ -90,7 +90,7 @@ const UserTimezoneSelect: FC = (props) => {
return (
-
+
);
};
diff --git a/grafana-plugin/src/containers/AlertRules/AlertRules.tsx b/grafana-plugin/src/containers/AlertRules/AlertRules.tsx
index 5c4d5996..10f48d9d 100644
--- a/grafana-plugin/src/containers/AlertRules/AlertRules.tsx
+++ b/grafana-plugin/src/containers/AlertRules/AlertRules.tsx
@@ -804,8 +804,8 @@ class AlertRules extends React.Component {
alertReceiveChannelStore.updateCounters();
openNotification(
- Demo alert was generated. Find it in the
-
"Incidents"
+ Demo alert was generated. Find it on the
+
"Alert Groups"
page and make sure it didn't freak out your colleagues 😉
);
@@ -821,8 +821,8 @@ class AlertRules extends React.Component {
alertReceiveChannelStore.sendDemoAlertToParticularRoute(id).then(() => {
openNotification(
- Demo alert was generated. Find it in the
-
"Incidents"
+ Demo alert was generated. Find it on the
+
"Alert Groups"
page and make sure it didn't freak out your colleagues 😉
);
diff --git a/grafana-plugin/src/containers/EscalationChainSteps/EscalationChainSteps.tsx b/grafana-plugin/src/containers/EscalationChainSteps/EscalationChainSteps.tsx
index 062b9b8d..8f9174aa 100644
--- a/grafana-plugin/src/containers/EscalationChainSteps/EscalationChainSteps.tsx
+++ b/grafana-plugin/src/containers/EscalationChainSteps/EscalationChainSteps.tsx
@@ -67,7 +67,7 @@ const EscalationChainSteps = observer((props: EscalationChainStepsProps) => {
// const STEP_COLORS = ['#52C41A', '#A0D911', '#FADB14', '#FAAD14', COLOR_RED];
const STEP_COLORS = ['#1A7F4B', '#33cc33', '#ffbf00', '#FF8000', COLOR_RED];
- const { alertReceiveChannelStore, escalationPolicyStore } = store;
+ const { escalationPolicyStore } = store;
const escalationPolicy = escalationPolicyStore.items[escalationPolicyId];
diff --git a/grafana-plugin/src/containers/GSelect/GSelect.tsx b/grafana-plugin/src/containers/GSelect/GSelect.tsx
index a41c31ce..07e8c562 100644
--- a/grafana-plugin/src/containers/GSelect/GSelect.tsx
+++ b/grafana-plugin/src/containers/GSelect/GSelect.tsx
@@ -32,6 +32,7 @@ interface GSelectProps {
showWarningIfEmptyValue?: boolean;
showError?: boolean;
nullItemName?: string;
+ fromOrganization?: boolean;
filterOptions?: (id: any) => boolean;
dropdownRender?: (menu: ReactElement) => ReactElement;
getOptionLabel?: (item: SelectableValue) => React.ReactNode;
@@ -59,6 +60,7 @@ const GSelect = observer((props: GSelectProps) => {
showWarningIfEmptyValue = false,
getDescription,
filterOptions,
+ fromOrganization,
} = props;
const store = useStore();
@@ -123,7 +125,7 @@ const GSelect = observer((props: GSelectProps) => {
(values as string[]).forEach((value: string) => {
if (!isNil(value) && !model.items[value] && model.updateItem) {
- model.updateItem(value);
+ model.updateItem(value, fromOrganization);
}
});
}, [value]);
diff --git a/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx b/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx
index 1ca31a27..b2984341 100644
--- a/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx
+++ b/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx
@@ -1,12 +1,10 @@
import React from 'react';
-import { SelectableValue } from '@grafana/data';
-import { HorizontalGroup, Icon, IconButton, Label, Tooltip } from '@grafana/ui';
+import { Icon, Label, Tooltip } from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';
import ReactDOM from 'react-dom';
-import Avatar from 'components/Avatar/Avatar';
import PluginLink from 'components/PluginLink/PluginLink';
import GSelect from 'containers/GSelect/GSelect';
import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl';
@@ -18,11 +16,14 @@ import styles from './GrafanaTeamSelect.module.css';
const cx = cn.bind(styles);
-interface GrafanaTeamSelectProps {}
+interface GrafanaTeamSelectProps {
+ currentPage: string;
+}
const GrafanaTeamSelect = observer((props: GrafanaTeamSelectProps) => {
const store = useStore();
+ const { currentPage } = props;
const { userStore, grafanaTeamStore } = store;
const grafanaTeams = grafanaTeamStore.getSearchResult();
const user = userStore.currentUser;
@@ -33,7 +34,15 @@ const GrafanaTeamSelect = observer((props: GrafanaTeamSelectProps) => {
const onTeamChange = async (teamId: GrafanaTeam['id']) => {
await userStore.updateCurrentUser({ current_team: teamId });
- window.location.reload();
+
+ const queryParams = new URLSearchParams();
+ queryParams.set('page', mapCurrentPage());
+ window.location.search = queryParams.toString();
+
+ function mapCurrentPage() {
+ if (currentPage === 'incident') {return 'incidents'}
+ return currentPage
+ }
};
return document.getElementsByClassName('page-header__inner')[0]
diff --git a/grafana-plugin/src/containers/OutgoingWebhookForm/OutgoingWebhookForm.config.ts b/grafana-plugin/src/containers/OutgoingWebhookForm/OutgoingWebhookForm.config.ts
index 76fecf4f..836f1bcf 100644
--- a/grafana-plugin/src/containers/OutgoingWebhookForm/OutgoingWebhookForm.config.ts
+++ b/grafana-plugin/src/containers/OutgoingWebhookForm/OutgoingWebhookForm.config.ts
@@ -34,7 +34,7 @@ export const form: { name: string; fields: FormItem[] } = {
},
{
name: 'data',
- getDisabled: (form_data) => Boolean(form_data.forward_whole_payload),
+ getDisabled: (form_data) => Boolean(form_data?.forward_whole_payload),
type: FormItemType.TextArea,
description: 'Available variables: {{ alert_payload }}, {{ alert_group_id }}',
extra: {
diff --git a/grafana-plugin/src/containers/OutgoingWebhookForm/OutgoingWebhookForm.tsx b/grafana-plugin/src/containers/OutgoingWebhookForm/OutgoingWebhookForm.tsx
index 8385d0f8..76891c47 100644
--- a/grafana-plugin/src/containers/OutgoingWebhookForm/OutgoingWebhookForm.tsx
+++ b/grafana-plugin/src/containers/OutgoingWebhookForm/OutgoingWebhookForm.tsx
@@ -1,19 +1,15 @@
-import React, { useCallback, useState } from 'react';
+import React, { useCallback } from 'react';
-import { Button, Drawer, Input, Modal } from '@grafana/ui';
+import { Button, Drawer } from '@grafana/ui';
import cn from 'classnames/bind';
-import { get } from 'lodash-es';
import { observer } from 'mobx-react';
-import Emoji from 'react-emoji-render';
import GForm from 'components/GForm/GForm';
-import IntegrationLogo from 'components/IntegrationLogo/IntegrationLogo';
import Text from 'components/Text/Text';
import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl';
import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types';
import { useStore } from 'state/useStore';
import { UserAction } from 'state/userAction';
-import { openErrorNotification } from 'utils';
import { form } from './OutgoingWebhookForm.config';
diff --git a/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx b/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx
index 264315b3..e199bf02 100644
--- a/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx
+++ b/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx
@@ -11,12 +11,8 @@ import {
Label,
Legend,
LoadingPlaceholder,
- Icon,
- Alert,
- Modal,
} from '@grafana/ui';
import cn from 'classnames/bind';
-import CopyToClipboard from 'react-copy-to-clipboard';
import { OnCallAppSettings } from 'types';
import Block from 'components/GBlock/Block';
@@ -28,6 +24,8 @@ import { createGrafanaToken, getPluginSyncStatus, startPluginSync, updateGrafana
import { GRAFANA_LICENSE_OSS } from 'utils/consts';
import { getItem, setItem } from 'utils/localStorage';
+import { constructSyncErrorMessage, constructErrorActionMessage } from './helpers';
+
import styles from './PluginConfigPage.module.css';
const cx = cn.bind(styles);
@@ -45,6 +43,8 @@ export const PluginConfigPage = (props: Props) => {
const [isSelfHostedInstall, setIsSelfHostedInstall] = useState(true);
const [retrySync, setRetrySync] = useState(false);
+ const INVALID_INVITE_TOKEN_ERROR_MSG = `It seems like your invite token may be invalid. ${constructErrorActionMessage('generating a new invite token')}`;
+
const setupPlugin = useCallback(async () => {
setItem('onCallApiUrl', onCallApiUrl);
setItem('grafanaUrl', grafanaUrl);
@@ -129,25 +129,37 @@ export const PluginConfigPage = (props: Props) => {
}, []);
const handleSyncException = useCallback((e) => {
+ const buildErrMsg = (msg: string): string =>
+ constructSyncErrorMessage(msg, plugin.meta.jsonData.onCallApiUrl);
+
if (plugin.meta.jsonData?.onCallApiUrl) {
- let statusMessage = plugin.meta.jsonData.onCallApiUrl + '\n' + e + ', retry or check settings & re-initialize.';
- if (e.response.status == 404) {
- statusMessage += '\nIf Grafana OnCall was just installed, restart Grafana for OnCall routes to be available.';
+ const { status: statusCode } = e.response;
+
+ let statusMessage: string;
+
+ if (statusCode == 403) {
+ statusMessage = buildErrMsg(INVALID_INVITE_TOKEN_ERROR_MSG);
+ } else if (statusCode === 404) {
+ statusMessage = buildErrMsg('If Grafana OnCall was just installed, restart Grafana for OnCall routes to be available.');
+ } else if (statusCode === 502) {
+ statusMessage = buildErrMsg(`Unable to communicate with either the Grafana API, or Grafana OnCall engine API. ${constructErrorActionMessage('verify that the API URLs that you entered are correct')}`);
+ } else {
+ statusMessage = buildErrMsg(`An unknown error occured. ${constructErrorActionMessage()}. If the error still occurs please reach out to support.`)
}
setPluginStatusMessage(statusMessage);
setRetrySync(true);
} else {
- setPluginStatusMessage('OnCall has not been setup, configure & initialize below.');
+ setPluginStatusMessage(buildErrMsg('OnCall has not been setup, configure & initialize below.'));
}
setPluginStatusOk(false);
setPluginConfigLoading(false);
}, []);
- const finishSync = useCallback((get_sync_response) => {
- if (get_sync_response.token_ok) {
+ const finishSync = useCallback((getSyncResponse) => {
+ if (getSyncResponse.token_ok) {
const versionInfo =
- get_sync_response.version && get_sync_response.license
- ? ` (${get_sync_response.license}, ${get_sync_response.version})`
+ getSyncResponse.version && getSyncResponse.license
+ ? ` (${getSyncResponse.license}, ${getSyncResponse.version})`
: '';
let pluginStatusMessage = `Connected to OnCall${versionInfo}\n - OnCall URL: ${plugin.meta.jsonData.onCallApiUrl}\n`
@@ -159,9 +171,8 @@ export const PluginConfigPage = (props: Props) => {
setIsSelfHostedInstall(plugin.meta.jsonData?.license === GRAFANA_LICENSE_OSS);
setPluginStatusOk(true);
} else {
- setPluginStatusMessage(
- `OnCall failed to connect to this grafana via: ${plugin.meta.jsonData.grafanaUrl} check URL, network, and API key.`
- );
+ setPluginStatusMessage(constructSyncErrorMessage(INVALID_INVITE_TOKEN_ERROR_MSG,
+ plugin.meta.jsonData.grafanaUrl));
setRetrySync(true);
}
setPluginConfigLoading(false);
@@ -221,14 +232,10 @@ export const PluginConfigPage = (props: Props) => {
)}
{'Plugin <-> backend connection status'}
- {pluginStatusMessage}
+ {pluginStatusMessage}
- {/* {'Plugin <-> backend connection status'}
-
- {pluginStatusMessage}
-
*/}
{retrySync && (