Explore: set correct datasource when creating panel in a dashboard (#46530)

This commit is contained in:
Giordano Ricci 2022-03-14 23:26:13 +00:00 committed by GitHub
parent bd21355f87
commit 00f67cad1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 44 deletions

View File

@ -41,7 +41,7 @@ describe('Add to Dashboard Modal', () => {
it('Does not submit if the form is invalid', async () => {
const saveMock = jest.fn();
render(<AddToDashboardModal queries={[]} visualization="table" onSave={saveMock} onClose={() => {}} />);
render(<AddToDashboardModal onSave={saveMock} onClose={() => {}} />);
// there shouldn't be any alert in the modal
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
@ -63,7 +63,7 @@ describe('Add to Dashboard Modal', () => {
it('Correctly submits if the form is valid', async () => {
const saveMock = jest.fn();
render(<AddToDashboardModal queries={[]} visualization="table" onSave={saveMock} onClose={() => {}} />);
render(<AddToDashboardModal onSave={saveMock} onClose={() => {}} />);
await waitForSearchFolderResponse();
const dashboardNameInput = screen.getByRole<HTMLInputElement>('textbox', { name: /dashboard name/i });
@ -77,8 +77,6 @@ describe('Add to Dashboard Modal', () => {
expect(saveMock).toHaveBeenCalledWith(
{
dashboardName: dashboardNameInput.value,
queries: [],
visualization: 'table',
folderId: 1,
},
expect.anything()
@ -91,7 +89,7 @@ describe('Add to Dashboard Modal', () => {
// name-exists is triggered when trying to create a dashboard in a folder that already has a dashboard with the same name
const saveMock = jest.fn().mockResolvedValue({ status: 'name-exists', message: 'name exists' });
render(<AddToDashboardModal queries={[]} visualization="table" onSave={saveMock} onClose={() => {}} />);
render(<AddToDashboardModal onSave={saveMock} onClose={() => {}} />);
userEvent.click(screen.getByRole('button', { name: /save and keep exploring/i }));
@ -106,7 +104,7 @@ describe('Add to Dashboard Modal', () => {
// dashboard name field
const saveMock = jest.fn().mockResolvedValue({ status: 'empty-name', message: 'empty name' });
render(<AddToDashboardModal queries={[]} visualization="table" onSave={saveMock} onClose={() => {}} />);
render(<AddToDashboardModal onSave={saveMock} onClose={() => {}} />);
userEvent.click(screen.getByRole('button', { name: /save and keep exploring/i }));
@ -119,7 +117,7 @@ describe('Add to Dashboard Modal', () => {
// https://github.com/grafana/grafana/blob/44f1e381cbc7a5e236b543bc6bd06b00e3152d7f/pkg/models/dashboards.go#L71
const saveMock = jest.fn().mockResolvedValue({ status: 'name-match', message: 'name match' });
render(<AddToDashboardModal queries={[]} visualization="table" onSave={saveMock} onClose={() => {}} />);
render(<AddToDashboardModal onSave={saveMock} onClose={() => {}} />);
userEvent.click(screen.getByRole('button', { name: /save and keep exploring/i }));
@ -129,7 +127,7 @@ describe('Add to Dashboard Modal', () => {
it('Correctly handles unknown API Errors', async () => {
const saveMock = jest.fn().mockResolvedValue({ status: 'unknown-error', message: 'unknown error' });
render(<AddToDashboardModal queries={[]} visualization="table" onSave={saveMock} onClose={() => {}} />);
render(<AddToDashboardModal onSave={saveMock} onClose={() => {}} />);
userEvent.click(screen.getByRole('button', { name: /save and keep exploring/i }));

View File

@ -1,5 +1,4 @@
import React, { useState } from 'react';
import { DataQuery } from '@grafana/data';
import { Alert, Button, Field, Input, InputControl, Modal } from '@grafana/ui';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
import { useForm } from 'react-hook-form';
@ -22,8 +21,6 @@ type FormDTO = SaveToNewDashboardDTO;
interface Props {
onClose: () => void;
queries: DataQuery[];
visualization: string;
onSave: (data: FormDTO, redirect: boolean) => Promise<void | ErrorResponse>;
}
@ -31,7 +28,7 @@ function withRedirect<T extends any[]>(fn: (redirect: boolean, ...args: T) => {}
return async (...args: T) => fn(redirect, ...args);
}
export const AddToDashboardModal = ({ onClose, queries, visualization, onSave }: Props) => {
export const AddToDashboardModal = ({ onClose, onSave }: Props) => {
const [submissionError, setSubmissionError] = useState<string>();
const {
register,
@ -39,7 +36,7 @@ export const AddToDashboardModal = ({ onClose, queries, visualization, onSave }:
control,
formState: { errors, isSubmitting },
setError,
} = useForm<FormDTO>({ defaultValues: { queries, visualization } });
} = useForm<FormDTO>();
const onSubmit = async (withRedirect: boolean, data: FormDTO) => {
setSubmissionError(undefined);
@ -66,9 +63,6 @@ export const AddToDashboardModal = ({ onClose, queries, visualization, onSave }:
return (
<Modal title="Add panel to dashboard" onDismiss={onClose} isOpen>
<form>
<input type="hidden" {...register('queries')} />
<input type="hidden" {...register('visualization')} />
<p>Create a new dashboard and add a panel with the explored queries.</p>
<Field

View File

@ -1,22 +1,37 @@
import { DataQuery } from '@grafana/data';
import { DataQuery, DataSourceRef } from '@grafana/data';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
export interface SaveToNewDashboardDTO {
dashboardName: string;
folderId: number;
}
interface SaveOptions {
queries: DataQuery[];
visualization: string;
panel: string;
datasource: DataSourceRef;
}
const createDashboard = (dashboardName: string, folderId: number, queries: DataQuery[], visualization: string) => {
const createDashboard = (
dashboardName: string,
folderId: number,
queries: DataQuery[],
datasource: DataSourceRef,
panel: string
) => {
const dashboard = getDashboardSrv().create({ title: dashboardName }, { folderId });
dashboard.addPanel({ targets: queries, type: visualization, title: 'New Panel' });
dashboard.addPanel({ targets: queries, type: panel, title: 'New Panel', datasource });
return getDashboardSrv().saveDashboard({ dashboard, folderId }, { showErrorAlert: false, showSuccessAlert: false });
};
export const addToDashboard = async (data: SaveToNewDashboardDTO): Promise<string> => {
const res = await createDashboard(data.dashboardName, data.folderId, data.queries, data.visualization);
export const addToDashboard = async (data: SaveToNewDashboardDTO, options: SaveOptions): Promise<string> => {
const res = await createDashboard(
data.dashboardName,
data.folderId,
options.queries,
options.datasource,
options.panel
);
return res.data.url;
};

View File

@ -17,7 +17,18 @@ const setup = (
queries: DataQuery[] = [],
queryResponse: ExplorePanelData = createEmptyQueryResponse()
) => {
const store = configureStore({ explore: { left: { queries, queryResponse } } as ExploreState });
const store = configureStore({
explore: {
left: {
queries,
queryResponse,
datasourceInstance: {
type: 'loki',
uid: 'someuid',
},
},
} as ExploreState,
});
return render(<Provider store={store}>{children}</Provider>);
};
@ -104,6 +115,26 @@ describe('Add to Dashboard Button', () => {
});
});
it('Correct datasource ref is useed', async () => {
setup(<AddToDashboard exploreId={ExploreId.left} />, [{ refId: 'A' }]);
await openModal();
userEvent.click(screen.getByRole('button', { name: /save and keep exploring/i }));
await waitForElementToBeRemoved(() => screen.queryByRole('dialog', { name: 'Add panel to dashboard' }));
expect(addToDashboardMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
datasource: {
type: 'loki',
uid: 'someuid',
},
})
);
});
it('All queries are correctly passed through', async () => {
const queries: DataQuery[] = [{ refId: 'A' }, { refId: 'B', hide: true }];
setup(<AddToDashboard exploreId={ExploreId.left} />, queries);
@ -115,6 +146,7 @@ describe('Add to Dashboard Button', () => {
await waitForElementToBeRemoved(() => screen.queryByRole('dialog', { name: 'Add panel to dashboard' }));
expect(addToDashboardMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
queries: queries,
})
@ -132,8 +164,9 @@ describe('Add to Dashboard Button', () => {
await waitForElementToBeRemoved(() => screen.queryByRole('dialog', { name: 'Add panel to dashboard' }));
expect(addToDashboardMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
visualization: 'table',
panel: 'table',
})
);
});
@ -149,8 +182,9 @@ describe('Add to Dashboard Button', () => {
await waitForElementToBeRemoved(() => screen.queryByRole('dialog', { name: 'Add panel to dashboard' }));
expect(addToDashboardMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
visualization: 'table',
panel: 'table',
})
);
});
@ -171,9 +205,10 @@ describe('Add to Dashboard Button', () => {
// Query A comes before B, but it's hidden. visualization will be picked according to frames generated by B
expect(addToDashboardMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
queries: queries,
visualization: 'timeseries',
panel: 'timeseries',
})
);
});
@ -193,8 +228,9 @@ describe('Add to Dashboard Button', () => {
// Query A comes before B, but it's hidden. visualization will be picked according to frames generated by B
expect(addToDashboardMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
visualization: 'logs',
panel: 'logs',
})
);
});
@ -213,8 +249,9 @@ describe('Add to Dashboard Button', () => {
await waitForElementToBeRemoved(() => screen.queryByRole('dialog', { name: 'Add panel to dashboard' }));
expect(addToDashboardMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
visualization: 'timeseries',
panel: 'timeseries',
})
);
});
@ -233,8 +270,9 @@ describe('Add to Dashboard Button', () => {
await waitForElementToBeRemoved(() => screen.queryByRole('dialog', { name: 'Add panel to dashboard' }));
expect(addToDashboardMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
visualization: 'nodeGraph',
panel: 'nodeGraph',
})
);
});
@ -254,8 +292,9 @@ describe('Add to Dashboard Button', () => {
await waitForElementToBeRemoved(() => screen.queryByRole('dialog', { name: 'Add panel to dashboard' }));
expect(addToDashboardMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
visualization: 'table',
panel: 'table',
})
);
});

View File

@ -13,7 +13,7 @@ import { AddToDashboardModal, ErrorResponse } from './AddToDashboardModal';
const isVisible = (query: DataQuery) => !query.hide;
const hasRefId = (refId: DataFrame['refId']) => (frame: DataFrame) => frame.refId === refId;
const getMainVisualization = (
const getMainPanel = (
queries: DataQuery[],
graphFrames?: DataFrame[],
logsFrames?: DataFrame[],
@ -46,16 +46,26 @@ export const AddToDashboard = ({ exploreId }: Props) => {
const dispatch = useDispatch();
const selectExploreItem = getExploreItemSelector(exploreId);
const { queries, mainVisualization } = useSelector((state: StoreState) => {
const queries = selectExploreItem(state)?.queries || [];
const { graphFrames, logsFrames, nodeGraphFrames } = selectExploreItem(state)?.queryResponse || {};
const { queries, panel, datasource } = useSelector((state: StoreState) => {
const exploreItem = selectExploreItem(state);
const queries = exploreItem?.queries || [];
const datasource = exploreItem?.datasourceInstance;
const { graphFrames, logsFrames, nodeGraphFrames } = exploreItem?.queryResponse || {};
return { queries, mainVisualization: getMainVisualization(queries, graphFrames, logsFrames, nodeGraphFrames) };
return {
queries,
datasource: { type: datasource?.type, uid: datasource?.uid },
panel: getMainPanel(queries, graphFrames, logsFrames, nodeGraphFrames),
};
});
const handleSave = async (data: SaveToNewDashboardDTO, redirect: boolean): Promise<void | ErrorResponse> => {
try {
const redirectURL = await addToDashboard(data);
const redirectURL = await addToDashboard(data, {
queries,
datasource,
panel,
});
if (redirect) {
locationService.push(redirectURL);
@ -80,14 +90,7 @@ export const AddToDashboard = ({ exploreId }: Props) => {
Add to dashboard
</ToolbarButton>
{isOpen && (
<AddToDashboardModal
onClose={() => setIsOpen(false)}
queries={queries}
visualization={mainVisualization}
onSave={handleSave}
/>
)}
{isOpen && <AddToDashboardModal onClose={() => setIsOpen(false)} onSave={handleSave} />}
</>
);
};