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 - - - -
- - - setInterval(value.value)} + placeholder="Heartbeat Timeout" + value={interval} + options={(timeoutOptions || []).map((timeoutOption: SelectOption) => ({ + value: timeoutOption.value, + label: timeoutOption.display_name, + }))} + /> + + +
+
+ + + +
+ +

+ To send periodic heartbeat alerts from to + OnCall, do the following: + - - - -

- - - -
-

- To send periodic heartbeat alerts from to OnCall, do - the following: - -

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