[Alerting] - Display scenes using SceneAppPage for caching (#74767)

* Display scenes using SceneAppPage for caching

Also, reorganizing them in different tabs per Torkel's suggestion

* Fix lint

* Alerting home app poc

* Fix imports

* fix routing

* remove width

* Apply feature flags back

* Add comment regarding enabling the rule group query variable

* Upgrade scenes lib

* Add query variables back

---------

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
Virginia Cepeda
2023-09-19 09:17:56 -03:00
committed by GitHub
parent 7209e84cd2
commit 0d3c47da99
11 changed files with 165 additions and 128 deletions

View File

@@ -251,7 +251,7 @@
"@grafana/lezer-traceql": "0.0.6", "@grafana/lezer-traceql": "0.0.6",
"@grafana/monaco-logql": "^0.0.7", "@grafana/monaco-logql": "^0.0.7",
"@grafana/runtime": "workspace:*", "@grafana/runtime": "workspace:*",
"@grafana/scenes": "^1.1.1", "@grafana/scenes": "^1.3.1",
"@grafana/schema": "workspace:*", "@grafana/schema": "workspace:*",
"@grafana/ui": "workspace:*", "@grafana/ui": "workspace:*",
"@kusto/monaco-kusto": "^7.4.0", "@kusto/monaco-kusto": "^7.4.0",

View File

@@ -93,6 +93,13 @@ const unifiedRoutes: RouteDescriptor[] = [
() => import(/* webpackChunkName: "AlertingHome" */ 'app/features/alerting/unified/home/Home') () => import(/* webpackChunkName: "AlertingHome" */ 'app/features/alerting/unified/home/Home')
), ),
}, },
{
path: '/alerting/home',
exact: false,
component: SafeDynamicImport(
() => import(/* webpackChunkName: "AlertingHome" */ 'app/features/alerting/unified/home/Home')
),
},
{ {
path: '/alerting/list', path: '/alerting/list',
roles: evaluateAccess([AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleExternalRead]), roles: evaluateAccess([AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleExternalRead]),

View File

@@ -4,8 +4,23 @@ import SVG from 'react-inlinesvg';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { Stack } from '@grafana/experimental'; import { Stack } from '@grafana/experimental';
import { EmbeddedScene, SceneFlexLayout, SceneFlexItem, SceneReactObject } from '@grafana/scenes';
import { Icon, useStyles2, useTheme2 } from '@grafana/ui'; import { Icon, useStyles2, useTheme2 } from '@grafana/ui';
export const getOverviewScene = () => {
return new EmbeddedScene({
body: new SceneFlexLayout({
children: [
new SceneFlexItem({
body: new SceneReactObject({
component: GettingStarted,
}),
}),
],
}),
});
};
export default function GettingStarted({ showWelcomeHeader }: { showWelcomeHeader?: boolean }) { export default function GettingStarted({ showWelcomeHeader }: { showWelcomeHeader?: boolean }) {
const theme = useTheme2(); const theme = useTheme2();
const styles = useStyles2(getWelcomePageStyles); const styles = useStyles2(getWelcomePageStyles);
@@ -130,32 +145,43 @@ export function WelcomeHeader({ className }: { className?: string }) {
const styles = useStyles2(getWelcomeHeaderStyles); const styles = useStyles2(getWelcomeHeaderStyles);
return ( return (
<ContentBox className={cx(styles.ctaContainer, className)}> <div className={styles.welcomeHeaderWrapper}>
<WelcomeCTABox <div className={styles.subtitle}>Learn about problems in your systems moments after they occur</div>
title="Alert rules"
description="Define the condition that must be met before an alert rule fires" <ContentBox className={cx(styles.ctaContainer, className)}>
href="/alerting/list" <WelcomeCTABox
hrefText="Manage alert rules" title="Alert rules"
/> description="Define the condition that must be met before an alert rule fires"
<div className={styles.separator} /> href="/alerting/list"
<WelcomeCTABox hrefText="Manage alert rules"
title="Contact points" />
description="Configure who receives notifications and how they are sent" <div className={styles.separator} />
href="/alerting/notifications" <WelcomeCTABox
hrefText="Manage contact points" title="Contact points"
/> description="Configure who receives notifications and how they are sent"
<div className={styles.separator} /> href="/alerting/notifications"
<WelcomeCTABox hrefText="Manage contact points"
title="Notification policies" />
description="Configure how firing alert instances are routed to contact points" <div className={styles.separator} />
href="/alerting/routes" <WelcomeCTABox
hrefText="Manage notification policies" title="Notification policies"
/> description="Configure how firing alert instances are routed to contact points"
</ContentBox> href="/alerting/routes"
hrefText="Manage notification policies"
/>
</ContentBox>
</div>
); );
} }
const getWelcomeHeaderStyles = (theme: GrafanaTheme2) => ({ const getWelcomeHeaderStyles = (theme: GrafanaTheme2) => ({
welcomeHeaderWrapper: css({
color: theme.colors.text.primary,
}),
subtitle: css({
color: theme.colors.text.secondary,
paddingBottom: theme.spacing(2),
}),
ctaContainer: css` ctaContainer: css`
padding: ${theme.spacing(4, 2)}; padding: ${theme.spacing(4, 2)};
display: flex; display: flex;

View File

@@ -1,50 +1,71 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { Tab, TabContent, TabsBar } from '@grafana/ui'; import { SceneApp, SceneAppPage } from '@grafana/scenes';
import { usePageNav } from 'app/core/components/Page/usePageNav';
import { PluginPageContext, PluginPageContextType } from 'app/features/plugins/components/PluginPageContext';
import { AlertingPageWrapper } from '../components/AlertingPageWrapper'; import { getOverviewScene, WelcomeHeader } from './GettingStarted';
import { getGrafanaScenes } from './Insights';
import GettingStarted, { WelcomeHeader } from './GettingStarted'; let homeApp: SceneApp | undefined;
import Insights from './Insights';
type HomeTabs = 'insights' | 'gettingStarted'; export function getHomeApp(insightsEnabled: boolean) {
if (homeApp) {
return homeApp;
}
if (insightsEnabled) {
homeApp = new SceneApp({
pages: [
new SceneAppPage({
title: 'Alerting',
subTitle: <WelcomeHeader />,
url: '/alerting',
hideFromBreadcrumbs: true,
tabs: [
new SceneAppPage({
title: 'Grafana',
url: '/alerting/home/insights',
getScene: getGrafanaScenes,
}),
new SceneAppPage({
title: 'Overview',
url: '/alerting/home/overview',
getScene: getOverviewScene,
}),
],
}),
],
});
} else {
homeApp = new SceneApp({
pages: [
new SceneAppPage({
title: 'Alerting',
subTitle: <WelcomeHeader />,
url: '/alerting',
hideFromBreadcrumbs: true,
getScene: getOverviewScene,
}),
],
});
}
return homeApp;
}
export default function Home() { export default function Home() {
const [activeTab, setActiveTab] = useState<HomeTabs>('insights'); const insightsEnabled = !!config.featureToggles.alertingInsights;
const appScene = getHomeApp(insightsEnabled);
const sectionNav = usePageNav('alerting')!;
const [pluginContext] = useState<PluginPageContextType>({ sectionNav });
const alertingInsightsEnabled = config.featureToggles.alertingInsights;
return ( return (
<AlertingPageWrapper pageId={'alerting'}> <PluginPageContext.Provider value={pluginContext}>
{alertingInsightsEnabled && ( <appScene.Component model={appScene} />
<> </PluginPageContext.Provider>
<WelcomeHeader />
<TabsBar>
<Tab
key={'insights'}
label={'Insights'}
active={activeTab === 'insights'}
onChangeTab={() => {
setActiveTab('insights');
}}
/>
<Tab
key={'gettingStarted'}
label={'Overview'}
active={activeTab === 'gettingStarted'}
onChangeTab={() => {
setActiveTab('gettingStarted');
}}
/>
</TabsBar>
<TabContent>
{activeTab === 'insights' && <Insights />}
{activeTab === 'gettingStarted' && <GettingStarted />}
</TabContent>
</>
)}
{!alertingInsightsEnabled && <GettingStarted showWelcomeHeader={true} />}
</AlertingPageWrapper>
); );
} }

View File

@@ -1,16 +1,13 @@
import { css } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { import {
EmbeddedScene, EmbeddedScene,
NestedScene,
QueryVariable, QueryVariable,
SceneFlexItem,
SceneFlexLayout, SceneFlexLayout,
SceneTimeRange, SceneTimeRange,
SceneVariableSet, SceneVariableSet,
VariableValueSelectors, VariableValueSelectors,
} from '@grafana/scenes'; } from '@grafana/scenes';
import { useStyles2, CollapsableSection } from '@grafana/ui';
import { getFiringAlertsScene } from '../insights/grafana/FiringAlertsPercentage'; import { getFiringAlertsScene } from '../insights/grafana/FiringAlertsPercentage';
import { getFiringAlertsRateScene } from '../insights/grafana/FiringAlertsRate'; import { getFiringAlertsRateScene } from '../insights/grafana/FiringAlertsRate';
@@ -49,31 +46,50 @@ const grafanaCloudPromDs = {
const THIS_WEEK_TIME_RANGE = new SceneTimeRange({ from: 'now-1w', to: 'now' }); const THIS_WEEK_TIME_RANGE = new SceneTimeRange({ from: 'now-1w', to: 'now' });
const LAST_WEEK_TIME_RANGE = new SceneTimeRange({ from: 'now-2w', to: 'now-1w' }); const LAST_WEEK_TIME_RANGE = new SceneTimeRange({ from: 'now-2w', to: 'now-1w' });
function getGrafanaScenes() { export function getGrafanaScenes() {
return new EmbeddedScene({ return new EmbeddedScene({
body: new SceneFlexLayout({ body: new SceneFlexLayout({
wrap: 'wrap', direction: 'column',
children: [ children: [
getMostFiredInstancesScene(THIS_WEEK_TIME_RANGE, ashDs, 'Top 10 firing instances this week'), new SceneFlexLayout({
children: [
getFiringAlertsRateScene(THIS_WEEK_TIME_RANGE, ashDs, 'Alerts firing per minute'), getMostFiredInstancesScene(THIS_WEEK_TIME_RANGE, ashDs, 'Top 10 firing instances this week'),
getFiringAlertsRateScene(THIS_WEEK_TIME_RANGE, ashDs, 'Alerts firing per minute'),
getFiringAlertsScene(THIS_WEEK_TIME_RANGE, ashDs, 'Firing alerts this week'), ],
}),
getFiringAlertsScene(LAST_WEEK_TIME_RANGE, ashDs, 'Firing alerts last week'), new SceneFlexLayout({
children: [
getFiringAlertsScene(THIS_WEEK_TIME_RANGE, ashDs, 'Firing alerts this week'),
getFiringAlertsScene(LAST_WEEK_TIME_RANGE, ashDs, 'Firing alerts last week'),
],
}),
new SceneFlexItem({
ySizing: 'content',
body: getCloudScenes(),
}),
new SceneFlexItem({
ySizing: 'content',
body: getMimirManagedRulesScenes(),
}),
new SceneFlexItem({
ySizing: 'content',
body: getMimirManagedRulesPerGroupScenes(),
}),
], ],
}), }),
}); });
} }
function getCloudScenes() { function getCloudScenes() {
return new EmbeddedScene({ return new NestedScene({
title: 'Mimir alertmanager',
canCollapse: true,
isCollapsed: true,
body: new SceneFlexLayout({ body: new SceneFlexLayout({
wrap: 'wrap', wrap: 'wrap',
children: [ children: [
getAlertsByStateScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Alerts by State'), getAlertsByStateScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Alerts by State'),
getNotificationsScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Notifications'), getNotificationsScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Notifications'),
getSilencesScene(LAST_WEEK_TIME_RANGE, cloudUsageDs, 'Silences'), getSilencesScene(LAST_WEEK_TIME_RANGE, cloudUsageDs, 'Silences'),
getInvalidConfigScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Invalid configuration'), getInvalidConfigScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Invalid configuration'),
], ],
@@ -82,7 +98,10 @@ function getCloudScenes() {
} }
function getMimirManagedRulesScenes() { function getMimirManagedRulesScenes() {
return new EmbeddedScene({ return new NestedScene({
title: 'Mimir-managed rules',
canCollapse: true,
isCollapsed: true,
body: new SceneFlexLayout({ body: new SceneFlexLayout({
wrap: 'wrap', wrap: 'wrap',
children: [ children: [
@@ -108,7 +127,10 @@ function getMimirManagedRulesPerGroupScenes() {
query: 'label_values(grafanacloud_instance_rule_group_rules,rule_group)', query: 'label_values(grafanacloud_instance_rule_group_rules,rule_group)',
}); });
return new EmbeddedScene({ return new NestedScene({
title: 'Mimir-managed Rules - Per Rule Group',
canCollapse: true,
isCollapsed: true,
body: new SceneFlexLayout({ body: new SceneFlexLayout({
wrap: 'wrap', wrap: 'wrap',
children: [ children: [
@@ -124,37 +146,3 @@ function getMimirManagedRulesPerGroupScenes() {
controls: [new VariableValueSelectors({})], controls: [new VariableValueSelectors({})],
}); });
} }
export default function Insights() {
const styles = useStyles2(getStyles);
const grafanaScenes = getGrafanaScenes();
const cloudScenes = getCloudScenes();
const mimirRulesScenes = getMimirManagedRulesScenes();
const mimirRulesPerGroupScenes = getMimirManagedRulesPerGroupScenes();
return (
<div className={styles.container}>
<CollapsableSection label="Grafana" isOpen={true}>
<grafanaScenes.Component model={grafanaScenes} />
</CollapsableSection>
<CollapsableSection label="Mimir Alertmanager" isOpen={true}>
<cloudScenes.Component model={cloudScenes} />
</CollapsableSection>
<CollapsableSection label="Mimir-managed Rules" isOpen={true}>
<mimirRulesScenes.Component model={mimirRulesScenes} />
</CollapsableSection>
<CollapsableSection label="Mimir-managed Rules - Per Rule Group" isOpen={true}>
<mimirRulesPerGroupScenes.Component model={mimirRulesPerGroupScenes} />
</CollapsableSection>
</div>
);
}
const getStyles = (theme: GrafanaTheme2) => ({
container: css({
padding: '10px 0 10px 0',
}),
});

View File

@@ -61,8 +61,7 @@ export function getFiringAlertsScene(timeRange: SceneTimeRange, datasource: Data
}); });
return new SceneFlexItem({ return new SceneFlexItem({
width: 'calc(50% - 4px)', minHeight: 300,
height: 300,
body: PanelBuilders.stat().setTitle(panelTitle).setData(transformation).setUnit('percent').build(), body: PanelBuilders.stat().setTitle(panelTitle).setData(transformation).setUnit('percent').build(),
}); });
} }

View File

@@ -18,8 +18,7 @@ export function getFiringAlertsRateScene(timeRange: SceneTimeRange, datasource:
}); });
return new SceneFlexItem({ return new SceneFlexItem({
width: 'calc(50% - 4px)', minHeight: 300,
height: 300,
body: PanelBuilders.timeseries() body: PanelBuilders.timeseries()
.setTitle(panelTitle) .setTitle(panelTitle)
.setData(query) .setData(query)

View File

@@ -103,8 +103,7 @@ export function getMostFiredInstancesScene(timeRange: SceneTimeRange, datasource
}); });
return new SceneFlexItem({ return new SceneFlexItem({
width: 'calc(50% - 4px)', minHeight: 300,
height: 300,
body: PanelBuilders.table().setTitle(panelTitle).setData(transformation).build(), body: PanelBuilders.table().setTitle(panelTitle).setData(transformation).build(),
}); });
} }

View File

@@ -54,8 +54,7 @@ export function getMostFiredRulesScene(timeRange: SceneTimeRange, datasource: Da
}); });
return new SceneFlexItem({ return new SceneFlexItem({
width: 'calc(50% - 4px)', minHeight: 300,
height: 300,
body: PanelBuilders.table().setTitle(panelTitle).setData(transformation).build(), body: PanelBuilders.table().setTitle(panelTitle).setData(transformation).build(),
}); });
} }

View File

@@ -49,8 +49,7 @@ export function getMostFiredLabelsScene(timeRange: SceneTimeRange, datasource: D
}); });
return new SceneFlexItem({ return new SceneFlexItem({
width: 'calc(50% - 8px)', minHeight: 300,
height: 300,
body: PanelBuilders.table().setTitle(panelTitle).setData(query).build(), body: PanelBuilders.table().setTitle(panelTitle).setData(query).build(),
}); });
} }

View File

@@ -4010,9 +4010,9 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@grafana/scenes@npm:^1.1.1": "@grafana/scenes@npm:^1.3.1":
version: 1.1.1 version: 1.3.1
resolution: "@grafana/scenes@npm:1.1.1" resolution: "@grafana/scenes@npm:1.3.1"
dependencies: dependencies:
"@grafana/e2e-selectors": 10.0.2 "@grafana/e2e-selectors": 10.0.2
react-grid-layout: 1.3.4 react-grid-layout: 1.3.4
@@ -4024,7 +4024,7 @@ __metadata:
"@grafana/runtime": 10.0.3 "@grafana/runtime": 10.0.3
"@grafana/schema": 10.0.3 "@grafana/schema": 10.0.3
"@grafana/ui": 10.0.3 "@grafana/ui": 10.0.3
checksum: 6405998a40e38f088443f5d4b1f5ea1f73e5bc0d08216e4aaccf8ff0b68ec4c3d691430857f357d3eed335dee0dc2e24c41c5c2f286fc7fdd32375382ad3eafe checksum: fe348e4eaaa3604d0d1ec745b14ae1c0752ce1aa481e5f05a10cdf9392448386e600c2a94f2ae23af83f59daf34cd11fb7a336da863b8624b98c8d3c2732b3c3
languageName: node languageName: node
linkType: hard linkType: hard
@@ -19702,7 +19702,7 @@ __metadata:
"@grafana/lezer-traceql": 0.0.6 "@grafana/lezer-traceql": 0.0.6
"@grafana/monaco-logql": ^0.0.7 "@grafana/monaco-logql": ^0.0.7
"@grafana/runtime": "workspace:*" "@grafana/runtime": "workspace:*"
"@grafana/scenes": ^1.1.1 "@grafana/scenes": ^1.3.1
"@grafana/schema": "workspace:*" "@grafana/schema": "workspace:*"
"@grafana/tsconfig": ^1.3.0-rc1 "@grafana/tsconfig": ^1.3.0-rc1
"@grafana/ui": "workspace:*" "@grafana/ui": "workspace:*"