From a1e4f722805ae8e2f3831bbbb9b5f2286662b779 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Fri, 6 Jan 2023 11:34:11 +0000 Subject: [PATCH 01/10] remove send_link_to_channel_message_or_fallback_to_full_incident --- engine/apps/telegram/tasks.py | 13 ------------- engine/settings/prod_without_db.py | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/engine/apps/telegram/tasks.py b/engine/apps/telegram/tasks.py index 6b67d1c6..57b2b373 100644 --- a/engine/apps/telegram/tasks.py +++ b/engine/apps/telegram/tasks.py @@ -111,19 +111,6 @@ def send_link_to_channel_message_or_fallback_to_full_alert_group( ) -@shared_dedicated_queue_retry_task(bind=True, autoretry_for=(Exception,), retry_backoff=True, max_retries=None) -def send_link_to_channel_message_or_fallback_to_full_incident( - self, alert_group_pk, notification_policy_pk, user_connector_pk -): - """ - Deprecated task, use send_link_to_channel_message_or_fallback_to_full_alert_group above instead. - TODO: remove this task after releasing the new version of the task - """ - send_link_to_channel_message_or_fallback_to_full_alert_group( - self, alert_group_pk, notification_policy_pk, user_connector_pk - ) - - @shared_dedicated_queue_retry_task( bind=True, autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else None ) diff --git a/engine/settings/prod_without_db.py b/engine/settings/prod_without_db.py index ba5e5a33..7aa510cf 100644 --- a/engine/settings/prod_without_db.py +++ b/engine/settings/prod_without_db.py @@ -143,7 +143,7 @@ CELERY_TASK_ROUTES = { "apps.telegram.tasks.edit_message": {"queue": "telegram"}, "apps.telegram.tasks.on_create_alert_telegram_representative_async": {"queue": "telegram"}, "apps.telegram.tasks.register_telegram_webhook": {"queue": "telegram"}, - "apps.telegram.tasks.send_link_to_channel_message_or_fallback_to_full_incident": {"queue": "telegram"}, + "apps.telegram.tasks.send_link_to_channel_message_or_fallback_to_full_alert_group": {"queue": "telegram"}, "apps.telegram.tasks.send_log_and_actions_message": {"queue": "telegram"}, # WEBHOOK "apps.alerts.tasks.custom_button_result.custom_button_result": {"queue": "webhook"}, From d8dcb673da630789f69f56acbc3ff2f7f751ec60 Mon Sep 17 00:00:00 2001 From: Rares Mardare <40542072+teodosii@users.noreply.github.com> Date: Fri, 6 Jan 2023 15:36:17 +0200 Subject: [PATCH 02/10] Move alerts inside PageNav (#1040) # What this PR does Fix for #904 - TopNav: Move Alerts inside PluginPage Fix for #908 - Horizontal menu scrolling fix for tabs There's also a few styling tweaks to be more in match with Grafana core styles. --- grafana-plugin/src/PluginPage.tsx | 19 +- .../DefaultPageLayout.module.scss | 27 +- .../DefaultPageLayout/DefaultPageLayout.tsx | 60 +++- .../GrafanaTeamSelect.module.scss | 19 +- .../GrafanaTeamSelect/GrafanaTeamSelect.tsx | 12 +- .../src/img/grafanaGlobalStyles.css | 44 +-- .../src/navbar/Header/Header.module.scss | 10 + grafana-plugin/src/navbar/Header/Header.tsx | 10 +- .../src/navbar/LegacyNavTabsBar.module.scss | 3 +- .../src/navbar/LegacyNavTabsBar.tsx | 24 +- .../escalation-chains/EscalationChains.tsx | 146 +++++---- .../src/pages/incident/Incident.module.css | 4 + .../src/pages/incident/Incident.tsx | 130 ++++---- .../src/pages/incidents/Incidents.tsx | 12 +- .../src/pages/integrations/Integrations.tsx | 206 +++++++------ .../src/pages/maintenance/Maintenance.tsx | 6 +- .../organization-logs/OrganizationLog.tsx | 69 +++-- .../outgoing_webhooks/OutgoingWebhooks.tsx | 94 +++--- .../src/pages/schedule/Schedule.tsx | 278 +++++++++--------- .../src/pages/schedules/Schedules.tsx | 6 +- .../src/pages/settings/SettingsPage.tsx | 7 +- grafana-plugin/src/pages/test/Test.tsx | 13 +- grafana-plugin/src/pages/users/Users.tsx | 150 +++++----- .../src/plugin/GrafanaPluginRootPage.tsx | 16 +- grafana-plugin/src/style/utils.css | 12 + grafana-plugin/src/utils/consts.ts | 2 + 26 files changed, 689 insertions(+), 690 deletions(-) diff --git a/grafana-plugin/src/PluginPage.tsx b/grafana-plugin/src/PluginPage.tsx index 43e0d91f..59a6188d 100644 --- a/grafana-plugin/src/PluginPage.tsx +++ b/grafana-plugin/src/PluginPage.tsx @@ -6,25 +6,34 @@ import Header from 'navbar/Header/Header'; import { pages } from 'pages'; import { isTopNavbar } from 'plugin/GrafanaPluginRootPage.helpers'; import { useStore } from 'state/useStore'; +import { DEFAULT_PAGE } from 'utils/consts'; import { useQueryParams } from 'utils/hooks'; -export const PluginPage = (isTopNavbar() ? RealPlugin : PluginPageFallback) as React.ComponentType; +export const PluginPage = ( + isTopNavbar() ? RealPlugin : PluginPageFallback +) as React.ComponentType; -function RealPlugin(props: PluginPageProps): React.ReactNode { +interface ExtendedPluginPageProps extends PluginPageProps { + renderAlertsFn?: () => React.ReactNode; +} + +function RealPlugin(props: ExtendedPluginPageProps): React.ReactNode { const store = useStore(); const queryParams = useQueryParams(); - const page = queryParams.get('page'); + const page = queryParams.get('page') || DEFAULT_PAGE; return ( + {/* Render alerts at the top */} + {props.renderAlertsFn && props.renderAlertsFn()}
-

{pages[page].text}

+ {pages[page].text &&

{pages[page].text}

} {props.children} ); } -function PluginPageFallback(props: PluginPageProps): React.ReactNode { +function PluginPageFallback(props: ExtendedPluginPageProps): React.ReactNode { return props.children; } diff --git a/grafana-plugin/src/containers/DefaultPageLayout/DefaultPageLayout.module.scss b/grafana-plugin/src/containers/DefaultPageLayout/DefaultPageLayout.module.scss index 4eb9b584..8526fd34 100644 --- a/grafana-plugin/src/containers/DefaultPageLayout/DefaultPageLayout.module.scss +++ b/grafana-plugin/src/containers/DefaultPageLayout/DefaultPageLayout.module.scss @@ -1,15 +1,32 @@ .root { + width: 100%; height: 100%; display: flex; flex-direction: column; +} - .alerts_horizontal { - display: flex; - gap: 10px; +.alerts-container { + display: flex; + flex-direction: column; + margin-bottom: 24px; + gap: 10px; + + &:empty { + display: none; } +} - .alert { - margin: 24px 0; +.navbar-legacy .alerts-container { + padding-top: 10px; +} + +.alert { + margin: 0; +} + +@media (max-width: 768px) { + .navbar-legacy { + padding-top: 50px; } } diff --git a/grafana-plugin/src/containers/DefaultPageLayout/DefaultPageLayout.tsx b/grafana-plugin/src/containers/DefaultPageLayout/DefaultPageLayout.tsx index e81aeada..cb2ebb2b 100644 --- a/grafana-plugin/src/containers/DefaultPageLayout/DefaultPageLayout.tsx +++ b/grafana-plugin/src/containers/DefaultPageLayout/DefaultPageLayout.tsx @@ -1,19 +1,24 @@ -import plugin from '../../../package.json'; // eslint-disable-line import React, { FC, useEffect, useState, useCallback } from 'react'; import { Alert } from '@grafana/ui'; +import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import { observer } from 'mobx-react'; import { AppRootProps } from 'types'; import PluginLink from 'components/PluginLink/PluginLink'; import { getIfChatOpsConnected } from 'containers/DefaultPageLayout/helper'; +import { pages } from 'pages'; +import { isTopNavbar } from 'plugin/GrafanaPluginRootPage.helpers'; import { AppFeature } from 'state/features'; import { useStore } from 'state/useStore'; import LocationHelper from 'utils/LocationHelper'; import { isUserActionAllowed, UserActions } from 'utils/authorization'; -import { GRAFANA_LICENSE_OSS } from 'utils/consts'; -import { useForceUpdate } from 'utils/hooks'; +import { DEFAULT_PAGE, GRAFANA_LICENSE_OSS } from 'utils/consts'; +import { useForceUpdate, useQueryParams } from 'utils/hooks'; + +import plugin from '../../../package.json'; // eslint-disable-line + import { getItem, setItem } from 'utils/localStorage'; import sanitize from 'utils/sanitize'; @@ -33,10 +38,12 @@ enum AlertID { const DefaultPageLayout: FC = observer((props) => { const { children, query } = props; + const queryParams = useQueryParams(); const [showSlackInstallAlert, setShowSlackInstallAlert] = useState(); const forceUpdate = useForceUpdate(); + const page = queryParams.get('page') || DEFAULT_PAGE; const handleCloseInstallSlackAlert = useCallback(() => { setShowSlackInstallAlert(undefined); @@ -68,15 +75,41 @@ const DefaultPageLayout: FC = observer((props) => { const isChatOpsConnected = getIfChatOpsConnected(currentUser); const isPhoneVerified = currentUser?.cloud_connection_status === 3 || currentUser?.verified_phone_number; - return ( -
-
+ if (isTopNavbar()) { + return renderTopNavbar(); + } + + return renderLegacyNavbar(); + + function renderTopNavbar(): JSX.Element { + return ( + +
{children}
+
+ ); + } + + function renderLegacyNavbar(): JSX.Element { + return ( + +
+
+ {renderAlertsFn()} + {children} +
+
+
+ ); + } + + function renderAlertsFn(): JSX.Element { + return ( +
{showSlackInstallAlert && ( {getSlackMessage( @@ -88,7 +121,7 @@ const DefaultPageLayout: FC = observer((props) => { )} {currentTeam?.banner.title != null && !getItem(currentTeam?.banner.title) && ( = observer((props) => { store.backendVersion !== plugin?.version && !getItem(`version_mismatch_${store.backendVersion}_${plugin?.version}`) && ( = observer((props) => { ) && ( = observer((props) => { )}
- {children} -
- ); + ); + } }); export default DefaultPageLayout; diff --git a/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.module.scss b/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.module.scss index 8c90a385..cea8e371 100644 --- a/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.module.scss +++ b/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.module.scss @@ -1,24 +1,17 @@ .teamSelect { width: 200px; - right: 0; - position: absolute; - padding: 16px 0; - margin-right: 24px; - - &--topRight { - right: 14px; - top: 12px; - } - &--topRightIncident { - right: 32px; - top: 36px; - } } .teamSelectLabel { display: flex; } +.teamSelectText, +.teamSelectLink { + line-height: 1.25; + margin-bottom: 4px; +} + .teamSelectLink { margin-left: auto; } diff --git a/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx b/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx index 857dc370..89d50118 100644 --- a/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx +++ b/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx @@ -49,13 +49,15 @@ const GrafanaTeamSelect = observer((props: GrafanaTeamSelectProps) => { }; const content = ( -
+
diff --git a/grafana-plugin/src/img/grafanaGlobalStyles.css b/grafana-plugin/src/img/grafanaGlobalStyles.css index 9e29d7ba..aaebbf02 100644 --- a/grafana-plugin/src/img/grafanaGlobalStyles.css +++ b/grafana-plugin/src/img/grafanaGlobalStyles.css @@ -4,24 +4,12 @@ max-width: unset !important; } -.oncall-header { - padding-top: 0; - padding-bottom: 36px; -} - -.scrollbar-view h1:first-child { - margin-bottom: 0 !important; -} - -.page-container.page-body { - flex-grow: 1 !important; +[class$='-page-header'] { + display: none; } .page-container { max-width: unset !important; - flex-grow: unset !important; - flex-basis: unset !important; - overflow-x: auto; } .page-scrollbar-content > div:first-child { @@ -34,34 +22,6 @@ margin-right: 8px; } -/* This is for Grafana 8, remove later */ -@media (max-width: 1540px) { - .page-header__tabs > ul > li > a > div { - display: none; - } -} - -@media (max-width: 1540px) { - .page-header__tabs > div > div > a > div { - display: none; - } -} - -@media (max-width: 1300px) { - .sidemenu { - position: fixed !important; - height: 100%; - } - - .main-view { - padding-left: 50px; - } - - .page-header__tabs li a { - white-space: nowrap; - } -} - .page-header__info-block { flex-grow: 1; /* Stretch the navigation subtitle panel */ } diff --git a/grafana-plugin/src/navbar/Header/Header.module.scss b/grafana-plugin/src/navbar/Header/Header.module.scss index 6df19f38..8fb7ee27 100644 --- a/grafana-plugin/src/navbar/Header/Header.module.scss +++ b/grafana-plugin/src/navbar/Header/Header.module.scss @@ -2,6 +2,11 @@ margin-right: 4px; } +.header-topnavbar { + padding-top: 0; + padding-bottom: 36px; +} + .navbar-heading { padding: 4px; margin: 0 0 0 8px; @@ -16,3 +21,8 @@ align-items: center; padding-top: 6px; } + +.navbar-left { + display: flex; + flex-basis: 100%; +} diff --git a/grafana-plugin/src/navbar/Header/Header.tsx b/grafana-plugin/src/navbar/Header/Header.tsx index f1463c8f..157e74b9 100644 --- a/grafana-plugin/src/navbar/Header/Header.tsx +++ b/grafana-plugin/src/navbar/Header/Header.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Card } from '@grafana/ui'; -import classnames from 'classnames'; import cn from 'classnames/bind'; import gitHubStarSVG from 'assets/img/github_star.svg'; @@ -16,15 +15,16 @@ const cx = cn.bind(styles); export default function Header({ page, backendLicense }: { page: string; backendLicense: string }) { return ( -
-
-
+
+
+
Grafana OnCall
{renderHeading()}
- +
+
diff --git a/grafana-plugin/src/navbar/LegacyNavTabsBar.module.scss b/grafana-plugin/src/navbar/LegacyNavTabsBar.module.scss index c3b2ca3c..be25eb95 100644 --- a/grafana-plugin/src/navbar/LegacyNavTabsBar.module.scss +++ b/grafana-plugin/src/navbar/LegacyNavTabsBar.module.scss @@ -1,3 +1,4 @@ .root { - min-width: 1500px; + overflow-x: auto; + white-space: nowrap; } diff --git a/grafana-plugin/src/navbar/LegacyNavTabsBar.tsx b/grafana-plugin/src/navbar/LegacyNavTabsBar.tsx index c6564d3c..e46599f8 100644 --- a/grafana-plugin/src/navbar/LegacyNavTabsBar.tsx +++ b/grafana-plugin/src/navbar/LegacyNavTabsBar.tsx @@ -19,16 +19,18 @@ export default function LegacyNavTabsBar({ currentPage }: { currentPage: string .filter((page) => (page.hideFromTabsFn ? !page.hideFromTabsFn(store) : !page.hideFromTabs)); return ( - - {navigationPages.map((page, index) => ( - - ))} - +
+ + {navigationPages.map((page, index) => ( + + ))} + +
); } diff --git a/grafana-plugin/src/pages/escalation-chains/EscalationChains.tsx b/grafana-plugin/src/pages/escalation-chains/EscalationChains.tsx index d01bd347..4570f32f 100644 --- a/grafana-plugin/src/pages/escalation-chains/EscalationChains.tsx +++ b/grafana-plugin/src/pages/escalation-chains/EscalationChains.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Button, HorizontalGroup, Icon, IconButton, LoadingPlaceholder, Tooltip, VerticalGroup } from '@grafana/ui'; -import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import { debounce } from 'lodash-es'; import { observer } from 'mobx-react'; @@ -25,7 +24,6 @@ import EscalationChainForm from 'containers/EscalationChainForm/EscalationChainF import EscalationChainSteps from 'containers/EscalationChainSteps/EscalationChainSteps'; import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; import { EscalationChain } from 'models/escalation_chain/escalation_chain.types'; -import { pages } from 'pages'; import { PageProps, WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; import LocationHelper from 'utils/LocationHelper'; @@ -135,90 +133,88 @@ class EscalationChainsPage extends React.Component - - {() => ( - <> -
-
- + + {() => ( + <> +
+
+ +
+ {!searchResult || searchResult.length ? ( +
+
+ + + +
+ {searchResult ? ( + + {(item) => } + + ) : ( + + )} +
+
+
{this.renderEscalation()}
- {!searchResult || searchResult.length ? ( -
-
- + ) : ( + + No escalations found, check your filtering and current team. + -
- {searchResult ? ( - - {(item) => } - - ) : ( - - )} -
-
-
{this.renderEscalation()}
-
- ) : ( - - No escalations found, check your filtering and current team. - - - - - } - /> - )} -
- {showCreateEscalationChainModal && ( - { - this.setState({ - showCreateEscalationChainModal: false, - escalationChainIdToCopy: undefined, - }); - }} - onUpdate={this.handleEscalationChainCreate} + + } /> )} - - )} -
- +
+ {showCreateEscalationChainModal && ( + { + this.setState({ + showCreateEscalationChainModal: false, + escalationChainIdToCopy: undefined, + }); + }} + onUpdate={this.handleEscalationChainCreate} + /> + )} + + )} + ); } diff --git a/grafana-plugin/src/pages/incident/Incident.module.css b/grafana-plugin/src/pages/incident/Incident.module.css index 1460c4e7..eda3f4b5 100644 --- a/grafana-plugin/src/pages/incident/Incident.module.css +++ b/grafana-plugin/src/pages/incident/Incident.module.css @@ -6,6 +6,10 @@ flex-grow: 1; } +.block { + padding: 0 0 20px 0; +} + .payload-subtitle { margin-bottom: var(--title-marginBottom); } diff --git a/grafana-plugin/src/pages/incident/Incident.tsx b/grafana-plugin/src/pages/incident/Incident.tsx index b155f4c4..af34dadc 100644 --- a/grafana-plugin/src/pages/incident/Incident.tsx +++ b/grafana-plugin/src/pages/incident/Incident.tsx @@ -14,7 +14,6 @@ import { Modal, Tooltip, } from '@grafana/ui'; -import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import { observer } from 'mobx-react'; import moment from 'moment-timezone'; @@ -46,7 +45,6 @@ import { GroupedAlert, } from 'models/alertgroup/alertgroup.types'; import { ResolutionNoteSourceTypesToDisplayName } from 'models/resolution_note/resolution_note.types'; -import { pages } from 'pages'; import { PageProps, WithStoreProps } from 'state/types'; import { useStore } from 'state/useStore'; import { withMobXProviderContext } from 'state/withStore'; @@ -127,71 +125,69 @@ class IncidentPage extends React.Component } return ( - - - {() => ( -
- {errorData.isNotFoundError ? ( -
- - 404 - Incident not found - - - - -
- ) : ( - <> - {this.renderHeader()} -
-
- - - -
-
{this.renderTimeline()}
+ + {() => ( +
+ {errorData.isNotFoundError ? ( +
+ + 404 + Incident not found + + + + +
+ ) : ( + <> + {this.renderHeader()} +
+
+ + +
- {showIntegrationSettings && ( - { - alertReceiveChannelStore.updateItem(incident.alert_receive_channel.id); - }} - onUpdateTemplates={() => { - store.alertGroupStore.getAlert(id); - }} - startTab={IntegrationSettingsTab.Templates} - id={incident.alert_receive_channel.id} - onHide={() => - this.setState({ - showIntegrationSettings: undefined, - }) - } - /> - )} - {showAttachIncidentForm && ( - { - this.setState({ - showAttachIncidentForm: false, - }); - }} - onUpdate={this.update} - /> - )} - - )} -
- )} - - +
{this.renderTimeline()}
+
+ {showIntegrationSettings && ( + { + alertReceiveChannelStore.updateItem(incident.alert_receive_channel.id); + }} + onUpdateTemplates={() => { + store.alertGroupStore.getAlert(id); + }} + startTab={IntegrationSettingsTab.Templates} + id={incident.alert_receive_channel.id} + onHide={() => + this.setState({ + showIntegrationSettings: undefined, + }) + } + /> + )} + {showAttachIncidentForm && ( + { + this.setState({ + showAttachIncidentForm: false, + }); + }} + onUpdate={this.update} + /> + )} + + )} +
+ )} + ); } @@ -210,7 +206,7 @@ class IncidentPage extends React.Component const showLinkTo = !incident.dependent_alert_groups.length && !incident.root_alert_group && !incident.resolved; return ( - + diff --git a/grafana-plugin/src/pages/incidents/Incidents.tsx b/grafana-plugin/src/pages/incidents/Incidents.tsx index 10503e7b..ec980869 100644 --- a/grafana-plugin/src/pages/incidents/Incidents.tsx +++ b/grafana-plugin/src/pages/incidents/Incidents.tsx @@ -1,7 +1,6 @@ import React, { ReactElement, SyntheticEvent } from 'react'; import { Button, Icon, Tooltip, VerticalGroup, LoadingPlaceholder, HorizontalGroup } from '@grafana/ui'; -import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import { get } from 'lodash-es'; import { observer } from 'mobx-react'; @@ -20,7 +19,6 @@ import IncidentsFilters from 'containers/IncidentsFilters/IncidentsFilters'; import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; import { Alert, Alert as AlertType, AlertAction } from 'models/alertgroup/alertgroup.types'; import { User } from 'models/user/user.types'; -import { pages } from 'pages'; import { getActionButtons, getIncidentStatusTag, renderRelatedUsers } from 'pages/incident/Incident.helpers'; import { move } from 'state/helpers'; import { PageProps, WithStoreProps } from 'state/types'; @@ -101,12 +99,10 @@ class Incidents extends React.Component render() { return ( - -
- {this.renderIncidentFilters()} - {this.renderTable()} -
-
+
+ {this.renderIncidentFilters()} + {this.renderTable()} +
); } diff --git a/grafana-plugin/src/pages/integrations/Integrations.tsx b/grafana-plugin/src/pages/integrations/Integrations.tsx index 5874adcb..bfe56d06 100644 --- a/grafana-plugin/src/pages/integrations/Integrations.tsx +++ b/grafana-plugin/src/pages/integrations/Integrations.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Button, LoadingPlaceholder, VerticalGroup } from '@grafana/ui'; -import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import { debounce } from 'lodash-es'; import { observer } from 'mobx-react'; @@ -24,7 +23,6 @@ import { IntegrationSettingsTab } from 'containers/IntegrationSettings/Integrati import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; import { AlertReceiveChannel } from 'models/alert_receive_channel'; import { AlertReceiveChannelOption } from 'models/alert_receive_channel/alert_receive_channel.types'; -import { pages } from 'pages'; import { PageProps, WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; import LocationHelper from 'utils/LocationHelper'; @@ -131,121 +129,119 @@ class Integrations extends React.Component const searchResult = alertReceiveChannelStore.getSearchResult(); return ( - - - {() => ( - <> -
-
- + + {() => ( + <> +
+
+ +
+ {searchResult?.length ? ( +
+
+ + + +
+ + {(item) => ( + { + this.setState({ + alertReceiveChannelToShowSettings: item.id, + integrationSettingsTab: IntegrationSettingsTab.Heartbeat, + }); + }} + /> + )} + +
+
+
+ { + this.setState({ + alertReceiveChannelToShowSettings: store.selectedAlertReceiveChannel, + integrationSettingsTab, + }); + }} + /> +
- {searchResult?.length ? ( -
-
+ ) : searchResult ? ( + + No integrations found. Review your filter and team settings. -
- - {(item) => ( - { - this.setState({ - alertReceiveChannelToShowSettings: item.id, - integrationSettingsTab: IntegrationSettingsTab.Heartbeat, - }); - }} - /> - )} - -
-
-
- { - this.setState({ - alertReceiveChannelToShowSettings: store.selectedAlertReceiveChannel, - integrationSettingsTab, - }); - }} - /> -
-
- ) : searchResult ? ( - - No integrations found. Review your filter and team settings. - - - - - } - /> - ) : ( - - )} -
- {alertReceiveChannelToShowSettings && ( - { - alertReceiveChannelStore.updateItem(alertReceiveChannelToShowSettings); - }} - startTab={integrationSettingsTab} - id={alertReceiveChannelToShowSettings} - onHide={() => { - this.setState({ - alertReceiveChannelToShowSettings: undefined, - integrationSettingsTab: undefined, - }); - LocationHelper.update({ tab: undefined }, 'partial'); - }} + + } /> + ) : ( + )} - {showCreateIntegrationModal && ( - { - this.setState({ showCreateIntegrationModal: false }); - }} - onCreate={this.handleCreateNewAlertReceiveChannel} - /> - )} - - )} -
- +
+ {alertReceiveChannelToShowSettings && ( + { + alertReceiveChannelStore.updateItem(alertReceiveChannelToShowSettings); + }} + startTab={integrationSettingsTab} + id={alertReceiveChannelToShowSettings} + onHide={() => { + this.setState({ + alertReceiveChannelToShowSettings: undefined, + integrationSettingsTab: undefined, + }); + LocationHelper.update({ tab: undefined }, 'partial'); + }} + /> + )} + {showCreateIntegrationModal && ( + { + this.setState({ showCreateIntegrationModal: false }); + }} + onCreate={this.handleCreateNewAlertReceiveChannel} + /> + )} + + )} + ); } diff --git a/grafana-plugin/src/pages/maintenance/Maintenance.tsx b/grafana-plugin/src/pages/maintenance/Maintenance.tsx index 90ebff26..0cccf565 100644 --- a/grafana-plugin/src/pages/maintenance/Maintenance.tsx +++ b/grafana-plugin/src/pages/maintenance/Maintenance.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Button, VerticalGroup } from '@grafana/ui'; -import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import { observer } from 'mobx-react'; import moment from 'moment-timezone'; @@ -16,7 +15,6 @@ import { WithPermissionControl } from 'containers/WithPermissionControl/WithPerm import { getAlertReceiveChannelDisplayName } from 'models/alert_receive_channel/alert_receive_channel.helpers'; import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; import { Maintenance, MaintenanceMode, MaintenanceType } from 'models/maintenance/maintenance.types'; -import { pages } from 'pages'; import { PageProps, WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; import { UserActions } from 'utils/authorization'; @@ -117,7 +115,7 @@ class MaintenancePage extends React.Component + <>
)} - + ); } diff --git a/grafana-plugin/src/pages/organization-logs/OrganizationLog.tsx b/grafana-plugin/src/pages/organization-logs/OrganizationLog.tsx index 6a063e0e..66a7d5b5 100644 --- a/grafana-plugin/src/pages/organization-logs/OrganizationLog.tsx +++ b/grafana-plugin/src/pages/organization-logs/OrganizationLog.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Button, HorizontalGroup, Tag, Tooltip } from '@grafana/ui'; -import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import { debounce } from 'lodash-es'; import { observer } from 'mobx-react'; @@ -96,41 +95,39 @@ class OrganizationLogPage extends React.Component -
- - ( -
- - Organization Logs - - -
- )} - showHeader={true} - data={results} - loading={loading} - emptyText={results ? 'No logs found' : 'Loading...'} - columns={columns} - pagination={{ - page, - total: Math.ceil((total || 0) / ITEMS_PER_PAGE), - onChange: this.handleChangePage, - }} - rowClassName={cx('align-top')} - expandable={{ - expandedRowRender: this.renderFullDescription, - expandRowByClick: true, - expandedRowKeys: expandedLogsKeys, - onExpandedRowsChange: this.handleExpandedRowsChange, - }} - /> -
- +
+ + ( +
+ + Organization Logs + + +
+ )} + showHeader={true} + data={results} + loading={loading} + emptyText={results ? 'No logs found' : 'Loading...'} + columns={columns} + pagination={{ + page, + total: Math.ceil((total || 0) / ITEMS_PER_PAGE), + onChange: this.handleChangePage, + }} + rowClassName={cx('align-top')} + expandable={{ + expandedRowRender: this.renderFullDescription, + expandRowByClick: true, + expandedRowKeys: expandedLogsKeys, + onExpandedRowsChange: this.handleExpandedRowsChange, + }} + /> +
); } diff --git a/grafana-plugin/src/pages/outgoing_webhooks/OutgoingWebhooks.tsx b/grafana-plugin/src/pages/outgoing_webhooks/OutgoingWebhooks.tsx index 4fa62c40..deff31b9 100644 --- a/grafana-plugin/src/pages/outgoing_webhooks/OutgoingWebhooks.tsx +++ b/grafana-plugin/src/pages/outgoing_webhooks/OutgoingWebhooks.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Button, HorizontalGroup } from '@grafana/ui'; -import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import { observer } from 'mobx-react'; import LegacyNavHeading from 'navbar/LegacyNavHeading'; @@ -19,7 +18,6 @@ import OutgoingWebhookForm from 'containers/OutgoingWebhookForm/OutgoingWebhookF import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; import { ActionDTO } from 'models/action'; import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types'; -import { pages } from 'pages'; import { PageProps, WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; import LocationHelper from 'utils/LocationHelper'; @@ -111,54 +109,52 @@ class OutgoingWebhooks extends React.Component - - {() => ( - <> -
- ( -
- - Outgoing Webhooks - -
- - - - - -
+ + {() => ( + <> +
+ ( +
+ + Outgoing Webhooks + +
+ + + + +
- )} - rowKey="id" - columns={columns} - data={webhooks} - /> -
- {outgoingWebhookIdToEdit && ( - - )} - - )} - - +
+ )} + rowKey="id" + columns={columns} + data={webhooks} + /> +
+ {outgoingWebhookIdToEdit && ( + + )} + + )} + ); } diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index edb20db2..a1c428f5 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Button, HorizontalGroup, VerticalGroup, IconButton, ToolbarButton, Icon, Modal } from '@grafana/ui'; -import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import dayjs from 'dayjs'; import { observer } from 'mobx-react'; @@ -20,7 +19,6 @@ import ScheduleICalSettings from 'containers/ScheduleIcalLink/ScheduleIcalLink'; import UsersTimezones from 'containers/UsersTimezones/UsersTimezones'; import { Schedule, ScheduleType, Shift } from 'models/schedule/schedule.types'; import { Timezone } from 'models/timezone/timezone.types'; -import { pages } from 'pages'; import { PageProps, WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; import LocationHelper from 'utils/LocationHelper'; @@ -112,156 +110,154 @@ class SchedulePage extends React.Component shiftIdToShowOverridesForm; return ( - - - {() => ( - <> -
- -
- + + {() => ( + <> +
+ +
+ + + + + + + {schedule?.name} + + {schedule && } + + + {users && ( + + Current timezone: + + + )} - - - - - {schedule?.name} - - {schedule && } - - - {users && ( - - Current timezone: - - - )} - - {(schedule?.type === ScheduleType.Ical || schedule?.type === ScheduleType.Calendar) && ( - - )} - { - this.setState({ showEditForm: true }); - }} - /> - - - + + {(schedule?.type === ScheduleType.Ical || schedule?.type === ScheduleType.Calendar) && ( + + )} + { + this.setState({ showEditForm: true }); + }} + /> + + + + + + +
+
+ +
+ +
+
+ + + + + + + + + {startMoment.format('DD MMM')} - {startMoment.add(6, 'day').format('DD MMM')} +
-
- -
- -
-
- - - - - - - - - {startMoment.format('DD MMM')} - {startMoment.add(6, 'day').format('DD MMM')} - - - -
- - - -
- -
- {showEditForm && ( - { - this.setState({ showEditForm: false }); - }} - /> - )} - {showScheduleICalSettings && ( - this.setState({ showScheduleICalSettings: false })} - > - - - )} - - )} - - + + + +
+ +
+ {showEditForm && ( + { + this.setState({ showEditForm: false }); + }} + /> + )} + {showScheduleICalSettings && ( + this.setState({ showScheduleICalSettings: false })} + > + + + )} + + )} + ); } diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index e0750a76..f72e3fbc 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Button, HorizontalGroup, IconButton, LoadingPlaceholder, VerticalGroup } from '@grafana/ui'; -import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import dayjs from 'dayjs'; import { debounce } from 'lodash-es'; @@ -25,7 +24,6 @@ import { WithPermissionControl } from 'containers/WithPermissionControl/WithPerm import { Schedule, ScheduleType } from 'models/schedule/schedule.types'; import { getSlackChannelName } from 'models/slack_channel/slack_channel.helpers'; import { Timezone } from 'models/timezone/timezone.types'; -import { pages } from 'pages'; import { getStartOfWeek } from 'pages/schedule/Schedule.helpers'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; @@ -135,7 +133,7 @@ class SchedulesPage extends React.Component + <>
@@ -192,7 +190,7 @@ class SchedulesPage extends React.Component )} - + ); } diff --git a/grafana-plugin/src/pages/settings/SettingsPage.tsx b/grafana-plugin/src/pages/settings/SettingsPage.tsx index 1b1ab2cf..817bf9ea 100644 --- a/grafana-plugin/src/pages/settings/SettingsPage.tsx +++ b/grafana-plugin/src/pages/settings/SettingsPage.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Tab, TabsBar } from '@grafana/ui'; -import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import { observer } from 'mobx-react'; @@ -35,11 +34,7 @@ class SettingsPage extends React.Component }; render() { - return ( - -
{this.renderContent()}
-
- ); + return
{this.renderContent()}
; } renderContent() { diff --git a/grafana-plugin/src/pages/test/Test.tsx b/grafana-plugin/src/pages/test/Test.tsx index ce1a8fe0..2309c6c9 100644 --- a/grafana-plugin/src/pages/test/Test.tsx +++ b/grafana-plugin/src/pages/test/Test.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Button } from '@grafana/ui'; -import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import { observer } from 'mobx-react'; @@ -17,13 +16,11 @@ const cx = cn.bind(styles); class Test extends React.Component { render() { return ( - -
- - {(disabled) => } - -
-
+
+ + {(disabled) => } + +
); } } diff --git a/grafana-plugin/src/pages/users/Users.tsx b/grafana-plugin/src/pages/users/Users.tsx index 924ad5a0..4717ecfc 100644 --- a/grafana-plugin/src/pages/users/Users.tsx +++ b/grafana-plugin/src/pages/users/Users.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Alert, Button, HorizontalGroup, Icon, VerticalGroup } from '@grafana/ui'; -import { PluginPage } from 'PluginPage'; import cn from 'classnames/bind'; import { debounce } from 'lodash-es'; import { observer } from 'mobx-react'; @@ -20,7 +19,6 @@ import UsersFilters from 'components/UsersFilters/UsersFilters'; import UserSettings from 'containers/UserSettings/UserSettings'; import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; import { User as UserType } from 'models/user/user.types'; -import { pages } from 'pages'; import { PageProps, WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; import LocationHelper from 'utils/LocationHelper'; @@ -159,85 +157,83 @@ class Users extends React.Component { const { count, results } = userStore.getSearchResult(); return ( - - - {() => ( - <> -
-
-
-
-
- - Users - - - To manage permissions or add users, please visit{' '} - Grafana user management - -
+ + {() => ( + <> +
+
+
+
+
+ + Users + + + To manage permissions or add users, please visit{' '} + Grafana user management +
- - -
- {isUserActionAllowed(UserActions.UserSettingsRead) ? ( - <> -
- - -
- - - - ) : ( - - You don't have enough permissions to view other users because you are not Admin.{' '} - Click here to open your profile - - } - severity="info" - /> - )} + + +
- {userPkToEdit && } + {isUserActionAllowed(UserActions.UserSettingsRead) ? ( + <> +
+ + +
+ + + + ) : ( + + You don't have enough permissions to view other users because you are not Admin.{' '} + Click here to open your profile + + } + severity="info" + /> + )}
- - )} - - + {userPkToEdit && } +
+ + )} +
); } diff --git a/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx b/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx index 3eaa6d5d..16920d12 100644 --- a/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx +++ b/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx @@ -23,6 +23,7 @@ import { routes } from 'pages/routes'; import { rootStore } from 'state'; import { useStore } from 'state/useStore'; import { isUserActionAllowed } from 'utils/authorization'; +import { DEFAULT_PAGE } from 'utils/consts'; import { useQueryParams, useQueryPath } from 'utils/hooks'; dayjs.extend(utc); @@ -49,7 +50,7 @@ export const GrafanaPluginRootPage = (props: AppRootProps) => ( export const Root = observer((props: AppRootProps) => { const [didFinishLoading, setDidFinishLoading] = useState(false); const queryParams = useQueryParams(); - const page = queryParams.get('page'); + const page = queryParams.get('page') || DEFAULT_PAGE; const path = useQueryPath(); // Required to support grafana instances that use a custom `root_url`. @@ -93,18 +94,15 @@ export const Root = observer((props: AppRootProps) => { {!isTopNavbar() && ( <>
- + )}
{userHasAccess ? ( diff --git a/grafana-plugin/src/style/utils.css b/grafana-plugin/src/style/utils.css index 2a35481f..314abf57 100644 --- a/grafana-plugin/src/style/utils.css +++ b/grafana-plugin/src/style/utils.css @@ -2,6 +2,10 @@ position: relative; } +.u-overflow-x-auto { + overflow-x: auto; +} + .u-pull-right { margin-left: auto; } @@ -18,6 +22,10 @@ width: 100%; } +.u-height-100 { + height: 100%; +} + .u-flex { display: flex; flex-direction: row; @@ -28,6 +36,10 @@ align-items: center; } +.u-flex-grow-1 { + flex-grow: 1; +} + .u-align-items-center { align-items: center; } diff --git a/grafana-plugin/src/utils/consts.ts b/grafana-plugin/src/utils/consts.ts index 3a5a2f79..5d66dc2d 100644 --- a/grafana-plugin/src/utils/consts.ts +++ b/grafana-plugin/src/utils/consts.ts @@ -7,3 +7,5 @@ export const GRAFANA_LICENSE_OSS = 'OpenSource'; // Reusable breakpoint sizes export const BREAKPOINT_TABS = 1024; + +export const DEFAULT_PAGE = 'incidents'; From b3d81646e606224498a731236bd74391d3d1873c Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Mon, 9 Jan 2023 09:58:08 +0000 Subject: [PATCH 03/10] Update publishing workflows to use PATs with fine-grained access control (#1100) Secrets have already been created by @mderynck Signed-off-by: Jack Baldry Signed-off-by: Jack Baldry --- .../workflows/publish-technical-documentation-next.yml | 8 ++++++-- .../workflows/publish-technical-documentation-release.yml | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-technical-documentation-next.yml b/.github/workflows/publish-technical-documentation-next.yml index 1e0e3e9c..5f8190a1 100644 --- a/.github/workflows/publish-technical-documentation-next.yml +++ b/.github/workflows/publish-technical-documentation-next.yml @@ -28,7 +28,9 @@ jobs: uses: "actions/checkout@v3" - name: "Clone website-sync Action" - run: "git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.GH_BOT_ACCESS_TOKEN }}@github.com/grafana/website-sync ./.github/actions/website-sync" + # WEBSITE_SYNC_ONCALL is a fine-grained GitHub Personal Access Token that expires. + # It must be updated in the grafanabot GitHub account. + run: "git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.WEBSITE_SYNC_ONCALL }}@github.com/grafana/website-sync ./.github/actions/website-sync" - name: "Publish to website repository (next)" uses: "./.github/actions/website-sync" @@ -37,6 +39,8 @@ jobs: repository: "grafana/website" branch: "master" host: "github.com" - github_pat: "${{ secrets.GH_BOT_ACCESS_TOKEN }}" + # PUBLISH_TO_WEBSITE_ONCALL is a fine-grained GitHub Personal Access Token that expires. + # It must be updated in the grafanabot GitHub account. + github_pat: "grafanabot:${{ secrets.PUBLISH_TO_WEBSITE_ONCALL }}" source_folder: "docs/sources" target_folder: "content/docs/oncall/next" diff --git a/.github/workflows/publish-technical-documentation-release.yml b/.github/workflows/publish-technical-documentation-release.yml index 707e20c7..fefa3b5e 100644 --- a/.github/workflows/publish-technical-documentation-release.yml +++ b/.github/workflows/publish-technical-documentation-release.yml @@ -58,7 +58,9 @@ jobs: - name: "Clone website-sync Action" if: "steps.has-matching-release-tag.outputs.bool == 'true'" - run: "git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.GH_BOT_ACCESS_TOKEN }}@github.com/grafana/website-sync ./.github/actions/website-sync" + # WEBSITE_SYNC_ONCALL is a fine-grained GitHub Personal Access Token that expires. + # It must be updated in the grafanabot GitHub account. + run: "git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.WEBSITE_SYNC_ONCALL }}@github.com/grafana/website-sync ./.github/actions/website-sync" - name: "Publish to website repository (release)" if: "steps.has-matching-release-tag.outputs.bool == 'true'" @@ -68,7 +70,9 @@ jobs: repository: "grafana/website" branch: "master" host: "github.com" - github_pat: "${{ secrets.GH_BOT_ACCESS_TOKEN }}" + # PUBLISH_TO_WEBSITE_ONCALL is a fine-grained GitHub Personal Access Token that expires. + # It must be updated in the grafanabot GitHub account. + github_pat: "grafanabot:${{ secrets.PUBLISH_TO_WEBSITE_ONCALL }}" source_folder: "docs/sources" # Append ".x" to target to produce a v..x directory. target_folder: "content/docs/oncall/${{ steps.target.outputs.target }}.x" From db2e4f4018898db537f5a670a35183885671f955 Mon Sep 17 00:00:00 2001 From: Rares Mardare <40542072+teodosii@users.noreply.github.com> Date: Mon, 9 Jan 2023 17:50:55 +0200 Subject: [PATCH 04/10] Update prettier@2.8.2 (#1112) # What this PR does Updated prettier to `2.8.2` to fix linter issue on CI --- .pre-commit-config.yaml | 14 ++++++++++---- grafana-plugin/package.json | 2 +- .../src/models/timezone/timezone.types.ts | 6 +++--- grafana-plugin/yarn.lock | 7 ++++++- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5f384934..464bbf88 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,8 @@ repos: - id: isort name: isort - pd-migrator files: ^tools/pagerduty-migrator - args: [--settings-file=tools/pagerduty-migrator/.isort.cfg, --filter-files] + args: + [--settings-file=tools/pagerduty-migrator/.isort.cfg, --filter-files] - repo: https://github.com/psf/black rev: 22.3.0 @@ -33,7 +34,12 @@ repos: files: ^tools/pagerduty-migrator # Make sure config is compatible with black # https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#line-length - args: [--max-line-length=88, "--select=C,E,F,W,B,B950", "--extend-ignore=E203,E501"] + args: + [ + --max-line-length=88, + "--select=C,E,F,W,B,B950", + "--extend-ignore=E203,E501", + ] - repo: https://github.com/pre-commit/mirrors-eslint rev: v8.25.0 @@ -56,12 +62,12 @@ repos: types_or: [css, javascript, jsx, ts, tsx] files: ^grafana-plugin/src additional_dependencies: - - prettier@^2.7.1 + - prettier@2.8.2 - id: prettier name: prettier - json types_or: [json] additional_dependencies: - - prettier@^2.7.1 + - prettier@2.8.2 - repo: https://github.com/thibaudcolas/pre-commit-stylelint rev: v13.13.1 diff --git a/grafana-plugin/package.json b/grafana-plugin/package.json index ee4efd6f..2cb20dbe 100644 --- a/grafana-plugin/package.json +++ b/grafana-plugin/package.json @@ -115,7 +115,7 @@ "eslint-plugin-import": "^2.25.4", "mobx": "5.13.0", "mobx-react": "6.1.1", - "prettier": "^2.7.1", + "prettier": "^2.8.2", "rc-table": "^7.17.1", "react-copy-to-clipboard": "^5.0.2", "react-draggable": "^4.4.5", diff --git a/grafana-plugin/src/models/timezone/timezone.types.ts b/grafana-plugin/src/models/timezone/timezone.types.ts index 6ed40261..2b62579b 100644 --- a/grafana-plugin/src/models/timezone/timezone.types.ts +++ b/grafana-plugin/src/models/timezone/timezone.types.ts @@ -1,4 +1,4 @@ -const tzs = [ +const tzs: string[] = [ 'Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', @@ -591,6 +591,6 @@ const tzs = [ 'W-SU', 'WET', 'Zulu', -] as const; +]; -export type Timezone = typeof tzs[number]; +export type Timezone = (typeof tzs)[number]; diff --git a/grafana-plugin/yarn.lock b/grafana-plugin/yarn.lock index 7d7cc950..6ef39fdc 100644 --- a/grafana-plugin/yarn.lock +++ b/grafana-plugin/yarn.lock @@ -10913,11 +10913,16 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@2.7.1, prettier@^2.7.1: +prettier@2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== +prettier@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.2.tgz#c4ea1b5b454d7c4b59966db2e06ed7eec5dfd160" + integrity sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw== + pretty-error@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" From 87fad5eec1b6a023cb05858b7519bbb4534ebcf4 Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Mon, 9 Jan 2023 13:15:27 -0300 Subject: [PATCH 05/10] Add select_related to fetch schedules user group information (#1109) --- engine/apps/api/serializers/schedule_polymorphic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/apps/api/serializers/schedule_polymorphic.py b/engine/apps/api/serializers/schedule_polymorphic.py index 9da80611..382054d8 100644 --- a/engine/apps/api/serializers/schedule_polymorphic.py +++ b/engine/apps/api/serializers/schedule_polymorphic.py @@ -12,7 +12,7 @@ from common.api_helpers.mixins import EagerLoadingMixin class PolymorphicScheduleSerializer(EagerLoadingMixin, PolymorphicSerializer): - SELECT_RELATED = ["organization"] + SELECT_RELATED = ["organization", "user_group"] resource_type_field_name = "type" From abbb5a83813c84e9ac2a3646ef8a26d2aa2a44e8 Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Mon, 9 Jan 2023 14:10:23 -0300 Subject: [PATCH 06/10] Update schedules query not to defer ical fields used for on-call check (#1114) Do not defer cached ical fields which are later needed for calculating on-call users listed in the schedules list page. --- engine/apps/api/views/schedule.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/apps/api/views/schedule.py b/engine/apps/api/views/schedule.py index 4db5d3e3..fa61c5dd 100644 --- a/engine/apps/api/views/schedule.py +++ b/engine/apps/api/views/schedule.py @@ -135,9 +135,7 @@ class ScheduleView( organization = self.request.auth.organization queryset = OnCallSchedule.objects.filter(organization=organization, team=self.request.user.current_team).defer( # avoid requesting large text fields which are not used when listing schedules - "cached_ical_file_primary", "prev_ical_file_primary", - "cached_ical_file_overrides", "prev_ical_file_overrides", ) if not is_short_request: From 78bbfe0c4c9e36274280416b32651be57671f7bb Mon Sep 17 00:00:00 2001 From: Michael Derynck Date: Mon, 9 Jan 2023 17:14:07 +0000 Subject: [PATCH 07/10] Update tokens used by GH Actions (#1102) Separate tokens for GH actions with minimal scope --- .github/workflows/helm_release.yml | 2 +- .github/workflows/issue_commands.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/helm_release.yml b/.github/workflows/helm_release.yml index 7f0f9672..aaf49b55 100644 --- a/.github/workflows/helm_release.yml +++ b/.github/workflows/helm_release.yml @@ -13,6 +13,6 @@ jobs: cr_configfile: helm/cr.yaml ct_configfile: helm/ct.yaml secrets: - helm_repo_token: ${{ secrets.GH_BOT_ACCESS_TOKEN }} + helm_repo_token: ${{ secrets.GH_HELM_RELEASE }} # See https://github.com/grafana/helm-charts/blob/main/INTERNAL.md about this key gpg_key_base64: ${{ secrets.HELM_SIGN_KEY_BASE64 }} diff --git a/.github/workflows/issue_commands.yml b/.github/workflows/issue_commands.yml index 2279e1d0..f2d82f7d 100644 --- a/.github/workflows/issue_commands.yml +++ b/.github/workflows/issue_commands.yml @@ -19,5 +19,5 @@ jobs: - name: Run Commands uses: ./actions/commands with: - token: ${{secrets.GH_BOT_ACCESS_TOKEN}} + token: ${{secrets.GH_ISSUE_COMMANDS}} configPath: issue_and_pr_commands \ No newline at end of file From 86436f9f15609a27dafe633d190b447be14790b0 Mon Sep 17 00:00:00 2001 From: Rares Mardare <40542072+teodosii@users.noreply.github.com> Date: Tue, 10 Jan 2023 09:14:04 +0200 Subject: [PATCH 08/10] Faro - Point to 3 separate apps instead of just 1 for all environments (#1110) # What this PR does - App will now send traces to either of the Faro configured apps: `grafana-oncall-dev`, `grafana-oncall-ops` or `grafana-oncall-prod` instead of just `grafana-oncall` for all of 3. - Added tests to reflect latest changes --- grafana-plugin/src/utils/consts.ts | 16 ++++ grafana-plugin/src/utils/faro.test.disabled | 89 --------------------- grafana-plugin/src/utils/faro.test.tsx | 65 +++++++++++++++ grafana-plugin/src/utils/faro.ts | 57 +++++++------ 4 files changed, 108 insertions(+), 119 deletions(-) delete mode 100644 grafana-plugin/src/utils/faro.test.disabled create mode 100644 grafana-plugin/src/utils/faro.test.tsx diff --git a/grafana-plugin/src/utils/consts.ts b/grafana-plugin/src/utils/consts.ts index 5d66dc2d..18071482 100644 --- a/grafana-plugin/src/utils/consts.ts +++ b/grafana-plugin/src/utils/consts.ts @@ -1,11 +1,27 @@ import plugin from '../../package.json'; // eslint-disable-line +// Navbar export const APP_TITLE = 'Grafana OnCall'; export const APP_SUBTITLE = `Developer-friendly incident response (${plugin?.version})`; +// License export const GRAFANA_LICENSE_OSS = 'OpenSource'; // Reusable breakpoint sizes export const BREAKPOINT_TABS = 1024; +// Default redirect page export const DEFAULT_PAGE = 'incidents'; + +// Environment options list for onCallApiUrl +export const ONCALL_PROD = 'https://oncall-prod-us-central-0.grafana.net/oncall'; +export const ONCALL_OPS = 'https://oncall-ops-us-east-0.grafana.net/oncall'; +export const ONCALL_DEV = 'https://oncall-dev-us-central-0.grafana.net/oncall'; + +// Faro +export const FARO_ENDPOINT_DEV = + 'https://faro-collector-prod-us-central-0.grafana.net/collect/fb03e474a96cf867f4a34590c002984c'; +export const FARO_ENDPOINT_OPS = + 'https://faro-collector-prod-us-central-0.grafana.net/collect/40ccaafad6b71aa90fc53c3b0a1adb31'; +export const FARO_ENDPOINT_PROD = + 'https://faro-collector-prod-us-central-0.grafana.net/collect/03a11ed03c3af04dcfc3be9755f2b053'; diff --git a/grafana-plugin/src/utils/faro.test.disabled b/grafana-plugin/src/utils/faro.test.disabled deleted file mode 100644 index 6a3c0553..00000000 --- a/grafana-plugin/src/utils/faro.test.disabled +++ /dev/null @@ -1,89 +0,0 @@ -import 'jest/matchMedia.ts'; -import { describe, test } from '@jest/globals'; - -import FaroHelper from 'utils/faro'; - -import '@testing-library/jest-dom'; - -jest.mock('@grafana/faro-web-sdk', () => ({ - initializeFaro: jest.fn().mockReturnValue({ - api: { - pushLog: jest.fn(), - }, - }), - getWebInstrumentations: () => [], -})); - -jest.mock('@grafana/faro-web-tracing', () => ({ - TracingInstrumentation: jest.fn(), -})); -jest.mock('@opentelemetry/instrumentation-document-load', () => ({ - DocumentLoadInstrumentation: jest.fn(), -})); -jest.mock('@opentelemetry/instrumentation-fetch', () => ({ - FetchInstrumentation: jest.fn(), -})); - -describe('Faro', () => { - const OLD_ENV = process.env; - - beforeEach(() => { - jest.resetModules(); - process.env = { ...OLD_ENV }; - FaroHelper.faro = undefined; - jest.clearAllMocks(); - }); - - const getDefaultValues = () => ({ - faroUrl: 'localhost:12345/collect', - apiKey: 'secret', - enabled: 'true', - }); - - const getProcessEnv = (config: { faroUrl?: string; apiKey?: string; enabled?: string } = {}) => { - const configObject = { - ...getDefaultValues(), - ...config, - }; - - const { faroUrl, apiKey, enabled } = configObject; - - return { - FARO_URL: faroUrl, - FARO_API_KEY: apiKey, - FARO_ENABLED: enabled, - }; - }; - - test('It initializes without api key', () => { - process.env = getProcessEnv({ apiKey: '' }); - const faro = FaroHelper.initializeFaro(); - expect(faro).toBeDefined(); - }); - - test('It initializes faro ENABLED === true', () => { - process.env = getProcessEnv(); - const faro = FaroHelper.initializeFaro(); - - expect(faro).toBeDefined(); - expect(faro.api.pushLog).toHaveBeenCalledTimes(1); - }); - - test('It does not initialize faro if ENABLED != true', () => { - process.env = getProcessEnv({ enabled: 'some-other-value-here' }); - const faro = FaroHelper.initializeFaro(); - expect(faro).toBeUndefined(); - }); - - test('It skips initializing if values are missing', () => { - let faro; - - process.env = getProcessEnv({ faroUrl: undefined }); - faro = FaroHelper.initializeFaro(); - expect(faro).toBeUndefined(); - - process.env = getProcessEnv({ enabled: undefined }); - faro = FaroHelper.initializeFaro(); - expect(faro).toBeUndefined(); - }); -}); diff --git a/grafana-plugin/src/utils/faro.test.tsx b/grafana-plugin/src/utils/faro.test.tsx new file mode 100644 index 00000000..9310d2b1 --- /dev/null +++ b/grafana-plugin/src/utils/faro.test.tsx @@ -0,0 +1,65 @@ +import 'jest/matchMedia.ts'; +import { describe, test } from '@jest/globals'; + +import FaroHelper from 'utils/faro'; + +import '@testing-library/jest-dom'; +import { ONCALL_DEV, ONCALL_OPS, ONCALL_PROD } from './consts'; + +const ErrorMock = jest.spyOn(window, 'Error'); + +jest.mock('@grafana/faro-web-sdk', () => ({ + initializeFaro: jest.fn().mockReturnValue({ + api: { + pushLog: jest.fn(), + }, + }), + getWebInstrumentations: () => [], +})); + +jest.mock('@grafana/faro-web-tracing', () => ({ + TracingInstrumentation: jest.fn(), +})); +jest.mock('@opentelemetry/instrumentation-document-load', () => ({ + DocumentLoadInstrumentation: jest.fn(), +})); +jest.mock('@opentelemetry/instrumentation-fetch', () => ({ + FetchInstrumentation: jest.fn(), +})); + +describe('Faro', () => { + beforeEach(() => { + jest.clearAllMocks(); + + FaroHelper.faro = undefined; + }); + + test.each([ONCALL_DEV, ONCALL_OPS, ONCALL_PROD])('It initializes faro for environment %s', (onCallApiUrl) => { + const faro = FaroHelper.initializeFaro(onCallApiUrl); + expect(faro).toBeDefined(); + expect(ErrorMock).not.toHaveBeenCalled(); + }); + + test.each(['https://test.com', 'http://localhost:3000'])( + 'It fails initializing for dummy environment values', + (onCallApiUrl) => { + const faro = FaroHelper.initializeFaro(onCallApiUrl); + expect(faro).toBeUndefined(); + } + ); + + test('It does not reinitialize faro instance if already initialized', () => { + const instance = FaroHelper.initializeFaro(ONCALL_DEV); + expect(instance).toBeDefined(); + + const result = FaroHelper.initializeFaro(ONCALL_PROD); + expect(result).toBeUndefined(); + expect(FaroHelper.faro).toBe(instance); + }); + + test('Initializer throws error for wrong env value', () => { + const faro = FaroHelper.initializeFaro('https://test.com'); + expect(ErrorMock).toHaveBeenCalledWith(`No match found for given onCallApiUrl = https://test.com`); + expect(faro).toBeUndefined(); + }); +}); diff --git a/grafana-plugin/src/utils/faro.ts b/grafana-plugin/src/utils/faro.ts index 357b9e94..da5a0fad 100644 --- a/grafana-plugin/src/utils/faro.ts +++ b/grafana-plugin/src/utils/faro.ts @@ -6,47 +6,45 @@ import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-u import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'; import plugin from '../../package.json'; // eslint-disable-line +import { + FARO_ENDPOINT_DEV, + FARO_ENDPOINT_OPS, + FARO_ENDPOINT_PROD, + ONCALL_DEV, + ONCALL_OPS, + ONCALL_PROD, +} from './consts'; const IGNORE_URLS = [/^((?!\/{0,1}a\/grafana\-oncall\-app\\).)*$/]; -interface FaroConfig { - url: string; - enabled: boolean; - environment: string; +export function getAppNameUrlPair(onCallApiUrl: string): { appName: string; url: string } { + const baseName = 'grafana-oncall'; + + switch (onCallApiUrl) { + case ONCALL_DEV: + return { appName: `${baseName}-dev`, url: FARO_ENDPOINT_DEV }; + case ONCALL_OPS: + return { appName: `${baseName}-ops`, url: FARO_ENDPOINT_OPS }; + case ONCALL_PROD: + return { appName: `${baseName}-prod`, url: FARO_ENDPOINT_PROD }; + default: + throw new Error(`No match found for given onCallApiUrl = ${onCallApiUrl}`); + } } class FaroHelper { faro: Faro; initializeFaro(onCallApiUrl: string) { - const faroConfig: FaroConfig = { - url: 'https://faro-collector-prod-us-central-0.grafana.net/collect/f3a038193e7802cf47531ca94cfbada7', - enabled: false, - environment: undefined, - }; - - if (onCallApiUrl === 'https://oncall-prod-us-central-0.grafana.net/oncall') { - faroConfig.enabled = true; - faroConfig.environment = 'prod'; - } else if (onCallApiUrl === 'https://oncall-ops-us-east-0.grafana.net/oncall') { - faroConfig.enabled = true; - faroConfig.environment = 'ops'; - } else if (onCallApiUrl === 'https://oncall-dev-us-central-0.grafana.net/oncall') { - faroConfig.enabled = true; - faroConfig.environment = 'dev'; - } else { - // This opensource, don't send traces - /* faroConfig.enabled = true; - faroConfig.environment = 'local'; */ - } - - if (!faroConfig?.enabled || !faroConfig?.url || this.faro) { + if (this.faro) { return undefined; } try { + const { appName, url } = getAppNameUrlPair(onCallApiUrl); + const faroOptions = { - url: faroConfig.url, + url: url, isolate: true, instrumentations: [ ...getWebInstrumentations({ @@ -63,15 +61,14 @@ class FaroHelper { ], session: (window as any).__PRELOADED_STATE__?.faro?.session, app: { - name: 'grafana-oncall', + name: appName, version: plugin?.version, - environment: faroConfig.environment, }, }; this.faro = initializeFaro(faroOptions); - this.faro.api.pushLog([`Faro was initialized for ${faroConfig.environment}`]); + this.faro.api.pushLog([`Faro was initialized for ${appName}`]); } catch (ex) {} return this.faro; From fa6906a60640407a50187bca13959fa355f04e00 Mon Sep 17 00:00:00 2001 From: Innokentii Konstantinov Date: Tue, 10 Jan 2023 15:41:38 +0800 Subject: [PATCH 09/10] Simplify and speed up slack rendering (#1105) Simplify and speed up slack rendering. --- .../renderers/slack_renderer.py | 60 +++---------------- 1 file changed, 8 insertions(+), 52 deletions(-) diff --git a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py index 0f2e00c0..71925009 100644 --- a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py @@ -74,41 +74,14 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer): return AlertSlackRenderer def render_alert_group_blocks(self): - non_resolve_alerts_queryset = self.alert_group.alerts.filter(is_resolve_signal=False) - if not self.alert_group.channel.organization.slack_team_identity.installed_via_granular_permissions: - blocks = [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": ":warning: *Action required - reinstall app*\n" - "Slack is deprecating current permission model. We will support it till DATE\n" # TODO: deprecation date - "Don't worry - we migrate OnCall to new one, but it required to reinstall app." - 'Press "Upgrade" button to see more detailed instruction and upgrade.', - }, - }, - {"type": "divider"}, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Upgrade", - }, - "value": "click_me_123", - "url": self.alert_group.channel.organization.web_slack_page_link, - }, - ], - }, - ] - else: - blocks = [] - if non_resolve_alerts_queryset.count() <= 1: - blocks.extend(self.alert_renderer.render_alert_blocks()) - else: - blocks.extend(self._get_alert_group_base_blocks_if_grouped()) + blocks = self.alert_renderer.render_alert_blocks() + alerts_count = self.alert_group.alerts.count() + if alerts_count > 1: + text = ( + f":package: Showing the last alert only out of {alerts_count} total. " + f"Visit <{self.alert_group.web_link}|the plugin page> to see them all." + ) + blocks.append({"type": "context", "elements": [{"type": "mrkdwn", "text": text}]}) return blocks def render_alert_group_attachments(self): @@ -189,23 +162,6 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer): attachment["color"] = color return attachments - def _get_text_alert_grouped(self): - alert_count = self.alert_group.alerts.count() - link = self.alert_group.web_link - - text = ( - f":package: Showing the last alert only out of {alert_count} total. " - f"Visit <{link}|the plugin page> to see them all." - ) - - return text - - def _get_alert_group_base_blocks_if_grouped(self): - text = self._get_text_alert_grouped() - blocks = self.alert_renderer.render_alert_blocks() - blocks.append({"type": "context", "elements": [{"type": "mrkdwn", "text": text}]}) - return blocks - def _get_buttons_attachments(self): attachment = {"blocks": self._get_buttons_blocks()} return attachment From 2ac4d88e885a40a3cdf7f5a83d3fba0f735789e2 Mon Sep 17 00:00:00 2001 From: Rares Mardare <40542072+teodosii@users.noreply.github.com> Date: Tue, 10 Jan 2023 10:37:52 +0200 Subject: [PATCH 10/10] v1.1.15 CHANGELOG (#1116) # What this PR does `v1.1.15` --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfde9ae5..b203ff40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ 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). +## v1.1.15 (2023-01-10) + +### Changed + +- Simplify and speed up slack rendering (#1105) +- Faro - Point to 3 separate apps instead of just 1 for all environments (#1110) +- Schedules - (#1114 #1109) + +### Fixed + +- Bugfix for topnavbar to place alerts inside PageNav (#1040) + ## v1.1.14 (2023-01-05) ### Changed