diff --git a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.helpers.ts b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.helpers.ts index 99b9dac0..9de5be3d 100644 --- a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.helpers.ts +++ b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.helpers.ts @@ -183,4 +183,15 @@ export class AlertReceiveChannelHelper { data, }); } + + static async checkIfTokenExists(integrationId: string) { + try { + await onCallApi({ skipErrorHandling: true }).GET('/alert_receive_channels/{id}/api_token/', { + params: { path: { id: integrationId } }, + }); + return true; + } catch (_e) { + return false; + } + } } diff --git a/grafana-plugin/src/models/alert_receive_channel_connected_channels/alert_receive_channel_connected_channels.ts b/grafana-plugin/src/models/alert_receive_channel_connected_channels/alert_receive_channel_connected_channels.ts index fef65a54..e5e0dab8 100644 --- a/grafana-plugin/src/models/alert_receive_channel_connected_channels/alert_receive_channel_connected_channels.ts +++ b/grafana-plugin/src/models/alert_receive_channel_connected_channels/alert_receive_channel_connected_channels.ts @@ -34,11 +34,19 @@ export class AlertReceiveChannelConnectedChannelsStore { } @AutoLoadingState(ActionKey.FETCH_INTEGRATIONS_AVAILABLE_FOR_CONNECTION) - async fetchItemsAvailableForConnection({ search, page }: { search?: string; page: number }) { + async fetchItemsAvailableForConnection({ + search, + page, + currentIntegrationId, + }: { + search?: string; + page: number; + currentIntegrationId: string; + }) { await this.rootStore.alertReceiveChannelStore.fetchPaginatedItems({ filters: { search, - id_ne: this.itemsAsList.map(({ alert_receive_channel: { id } }) => id), + id_ne: [...this.itemsAsList.map(({ alert_receive_channel: { id } }) => id), currentIntegrationId], }, perpage: 10, page, diff --git a/grafana-plugin/src/network/oncall-api/autogenerated-api.types.d.ts b/grafana-plugin/src/network/oncall-api/autogenerated-api.types.d.ts index 7f9fa049..7b468869 100644 --- a/grafana-plugin/src/network/oncall-api/autogenerated-api.types.d.ts +++ b/grafana-plugin/src/network/oncall-api/autogenerated-api.types.d.ts @@ -39,6 +39,24 @@ export interface paths { patch: operations['alert_receive_channels_partial_update']; trace?: never; }; + '/alert_receive_channels/{id}/api_token/': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** @description Internal API endpoints for alert receive channels (integrations). */ + get: operations['alert_receive_channels_api_token_retrieve']; + put?: never; + /** @description Internal API endpoints for alert receive channels (integrations). */ + post: operations['alert_receive_channels_api_token_create']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/alert_receive_channels/{id}/change_team/': { parameters: { query?: never; @@ -245,6 +263,23 @@ export interface paths { patch?: never; trace?: never; }; + '/alert_receive_channels/{id}/status_options/': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** @description Internal API endpoints for alert receive channels (integrations). */ + get: operations['alert_receive_channels_status_options_list']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/alert_receive_channels/{id}/stop_maintenance/': { parameters: { query?: never; @@ -366,6 +401,23 @@ export interface paths { patch?: never; trace?: never; }; + '/alert_receive_channels/test_connection/': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** @description Internal API endpoints for alert receive channels (integrations). */ + post: operations['alert_receive_channels_test_connection_create']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/alert_receive_channels/validate_name/': { parameters: { query?: never; @@ -1448,6 +1500,10 @@ export interface components { readonly alertmanager_v2_migrated_at: string | null; additional_settings?: components['schemas']['AdditionalSettingsField'] | null; }; + AlertReceiveChannelBacksyncStatusOptions: { + value: string; + display_name: string; + }; AlertReceiveChannelConnectContactPoint: { datasource_uid: string; contact_point_name: string; @@ -1746,6 +1802,9 @@ export interface components { readonly status: boolean; readonly instruction: string; }; + IntegrationTokenPostResponse: { + token: string; + }; Key: { id: string; name: string; @@ -2440,6 +2499,49 @@ export interface operations { }; }; }; + alert_receive_channels_api_token_retrieve: { + parameters: { + query?: never; + header?: never; + path: { + /** @description A string identifying this alert receive channel. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No response body */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + alert_receive_channels_api_token_create: { + parameters: { + query?: never; + header?: never; + path: { + /** @description A string identifying this alert receive channel. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['IntegrationTokenPostResponse']; + }; + }; + }; + }; alert_receive_channels_change_team_update: { parameters: { query: { @@ -2661,7 +2763,7 @@ export interface operations { }; responses: { /** @description No response body */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -2799,6 +2901,28 @@ export interface operations { }; }; }; + alert_receive_channels_status_options_list: { + parameters: { + query?: never; + header?: never; + path: { + /** @description A string identifying this alert receive channel. */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['AlertReceiveChannelBacksyncStatusOptions'][]; + }; + }; + }; + }; alert_receive_channels_stop_maintenance_create: { parameters: { query?: never; @@ -3004,6 +3128,30 @@ export interface operations { }; }; }; + alert_receive_channels_test_connection_create: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['AlertReceiveChannel']; + 'application/x-www-form-urlencoded': components['schemas']['AlertReceiveChannel']; + 'multipart/form-data': components['schemas']['AlertReceiveChannel']; + }; + }; + responses: { + /** @description No response body */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; alert_receive_channels_validate_name_retrieve: { parameters: { query: { diff --git a/grafana-plugin/src/pages/integration/Integration.hooks.ts b/grafana-plugin/src/pages/integration/Integration.hooks.ts new file mode 100644 index 00000000..3cb0d353 --- /dev/null +++ b/grafana-plugin/src/pages/integration/Integration.hooks.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react'; + +import { AlertReceiveChannelHelper } from 'models/alert_receive_channel/alert_receive_channel.helpers'; + +import { useCurrentIntegration } from './OutgoingTab/OutgoingTab.hooks'; + +export const useIntegrationTokenCheck = () => { + const [tokenExists, setTokenExists] = useState(true); + + const { id } = useCurrentIntegration(); + + useEffect(() => { + const checkToken = async () => { + const tokenExists = await AlertReceiveChannelHelper.checkIfTokenExists(id); + setTokenExists(tokenExists); + }; + checkToken(); + }, [id]); + + return tokenExists; +}; diff --git a/grafana-plugin/src/pages/integration/OutgoingTab/ConnectIntegrationModal.tsx b/grafana-plugin/src/pages/integration/OutgoingTab/ConnectIntegrationModal.tsx index d0ab18b7..ec4ad035 100644 --- a/grafana-plugin/src/pages/integration/OutgoingTab/ConnectIntegrationModal.tsx +++ b/grafana-plugin/src/pages/integration/OutgoingTab/ConnectIntegrationModal.tsx @@ -38,6 +38,7 @@ export const ConnectIntegrationModal = observer(({ onDismiss }: { onDismiss: () await alertReceiveChannelConnectedChannelsStore.fetchItemsAvailableForConnection({ page, search, + currentIntegrationId: currentIntegration.id, }); }; diff --git a/grafana-plugin/src/pages/integration/OutgoingTab/ConnectedIntegrationsTable.tsx b/grafana-plugin/src/pages/integration/OutgoingTab/ConnectedIntegrationsTable.tsx index 1bd66a5d..547a403f 100644 --- a/grafana-plugin/src/pages/integration/OutgoingTab/ConnectedIntegrationsTable.tsx +++ b/grafana-plugin/src/pages/integration/OutgoingTab/ConnectedIntegrationsTable.tsx @@ -1,6 +1,16 @@ import React, { FC } from 'react'; -import { HorizontalGroup, Tooltip, Icon, useStyles2, IconButton, Switch, Checkbox, ConfirmModal } from '@grafana/ui'; +import { + HorizontalGroup, + Tooltip, + Icon, + useStyles2, + IconButton, + Switch, + Checkbox, + ConfirmModal, + useTheme2, +} from '@grafana/ui'; import { observer } from 'mobx-react'; import Emoji from 'react-emoji-render'; @@ -9,6 +19,7 @@ import { IntegrationLogoWithTitle } from 'components/IntegrationLogo/Integration import { Text } from 'components/Text/Text'; import { AlertReceiveChannelHelper } from 'models/alert_receive_channel/alert_receive_channel.helpers'; import { ApiSchemas } from 'network/oncall-api/api.types'; +import { useIntegrationTokenCheck } from 'pages/integration/Integration.hooks'; import { useStore } from 'state/useStore'; import { PLUGIN_ROOT } from 'utils/consts'; import { useConfirmModal } from 'utils/hooks'; @@ -33,6 +44,8 @@ interface ConnectedIntegrationsTableProps { const ConnectedIntegrationsTable: FC = observer( ({ selectable, allowDelete, onChange, onBacksyncChange, tableProps, defaultBacksyncedIds = [], allowBacksync }) => { const { alertReceiveChannelStore } = useStore(); + const { colors } = useTheme2(); + const tokenExists = useIntegrationTokenCheck(); const columns = [ ...(selectable @@ -70,15 +83,22 @@ const ConnectedIntegrationsTable: FC = observer title: ( Backsync - Switch on to start sending data from other integrations}> - - + {tokenExists ? ( + Switch on to start sending data from other integrations}> + {} + + ) : ( + Token must be generated to enable backsync}> + {} + + )} ), render: (connectedIntegration: ConnectedIntegration) => ( onBacksyncChange(connectedIntegration.id, checked)} + disabled={!tokenExists} /> ), }, @@ -98,15 +118,21 @@ const ConnectedIntegrationsTable: FC = observer const BacksyncSwitcher = ({ onChange, defaultChecked, + disabled, }: { onChange: (checked: boolean) => void; defaultChecked?: boolean; + disabled?: boolean; }) => { const styles = useStyles2(getStyles); return (
- onChange(currentTarget.checked)} /> + onChange(currentTarget.checked)} + />
); };