Dashboards: Add dashboard embed route (#69596)

* Dashboard embed: Set up route

* Dashboard embed: Cleanup

* Dashboard embed: Separate routes

* Dashboard embed: Render dashboard page

* Dashboard embed: Add toolbar

* Dashboard embed: Send JSON on save

* Dashboard embed: Add JSON param

* Dashboard embed: Make the dashboard editable

* Fix sending dashboard to remote server

* Add notifications

* Add "dashboardEmbed" feature toggle

* Use the toggle

* Update toggles

* Add toggle on backend

* Add get JSON endpoint

* Add drawer

* Close drawer on success

* Update toggles

* Cleanup

* Update toggle

* Allow embedding for the d-embed url

* Allow embedding via custom X-Allow-Embedding header

* Use callbackUrl

* Cleanup

* Update public/app/features/dashboard/containers/EmbeddedDashboardPage.tsx

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>

* Use theme for spacing

* Update toggles

* Update public/app/features/dashboard/components/EmbeddedDashboard/SaveDashboardForm.tsx

Co-authored-by: Polina Boneva <13227501+polibb@users.noreply.github.com>

* Add select data source modal

---------

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
Co-authored-by: Polina Boneva <13227501+polibb@users.noreply.github.com>
This commit is contained in:
Alex Khomenko
2023-07-06 17:43:20 +03:00
committed by GitHub
parent a8d2a9ae2b
commit 420b19e0e4
14 changed files with 336 additions and 2 deletions

View File

@@ -0,0 +1,73 @@
import React, { useMemo, useState } from 'react';
import { config } from '@grafana/runtime';
import { Drawer, Tab, TabsBar } from '@grafana/ui';
import { DashboardModel } from '../../state';
import DashboardValidation from '../SaveDashboard/DashboardValidation';
import { SaveDashboardDiff } from '../SaveDashboard/SaveDashboardDiff';
import { SaveDashboardData } from '../SaveDashboard/types';
import { jsonDiff } from '../VersionHistory/utils';
import { SaveDashboardForm } from './SaveDashboardForm';
type SaveDashboardDrawerProps = {
dashboard: DashboardModel;
onDismiss: () => void;
dashboardJson: string;
onSave: (clone: DashboardModel) => Promise<unknown>;
};
export const SaveDashboardDrawer = ({ dashboard, onDismiss, dashboardJson, onSave }: SaveDashboardDrawerProps) => {
const data = useMemo<SaveDashboardData>(() => {
const clone = dashboard.getSaveModelClone();
const cloneJSON = JSON.stringify(clone, null, 2);
const cloneSafe = JSON.parse(cloneJSON); // avoids undefined issues
const diff = jsonDiff(JSON.parse(JSON.stringify(dashboardJson, null, 2)), cloneSafe);
let diffCount = 0;
for (const d of Object.values(diff)) {
diffCount += d.length;
}
return {
clone,
diff,
diffCount,
hasChanges: diffCount > 0,
};
}, [dashboard, dashboardJson]);
const [showDiff, setShowDiff] = useState(false);
return (
<Drawer
title={'Save dashboard'}
onClose={onDismiss}
subtitle={dashboard.title}
tabs={
<TabsBar>
<Tab label={'Details'} active={!showDiff} onChangeTab={() => setShowDiff(false)} />
{data.hasChanges && (
<Tab label={'Changes'} active={showDiff} onChangeTab={() => setShowDiff(true)} counter={data.diffCount} />
)}
</TabsBar>
}
scrollableContent
>
{showDiff ? (
<SaveDashboardDiff diff={data.diff} oldValue={dashboardJson} newValue={data.clone} />
) : (
<SaveDashboardForm
dashboard={dashboard}
saveModel={data}
onCancel={onDismiss}
onSuccess={onDismiss}
onSubmit={onSave}
/>
)}
{config.featureToggles.showDashboardValidationWarnings && <DashboardValidation dashboard={dashboard} />}
</Drawer>
);
};

View File

@@ -0,0 +1,57 @@
import React, { useMemo, useState } from 'react';
import { Stack } from '@grafana/experimental';
import { Button, Form } from '@grafana/ui';
import { useAppNotification } from 'app/core/copy/appNotification';
import { DashboardModel } from '../../state';
import { SaveDashboardData } from '../SaveDashboard/types';
interface SaveDashboardProps {
dashboard: DashboardModel;
onCancel: () => void;
onSubmit?: (clone: DashboardModel) => Promise<unknown>;
onSuccess: () => void;
saveModel: SaveDashboardData;
}
export const SaveDashboardForm = ({ dashboard, onCancel, onSubmit, onSuccess, saveModel }: SaveDashboardProps) => {
const [saving, setSaving] = useState(false);
const notifyApp = useAppNotification();
const hasChanges = useMemo(() => dashboard.hasTimeChanged() || saveModel.hasChanges, [dashboard, saveModel]);
const onFormSubmit = async () => {
if (!onSubmit) {
return;
}
setSaving(true);
onSubmit(saveModel.clone)
.then(() => {
notifyApp.success('Dashboard saved locally');
onSuccess();
})
.catch((error) => {
notifyApp.error(error.message || 'Error saving dashboard');
})
.finally(() => setSaving(false));
};
return (
<Form onSubmit={onFormSubmit}>
{() => {
return (
<Stack gap={2}>
<Stack alignItems="center">
<Button variant="secondary" onClick={onCancel} fill="outline">
Cancel
</Button>
<Button type="submit" disabled={!hasChanges} icon={saving ? 'fa fa-spinner' : undefined}>
Save
</Button>
{!hasChanges && <div>No changes to save</div>}
</Stack>
</Stack>
);
}}
</Form>
);
};