Alerting: Add Insights tab (#72407)

* Add tabs for Insights/Getting Started in home page

* Display panels using the scenes framework

* Hide Insights page under a feature flag

* Add a new panel and improve ASH queries

* Improve panels layout

* Add more panels

* Change datasource UID

* Rename most fired alerts table title

* Show navigation cards on top

* Improve panels

* Add transformation to display link to view alert rule

* Restructure panels

* Fix panels layout

* Fix grid layout in landing tab

* Add transformation to most fired rules table

* Move grafana-specific scenes under dedicated folder

* Add scene with custom datasource that queries an API

* Add scenes panels for more datasources

Not just ASH but including grafanacloud-usage and grafanacloud-prom metrics

* Changes to grafana panels

* Add per rule group scenes with query variables

Also improve existing panels legends/formatting

* Fix lint

* Fix legends for some panels

* Fix lint

* Move files under new alerting home directory

* Refactor transformation in MostFiredInstancesTable scene

* fix lint

* Display panels in collapsable sections

* Improvements to data displayed in several panels
This commit is contained in:
Virginia Cepeda 2023-08-31 17:01:47 -03:00 committed by GitHub
parent 85a207fceb
commit 6755be66f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1133 additions and 65 deletions

View File

@ -91,7 +91,7 @@ const unifiedRoutes: RouteDescriptor[] = [
{
path: '/alerting',
component: SafeDynamicImport(
() => import(/* webpackChunkName: "AlertingHome" */ 'app/features/alerting/unified/Home')
() => import(/* webpackChunkName: "AlertingHome" */ 'app/features/alerting/unified/home/Home')
),
},
{

View File

@ -6,6 +6,7 @@ export enum AlertingFeature {
NotificationPoliciesV2MatchingInstances = 'notification-policies.v2.matching-instances',
DetailsViewV2 = 'details-view.v2',
ContactPointsV2 = 'contact-points.v2',
InsightsPage = 'insights-page',
}
const FEATURES: FeatureDescription[] = [
@ -21,5 +22,9 @@ const FEATURES: FeatureDescription[] = [
name: AlertingFeature.DetailsViewV2,
defaultValue: false,
},
{
name: AlertingFeature.InsightsPage,
defaultValue: false,
},
];
export default FEATURES;

View File

@ -6,74 +6,69 @@ import { GrafanaTheme2 } from '@grafana/data';
import { Stack } from '@grafana/experimental';
import { Icon, useStyles2, useTheme2 } from '@grafana/ui';
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
export default function Home() {
export default function GettingStarted({ showWelcomeHeader }: { showWelcomeHeader?: boolean }) {
const theme = useTheme2();
const styles = useStyles2(getWelcomePageStyles);
return (
<AlertingPageWrapper pageId={'alerting'}>
<div className={styles.grid}>
<WelcomeHeader className={styles.ctaContainer} />
<ContentBox className={styles.flowBlock}>
<div className={styles.grid}>
{showWelcomeHeader && <WelcomeHeader className={styles.ctaContainer} />}
<ContentBox className={styles.flowBlock}>
<div>
<h3>How it works</h3>
<ul className={styles.list}>
<li>
Grafana alerting periodically queries data sources and evaluates the condition defined in the alert rule
</li>
<li>If the condition is breached, an alert instance fires</li>
<li>Firing instances are routed to notification policies based on matching labels</li>
<li>Notifications are sent out to the contact points specified in the notification policy</li>
</ul>
</div>
<SVG
src={`public/img/alerting/at_a_glance_${theme.name.toLowerCase()}.svg`}
width={undefined}
height={undefined}
/>
</ContentBox>
<ContentBox className={styles.gettingStartedBlock}>
<h3>Get started</h3>
<Stack direction="column" alignItems="space-between">
<ul className={styles.list}>
<li>
<strong>Create an alert rule</strong> by adding queries and expressions from multiple data sources.
</li>
<li>
<strong>Add labels</strong> to your alert rules <strong>to connect them to notification policies</strong>
</li>
<li>
<strong>Configure contact points</strong> to define where to send your notifications to.
</li>
<li>
<strong>Configure notification policies</strong> to route your alert instances to contact points.
</li>
</ul>
<div>
<h3>How it works</h3>
<ul className={styles.list}>
<li>
Grafana alerting periodically queries data sources and evaluates the condition defined in the alert rule
</li>
<li>If the condition is breached, an alert instance fires</li>
<li>Firing instances are routed to notification policies based on matching labels</li>
<li>Notifications are sent out to the contact points specified in the notification policy</li>
</ul>
<ArrowLink href="https://grafana.com/docs/grafana/latest/alerting/" title="Read more in the Docs" />
</div>
<SVG
src={`public/img/alerting/at_a_glance_${theme.name.toLowerCase()}.svg`}
width={undefined}
height={undefined}
/>
</ContentBox>
<ContentBox className={styles.gettingStartedBlock}>
<h3>Get started</h3>
<Stack direction="column" alignItems="space-between">
<ul className={styles.list}>
<li>
<strong>Create an alert rule</strong> by adding queries and expressions from multiple data sources.
</li>
<li>
<strong>Add labels</strong> to your alert rules{' '}
<strong>to connect them to notification policies</strong>
</li>
<li>
<strong>Configure contact points</strong> to define where to send your notifications to.
</li>
<li>
<strong>Configure notification policies</strong> to route your alert instances to contact points.
</li>
</ul>
<div>
<ArrowLink href="https://grafana.com/docs/grafana/latest/alerting/" title="Read more in the Docs" />
</div>
</Stack>
</ContentBox>
<ContentBox className={styles.videoBlock}>
<iframe
title="Alerting - Introductory video"
src="https://player.vimeo.com/video/720001629?h=c6c1732f92"
width="960"
height="540"
allow="autoplay; fullscreen"
allowFullScreen
frameBorder="0"
// This is necessary because color-scheme defined on :root has impact on iframe elements
// More about how color-scheme works for iframes https://github.com/w3c/csswg-drafts/issues/4772
// Summary: If the color scheme of an iframe differs from embedding document iframe gets an opaque canvas bg appropriate to its color scheme
style={{ colorScheme: 'light dark' }}
></iframe>
</ContentBox>
</div>
</AlertingPageWrapper>
</Stack>
</ContentBox>
<ContentBox className={styles.videoBlock}>
<iframe
title="Alerting - Introductory video"
src="https://player.vimeo.com/video/720001629?h=c6c1732f92"
width="960"
height="540"
allow="autoplay; fullscreen"
allowFullScreen
frameBorder="0"
// This is necessary because color-scheme defined on :root has impact on iframe elements
// More about how color-scheme works for iframes https://github.com/w3c/csswg-drafts/issues/4772
// Summary: If the color scheme of an iframe differs from embedding document iframe gets an opaque canvas bg appropriate to its color scheme
style={{ colorScheme: 'light dark' }}
></iframe>
</ContentBox>
</div>
);
}
@ -105,7 +100,6 @@ const getWelcomePageStyles = (theme: GrafanaTheme2) => ({
`,
videoBlock: css`
grid-column: 3 / span 3;
grid-row: 3 / span 1;
// Video required
position: relative;
@ -132,7 +126,7 @@ const getWelcomePageStyles = (theme: GrafanaTheme2) => ({
`,
});
function WelcomeHeader({ className }: { className?: string }) {
export function WelcomeHeader({ className }: { className?: string }) {
const styles = useStyles2(getWelcomeHeaderStyles);
return (

View File

@ -0,0 +1,49 @@
import React, { useState } from 'react';
import { Enable, Disable } from 'react-enable';
import { Tab, TabContent, TabsBar } from '@grafana/ui';
import { AlertingPageWrapper } from '../components/AlertingPageWrapper';
import { AlertingFeature } from '../features';
import GettingStarted, { WelcomeHeader } from './GettingStarted';
import Insights from './Insights';
type HomeTabs = 'insights' | 'gettingStarted';
export default function Home() {
const [activeTab, setActiveTab] = useState<HomeTabs>('insights');
return (
<AlertingPageWrapper pageId={'alerting'}>
<Enable feature={AlertingFeature.InsightsPage}>
<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>
</Enable>
<Disable feature={AlertingFeature.InsightsPage}>
<GettingStarted showWelcomeHeader={true} />
</Disable>
</AlertingPageWrapper>
);
}

View File

@ -0,0 +1,160 @@
import { css } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import {
EmbeddedScene,
QueryVariable,
SceneFlexLayout,
SceneTimeRange,
SceneVariableSet,
VariableValueSelectors,
} from '@grafana/scenes';
import { useStyles2, CollapsableSection } from '@grafana/ui';
import { getFiringAlertsScene } from '../insights/grafana/FiringAlertsPercentage';
import { getFiringAlertsRateScene } from '../insights/grafana/FiringAlertsRate';
import { getMostFiredInstancesScene } from '../insights/grafana/MostFiredInstancesTable';
import { getAlertsByStateScene } from '../insights/mimir/AlertsByState';
import { getInvalidConfigScene } from '../insights/mimir/InvalidConfig';
import { getNotificationsScene } from '../insights/mimir/Notifications';
import { getSilencesScene } from '../insights/mimir/Silences';
import { getRuleGroupEvaluationDurationScene } from '../insights/mimir/perGroup/RuleGroupEvaluationDurationScene';
import { getRuleGroupEvaluationsScene } from '../insights/mimir/perGroup/RuleGroupEvaluationsScene';
import { getRuleGroupIntervalScene } from '../insights/mimir/perGroup/RuleGroupIntervalScene';
import { getRulesPerGroupScene } from '../insights/mimir/perGroup/RulesPerGroupScene';
import { getEvalSuccessVsFailuresScene } from '../insights/mimir/rules/EvalSuccessVsFailuresScene';
import { getFiringCloudAlertsScene } from '../insights/mimir/rules/Firing';
import { getInstancesByStateScene } from '../insights/mimir/rules/InstancesByState';
import { getInstancesPercentageByStateScene } from '../insights/mimir/rules/InstancesPercentageByState';
import { getMissedIterationsScene } from '../insights/mimir/rules/MissedIterationsScene';
import { getMostFiredInstancesScene as getMostFiredCloudInstances } from '../insights/mimir/rules/MostFiredInstances';
import { getPendingCloudAlertsScene } from '../insights/mimir/rules/Pending';
const ashDs = {
type: 'loki',
uid: 'grafanacloud-alert-state-history',
};
const cloudUsageDs = {
type: 'prometheus',
uid: 'grafanacloud-usage',
};
const grafanaCloudPromDs = {
type: 'prometheus',
uid: 'grafanacloud-prom',
};
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' });
function getGrafanaScenes() {
return new EmbeddedScene({
body: new SceneFlexLayout({
wrap: 'wrap',
children: [
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'),
],
}),
});
}
function getCloudScenes() {
return new EmbeddedScene({
body: new SceneFlexLayout({
wrap: 'wrap',
children: [
getAlertsByStateScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Alerts by State'),
getNotificationsScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Notifications'),
getSilencesScene(LAST_WEEK_TIME_RANGE, cloudUsageDs, 'Silences'),
getInvalidConfigScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Invalid configuration'),
],
}),
});
}
function getMimirManagedRulesScenes() {
return new EmbeddedScene({
body: new SceneFlexLayout({
wrap: 'wrap',
children: [
getMostFiredCloudInstances(THIS_WEEK_TIME_RANGE, grafanaCloudPromDs, 'Top 10 firing instance this week'),
getFiringCloudAlertsScene(THIS_WEEK_TIME_RANGE, grafanaCloudPromDs, 'Firing'),
getPendingCloudAlertsScene(THIS_WEEK_TIME_RANGE, grafanaCloudPromDs, 'Pending'),
getInstancesByStateScene(THIS_WEEK_TIME_RANGE, grafanaCloudPromDs, 'Count of alert instances by state'),
getInstancesPercentageByStateScene(THIS_WEEK_TIME_RANGE, grafanaCloudPromDs, '% of Alert Instances by State'),
getEvalSuccessVsFailuresScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Evaluation Success vs Failures'),
getMissedIterationsScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Iterations Missed'),
],
}),
});
}
function getMimirManagedRulesPerGroupScenes() {
const ruleGroupHandler = new QueryVariable({
label: 'Rule Group',
name: 'rule_group',
datasource: cloudUsageDs,
query: 'label_values(grafanacloud_instance_rule_group_rules,rule_group)',
});
return new EmbeddedScene({
body: new SceneFlexLayout({
wrap: 'wrap',
children: [
getRuleGroupEvaluationsScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Rule Group Evaluation'),
getRuleGroupIntervalScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Rule Group Interval'),
getRuleGroupEvaluationDurationScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Rule Group Evaluation Duration'),
getRulesPerGroupScene(THIS_WEEK_TIME_RANGE, cloudUsageDs, 'Rules per Group'),
],
}),
$variables: new SceneVariableSet({
variables: [ruleGroupHandler],
}),
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

@ -0,0 +1,68 @@
import { PanelBuilders, SceneDataTransformer, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef } from '@grafana/schema';
const TOTALS = 'sum(count_over_time({from="state-history"} | json [1w]))';
const TOTALS_FIRING = 'sum(count_over_time({from="state-history"} | json | current="Alerting"[1w]))';
export function getFiringAlertsScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
instant: true,
expr: TOTALS_FIRING,
},
{
refId: 'B',
instant: true,
expr: TOTALS,
},
],
$timeRange: timeRange,
});
const transformation = new SceneDataTransformer({
$data: query,
transformations: [
{
id: 'calculateField',
options: {
mode: 'binary',
reduce: {
reducer: 'mean',
},
replaceFields: false,
binary: {
left: 'Value #A',
reducer: 'sum',
operator: '*',
right: '100',
},
},
},
{
id: 'calculateField',
options: {
mode: 'binary',
reduce: {
reducer: 'sum',
},
binary: {
left: 'Value #A * 100',
reducer: 'sum',
operator: '/',
right: 'Value #B',
},
replaceFields: true,
},
},
],
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.stat().setTitle(panelTitle).setData(transformation).setUnit('percent').build(),
});
}

View File

@ -0,0 +1,32 @@
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const RATE_FIRING = 'sum(count_over_time({from="state-history"} | json | current="Alerting"[5m]))';
export function getFiringAlertsRateScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: RATE_FIRING,
range: true,
legendFormat: 'Number of fires',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.setCustomFieldConfig('fillOpacity', 10)
.setCustomFieldConfig('spanNulls', true)
.setCustomFieldConfig('pointSize', 5)
.build(),
});
}

View File

@ -0,0 +1,110 @@
import React from 'react';
import { Observable, map } from 'rxjs';
import { DataFrame, Field } from '@grafana/data';
import {
CustomTransformOperator,
PanelBuilders,
SceneDataTransformer,
SceneFlexItem,
SceneQueryRunner,
SceneTimeRange,
} from '@grafana/scenes';
import { DataSourceRef } from '@grafana/schema';
import { Icon, Link } from '@grafana/ui';
import { createUrl } from '../../utils/url';
const TOP_FIRING_INSTANCES =
'topk(10, sum by(labels_alertname, ruleUID) (count_over_time({from="state-history"} | json | current = `Alerting` [1w])))';
export function getMostFiredInstancesScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: TOP_FIRING_INSTANCES,
instant: true,
},
],
$timeRange: timeRange,
});
const createRuleLink = (field: Field<string>, frame: DataFrame) => {
return {
...field,
values: field.values.map((value, index) => {
const ruleUIDs = frame.fields.find((field) => field.name === 'ruleUID');
const ruleUID = ruleUIDs?.values[index];
return (
<Link key={value} target="_blank" href={createUrl(`/alerting/grafana/${ruleUID}/view`)}>
{value} <Icon name="external-link-alt" />
</Link>
);
}),
};
};
const ruleLinkTransformation: CustomTransformOperator = () => (source: Observable<DataFrame[]>) => {
return source.pipe(
map((data: DataFrame[]) => {
return data.map((frame: DataFrame) => {
return {
...frame,
fields: frame.fields.map((field) => {
//Transforming the column `labels_alertname` to show a link to the rule view page next to the alert name
if (field.name === 'labels_alertname') {
return createRuleLink(field, frame);
}
return field;
}),
};
});
})
);
};
const transformation = new SceneDataTransformer({
$data: query,
transformations: [
ruleLinkTransformation,
{
id: 'sortBy',
options: {
fields: {},
sort: [
{
field: 'Value #A',
desc: true,
},
],
},
},
{
id: 'organize',
options: {
excludeByName: {
Time: true,
ruleUID: true,
},
indexByName: {
labels_alertname: 0,
'Value #A': 1,
},
renameByName: {
labels_alertname: 'Alert Name',
'Value #A': 'Fires this week',
},
},
},
],
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.table().setTitle(panelTitle).setData(transformation).build(),
});
}

View File

@ -0,0 +1,61 @@
import { PanelBuilders, SceneDataTransformer, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef } from '@grafana/schema';
const TOP_5_FIRING_RULES =
'topk(5, sum by(group, labels_grafana_folder) (count_over_time({from="state-history"} | json | current = `Alerting` [1w])))';
export function getMostFiredRulesScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: TOP_5_FIRING_RULES,
instant: true,
},
],
$timeRange: timeRange,
});
const transformation = new SceneDataTransformer({
$data: query,
transformations: [
{
id: 'sortBy',
options: {
fields: {},
sort: [
{
field: 'Value #A',
desc: true,
},
],
},
},
{
id: 'organize',
options: {
excludeByName: {
Time: true,
},
indexByName: {
group: 0,
labels_grafana_folder: 1,
'Value #A': 2,
},
renameByName: {
group: 'Group',
labels_grafana_folder: 'Folder',
'Value #A': 'Fires this week',
},
},
},
],
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.table().setTitle(panelTitle).setData(transformation).build(),
});
}

View File

@ -0,0 +1,56 @@
import { Observable } from 'rxjs';
import { DataQueryRequest, DataQueryResponse, DataQueryResponseData, TestDataSourceResponse } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import {
PanelBuilders,
RuntimeDataSource,
SceneFlexItem,
SceneQueryRunner,
SceneTimeRange,
sceneUtils,
} from '@grafana/scenes';
import { DataQuery, DataSourceRef } from '@grafana/schema';
import { getTimeRange } from 'app/features/dashboard/utils/timeRange';
export const getLabelsInfo = (from: number, to: number): Observable<DataQueryResponseData> => {
return getBackendSrv().fetch({
url: `/api/v1/rules/history`,
params: { from, to, limit: 100 },
method: 'GET',
});
};
class LokiAPIDatasource extends RuntimeDataSource {
private timeRange: SceneTimeRange;
constructor(pluginId: string, uid: string, timeRange: SceneTimeRange) {
super(pluginId, uid);
this.timeRange = timeRange;
}
query(request: DataQueryRequest<DataQuery>): Promise<DataQueryResponse> | Observable<DataQueryResponse> {
const timeRange = getTimeRange({ from: this.timeRange.state.from, to: this.timeRange.state.to });
return getLabelsInfo(timeRange.from.unix(), timeRange.to.unix());
}
testDatasource(): Promise<TestDataSourceResponse> {
return Promise.resolve({ status: 'success', message: 'Data source is working', title: 'Success' });
}
}
export function getMostFiredLabelsScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
sceneUtils.registerRuntimeDataSource({ dataSource: new LokiAPIDatasource('loki-api-ds', 'LOKI-API', timeRange) });
const query = new SceneQueryRunner({
datasource: { uid: 'LOKI-API', type: 'loki-api-ds' },
queries: [{ refId: 'A', expr: 'vector(1)' }],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 8px)',
height: 300,
body: PanelBuilders.table().setTitle(panelTitle).setData(query).build(),
});
}

View File

@ -0,0 +1,29 @@
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const QUERY = 'sum by (state) (grafanacloud_instance_alertmanager_alerts)';
export function getAlertsByStateScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: QUERY,
range: true,
legendFormat: '{{state}}',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.build(),
});
}

View File

@ -0,0 +1,30 @@
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const QUERY_A = 'sum by (cluster)(grafanacloud_instance_alertmanager_invalid_config)';
export function getInvalidConfigScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: QUERY_A,
range: true,
legendFormat: '{{cluster}}',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.setUnit('bool_yes_no')
.build(),
});
}

View File

@ -0,0 +1,37 @@
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const QUERY_A =
'sum by(cluster)(grafanacloud_instance_alertmanager_notifications_per_second) - sum by (cluster)(grafanacloud_instance_alertmanager_notifications_failed_per_second)';
const QUERY_B = 'sum by(cluster)(grafanacloud_instance_alertmanager_notifications_failed_per_second)';
export function getNotificationsScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: QUERY_A,
range: true,
legendFormat: '{{cluster}} - success',
},
{
refId: 'B',
expr: QUERY_B,
range: true,
legendFormat: '{{cluster}} - failed',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.build(),
});
}

View File

@ -0,0 +1,29 @@
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const QUERY_A = 'sum by (state) (grafanacloud_instance_alertmanager_silences)';
export function getSilencesScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: QUERY_A,
range: true,
legendFormat: '{{state}}',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.build(),
});
}

View File

@ -0,0 +1,34 @@
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const QUERY_A = `grafanacloud_instance_rule_group_last_duration_seconds{rule_group="$rule_group"}`;
export function getRuleGroupEvaluationDurationScene(
timeRange: SceneTimeRange,
datasource: DataSourceRef,
panelTitle: string
) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: QUERY_A,
range: true,
legendFormat: '{{rule_group}}',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.setUnit('s')
.build(),
});
}

View File

@ -0,0 +1,36 @@
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const QUERY_A = `grafanacloud_instance_rule_evaluations_total:rate5m{rule_group="$rule_group"} - grafanacloud_instance_rule_evaluation_failures_total:rate5m{rule_group=~"$rule_group"}`;
const QUERY_B = `grafanacloud_instance_rule_evaluation_failures_total:rate5m{rule_group=~"$rule_group"}`;
export function getRuleGroupEvaluationsScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: QUERY_A,
range: true,
legendFormat: 'success',
},
{
refId: 'B',
expr: QUERY_B,
range: true,
legendFormat: 'failed',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.build(),
});
}

View File

@ -0,0 +1,30 @@
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const QUERY_A = `grafanacloud_instance_rule_group_interval_seconds{rule_group="$rule_group"}`;
export function getRuleGroupIntervalScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: QUERY_A,
range: true,
legendFormat: 'interval',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.setUnit('s')
.build(),
});
}

View File

@ -0,0 +1,30 @@
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const QUERY_A = `sum(grafanacloud_instance_rule_group_rules{rule_group="$rule_group"})`;
export function getRulesPerGroupScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: QUERY_A,
range: true,
legendFormat: 'number of rules',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.setUnit('none')
.build(),
});
}

View File

@ -0,0 +1,42 @@
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const QUERY_A =
'sum(grafanacloud_instance_rule_evaluations_total:rate5m) - sum(grafanacloud_instance_rule_evaluation_failures_total:rate5m)';
const QUERY_B = 'sum(grafanacloud_instance_rule_evaluation_failures_total:rate5m)';
export function getEvalSuccessVsFailuresScene(
timeRange: SceneTimeRange,
datasource: DataSourceRef,
panelTitle: string
) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: QUERY_A,
range: true,
legendFormat: 'success',
},
{
refId: 'B',
expr: QUERY_B,
range: true,
legendFormat: 'failed',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.build(),
});
}

View File

@ -0,0 +1,41 @@
import { ThresholdsMode } from '@grafana/data';
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef } from '@grafana/schema';
const QUERY = 'sum by (alertstate) (ALERTS{alertstate="firing"})';
export function getFiringCloudAlertsScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
instant: true,
expr: QUERY,
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(25% - 4px)',
height: 300,
body: PanelBuilders.stat()
.setTitle(panelTitle)
.setData(query)
.setThresholds({
mode: ThresholdsMode.Absolute,
steps: [
{
color: 'red',
value: 0,
},
{
color: 'red',
value: 80,
},
],
})
.build(),
});
}

View File

@ -0,0 +1,29 @@
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const QUERY = 'sum by (alertstate) (ALERTS)';
export function getInstancesByStateScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: QUERY,
range: true,
legendFormat: '{{state}}',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.build(),
});
}

View File

@ -0,0 +1,50 @@
import { ThresholdsMode } from '@grafana/data';
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const QUERY = 'sum by (alertstate) (ALERTS) / ignoring(alertstate) group_left sum(ALERTS)';
export function getInstancesPercentageByStateScene(
timeRange: SceneTimeRange,
datasource: DataSourceRef,
panelTitle: string
) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: QUERY,
range: true,
legendFormat: '{{alertstate}}',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.setCustomFieldConfig('fillOpacity', 45)
.setUnit('percentunit')
.setMax(1)
.setThresholds({
mode: ThresholdsMode.Absolute,
steps: [
{
color: 'green',
value: 0,
},
{
color: 'red',
value: 80,
},
],
})
.build(),
});
}

View File

@ -0,0 +1,29 @@
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef, GraphDrawStyle } from '@grafana/schema';
const QUERY_A = 'sum(grafanacloud_instance_rule_group_iterations_missed_total:rate5m)';
export function getMissedIterationsScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: QUERY_A,
range: true,
legendFormat: 'missed',
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(50% - 4px)',
height: 300,
body: PanelBuilders.timeseries()
.setTitle(panelTitle)
.setData(query)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.build(),
});
}

View File

@ -0,0 +1,46 @@
import { PanelBuilders, SceneDataTransformer, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef } from '@grafana/schema';
const TOP_5_FIRING_INSTANCES = 'topk(10, sum by (alertname) (ALERTS))';
export function getMostFiredInstancesScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
expr: TOP_5_FIRING_INSTANCES,
range: true,
},
],
$timeRange: timeRange,
});
const transformation = new SceneDataTransformer({
$data: query,
transformations: [
{
id: 'timeSeriesTable',
options: {},
},
{
id: 'organize',
options: {
excludeByName: {},
indexByName: {},
renameByName: {
Trend: '',
alertname: 'Alert Rule Name',
},
},
},
],
});
return new SceneFlexItem({
width: 'calc(50% - 8px)',
height: 300,
body: PanelBuilders.table().setTitle(panelTitle).setData(transformation).build(),
});
}

View File

@ -0,0 +1,41 @@
import { ThresholdsMode } from '@grafana/data';
import { PanelBuilders, SceneFlexItem, SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';
import { DataSourceRef } from '@grafana/schema';
const QUERY = 'sum by (alertstate) (ALERTS{alertstate="pending"})';
export function getPendingCloudAlertsScene(timeRange: SceneTimeRange, datasource: DataSourceRef, panelTitle: string) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
refId: 'A',
instant: true,
expr: QUERY,
},
],
$timeRange: timeRange,
});
return new SceneFlexItem({
width: 'calc(25% - 4px)',
height: 300,
body: PanelBuilders.stat()
.setTitle(panelTitle)
.setData(query)
.setThresholds({
mode: ThresholdsMode.Absolute,
steps: [
{
color: 'yellow',
value: 0,
},
{
color: 'red',
value: 80,
},
],
})
.build(),
});
}