Improve insights (#3896)

# What this PR does
Redesign panels, update queries, handle no data scenario better

## Which issue(s) this PR fixes
https://github.com/grafana/oncall/issues/3763


## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
This commit is contained in:
Dominik Broj 2024-02-15 13:52:04 +01:00 committed by GitHub
parent 445ac74088
commit caef9e2eb0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 174 additions and 539 deletions

View file

@ -43,14 +43,13 @@ test.describe('Insights', () => {
test('Viewer can see all the panels in OnCall insights', async ({ viewerRolePage: { page } }) => {
await goToOnCallPage(page, 'insights');
[
'Total alert groups',
'Total alert groups by state',
'New alert groups for selected period',
'Mean time to respond \\(MTTR\\)',
'MTTR changed for period',
'New alert groups during time period',
'New alert groups',
'Mean time to respond \\(MTTR\\) average',
'Alert groups by Integration',
'Mean time to respond \\(MTTR\\) by Integration',
'Alert groups by Team',
'Mean time to respond \\(MTTR\\) by Team',
'New alert groups notifications',
].forEach(async (panelTitle) => {
await expect(page.getByRole('heading', { name: new RegExp(`^${panelTitle}$`) }).first()).toBeVisible();
});
@ -58,7 +57,7 @@ test.describe('Insights', () => {
test('There is no panel that misses data', async ({ adminRolePage: { page } }) => {
await goToOnCallPage(page, 'insights');
await page.getByText('Last 7 days').click();
await page.getByText('Last 24 hours').click();
await page.getByText('Last 1 hour').click();
await page.waitForTimeout(2000);
await expect(page.getByText('No data')).toBeHidden();

View file

@ -282,7 +282,7 @@ export class AlertGroupStore extends BaseStore {
this.updateAlertGroups();
}
@action
@action.bound
async updateAlertGroups() {
this.alertGroupsLoading = true;

View file

@ -12,14 +12,14 @@ class LoaderStoreClass {
makeObservable(this);
}
@action
@action.bound
setLoadingAction(actionKey: string, isLoading: boolean) {
this.items[actionKey] = isLoading;
}
isLoading(actionKey: string): boolean {
isLoading = (actionKey: string): boolean => {
return !!this.items[actionKey];
}
};
}
export const LoaderStore = new LoaderStoreClass();

View file

@ -0,0 +1,35 @@
import { useEffect, useState } from 'react';
import { useStore } from 'state/useStore';
const TWENTY_SECS = 20_000;
const FIVE_SECS = 5_000;
export const useAlertCreationChecker = () => {
const {
alertGroupStore: { updateAlertGroups, alerts },
} = useStore();
const [isFirstAlertCheckDone, setIsFirstAlertCheckDone] = useState(false);
const isAnyAlertCreatedMoreThan20SecsAgo = Array.from(alerts).some(([_key, alert]) => {
const alertTime = new Date(alert.started_at).getTime();
const nowTime = new Date().getTime();
return nowTime - alertTime > TWENTY_SECS;
});
useEffect(() => {
const fetch = async () => {
if (!isAnyAlertCreatedMoreThan20SecsAgo) {
await updateAlertGroups();
}
setIsFirstAlertCheckDone(true);
};
fetch();
const interval = setInterval(() => {
fetch();
}, FIVE_SECS);
return () => clearInterval(interval);
}, [isAnyAlertCreatedMoreThan20SecsAgo]);
return { isAnyAlertCreatedMoreThan20SecsAgo, isFirstAlertCheckDone };
};

View file

@ -6,6 +6,6 @@
margin: 0;
}
.alertBox {
.spaceTop {
margin-top: 16px;
}

View file

@ -14,29 +14,28 @@ import {
SceneAppPage,
useSceneApp,
} from '@grafana/scenes';
import { Alert } from '@grafana/ui';
import { Alert, LoadingPlaceholder, VerticalGroup } from '@grafana/ui';
import { observer } from 'mobx-react';
import { Text } from 'components/Text/Text';
import { Tutorial } from 'components/Tutorial/Tutorial';
import { TutorialStep } from 'components/Tutorial/Tutorial.types';
import { useStore } from 'state/useStore';
import { DOCS_ROOT } from 'utils/consts';
import { DOCS_ROOT, PLUGIN_ROOT } from 'utils/consts';
import { useAlertCreationChecker } from './Insights.hooks';
import styles from './Insights.module.scss';
import { InsightsConfig } from './Insights.types';
import getAlertGroupsByIntegrationScene from './scenes/AlertGroupsByIntegration';
import getAlertGroupsByTeamScene from './scenes/AlertGroupsByTeam';
import getMTTRScene from './scenes/MTTR';
import getMTTRByIntegrationScene from './scenes/MTTRByIntegration';
import getMTTRByTeamScene from './scenes/MTTRByTeam';
import getMTTRChangedForPeriodStatScene from './scenes/MTTRChangedForPeriodStat';
import getMTTRChangedForPeriodTimeseriesScene from './scenes/MTTRChangedForPeriodTimeseries';
import getNewAlertGroupsDuringTimePeriodScene from './scenes/NewAlertGroupsDuringTimePeriod';
import getNewAlertGroupsForSelectedPeriodScene from './scenes/NewAlertGroupsForSelectedPeriod';
import getNewAlertGroupsNotificationsDuringTimePeriodScene from './scenes/NewAlertGroupsNotificationsDuringTimePeriod';
import getNewAlertGroupsNotificationsForPeriodTableScene from './scenes/NewAlertGroupsNotificationsForPeriodTable';
import getNewAlertGroupsNotificationsInTotalScene from './scenes/NewAlertGroupsNotificationsInTotal';
import getTotalAlertGroupsScene from './scenes/TotalAlertGroups';
import getTotalAlertGroupsByStateScene from './scenes/TotalAlertGroupsByState';
import { getAlertGroupsByIntegrationScene } from './scenes/AlertGroupsByIntegration';
import { getAlertGroupsByTeamScene } from './scenes/AlertGroupsByTeam';
import { getMTTRAverage } from './scenes/MTTRAverageStat';
import { getMTTRByIntegrationScene } from './scenes/MTTRByIntegration';
import { getMTTRByTeamScene } from './scenes/MTTRByTeam';
import { getMTTRChangedTimeseriesScene } from './scenes/MTTRChangedTimeseries';
import { getNewAlertGroupsScene } from './scenes/NewAlertGroups';
import { getNewAlertGroupsNotificationsTableScene } from './scenes/NewAlertGroupsNotificationsTable';
import { getNewAlertGroupsNotificationsTimeseriesScene } from './scenes/NewAlertGroupsNotificationsTimeseries';
import { getNewAlertGroupsTimeseriesScene } from './scenes/NewAlertGroupsTimeseries';
import getVariables from './variables';
export const Insights = observer(() => {
@ -45,8 +44,8 @@ export const Insights = observer(() => {
insightsDatasource,
organizationStore: { currentOrganization },
} = useStore();
const [showAllStackInfo, setShowAllStackInfo] = useState(false);
const [datasource, setDatasource] = useState<string>();
const { isAnyAlertCreatedMoreThan20SecsAgo, isFirstAlertCheckDone } = useAlertCreationChecker();
const config = useMemo(
() => ({
@ -54,7 +53,7 @@ export const Insights = observer(() => {
datasource: { uid: isOpenSource ? '$datasource' : insightsDatasource },
stack: currentOrganization?.stack_slug,
}),
[]
[isOpenSource, currentOrganization?.stack_slug]
);
const variables = useMemo(() => getVariables(config), [config]);
@ -64,26 +63,33 @@ export const Insights = observer(() => {
const appScene = useSceneApp(getAppScene);
useEffect(() => {
const stackListener = variables.stack.subscribeToState(({ text }) => {
setShowAllStackInfo((text as string[]).includes('All'));
});
if (!isAnyAlertCreatedMoreThan20SecsAgo) {
return undefined;
}
const dataSourceListener =
isOpenSource &&
variables.datasource.subscribeToState(({ text }) => {
setDatasource(`${text}`);
});
return () => {
stackListener?.unsubscribe?.();
dataSourceListener?.unsubscribe?.();
};
}, []);
}, [isAnyAlertCreatedMoreThan20SecsAgo]);
if (!isFirstAlertCheckDone) {
return <LoadingPlaceholder text="Loading..." />;
}
return (
<div className={styles.insights}>
<InsightsGeneralInfo />
{showAllStackInfo && <AllStacksSelectedWarning />}
{isOpenSource && !datasource && <NoDatasourceWarning />}
<appScene.Component model={appScene} />
{isAnyAlertCreatedMoreThan20SecsAgo ? (
<>
{isOpenSource && !datasource && <NoDatasourceWarning />}
<appScene.Component model={appScene} />
</>
) : (
<NoAlertCreatedTutorial />
)}
</div>
);
});
@ -97,15 +103,23 @@ const InsightsGeneralInfo = () => {
return <Text type="secondary">Find out more about OnCall Insights and Metrics in our {docsLink}.</Text>;
};
const AllStacksSelectedWarning = () => {
const [alertVisible, setAlertVisible] = useState(true);
return alertVisible ? (
<Alert onRemove={() => setAlertVisible(false)} severity="warning" title="" className={styles.alertBox}>
Retrieving insights from multiple stacks has performance impact and loading data might take significantly more
time. We recommend to select only specific stacks.
</Alert>
) : null;
const NoAlertCreatedTutorial = () => {
return (
<div className={styles.spaceTop}>
<Tutorial
step={TutorialStep.Integrations}
title={
<VerticalGroup align="center" spacing="lg">
<Text type="secondary">
Your OnCall stack doesnt have any alerts to visualise insights.
<br />
Make sure that you setup OnCall configuration to start monitoring.
</Text>
</VerticalGroup>
}
/>
</div>
);
};
const NoDatasourceWarning = () => {
@ -128,10 +142,10 @@ const getRootScene = (config: InsightsConfig, variables: ReturnType<typeof getVa
pages: [
new SceneAppPage({
title: 'OnCall Insights',
url: '/a/grafana-oncall-app/insights',
url: `${PLUGIN_ROOT}/insights`,
getScene: () =>
new EmbeddedScene({
$timeRange: new SceneTimeRange({ from: 'now-7d', to: 'now' }),
$timeRange: new SceneTimeRange({ from: 'now-24h', to: 'now' }),
$variables: new SceneVariableSet({
variables: Object.values(variables),
}),
@ -152,22 +166,12 @@ const getRootScene = (config: InsightsConfig, variables: ReturnType<typeof getVa
direction: 'column',
children: [
new SceneFlexLayout({
height: 200,
children: [
getTotalAlertGroupsScene(config),
getTotalAlertGroupsByStateScene(config),
getNewAlertGroupsForSelectedPeriodScene(config),
getMTTRScene(config),
getMTTRChangedForPeriodStatScene(config),
],
height: 300,
children: [getNewAlertGroupsScene(config), getMTTRAverage(config)],
}),
new SceneFlexLayout({
height: 400,
children: [getNewAlertGroupsDuringTimePeriodScene(config)],
}),
new SceneFlexLayout({
height: 400,
children: [getMTTRChangedForPeriodTimeseriesScene(config)],
height: 300,
children: [getNewAlertGroupsTimeseriesScene(config), getMTTRChangedTimeseriesScene(config)],
}),
],
}),
@ -177,31 +181,10 @@ const getRootScene = (config: InsightsConfig, variables: ReturnType<typeof getVa
canCollapse: true,
isCollapsed: false,
body: new SceneFlexLayout({
height: 400,
height: 300,
children: [getAlertGroupsByIntegrationScene(config), getMTTRByIntegrationScene(config)],
}),
}),
new NestedScene({
title: 'Notified alert groups by Users (based on all Integrations)',
canCollapse: true,
isCollapsed: false,
body: new SceneFlexLayout({
direction: 'column',
children: [
new SceneFlexLayout({
height: 400,
children: [getNewAlertGroupsNotificationsDuringTimePeriodScene(config)],
}),
new SceneFlexLayout({
height: 400,
children: [
getNewAlertGroupsNotificationsInTotalScene(config),
getNewAlertGroupsNotificationsForPeriodTableScene(config),
],
}),
],
}),
}),
new NestedScene({
title: 'Teams data',
canCollapse: true,
@ -210,12 +193,29 @@ const getRootScene = (config: InsightsConfig, variables: ReturnType<typeof getVa
direction: 'column',
children: [
new SceneFlexLayout({
height: 400,
height: 300,
children: [getAlertGroupsByTeamScene(config), getMTTRByTeamScene(config)],
}),
],
}),
}),
new NestedScene({
title: 'Notified alert groups by Users (based on all Teams and Integrations)',
canCollapse: true,
isCollapsed: false,
body: new SceneFlexLayout({
direction: 'column',
children: [
new SceneFlexLayout({
height: 300,
children: [
getNewAlertGroupsNotificationsTimeseriesScene(config),
getNewAlertGroupsNotificationsTableScene(config),
],
}),
],
}),
}),
],
}),
}),

View file

@ -3,14 +3,14 @@ import { SceneDataTransformer, SceneFlexItem, SceneQueryRunner, VizPanel } from
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getAlertGroupsByIntegrationScene({ datasource }: InsightsConfig) {
export function getAlertGroupsByIntegrationScene({ datasource, stack }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
editorMode: 'code',
exemplar: false,
expr: 'sort_desc(max_over_time(sum by(integration) (avg without(pod, instance)($alert_groups_total{slug=~"$stack", team=~"$team", integration=~"$integration"}))[1d:]))',
expr: `sort_desc(delta(max_over_time(sum by(integration) (avg without(pod, instance)($alert_groups_total{slug=~"${stack}", team=~"$team", integration=~"$integration"}))[1h:])[$__range:])>=0)`,
format: 'table',
instant: true,
legendFormat: '__auto',
@ -74,6 +74,7 @@ export default function getAlertGroupsByIntegrationScene({ datasource }: Insight
},
],
},
decimals: 0,
},
overrides: [
{
@ -97,7 +98,7 @@ export default function getAlertGroupsByIntegrationScene({ datasource }: Insight
],
},
options: {
cellHeight: 'sm',
cellHeight: 'md',
footer: {
countRows: false,
fields: '',

View file

@ -3,14 +3,14 @@ import { SceneDataTransformer, SceneFlexItem, SceneQueryRunner, VizPanel } from
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getAlertGroupsByTeamScene({ datasource }: InsightsConfig) {
export function getAlertGroupsByTeamScene({ datasource, stack }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
editorMode: 'code',
exemplar: false,
expr: 'sort_desc(max_over_time(sum by(team) (avg without(pod, instance)($alert_groups_total{slug=~"$stack", team=~"$team", integration=~"$integration"}))[1d:]))',
expr: `sort_desc(delta(max_over_time(sum by(team) (avg without(pod, instance)($alert_groups_total{slug=~"${stack}", team=~"$team", integration=~"$integration"}))[1h:])[$__range:])>=0)`,
format: 'table',
instant: true,
legendFormat: '__auto',
@ -74,6 +74,7 @@ export default function getAlertGroupsByTeamScene({ datasource }: InsightsConfig
},
],
},
decimals: 0,
},
overrides: [
{
@ -97,7 +98,7 @@ export default function getAlertGroupsByTeamScene({ datasource }: InsightsConfig
],
},
options: {
cellHeight: 'sm',
cellHeight: 'md',
footer: {
countRows: false,
fields: '',

View file

@ -1,74 +0,0 @@
import { ThresholdsMode } from '@grafana/data';
import { SceneFlexItem, SceneQueryRunner, VizPanel } from '@grafana/scenes';
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getMTTRScene({ datasource }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
editorMode: 'code',
exemplar: false,
expr: 'avg_over_time((sum($alert_groups_response_time_seconds_sum{slug=~"$stack", team=~"$team", integration=~"$integration"}) / sum($alert_groups_response_time_seconds_count{slug=~"$stack", team=~"$team", integration=~"$integration"}))[$__range:])',
instant: true,
legendFormat: '__auto',
range: false,
refId: 'A',
},
],
});
return new SceneFlexItem({
$data: query,
body: new VizPanel({
title: 'Mean time to respond (MTTR)',
description: 'Mean time between the start and first action of all alert groups for the last 7 days',
pluginId: 'stat',
fieldConfig: {
defaults: {
color: {
mode: 'thresholds',
},
mappings: [],
thresholds: {
mode: ThresholdsMode.Absolute,
steps: [
{
color: 'text',
value: null,
},
],
},
unit: 's',
},
overrides: [
{
matcher: {
id: 'byName',
options: 'Value',
},
properties: [
{
id: 'displayName',
value: 'MTTR',
},
],
},
],
},
options: {
colorMode: 'value',
graphMode: 'none',
justifyMode: 'center',
orientation: 'auto',
reduceOptions: {
calcs: ['lastNotNull'],
fields: '',
values: false,
},
textMode: 'auto',
},
}),
});
}

View file

@ -3,17 +3,17 @@ import { SceneFlexItem, SceneQueryRunner, VizPanel } from '@grafana/scenes';
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getMTTRChangedForPeriodStatScene({ datasource }: InsightsConfig) {
export function getMTTRAverage({ datasource, stack }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
editorMode: 'code',
exemplar: false,
expr: 'avg(sum($alert_groups_response_time_seconds_sum{slug=~"$stack", team=~"$team", integration=~"$integration"}) / sum($alert_groups_response_time_seconds_count{slug=~"$stack", team=~"$team", integration=~"$integration"}))',
instant: false,
expr: `avg_over_time((sum($alert_groups_response_time_seconds_sum{slug=~"${stack}", team=~"$team", integration=~"$integration"}) / sum($alert_groups_response_time_seconds_count{slug=~"${stack}", team=~"$team", integration=~"$integration"}))[$__range:])`,
instant: true,
legendFormat: '__auto',
range: true,
range: false,
refId: 'A',
},
],
@ -22,7 +22,7 @@ export default function getMTTRChangedForPeriodStatScene({ datasource }: Insight
return new SceneFlexItem({
$data: query,
body: new VizPanel({
title: 'MTTR changed for period',
title: 'Mean time to respond (MTTR) average',
pluginId: 'stat',
fieldConfig: {
defaults: {
@ -57,7 +57,7 @@ export default function getMTTRChangedForPeriodStatScene({ datasource }: Insight
justifyMode: 'center',
orientation: 'auto',
reduceOptions: {
calcs: ['diff'],
calcs: ['lastNotNull'],
fields: '',
values: false,
},

View file

@ -3,14 +3,14 @@ import { SceneDataTransformer, SceneFlexItem, SceneQueryRunner, VizPanel } from
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getMTTRByIntegrationScene({ datasource }: InsightsConfig) {
export function getMTTRByIntegrationScene({ datasource, stack }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
editorMode: 'code',
exemplar: false,
expr: 'sort_desc(avg_over_time((sum by (integration)($alert_groups_response_time_seconds_sum{slug=~"$stack", team=~"$team", integration=~"$integration"}) / sum by (integration)($alert_groups_response_time_seconds_count{slug=~"$stack", team=~"$team", integration=~"$integration"}))[$__range:]))',
expr: `sort_desc(avg_over_time((sum by (integration)($alert_groups_response_time_seconds_sum{slug=~"${stack}", team=~"$team", integration=~"$integration"}) / sum by (integration)($alert_groups_response_time_seconds_count{slug=~"${stack}", team=~"$team", integration=~"$integration"}))[$__range:]))`,
format: 'table',
instant: true,
legendFormat: '__auto',
@ -105,14 +105,14 @@ export default function getMTTRByIntegrationScene({ datasource }: InsightsConfig
},
{
id: 'custom.width',
value: 300,
value: 200,
},
],
},
],
},
options: {
cellHeight: 'sm',
cellHeight: 'md',
footer: {
countRows: false,
fields: '',

View file

@ -3,14 +3,14 @@ import { SceneDataTransformer, SceneFlexItem, SceneQueryRunner, VizPanel } from
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getMTTRByTeamScene({ datasource }: InsightsConfig) {
export function getMTTRByTeamScene({ datasource, stack }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
editorMode: 'code',
exemplar: false,
expr: 'sort_desc(avg_over_time((sum by(team) ($alert_groups_response_time_seconds_sum{slug=~"$stack", team=~"$team", integration=~"$integration"}) / sum by(team)($alert_groups_response_time_seconds_count{slug=~"$stack", team=~"$team", integration=~"$integration"}))[$__range:]))',
expr: `sort_desc(avg_over_time((sum by(team) ($alert_groups_response_time_seconds_sum{slug=~"${stack}", team=~"$team", integration=~"$integration"}) / sum by(team)($alert_groups_response_time_seconds_count{slug=~"${stack}", team=~"$team", integration=~"$integration"}))[$__range:]))`,
format: 'table',
instant: true,
legendFormat: '__auto',
@ -47,7 +47,7 @@ export default function getMTTRByTeamScene({ datasource }: InsightsConfig) {
return new SceneFlexItem({
$data: transformedData,
body: new VizPanel({
title: 'Mean time to respond by Team (MTTR)',
title: 'Mean time to respond (MTTR) by Team',
pluginId: 'table',
fieldConfig: {
defaults: {
@ -102,7 +102,7 @@ export default function getMTTRByTeamScene({ datasource }: InsightsConfig) {
],
},
options: {
cellHeight: 'sm',
cellHeight: 'md',
footer: {
countRows: false,
fields: '',

View file

@ -3,14 +3,14 @@ import { SceneFlexItem, SceneQueryRunner, VizPanel } from '@grafana/scenes';
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getMTTRChangedForPeriodTimeseriesScene({ datasource }: InsightsConfig) {
export function getMTTRChangedTimeseriesScene({ datasource, stack }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
editorMode: 'code',
exemplar: false,
expr: 'avg(sum($alert_groups_response_time_seconds_sum{slug=~"$stack", team=~"$team", integration=~"$integration"}) / sum($alert_groups_response_time_seconds_count{slug=~"$stack", team=~"$team", integration=~"$integration"}))',
expr: `avg(sum($alert_groups_response_time_seconds_sum{slug=~"${stack}", team=~"$team", integration=~"$integration"}) / sum($alert_groups_response_time_seconds_count{slug=~"${stack}", team=~"$team", integration=~"$integration"}))`,
instant: false,
legendFormat: '__auto',
range: true,
@ -22,7 +22,7 @@ export default function getMTTRChangedForPeriodTimeseriesScene({ datasource }: I
return new SceneFlexItem({
$data: query,
body: new VizPanel({
title: 'MTTR changed for period',
title: 'Mean time to respond (MTTR) changed',
pluginId: 'timeseries',
fieldConfig: {
defaults: {

View file

@ -3,7 +3,7 @@ import { SceneFlexItem, SceneQueryRunner, VizPanel } from '@grafana/scenes';
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getNewAlertGroupsForSelectedPeriodScene({ datasource }: InsightsConfig) {
export function getNewAlertGroupsScene({ datasource, stack }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
@ -12,7 +12,7 @@ export default function getNewAlertGroupsForSelectedPeriodScene({ datasource }:
editorMode: 'code',
excludeNullMetadata: false,
exemplar: false,
expr: 'increase(max_over_time(sum(avg without(pod, instance) ($alert_groups_total{slug=~"$stack", team=~"$team", integration=~"$integration"}))[1d:])[$__range:])',
expr: `delta(max_over_time(sum(avg without(pod, instance) ($alert_groups_total{slug=~"${stack}", team=~"$team", integration=~"$integration"}))[30m:])[$__range:]) >= 0`,
format: 'time_series',
fullMetaSearch: false,
includeNullMetadata: true,
@ -28,7 +28,7 @@ export default function getNewAlertGroupsForSelectedPeriodScene({ datasource }:
return new SceneFlexItem({
$data: query,
body: new VizPanel({
title: 'New alert groups for selected period',
title: 'New alert groups',
pluginId: 'stat',
fieldConfig: {
defaults: {

View file

@ -1,113 +0,0 @@
import { ThresholdsMode } from '@grafana/data';
import { SceneDataTransformer, SceneFlexItem, SceneQueryRunner, VizPanel } from '@grafana/scenes';
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getNewAlertGroupsNotificationsInTotalScene({ datasource }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
editorMode: 'code',
exemplar: false,
expr: 'sort_desc(max_over_time(sum by(username) (avg without(pod, instance)($user_was_notified_of_alert_groups_total{slug=~"$stack"}))[1d:]))',
format: 'table',
instant: true,
legendFormat: '__auto',
range: false,
refId: 'A',
},
],
});
const transformedData = new SceneDataTransformer({
$data: query,
transformations: [
{
id: 'seriesToRows',
options: {},
},
{
id: 'organize',
options: {
excludeByName: {
Time: true,
username: false,
},
indexByName: {},
renameByName: {
Metric: 'Integration',
Value: 'Alert groups',
team: 'Team',
username: 'Username',
},
},
},
],
});
return new SceneFlexItem({
$data: transformedData,
body: new VizPanel({
title: 'New alert groups notifications in total',
pluginId: 'table',
fieldConfig: {
defaults: {
color: {
mode: 'thresholds',
},
custom: {
align: 'auto',
cellOptions: {
mode: 'gradient',
type: 'gauge',
valueDisplayMode: 'color',
},
filterable: false,
inspect: false,
},
mappings: [],
thresholds: {
mode: ThresholdsMode.Absolute,
steps: [
{
color: 'green',
value: null,
},
],
},
},
overrides: [
{
matcher: {
id: 'byName',
options: 'Username',
},
properties: [
{
id: 'custom.cellOptions',
value: {
type: 'auto',
},
},
{
id: 'custom.width',
value: 300,
},
],
},
],
},
options: {
cellHeight: 'sm',
footer: {
countRows: false,
fields: '',
reducer: ['sum'],
show: false,
},
showHeader: true,
},
}),
});
}

View file

@ -3,14 +3,14 @@ import { SceneDataTransformer, SceneFlexItem, SceneQueryRunner, VizPanel } from
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getNewAlertGroupsNotificationsForPeriodTableScene({ datasource }: InsightsConfig) {
export function getNewAlertGroupsNotificationsTableScene({ datasource, stack }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
editorMode: 'code',
exemplar: false,
expr: 'sort_desc(increase(max_over_time(sum by (username) (avg without(pod, instance) ($user_was_notified_of_alert_groups_total{slug=~"$stack"}))[1h:])[$__range:]))',
expr: `sort_desc(delta(max_over_time(sum by (username) (avg without(pod, instance) ($user_was_notified_of_alert_groups_total{slug=~"${stack}"}))[1h:])[$__range:])>=0)`,
format: 'table',
instant: true,
legendFormat: '__auto',
@ -49,7 +49,7 @@ export default function getNewAlertGroupsNotificationsForPeriodTableScene({ data
return new SceneFlexItem({
$data: transformedData,
body: new VizPanel({
title: 'New alert groups notifications for period',
title: 'New alert groups notifications',
pluginId: 'table',
fieldConfig: {
defaults: {
@ -99,7 +99,7 @@ export default function getNewAlertGroupsNotificationsForPeriodTableScene({ data
],
},
options: {
cellHeight: 'sm',
cellHeight: 'md',
footer: {
countRows: false,
fields: '',

View file

@ -3,7 +3,7 @@ import { SceneFlexItem, SceneQueryRunner, VizPanel } from '@grafana/scenes';
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getNewAlertGroupsNotificationsDuringTimePeriodScene({ datasource }: InsightsConfig) {
export function getNewAlertGroupsNotificationsTimeseriesScene({ datasource, stack }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
@ -12,7 +12,7 @@ export default function getNewAlertGroupsNotificationsDuringTimePeriodScene({ da
editorMode: 'code',
excludeNullMetadata: false,
exemplar: false,
expr: 'increase(max_over_time(sum by (username) (avg without(pod, instance) ($user_was_notified_of_alert_groups_total{slug=~"$stack"}))[30m:])[1h:])',
expr: `delta(max_over_time(sum by (username) (avg without(pod, instance) ($user_was_notified_of_alert_groups_total{slug=~"${stack}"}))[30m:])[1h:]) >= 0`,
fullMetaSearch: false,
instant: false,
legendFormat: '__auto',
@ -26,7 +26,7 @@ export default function getNewAlertGroupsNotificationsDuringTimePeriodScene({ da
return new SceneFlexItem({
$data: query,
body: new VizPanel({
title: 'New alert groups notifications during time period',
title: 'New alert groups notifications',
pluginId: 'timeseries',
fieldConfig: {
defaults: {

View file

@ -3,7 +3,7 @@ import { SceneFlexItem, SceneQueryRunner, VizPanel } from '@grafana/scenes';
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getNewAlertGroupsDuringTimePeriodScene({ datasource }: InsightsConfig) {
export function getNewAlertGroupsTimeseriesScene({ datasource, stack }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
@ -12,7 +12,7 @@ export default function getNewAlertGroupsDuringTimePeriodScene({ datasource }: I
editorMode: 'code',
excludeNullMetadata: false,
exemplar: false,
expr: 'increase(max_over_time(sum by (integration) (avg without(pod, instance) ($alert_groups_total{slug=~"$stack", team=~"$team", integration=~"$integration"}))[30m:])[1h:])',
expr: `delta(max_over_time(sum by (integration) (avg without(pod, instance) ($alert_groups_total{slug=~"${stack}", team=~"$team", integration=~"$integration"}))[30m:])[1h:]) >= 0`,
fullMetaSearch: false,
instant: false,
legendFormat: '__auto',
@ -26,7 +26,7 @@ export default function getNewAlertGroupsDuringTimePeriodScene({ datasource }: I
return new SceneFlexItem({
$data: query,
body: new VizPanel({
title: 'New alert groups during time period',
title: 'New alert groups',
pluginId: 'timeseries',
fieldConfig: {
defaults: {

View file

@ -1,79 +0,0 @@
import { ThresholdsMode } from '@grafana/data';
import { SceneFlexItem, SceneQueryRunner, VizPanel } from '@grafana/scenes';
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getTotalAlertGroupsScene({ datasource }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
disableTextWrap: false,
editorMode: 'code',
excludeNullMetadata: false,
exemplar: false,
expr: 'max_over_time(sum(avg without(pod, instance) ($alert_groups_total{slug=~"$stack", team=~"$team", integration=~"$integration"}))[1d:])',
format: 'time_series',
fullMetaSearch: false,
instant: false,
legendFormat: '__auto',
range: true,
refId: 'A',
useBackend: false,
},
],
});
return new SceneFlexItem({
$data: query,
body: new VizPanel({
pluginId: 'stat',
fieldConfig: {
defaults: {
color: {
mode: 'thresholds',
},
mappings: [],
thresholds: {
mode: ThresholdsMode.Absolute,
steps: [
{
color: 'text',
value: null,
},
],
},
unit: 'none',
},
overrides: [
{
matcher: {
id: 'byName',
options: 'Value',
},
properties: [
{
id: 'displayName',
value: 'Total alert groups',
},
],
},
],
},
options: {
colorMode: 'value',
graphMode: 'none',
justifyMode: 'center',
orientation: 'auto',
reduceOptions: {
calcs: ['lastNotNull'],
fields: '',
values: false,
},
textMode: 'auto',
},
pluginVersion: '9.5.2',
title: 'Total alert groups',
}),
});
}

View file

@ -1,133 +0,0 @@
import { ThresholdsMode } from '@grafana/data';
import { SceneDataTransformer, SceneFlexItem, SceneQueryRunner, VizPanel } from '@grafana/scenes';
import { InsightsConfig } from 'pages/insights/Insights.types';
export default function getTotalAlertGroupsByStateScene({ datasource }: InsightsConfig) {
const query = new SceneQueryRunner({
datasource,
queries: [
{
disableTextWrap: false,
editorMode: 'code',
excludeNullMetadata: false,
expr: 'sum by (state) (avg without(pod, instance) ($alert_groups_total{slug=~"$stack", team=~"$team", integration=~"$integration"}))',
fullMetaSearch: false,
legendFormat: '__auto',
range: true,
refId: 'A',
useBackend: false,
},
],
});
const transformedData = new SceneDataTransformer({
$data: query,
transformations: [
{
id: 'joinByLabels',
options: {
value: 'state',
},
},
{
id: 'organize',
options: {
excludeByName: {},
indexByName: {
acknowledged: 1,
firing: 0,
resolved: 2,
silenced: 3,
},
renameByName: {},
},
},
],
});
return new SceneFlexItem({
$data: transformedData,
body: new VizPanel({
title: 'Total alert groups by state',
pluginId: 'bargauge',
fieldConfig: {
defaults: {
color: {
mode: 'thresholds',
},
mappings: [],
thresholds: {
mode: ThresholdsMode.Absolute,
steps: [
{
color: 'green',
value: null,
},
],
},
},
overrides: [
{
matcher: {
id: 'byName',
options: 'firing',
},
properties: [
{
id: 'color',
value: {
fixedColor: 'red',
mode: 'fixed',
},
},
],
},
{
matcher: {
id: 'byName',
options: 'acknowledged',
},
properties: [
{
id: 'color',
value: {
fixedColor: 'dark-yellow',
mode: 'fixed',
},
},
],
},
{
matcher: {
id: 'byName',
options: 'silenced',
},
properties: [
{
id: 'color',
value: {
mode: 'fixed',
},
},
],
},
],
},
options: {
displayMode: 'gradient',
minVizHeight: 10,
minVizWidth: 0,
orientation: 'vertical',
reduceOptions: {
calcs: ['lastNotNull'],
fields: '',
values: false,
},
showUnfilled: true,
valueMode: 'color',
},
pluginVersion: '9.5.2',
}),
});
}

View file

@ -26,18 +26,6 @@ const getVariables = ({ isOpenSource, datasource, stack }: InsightsConfig) => ({
}),
}
: {}),
stack: new QueryVariable({
...DEFAULT_VARIABLE_CONFIG,
name: 'stack',
label: 'Stack',
value: stack,
datasource,
definition: 'label_values(${alert_groups_total},slug)',
query: {
query: 'label_values(${alert_groups_total},slug)',
refId: 'PrometheusVariableQueryEditor-VariableQuery',
},
}),
team: new QueryVariable({
...DEFAULT_VARIABLE_CONFIG,
name: 'team',
@ -45,9 +33,9 @@ const getVariables = ({ isOpenSource, datasource, stack }: InsightsConfig) => ({
text: ['All'],
value: ['$__all'],
datasource,
definition: 'label_values(${alert_groups_total}{slug=~"$stack"},team)',
definition: `label_values(\${alert_groups_total}{slug=~"${stack}"},team)`,
query: {
query: 'label_values(${alert_groups_total}{slug=~"$stack"},team)',
query: `label_values(\${alert_groups_total}{slug=~"${stack}"},team)`,
refId: 'PrometheusVariableQueryEditor-VariableQuery',
},
refresh: 2,
@ -59,9 +47,9 @@ const getVariables = ({ isOpenSource, datasource, stack }: InsightsConfig) => ({
text: ['All'],
value: ['$__all'],
datasource,
definition: 'label_values(${alert_groups_total}{team=~"$team",slug=~"$stack"},integration)',
definition: `label_values(\${alert_groups_total}{team=~"$team",slug=~"${stack}"},integration)`,
query: {
query: 'label_values(${alert_groups_total}{team=~"$team",slug=~"$stack"},integration)',
query: `label_values(\${alert_groups_total}{team=~"$team",slug=~"${stack}"},integration)`,
refId: 'PrometheusVariableQueryEditor-VariableQuery',
},
refresh: 2,
@ -77,8 +65,8 @@ const getVariables = ({ isOpenSource, datasource, stack }: InsightsConfig) => ({
query: 'metrics(alert_groups_total)',
refId: 'PrometheusVariableQueryEditor-VariableQuery',
},
text: ['oncall_alert_groups_total', 'grafanacloud_oncall_stack_alert_groups_total'],
value: ['oncall_alert_groups_total', 'grafanacloud_oncall_stack_alert_groups_total'],
text: ['oncall_alert_groups_total', 'grafanacloud_oncall_instance_alert_groups_total'],
value: ['oncall_alert_groups_total', 'grafanacloud_oncall_instance_alert_groups_total'],
definition: 'metrics(alert_groups_total)',
hide: 2,
includeAll: false,

View file

@ -2,6 +2,9 @@ import React, { useEffect, useRef, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { ActionKey } from 'models/loader/action-keys';
import { useStore } from 'state/useStore';
export function useForceUpdate() {
const [, setValue] = useState(0);
return () => setValue((value) => value + 1);
@ -69,3 +72,10 @@ export function useDebouncedCallback<A extends any[]>(callback: (...args: A) =>
}, wait);
};
}
export const useIsLoading = (actionKey: ActionKey) => {
const {
loaderStore: { isLoading },
} = useStore();
return isLoading(actionKey);
};