grafana/public/app/plugins/panel/alertlist/unified-alerting/UngroupedView.tsx
Sonia Aguilar 64ee42d01e
Alerting: Add limits and move state and label matching filters to the BE (#66267)
* WIP

* Add instance totals to combined rule. Use totals to display instances stats in the UI

* WIP

* add global summaries, fix TS errors

* fix useCombined test

* fix test

* use activeAt from rule when available

* Fix NaN in global stats

* Add no data total to global summary

* Add totals recalculation for filtered rules

* Fix instances totals, remove instances filtering from alert list view

* Update tests

* Fetch alerts considering filtering label matchers

* WIP - Fetch alerts appending state filter to endpoint

* Fix multiple values for state in request being applyied

* fix test

* Calculate hidden by for grafana managed alerts

* Use INSTANCES_DISPLAY_LIMIT constant for limiting alert instances instead of 1

* Rename matchers parameter according to API changes

* Fix calculating total number of grafana instances

* Rename matcher prop after previous change

* Display button to remove max instances limit

* Change matcher query param to be an array of strings

* Add test for paramsWithMatcherAndState method

* Refactor matcher to be an string array to be consistent with state

* Use matcher query string as matcher object type (encoded JSON)

* Avoind encoding matcher parameters twice

* fix tests

* Enable toggle for the limit/show all button and restore limit and filters when we come back from custom view

* Move getMatcherListFromString method to utils/alertmanager.ts

* Fix limit toggle button being shown when it's not necessary

* Use filteredTotals from be response to calculate hidden by count

* Fix variables not being replaced correctly

* Fix total shown to be all the instances filtered without limits

* Adress some PR review comments

* Move paramsWithMatcherAndState inside prometheusUrlBuilder method

---------

Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com>
Co-authored-by: Virginia Cepeda <virginia.cepeda@grafana.com>
2023-04-25 11:19:20 +02:00

177 lines
6.6 KiB
TypeScript

import { css } from '@emotion/css';
import React from 'react';
import { useLocation } from 'react-use';
import { GrafanaTheme2, intervalToAbbreviatedDurationString } from '@grafana/data';
import { Stack } from '@grafana/experimental';
import { Icon, useStyles2 } from '@grafana/ui';
import alertDef from 'app/features/alerting/state/alertDef';
import { Spacer } from 'app/features/alerting/unified/components/Spacer';
import { fromCombinedRule, stringifyIdentifier } from 'app/features/alerting/unified/utils/rule-id';
import {
alertStateToReadable,
alertStateToState,
getFirstActiveAt,
isAlertingRule,
} from 'app/features/alerting/unified/utils/rules';
import { createUrl } from 'app/features/alerting/unified/utils/url';
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import { GRAFANA_RULES_SOURCE_NAME } from '../../../../features/alerting/unified/utils/datasource';
import { AlertingRule, AlertInstanceTotalState, CombinedRuleWithLocation } from '../../../../types/unified-alerting';
import { AlertInstances } from '../AlertInstances';
import { getStyles } from '../UnifiedAlertList';
import { UnifiedAlertListOptions } from '../types';
type Props = {
rules: CombinedRuleWithLocation[];
options: UnifiedAlertListOptions;
handleInstancesLimit?: (limit: boolean) => void;
limitInstances: boolean;
};
function getGrafanaInstancesTotal(totals: Partial<Record<AlertInstanceTotalState, number>>) {
return Object.values(totals)
.filter((total) => total !== undefined)
.reduce((total, currentTotal) => total + currentTotal, 0);
}
const UngroupedModeView = ({ rules, options, handleInstancesLimit, limitInstances }: Props) => {
const styles = useStyles2(getStyles);
const stateStyle = useStyles2(getStateTagStyles);
const { href: returnTo } = useLocation();
const rulesToDisplay = rules.length <= options.maxItems ? rules : rules.slice(0, options.maxItems);
return (
<>
<ol className={styles.alertRuleList}>
{rulesToDisplay.map((ruleWithLocation, index) => {
const { namespaceName, groupName, dataSourceName } = ruleWithLocation;
const alertingRule: AlertingRule | undefined = isAlertingRule(ruleWithLocation.promRule)
? ruleWithLocation.promRule
: undefined;
const firstActiveAt = getFirstActiveAt(alertingRule);
const indentifier = fromCombinedRule(ruleWithLocation.dataSourceName, ruleWithLocation);
const strIndentifier = stringifyIdentifier(indentifier);
const grafanaInstancesTotal =
ruleWithLocation.dataSourceName === GRAFANA_RULES_SOURCE_NAME
? getGrafanaInstancesTotal(ruleWithLocation.instanceTotals)
: undefined;
const grafanaFilteredInstancesTotal =
ruleWithLocation.dataSourceName === GRAFANA_RULES_SOURCE_NAME
? getGrafanaInstancesTotal(ruleWithLocation.filteredInstanceTotals)
: undefined;
const href = createUrl(
`/alerting/${encodeURIComponent(dataSourceName)}/${encodeURIComponent(strIndentifier)}/view`,
{ returnTo: returnTo ?? '' }
);
if (alertingRule) {
return (
<li
className={styles.alertRuleItem}
key={`alert-${namespaceName}-${groupName}-${ruleWithLocation.name}-${index}`}
>
<div className={stateStyle.icon}>
<Icon
name={alertDef.getStateDisplayModel(alertingRule.state).iconClass}
className={stateStyle[alertStateToState(alertingRule.state)]}
size={'lg'}
/>
</div>
<div className={styles.alertNameWrapper}>
<div className={styles.instanceDetails}>
<Stack direction="row" gap={1} wrap={false}>
<div className={styles.alertName} title={ruleWithLocation.name}>
{ruleWithLocation.name}
</div>
<Spacer />
{href && (
<a href={href} target="__blank" className={styles.link} rel="noopener">
<Stack alignItems="center" gap={1}>
View alert rule
<Icon name={'external-link-alt'} size="sm" />
</Stack>
</a>
)}
</Stack>
<div className={styles.alertDuration}>
<span className={stateStyle[alertStateToState(alertingRule.state)]}>
{alertStateToReadable(alertingRule.state)}
</span>{' '}
{firstActiveAt && alertingRule.state !== PromAlertingRuleState.Inactive && (
<>
for{' '}
<span>
{intervalToAbbreviatedDurationString({
start: firstActiveAt,
end: Date.now(),
})}
</span>
</>
)}
</div>
</div>
<AlertInstances
alerts={alertingRule.alerts ?? []}
options={options}
grafanaTotalInstances={grafanaInstancesTotal}
grafanaFilteredInstancesTotal={grafanaFilteredInstancesTotal}
handleInstancesLimit={handleInstancesLimit}
limitInstances={limitInstances}
/>
</div>
</li>
);
} else {
return null;
}
})}
</ol>
</>
);
};
const getStateTagStyles = (theme: GrafanaTheme2) => ({
common: css`
width: 70px;
text-align: center;
align-self: stretch;
display: inline-block;
color: white;
border-radius: ${theme.shape.borderRadius()};
font-size: ${theme.typography.bodySmall.fontSize};
text-transform: capitalize;
line-height: 1.2;
flex-shrink: 0;
display: flex;
flex-direction: column;
justify-content: center;
`,
icon: css`
margin-top: ${theme.spacing(2.5)};
align-self: flex-start;
`,
good: css`
color: ${theme.colors.success.main};
`,
bad: css`
color: ${theme.colors.error.main};
`,
warning: css`
color: ${theme.colors.warning.main};
`,
neutral: css`
color: ${theme.colors.secondary.main};
`,
info: css`
color: ${theme.colors.primary.main};
`,
});
export default UngroupedModeView;