Alerting: Allow copying error messages from query badges (#80078)

* Add interactive mode to the Badge component

* Add expression errors as Alert components instead of badges

* Revert "Add interactive mode to the Badge component"

This reverts commit 9558743fc7.
pull/80270/head
Konrad Lalik 1 year ago committed by GitHub
parent d738b96742
commit 282a3f9a66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      public/app/features/alerting/unified/components/expressions/Expression.tsx
  2. 32
      public/app/features/alerting/unified/components/expressions/ExpressionStatusIndicator.test.tsx
  3. 26
      public/app/features/alerting/unified/components/expressions/ExpressionStatusIndicator.tsx
  4. 6
      public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx

@ -4,7 +4,7 @@ import React, { FC, useCallback, useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { DataFrame, dateTimeFormat, GrafanaTheme2, isTimeSeriesFrames, LoadingState, PanelData } from '@grafana/data';
import { AutoSizeInput, Button, clearButtonStyles, IconButton, Stack, useStyles2 } from '@grafana/ui';
import { Alert, AutoSizeInput, Button, clearButtonStyles, IconButton, Stack, useStyles2 } from '@grafana/ui';
import { ClassicConditions } from 'app/features/expressions/components/ClassicConditions';
import { Math } from 'app/features/expressions/components/Math';
import { Reduce } from 'app/features/expressions/components/Reduce';
@ -136,12 +136,20 @@ export const Expression: FC<ExpressionProps> = ({
onUpdateRefId={(newRefId) => onUpdateRefId(query.refId, newRefId)}
onUpdateExpressionType={(type) => onUpdateExpressionType(query.refId, type)}
onSetCondition={onSetCondition}
warning={warning}
error={error}
query={query}
alertCondition={alertCondition}
/>
<div className={styles.expression.body}>
{error && (
<Alert title="Expression failed" severity="error">
{error.message}
</Alert>
)}
{warning && (
<Alert title="Expression warning" severity="warning">
{warning.message}
</Alert>
)}
<div className={styles.expression.description}>{selectedExpressionDescription}</div>
{renderExpressionType(query)}
</div>
@ -275,8 +283,6 @@ interface HeaderProps {
onUpdateRefId: (refId: string) => void;
onRemoveExpression: () => void;
onUpdateExpressionType: (type: ExpressionQueryType) => void;
warning?: Error;
error?: Error;
onSetCondition: (refId: string) => void;
query: ExpressionQuery;
alertCondition: boolean;
@ -287,11 +293,9 @@ const Header: FC<HeaderProps> = ({
queryType,
onUpdateRefId,
onRemoveExpression,
warning,
onSetCondition,
alertCondition,
query,
error,
}) => {
const styles = useStyles2(getStyles);
const clearButton = useStyles2(clearButtonStyles);
@ -335,12 +339,7 @@ const Header: FC<HeaderProps> = ({
<div>{getExpressionLabel(queryType)}</div>
</Stack>
<Spacer />
<ExpressionStatusIndicator
error={error}
warning={warning}
onSetCondition={() => onSetCondition(query.refId)}
isCondition={alertCondition}
/>
<ExpressionStatusIndicator onSetCondition={() => onSetCondition(query.refId)} isCondition={alertCondition} />
<IconButton
name="trash-alt"
variant="secondary"

@ -4,47 +4,15 @@ import React from 'react';
import { ExpressionStatusIndicator } from './ExpressionStatusIndicator';
describe('ExpressionStatusIndicator', () => {
it('should render two elements when error and not condition', () => {
render(<ExpressionStatusIndicator isCondition={false} warning={new Error('this is a warning')} />);
expect(screen.getByText('Warning')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Set as alert condition' })).toBeInTheDocument();
});
it('should render one element when warning and condition', () => {
render(<ExpressionStatusIndicator isCondition warning={new Error('this is a warning')} />);
expect(screen.getByText('Alert condition')).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Set as alert condition' })).not.toBeInTheDocument();
});
it('should render two elements when error and not condition', () => {
render(<ExpressionStatusIndicator isCondition={false} error={new Error('this is a error')} />);
expect(screen.getByText('Error')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Set as alert condition' })).toBeInTheDocument();
});
it('should render one element when error and condition', () => {
render(<ExpressionStatusIndicator isCondition error={new Error('this is a error')} />);
expect(screen.getByText('Alert condition')).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Set as alert condition' })).not.toBeInTheDocument();
});
it('should render one element if condition', () => {
render(<ExpressionStatusIndicator isCondition />);
expect(screen.queryByText('Error')).not.toBeInTheDocument();
expect(screen.queryByText('Warning')).not.toBeInTheDocument();
expect(screen.getByText('Alert condition')).toBeInTheDocument();
});
it('should render one element if not condition', () => {
render(<ExpressionStatusIndicator isCondition={false} />);
expect(screen.queryByText('Error')).not.toBeInTheDocument();
expect(screen.queryByText('Warning')).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Alert condition' })).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Set as alert condition' })).toBeInTheDocument();
});

@ -5,35 +5,17 @@ import { GrafanaTheme2 } from '@grafana/data';
import { Badge, clearButtonStyles, useStyles2 } from '@grafana/ui';
interface AlertConditionProps {
warning?: Error;
error?: Error;
isCondition?: boolean;
onSetCondition?: () => void;
}
export const ExpressionStatusIndicator = ({ error, warning, isCondition, onSetCondition }: AlertConditionProps) => {
export const ExpressionStatusIndicator = ({ isCondition, onSetCondition }: AlertConditionProps) => {
const styles = useStyles2(getStyles);
const elements: JSX.Element[] = [];
if (error && isCondition) {
return <Badge color="red" icon="exclamation-circle" text="Alert condition" tooltip={error.message} />;
} else if (error) {
elements.push(<Badge key="error" color="red" icon="exclamation-circle" text="Error" tooltip={error.message} />);
}
if (warning && isCondition) {
return <Badge color="orange" icon="exclamation-triangle" text="Alert condition" tooltip={warning.message} />;
} else if (warning) {
elements.push(
<Badge key="warning" color="orange" icon="exclamation-triangle" text="Warning" tooltip={warning.message} />
);
}
if (isCondition) {
elements.unshift(<Badge key="condition" color="green" icon="check" text="Alert condition" />);
return <Badge key="condition" color="green" icon="check" text="Alert condition" />;
} else {
elements.unshift(
return (
<button
key="make-condition"
type="button"
@ -44,8 +26,6 @@ export const ExpressionStatusIndicator = ({ error, warning, isCondition, onSetCo
</button>
);
}
return <>{elements}</>;
};
const getStyles = (theme: GrafanaTheme2) => {

@ -130,11 +130,7 @@ export const QueryWrapper = ({
onChangeQueryOptions={onChangeQueryOptions}
index={index}
/>
<ExpressionStatusIndicator
error={error}
onSetCondition={() => onSetCondition(query.refId)}
isCondition={isAlertCondition}
/>
<ExpressionStatusIndicator onSetCondition={() => onSetCondition(query.refId)} isCondition={isAlertCondition} />
</Stack>
);
}

Loading…
Cancel
Save