mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 18:30:41 -06:00
Dashboard scene: Share modal export (#75640)
* Dashboard scene: Share modal export * i18n
This commit is contained in:
parent
7a38090bc0
commit
933d920121
199
public/app/features/dashboard-scene/sharing/ShareExportTab.tsx
Normal file
199
public/app/features/dashboard-scene/sharing/ShareExportTab.tsx
Normal file
@ -0,0 +1,199 @@
|
||||
import saveAs from 'file-saver';
|
||||
import React from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { config, getBackendSrv } from '@grafana/runtime';
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectRef } from '@grafana/scenes';
|
||||
import { Button, ClipboardButton, CodeEditor, Field, Modal, Switch, VerticalGroup } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal';
|
||||
import { trackDashboardSharingActionPerType } from 'app/features/dashboard/components/ShareModal/analytics';
|
||||
import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
|
||||
|
||||
import { SceneShareTabState } from './types';
|
||||
|
||||
const exportExternallyTranslation = t('share-modal.export.share-externally-label', `Export for sharing externally`);
|
||||
const exportDefaultTranslation = t('share-modal.export.share-default-label', `Export with default values removed`);
|
||||
|
||||
interface ShareExportTabState extends SceneShareTabState {
|
||||
dashboardRef: SceneObjectRef<DashboardScene>;
|
||||
isSharingExternally?: boolean;
|
||||
shouldTrimDefaults?: boolean;
|
||||
isViewingJSON?: boolean;
|
||||
}
|
||||
|
||||
export class ShareExportTab extends SceneObjectBase<ShareExportTabState> {
|
||||
static Component = ShareExportTabRenderer;
|
||||
|
||||
private _exporter = new DashboardExporter();
|
||||
|
||||
constructor(state: Omit<ShareExportTabState, 'panelRef'>) {
|
||||
super({
|
||||
isSharingExternally: false,
|
||||
shouldTrimDefaults: false,
|
||||
isViewingJSON: false,
|
||||
...state,
|
||||
});
|
||||
}
|
||||
|
||||
public getTabLabel() {
|
||||
return t('share-modal.tab-title.export', 'Export');
|
||||
}
|
||||
|
||||
public onShareExternallyChange = () => {
|
||||
this.setState({
|
||||
isSharingExternally: !this.state.isSharingExternally,
|
||||
});
|
||||
};
|
||||
|
||||
public onTrimDefaultsChange = () => {
|
||||
this.setState({
|
||||
shouldTrimDefaults: !this.state.shouldTrimDefaults,
|
||||
});
|
||||
};
|
||||
|
||||
public onViewJSON = () => {
|
||||
this.setState({
|
||||
isViewingJSON: !this.state.isViewingJSON,
|
||||
});
|
||||
};
|
||||
|
||||
public getClipboardText() {
|
||||
return;
|
||||
}
|
||||
|
||||
public async getExportableDashboardJson() {
|
||||
const { dashboardRef, isSharingExternally, shouldTrimDefaults } = this.state;
|
||||
const saveModel = transformSceneToSaveModel(dashboardRef.resolve());
|
||||
|
||||
const exportable = isSharingExternally
|
||||
? await this._exporter.makeExportable(new DashboardModel(saveModel))
|
||||
: saveModel;
|
||||
|
||||
if (shouldTrimDefaults) {
|
||||
const trimmed = await getBackendSrv().post('/api/dashboards/trim', { dashboard: exportable });
|
||||
return trimmed.dashboard;
|
||||
} else {
|
||||
return exportable;
|
||||
}
|
||||
}
|
||||
|
||||
public async onSaveAsFile() {
|
||||
const dashboardJson = await this.getExportableDashboardJson();
|
||||
const dashboardJsonPretty = JSON.stringify(dashboardJson, null, 2);
|
||||
|
||||
const blob = new Blob([dashboardJsonPretty], {
|
||||
type: 'application/json;charset=utf-8',
|
||||
});
|
||||
|
||||
const time = new Date().getTime();
|
||||
saveAs(blob, `${dashboardJson.title}-${time}.json`);
|
||||
trackDashboardSharingActionPerType('save_export', shareDashboardType.export);
|
||||
}
|
||||
}
|
||||
|
||||
function ShareExportTabRenderer({ model }: SceneComponentProps<ShareExportTab>) {
|
||||
const { isSharingExternally, shouldTrimDefaults, isViewingJSON, modalRef } = model.useState();
|
||||
|
||||
const dashboardJson = useAsync(async () => {
|
||||
if (isViewingJSON) {
|
||||
const json = await model.getExportableDashboardJson();
|
||||
return JSON.stringify(json, null, 2);
|
||||
}
|
||||
|
||||
return '';
|
||||
}, [isViewingJSON]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isViewingJSON && (
|
||||
<>
|
||||
<p className="share-modal-info-text">
|
||||
<Trans i18nKey="share-modal.export.info-text">Export this dashboard.</Trans>
|
||||
</p>
|
||||
<VerticalGroup spacing="md">
|
||||
<Field label={exportExternallyTranslation}>
|
||||
<Switch
|
||||
id="share-externally-toggle"
|
||||
value={isSharingExternally}
|
||||
onChange={model.onShareExternallyChange}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{config.featureToggles.trimDefaults && (
|
||||
<Field label={exportDefaultTranslation}>
|
||||
<Switch id="trim-defaults-toggle" value={shouldTrimDefaults} onChange={model.onTrimDefaultsChange} />
|
||||
</Field>
|
||||
)}
|
||||
</VerticalGroup>
|
||||
|
||||
<Modal.ButtonRow>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
modalRef?.resolve().onDismiss();
|
||||
}}
|
||||
fill="outline"
|
||||
>
|
||||
<Trans i18nKey="share-modal.export.cancel-button">Cancel</Trans>
|
||||
</Button>
|
||||
<Button variant="secondary" icon="brackets-curly" onClick={model.onViewJSON}>
|
||||
<Trans i18nKey="share-modal.export.view-button">View JSON</Trans>
|
||||
</Button>
|
||||
<Button variant="primary" icon="save" onClick={() => model.onSaveAsFile()}>
|
||||
<Trans i18nKey="share-modal.export.save-button">Save to file</Trans>
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isViewingJSON && (
|
||||
<>
|
||||
<AutoSizer disableHeight>
|
||||
{({ width }) => {
|
||||
if (dashboardJson.value) {
|
||||
return (
|
||||
<CodeEditor
|
||||
value={dashboardJson.value ?? ''}
|
||||
language="json"
|
||||
showMiniMap={false}
|
||||
height="500px"
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (dashboardJson.loading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return null;
|
||||
}}
|
||||
</AutoSizer>
|
||||
|
||||
<Modal.ButtonRow>
|
||||
<Button variant="secondary" fill="outline" onClick={model.onViewJSON} icon="arrow-left">
|
||||
<Trans i18nKey="share-modal.export.back-button">Back to export config</Trans>
|
||||
</Button>
|
||||
<ClipboardButton
|
||||
variant="secondary"
|
||||
icon="copy"
|
||||
disabled={dashboardJson.loading}
|
||||
getText={() => dashboardJson.value ?? ''}
|
||||
>
|
||||
<Trans i18nKey="share-modal.view-json.copy-button">Copy to Clipboard</Trans>
|
||||
</ClipboardButton>
|
||||
<Button variant="primary" icon="save" disabled={dashboardJson.loading} onClick={() => model.onSaveAsFile()}>
|
||||
<Trans i18nKey="share-modal.export.save-button">Save to file</Trans>
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -3,14 +3,7 @@ import React from 'react';
|
||||
import { dateTime, UrlQueryMap } from '@grafana/data';
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { config, locationService } from '@grafana/runtime';
|
||||
import {
|
||||
SceneComponentProps,
|
||||
SceneObjectBase,
|
||||
SceneObjectState,
|
||||
SceneObjectRef,
|
||||
VizPanel,
|
||||
sceneGraph,
|
||||
} from '@grafana/scenes';
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectRef, VizPanel, sceneGraph } from '@grafana/scenes';
|
||||
import { TimeZone } from '@grafana/schema';
|
||||
import { Alert, ClipboardButton, Field, FieldSet, Icon, Input, Switch } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
@ -22,7 +15,8 @@ import { shareDashboardType } from 'app/features/dashboard/components/ShareModal
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { getDashboardUrl } from '../utils/utils';
|
||||
|
||||
export interface ShareLinkTabState extends SceneObjectState, ShareOptions {
|
||||
import { SceneShareTabState } from './types';
|
||||
export interface ShareLinkTabState extends SceneShareTabState, ShareOptions {
|
||||
panelRef?: SceneObjectRef<VizPanel>;
|
||||
dashboardRef: SceneObjectRef<DashboardScene>;
|
||||
}
|
||||
|
@ -9,9 +9,10 @@ import { t } from 'app/core/internationalization';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { ShareExportTab } from './ShareExportTab';
|
||||
import { ShareLinkTab } from './ShareLinkTab';
|
||||
import { ShareSnapshotTab } from './ShareSnapshotTab';
|
||||
import { SceneShareTab } from './types';
|
||||
import { ModalSceneObjectLike, SceneShareTab } from './types';
|
||||
|
||||
interface ShareModalState extends SceneObjectState {
|
||||
dashboardRef: SceneObjectRef<DashboardScene>;
|
||||
@ -23,7 +24,7 @@ interface ShareModalState extends SceneObjectState {
|
||||
/**
|
||||
* Used for full dashboard share modal and the panel level share modal
|
||||
*/
|
||||
export class ShareModal extends SceneObjectBase<ShareModalState> {
|
||||
export class ShareModal extends SceneObjectBase<ShareModalState> implements ModalSceneObjectLike {
|
||||
static Component = SharePanelModalRenderer;
|
||||
|
||||
constructor(state: Omit<ShareModalState, 'activeTab'>) {
|
||||
@ -38,10 +39,14 @@ export class ShareModal extends SceneObjectBase<ShareModalState> {
|
||||
private buildTabs() {
|
||||
const { dashboardRef, panelRef } = this.state;
|
||||
|
||||
const tabs: SceneShareTab[] = [new ShareLinkTab({ dashboardRef, panelRef })];
|
||||
const tabs: SceneShareTab[] = [new ShareLinkTab({ dashboardRef, panelRef, modalRef: this.getRef() })];
|
||||
|
||||
if (!panelRef) {
|
||||
tabs.push(new ShareExportTab({ dashboardRef, modalRef: this.getRef() }));
|
||||
}
|
||||
|
||||
if (contextSrv.isSignedIn && config.snapshotEnabled) {
|
||||
tabs.push(new ShareSnapshotTab({ panelRef }));
|
||||
tabs.push(new ShareSnapshotTab({ panelRef, modalRef: this.getRef() }));
|
||||
}
|
||||
|
||||
this.setState({ tabs });
|
||||
@ -74,7 +79,7 @@ export class ShareModal extends SceneObjectBase<ShareModalState> {
|
||||
// }
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
onDismiss = () => {
|
||||
const dashboard = getDashboardSceneFor(this);
|
||||
dashboard.closeModal();
|
||||
};
|
||||
@ -110,7 +115,7 @@ function SharePanelModalRenderer({ model }: SceneComponentProps<ShareModal>) {
|
||||
const currentTab = tabs.find((t) => t.getTabLabel() === activeTab);
|
||||
|
||||
return (
|
||||
<Modal isOpen={true} title={header} onDismiss={model.onClose}>
|
||||
<Modal isOpen={true} title={header} onDismiss={model.onDismiss}>
|
||||
<TabContent>{currentTab && <currentTab.Component model={currentTab} />}</TabContent>
|
||||
</Modal>
|
||||
);
|
||||
|
@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectState, SceneObjectRef, VizPanel } from '@grafana/scenes';
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes';
|
||||
import { t } from 'app/core/internationalization';
|
||||
|
||||
export interface ShareSnapshotTabState extends SceneObjectState {
|
||||
import { SceneShareTabState } from './types';
|
||||
|
||||
export interface ShareSnapshotTabState extends SceneShareTabState {
|
||||
panelRef?: SceneObjectRef<VizPanel>;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,13 @@
|
||||
import { SceneObject, SceneObjectState } from '@grafana/scenes';
|
||||
import { SceneObject, SceneObjectRef, SceneObjectState } from '@grafana/scenes';
|
||||
|
||||
export interface SceneShareTab<T extends SceneObjectState = SceneObjectState> extends SceneObject<T> {
|
||||
export interface ModalSceneObjectLike {
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export interface SceneShareTabState extends SceneObjectState {
|
||||
modalRef?: SceneObjectRef<ModalSceneObjectLike>;
|
||||
}
|
||||
|
||||
export interface SceneShareTab<T extends SceneShareTabState = SceneShareTabState> extends SceneObject<T> {
|
||||
getTabLabel(): string;
|
||||
}
|
||||
|
@ -760,6 +760,7 @@
|
||||
"time-range-description": "Wandelt den aktuellen relativen Zeitbereich in einen absoluten Zeitbereich um"
|
||||
},
|
||||
"export": {
|
||||
"back-button": "",
|
||||
"cancel-button": "Abbrechen",
|
||||
"info-text": "Dieses Dashboard exportieren.",
|
||||
"save-button": "In Datei speichern …",
|
||||
|
@ -760,6 +760,7 @@
|
||||
"time-range-description": "Transforms the current relative time range to an absolute time range"
|
||||
},
|
||||
"export": {
|
||||
"back-button": "Back to export config",
|
||||
"cancel-button": "Cancel",
|
||||
"info-text": "Export this dashboard.",
|
||||
"save-button": "Save to file",
|
||||
|
@ -765,6 +765,7 @@
|
||||
"time-range-description": "Transforma el intervalo de tiempo relativo actual en un intervalo de tiempo absoluto"
|
||||
},
|
||||
"export": {
|
||||
"back-button": "",
|
||||
"cancel-button": "Cancelar",
|
||||
"info-text": "Exporte este panel de control.",
|
||||
"save-button": "Guardar en archivo",
|
||||
|
@ -765,6 +765,7 @@
|
||||
"time-range-description": "Transforme la période temporelle relative actuelle en une période temporelle absolue"
|
||||
},
|
||||
"export": {
|
||||
"back-button": "",
|
||||
"cancel-button": "Annuler",
|
||||
"info-text": "Exporter ce tableau de bord.",
|
||||
"save-button": "Enregistrer dans un fichier",
|
||||
|
@ -760,6 +760,7 @@
|
||||
"time-range-description": "Ŧřäʼnşƒőřmş ŧĥę čūřřęʼnŧ řęľäŧįvę ŧįmę řäʼnģę ŧő äʼn äþşőľūŧę ŧįmę řäʼnģę"
|
||||
},
|
||||
"export": {
|
||||
"back-button": "",
|
||||
"cancel-button": "Cäʼnčęľ",
|
||||
"info-text": "Ēχpőřŧ ŧĥįş đäşĥþőäřđ.",
|
||||
"save-button": "Ŝävę ŧő ƒįľę",
|
||||
|
@ -755,6 +755,7 @@
|
||||
"time-range-description": "将当前相对时间范围转换为绝对时间范围"
|
||||
},
|
||||
"export": {
|
||||
"back-button": "",
|
||||
"cancel-button": "取消",
|
||||
"info-text": "导出此仪表板。",
|
||||
"save-button": "保存至文件",
|
||||
|
Loading…
Reference in New Issue
Block a user