mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
85a207fceb
commit
6755be66f9
@ -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')
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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 (
|
49
public/app/features/alerting/unified/home/Home.tsx
Normal file
49
public/app/features/alerting/unified/home/Home.tsx
Normal 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>
|
||||
);
|
||||
}
|
160
public/app/features/alerting/unified/home/Insights.tsx
Normal file
160
public/app/features/alerting/unified/home/Insights.tsx
Normal 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',
|
||||
}),
|
||||
});
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user