Alerting: List V2 - datasource icons for rules (#109033)
Add datasource icons to the rule list item components
This commit is contained in:
@@ -53,6 +53,7 @@ export function GrafanaRuleListItem({
|
||||
isPaused: rule?.isPaused,
|
||||
application: 'grafana' as const,
|
||||
actions: <RuleActionsButtons promRule={rule} groupIdentifier={groupIdentifier} compact />,
|
||||
querySourceUIDs: rule?.queriedDatasourceUIDs,
|
||||
};
|
||||
|
||||
if (prometheusRuleType.grafana.alertingRule(rule)) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import pluralize from 'pluralize';
|
||||
import { ReactNode, useEffect, useId } from 'react';
|
||||
import { ReactNode, forwardRef, memo, useEffect, useId } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Alert, Stack, Text, TextLink, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { Rule, RuleGroupIdentifierV2, RuleHealth, RulesSourceIdentifier } from 'app/types/unified-alerting';
|
||||
@@ -13,7 +13,7 @@ import { AlertLabels } from '../../components/AlertLabels';
|
||||
import { MetaText } from '../../components/MetaText';
|
||||
import { ProvisioningBadge } from '../../components/Provisioning';
|
||||
import { PluginOriginBadge } from '../../plugins/PluginOriginBadge';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
||||
import { GRAFANA_RULES_SOURCE_NAME, getDataSourceByUid } from '../../utils/datasource';
|
||||
import { getGroupOriginName } from '../../utils/groupIdentifier';
|
||||
import { labelsSize } from '../../utils/labels';
|
||||
import { createContactPointSearchLink } from '../../utils/misc';
|
||||
@@ -49,6 +49,7 @@ export interface AlertRuleListItemProps {
|
||||
operation?: RuleOperation;
|
||||
// the grouped view doesn't need to show the location again – it's redundant
|
||||
showLocation?: boolean;
|
||||
querySourceUIDs?: string[];
|
||||
}
|
||||
|
||||
export const AlertRuleListItem = (props: AlertRuleListItemProps) => {
|
||||
@@ -75,6 +76,7 @@ export const AlertRuleListItem = (props: AlertRuleListItemProps) => {
|
||||
actions = null,
|
||||
operation,
|
||||
showLocation = true,
|
||||
querySourceUIDs = [],
|
||||
} = props;
|
||||
|
||||
const listItemAriaId = useId();
|
||||
@@ -94,6 +96,10 @@ export const AlertRuleListItem = (props: AlertRuleListItemProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (querySourceUIDs.length > 0) {
|
||||
metadata.push(<QuerySourceIcons queriedDatasourceUIDs={querySourceUIDs} />);
|
||||
}
|
||||
|
||||
if (!isPaused) {
|
||||
if (lastEvaluation && evaluationInterval) {
|
||||
metadata.push(
|
||||
@@ -179,6 +185,7 @@ export function RecordingRuleListItem({
|
||||
origin,
|
||||
actions,
|
||||
showLocation = true,
|
||||
querySourceUIDs = [],
|
||||
}: RecordingRuleListItemProps) {
|
||||
const metadata: ReactNode[] = [];
|
||||
if (namespace && group && showLocation) {
|
||||
@@ -195,6 +202,10 @@ export function RecordingRuleListItem({
|
||||
);
|
||||
}
|
||||
|
||||
if (querySourceUIDs.length > 0) {
|
||||
metadata.push(<QuerySourceIcons queriedDatasourceUIDs={querySourceUIDs} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
title={
|
||||
@@ -293,6 +304,29 @@ function Summary({ content, error }: SummaryProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
interface QuerySourceIconsProps {
|
||||
queriedDatasourceUIDs: string[];
|
||||
}
|
||||
|
||||
const QuerySourceIcons = memo(function QuerySourceIcons({ queriedDatasourceUIDs }: QuerySourceIconsProps) {
|
||||
// Make icons unique - deduplicate datasource UIDs
|
||||
const dataSources = Array.from(new Set(queriedDatasourceUIDs))
|
||||
.map(getDataSourceByUid)
|
||||
.filter((ds): ds is DataSourceInstanceSettings => ds !== undefined);
|
||||
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" gap={0.5}>
|
||||
{dataSources.map((dataSource) => {
|
||||
return (
|
||||
<Tooltip key={dataSource.uid} content={dataSource.name}>
|
||||
<DataSourceLogo dataSource={dataSource} />
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
function RuleLabels({ labels }: { labels: Labels }) {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
@@ -417,3 +451,33 @@ export type RuleListItemCommonProps = Pick<
|
||||
AlertRuleListItemProps,
|
||||
Extract<keyof AlertRuleListItemProps, keyof RecordingRuleListItemProps>
|
||||
>;
|
||||
|
||||
interface DataSourceLogoProps {
|
||||
dataSource: DataSourceInstanceSettings;
|
||||
}
|
||||
|
||||
const DataSourceLogo = forwardRef<HTMLImageElement, DataSourceLogoProps>(({ dataSource }, ref) => {
|
||||
const styles = useStyles2(dataSourceLogoStyles);
|
||||
|
||||
return (
|
||||
<img
|
||||
ref={ref}
|
||||
className={cx(styles.logo, {
|
||||
[styles.filter]: dataSource.meta.builtIn,
|
||||
})}
|
||||
alt={`${dataSource.meta.name} logo`}
|
||||
src={dataSource.meta.info.logos.small}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const dataSourceLogoStyles = (theme: GrafanaTheme2) => ({
|
||||
logo: css({
|
||||
height: '14px',
|
||||
width: '14px',
|
||||
borderRadius: theme.shape.radius.default,
|
||||
}),
|
||||
filter: css({
|
||||
filter: `invert(${theme.isLight ? 1 : 0})`,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ export const ListItem = (props: ListItemProps) => {
|
||||
</Stack>
|
||||
|
||||
{/* metadata */}
|
||||
<Stack direction="row" gap={0.5} alignItems="center">
|
||||
<Stack direction="row" gap={1} alignItems="center">
|
||||
{meta?.map((item, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{index > 0 && <Separator />}
|
||||
@@ -72,7 +72,7 @@ export const SkeletonListItem = () => {
|
||||
|
||||
const Separator = () => (
|
||||
<Text color="secondary" variant="bodySmall">
|
||||
{'·'}
|
||||
{'|'}
|
||||
</Text>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user