Dashboards: Show repeated row with Dashboard ds warning (#73787)

This commit is contained in:
Juan Cabanas 2023-08-28 10:00:11 -03:00 committed by GitHub
parent 5bd58cac57
commit 36d7cc9384
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 115 additions and 8 deletions

View File

@ -180,6 +180,13 @@ export const Pages = {
Annotations: {
marker: 'data-testid annotation-marker',
},
Rows: {
Repeated: {
ConfigSection: {
warningMessage: 'data-testid Repeated rows warning message',
},
},
},
},
Dashboards: {
url: '/dashboards',

View File

@ -2,6 +2,8 @@ import { screen, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard/types';
import { PanelModel } from '../../state/PanelModel';
import { DashboardRow } from './DashboardRow';
@ -17,6 +19,7 @@ describe('DashboardRow', () => {
canEdit: true,
},
events: { subscribe: jest.fn() },
getRowPanels: () => [],
};
panel = new PanelModel({ collapsed: false });
@ -67,4 +70,28 @@ describe('DashboardRow', () => {
expect(screen.queryByRole('button', { name: 'Delete row' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Row options' })).not.toBeInTheDocument();
});
it('Should return warning message when row panel has a panel with dashboard ds set', async () => {
const panel = new PanelModel({
datasource: {
type: 'datasource',
uid: SHARED_DASHBOARD_QUERY,
},
});
const rowPanel = new PanelModel({ collapsed: true, panels: [panel] });
const dashboardRow = new DashboardRow({ panel: rowPanel, dashboard: dashboardMock });
expect(dashboardRow.getWarning()).toBeDefined();
});
it('Should not return warning message when row panel does not have a panel with dashboard ds set', async () => {
const panel = new PanelModel({
datasource: {
type: 'datasource',
uid: 'ds-uid',
},
});
const rowPanel = new PanelModel({ collapsed: true, panels: [panel] });
const dashboardRow = new DashboardRow({ panel: rowPanel, dashboard: dashboardMock });
expect(dashboardRow.getWarning()).not.toBeDefined();
});
});

View File

@ -1,4 +1,5 @@
import classNames from 'classnames';
import { indexOf } from 'lodash';
import React from 'react';
import { Unsubscribable } from 'rxjs';
@ -6,6 +7,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { getTemplateSrv, RefreshEvent } from '@grafana/runtime';
import { Icon } from '@grafana/ui';
import appEvents from 'app/core/app_events';
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard/types';
import { ShowConfirmModalEvent } from '../../../../types/events';
import { DashboardModel } from '../../state/DashboardModel';
@ -38,6 +40,23 @@ export class DashboardRow extends React.Component<DashboardRowProps> {
this.props.dashboard.toggleRow(this.props.panel);
};
getWarning = () => {
const panels = !!this.props.panel.panels?.length
? this.props.panel.panels
: this.props.dashboard.getRowPanels(indexOf(this.props.dashboard.panels, this.props.panel));
const isAnyPanelUsingDashboardDS = panels.some((p) => p.datasource?.uid === SHARED_DASHBOARD_QUERY);
if (isAnyPanelUsingDashboardDS) {
return (
<p>
Panels in this row use the {SHARED_DASHBOARD_QUERY} data source. These panels will reference the panel in the
original row, not the ones in the repeated rows.
</p>
);
}
return undefined;
};
onUpdate = (title: string, repeat?: string | null) => {
this.props.panel.setProperty('title', title);
this.props.panel.setProperty('repeat', repeat ?? undefined);
@ -94,6 +113,7 @@ export class DashboardRow extends React.Component<DashboardRowProps> {
title={this.props.panel.title}
repeat={this.props.panel.repeat}
onUpdate={this.onUpdate}
warning={this.getWarning()}
/>
<button type="button" className="pointer" onClick={this.onDelete} aria-label="Delete row">
<Icon name="trash-alt" />

View File

@ -9,9 +9,10 @@ export interface RowOptionsButtonProps {
title: string;
repeat?: string | null;
onUpdate: OnRowOptionsUpdate;
warning?: React.ReactNode;
}
export const RowOptionsButton = ({ repeat, title, onUpdate }: RowOptionsButtonProps) => {
export const RowOptionsButton = ({ repeat, title, onUpdate, warning }: RowOptionsButtonProps) => {
const onUpdateChange = (hideModal: () => void) => (title: string, repeat?: string | null) => {
onUpdate(title, repeat);
hideModal();
@ -26,7 +27,13 @@ export const RowOptionsButton = ({ repeat, title, onUpdate }: RowOptionsButtonPr
className="pointer"
aria-label="Row options"
onClick={() => {
showModal(RowOptionsModal, { title, repeat, onDismiss: hideModal, onUpdate: onUpdateChange(hideModal) });
showModal(RowOptionsModal, {
title,
repeat,
onDismiss: hideModal,
onUpdate: onUpdateChange(hideModal),
warning,
});
}}
>
<Icon name="cog" />

View File

@ -0,0 +1,34 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { TestProvider } from 'test/helpers/TestProvider';
import { selectors } from '@grafana/e2e-selectors';
import { RowOptionsForm } from './RowOptionsForm';
jest.mock('../RepeatRowSelect/RepeatRowSelect', () => ({
RepeatRowSelect: () => <div />,
}));
describe('DashboardRow', () => {
it('Should show warning component when has warningMessage prop', () => {
render(
<TestProvider>
<RowOptionsForm repeat={'3'} title="" onCancel={jest.fn()} onUpdate={jest.fn()} warning="a warning message" />
</TestProvider>
);
expect(
screen.getByTestId(selectors.pages.Dashboard.Rows.Repeated.ConfigSection.warningMessage)
).toBeInTheDocument();
});
it('Should not show warning component when does not have warningMessage prop', () => {
render(
<TestProvider>
<RowOptionsForm repeat={'3'} title="" onCancel={jest.fn()} onUpdate={jest.fn()} />
</TestProvider>
);
expect(
screen.queryByTestId(selectors.pages.Dashboard.Rows.Repeated.ConfigSection.warningMessage)
).not.toBeInTheDocument();
});
});

View File

@ -1,6 +1,7 @@
import React, { useCallback, useState } from 'react';
import { Button, Field, Form, Modal, Input } from '@grafana/ui';
import { selectors } from '@grafana/e2e-selectors';
import { Button, Field, Form, Modal, Input, Alert } from '@grafana/ui';
import { RepeatRowSelect } from '../RepeatRowSelect/RepeatRowSelect';
@ -11,9 +12,10 @@ export interface Props {
repeat?: string | null;
onUpdate: OnRowOptionsUpdate;
onCancel: () => void;
warning?: React.ReactNode;
}
export const RowOptionsForm = ({ repeat, title, onUpdate, onCancel }: Props) => {
export const RowOptionsForm = ({ repeat, title, warning, onUpdate, onCancel }: Props) => {
const [newRepeat, setNewRepeat] = useState<string | null | undefined>(repeat);
const onChangeRepeat = useCallback((name?: string | null) => setNewRepeat(name), [setNewRepeat]);
@ -29,11 +31,20 @@ export const RowOptionsForm = ({ repeat, title, onUpdate, onCancel }: Props) =>
<Field label="Title">
<Input {...register('title')} type="text" />
</Field>
<Field label="Repeat for">
<RepeatRowSelect repeat={newRepeat} onChange={onChangeRepeat} />
</Field>
{warning && (
<Alert
data-testid={selectors.pages.Dashboard.Rows.Repeated.ConfigSection.warningMessage}
severity="warning"
title=""
topSpacing={3}
bottomSpacing={0}
>
{warning}
</Alert>
)}
<Modal.ButtonRow>
<Button type="button" variant="secondary" onClick={onCancel} fill="outline">
Cancel

View File

@ -8,15 +8,16 @@ import { OnRowOptionsUpdate, RowOptionsForm } from './RowOptionsForm';
export interface RowOptionsModalProps {
title: string;
repeat?: string | null;
warning?: React.ReactNode;
onDismiss: () => void;
onUpdate: OnRowOptionsUpdate;
}
export const RowOptionsModal = ({ repeat, title, onDismiss, onUpdate }: RowOptionsModalProps) => {
export const RowOptionsModal = ({ repeat, title, onDismiss, onUpdate, warning }: RowOptionsModalProps) => {
const styles = getStyles();
return (
<Modal isOpen={true} title="Row options" icon="copy" onDismiss={onDismiss} className={styles.modal}>
<RowOptionsForm repeat={repeat} title={title} onCancel={onDismiss} onUpdate={onUpdate} />
<RowOptionsForm repeat={repeat} title={title} onCancel={onDismiss} onUpdate={onUpdate} warning={warning} />
</Modal>
);
};