From 2141de74421fbe82edc7dc9285f2dd194ff6bba6 Mon Sep 17 00:00:00 2001
From: Maxim
Date: Fri, 14 Jul 2023 17:55:45 +0300
Subject: [PATCH 1/7] bring heartbeats back to UI
---
.../IntegrationHeartbeatForm.tsx | 48 +++++++--------
.../alert_receive_channel.ts | 60 +++++++------------
.../src/pages/integration/Integration.tsx | 17 ++----
3 files changed, 52 insertions(+), 73 deletions(-)
diff --git a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
index ab9b40d6c9..f8efcd88ab 100644
--- a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
+++ b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
@@ -27,16 +27,16 @@ const IntegrationHeartbeatForm = observer(({ alertReceveChannelId, onClose }: In
const { heartbeatStore, alertReceiveChannelStore } = useStore();
const alertReceiveChannel = alertReceiveChannelStore.items[alertReceveChannelId];
+ const heartbeatId = alertReceiveChannelStore.alertReceiveChannelToHeartbeat[alertReceiveChannel.id];
+ const heartbeat = heartbeatStore.items[heartbeatId];
useEffect(() => {
heartbeatStore.updateTimeoutOptions();
- }, [heartbeatStore]);
+ }, []);
useEffect(() => {
- if (alertReceiveChannel.heartbeat) {
- setInterval(alertReceiveChannel.heartbeat.timeout_seconds);
- }
- }, [alertReceiveChannel]);
+ setInterval(heartbeat.timeout_seconds);
+ }, [heartbeat]);
const timeoutOptions = heartbeatStore.timeoutOptions;
@@ -66,22 +66,30 @@ const IntegrationHeartbeatForm = observer(({ alertReceveChannelId, onClose }: In
-
-
+
+ {/*
+ To send periodic heartbeat alerts from to OnCall, do
+ the following:
+
+
*/}
@@ -91,24 +99,14 @@ const IntegrationHeartbeatForm = observer(({ alertReceveChannelId, onClose }: In
);
async function onSave() {
- const heartbeat = alertReceiveChannel.heartbeat;
-
- if (heartbeat) {
- await heartbeatStore.saveHeartbeat(heartbeat.id, {
- alert_receive_channel: heartbeat.alert_receive_channel,
- timeout_seconds: interval,
- });
-
- onClose();
- } else {
- await heartbeatStore.createHeartbeat(alertReceveChannelId, {
- timeout_seconds: interval,
- });
+ await heartbeatStore.saveHeartbeat(heartbeat.id, {
+ alert_receive_channel: heartbeat.alert_receive_channel,
+ timeout_seconds: interval,
+ });
- onClose();
- }
+ onClose();
- await alertReceiveChannelStore.updateItem(alertReceveChannelId);
+ await alertReceiveChannelStore.loadItem(alertReceveChannelId);
}
});
diff --git a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts
index fb81d5783b..67b2c8d309 100644
--- a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts
+++ b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts
@@ -91,11 +91,14 @@ export class AlertReceiveChannelStore extends BaseStore {
async loadItem(id: AlertReceiveChannel['id'], skipErrorHandling = false): Promise {
const alertReceiveChannel = await this.getById(id, skipErrorHandling);
+ // @ts-ignore
this.items = {
...this.items,
- [id]: alertReceiveChannel,
+ [id]: omit(alertReceiveChannel, 'heartbeat'),
};
+ this.populateHearbeats([alertReceiveChannel]);
+
return alertReceiveChannel;
}
@@ -118,31 +121,7 @@ export class AlertReceiveChannelStore extends BaseStore {
this.searchResult = results.map((item: AlertReceiveChannel) => item.id);
- const heartbeats = results.reduce((acc: any, alertReceiveChannel: AlertReceiveChannel) => {
- if (alertReceiveChannel.heartbeat) {
- acc[alertReceiveChannel.heartbeat.id] = alertReceiveChannel.heartbeat;
- }
-
- return acc;
- }, {});
-
- this.rootStore.heartbeatStore.items = {
- ...this.rootStore.heartbeatStore.items,
- ...heartbeats,
- };
-
- const alertReceiveChannelToHeartbeat = results.reduce((acc: any, alertReceiveChannel: AlertReceiveChannel) => {
- if (alertReceiveChannel.heartbeat) {
- acc[alertReceiveChannel.id] = alertReceiveChannel.heartbeat.id;
- }
-
- return acc;
- }, {});
-
- this.alertReceiveChannelToHeartbeat = {
- ...this.alertReceiveChannelToHeartbeat,
- ...alertReceiveChannelToHeartbeat,
- };
+ this.populateHearbeats(results);
this.updateCounters();
@@ -170,7 +149,15 @@ export class AlertReceiveChannelStore extends BaseStore {
results: results.map((item: AlertReceiveChannel) => item.id),
};
- const heartbeats = results.reduce((acc: any, alertReceiveChannel: AlertReceiveChannel) => {
+ this.populateHearbeats(results);
+
+ this.updateCounters();
+
+ return results;
+ }
+
+ populateHearbeats(alertReceiveChannels: AlertReceiveChannel[]) {
+ const heartbeats = alertReceiveChannels.reduce((acc: any, alertReceiveChannel: AlertReceiveChannel) => {
if (alertReceiveChannel.heartbeat) {
acc[alertReceiveChannel.heartbeat.id] = alertReceiveChannel.heartbeat;
}
@@ -183,22 +170,21 @@ export class AlertReceiveChannelStore extends BaseStore {
...heartbeats,
};
- const alertReceiveChannelToHeartbeat = results.reduce((acc: any, alertReceiveChannel: AlertReceiveChannel) => {
- if (alertReceiveChannel.heartbeat) {
- acc[alertReceiveChannel.id] = alertReceiveChannel.heartbeat.id;
- }
+ const alertReceiveChannelToHeartbeat = alertReceiveChannels.reduce(
+ (acc: any, alertReceiveChannel: AlertReceiveChannel) => {
+ if (alertReceiveChannel.heartbeat) {
+ acc[alertReceiveChannel.id] = alertReceiveChannel.heartbeat.id;
+ }
- return acc;
- }, {});
+ return acc;
+ },
+ {}
+ );
this.alertReceiveChannelToHeartbeat = {
...this.alertReceiveChannelToHeartbeat,
...alertReceiveChannelToHeartbeat,
};
-
- this.updateCounters();
-
- return results;
}
@action
diff --git a/grafana-plugin/src/pages/integration/Integration.tsx b/grafana-plugin/src/pages/integration/Integration.tsx
index 579d5c25c6..51b52057a6 100644
--- a/grafana-plugin/src/pages/integration/Integration.tsx
+++ b/grafana-plugin/src/pages/integration/Integration.tsx
@@ -726,7 +726,7 @@ const IntegrationActions: React.FC = ({
alertReceiveChannel,
changeIsTemplateSettingsOpen,
}) => {
- const { alertReceiveChannelStore, heartbeatStore } = useStore();
+ const { alertReceiveChannelStore } = useStore();
const history = useHistory();
@@ -927,9 +927,7 @@ const IntegrationActions: React.FC = ({
);
function showHeartbeatSettings() {
- const heartbeatId = alertReceiveChannelStore.alertReceiveChannelToHeartbeat[alertReceiveChannel.id];
- const heartbeat = heartbeatStore.items[heartbeatId];
- return !!heartbeat?.last_heartbeat_time_verbal;
+ return alertReceiveChannel.is_available_for_integration_heartbeat;
}
function deleteIntegration() {
@@ -1159,22 +1157,19 @@ const IntegrationHeader: React.FC = ({
const heartbeatId = alertReceiveChannelStore.alertReceiveChannelToHeartbeat[alertReceiveChannel.id];
const heartbeat = heartbeatStore.items[heartbeatId];
- const heartbeatStatus = Boolean(heartbeat?.status);
-
- if (
- !alertReceiveChannel.is_available_for_integration_heartbeat ||
- !alertReceiveChannel.heartbeat?.last_heartbeat_time_verbal
- ) {
+ if (!alertReceiveChannel.is_available_for_integration_heartbeat || !heartbeat?.last_heartbeat_time_verbal) {
return null;
}
+ const heartbeatStatus = Boolean(heartbeat?.status);
+
return (
: }
- tooltipTitle={`Last heartbeat: ${alertReceiveChannel.heartbeat?.last_heartbeat_time_verbal}`}
+ tooltipTitle={`Last heartbeat: ${heartbeat?.last_heartbeat_time_verbal}`}
tooltipContent={undefined}
/>
);
From 4c71c959f0e82950639cbc92da034aca233362e4 Mon Sep 17 00:00:00 2001
From: Maxim
Date: Mon, 17 Jul 2023 11:04:59 +0300
Subject: [PATCH 2/7] fix heartbeat rendering
---
.../IntegrationHeartbeatForm.module.scss | 8 ++++++++
.../IntegrationHeartbeatForm.tsx | 13 ++++++++-----
.../alert_receive_channel/alert_receive_channel.ts | 9 ++++-----
.../src/pages/integrations/Integrations.tsx | 14 +++++---------
4 files changed, 25 insertions(+), 19 deletions(-)
create mode 100644 grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.module.scss
diff --git a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.module.scss b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.module.scss
new file mode 100644
index 0000000000..d5b6e3de14
--- /dev/null
+++ b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.module.scss
@@ -0,0 +1,8 @@
+.instruction {
+ ol,
+ ul {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ }
+}
diff --git a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
index f8efcd88ab..15a250f309 100644
--- a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
+++ b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
@@ -4,6 +4,7 @@ import { SelectableValue } from '@grafana/data';
import { Button, Drawer, Field, HorizontalGroup, Select, VerticalGroup } from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';
+import Emoji from 'react-emoji-render';
import IntegrationInputField from 'components/IntegrationInputField/IntegrationInputField';
import Text from 'components/Text/Text';
@@ -14,7 +15,9 @@ import { useStore } from 'state/useStore';
import { withMobXProviderContext } from 'state/withStore';
import { UserActions } from 'utils/authorization';
-const cx = cn.bind({});
+import styles from './IntegrationHeartbeatForm.module.scss';
+
+const cx = cn.bind(styles);
interface IntegrationHeartbeatFormProps {
alertReceveChannelId: AlertReceiveChannel['id'];
@@ -71,7 +74,7 @@ const IntegrationHeartbeatForm = observer(({ alertReceveChannelId, onClose }: In
- {/*
+
To send periodic heartbeat alerts from to OnCall, do
the following:
-
*/}
+
diff --git a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts
index 67b2c8d309..01aeba2837 100644
--- a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts
+++ b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts
@@ -119,10 +119,10 @@ export class AlertReceiveChannelStore extends BaseStore {
),
};
- this.searchResult = results.map((item: AlertReceiveChannel) => item.id);
-
this.populateHearbeats(results);
+ this.searchResult = results.map((item: AlertReceiveChannel) => item.id);
+
this.updateCounters();
return results;
@@ -143,14 +143,13 @@ export class AlertReceiveChannelStore extends BaseStore {
),
};
- this.paginatedSearchResult = results.map((item: AlertReceiveChannel) => item.id);
+ this.populateHearbeats(results);
+
this.paginatedSearchResult = {
count,
results: results.map((item: AlertReceiveChannel) => item.id),
};
- this.populateHearbeats(results);
-
this.updateCounters();
return results;
diff --git a/grafana-plugin/src/pages/integrations/Integrations.tsx b/grafana-plugin/src/pages/integrations/Integrations.tsx
index 8799b3b8be..f5a243f569 100644
--- a/grafana-plugin/src/pages/integrations/Integrations.tsx
+++ b/grafana-plugin/src/pages/integrations/Integrations.tsx
@@ -26,9 +26,7 @@ import RemoteFilters from 'containers/RemoteFilters/RemoteFilters';
import TeamName from 'containers/TeamName/TeamName';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
import { HeartIcon, HeartRedIcon } from 'icons';
-import { AlertReceiveChannelStore } from 'models/alert_receive_channel/alert_receive_channel';
import { AlertReceiveChannel, MaintenanceMode } from 'models/alert_receive_channel/alert_receive_channel.types';
-import { HeartbeatStore } from 'models/heartbeat/heartbeat';
import IntegrationHelper from 'pages/integration/Integration.helper';
import { PageProps, WithStoreProps } from 'state/types';
import { withMobXProviderContext } from 'state/withStore';
@@ -128,7 +126,7 @@ class Integrations extends React.Component
render() {
const { store, query } = this.props;
const { alertReceiveChannelId, page, confirmationModal } = this.state;
- const { grafanaTeamStore, alertReceiveChannelStore, heartbeatStore } = store;
+ const { grafanaTeamStore, alertReceiveChannelStore } = store;
const { count, results } = alertReceiveChannelStore.getPaginatedSearchResult();
@@ -162,7 +160,7 @@ class Integrations extends React.Component
width: '5%',
title: 'Heartbeat',
key: 'heartbeat',
- render: (item: AlertReceiveChannel) => this.renderHeartbeat(item, alertReceiveChannelStore, heartbeatStore),
+ render: (item: AlertReceiveChannel) => this.renderHeartbeat(item),
},
{
width: '15%',
@@ -345,11 +343,9 @@ class Integrations extends React.Component
);
}
- renderHeartbeat(
- item: AlertReceiveChannel,
- alertReceiveChannelStore: AlertReceiveChannelStore,
- heartbeatStore: HeartbeatStore
- ) {
+ renderHeartbeat(item: AlertReceiveChannel) {
+ const { store } = this.props;
+ const { alertReceiveChannelStore, heartbeatStore } = store;
const alertReceiveChannel = alertReceiveChannelStore.items[item.id];
const heartbeatId = alertReceiveChannelStore.alertReceiveChannelToHeartbeat[alertReceiveChannel.id];
From 6e3ce044421ede342aae9e7d1adc4806927044f2 Mon Sep 17 00:00:00 2001
From: Maxim
Date: Tue, 18 Jul 2023 16:42:13 +0300
Subject: [PATCH 3/7] tune heartbeat form a bit
---
.../integrations/heartbeat.test.ts | 46 +++++++
.../IntegrationHeartbeatForm.tsx | 118 +++++++++---------
.../src/pages/integration/Integration.tsx | 6 +-
3 files changed, 113 insertions(+), 57 deletions(-)
create mode 100644 grafana-plugin/integration-tests/integrations/heartbeat.test.ts
diff --git a/grafana-plugin/integration-tests/integrations/heartbeat.test.ts b/grafana-plugin/integration-tests/integrations/heartbeat.test.ts
new file mode 100644
index 0000000000..d6fe166de6
--- /dev/null
+++ b/grafana-plugin/integration-tests/integrations/heartbeat.test.ts
@@ -0,0 +1,46 @@
+import { test, Page, Locator } from '../fixtures';
+
+import { generateRandomValue, selectDropdownValue } from '../utils/forms';
+import { createIntegration } from '../utils/integrations';
+
+test.describe("updating an integration's heartbeat interval works", () => {
+ test.slow();
+
+ const _openIntegrationSettingsPopup = async (page: Page): Promise => {
+ const integrationSettingsPopupElement = page.getByTestId('integration-settings-context-menu');
+ await integrationSettingsPopupElement.click();
+ return integrationSettingsPopupElement;
+ };
+
+ const changeHeartbeatInterval = async (page: Page, heartbeatIntervalValue: string): Promise => {
+ const heartbeatSettingsForm = page.getByTestId('heartbeat-settings-form');
+
+ await selectDropdownValue({
+ page,
+ startingLocator: heartbeatSettingsForm,
+ selectType: 'grafanaSelect',
+ placeholderText: 'Heartbeat Timeout',
+ value: heartbeatIntervalValue,
+ optionExactMatch: false,
+ });
+
+ await heartbeatSettingsForm.getByTestId('update-heartbeat').click();
+ };
+
+ test('"change heartbeat interval', async ({ adminRolePage: { page } }) => {
+ const integrationName = generateRandomValue();
+ await createIntegration(page, integrationName);
+
+ const integrationSettingsPopupElement = await _openIntegrationSettingsPopup(page);
+
+ await integrationSettingsPopupElement.click();
+
+ await page.getByTestId('integration-heartbeat-settings').click();
+
+ await changeHeartbeatInterval(page, '1 day');
+
+ const heartbeatSettingsForm = page.getByTestId('heartbeat-settings-form');
+
+ await heartbeatSettingsForm.getByTestId('close-heartbeat-form').click();
+ });
+});
diff --git a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
index 15a250f309..9880f765b7 100644
--- a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
+++ b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
@@ -6,6 +6,7 @@ import cn from 'classnames/bind';
import { observer } from 'mobx-react';
import Emoji from 'react-emoji-render';
+import Collapse from 'components/Collapse/Collapse';
import IntegrationInputField from 'components/IntegrationInputField/IntegrationInputField';
import Text from 'components/Text/Text';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
@@ -13,6 +14,7 @@ import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_
import { SelectOption } from 'state/types';
import { useStore } from 'state/useStore';
import { withMobXProviderContext } from 'state/withStore';
+import { openNotification } from 'utils';
import { UserActions } from 'utils/authorization';
import styles from './IntegrationHeartbeatForm.module.scss';
@@ -45,69 +47,73 @@ const IntegrationHeartbeatForm = observer(({ alertReceveChannelId, onClose }: In
return (
-
-
- A heartbeat acts as a healthcheck for alert group monitoring. You can configure you monitoring to regularly
- send alerts to the heartbeat endpoint. If OnCall doen't receive one of these alerts, it will create an new
- alert group and escalate it
-
-
-
-
-
-
-
-
+
+
+
+
+
-
+
);
async function onSave() {
- await heartbeatStore.saveHeartbeat(heartbeat.id, {
- alert_receive_channel: heartbeat.alert_receive_channel,
- timeout_seconds: interval,
- });
-
- onClose();
+ await heartbeatStore
+ .saveHeartbeat(heartbeat.id, {
+ alert_receive_channel: heartbeat.alert_receive_channel,
+ timeout_seconds: interval,
+ })
+ .then(() => openNotification('Heartbeat settings have been updated'));
await alertReceiveChannelStore.loadItem(alertReceveChannelId);
}
diff --git a/grafana-plugin/src/pages/integration/Integration.tsx b/grafana-plugin/src/pages/integration/Integration.tsx
index 51b52057a6..bb5d32fd84 100644
--- a/grafana-plugin/src/pages/integration/Integration.tsx
+++ b/grafana-plugin/src/pages/integration/Integration.tsx
@@ -823,7 +823,11 @@ const IntegrationActions: React.FC = ({
{showHeartbeatSettings() && (
- setIsHeartbeatFormOpen(true)}>
+
setIsHeartbeatFormOpen(true)}
+ data-testid="integration-heartbeat-settings"
+ >
Heartbeat Settings
From fea5507a8b9e09e61e45edd064882afc97f5b43b Mon Sep 17 00:00:00 2001
From: Maxim
Date: Wed, 19 Jul 2023 13:23:19 +0300
Subject: [PATCH 4/7] add heartbeat integration test
---
.../integrations/heartbeat.test.ts | 55 ++++++++++++++-----
.../IntegrationHeartbeatForm.tsx | 4 +-
.../src/pages/integration/Integration.tsx | 1 +
3 files changed, 46 insertions(+), 14 deletions(-)
diff --git a/grafana-plugin/integration-tests/integrations/heartbeat.test.ts b/grafana-plugin/integration-tests/integrations/heartbeat.test.ts
index d6fe166de6..c202fffb14 100644
--- a/grafana-plugin/integration-tests/integrations/heartbeat.test.ts
+++ b/grafana-plugin/integration-tests/integrations/heartbeat.test.ts
@@ -1,9 +1,9 @@
-import { test, Page, Locator } from '../fixtures';
+import { test, Page, expect, Locator } from '../fixtures';
import { generateRandomValue, selectDropdownValue } from '../utils/forms';
import { createIntegration } from '../utils/integrations';
-test.describe("updating an integration's heartbeat interval works", () => {
+test.describe("updating an integration's heartbeat interval works", async () => {
test.slow();
const _openIntegrationSettingsPopup = async (page: Page): Promise => {
@@ -12,35 +12,64 @@ test.describe("updating an integration's heartbeat interval works", () => {
return integrationSettingsPopupElement;
};
- const changeHeartbeatInterval = async (page: Page, heartbeatIntervalValue: string): Promise => {
+ const _openHeartbeatSettingsForm = async (page: Page) => {
+ const integrationSettingsPopupElement = await _openIntegrationSettingsPopup(page);
+
+ await integrationSettingsPopupElement.click();
+
+ await page.getByTestId('integration-heartbeat-settings').click();
+ };
+
+ test('"change heartbeat interval', async ({ adminRolePage: { page } }) => {
+ const integrationName = generateRandomValue();
+ await createIntegration(page, integrationName);
+
+ await _openHeartbeatSettingsForm(page);
+
const heartbeatSettingsForm = page.getByTestId('heartbeat-settings-form');
+ const value = '30 minutes';
+
await selectDropdownValue({
page,
startingLocator: heartbeatSettingsForm,
selectType: 'grafanaSelect',
- placeholderText: 'Heartbeat Timeout',
- value: heartbeatIntervalValue,
+ value,
optionExactMatch: false,
});
await heartbeatSettingsForm.getByTestId('update-heartbeat').click();
- };
- test('"change heartbeat interval', async ({ adminRolePage: { page } }) => {
+ await heartbeatSettingsForm.getByTestId('close-heartbeat-form').click();
+
+ await _openHeartbeatSettingsForm(page);
+
+ const heartbeatIntervalValue = await heartbeatSettingsForm
+ .locator('div[class*="grafana-select-value-container"] > div[class*="-singleValue"]')
+ .textContent();
+
+ expect(heartbeatIntervalValue).toEqual(value);
+ });
+
+ test('"send heartbeat', async ({ adminRolePage: { page } }) => {
const integrationName = generateRandomValue();
await createIntegration(page, integrationName);
- const integrationSettingsPopupElement = await _openIntegrationSettingsPopup(page);
+ await _openHeartbeatSettingsForm(page);
- await integrationSettingsPopupElement.click();
+ const heartbeatSettingsForm = page.getByTestId('heartbeat-settings-form');
- await page.getByTestId('integration-heartbeat-settings').click();
+ const endpoint = await heartbeatSettingsForm
+ .getByTestId('input-wrapper')
+ .locator('input[class*="input-input"]')
+ .inputValue();
- await changeHeartbeatInterval(page, '1 day');
+ await page.goto(endpoint);
- const heartbeatSettingsForm = page.getByTestId('heartbeat-settings-form');
+ await page.goBack();
- await heartbeatSettingsForm.getByTestId('close-heartbeat-form').click();
+ const heartbeatBadge = await page.getByTestId('heartbeat-badge');
+
+ await expect(heartbeatBadge).toHaveClass(/--success/);
});
});
diff --git a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
index 9880f765b7..666c23d5a5 100644
--- a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
+++ b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
@@ -64,10 +64,12 @@ const IntegrationHeartbeatForm = observer(({ alertReceveChannelId, onClose }: In
onChange={(value: SelectableValue) => setInterval(value.value)}
placeholder="Heartbeat Timeout"
value={interval}
- options={(timeoutOptions || []).map((timeoutOption: SelectOption) => ({
+ isLoading={!timeoutOptions}
+ options={timeoutOptions?.map((timeoutOption: SelectOption) => ({
value: timeoutOption.value,
label: timeoutOption.display_name,
}))}
+ //options={[{ value: 121312, label: '1 day' }]}
/>
diff --git a/grafana-plugin/src/pages/integration/Integration.tsx b/grafana-plugin/src/pages/integration/Integration.tsx
index bb5d32fd84..692b82daac 100644
--- a/grafana-plugin/src/pages/integration/Integration.tsx
+++ b/grafana-plugin/src/pages/integration/Integration.tsx
@@ -1169,6 +1169,7 @@ const IntegrationHeader: React.FC = ({
return (
Date: Wed, 19 Jul 2023 13:27:44 +0300
Subject: [PATCH 5/7] improve heartbeat test a bit
---
.../integrations/heartbeat.test.ts | 4 +---
.../IntegrationHeartbeatForm.tsx | 15 ++++++++-------
2 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/grafana-plugin/integration-tests/integrations/heartbeat.test.ts b/grafana-plugin/integration-tests/integrations/heartbeat.test.ts
index c202fffb14..0b5b57c157 100644
--- a/grafana-plugin/integration-tests/integrations/heartbeat.test.ts
+++ b/grafana-plugin/integration-tests/integrations/heartbeat.test.ts
@@ -40,8 +40,6 @@ test.describe("updating an integration's heartbeat interval works", async () =>
await heartbeatSettingsForm.getByTestId('update-heartbeat').click();
- await heartbeatSettingsForm.getByTestId('close-heartbeat-form').click();
-
await _openHeartbeatSettingsForm(page);
const heartbeatIntervalValue = await heartbeatSettingsForm
@@ -51,7 +49,7 @@ test.describe("updating an integration's heartbeat interval works", async () =>
expect(heartbeatIntervalValue).toEqual(value);
});
- test('"send heartbeat', async ({ adminRolePage: { page } }) => {
+ test.skip('"send heartbeat', async ({ adminRolePage: { page } }) => {
const integrationName = generateRandomValue();
await createIntegration(page, integrationName);
diff --git a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
index 666c23d5a5..94775a4fd0 100644
--- a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
+++ b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
@@ -69,7 +69,6 @@ const IntegrationHeartbeatForm = observer(({ alertReceveChannelId, onClose }: In
value: timeoutOption.value,
label: timeoutOption.display_name,
}))}
- //options={[{ value: 121312, label: '1 day' }]}
/>
@@ -110,12 +109,14 @@ const IntegrationHeartbeatForm = observer(({ alertReceveChannelId, onClose }: In
);
async function onSave() {
- await heartbeatStore
- .saveHeartbeat(heartbeat.id, {
- alert_receive_channel: heartbeat.alert_receive_channel,
- timeout_seconds: interval,
- })
- .then(() => openNotification('Heartbeat settings have been updated'));
+ await heartbeatStore.saveHeartbeat(heartbeat.id, {
+ alert_receive_channel: heartbeat.alert_receive_channel,
+ timeout_seconds: interval,
+ });
+
+ onClose();
+
+ openNotification('Heartbeat settings have been updated');
await alertReceiveChannelStore.loadItem(alertReceveChannelId);
}
From 30c8c89f939c61c5e46ec57b240dd3d119ff0dce Mon Sep 17 00:00:00 2001
From: Maxim
Date: Wed, 19 Jul 2023 13:32:11 +0300
Subject: [PATCH 6/7] enable heartbeat test
---
grafana-plugin/integration-tests/integrations/heartbeat.test.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/grafana-plugin/integration-tests/integrations/heartbeat.test.ts b/grafana-plugin/integration-tests/integrations/heartbeat.test.ts
index 0b5b57c157..ab8086a360 100644
--- a/grafana-plugin/integration-tests/integrations/heartbeat.test.ts
+++ b/grafana-plugin/integration-tests/integrations/heartbeat.test.ts
@@ -49,7 +49,7 @@ test.describe("updating an integration's heartbeat interval works", async () =>
expect(heartbeatIntervalValue).toEqual(value);
});
- test.skip('"send heartbeat', async ({ adminRolePage: { page } }) => {
+ test('"send heartbeat', async ({ adminRolePage: { page } }) => {
const integrationName = generateRandomValue();
await createIntegration(page, integrationName);
From 167554415c99773f0dd428d9a674772220c72128 Mon Sep 17 00:00:00 2001
From: Maxim
Date: Tue, 25 Jul 2023 12:16:23 +0300
Subject: [PATCH 7/7] remove heartbeat inplace intructions, update CHANGELOG
---
CHANGELOG.md | 4 +++
.../IntegrationHeartbeatForm.tsx | 27 +++++++++----------
2 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4cb0465acc..043cadae5d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
+### Fixed
+
+- Bring heartbeats back to UI
+
## v1.3.15 (2023-07-19)
### Changed
diff --git a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
index 94775a4fd0..05e7c17d5c 100644
--- a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
+++ b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx
@@ -1,12 +1,10 @@
import React, { useEffect, useState } from 'react';
import { SelectableValue } from '@grafana/data';
-import { Button, Drawer, Field, HorizontalGroup, Select, VerticalGroup } from '@grafana/ui';
+import { Button, Drawer, Field, HorizontalGroup, Icon, Select, VerticalGroup } from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';
-import Emoji from 'react-emoji-render';
-import Collapse from 'components/Collapse/Collapse';
import IntegrationInputField from 'components/IntegrationInputField/IntegrationInputField';
import Text from 'components/Text/Text';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
@@ -78,17 +76,18 @@ const IntegrationHeartbeatForm = observer(({ alertReceveChannelId, onClose }: In
-
-
- To send periodic heartbeat alerts from to
- OnCall, do the following:
-
-
-
+
+
+
+ How to configure heartbeats
+
+
+
+