) {
+ const dashboard = getDashboardSceneFor(model);
+ const styles = useStyles2(getStyles);
+
+ const { isSharingExternally } = model.useState();
+
+ const dashboardJson = useAsync(async () => {
+ const json = await model.getExportableDashboardJson();
+ return JSON.stringify(json, null, 2);
+ }, [isSharingExternally]);
+
+ const switchLabel = t('export.json.export-externally-label', 'Export the dashboard to use in another instance');
+
+ return (
+ <>
+
+
+ Copy or download a JSON file containing the JSON of your dashboard
+
+
+
+
+
+
+
+ {({ width }) => {
+ if (dashboardJson.value) {
+ return (
+
+ );
+ }
+
+ return dashboardJson.loading && ;
+ }}
+
+
+
+
+ dashboardJson.value ?? ''}
+ >
+ Copy to clipboard
+
+
+
+
+ >
+ );
+}
+
+function getStyles(theme: GrafanaTheme2) {
+ return {
+ codeEditorBox: css({
+ margin: `${theme.spacing(2)} 0`,
+ }),
+ container: css({
+ paddingBottom: theme.spacing(2),
+ }),
+ };
+}
diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.test.tsx b/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.test.tsx
new file mode 100644
index 00000000000..4bc553b457c
--- /dev/null
+++ b/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.test.tsx
@@ -0,0 +1,56 @@
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
+import { SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes';
+
+import { DashboardGridItem } from '../../scene/DashboardGridItem';
+import { DashboardScene } from '../../scene/DashboardScene';
+
+import ExportButton from './ExportButton';
+
+const selector = e2eSelectors.pages.Dashboard.DashNav.NewExportButton;
+
+describe('ExportButton', () => {
+ it('should render Export menu', async () => {
+ setup();
+ expect(await screen.findByTestId(selector.arrowMenu)).toBeInTheDocument();
+ });
+
+ it('should render menu when arrow button clicked', async () => {
+ setup();
+
+ const arrowMenu = await screen.findByTestId(selector.arrowMenu);
+ await userEvent.click(arrowMenu);
+
+ expect(await screen.findByTestId(selector.Menu.container)).toBeInTheDocument();
+ });
+});
+
+function setup() {
+ const panel = new VizPanel({
+ title: 'Panel A',
+ pluginId: 'table',
+ key: 'panel-12',
+ });
+
+ const dashboard = new DashboardScene({
+ title: 'hello',
+ uid: 'dash-1',
+ $timeRange: new SceneTimeRange({}),
+ body: new SceneGridLayout({
+ children: [
+ new DashboardGridItem({
+ key: 'griditem-1',
+ x: 0,
+ y: 0,
+ width: 10,
+ height: 12,
+ body: panel,
+ }),
+ ],
+ }),
+ });
+
+ render();
+}
diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.tsx b/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.tsx
new file mode 100644
index 00000000000..52ec254b2d7
--- /dev/null
+++ b/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.tsx
@@ -0,0 +1,38 @@
+import { useCallback, useState } from 'react';
+
+import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
+import { Button, ButtonGroup, Dropdown, Icon } from '@grafana/ui';
+import { Trans, t } from 'app/core/internationalization';
+
+import { DashboardScene } from '../../scene/DashboardScene';
+
+import ExportMenu from './ExportMenu';
+
+const newExportButtonSelector = e2eSelectors.pages.Dashboard.DashNav.NewExportButton;
+
+export default function ExportButton({ dashboard }: { dashboard: DashboardScene }) {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const onMenuClick = useCallback((isOpen: boolean) => {
+ setIsOpen(isOpen);
+ }, []);
+
+ const MenuActions = () => ;
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.test.tsx b/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.test.tsx
new file mode 100644
index 00000000000..38045ba05b0
--- /dev/null
+++ b/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.test.tsx
@@ -0,0 +1,44 @@
+import { render, screen } from '@testing-library/react';
+
+import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
+import { SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes';
+
+import { DashboardGridItem } from '../../scene/DashboardGridItem';
+import { DashboardScene } from '../../scene/DashboardScene';
+
+import ExportMenu from './ExportMenu';
+
+const selector = e2eSelectors.pages.Dashboard.DashNav.NewExportButton.Menu;
+
+describe('ExportMenu', () => {
+ it('should render menu items', async () => {
+ setup();
+ expect(await screen.findByTestId(selector.exportAsJson)).toBeInTheDocument();
+ });
+});
+
+function setup() {
+ const panel = new VizPanel({
+ title: 'Panel A',
+ pluginId: 'table',
+ key: 'panel-12',
+ });
+ const dashboard = new DashboardScene({
+ title: 'hello',
+ uid: 'dash-1',
+ $timeRange: new SceneTimeRange({}),
+ body: new SceneGridLayout({
+ children: [
+ new DashboardGridItem({
+ key: 'griditem-1',
+ x: 0,
+ y: 0,
+ width: 10,
+ height: 12,
+ body: panel,
+ }),
+ ],
+ }),
+ });
+ render();
+}
diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.tsx b/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.tsx
new file mode 100644
index 00000000000..85bcb6c7749
--- /dev/null
+++ b/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.tsx
@@ -0,0 +1,32 @@
+import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
+import { Menu } from '@grafana/ui';
+import { t } from 'app/core/internationalization';
+
+import { DashboardScene } from '../../scene/DashboardScene';
+import { ShareDrawer } from '../ShareDrawer/ShareDrawer';
+
+import { ExportAsJson } from './ExportAsJson';
+
+const newExportButtonSelector = e2eSelectors.pages.Dashboard.DashNav.NewExportButton.Menu;
+
+export default function ExportMenu({ dashboard }: { dashboard: DashboardScene }) {
+ const onExportAsJsonClick = () => {
+ const drawer = new ShareDrawer({
+ title: t('export.json.title', 'Save dashboard JSON'),
+ body: new ExportAsJson({}),
+ });
+
+ dashboard.showModal(drawer);
+ };
+
+ return (
+
+ );
+}
diff --git a/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx b/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx
index a56428878da..746caf6b1ce 100644
--- a/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx
+++ b/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx
@@ -16,7 +16,7 @@ import { getDashboardSceneFor } from '../utils/utils';
import { SceneShareTabState } from './types';
-interface ShareExportTabState extends SceneShareTabState {
+export interface ShareExportTabState extends SceneShareTabState {
isSharingExternally?: boolean;
isViewingJSON?: boolean;
}
@@ -55,7 +55,7 @@ export class ShareExportTab extends SceneObjectBase {
return;
}
- public async getExportableDashboardJson() {
+ public getExportableDashboardJson = async () => {
const { isSharingExternally } = this.state;
const saveModel = transformSceneToSaveModel(getDashboardSceneFor(this));
@@ -70,9 +70,9 @@ export class ShareExportTab extends SceneObjectBase {
: saveModel;
return exportable;
- }
+ };
- public async onSaveAsFile() {
+ public onSaveAsFile = async () => {
const dashboardJson = await this.getExportableDashboardJson();
const dashboardJsonPretty = JSON.stringify(dashboardJson, null, 2);
const { isSharingExternally } = this.state;
@@ -90,7 +90,7 @@ export class ShareExportTab extends SceneObjectBase {
DashboardInteractions.exportDownloadJsonClicked({
externally: isSharingExternally,
});
- }
+ };
}
function ShareExportTabRenderer({ model }: SceneComponentProps) {
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 84d88cce4f2..70a894d96fd 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -686,6 +686,20 @@
"split-widen": "Widen pane"
}
},
+ "export": {
+ "json": {
+ "cancel-button": "Cancel",
+ "copy-button": "Copy to clipboard",
+ "download-button": "Download file",
+ "export-externally-label": "Export the dashboard to use in another instance",
+ "info-text": "Copy or download a JSON file containing the JSON of your dashboard",
+ "title": "Save dashboard JSON"
+ },
+ "menu": {
+ "export-as-json-label": "Export",
+ "export-as-json-tooltip": "Export"
+ }
+ },
"folder-picker": {
"loading": "Loading folders..."
},
@@ -1729,6 +1743,7 @@
},
"share-dashboard": {
"menu": {
+ "export-json-title": "Export as JSON",
"share-externally-title": "Share externally",
"share-internally-description": "Advanced settings",
"share-internally-title": "Share internally",
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index 46369331744..3d992a19570 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -686,6 +686,20 @@
"split-widen": "Ŵįđęʼn päʼnę"
}
},
+ "export": {
+ "json": {
+ "cancel-button": "Cäʼnčęľ",
+ "copy-button": "Cőpy ŧő čľįpþőäřđ",
+ "download-button": "Đőŵʼnľőäđ ƒįľę",
+ "export-externally-label": "Ēχpőřŧ ŧĥę đäşĥþőäřđ ŧő ūşę įʼn äʼnőŧĥęř įʼnşŧäʼnčę",
+ "info-text": "Cőpy őř đőŵʼnľőäđ ä ĴŜØŃ ƒįľę čőʼnŧäįʼnįʼnģ ŧĥę ĴŜØŃ őƒ yőūř đäşĥþőäřđ",
+ "title": "Ŝävę đäşĥþőäřđ ĴŜØŃ"
+ },
+ "menu": {
+ "export-as-json-label": "Ēχpőřŧ",
+ "export-as-json-tooltip": "Ēχpőřŧ"
+ }
+ },
"folder-picker": {
"loading": "Ŀőäđįʼnģ ƒőľđęřş..."
},
@@ -1729,6 +1743,7 @@
},
"share-dashboard": {
"menu": {
+ "export-json-title": "Ēχpőřŧ äş ĴŜØŃ",
"share-externally-title": "Ŝĥäřę ęχŧęřʼnäľľy",
"share-internally-description": "Åđväʼnčęđ şęŧŧįʼnģş",
"share-internally-title": "Ŝĥäřę įʼnŧęřʼnäľľy",