From 5001759bc134798e54836b95cd45114118119710 Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Mon, 22 May 2023 11:12:03 +0300 Subject: [PATCH 01/10] Templates tweaks (#1979) # What this PR does - Fixed Templates view - Fixed main integration page view for smaller screens - Show No Escalation Chain/ChatOps (Slack/Telegram for now, no MS teams yet) --- ...IntegrationCollapsibleTreeView.module.scss | 6 +- .../src/components/Tag/Tag.module.css | 1 + ...llapsedIntegrationRouteDisplay.module.scss | 31 ++++ .../CollapsedIntegrationRouteDisplay.tsx | 47 +++--- .../ExpandedIntegrationRouteDisplay.tsx | 73 ++++++---- .../integration_2/Integration2.helper.ts | 22 ++- .../integration_2/Integration2.module.scss | 52 +++++-- .../src/pages/integration_2/Integration2.tsx | 134 ++++++++++-------- .../IntegrationBlockItem.module.scss | 1 + .../IntegrationTemplateBlock.module.scss | 18 +++ .../IntegrationTemplateBlock.tsx | 38 +++-- .../IntegrationTemplatesList.tsx | 34 +++-- 12 files changed, 295 insertions(+), 162 deletions(-) create mode 100644 grafana-plugin/src/pages/integration_2/IntegrationTemplateBlock.module.scss diff --git a/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.module.scss b/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.module.scss index bd73eb3c..e5d8e9d4 100644 --- a/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.module.scss +++ b/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.module.scss @@ -5,9 +5,9 @@ &:before { content: ''; position: absolute; - height: calc(100% - 10px); + height: calc(100% - 20px); border: var(--border-weak); - margin-top: 0px; + margin-top: 20px; margin-left: -20px; } } @@ -45,4 +45,4 @@ display: flex; align-items: center; justify-content: center; -} \ No newline at end of file +} diff --git a/grafana-plugin/src/components/Tag/Tag.module.css b/grafana-plugin/src/components/Tag/Tag.module.css index c3dfbc1e..c8858482 100644 --- a/grafana-plugin/src/components/Tag/Tag.module.css +++ b/grafana-plugin/src/components/Tag/Tag.module.css @@ -3,4 +3,5 @@ line-height: 100%; padding: 5px 8px; color: white; + white-space: nowrap; } diff --git a/grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.module.scss b/grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.module.scss index 3eb80dda..4fcfed5d 100644 --- a/grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.module.scss +++ b/grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.module.scss @@ -1,3 +1,34 @@ .spacing { margin-bottom: 12px; } + +.icon-exclamation { + color: var(--error-text-color); +} + +.heading-container { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + overflow: hidden; + gap: 12px; + + &__item { + display: flex; + white-space: nowrap; + flex-direction: row; + gap: 8px; + } + + &__item--large { + flex-grow: 1; + overflow: hidden; + } + + &__text { + overflow: hidden; + max-width: calc(100% - 48px); + text-overflow: ellipsis; + } +} diff --git a/grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.tsx b/grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.tsx index 4ce63f5d..fd261823 100644 --- a/grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.tsx +++ b/grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { ConfirmModal, HorizontalGroup, Icon, IconButton } from '@grafana/ui'; +import { ConfirmModal, HorizontalGroup, Icon, VerticalGroup } from '@grafana/ui'; import cn from 'classnames/bind'; import { observer } from 'mobx-react'; @@ -42,8 +42,8 @@ const CollapsedIntegrationRouteDisplay: React.FC - +
+
{channelFilter.filtering_term && ( - {IntegrationHelper.truncateLine(channelFilter.filtering_term)} + + {channelFilter.filtering_term} + )} - - +
+ +
setRouteIdForDeletion(channelFilterId)} /> - - +
+
} content={
- - {channelFilter.slack_channel?.display_name && ( - + + {IntegrationHelper.getChatOpsChannels(channelFilter).map((chatOpsChannel, key) => ( + Publish to ChatOps - {channelFilter.slack_channel.display_name} + {chatOpsChannel} - )} + ))} + Escalate to + {escalationChain?.name && ( )} + {!escalationChain?.name && ( - + +
+ +
+ + No Escalation chain + +
)}
-
+
} /> diff --git a/grafana-plugin/src/pages/integration_2/ExpandedIntegrationRouteDisplay.tsx b/grafana-plugin/src/pages/integration_2/ExpandedIntegrationRouteDisplay.tsx index cdd857dd..6036a66f 100644 --- a/grafana-plugin/src/pages/integration_2/ExpandedIntegrationRouteDisplay.tsx +++ b/grafana-plugin/src/pages/integration_2/ExpandedIntegrationRouteDisplay.tsx @@ -17,6 +17,7 @@ import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/W import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; import { AlertTemplatesDTO } from 'models/alert_templates'; import { ChannelFilter } from 'models/channel_filter/channel_filter.types'; +import { AppFeature } from 'state/features'; import { useStore } from 'state/useStore'; import { UserActions } from 'utils/authorization'; @@ -45,8 +46,19 @@ interface ExpandedIntegrationRouteDisplayState { const ExpandedIntegrationRouteDisplay: React.FC = observer( ({ alertReceiveChannelId, channelFilterId, templates, routeIndex, openEditTemplateModal, onEditRegexpTemplate }) => { - const { escalationPolicyStore, escalationChainStore, alertReceiveChannelStore, grafanaTeamStore } = useStore(); - const hasChatOpsConnectors = false; + const store = useStore(); + const { + telegramChannelStore, + teamStore, + escalationPolicyStore, + escalationChainStore, + alertReceiveChannelStore, + grafanaTeamStore, + } = store; + + const isSlackInstalled = Boolean(teamStore.currentTeam?.slack_team_identity); + const isTelegramInstalled = + store.hasFeature(AppFeature.Telegram) && telegramChannelStore.currentTeamToTelegramChannel?.length > 0; const [{ isEscalationCollapsed, isRefreshingEscalationChains, routeIdForDeletion }, setState] = useReducer( (state: ExpandedIntegrationRouteDisplayState, newState: Partial) => ({ @@ -71,6 +83,9 @@ const ExpandedIntegrationRouteDisplay: React.FC @@ -101,31 +113,30 @@ const ExpandedIntegrationRouteDisplay: React.FC - - - Routing Template -
- + + Routing Template +
+ +
+
- -
-
+
+ + )} {routeIndex !== channelFiltersTotal.length - 1 && ( @@ -138,7 +149,7 @@ const ExpandedIntegrationRouteDisplay: React.FC )} - {hasChatOpsConnectors && ( + {(isSlackInstalled || isTelegramInstalled) && ( Publish to ChatOps diff --git a/grafana-plugin/src/pages/integration_2/Integration2.helper.ts b/grafana-plugin/src/pages/integration_2/Integration2.helper.ts index 0ed75b5f..7e12018e 100644 --- a/grafana-plugin/src/pages/integration_2/Integration2.helper.ts +++ b/grafana-plugin/src/pages/integration_2/Integration2.helper.ts @@ -1,7 +1,12 @@ +/* + [oncall-private] + Any change to this file needs to be done in the oncall-private also +*/ + import dayjs from 'dayjs'; import { MaintenanceMode } from 'models/alert_receive_channel/alert_receive_channel.types'; -import { ChannelFilter } from 'models/channel_filter'; +import { ChannelFilter } from 'models/channel_filter/channel_filter.types'; import { MAX_CHARACTERS_COUNT, TEXTAREA_ROWS_COUNT } from './Integration2.config'; @@ -31,7 +36,7 @@ const IntegrationHelper = { return slice.length === line.length ? slice : `${slice} ...`; }, - getRouteConditionWording(channelFilters: Array, routeIndex: number) { + getRouteConditionWording(channelFilters: Array, routeIndex: number): 'Default' | 'Else' | 'If' { const totalCount = Object.keys(channelFilters).length; if (routeIndex === totalCount - 1) { @@ -54,6 +59,19 @@ const IntegrationHelper = { return `${hourDiff}h left`; }, + + getChatOpsChannels(channelFilter: ChannelFilter) { + const channels = []; + + if (channelFilter.notify_in_slack && channelFilter.slack_channel?.display_name) { + channels.push(channelFilter.slack_channel.display_name); + } + if (channelFilter.telegram_channel) { + channels.push(channelFilter.telegram_channel); + } + + return channels; + }, }; export default IntegrationHelper; diff --git a/grafana-plugin/src/pages/integration_2/Integration2.module.scss b/grafana-plugin/src/pages/integration_2/Integration2.module.scss index d3085883..b6ae4ea6 100644 --- a/grafana-plugin/src/pages/integration_2/Integration2.module.scss +++ b/grafana-plugin/src/pages/integration_2/Integration2.module.scss @@ -124,12 +124,7 @@ $LARGE-MARGIN: 24px; } .input { - &--short { - width: 500px; - } - &--long { - width: 700px; - } + flex-grow: 1; } .how-to-connect__container { @@ -146,15 +141,50 @@ $LARGE-MARGIN: 24px; padding: 4px 8px; } -.templates__content { - padding-left: 12px; +.vertical-block { + display: flex; + flex-direction: column; + gap: 8px; +} + +.templates { + &__content { + padding-left: 12px; + } + + &__container { + width: 100%; + padding-right: 48px; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + overflow: hidden; + gap: 12px; + } + + &__item { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + &__item--large { + max-width: calc((100% - 150px) / 2); + } + &__item--small { + max-width: 150px; + } + + &__item-text { + margin-right: 4px; + } } .templates__content, -.templates__container { +.templates__outer-container { display: flex; width: 100%; align-items: center; + overflow: hidden; } .templates__edit { @@ -164,3 +194,7 @@ $LARGE-MARGIN: 24px; .template-drawer { padding-bottom: 24px; } + +.no-wrap { + white-space: nowrap; +} diff --git a/grafana-plugin/src/pages/integration_2/Integration2.tsx b/grafana-plugin/src/pages/integration_2/Integration2.tsx index ae63911c..d1080f21 100644 --- a/grafana-plugin/src/pages/integration_2/Integration2.tsx +++ b/grafana-plugin/src/pages/integration_2/Integration2.tsx @@ -158,7 +158,7 @@ class Integration2 extends React.Component
{isTemplateSettingsOpen && ( this.setState({ isTemplateSettingsOpen: false })} @@ -197,63 +197,67 @@ class Integration2 extends React.Component )} - - {alertReceiveChannelCounter && ( - - - - )} +
+ + {alertReceiveChannelCounter && ( + + + + )} - - - {alertReceiveChannel.maintenance_till && ( - )} - {this.renderHearbeat(alertReceiveChannel)} + {alertReceiveChannel.maintenance_till && ( + + )} + + {this.renderHearbeat(alertReceiveChannel)} - - Type: - - {integration?.display_name} + Type: + + + {integration?.display_name} + + + + Team: + + + + Created by: + - - Team: - - - - Created by: - - - +
+
- - - Grouping: +
+
+ + Grouping: + {IntegrationHelper.truncateLine(templates['grouping_id_template'] || '')} - +
- - Autoresolve: +
+ + Autoresolve: + {IntegrationHelper.truncateLine(templates['resolve_condition_template'] || '')} - +
- - Visualisation: +
+ + Visualisation: + Multiple - - +
+
setIsHearbeatFormOpen(true)}> - Hearbeat + Hearbeat Settings
{!alertReceiveChannel.maintenance_till && ( diff --git a/grafana-plugin/src/pages/integration_2/IntegrationBlockItem.module.scss b/grafana-plugin/src/pages/integration_2/IntegrationBlockItem.module.scss index bad75ae3..f183c3ff 100644 --- a/grafana-plugin/src/pages/integration_2/IntegrationBlockItem.module.scss +++ b/grafana-plugin/src/pages/integration_2/IntegrationBlockItem.module.scss @@ -4,6 +4,7 @@ margin-bottom: 12px; &__content { + width: 100%; padding-top: 12px; padding-bottom: 12px; } diff --git a/grafana-plugin/src/pages/integration_2/IntegrationTemplateBlock.module.scss b/grafana-plugin/src/pages/integration_2/IntegrationTemplateBlock.module.scss new file mode 100644 index 00000000..12c604e3 --- /dev/null +++ b/grafana-plugin/src/pages/integration_2/IntegrationTemplateBlock.module.scss @@ -0,0 +1,18 @@ +.container { + display: flex; + flex-direction: row; + gap: 4px; + + &__item { + flex-grow: 1; + white-space: nowrap; + overflow: hidden; + display: flex; + flex-direction: row; + gap: 4px; + } + + label { + margin-right: 0px; + } +} diff --git a/grafana-plugin/src/pages/integration_2/IntegrationTemplateBlock.tsx b/grafana-plugin/src/pages/integration_2/IntegrationTemplateBlock.tsx index 6928f2de..8882af94 100644 --- a/grafana-plugin/src/pages/integration_2/IntegrationTemplateBlock.tsx +++ b/grafana-plugin/src/pages/integration_2/IntegrationTemplateBlock.tsx @@ -1,8 +1,11 @@ import React from 'react'; -import { Button, HorizontalGroup, Icon, InlineLabel, LoadingPlaceholder, Tooltip } from '@grafana/ui'; +import { Button, InlineLabel, LoadingPlaceholder, Tooltip } from '@grafana/ui'; +import cn from 'classnames/bind'; -import Text from 'components/Text/Text'; +import styles from './IntegrationTemplateBlock.module.scss'; + +const cx = cn.bind(styles); interface IntegrationTemplateBlockProps { label: string; @@ -20,9 +23,7 @@ const IntegrationTemplateBlock: React.FC = ({ label, labelTooltip, renderInput, - showHelp, onEdit, - onHelp, onRemove, isLoading, }) => { @@ -32,27 +33,22 @@ const IntegrationTemplateBlock: React.FC = ({ } return ( - +
{label} - {renderInput()} - - - )} - - {isLoading && } - + {isLoading && } +
+
); }; diff --git a/grafana-plugin/src/pages/integration_2/IntegrationTemplatesList.tsx b/grafana-plugin/src/pages/integration_2/IntegrationTemplatesList.tsx index 94487ff7..9f6fe760 100644 --- a/grafana-plugin/src/pages/integration_2/IntegrationTemplatesList.tsx +++ b/grafana-plugin/src/pages/integration_2/IntegrationTemplatesList.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { ConfirmModal, VerticalGroup } from '@grafana/ui'; +import { ConfirmModal } from '@grafana/ui'; import cn from 'classnames/bind'; import MonacoEditor from 'components/MonacoEditor/MonacoEditor'; @@ -57,7 +57,7 @@ const IntegrationTemplateList: React.FC = ({ - + onShowConfirmModal('grouping_id_template')} isLoading={isRestoringTemplate && templateRestoreName === 'grouping_id_template'} @@ -95,11 +95,11 @@ const IntegrationTemplateList: React.FC = ({ )} onEdit={() => openEditTemplateModal('resolve_condition_template')} /> - + - + Web = ({ )} onEdit={() => openEditTemplateModal('web_image_url_template')} /> - + - + onShowConfirmModal('acknowledge_condition_template')} @@ -203,11 +203,11 @@ const IntegrationTemplateList: React.FC = ({ )} onEdit={() => openEditTemplateModal('source_link_template')} /> - + - + onShowConfirmModal('phone_call_title_template')} @@ -245,11 +245,11 @@ const IntegrationTemplateList: React.FC = ({ )} onEdit={() => openEditTemplateModal('sms_title_template')} /> - + - + Slack = ({ )} onEdit={() => openEditTemplateModal('slack_image_url_template')} /> - + - + Telegram = ({ )} onEdit={() => openEditTemplateModal('telegram_image_url_template')} /> - + - + Email = ({ )} onEdit={() => openEditTemplateModal('email_message_template')} /> - +
); @@ -451,4 +451,8 @@ const IntegrationTemplateList: React.FC = ({ } }; +const VerticalBlock: React.FC<{ children: React.ReactElement[] }> = ({ children }) => { + return
{children}
; +}; + export default IntegrationTemplateList; From 07368f3b9345e252eb9f4d4c9d282c3509b18c70 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Mon, 22 May 2023 13:20:06 +0100 Subject: [PATCH 02/10] Allow passing Firebase credentials via environment variable (#1969) # What this PR does Allow passing Google application credentials (used to send FCM messages using `fcm-django`) as an environment variable `GOOGLE_APPLICATION_CREDENTIALS_JSON_BASE64`. If the env variable is not provided, credentials will be taken from file. This change allows uWSGI workers send messages to FCM (currently it's not possible because the uWSGI user doesn't have access to the credentials file) + makes configuration more consistent. Also removes a redundant `FCM_PROJECT_ID` env variable (Google application credentials already contain the project ID). ## Which issue(s) this PR fixes ## 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] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --- CHANGELOG.md | 6 ++++++ docker-compose-developer.yml | 1 - engine/settings/base.py | 15 +++++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64db26d8..055c9108 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Allow passing Firebase credentials via environment variable by @vadimkerr ([#1969](https://github.com/grafana/oncall/pull/1969)) + ## v1.2.26 (2023-05-18) ### Fixed diff --git a/docker-compose-developer.yml b/docker-compose-developer.yml index 815b8813..97ef0c72 100644 --- a/docker-compose-developer.yml +++ b/docker-compose-developer.yml @@ -25,7 +25,6 @@ x-env-vars: &oncall-env-vars BROKER_TYPE: ${BROKER_TYPE} GRAFANA_API_URL: http://localhost:3000 GOOGLE_APPLICATION_CREDENTIALS: /etc/app/gcp_service_account.json - FCM_PROJECT_ID: oncall-mobile-dev # basically this is needed because the oncall backend containers have been configured to communicate w/ grafana via # http://localhost:3000 (GRAFANA_API_URL). This URL is used in two scenarios: diff --git a/engine/settings/base.py b/engine/settings/base.py index eb9ed5d9..582611fb 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -1,8 +1,10 @@ +import base64 +import json import os from random import randrange from celery.schedules import crontab -from firebase_admin import initialize_app +from firebase_admin import credentials, initialize_app from common.utils import getenv_boolean, getenv_integer @@ -587,13 +589,18 @@ EXTRA_MESSAGING_BACKENDS = [ ("apps.mobile_app.backend.MobileAppCriticalBackend", 6), ] -FIREBASE_APP = initialize_app(options={"projectId": os.environ.get("FCM_PROJECT_ID", None)}) +# Firebase credentials can be passed as base64 encoded JSON string in GOOGLE_APPLICATION_CREDENTIALS_JSON_BASE64 env variable. +# If it's not passed, firebase_admin will use a file located at GOOGLE_APPLICATION_CREDENTIALS env variable. +credential = None +GOOGLE_APPLICATION_CREDENTIALS_JSON_BASE64 = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS_JSON_BASE64", None) +if GOOGLE_APPLICATION_CREDENTIALS_JSON_BASE64: + credentials_json = json.loads(base64.b64decode(GOOGLE_APPLICATION_CREDENTIALS_JSON_BASE64)) + credential = credentials.Certificate(credentials_json) FCM_RELAY_ENABLED = getenv_boolean("FCM_RELAY_ENABLED", default=False) FCM_DJANGO_SETTINGS = { # an instance of firebase_admin.App to be used as default for all fcm-django requests - # default: None (the default Firebase app) - "DEFAULT_FIREBASE_APP": None, + "DEFAULT_FIREBASE_APP": initialize_app(credential=credential), "APP_VERBOSE_NAME": "OnCall", "ONE_DEVICE_PER_USER": True, "DELETE_INACTIVE_DEVICES": False, From 663987c57eef537b77d8151b6512112cab08787e Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Mon, 22 May 2023 14:32:21 +0100 Subject: [PATCH 03/10] Bring back FCM_PROJECT_ID env variable (#1980) Bring back `FCM_PROJECT_ID` env variable that was removed in https://github.com/grafana/oncall/pull/1969. I made an incorrect assumption that project ID is already specified in the credentials file, but in fact project ID can be different from the one in credentials file. --- docker-compose-developer.yml | 1 + engine/settings/base.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docker-compose-developer.yml b/docker-compose-developer.yml index 97ef0c72..815b8813 100644 --- a/docker-compose-developer.yml +++ b/docker-compose-developer.yml @@ -25,6 +25,7 @@ x-env-vars: &oncall-env-vars BROKER_TYPE: ${BROKER_TYPE} GRAFANA_API_URL: http://localhost:3000 GOOGLE_APPLICATION_CREDENTIALS: /etc/app/gcp_service_account.json + FCM_PROJECT_ID: oncall-mobile-dev # basically this is needed because the oncall backend containers have been configured to communicate w/ grafana via # http://localhost:3000 (GRAFANA_API_URL). This URL is used in two scenarios: diff --git a/engine/settings/base.py b/engine/settings/base.py index 582611fb..12c7ee66 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -597,10 +597,13 @@ if GOOGLE_APPLICATION_CREDENTIALS_JSON_BASE64: credentials_json = json.loads(base64.b64decode(GOOGLE_APPLICATION_CREDENTIALS_JSON_BASE64)) credential = credentials.Certificate(credentials_json) +# FCM_PROJECT_ID can be different from the project ID in the credentials file. +FCM_PROJECT_ID = os.environ.get("FCM_PROJECT_ID", None) + FCM_RELAY_ENABLED = getenv_boolean("FCM_RELAY_ENABLED", default=False) FCM_DJANGO_SETTINGS = { # an instance of firebase_admin.App to be used as default for all fcm-django requests - "DEFAULT_FIREBASE_APP": initialize_app(credential=credential), + "DEFAULT_FIREBASE_APP": initialize_app(credential=credential, options={"projectId": FCM_PROJECT_ID}), "APP_VERBOSE_NAME": "OnCall", "ONE_DEVICE_PER_USER": True, "DELETE_INACTIVE_DEVICES": False, From 53d34164ef7b1ec10c327c51a52c201259d48be2 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Mon, 22 May 2023 20:16:31 +0100 Subject: [PATCH 04/10] Fix SQLite permission issue (#1984) # What this PR does Fixes https://github.com/grafana/oncall/issues/1960. ## 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] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --- CHANGELOG.md | 4 ++++ engine/Dockerfile | 7 +++++-- engine/apps/telegram/views.py | 3 --- engine/engine/celery.py | 7 +++++++ .../management/commands/create_sqlite_db.py | 15 +++++++++++++++ 5 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 engine/engine/management/commands/create_sqlite_db.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 055c9108..98fb514e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow passing Firebase credentials via environment variable by @vadimkerr ([#1969](https://github.com/grafana/oncall/pull/1969)) +### Fixed + +- Fix SQLite permission issue by @vadimkerr ([#1984](https://github.com/grafana/oncall/pull/1984)) + ## v1.2.26 (2023-05-18) ### Fixed diff --git a/engine/Dockerfile b/engine/Dockerfile index bf5672a5..ad1bbd6c 100644 --- a/engine/Dockerfile +++ b/engine/Dockerfile @@ -19,9 +19,12 @@ RUN pip install -r requirements.txt # https://stackoverflow.com/questions/34398632/docker-how-to-run-pip-requirements-txt-only-if-there-was-a-change/34399661#34399661 COPY ./ ./ -# Collect static files and create an SQLite database -RUN mkdir -p /var/lib/oncall +# Collect static files RUN DJANGO_SETTINGS_MODULE=settings.prod_without_db DATABASE_TYPE=sqlite3 DATABASE_NAME=/var/lib/oncall/oncall.db SECRET_KEY="ThEmUsTSecretKEYforBUILDstage123" SILK_PROFILER_ENABLED="True" python manage.py collectstatic --no-input + +# Create SQLite database and set permissions +RUN mkdir -p /var/lib/oncall +RUN DATABASE_TYPE=sqlite3 DATABASE_NAME=/var/lib/oncall/oncall.db python manage.py create_sqlite_db RUN chown -R 1000:2000 /var/lib/oncall # This is required for silk profilers to sync between uwsgi workers diff --git a/engine/apps/telegram/views.py b/engine/apps/telegram/views.py index 76740711..6db87e99 100644 --- a/engine/apps/telegram/views.py +++ b/engine/apps/telegram/views.py @@ -1,11 +1,8 @@ from rest_framework.response import Response from rest_framework.views import APIView -from apps.telegram.tasks import register_telegram_webhook from apps.telegram.updates.update_manager import UpdateManager -register_telegram_webhook.delay() - class WebHookView(APIView): def get(self, request, format=None): diff --git a/engine/engine/celery.py b/engine/engine/celery.py index c78459d5..c7ac45fa 100644 --- a/engine/engine/celery.py +++ b/engine/engine/celery.py @@ -53,6 +53,13 @@ def on_after_setup_logger(logger, **kwargs): ) +@celery.signals.worker_ready.connect +def on_worker_ready(*args, **kwargs): + from apps.telegram.tasks import register_telegram_webhook + + register_telegram_webhook.delay() + + if settings.OTEL_TRACING_ENABLED and settings.OTEL_EXPORTER_OTLP_ENDPOINT: @celery.signals.worker_process_init.connect(weak=False) diff --git a/engine/engine/management/commands/create_sqlite_db.py b/engine/engine/management/commands/create_sqlite_db.py new file mode 100644 index 00000000..eb99b439 --- /dev/null +++ b/engine/engine/management/commands/create_sqlite_db.py @@ -0,0 +1,15 @@ +from django.conf import settings +from django.core.management.base import BaseCommand +from django.db import connection + + +class Command(BaseCommand): + """ + Create SQLite3 database file if it doesn't exist. + """ + + def handle(self, *args, **options): + assert settings.DATABASE_TYPE == "sqlite3" + + # Creating a cursor creates the database file if it doesn't exist. + connection.cursor() From f04430568fa3a7630a766712c0131b25b1c6d24f Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Tue, 23 May 2023 09:56:33 +0800 Subject: [PATCH 05/10] Refactor alertmanager templates (#1944) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What this PR does This PR changes default templates for alertmanager: Web: Screenshot 2023-05-17 at 6 20 06 PM Slack: Screenshot 2023-05-17 at 6 17 30 PM Telegram: Screenshot 2023-05-17 at 6 16 46 PM MS teams: ![Uploading Screenshot 2023-05-23 at 9.46.21 AM.png…]() ## Which issue(s) this PR fixes ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --- engine/config_integrations/alertmanager.py | 294 +++++++++++++-------- 1 file changed, 186 insertions(+), 108 deletions(-) diff --git a/engine/config_integrations/alertmanager.py b/engine/config_integrations/alertmanager.py index 8a2f8464..579174de 100644 --- a/engine/config_integrations/alertmanager.py +++ b/engine/config_integrations/alertmanager.py @@ -20,10 +20,67 @@ Alerts from Grafana Alertmanager are automatically routed to this integration.
Creating contact points and routes for other alertmanagers... {% endif %}""" -# Default templates +# Web +web_title = """{{- payload.get("labels", {}).get("alertname", "No title (check Title Template)") -}}""" +web_message = """\ +{%- set annotations = payload.annotations.copy() -%} +{%- set labels = payload.labels.copy() -%} + +{%- if "summary" in annotations %} +{{ annotations.summary }} +{%- set _ = annotations.pop('summary') -%} +{%- endif %} + +{%- if "message" in annotations %} +{{ annotations.message }} +{%- set _ = annotations.pop('message') -%} +{%- endif %} + +{% set severity = labels.severity | default("Unknown") -%} +{%- set severity_emoji = {"critical": ":rotating_light:", "warning": ":warning:" }[severity] | default(":question:") -%} +Severity: {{ severity }} {{ severity_emoji }} + +{%- set status = payload.status | default("Unknown") %} +{%- set status_emoji = {"firing": ":fire:", "resolved": ":white_check_mark:"}[status] | default(":warning:") %} +Status: {{ status }} {{ status_emoji }} (on the source) + +{% if "runbook_url" in annotations -%} +[:book: Runbook:link:]({{ annotations.runbook_url }}) +{%- set _ = annotations.pop('runbook_url') -%} +{%- endif %} + +{%- if "runbook_url_internal" in annotations -%} +[:closed_book: Runbook (internal):link:]({{ annotations.runbook_url_internal }}) +{%- set _ = annotations.pop('runbook_url_internal') -%} +{%- endif %} + +:label: Labels: +{%- for k, v in payload["labels"].items() %} +- {{ k }}: {{ v }} +{%- endfor %} + +{% if annotations | length > 0 -%} +:pushpin: Other annotations: +{%- for k, v in annotations.items() %} +- {{ k }}: {{ v }} +{%- endfor %} +{% endif %} +""" # noqa: W291 + +web_image_url = None + +# Behaviour +source_link = "{{ payload.generatorURL }}" + +grouping_id = "{{ payload.labels }}" + +resolve_condition = """{{ payload.status == "resolved" }}""" + +acknowledge_condition = None + +# Slack slack_title = """\ -{# Usually title is located in payload.labels.alertname #} -{% set title = payload.get("labels", {}).get("alertname", "No title (check Web Title Template)") %} +{% set title = payload.get("labels", {}).get("alertname", "No title (check Title Template)") %} {# Combine the title from different built-in variables into slack-formatted url #} *<{{ grafana_oncall_link }}|#{{ grafana_oncall_incident_id }} {{ title }}>* via {{ integration_name }} {% if source_link %} @@ -31,74 +88,131 @@ slack_title = """\ {%- endif %} """ -slack_message = """\ -{{- payload.message }} -{%- if "status" in payload -%} -*Status*: {{ payload.status }} -{% endif -%} -*Labels:* {% for k, v in payload["labels"].items() %} -{{ k }}: {{ v }}{% endfor %} -*Annotations:* -{%- for k, v in payload.get("annotations", {}).items() %} -{#- render annotation as slack markdown url if it starts with http #} -{{ k }}: {% if v.startswith("http") %} <{{v}}|here> {% else %} {{v}} {% endif -%} -{% endfor %} -""" # noqa: W291 +# default slack message template is identical to web message template, except urls +# It can be based on web message template (see example), but it can affect existing templates +# slack_message = """ +# {% set mkdwn_link_regex = "\[([\w\s\d:]+)\]\((https?:\/\/[\w\d./?=#]+)\)" %} +# {{ web_message +# | regex_replace(mkdwn_link_regex, "<\\2|\\1>") +# }} +# """ +slack_message = """\ +{%- set annotations = payload.annotations.copy() -%} +{%- set labels = payload.labels.copy() -%} + +{%- if "summary" in annotations %} +{{ annotations.summary }} +{%- set _ = annotations.pop('summary') -%} +{%- endif %} + +{%- if "message" in annotations %} +{{ annotations.message }} +{%- set _ = annotations.pop('message') -%} +{%- endif %} + +{# Optionally set oncall_slack_user_group to slack user group in the following format "@users-oncall" #} +{%- set oncall_slack_user_group = None -%} +{%- if oncall_slack_user_group %} +Heads up {{ oncall_slack_user_group }} +{%- endif %} + +{% set severity = labels.severity | default("Unknown") -%} +{%- set severity_emoji = {"critical": ":rotating_light:", "warning": ":warning:" }[severity] | default(":question:") -%} +Severity: {{ severity }} {{ severity_emoji }} + +{%- set status = payload.status | default("Unknown") %} +{%- set status_emoji = {"firing": ":fire:", "resolved": ":white_check_mark:"}[status] | default(":warning:") %} +Status: {{ status }} {{ status_emoji }} (on the source) + +{% if "runbook_url" in annotations -%} +<{{ annotations.runbook_url }}|:book: Runbook:link:> +{%- set _ = annotations.pop('runbook_url') -%} +{%- endif %} + +{%- if "runbook_url_internal" in annotations -%} +<{{ annotations.runbook_url_internal }}|:closed_book: Runbook (internal):link:> +{%- set _ = annotations.pop('runbook_url_internal') -%} +{%- endif %} + +:label: Labels: +{%- for k, v in payload["labels"].items() %} +- {{ k }}: {{ v }} +{%- endfor %} + +{% if annotations | length > 0 -%} +:pushpin: Other annotations: +{%- for k, v in annotations.items() %} +- {{ k }}: {{ v }} +{%- endfor %} +{% endif %} +""" # noqa: W291 slack_image_url = None -web_title = """\ -{# Usually title is located in payload.labels.alertname #} -{{- payload.get("labels", {}).get("alertname", "No title (check Web Title Template)") }} -""" +# SMS +sms_title = web_title -web_message = """\ -{{- payload.message }} -{%- if "status" in payload %} -**Status**: {{ payload.status }} -{% endif -%} -**Labels:** {% for k, v in payload["labels"].items() %} -*{{ k }}*: {{ v }}{% endfor %} -**Annotations:** -{%- for k, v in payload.get("annotations", {}).items() %} -{#- render annotation as markdown url if it starts with http #} -*{{ k }}*: {% if v.startswith("http") %} [here]({{v}}){% else %} {{v}} {% endif -%} -{% endfor %} +# Phone +phone_call_title = web_title + +# Telegram +telegram_title = web_title + +# default telegram message template is identical to web message template, except urls +# It can be based on web message template (see example), but it can affect existing templates +# telegram_message = """ +# {% set mkdwn_link_regex = "\[([\w\s\d:]+)\]\((https?:\/\/[\w\d./?=#]+)\)" %} +# {{ web_message +# | regex_replace(mkdwn_link_regex, "\\1") +# }} +# """ +telegram_message = """\ +{%- set annotations = payload.annotations.copy() -%} +{%- set labels = payload.labels.copy() -%} + +{%- if "summary" in annotations %} +{{ annotations.summary }} +{%- set _ = annotations.pop('summary') -%} +{%- endif %} + +{%- if "message" in annotations %} +{{ annotations.message }} +{%- set _ = annotations.pop('message') -%} +{%- endif %} + +{% set severity = labels.severity | default("Unknown") -%} +{%- set severity_emoji = {"critical": ":rotating_light:", "warning": ":warning:" }[severity] | default(":question:") -%} +Severity: {{ severity }} {{ severity_emoji }} + +{%- set status = payload.status | default("Unknown") %} +{%- set status_emoji = {"firing": ":fire:", "resolved": ":white_check_mark:"}[status] | default(":warning:") %} +Status: {{ status }} {{ status_emoji }} (on the source) + +{% if "runbook_url" in annotations -%} +:book: Runbook:link: +{%- set _ = annotations.pop('runbook_url') -%} +{%- endif %} + +{%- if "runbook_url_internal" in annotations -%} +:closed_book: Runbook (internal):link: +{%- set _ = annotations.pop('runbook_url_internal') -%} +{%- endif %} + +:label: Labels: +{%- for k, v in payload["labels"].items() %} +- {{ k }}: {{ v }} +{%- endfor %} + +{% if annotations | length > 0 -%} +:pushpin: Other annotations: +{%- for k, v in annotations.items() %} +- {{ k }}: {{ v }} +{%- endfor %} +{% endif %} """ # noqa: W291 - -web_image_url = slack_image_url - -sms_title = '{{ payload.get("labels", {}).get("alertname", "Title undefined") }}' -phone_call_title = sms_title - -telegram_title = sms_title - -telegram_message = """\ -{{- payload.messsage }} -{%- if "status" in payload -%} -Status: {{ payload.status }} -{% endif -%} -Labels: {% for k, v in payload["labels"].items() %} -{{ k }}: {{ v }}{% endfor %} -Annotations: -{%- for k, v in payload.get("annotations", {}).items() %} -{#- render annotation as markdown url if it starts with http #} -{{ k }}: {{ v }} -{% endfor %}""" # noqa: W291 - -telegram_image_url = slack_image_url - -source_link = "{{ payload.generatorURL }}" - -grouping_id = "{{ payload.labels }}" - -resolve_condition = """\ -{{ payload.get("status", "") == "resolved" }} -""" - -acknowledge_condition = None +telegram_image_url = None tests = { "payload": { @@ -131,36 +245,12 @@ tests = { "+-+kube_job_status_succeeded%7Bjob%3D%22kube-state-metrics%22%7D+%3E+0&g0.tab=1" "|source>*)" ), - "message": ( - "*Status*: firing\n" - "*Labels:* \n" - "job: kube-state-metrics\n" - "instance: 10.143.139.7:8443\n" - "job_name: email-tracking-perform-initialization-1.0.50\n" - "severity: warning\n" - "alertname: KubeJobCompletion\n" - "namespace: default\n" - "prometheus: monitoring/k8s\n" - "*Annotations:*\n" - "message: Job default/email-tracking-perform-initialization-1.0.50 is taking more than one hour to complete. \n" - "runbook_url: " - ), + "message": "\nJob default/email-tracking-perform-initialization-1.0.50 is taking more than one hour to complete.\n\n\n\nSeverity: warning :warning:\nStatus: firing :fire: (on the source)\n\n\n\n:label: Labels:\n- job: kube-state-metrics\n- instance: 10.143.139.7:8443\n- job_name: email-tracking-perform-initialization-1.0.50\n- severity: warning\n- alertname: KubeJobCompletion\n- namespace: default\n- prometheus: monitoring/k8s\n\n", # noqa "image_url": None, }, "web": { "title": "KubeJobCompletion", - "message": """

Status: firing
-Labels:
-job: kube-state-metrics
-instance: 10.143.139.7:8443
-job_name: email-tracking-perform-initialization-1.0.50
-severity: warning
-alertname: KubeJobCompletion
-namespace: default
-prometheus: monitoring/k8s
-Annotations:
-message: Job default/email-tracking-perform-initialization-1.0.50 is taking more than one hour to complete.
-runbook_url: here

""", # noqa + "message": '

Job default/email-tracking-perform-initialization-1.0.50 is taking more than one hour to complete.

\n

Severity: warning ⚠️
\nStatus: firing 🔥 (on the source)

\n

📖 Runbook🔗

\n

🏷️ Labels:

\n
    \n
  • job: kube-state-metrics
  • \n
  • instance: 10.143.139.7:8443
  • \n
  • job_name: email-tracking-perform-initialization-1.0.50
  • \n
  • severity: warning
  • \n
  • alertname: KubeJobCompletion
  • \n
  • namespace: default
  • \n
  • prometheus: monitoring/k8s
  • \n
', # noqa "image_url": None, }, "sms": { @@ -171,20 +261,7 @@ tests = { }, "telegram": { "title": "KubeJobCompletion", - "message": ( - "Status: firing\n" - "Labels: \n" - "job: kube-state-metrics\n" - "instance: 10.143.139.7:8443\n" - "job_name: email-tracking-perform-initialization-1.0.50\n" - "severity: warning\n" - "alertname: KubeJobCompletion\n" - "namespace: default\n" - "prometheus: monitoring/k8s\n" - "Annotations:\n" - "message: Job default/email-tracking-perform-initialization-1.0.50 is taking more than one hour to complete.\n\n" - "runbook_url: https://github.com/kubernetes-monitoring/kubernetes-mixin/tree/master/runbook.md#alert-name-kubejobcompletion\n" - ), + "message": "\nJob default/email-tracking-perform-initialization-1.0.50 is taking more than one hour to complete.\n\nSeverity: warning ⚠️\nStatus: firing 🔥 (on the source)\n\n📖 Runbook🔗\n\n🏷️ Labels:\n- job: kube-state-metrics\n- instance: 10.143.139.7:8443\n- job_name: email-tracking-perform-initialization-1.0.50\n- severity: warning\n- alertname: KubeJobCompletion\n- namespace: default\n- prometheus: monitoring/k8s\n\n", # noqa "image_url": None, }, } @@ -196,11 +273,12 @@ example_payload = { "alerts": [ { "status": "firing", - "labels": { - "alertname": "TestAlert", - "region": "eu-1", + "labels": {"alertname": "TestAlert", "region": "eu-1", "severity": "critical"}, + "annotations": { + "message": "This is test alert", + "description": "This alert was sent by user for the demonstration purposes", + "runbook_url": "https://grafana.com/", }, - "annotations": {"description": "This alert was sent by user for the demonstration purposes"}, "startsAt": "2018-12-25T15:47:47.377363608Z", "endsAt": "0001-01-01T00:00:00Z", "generatorURL": "", From 129bd983286b01b31bb44e038095c69247b7d87a Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Tue, 23 May 2023 10:55:18 +0300 Subject: [PATCH 06/10] Refactored templates within templates&grouping (#1981) # What this PR does As the title says -> Refactored templates within templates&grouping - File structure was changed - Templates config added instead of manually rendering each item, this way that config can be easily overwritten in oncall-private --- .../IntegrationBlock.module.scss | 0 .../Integrations}/IntegrationBlock.tsx | 0 .../IntegrationBlockItem.module.scss | 0 .../Integrations}/IntegrationBlockItem.tsx | 0 .../IntegrationTemplateBlock.module.scss | 0 .../IntegrationTemplateBlock.tsx | 0 ...llapsedIntegrationRouteDisplay.module.scss | 0 .../CollapsedIntegrationRouteDisplay.tsx | 9 +- ...xpandedIntegrationRouteDisplay.module.scss | 0 .../ExpandedIntegrationRouteDisplay.tsx | 11 +- .../Integration2HeartbeatForm.tsx | 0 .../IntegrationTemplatesList.config.ts | 131 +++++ .../IntegrationTemplatesList.tsx | 131 +++++ .../src/pages/integration_2/Integration2.tsx | 17 +- .../Integration2HeartbeatForm.module.scss | 0 .../IntegrationTemplatesList.tsx | 458 ------------------ 16 files changed, 279 insertions(+), 478 deletions(-) rename grafana-plugin/src/{pages/integration_2 => components/Integrations}/IntegrationBlock.module.scss (100%) rename grafana-plugin/src/{pages/integration_2 => components/Integrations}/IntegrationBlock.tsx (100%) rename grafana-plugin/src/{pages/integration_2 => components/Integrations}/IntegrationBlockItem.module.scss (100%) rename grafana-plugin/src/{pages/integration_2 => components/Integrations}/IntegrationBlockItem.tsx (100%) rename grafana-plugin/src/{pages/integration_2 => components/Integrations}/IntegrationTemplateBlock.module.scss (100%) rename grafana-plugin/src/{pages/integration_2 => components/Integrations}/IntegrationTemplateBlock.tsx (100%) rename grafana-plugin/src/{pages/integration_2 => containers/IntegrationContainers/CollapsedIntegrationRouteDisplay}/CollapsedIntegrationRouteDisplay.module.scss (100%) rename grafana-plugin/src/{pages/integration_2 => containers/IntegrationContainers/CollapsedIntegrationRouteDisplay}/CollapsedIntegrationRouteDisplay.tsx (92%) rename grafana-plugin/src/{pages/integration_2 => containers/IntegrationContainers/ExpandedIntegrationRouteDisplay}/ExpandedIntegrationRouteDisplay.module.scss (100%) rename grafana-plugin/src/{pages/integration_2 => containers/IntegrationContainers/ExpandedIntegrationRouteDisplay}/ExpandedIntegrationRouteDisplay.tsx (96%) rename grafana-plugin/src/{pages/integration_2 => containers/IntegrationContainers/Integration2HearbeatForm}/Integration2HeartbeatForm.tsx (100%) create mode 100644 grafana-plugin/src/containers/IntegrationContainers/IntegrationTemplatesList.config.ts create mode 100644 grafana-plugin/src/containers/IntegrationContainers/IntegrationTemplatesList.tsx delete mode 100644 grafana-plugin/src/pages/integration_2/Integration2HeartbeatForm.module.scss delete mode 100644 grafana-plugin/src/pages/integration_2/IntegrationTemplatesList.tsx diff --git a/grafana-plugin/src/pages/integration_2/IntegrationBlock.module.scss b/grafana-plugin/src/components/Integrations/IntegrationBlock.module.scss similarity index 100% rename from grafana-plugin/src/pages/integration_2/IntegrationBlock.module.scss rename to grafana-plugin/src/components/Integrations/IntegrationBlock.module.scss diff --git a/grafana-plugin/src/pages/integration_2/IntegrationBlock.tsx b/grafana-plugin/src/components/Integrations/IntegrationBlock.tsx similarity index 100% rename from grafana-plugin/src/pages/integration_2/IntegrationBlock.tsx rename to grafana-plugin/src/components/Integrations/IntegrationBlock.tsx diff --git a/grafana-plugin/src/pages/integration_2/IntegrationBlockItem.module.scss b/grafana-plugin/src/components/Integrations/IntegrationBlockItem.module.scss similarity index 100% rename from grafana-plugin/src/pages/integration_2/IntegrationBlockItem.module.scss rename to grafana-plugin/src/components/Integrations/IntegrationBlockItem.module.scss diff --git a/grafana-plugin/src/pages/integration_2/IntegrationBlockItem.tsx b/grafana-plugin/src/components/Integrations/IntegrationBlockItem.tsx similarity index 100% rename from grafana-plugin/src/pages/integration_2/IntegrationBlockItem.tsx rename to grafana-plugin/src/components/Integrations/IntegrationBlockItem.tsx diff --git a/grafana-plugin/src/pages/integration_2/IntegrationTemplateBlock.module.scss b/grafana-plugin/src/components/Integrations/IntegrationTemplateBlock.module.scss similarity index 100% rename from grafana-plugin/src/pages/integration_2/IntegrationTemplateBlock.module.scss rename to grafana-plugin/src/components/Integrations/IntegrationTemplateBlock.module.scss diff --git a/grafana-plugin/src/pages/integration_2/IntegrationTemplateBlock.tsx b/grafana-plugin/src/components/Integrations/IntegrationTemplateBlock.tsx similarity index 100% rename from grafana-plugin/src/pages/integration_2/IntegrationTemplateBlock.tsx rename to grafana-plugin/src/components/Integrations/IntegrationTemplateBlock.tsx diff --git a/grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.module.scss b/grafana-plugin/src/containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay.module.scss similarity index 100% rename from grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.module.scss rename to grafana-plugin/src/containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay.module.scss diff --git a/grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.tsx b/grafana-plugin/src/containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay.tsx similarity index 92% rename from grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.tsx rename to grafana-plugin/src/containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay.tsx index fd261823..82e9b68f 100644 --- a/grafana-plugin/src/pages/integration_2/CollapsedIntegrationRouteDisplay.tsx +++ b/grafana-plugin/src/containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay.tsx @@ -4,18 +4,17 @@ import { ConfirmModal, HorizontalGroup, Icon, VerticalGroup } from '@grafana/ui' import cn from 'classnames/bind'; import { observer } from 'mobx-react'; +import IntegrationBlock from 'components/Integrations/IntegrationBlock'; import PluginLink from 'components/PluginLink/PluginLink'; import Text from 'components/Text/Text'; import TooltipBadge from 'components/TooltipBadge/TooltipBadge'; +import styles from 'containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay.module.scss'; +import { RouteButtonsDisplay } from 'containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay'; import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; import { ChannelFilter } from 'models/channel_filter'; +import IntegrationHelper from 'pages/integration_2/Integration2.helper'; import { useStore } from 'state/useStore'; -import styles from './CollapsedIntegrationRouteDisplay.module.scss'; -import { RouteButtonsDisplay } from './ExpandedIntegrationRouteDisplay'; -import IntegrationHelper from './Integration2.helper'; -import IntegrationBlock from './IntegrationBlock'; - const cx = cn.bind(styles); interface CollapsedIntegrationRouteDisplayProps { diff --git a/grafana-plugin/src/pages/integration_2/ExpandedIntegrationRouteDisplay.module.scss b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.module.scss similarity index 100% rename from grafana-plugin/src/pages/integration_2/ExpandedIntegrationRouteDisplay.module.scss rename to grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.module.scss diff --git a/grafana-plugin/src/pages/integration_2/ExpandedIntegrationRouteDisplay.tsx b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx similarity index 96% rename from grafana-plugin/src/pages/integration_2/ExpandedIntegrationRouteDisplay.tsx rename to grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx index 6036a66f..41446dd7 100644 --- a/grafana-plugin/src/pages/integration_2/ExpandedIntegrationRouteDisplay.tsx +++ b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx @@ -5,6 +5,8 @@ import { Button, HorizontalGroup, InlineLabel, VerticalGroup, Icon, Tooltip, Con import cn from 'classnames/bind'; import { observer } from 'mobx-react'; +import IntegrationBlock from 'components/Integrations/IntegrationBlock'; +import IntegrationBlockItem from 'components/Integrations/IntegrationBlockItem'; import MonacoEditor from 'components/MonacoEditor/MonacoEditor'; import PluginLink from 'components/PluginLink/PluginLink'; import Text from 'components/Text/Text'; @@ -12,21 +14,18 @@ import TooltipBadge from 'components/TooltipBadge/TooltipBadge'; import { ChatOpsConnectors } from 'containers/AlertRules/parts'; import EscalationChainSteps from 'containers/EscalationChainSteps/EscalationChainSteps'; import GSelect from 'containers/GSelect/GSelect'; +import styles from 'containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.module.scss'; import TeamName from 'containers/TeamName/TeamName'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; import { AlertTemplatesDTO } from 'models/alert_templates'; import { ChannelFilter } from 'models/channel_filter/channel_filter.types'; +import { MONACO_INPUT_HEIGHT_SMALL, MONACO_OPTIONS } from 'pages/integration_2/Integration2.config'; +import IntegrationHelper from 'pages/integration_2/Integration2.helper'; import { AppFeature } from 'state/features'; import { useStore } from 'state/useStore'; import { UserActions } from 'utils/authorization'; -import styles from './ExpandedIntegrationRouteDisplay.module.scss'; -import { MONACO_INPUT_HEIGHT_SMALL, MONACO_OPTIONS } from './Integration2.config'; -import IntegrationHelper from './Integration2.helper'; -import IntegrationBlock from './IntegrationBlock'; -import IntegrationBlockItem from './IntegrationBlockItem'; - const cx = cn.bind(styles); interface ExpandedIntegrationRouteDisplayProps { diff --git a/grafana-plugin/src/pages/integration_2/Integration2HeartbeatForm.tsx b/grafana-plugin/src/containers/IntegrationContainers/Integration2HearbeatForm/Integration2HeartbeatForm.tsx similarity index 100% rename from grafana-plugin/src/pages/integration_2/Integration2HeartbeatForm.tsx rename to grafana-plugin/src/containers/IntegrationContainers/Integration2HearbeatForm/Integration2HeartbeatForm.tsx diff --git a/grafana-plugin/src/containers/IntegrationContainers/IntegrationTemplatesList.config.ts b/grafana-plugin/src/containers/IntegrationContainers/IntegrationTemplatesList.config.ts new file mode 100644 index 00000000..8ebc1958 --- /dev/null +++ b/grafana-plugin/src/containers/IntegrationContainers/IntegrationTemplatesList.config.ts @@ -0,0 +1,131 @@ +import { MONACO_INPUT_HEIGHT_SMALL, MONACO_INPUT_HEIGHT_TALL } from 'pages/integration_2/Integration2.config'; + +interface TemplateToRender { + name: string; + label: string; + height: string; +} + +interface TemplateBlock { + name: string; + contents: TemplateToRender[]; +} + +export const templatesToRender: TemplateBlock[] = [ + { + name: null, + contents: [ + { + name: 'grouping_id_template', + label: 'Grouping', + height: MONACO_INPUT_HEIGHT_TALL, + }, + { + name: 'resolve_condition_template', + label: 'Auto resolve', + height: MONACO_INPUT_HEIGHT_SMALL, + }, + ], + }, + { + name: 'Web', + contents: [ + { + name: 'web_title_template', + label: 'Title', + height: MONACO_INPUT_HEIGHT_TALL, + }, + { + name: 'web_message_template', + label: 'Message', + height: MONACO_INPUT_HEIGHT_TALL, + }, + { + name: 'web_image_url_template', + label: 'Image', + height: MONACO_INPUT_HEIGHT_SMALL, + }, + ], + }, + { + name: null, + contents: [ + { + name: 'acknowledge_condition_template', + label: 'Auto acknowledge', + height: MONACO_INPUT_HEIGHT_SMALL, + }, + { + name: 'source_link_template', + label: 'Source link', + height: MONACO_INPUT_HEIGHT_SMALL, + }, + ], + }, + { + name: null, + contents: [ + { + name: 'phone_call_title_template', + label: 'Phone Call', + height: MONACO_INPUT_HEIGHT_SMALL, + }, + { + name: 'sms_title_template', + label: 'SMS', + height: MONACO_INPUT_HEIGHT_SMALL, + }, + ], + }, + { + name: 'Slack', + contents: [ + { + name: 'slack_title_template', + label: 'Title', + height: MONACO_INPUT_HEIGHT_SMALL, + }, + { + name: 'slack_message_template', + label: 'Message', + height: MONACO_INPUT_HEIGHT_TALL, + }, + { + name: 'slack_image_url_template', + label: 'Image', + height: MONACO_INPUT_HEIGHT_SMALL, + }, + ], + }, + { + name: 'Telegram', + contents: [ + { + name: 'telegram_title_template', + label: 'Title', + height: MONACO_INPUT_HEIGHT_SMALL, + }, + { + name: 'telegram_message_template', + label: 'Message', + height: MONACO_INPUT_HEIGHT_TALL, + }, + { + name: 'telegram_image_url_template', + label: 'Image', + height: MONACO_INPUT_HEIGHT_SMALL, + }, + ], + }, + { + name: 'Email', + contents: [ + { + name: 'email_title_template', + label: 'Title', + height: MONACO_INPUT_HEIGHT_SMALL, + }, + { name: 'email_message_template', label: 'Message', height: MONACO_INPUT_HEIGHT_TALL }, + ], + }, +]; diff --git a/grafana-plugin/src/containers/IntegrationContainers/IntegrationTemplatesList.tsx b/grafana-plugin/src/containers/IntegrationContainers/IntegrationTemplatesList.tsx new file mode 100644 index 00000000..32e783f0 --- /dev/null +++ b/grafana-plugin/src/containers/IntegrationContainers/IntegrationTemplatesList.tsx @@ -0,0 +1,131 @@ +import React, { useState } from 'react'; + +import { ConfirmModal } from '@grafana/ui'; +import cn from 'classnames/bind'; + +import IntegrationBlockItem from 'components/Integrations/IntegrationBlockItem'; +import IntegrationTemplateBlock from 'components/Integrations/IntegrationTemplateBlock'; +import MonacoEditor from 'components/MonacoEditor/MonacoEditor'; +import Text from 'components/Text/Text'; +import { templatesToRender } from 'containers/IntegrationContainers/IntegrationTemplatesList.config'; +import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; +import { AlertTemplatesDTO } from 'models/alert_templates'; +import { MONACO_INPUT_HEIGHT_TALL, MONACO_OPTIONS } from 'pages/integration_2/Integration2.config'; +import IntegrationHelper from 'pages/integration_2/Integration2.helper'; +import styles from 'pages/integration_2/Integration2.module.scss'; +import { useStore } from 'state/useStore'; +import { openErrorNotification, openNotification } from 'utils'; + +const cx = cn.bind(styles); + +interface IntegrationTemplateListProps { + templates: AlertTemplatesDTO[]; + alertReceiveChannelId: AlertReceiveChannel['id']; + openEditTemplateModal: (templateName: string | string[]) => void; +} + +const IntegrationTemplateList: React.FC = ({ + templates, + openEditTemplateModal, + alertReceiveChannelId, +}) => { + const { alertReceiveChannelStore } = useStore(); + const [isRestoringTemplate, setIsRestoringTemplate] = useState(false); + const [templateRestoreName, setTemplateRestoreName] = useState(undefined); + const [showConfirmModal, setShowConfirmModal] = useState(false); + + return ( +
+ {showConfirmModal && ( + onResetTemplate(templateRestoreName)} + onDismiss={() => onDismiss()} + /> + )} + + + + Templates are used to interpret alert from monitoring. Reduce noise, customize visualization + + + + {templatesToRender.map((template, key) => ( + + + {template.name && {template.name}} + + {template.contents.map((contents, innerKey) => ( + onShowConfirmModal(contents.name)} + label={contents.label} + renderInput={() => ( +
+ +
+ )} + onEdit={() => openEditTemplateModal(contents.name)} + /> + ))} +
+
+ ))} +
+ ); + + function onShowConfirmModal(templateName: string) { + setTemplateRestoreName(templateName); + setShowConfirmModal(true); + } + + function onDismiss() { + setTemplateRestoreName(undefined); + setShowConfirmModal(false); + } + + 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); + } + }) + .finally(() => { + setIsRestoringTemplate(false); + setShowConfirmModal(false); + }); + } +}; + +const VerticalBlock: React.FC<{ children: any[] }> = ({ children }) => { + return
{children}
; +}; + +export default IntegrationTemplateList; diff --git a/grafana-plugin/src/pages/integration_2/Integration2.tsx b/grafana-plugin/src/pages/integration_2/Integration2.tsx index d1080f21..470079b6 100644 --- a/grafana-plugin/src/pages/integration_2/Integration2.tsx +++ b/grafana-plugin/src/pages/integration_2/Integration2.tsx @@ -27,6 +27,7 @@ import IntegrationCollapsibleTreeView, { } from 'components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView'; import IntegrationInputField from 'components/IntegrationInputField/IntegrationInputField'; import IntegrationLogo from 'components/IntegrationLogo/IntegrationLogo'; +import IntegrationBlock from 'components/Integrations/IntegrationBlock'; import MonacoEditor, { MONACO_LANGUAGE } from 'components/MonacoEditor/MonacoEditor'; import PageErrorHandlingWrapper, { PageBaseState } from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper'; import { initErrorDataState } from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.helpers'; @@ -36,6 +37,10 @@ import Text from 'components/Text/Text'; import TooltipBadge from 'components/TooltipBadge/TooltipBadge'; import { WithContextMenu } from 'components/WithContextMenu/WithContextMenu'; import EditRegexpRouteTemplateModal from 'containers/EditRegexpRouteTemplateModal/EditRegexpRouteTemplateModal'; +import CollapsedIntegrationRouteDisplay from 'containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay'; +import ExpandedIntegrationRouteDisplay from 'containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay'; +import Integration2HeartbeatForm from 'containers/IntegrationContainers/Integration2HearbeatForm/Integration2HeartbeatForm'; +import IntegrationTemplateList from 'containers/IntegrationContainers/IntegrationTemplatesList'; import IntegrationForm2 from 'containers/IntegrationForm/IntegrationForm2'; import IntegrationTemplate from 'containers/IntegrationTemplate/IntegrationTemplate'; import MaintenanceForm from 'containers/MaintenanceForm/MaintenanceForm'; @@ -47,6 +52,9 @@ import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_ import { ChannelFilter } from 'models/channel_filter'; import { MaintenanceType } from 'models/maintenance/maintenance.types'; import { API_HOST, API_PATH_PREFIX } from 'network'; +import { INTEGRATION_TEMPLATES_LIST, MONACO_PAYLOAD_OPTIONS } from 'pages/integration_2/Integration2.config'; +import IntegrationHelper from 'pages/integration_2/Integration2.helper'; +import styles from 'pages/integration_2/Integration2.module.scss'; import { PageProps, WithStoreProps } from 'state/types'; import { useStore } from 'state/useStore'; import { withMobXProviderContext } from 'state/withStore'; @@ -56,15 +64,6 @@ import LocationHelper from 'utils/LocationHelper'; import { UserActions } from 'utils/authorization'; import { DATASOURCE_ALERTING, PLUGIN_ROOT } from 'utils/consts'; -import CollapsedIntegrationRouteDisplay from './CollapsedIntegrationRouteDisplay'; -import ExpandedIntegrationRouteDisplay from './ExpandedIntegrationRouteDisplay'; -import { INTEGRATION_TEMPLATES_LIST, MONACO_PAYLOAD_OPTIONS } from './Integration2.config'; -import IntegrationHelper from './Integration2.helper'; -import styles from './Integration2.module.scss'; -import Integration2HeartbeatForm from './Integration2HeartbeatForm'; -import IntegrationBlock from './IntegrationBlock'; -import IntegrationTemplateList from './IntegrationTemplatesList'; - const cx = cn.bind(styles); interface Integration2Props extends WithStoreProps, PageProps, RouteComponentProps<{ id: string }> {} diff --git a/grafana-plugin/src/pages/integration_2/Integration2HeartbeatForm.module.scss b/grafana-plugin/src/pages/integration_2/Integration2HeartbeatForm.module.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/grafana-plugin/src/pages/integration_2/IntegrationTemplatesList.tsx b/grafana-plugin/src/pages/integration_2/IntegrationTemplatesList.tsx deleted file mode 100644 index 9f6fe760..00000000 --- a/grafana-plugin/src/pages/integration_2/IntegrationTemplatesList.tsx +++ /dev/null @@ -1,458 +0,0 @@ -import React, { useState } from 'react'; - -import { ConfirmModal } from '@grafana/ui'; -import cn from 'classnames/bind'; - -import MonacoEditor from 'components/MonacoEditor/MonacoEditor'; -import Text from 'components/Text/Text'; -import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; -import { AlertTemplatesDTO } from 'models/alert_templates'; -import { useStore } from 'state/useStore'; -import { openErrorNotification, openNotification } from 'utils'; - -import { MONACO_INPUT_HEIGHT_SMALL, MONACO_INPUT_HEIGHT_TALL, MONACO_OPTIONS } from './Integration2.config'; -import IntegrationHelper from './Integration2.helper'; -import styles from './Integration2.module.scss'; -import IntegrationBlockItem from './IntegrationBlockItem'; -import IntegrationTemplateBlock from './IntegrationTemplateBlock'; - -const cx = cn.bind(styles); - -interface IntegrationTemplateListProps { - templates: AlertTemplatesDTO[]; - alertReceiveChannelId: AlertReceiveChannel['id']; - openEditTemplateModal: (templateName: string | string[]) => void; -} - -const IntegrationTemplateList: React.FC = ({ - templates, - openEditTemplateModal, - alertReceiveChannelId, -}) => { - const { alertReceiveChannelStore } = useStore(); - const [isRestoringTemplate, setIsRestoringTemplate] = useState(false); - const [templateRestoreName, setTemplateRestoreName] = useState(undefined); - const [showConfirmModal, setShowConfirmModal] = useState(false); - - return ( -
- {showConfirmModal && ( - onResetTemplate(templateRestoreName)} - onDismiss={() => onDismiss()} - /> - )} - - - - Templates are used to interpret alert from monitoring. Reduce noise, customize visualization - - - - - - onShowConfirmModal('grouping_id_template')} - isLoading={isRestoringTemplate && templateRestoreName === 'grouping_id_template'} - label={'Grouping'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('grouping_id_template')} - /> - - onShowConfirmModal('resolve_condition_template')} - label={'Auto resolve'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('resolve_condition_template')} - /> -
-
- - - - Web - - onShowConfirmModal('web_title_template')} - label={'Title'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('web_title_template')} - /> - - onShowConfirmModal('web_message_template')} - label={'Message'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('web_message_template')} - /> - - onShowConfirmModal('web_image_url_template')} - label={'Image'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('web_image_url_template')} - /> -
-
- - - - onShowConfirmModal('acknowledge_condition_template')} - label={'Auto acknowledge'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('acknowledge_condition_template')} - /> - - onShowConfirmModal('source_link_template')} - label={'Source Link'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('source_link_template')} - /> -
-
- - - - onShowConfirmModal('phone_call_title_template')} - label={'Phone Call'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('phone_call_title_template')} - /> - - onShowConfirmModal('sms_title_template')} - label={'SMS'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('sms_title_template')} - /> -
-
- - - - Slack - - onShowConfirmModal('slack_title_template')} - label={'Title'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('slack_title_template')} - /> - - onShowConfirmModal('slack_message_template')} - label={'Message'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('slack_message_template')} - /> - - onShowConfirmModal('slack_image_url_template')} - label={'Image'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('slack_image_url_template')} - /> -
-
- - - - Telegram - onShowConfirmModal('telegram_title_template')} - label={'Title'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('telegram_title_template')} - /> - - onShowConfirmModal('telegram_message_template')} - label={'Message'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('telegram_message_template')} - /> - - onShowConfirmModal('telegram_image_url_template')} - label={'Image'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('telegram_image_url_template')} - /> -
-
- - - - Email - onShowConfirmModal('email_title_template')} - label={'Title'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('email_title_template')} - /> - - onShowConfirmModal('email_message_template')} - label={'Message'} - renderInput={() => ( -
- -
- )} - onEdit={() => openEditTemplateModal('email_message_template')} - /> -
-
-
- ); - - function onShowConfirmModal(templateName: string) { - setTemplateRestoreName(templateName); - setShowConfirmModal(true); - } - - function onDismiss() { - setTemplateRestoreName(undefined); - setShowConfirmModal(false); - } - - 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); - } - }) - .finally(() => { - setIsRestoringTemplate(false); - setShowConfirmModal(false); - }); - } -}; - -const VerticalBlock: React.FC<{ children: React.ReactElement[] }> = ({ children }) => { - return
{children}
; -}; - -export default IntegrationTemplateList; From d43ce506b849255dfd658f4ca7b870eb6b7d32a3 Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Tue, 23 May 2023 11:20:03 +0300 Subject: [PATCH 07/10] Fixed delete integration copy (#1988) # What this PR does Fixed delete integration copy --- .../src/pages/integration_2/Integration2.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/grafana-plugin/src/pages/integration_2/Integration2.tsx b/grafana-plugin/src/pages/integration_2/Integration2.tsx index 470079b6..58309bf8 100644 --- a/grafana-plugin/src/pages/integration_2/Integration2.tsx +++ b/grafana-plugin/src/pages/integration_2/Integration2.tsx @@ -908,13 +908,13 @@ const IntegrationActions: React.FC = ({ alertReceiveCha onClick={() => { setConfirmModal({ isOpen: true, - title: ( - <> + title: 'Delete Integration?', + body: ( + Are you sure you want to delete {' '} - integration? - + integration?{' '} + ), - body: <>This action cannot be undone., onConfirm: deleteIntegration, dismissText: 'Cancel', confirmText: 'Delete', From 98fb0ad5f54a594a3f3f1f6831e62707f58ec445 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Tue, 23 May 2023 16:54:34 +0800 Subject: [PATCH 08/10] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98fb514e..7ca11ba8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Allow passing Firebase credentials via environment variable by @vadimkerr ([#1969](https://github.com/grafana/oncall/pull/1969)) +- Update default Alertmanager templates by @iskhakov ([#1944](https://github.com/grafana/oncall/pull/1944)) ### Fixed From 0991ef80dba7fbc0d6b229367be4bfeacb2bf1b7 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Tue, 23 May 2023 16:55:06 +0800 Subject: [PATCH 09/10] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ca11ba8..4f1364fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Allow passing Firebase credentials via environment variable by @vadimkerr ([#1969](https://github.com/grafana/oncall/pull/1969)) + +### Changed + - Update default Alertmanager templates by @iskhakov ([#1944](https://github.com/grafana/oncall/pull/1944)) ### Fixed From aedd830184de83f3a32f870beaf511feba9f7562 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Tue, 23 May 2023 10:25:43 +0100 Subject: [PATCH 10/10] v1.2.27 changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f1364fe..78bfcd28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## v1.2.27 (2023-05-23) ### Added