289 lines
9.5 KiB
TypeScript
289 lines
9.5 KiB
TypeScript
import useAsyncFn from 'react-use/lib/useAsyncFn';
|
|
|
|
import { SelectableValue } from '@grafana/data';
|
|
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
|
import { getBackendSrv } from '@grafana/runtime';
|
|
import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes';
|
|
import { Button, ClipboardButton, Field, Input, Modal, RadioButtonGroup, Stack } from '@grafana/ui';
|
|
import { notifyApp } from 'app/core/actions';
|
|
import { createSuccessNotification } from 'app/core/copy/appNotification';
|
|
import { t, Trans } from 'app/core/internationalization';
|
|
import { getTrackingSource, shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
|
|
import { getDashboardSnapshotSrv, SnapshotSharingOptions } from 'app/features/dashboard/services/SnapshotSrv';
|
|
import { dispatch } from 'app/store/store';
|
|
|
|
import { DashboardScene } from '../scene/DashboardScene';
|
|
import { transformSceneToSaveModel, trimDashboardForSnapshot } from '../serialization/transformSceneToSaveModel';
|
|
import { DashboardInteractions } from '../utils/interactions';
|
|
|
|
import { SceneShareTabState, ShareView } from './types';
|
|
|
|
const selectors = e2eSelectors.pages.ShareDashboardModal.SnapshotScene;
|
|
|
|
export const getExpireOptions = () => {
|
|
const DEFAULT_EXPIRE_OPTION: SelectableValue<number> = {
|
|
label: t('share-modal.snapshot.expire-week', '1 Week'),
|
|
value: 60 * 60 * 24 * 7,
|
|
};
|
|
|
|
return [
|
|
{
|
|
label: t('share-modal.snapshot.expire-hour', '1 Hour'),
|
|
value: 60 * 60,
|
|
},
|
|
{
|
|
label: t('share-modal.snapshot.expire-day', '1 Day'),
|
|
value: 60 * 60 * 24,
|
|
},
|
|
DEFAULT_EXPIRE_OPTION,
|
|
{
|
|
label: t('share-modal.snapshot.expire-never', `Never`),
|
|
value: 0,
|
|
},
|
|
];
|
|
};
|
|
|
|
const getDefaultExpireOption = () => {
|
|
return getExpireOptions()[2];
|
|
};
|
|
|
|
export interface ShareSnapshotTabState extends SceneShareTabState {
|
|
dashboardRef: SceneObjectRef<DashboardScene>;
|
|
panelRef?: SceneObjectRef<VizPanel>;
|
|
snapshotName: string;
|
|
selectedExpireOption: SelectableValue<number>;
|
|
snapshotSharingOptions?: SnapshotSharingOptions;
|
|
}
|
|
|
|
export class ShareSnapshotTab extends SceneObjectBase<ShareSnapshotTabState> implements ShareView {
|
|
public tabId = shareDashboardType.snapshot;
|
|
static Component = ShareSnapshotTabRenderer;
|
|
|
|
public constructor(
|
|
state: Omit<ShareSnapshotTabState, 'snapshotName' | 'selectedExpireOption' | 'snapshotSharingOptions'>
|
|
) {
|
|
super({
|
|
...state,
|
|
snapshotName: state.dashboardRef.resolve().state.title,
|
|
selectedExpireOption: getDefaultExpireOption(),
|
|
});
|
|
|
|
this.addActivationHandler(() => {
|
|
this._onActivate();
|
|
});
|
|
}
|
|
|
|
private _onActivate() {
|
|
getDashboardSnapshotSrv()
|
|
.getSharingOptions()
|
|
.then((shareOptions) => {
|
|
if (this.isActive) {
|
|
this.setState({
|
|
snapshotSharingOptions: shareOptions,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
public getTabLabel() {
|
|
return t('share-modal.tab-title.snapshot', 'Snapshot');
|
|
}
|
|
|
|
public onSnasphotNameChange = (snapshotName: string) => {
|
|
this.setState({ snapshotName });
|
|
};
|
|
|
|
public onExpireChange = (option: number) => {
|
|
this.setState({
|
|
selectedExpireOption: getExpireOptions().find((o) => o.value === option),
|
|
});
|
|
};
|
|
|
|
private prepareSnapshot() {
|
|
const timeRange = sceneGraph.getTimeRange(this);
|
|
const { dashboardRef, panelRef } = this.state;
|
|
const saveModel = transformSceneToSaveModel(dashboardRef.resolve(), true);
|
|
|
|
return trimDashboardForSnapshot(
|
|
this.state.snapshotName.trim() || '',
|
|
timeRange.state.value,
|
|
saveModel,
|
|
panelRef?.resolve()
|
|
);
|
|
}
|
|
|
|
public onSnapshotCreate = async (external = false) => {
|
|
const { selectedExpireOption } = this.state;
|
|
const snapshot = this.prepareSnapshot();
|
|
|
|
// TODO
|
|
// snapshot.snapshot = {
|
|
// originalUrl: window.location.href,
|
|
// };
|
|
|
|
const cmdData = {
|
|
dashboard: snapshot,
|
|
name: snapshot.title,
|
|
expires: selectedExpireOption?.value,
|
|
external,
|
|
};
|
|
|
|
try {
|
|
const response = await getDashboardSnapshotSrv().create(cmdData);
|
|
dispatch(
|
|
notifyApp(createSuccessNotification(t('snapshot.share.success-creation', 'Your snapshot has been created')))
|
|
);
|
|
return response;
|
|
} finally {
|
|
if (external) {
|
|
DashboardInteractions.publishSnapshotClicked({
|
|
expires: cmdData.expires,
|
|
shareResource: getTrackingSource(this.state.panelRef),
|
|
});
|
|
} else {
|
|
DashboardInteractions.publishSnapshotLocalClicked({
|
|
expires: cmdData.expires,
|
|
shareResource: getTrackingSource(this.state.panelRef),
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
public onSnapshotDelete = async (url: string) => {
|
|
const response = await getBackendSrv().get(url);
|
|
dispatch(
|
|
notifyApp(createSuccessNotification(t('snapshot.share.success-delete', 'Your snapshot has been deleted')))
|
|
);
|
|
return response;
|
|
};
|
|
}
|
|
|
|
function ShareSnapshotTabRenderer({ model }: SceneComponentProps<ShareSnapshotTab>) {
|
|
const { snapshotName, selectedExpireOption, modalRef, snapshotSharingOptions } = model.useState();
|
|
|
|
const [snapshotResult, createSnapshot] = useAsyncFn(async (external = false) => {
|
|
return model.onSnapshotCreate(external);
|
|
});
|
|
|
|
const [deleteSnapshotResult, deleteSnapshot] = useAsyncFn(async (url: string) => {
|
|
return await getBackendSrv().get(url);
|
|
});
|
|
|
|
// If snapshot has been deleted - show message and allow to close modal
|
|
if (deleteSnapshotResult.value) {
|
|
return (
|
|
<Trans i18nKey="share-modal.snapshot.deleted-message">
|
|
The snapshot has been deleted. If you have already accessed it once, then it might take up to an hour before
|
|
before it is removed from browser caches or CDN caches.
|
|
</Trans>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{/* Before snapshot has been created show configuration */}
|
|
{!Boolean(snapshotResult.value) && (
|
|
<>
|
|
<div>
|
|
<p>
|
|
<Trans i18nKey="share-modal.snapshot.info-text-1">
|
|
A snapshot is an instant way to share an interactive dashboard publicly. When created, we strip
|
|
sensitive data like queries (metric, template, and annotation) and panel links, leaving only the visible
|
|
metric data and series names embedded in your dashboard.
|
|
</Trans>
|
|
</p>
|
|
<p>
|
|
<Trans i18nKey="share-modal.snapshot.info-text-2">
|
|
Keep in mind, your snapshot <em>can be viewed by anyone</em> that has the link and can access the URL.
|
|
Share wisely.
|
|
</Trans>
|
|
</p>
|
|
</div>
|
|
|
|
<Field label={t('share-modal.snapshot.name', `Snapshot name`)}>
|
|
<Input
|
|
id="snapshot-name-input"
|
|
width={30}
|
|
defaultValue={snapshotName}
|
|
onBlur={(e) => model.onSnasphotNameChange(e.target.value)}
|
|
/>
|
|
</Field>
|
|
|
|
<Field label={t('share-modal.snapshot.expire', `Expire`)}>
|
|
<RadioButtonGroup<number>
|
|
id="expire-select-input"
|
|
options={getExpireOptions()}
|
|
value={selectedExpireOption?.value}
|
|
onChange={model.onExpireChange}
|
|
/>
|
|
</Field>
|
|
|
|
<Modal.ButtonRow>
|
|
<Button
|
|
variant="secondary"
|
|
onClick={() => {
|
|
modalRef?.resolve().onDismiss();
|
|
}}
|
|
fill="outline"
|
|
>
|
|
<Trans i18nKey="share-modal.snapshot.cancel-button">Cancel</Trans>
|
|
</Button>
|
|
|
|
{snapshotSharingOptions?.externalEnabled && (
|
|
<Button variant="secondary" disabled={snapshotResult.loading} onClick={() => createSnapshot(true)}>
|
|
{snapshotSharingOptions?.externalSnapshotName}
|
|
</Button>
|
|
)}
|
|
<Button
|
|
variant="primary"
|
|
disabled={snapshotResult.loading}
|
|
onClick={() => createSnapshot()}
|
|
data-testid={selectors.PublishSnapshot}
|
|
>
|
|
<Trans i18nKey="share-modal.snapshot.local-button">Publish Snapshot</Trans>
|
|
</Button>
|
|
</Modal.ButtonRow>
|
|
</>
|
|
)}
|
|
|
|
{/* When snapshot has been created - show link and allow copy/deletion */}
|
|
{snapshotResult.value && (
|
|
<Stack direction="column" gap={0}>
|
|
<Field label={t('share-modal.snapshot.url-label', 'Snapshot URL')}>
|
|
<Input
|
|
data-testid={selectors.CopyUrlInput}
|
|
id="snapshot-url-input"
|
|
value={snapshotResult.value.url}
|
|
readOnly
|
|
addonAfter={
|
|
<ClipboardButton
|
|
data-testid={selectors.CopyUrlButton}
|
|
icon="copy"
|
|
variant="primary"
|
|
getText={() => snapshotResult.value!.url}
|
|
>
|
|
<Trans i18nKey="share-modal.snapshot.copy-link-button">Copy</Trans>
|
|
</ClipboardButton>
|
|
}
|
|
/>
|
|
</Field>
|
|
|
|
<div style={{ alignSelf: 'flex-end', padding: '5px' }}>
|
|
<Trans i18nKey="share-modal.snapshot.mistake-message">Did you make a mistake? </Trans>
|
|
<Button
|
|
fill="outline"
|
|
size="md"
|
|
variant="destructive"
|
|
onClick={() => {
|
|
deleteSnapshot(snapshotResult.value!.deleteUrl);
|
|
}}
|
|
>
|
|
<Trans i18nKey="share-modal.snapshot.delete-button">Delete snapshot.</Trans>
|
|
</Button>
|
|
</div>
|
|
</Stack>
|
|
)}
|
|
</>
|
|
);
|
|
}
|