mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: set correct datasource when creating panel in a dashboard (#46530)
This commit is contained in:
parent
bd21355f87
commit
00f67cad1b
@ -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 }));
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -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} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user