grafana/public/app/features/explore/AddToDashboard/index.tsx
Giordano Ricci 09f48173fe
Explore: allow users to save Explore state to a new panel in a new dashboard (#45148)
* Add Button & Form

* Save to new dashboard

* minor adjustements

* move modal to a separate component

* handle dashboard name related errors

* pick visualization based on query results

* lift state

* fix types

* Add Open & Close tests

* Add submit test

* add navigation tests

* add tests for API errors

* remove console log

* create wrapper component for AddToDashboardButton

* remove unused mapped prop

* add wrapper test

* rename isActive to isVisible

* invert control over save & redirect logic

* remove leftover commented code

* cleanup setup parameters

* reorganize code & improve tests

* Remove option to add to existing dashboard

* UI tweaks

* disable button if no queries

* Fix tests

* better accessible tests

* handle submission errors

* improve addToDashboard types

* use dashboardSrv' saveDashboard

* remove leftover test helper

* fix typo

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>

* Apply suggestions from code review

Co-authored-by: Kristina <kristina.durivage@grafana.com>

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>
Co-authored-by: Kristina <kristina.durivage@grafana.com>
2022-03-03 08:54:06 +00:00

94 lines
2.9 KiB
TypeScript

import React, { useState } from 'react';
import { DataFrame, DataQuery } from '@grafana/data';
import { ExploreId, StoreState } from 'app/types';
import { useSelector, useDispatch } from 'react-redux';
import { getExploreItemSelector } from '../state/selectors';
import { addToDashboard, SaveToNewDashboardDTO } from './addToDashboard';
import { locationService } from '@grafana/runtime';
import { notifyApp } from 'app/core/actions';
import { createSuccessNotification } from 'app/core/copy/appNotification';
import { ToolbarButton } from '@grafana/ui';
import { AddToDashboardModal, ErrorResponse } from './AddToDashboardModal';
const isVisible = (query: DataQuery) => !query.hide;
const hasRefId = (refId: DataFrame['refId']) => (frame: DataFrame) => frame.refId === refId;
const getMainVisualization = (
queries: DataQuery[],
graphFrames?: DataFrame[],
logsFrames?: DataFrame[],
nodeGraphFrames?: DataFrame[]
) => {
for (const { refId } of queries.filter(isVisible)) {
// traceview is not supported in dashboards, skipping it for now.
const hasQueryRefId = hasRefId(refId);
if (graphFrames?.some(hasQueryRefId)) {
return 'timeseries';
}
if (logsFrames?.some(hasQueryRefId)) {
return 'logs';
}
if (nodeGraphFrames?.some(hasQueryRefId)) {
return 'nodeGraph';
}
}
// falling back to table
return 'table';
};
interface Props {
exploreId: ExploreId;
}
export const AddToDashboard = ({ exploreId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
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 || {};
return { queries, mainVisualization: getMainVisualization(queries, graphFrames, logsFrames, nodeGraphFrames) };
});
const handleSave = async (data: SaveToNewDashboardDTO, redirect: boolean): Promise<void | ErrorResponse> => {
try {
const redirectURL = await addToDashboard(data);
if (redirect) {
locationService.push(redirectURL);
} else {
dispatch(notifyApp(createSuccessNotification(`Panel saved to ${data.dashboardName}`)));
setIsOpen(false);
}
return;
} catch (e) {
return { message: e.data?.message, status: e.data?.status ?? 'unknown-error' };
}
};
return (
<>
<ToolbarButton
icon="apps"
onClick={() => setIsOpen(true)}
aria-label="Add to dashboard"
disabled={queries.length === 0}
>
Add to Dashboard
</ToolbarButton>
{isOpen && (
<AddToDashboardModal
onClose={() => setIsOpen(false)}
queries={queries}
visualization={mainVisualization}
onSave={handleSave}
/>
)}
</>
);
};