grafana/public/app/features/explore/AddToDashboard/AddToDashboardModal.tsx
kay delaney 64bbb7a7ce
Chore: Update and enforce usage of typed react-redux hooks (#55349)
* Chore: Update and enforce usage of typed react-redux hooks
2022-09-19 10:49:35 +01:00

219 lines
7.1 KiB
TypeScript

import { partial } from 'lodash';
import React, { useEffect, useState } from 'react';
import { DeepMap, FieldError, useForm } from 'react-hook-form';
import { locationUtil, SelectableValue } from '@grafana/data';
import { config, locationService, reportInteraction } from '@grafana/runtime';
import { Alert, Button, Field, InputControl, Modal, RadioButtonGroup } from '@grafana/ui';
import { DashboardPicker } from 'app/core/components/Select/DashboardPicker';
import { contextSrv } from 'app/core/services/context_srv';
import { removeDashboardToFetchFromLocalStorage } from 'app/features/dashboard/state/initDashboard';
import { ExploreId, AccessControlAction, useSelector } from 'app/types';
import { getExploreItemSelector } from '../state/selectors';
import { setDashboardInLocalStorage, AddToDashboardError } from './addToDashboard';
enum SaveTarget {
NewDashboard = 'new-dashboard',
ExistingDashboard = 'existing-dashboard',
}
interface SaveTargetDTO {
saveTarget: SaveTarget;
}
interface SaveToNewDashboardDTO extends SaveTargetDTO {
saveTarget: SaveTarget.NewDashboard;
}
interface SaveToExistingDashboard extends SaveTargetDTO {
saveTarget: SaveTarget.ExistingDashboard;
dashboardUid: string;
}
type FormDTO = SaveToNewDashboardDTO | SaveToExistingDashboard;
function assertIsSaveToExistingDashboardError(
errors: DeepMap<FormDTO, FieldError>
): asserts errors is DeepMap<SaveToExistingDashboard, FieldError> {
// the shape of the errors object is always compatible with the type above, but we need to
// explicitly assert its type so that TS can narrow down FormDTO to SaveToExistingDashboard
// when we use it in the form.
}
function getDashboardURL(dashboardUid?: string) {
return dashboardUid ? `d/${dashboardUid}` : 'dashboard/new';
}
enum GenericError {
UNKNOWN = 'unknown-error',
NAVIGATION = 'navigation-error',
}
interface SubmissionError {
error: AddToDashboardError | GenericError;
message: string;
}
interface Props {
onClose: () => void;
exploreId: ExploreId;
}
export const AddToDashboardModal = ({ onClose, exploreId }: Props) => {
const exploreItem = useSelector(getExploreItemSelector(exploreId))!;
const [submissionError, setSubmissionError] = useState<SubmissionError | undefined>();
const {
handleSubmit,
control,
formState: { errors },
watch,
} = useForm<FormDTO>({
defaultValues: { saveTarget: SaveTarget.NewDashboard },
});
const canCreateDashboard = contextSrv.hasAccess(AccessControlAction.DashboardsCreate, contextSrv.isEditor);
const canWriteDashboard = contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor);
const saveTargets: Array<SelectableValue<SaveTarget>> = [];
if (canCreateDashboard) {
saveTargets.push({
label: 'New dashboard',
value: SaveTarget.NewDashboard,
});
}
if (canWriteDashboard) {
saveTargets.push({
label: 'Existing dashboard',
value: SaveTarget.ExistingDashboard,
});
}
const saveTarget = saveTargets.length > 1 ? watch('saveTarget') : saveTargets[0].value;
const modalTitle = `Add panel to ${saveTargets.length > 1 ? 'dashboard' : saveTargets[0].label!.toLowerCase()}`;
const onSubmit = async (openInNewTab: boolean, data: FormDTO) => {
setSubmissionError(undefined);
const dashboardUid = data.saveTarget === SaveTarget.ExistingDashboard ? data.dashboardUid : undefined;
reportInteraction('e2d_submit', {
newTab: openInNewTab,
saveTarget: data.saveTarget,
queries: exploreItem.queries.length,
});
try {
await setDashboardInLocalStorage({
dashboardUid,
datasource: exploreItem.datasourceInstance?.getRef(),
queries: exploreItem.queries,
queryResponse: exploreItem.queryResponse,
});
} catch (error) {
switch (error) {
case AddToDashboardError.FETCH_DASHBOARD:
setSubmissionError({ error, message: 'Could not fetch dashboard information. Please try again.' });
break;
case AddToDashboardError.SET_DASHBOARD_LS:
setSubmissionError({ error, message: 'Could not add panel to dashboard. Please try again.' });
break;
default:
setSubmissionError({ error: GenericError.UNKNOWN, message: 'Something went wrong. Please try again.' });
}
return;
}
const dashboardURL = getDashboardURL(dashboardUid);
if (!openInNewTab) {
onClose();
locationService.push(locationUtil.stripBaseFromUrl(dashboardURL));
return;
}
const didTabOpen = !!global.open(config.appUrl + dashboardURL, '_blank');
if (!didTabOpen) {
setSubmissionError({
error: GenericError.NAVIGATION,
message: 'Could not navigate to the selected dashboard. Please try again.',
});
removeDashboardToFetchFromLocalStorage();
return;
}
onClose();
};
useEffect(() => {
reportInteraction('e2d_open');
}, []);
return (
<Modal title={modalTitle} onDismiss={onClose} isOpen>
<form>
{saveTargets.length > 1 && (
<InputControl
control={control}
render={({ field: { ref, ...field } }) => (
<Field label="Target dashboard" description="Choose where to add the panel.">
<RadioButtonGroup options={saveTargets} {...field} id="e2d-save-target" />
</Field>
)}
name="saveTarget"
/>
)}
{saveTarget === SaveTarget.ExistingDashboard &&
(() => {
assertIsSaveToExistingDashboardError(errors);
return (
<InputControl
render={({ field: { ref, value, onChange, ...field } }) => (
<Field
label="Dashboard"
description="Select in which dashboard the panel will be created."
error={errors.dashboardUid?.message}
invalid={!!errors.dashboardUid}
>
<DashboardPicker
{...field}
inputId="e2d-dashboard-picker"
defaultOptions
onChange={(d) => onChange(d?.uid)}
/>
</Field>
)}
control={control}
name="dashboardUid"
shouldUnregister
rules={{ required: { value: true, message: 'This field is required.' } }}
/>
);
})()}
{submissionError && (
<Alert severity="error" title="Error adding the panel">
{submissionError.message}
</Alert>
)}
<Modal.ButtonRow>
<Button type="reset" onClick={onClose} fill="outline" variant="secondary">
Cancel
</Button>
<Button
type="submit"
variant="secondary"
onClick={handleSubmit(partial(onSubmit, true))}
icon="external-link-alt"
>
Open in new tab
</Button>
<Button type="submit" variant="primary" onClick={handleSubmit(partial(onSubmit, false))} icon="apps">
Open dashboard
</Button>
</Modal.ButtonRow>
</form>
</Modal>
);
};