From 00f67cad1be728a7d6107c8b963ecbad971c5fbc Mon Sep 17 00:00:00 2001 From: Giordano Ricci Date: Mon, 14 Mar 2022 23:26:13 +0000 Subject: [PATCH] Explore: set correct datasource when creating panel in a dashboard (#46530) --- .../AddToDashboardModal.test.tsx | 14 ++--- .../AddToDashboard/AddToDashboardModal.tsx | 10 +--- .../explore/AddToDashboard/addToDashboard.ts | 27 +++++++-- .../explore/AddToDashboard/index.test.tsx | 55 ++++++++++++++++--- .../features/explore/AddToDashboard/index.tsx | 31 ++++++----- 5 files changed, 93 insertions(+), 44 deletions(-) diff --git a/public/app/features/explore/AddToDashboard/AddToDashboardModal.test.tsx b/public/app/features/explore/AddToDashboard/AddToDashboardModal.test.tsx index 64adcec7e9c..5cc59ef43e6 100644 --- a/public/app/features/explore/AddToDashboard/AddToDashboardModal.test.tsx +++ b/public/app/features/explore/AddToDashboard/AddToDashboardModal.test.tsx @@ -41,7 +41,7 @@ describe('Add to Dashboard Modal', () => { it('Does not submit if the form is invalid', async () => { const saveMock = jest.fn(); - render( {}} />); + render( {}} />); // 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( {}} />); + render( {}} />); await waitForSearchFolderResponse(); const dashboardNameInput = screen.getByRole('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( {}} />); + render( {}} />); 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( {}} />); + render( {}} />); 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( {}} />); + render( {}} />); 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( {}} />); + render( {}} />); userEvent.click(screen.getByRole('button', { name: /save and keep exploring/i })); diff --git a/public/app/features/explore/AddToDashboard/AddToDashboardModal.tsx b/public/app/features/explore/AddToDashboard/AddToDashboardModal.tsx index 25dadb957cf..4ef879a9829 100644 --- a/public/app/features/explore/AddToDashboard/AddToDashboardModal.tsx +++ b/public/app/features/explore/AddToDashboard/AddToDashboardModal.tsx @@ -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; } @@ -31,7 +28,7 @@ function withRedirect(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(); const { register, @@ -39,7 +36,7 @@ export const AddToDashboardModal = ({ onClose, queries, visualization, onSave }: control, formState: { errors, isSubmitting }, setError, - } = useForm({ defaultValues: { queries, visualization } }); + } = useForm(); const onSubmit = async (withRedirect: boolean, data: FormDTO) => { setSubmissionError(undefined); @@ -66,9 +63,6 @@ export const AddToDashboardModal = ({ onClose, queries, visualization, onSave }: return (
- - -

Create a new dashboard and add a panel with the explored queries.

{ +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 => { - const res = await createDashboard(data.dashboardName, data.folderId, data.queries, data.visualization); +export const addToDashboard = async (data: SaveToNewDashboardDTO, options: SaveOptions): Promise => { + const res = await createDashboard( + data.dashboardName, + data.folderId, + options.queries, + options.datasource, + options.panel + ); return res.data.url; }; diff --git a/public/app/features/explore/AddToDashboard/index.test.tsx b/public/app/features/explore/AddToDashboard/index.test.tsx index 9fa7e8667b2..415980c1249 100644 --- a/public/app/features/explore/AddToDashboard/index.test.tsx +++ b/public/app/features/explore/AddToDashboard/index.test.tsx @@ -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({children}); }; @@ -104,6 +115,26 @@ describe('Add to Dashboard Button', () => { }); }); + it('Correct datasource ref is useed', async () => { + setup(, [{ 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(, 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', }) ); }); diff --git a/public/app/features/explore/AddToDashboard/index.tsx b/public/app/features/explore/AddToDashboard/index.tsx index 9fe40e673fa..6cc0f31303f 100644 --- a/public/app/features/explore/AddToDashboard/index.tsx +++ b/public/app/features/explore/AddToDashboard/index.tsx @@ -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 => { 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 - {isOpen && ( - setIsOpen(false)} - queries={queries} - visualization={mainVisualization} - onSave={handleSave} - /> - )} + {isOpen && setIsOpen(false)} onSave={handleSave} />} ); };