mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ShareDrawer: Share Internally (#89315)
This commit is contained in:
@@ -3082,9 +3082,6 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryButtons\`)", "3"],
|
[0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryButtons\`)", "3"],
|
||||||
[0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryComparison\`)", "4"]
|
[0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryComparison\`)", "4"]
|
||||||
],
|
],
|
||||||
"public/app/features/dashboard-scene/sharing/ShareButton/ShareButton.tsx:5381": [
|
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
|
||||||
],
|
|
||||||
"public/app/features/dashboard-scene/sharing/ShareExportTab.tsx:5381": [
|
"public/app/features/dashboard-scene/sharing/ShareExportTab.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScen
|
|||||||
import { getDashboardUrl } from 'app/features/dashboard-scene/utils/urlBuilders';
|
import { getDashboardUrl } from 'app/features/dashboard-scene/utils/urlBuilders';
|
||||||
import { dispatch } from 'app/store/store';
|
import { dispatch } from 'app/store/store';
|
||||||
|
|
||||||
|
import { ShareLinkConfiguration } from '../../features/dashboard-scene/sharing/ShareButton/utils';
|
||||||
|
import { t } from '../internationalization';
|
||||||
|
|
||||||
import { copyStringToClipboard } from './explore';
|
import { copyStringToClipboard } from './explore';
|
||||||
|
|
||||||
function buildHostUrl() {
|
function buildHostUrl() {
|
||||||
@@ -42,20 +45,21 @@ export const createAndCopyShortLink = async (path: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createAndCopyDashboardShortLink = async (
|
export const createAndCopyShareDashboardLink = async (
|
||||||
dashboard: DashboardScene,
|
dashboard: DashboardScene,
|
||||||
opts: { useAbsoluteTimeRange: boolean; theme: string },
|
opts: ShareLinkConfiguration,
|
||||||
panel?: VizPanel
|
panel?: VizPanel
|
||||||
) => {
|
) => {
|
||||||
const shareUrl = await createDashboardShareUrl(dashboard, opts, panel);
|
const shareUrl = createDashboardShareUrl(dashboard, opts, panel);
|
||||||
await createAndCopyShortLink(shareUrl);
|
if (opts.useShortUrl) {
|
||||||
|
return await createAndCopyShortLink(shareUrl);
|
||||||
|
} else {
|
||||||
|
copyStringToClipboard(shareUrl);
|
||||||
|
dispatch(notifyApp(createSuccessNotification(t('link.share.copy-to-clipboard', 'Link copied to clipboard'))));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createDashboardShareUrl = async (
|
export const createDashboardShareUrl = (dashboard: DashboardScene, opts: ShareLinkConfiguration, panel?: VizPanel) => {
|
||||||
dashboard: DashboardScene,
|
|
||||||
opts: { useAbsoluteTimeRange: boolean; theme: string },
|
|
||||||
panel?: VizPanel
|
|
||||||
) => {
|
|
||||||
const location = locationService.getLocation();
|
const location = locationService.getLocation();
|
||||||
const timeRange = sceneGraph.getTimeRange(panel ?? dashboard);
|
const timeRange = sceneGraph.getTimeRange(panel ?? dashboard);
|
||||||
|
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ export function ToolbarActions({ dashboard }: Props) {
|
|||||||
fill="outline"
|
fill="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
DashboardInteractions.toolbarShareClick();
|
DashboardInteractions.toolbarShareClick();
|
||||||
dashboard.showModal(new ShareModal({ dashboardRef: dashboard.getRef() }));
|
dashboard.showModal(new ShareModal({}));
|
||||||
}}
|
}}
|
||||||
data-testid={selectors.components.NavToolbar.shareDashboard}
|
data-testid={selectors.components.NavToolbar.shareDashboard}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
|||||||
iconClassName: 'share-alt',
|
iconClassName: 'share-alt',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
DashboardInteractions.panelMenuItemClicked('share');
|
DashboardInteractions.panelMenuItemClicked('share');
|
||||||
dashboard.showModal(new ShareModal({ panelRef: panel.getRef(), dashboardRef: dashboard.getRef() }));
|
dashboard.showModal(new ShareModal({ panelRef: panel.getRef() }));
|
||||||
},
|
},
|
||||||
shortcut: 'p s',
|
shortcut: 'p s',
|
||||||
});
|
});
|
||||||
@@ -139,7 +139,6 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
|||||||
dashboard.showModal(
|
dashboard.showModal(
|
||||||
new ShareModal({
|
new ShareModal({
|
||||||
panelRef: panel.getRef(),
|
panelRef: panel.getRef(),
|
||||||
dashboardRef: dashboard.getRef(),
|
|
||||||
activeTab: shareDashboardType.libraryPanel,
|
activeTab: shareDashboardType.libraryPanel,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export function setupKeyboardShortcuts(scene: DashboardScene) {
|
|||||||
keybindings.addBinding({
|
keybindings.addBinding({
|
||||||
key: 'p s',
|
key: 'p s',
|
||||||
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
|
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
|
||||||
scene.showModal(new ShareModal({ panelRef: vizPanel.getRef(), dashboardRef: scene.getRef() }));
|
scene.showModal(new ShareModal({ panelRef: vizPanel.getRef() }));
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -24,16 +24,6 @@ describe('ShareButton', () => {
|
|||||||
expect(await screen.findByTestId(selector.shareLink)).toBeInTheDocument();
|
expect(await screen.findByTestId(selector.shareLink)).toBeInTheDocument();
|
||||||
expect(await screen.findByTestId(selector.arrowMenu)).toBeInTheDocument();
|
expect(await screen.findByTestId(selector.arrowMenu)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call createAndCopyDashboardShortLink when share link clicked', async () => {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
const shareLink = await screen.findByTestId(selector.shareLink);
|
|
||||||
|
|
||||||
await userEvent.click(shareLink);
|
|
||||||
expect(createAndCopyDashboardShortLinkMock).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render menu when arrow button clicked', async () => {
|
it('should render menu when arrow button clicked', async () => {
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useAsyncFn } from 'react-use';
|
|||||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||||
import { VizPanel } from '@grafana/scenes';
|
import { VizPanel } from '@grafana/scenes';
|
||||||
import { Button, ButtonGroup, Dropdown } from '@grafana/ui';
|
import { Button, ButtonGroup, Dropdown } from '@grafana/ui';
|
||||||
|
import { t, Trans } from 'app/core/internationalization';
|
||||||
|
|
||||||
import { DashboardScene } from '../../scene/DashboardScene';
|
import { DashboardScene } from '../../scene/DashboardScene';
|
||||||
import { DashboardInteractions } from '../../utils/interactions';
|
import { DashboardInteractions } from '../../utils/interactions';
|
||||||
@@ -32,8 +33,13 @@ export default function ShareButton({ dashboard, panel }: { dashboard: Dashboard
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup data-testid={newShareButtonSelector.container}>
|
<ButtonGroup data-testid={newShareButtonSelector.container}>
|
||||||
<Button data-testid={newShareButtonSelector.shareLink} size="sm" tooltip="Copy shortened URL" onClick={buildUrl}>
|
<Button
|
||||||
Share
|
data-testid={newShareButtonSelector.shareLink}
|
||||||
|
size="sm"
|
||||||
|
tooltip={t('share-dashboard.share-button-tooltip', 'Copy shortened link')}
|
||||||
|
onClick={buildUrl}
|
||||||
|
>
|
||||||
|
<Trans i18nKey="share-dashboard.share-button">Share</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Dropdown overlay={MenuActions} placement="bottom-end" onVisibleChange={onMenuClick}>
|
<Dropdown overlay={MenuActions} placement="bottom-end" onVisibleChange={onMenuClick}>
|
||||||
<Button data-testid={newShareButtonSelector.arrowMenu} size="sm" icon={isOpen ? 'angle-up' : 'angle-down'} />
|
<Button data-testid={newShareButtonSelector.arrowMenu} size="sm" icon={isOpen ? 'angle-up' : 'angle-down'} />
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||||
@@ -35,14 +34,6 @@ describe('ShareMenu', () => {
|
|||||||
|
|
||||||
expect(await screen.queryByTestId(selector.shareExternally)).not.toBeInTheDocument();
|
expect(await screen.queryByTestId(selector.shareExternally)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
it('should call createAndCopyDashboardShortLink when share internally clicked', async () => {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
const shareLink = await screen.findByTestId(selector.shareInternally);
|
|
||||||
|
|
||||||
await userEvent.click(shareLink);
|
|
||||||
expect(createAndCopyDashboardShortLinkMock).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useAsyncFn } from 'react-use';
|
|
||||||
|
|
||||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||||
import { VizPanel } from '@grafana/scenes';
|
import { VizPanel } from '@grafana/scenes';
|
||||||
@@ -11,15 +10,20 @@ import { DashboardScene } from '../../scene/DashboardScene';
|
|||||||
import { ShareDrawer } from '../ShareDrawer/ShareDrawer';
|
import { ShareDrawer } from '../ShareDrawer/ShareDrawer';
|
||||||
|
|
||||||
import { ShareExternally } from './share-externally/ShareExternally';
|
import { ShareExternally } from './share-externally/ShareExternally';
|
||||||
|
import { ShareInternally } from './share-internally/ShareInternally';
|
||||||
import { ShareSnapshot } from './share-snapshot/ShareSnapshot';
|
import { ShareSnapshot } from './share-snapshot/ShareSnapshot';
|
||||||
import { buildShareUrl } from './utils';
|
|
||||||
|
|
||||||
const newShareButtonSelector = e2eSelectors.pages.Dashboard.DashNav.newShareButton.menu;
|
const newShareButtonSelector = e2eSelectors.pages.Dashboard.DashNav.newShareButton.menu;
|
||||||
|
|
||||||
export default function ShareMenu({ dashboard, panel }: { dashboard: DashboardScene; panel?: VizPanel }) {
|
export default function ShareMenu({ dashboard, panel }: { dashboard: DashboardScene; panel?: VizPanel }) {
|
||||||
const [_, buildUrl] = useAsyncFn(async () => {
|
const onShareInternallyClick = () => {
|
||||||
return await buildShareUrl(dashboard, panel);
|
const drawer = new ShareDrawer({
|
||||||
}, [dashboard]);
|
title: t('share-dashboard.menu.share-internally-title', 'Share internally'),
|
||||||
|
body: new ShareInternally({ panelRef: panel?.getRef() }),
|
||||||
|
});
|
||||||
|
|
||||||
|
dashboard.showModal(drawer);
|
||||||
|
};
|
||||||
|
|
||||||
const onShareExternallyClick = () => {
|
const onShareExternallyClick = () => {
|
||||||
const drawer = new ShareDrawer({
|
const drawer = new ShareDrawer({
|
||||||
@@ -46,7 +50,7 @@ export default function ShareMenu({ dashboard, panel }: { dashboard: DashboardSc
|
|||||||
label={t('share-dashboard.menu.share-internally-title', 'Share internally')}
|
label={t('share-dashboard.menu.share-internally-title', 'Share internally')}
|
||||||
description={t('share-dashboard.menu.share-internally-description', 'Advanced settings')}
|
description={t('share-dashboard.menu.share-internally-description', 'Advanced settings')}
|
||||||
icon="building"
|
icon="building"
|
||||||
onClick={buildUrl}
|
onClick={onShareInternallyClick}
|
||||||
/>
|
/>
|
||||||
{isPublicDashboardsEnabled() && (
|
{isPublicDashboardsEnabled() && (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { SceneComponentProps, SceneObjectRef, VizPanel } from '@grafana/scenes';
|
||||||
|
import { Alert, ClipboardButton, Divider, Label, Spinner, Stack, Switch, Text, useStyles2 } from '@grafana/ui';
|
||||||
|
import { t, Trans } from 'app/core/internationalization';
|
||||||
|
import { ThemePicker } from 'app/features/dashboard/components/ShareModal/ThemePicker';
|
||||||
|
|
||||||
|
import { ShareLinkTab } from '../../ShareLinkTab';
|
||||||
|
import { getShareLinkConfiguration } from '../utils';
|
||||||
|
|
||||||
|
export class ShareInternally extends ShareLinkTab {
|
||||||
|
static Component = ShareInternallyRenderer;
|
||||||
|
|
||||||
|
constructor(state: { panelRef?: SceneObjectRef<VizPanel> }) {
|
||||||
|
const { useAbsoluteTimeRange, useShortUrl, theme } = getShareLinkConfiguration();
|
||||||
|
super({
|
||||||
|
...state,
|
||||||
|
useLockedTime: useAbsoluteTimeRange,
|
||||||
|
useShortUrl,
|
||||||
|
selectedTheme: theme,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShareInternallyRenderer({ model }: SceneComponentProps<ShareInternally>) {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const { useLockedTime, useShortUrl, selectedTheme, isBuildUrlLoading } = model.useState();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Alert severity="info" title={t('link.share.config-alert-title', 'Link configuration')}>
|
||||||
|
<Trans i18nKey="link.share.config-alert-description">
|
||||||
|
Updating your settings will modify the default copy link to include these changes.
|
||||||
|
</Trans>
|
||||||
|
</Alert>
|
||||||
|
<div className={styles.configDescription}>
|
||||||
|
<Text variant="body">
|
||||||
|
<Trans i18nKey="link.share.config-description">
|
||||||
|
Create a personalized, direct link to share your dashboard within your organization, with the following
|
||||||
|
customization settings:
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<Stack justifyContent="space-between">
|
||||||
|
<Stack gap={2} direction="column">
|
||||||
|
<Stack gap={1} direction="column">
|
||||||
|
<Stack gap={1} alignItems="start">
|
||||||
|
<Switch
|
||||||
|
label={t('link.share.time-range-label', 'Lock time range')}
|
||||||
|
id="share-current-time-range"
|
||||||
|
value={useLockedTime}
|
||||||
|
onChange={model.onToggleLockedTime}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
description={t(
|
||||||
|
'link.share.time-range-description',
|
||||||
|
'Change the current relative time range to an absolute time range'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Trans i18nKey="link.share.time-range-label">Lock time range</Trans>
|
||||||
|
</Label>
|
||||||
|
</Stack>
|
||||||
|
<Stack gap={1} alignItems="start">
|
||||||
|
<Switch
|
||||||
|
id="share-short-url"
|
||||||
|
value={useShortUrl}
|
||||||
|
label={t('link.share.short-url-label', 'Shorten link')}
|
||||||
|
onChange={model.onUrlShorten}
|
||||||
|
/>
|
||||||
|
<Label>
|
||||||
|
<Trans i18nKey="link.share.short-url-label">Shorten link</Trans>
|
||||||
|
</Label>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<ThemePicker selectedTheme={selectedTheme} onChange={model.onThemeChange} />
|
||||||
|
</Stack>
|
||||||
|
{isBuildUrlLoading && <Spinner />}
|
||||||
|
</Stack>
|
||||||
|
<Divider spacing={1} />
|
||||||
|
<ClipboardButton
|
||||||
|
icon="link"
|
||||||
|
variant="primary"
|
||||||
|
fill="outline"
|
||||||
|
disabled={isBuildUrlLoading}
|
||||||
|
getText={model.getShareUrl}
|
||||||
|
onClipboardCopy={model.onCopy}
|
||||||
|
className={styles.copyButtonContainer}
|
||||||
|
>
|
||||||
|
<Trans i18nKey="link.share.copy-link-button">Copy link</Trans>
|
||||||
|
</ClipboardButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
configDescription: css({
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}),
|
||||||
|
copyButtonContainer: css({
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -1,16 +1,48 @@
|
|||||||
import { VizPanel } from '@grafana/scenes';
|
import { VizPanel } from '@grafana/scenes';
|
||||||
import { createAndCopyDashboardShortLink } from 'app/core/utils/shortLinks';
|
import { createAndCopyShareDashboardLink } from 'app/core/utils/shortLinks';
|
||||||
import { getTrackingSource } from 'app/features/dashboard/components/ShareModal/utils';
|
import { getTrackingSource } from 'app/features/dashboard/components/ShareModal/utils';
|
||||||
|
|
||||||
|
import store from '../../../../core/store';
|
||||||
import { DashboardScene } from '../../scene/DashboardScene';
|
import { DashboardScene } from '../../scene/DashboardScene';
|
||||||
import { DashboardInteractions } from '../../utils/interactions';
|
import { DashboardInteractions } from '../../utils/interactions';
|
||||||
|
|
||||||
|
export type ShareLinkConfiguration = {
|
||||||
|
useAbsoluteTimeRange: boolean;
|
||||||
|
useShortUrl: boolean;
|
||||||
|
theme: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_SHARE_LINK_CONFIGURATION: ShareLinkConfiguration = {
|
||||||
|
useAbsoluteTimeRange: true,
|
||||||
|
useShortUrl: true,
|
||||||
|
theme: 'current',
|
||||||
|
};
|
||||||
|
|
||||||
export const buildShareUrl = async (dashboard: DashboardScene, panel?: VizPanel) => {
|
export const buildShareUrl = async (dashboard: DashboardScene, panel?: VizPanel) => {
|
||||||
|
const { useAbsoluteTimeRange, useShortUrl, theme } = getShareLinkConfiguration();
|
||||||
DashboardInteractions.shareLinkCopied({
|
DashboardInteractions.shareLinkCopied({
|
||||||
currentTimeRange: true,
|
currentTimeRange: useAbsoluteTimeRange,
|
||||||
theme: 'current',
|
theme,
|
||||||
shortenURL: true,
|
shortenURL: useShortUrl,
|
||||||
shareResource: getTrackingSource(panel?.getRef()),
|
shareResource: getTrackingSource(panel?.getRef()),
|
||||||
});
|
});
|
||||||
return await createAndCopyDashboardShortLink(dashboard, { useAbsoluteTimeRange: true, theme: 'current' });
|
return await createAndCopyShareDashboardLink(dashboard, {
|
||||||
|
useAbsoluteTimeRange,
|
||||||
|
theme,
|
||||||
|
useShortUrl,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SHARE_LINK_CONFIGURATION = 'grafana.dashboard.link.shareConfiguration';
|
||||||
|
// Function that returns share link configuration from local storage
|
||||||
|
export function getShareLinkConfiguration(): ShareLinkConfiguration {
|
||||||
|
if (store.exists(SHARE_LINK_CONFIGURATION)) {
|
||||||
|
return store.getObject(SHARE_LINK_CONFIGURATION) || DEFAULT_SHARE_LINK_CONFIGURATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_SHARE_LINK_CONFIGURATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateShareLinkConfiguration(config: ShareLinkConfiguration) {
|
||||||
|
store.setObject(SHARE_LINK_CONFIGURATION, config);
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { DashboardModel } from 'app/features/dashboard/state';
|
|||||||
import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
|
import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
|
||||||
import { getVariablesCompatibility } from '../utils/getVariablesCompatibility';
|
import { getVariablesCompatibility } from '../utils/getVariablesCompatibility';
|
||||||
import { DashboardInteractions } from '../utils/interactions';
|
import { DashboardInteractions } from '../utils/interactions';
|
||||||
|
import { getDashboardSceneFor } from '../utils/utils';
|
||||||
|
|
||||||
import { SceneShareTabState } from './types';
|
import { SceneShareTabState } from './types';
|
||||||
|
|
||||||
@@ -56,8 +57,8 @@ export class ShareExportTab extends SceneObjectBase<ShareExportTabState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getExportableDashboardJson() {
|
public async getExportableDashboardJson() {
|
||||||
const { dashboardRef, isSharingExternally } = this.state;
|
const { isSharingExternally } = this.state;
|
||||||
const saveModel = transformSceneToSaveModel(dashboardRef.resolve());
|
const saveModel = transformSceneToSaveModel(getDashboardSceneFor(this));
|
||||||
|
|
||||||
const exportable = isSharingExternally
|
const exportable = isSharingExternally
|
||||||
? await this._exporter.makeExportable(
|
? await this._exporter.makeExportable(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
|||||||
|
|
||||||
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||||
import { gridItemToPanel, transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
|
import { gridItemToPanel, transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
|
||||||
|
import { getDashboardSceneFor } from '../utils/utils';
|
||||||
|
|
||||||
import { SceneShareTabState } from './types';
|
import { SceneShareTabState } from './types';
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ export class ShareLibraryPanelTab extends SceneObjectBase<ShareLibraryPanelTabSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ShareLibraryPanelTabRenderer({ model }: SceneComponentProps<ShareLibraryPanelTab>) {
|
function ShareLibraryPanelTabRenderer({ model }: SceneComponentProps<ShareLibraryPanelTab>) {
|
||||||
const { panelRef, dashboardRef, modalRef } = model.useState();
|
const { panelRef, modalRef } = model.useState();
|
||||||
|
|
||||||
if (!panelRef) {
|
if (!panelRef) {
|
||||||
return null;
|
return null;
|
||||||
@@ -36,7 +37,7 @@ function ShareLibraryPanelTabRenderer({ model }: SceneComponentProps<ShareLibrar
|
|||||||
const parent = panel.parent;
|
const parent = panel.parent;
|
||||||
|
|
||||||
if (parent instanceof DashboardGridItem) {
|
if (parent instanceof DashboardGridItem) {
|
||||||
const dashboardScene = dashboardRef.resolve();
|
const dashboardScene = getDashboardSceneFor(model);
|
||||||
const panelJson = gridItemToPanel(parent);
|
const panelJson = gridItemToPanel(parent);
|
||||||
const panelModel = new PanelModel(panelJson);
|
const panelModel = new PanelModel(panelJson);
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import { advanceTo, clear } from 'jest-date-mock';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { dateTime } from '@grafana/data';
|
import { dateTime } from '@grafana/data';
|
||||||
|
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { config, locationService } from '@grafana/runtime';
|
import { config, locationService, setPluginImportUtils } from '@grafana/runtime';
|
||||||
import { SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes';
|
import { SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes';
|
||||||
|
|
||||||
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
|
import { activateFullSceneTree } from '../utils/test-utils';
|
||||||
|
|
||||||
import { ShareLinkTab } from './ShareLinkTab';
|
import { ShareLinkTab } from './ShareLinkTab';
|
||||||
|
|
||||||
@@ -18,6 +20,11 @@ jest.mock('app/core/utils/shortLinks', () => ({
|
|||||||
createShortLink: jest.fn().mockResolvedValue(`http://localhost:3000/goto/shortend-uid`),
|
createShortLink: jest.fn().mockResolvedValue(`http://localhost:3000/goto/shortend-uid`),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
setPluginImportUtils({
|
||||||
|
importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})),
|
||||||
|
getPanelPluginFromCache: (id: string) => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
describe('ShareLinkTab', () => {
|
describe('ShareLinkTab', () => {
|
||||||
const fakeCurrentDate = dateTime('2019-02-11T19:00:00.000Z').toDate();
|
const fakeCurrentDate = dateTime('2019-02-11T19:00:00.000Z').toDate();
|
||||||
|
|
||||||
@@ -48,7 +55,7 @@ describe('ShareLinkTab', () => {
|
|||||||
describe('with disabled locked range range', () => {
|
describe('with disabled locked range range', () => {
|
||||||
it('should generate share url with relative time', async () => {
|
it('should generate share url with relative time', async () => {
|
||||||
const tab = buildAndRenderScenario({});
|
const tab = buildAndRenderScenario({});
|
||||||
act(() => tab.onToggleLockedTime());
|
await act(() => tab.onToggleLockedTime());
|
||||||
|
|
||||||
expect(await screen.findByRole('textbox', { name: 'Link URL' })).toHaveValue(
|
expect(await screen.findByRole('textbox', { name: 'Link URL' })).toHaveValue(
|
||||||
'http://dashboards.grafana.com/grafana/d/dash-1?from=now-6h&to=now&viewPanel=panel-12'
|
'http://dashboards.grafana.com/grafana/d/dash-1?from=now-6h&to=now&viewPanel=panel-12'
|
||||||
@@ -58,7 +65,7 @@ describe('ShareLinkTab', () => {
|
|||||||
|
|
||||||
it('should add theme when specified', async () => {
|
it('should add theme when specified', async () => {
|
||||||
const tab = buildAndRenderScenario({});
|
const tab = buildAndRenderScenario({});
|
||||||
act(() => tab.onThemeChange('light'));
|
await act(() => tab.onThemeChange('light'));
|
||||||
|
|
||||||
expect(await screen.findByRole('textbox', { name: 'Link URL' })).toHaveValue(
|
expect(await screen.findByRole('textbox', { name: 'Link URL' })).toHaveValue(
|
||||||
'http://dashboards.grafana.com/grafana/d/dash-1?from=2019-02-11T13:00:00.000Z&to=2019-02-11T19:00:00.000Z&viewPanel=panel-12&theme=light'
|
'http://dashboards.grafana.com/grafana/d/dash-1?from=2019-02-11T13:00:00.000Z&to=2019-02-11T19:00:00.000Z&viewPanel=panel-12&theme=light'
|
||||||
@@ -97,8 +104,8 @@ function buildAndRenderScenario(options: ScenarioOptions) {
|
|||||||
pluginId: 'table',
|
pluginId: 'table',
|
||||||
key: 'panel-12',
|
key: 'panel-12',
|
||||||
});
|
});
|
||||||
|
const tab = new ShareLinkTab({ panelRef: panel.getRef() });
|
||||||
const dashboard = new DashboardScene({
|
const scene = new DashboardScene({
|
||||||
title: 'hello',
|
title: 'hello',
|
||||||
uid: 'dash-1',
|
uid: 'dash-1',
|
||||||
meta: {
|
meta: {
|
||||||
@@ -117,9 +124,10 @@ function buildAndRenderScenario(options: ScenarioOptions) {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
overlay: tab,
|
||||||
});
|
});
|
||||||
|
|
||||||
const tab = new ShareLinkTab({ dashboardRef: dashboard.getRef(), panelRef: panel.getRef() });
|
activateFullSceneTree(scene);
|
||||||
|
|
||||||
render(<tab.Component model={tab} />);
|
render(<tab.Component model={tab} />);
|
||||||
|
|
||||||
|
|||||||
@@ -13,18 +13,24 @@ import { getTrackingSource, shareDashboardType } from 'app/features/dashboard/co
|
|||||||
|
|
||||||
import { DashboardInteractions } from '../utils/interactions';
|
import { DashboardInteractions } from '../utils/interactions';
|
||||||
import { getDashboardUrl } from '../utils/urlBuilders';
|
import { getDashboardUrl } from '../utils/urlBuilders';
|
||||||
|
import { getDashboardSceneFor } from '../utils/utils';
|
||||||
|
|
||||||
|
import { updateShareLinkConfiguration } from './ShareButton/utils';
|
||||||
import { SceneShareTabState } from './types';
|
import { SceneShareTabState } from './types';
|
||||||
export interface ShareLinkTabState extends SceneShareTabState, ShareOptions {
|
export interface ShareLinkTabState extends SceneShareTabState, ShareOptions {
|
||||||
panelRef?: SceneObjectRef<VizPanel>;
|
panelRef?: SceneObjectRef<VizPanel>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ShareOptions {
|
export interface ShareLinkConfiguration {
|
||||||
useLockedTime: boolean;
|
useLockedTime: boolean;
|
||||||
useShortUrl: boolean;
|
useShortUrl: boolean;
|
||||||
selectedTheme: string;
|
selectedTheme: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShareOptions extends ShareLinkConfiguration {
|
||||||
shareUrl: string;
|
shareUrl: string;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
|
isBuildUrlLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
|
export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
|
||||||
@@ -32,14 +38,15 @@ export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
|
|||||||
|
|
||||||
static Component = ShareLinkTabRenderer;
|
static Component = ShareLinkTabRenderer;
|
||||||
|
|
||||||
constructor(state: Omit<ShareLinkTabState, keyof ShareOptions>) {
|
constructor(state: Partial<ShareLinkTabState>) {
|
||||||
super({
|
super({
|
||||||
...state,
|
...state,
|
||||||
useLockedTime: true,
|
useLockedTime: state.useLockedTime ?? true,
|
||||||
useShortUrl: false,
|
useShortUrl: state.useShortUrl ?? false,
|
||||||
selectedTheme: 'current',
|
selectedTheme: state.selectedTheme ?? 'current',
|
||||||
shareUrl: '',
|
shareUrl: '',
|
||||||
imageUrl: '',
|
imageUrl: '',
|
||||||
|
isBuildUrlLoading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addActivationHandler(() => {
|
this.addActivationHandler(() => {
|
||||||
@@ -48,12 +55,13 @@ export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async buildUrl() {
|
async buildUrl() {
|
||||||
const { panelRef, dashboardRef, useLockedTime: useAbsoluteTimeRange, useShortUrl, selectedTheme } = this.state;
|
this.setState({ isBuildUrlLoading: true });
|
||||||
const dashboard = dashboardRef.resolve();
|
const { panelRef, useLockedTime: useAbsoluteTimeRange, useShortUrl, selectedTheme } = this.state;
|
||||||
|
const dashboard = getDashboardSceneFor(this);
|
||||||
const panel = panelRef?.resolve();
|
const panel = panelRef?.resolve();
|
||||||
|
|
||||||
const opts = { useAbsoluteTimeRange, theme: selectedTheme };
|
const opts = { useAbsoluteTimeRange, theme: selectedTheme, useShortUrl };
|
||||||
let shareUrl = await createDashboardShareUrl(dashboard, opts, panel);
|
let shareUrl = createDashboardShareUrl(dashboard, opts, panel);
|
||||||
|
|
||||||
if (useShortUrl) {
|
if (useShortUrl) {
|
||||||
shareUrl = await createShortLink(shareUrl);
|
shareUrl = await createShortLink(shareUrl);
|
||||||
@@ -81,26 +89,43 @@ export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
|
|||||||
timeZone: getRenderTimeZone(timeRange.getTimeZone()),
|
timeZone: getRenderTimeZone(timeRange.getTimeZone()),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({ shareUrl, imageUrl });
|
this.setState({ shareUrl, imageUrl, isBuildUrlLoading: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTabLabel() {
|
public getTabLabel() {
|
||||||
return t('share-modal.tab-title.link', 'Link');
|
return t('share-modal.tab-title.link', 'Link');
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggleLockedTime = () => {
|
onToggleLockedTime = async () => {
|
||||||
this.setState({ useLockedTime: !this.state.useLockedTime });
|
const useLockedTime = !this.state.useLockedTime;
|
||||||
this.buildUrl();
|
updateShareLinkConfiguration({
|
||||||
|
useAbsoluteTimeRange: useLockedTime,
|
||||||
|
useShortUrl: this.state.useShortUrl,
|
||||||
|
theme: this.state.selectedTheme,
|
||||||
|
});
|
||||||
|
this.setState({ useLockedTime });
|
||||||
|
await this.buildUrl();
|
||||||
};
|
};
|
||||||
|
|
||||||
onUrlShorten = () => {
|
onUrlShorten = async () => {
|
||||||
this.setState({ useShortUrl: !this.state.useShortUrl });
|
const useShortUrl = !this.state.useShortUrl;
|
||||||
this.buildUrl();
|
this.setState({ useShortUrl });
|
||||||
|
updateShareLinkConfiguration({
|
||||||
|
useShortUrl,
|
||||||
|
useAbsoluteTimeRange: this.state.useLockedTime,
|
||||||
|
theme: this.state.selectedTheme,
|
||||||
|
});
|
||||||
|
await this.buildUrl();
|
||||||
};
|
};
|
||||||
|
|
||||||
onThemeChange = (value: string) => {
|
onThemeChange = async (value: string) => {
|
||||||
this.setState({ selectedTheme: value });
|
this.setState({ selectedTheme: value });
|
||||||
this.buildUrl();
|
updateShareLinkConfiguration({
|
||||||
|
theme: value,
|
||||||
|
useShortUrl: this.state.useShortUrl,
|
||||||
|
useAbsoluteTimeRange: this.state.useLockedTime,
|
||||||
|
});
|
||||||
|
await this.buildUrl();
|
||||||
};
|
};
|
||||||
|
|
||||||
getShareUrl = () => {
|
getShareUrl = () => {
|
||||||
@@ -119,9 +144,9 @@ export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
|
|||||||
|
|
||||||
function ShareLinkTabRenderer({ model }: SceneComponentProps<ShareLinkTab>) {
|
function ShareLinkTabRenderer({ model }: SceneComponentProps<ShareLinkTab>) {
|
||||||
const state = model.useState();
|
const state = model.useState();
|
||||||
const { panelRef, dashboardRef } = state;
|
const { panelRef } = state;
|
||||||
|
|
||||||
const dashboard = dashboardRef.resolve();
|
const dashboard = getDashboardSceneFor(model);
|
||||||
const panel = panelRef?.resolve();
|
const panel = panelRef?.resolve();
|
||||||
|
|
||||||
const timeRange = sceneGraph.getTimeRange(panel ?? dashboard);
|
const timeRange = sceneGraph.getTimeRange(panel ?? dashboard);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { t } from 'app/core/internationalization';
|
|||||||
import { isPublicDashboardsEnabled } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
import { isPublicDashboardsEnabled } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
||||||
|
|
||||||
import { getTrackingSource } from '../../dashboard/components/ShareModal/utils';
|
import { getTrackingSource } from '../../dashboard/components/ShareModal/utils';
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
|
||||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
import { DashboardInteractions } from '../utils/interactions';
|
import { DashboardInteractions } from '../utils/interactions';
|
||||||
import { getDashboardSceneFor } from '../utils/utils';
|
import { getDashboardSceneFor } from '../utils/utils';
|
||||||
@@ -22,7 +21,6 @@ import { SharePublicDashboardTab } from './public-dashboards/SharePublicDashboar
|
|||||||
import { ModalSceneObjectLike, SceneShareTab, SceneShareTabState } from './types';
|
import { ModalSceneObjectLike, SceneShareTab, SceneShareTabState } from './types';
|
||||||
|
|
||||||
interface ShareModalState extends SceneObjectState {
|
interface ShareModalState extends SceneObjectState {
|
||||||
dashboardRef: SceneObjectRef<DashboardScene>;
|
|
||||||
panelRef?: SceneObjectRef<VizPanel>;
|
panelRef?: SceneObjectRef<VizPanel>;
|
||||||
tabs?: SceneShareTab[];
|
tabs?: SceneShareTab[];
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
@@ -51,36 +49,36 @@ export class ShareModal extends SceneObjectBase<ShareModalState> implements Moda
|
|||||||
}
|
}
|
||||||
|
|
||||||
private buildTabs() {
|
private buildTabs() {
|
||||||
const { dashboardRef, panelRef } = this.state;
|
const { panelRef } = this.state;
|
||||||
const modalRef = this.getRef();
|
const modalRef = this.getRef();
|
||||||
|
|
||||||
const tabs: SceneShareTab[] = [new ShareLinkTab({ dashboardRef, panelRef, modalRef })];
|
const tabs: SceneShareTab[] = [new ShareLinkTab({ panelRef, modalRef })];
|
||||||
const dashboard = getDashboardSceneFor(this);
|
const dashboard = getDashboardSceneFor(this);
|
||||||
|
|
||||||
if (!panelRef) {
|
if (!panelRef) {
|
||||||
tabs.push(new ShareExportTab({ dashboardRef, modalRef }));
|
tabs.push(new ShareExportTab({ modalRef }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contextSrv.isSignedIn && config.snapshotEnabled && dashboard.canEditDashboard()) {
|
if (contextSrv.isSignedIn && config.snapshotEnabled && dashboard.canEditDashboard()) {
|
||||||
tabs.push(new ShareSnapshotTab({ panelRef, dashboardRef, modalRef }));
|
tabs.push(new ShareSnapshotTab({ panelRef, dashboardRef: dashboard.getRef(), modalRef }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (panelRef) {
|
if (panelRef) {
|
||||||
tabs.push(new SharePanelEmbedTab({ panelRef, dashboardRef }));
|
tabs.push(new SharePanelEmbedTab({ panelRef }));
|
||||||
const panel = panelRef.resolve();
|
const panel = panelRef.resolve();
|
||||||
const isLibraryPanel = panel.parent instanceof LibraryVizPanel;
|
const isLibraryPanel = panel.parent instanceof LibraryVizPanel;
|
||||||
if (panel instanceof VizPanel) {
|
if (panel instanceof VizPanel) {
|
||||||
if (!isLibraryPanel) {
|
if (!isLibraryPanel) {
|
||||||
tabs.push(new ShareLibraryPanelTab({ panelRef, dashboardRef, modalRef }));
|
tabs.push(new ShareLibraryPanelTab({ panelRef, modalRef }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!panelRef) {
|
if (!panelRef) {
|
||||||
tabs.push(...customDashboardTabs.map((Tab) => new Tab({ dashboardRef, modalRef })));
|
tabs.push(...customDashboardTabs.map((Tab) => new Tab({ modalRef })));
|
||||||
|
|
||||||
if (isPublicDashboardsEnabled()) {
|
if (isPublicDashboardsEnabled()) {
|
||||||
tabs.push(new SharePublicDashboardTab({ dashboardRef, modalRef }));
|
tabs.push(new SharePublicDashboardTab({ modalRef }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { buildParams, shareDashboardType } from 'app/features/dashboard/componen
|
|||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
||||||
import { getDashboardUrl } from '../utils/urlBuilders';
|
import { getDashboardUrl } from '../utils/urlBuilders';
|
||||||
import { getPanelIdForVizPanel } from '../utils/utils';
|
import { getDashboardSceneFor, getPanelIdForVizPanel } from '../utils/utils';
|
||||||
|
|
||||||
import { SceneShareTabState } from './types';
|
import { SceneShareTabState } from './types';
|
||||||
|
|
||||||
@@ -31,10 +31,10 @@ export class SharePanelEmbedTab extends SceneObjectBase<SharePanelEmbedTabState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SharePanelEmbedTabRenderer({ model }: SceneComponentProps<SharePanelEmbedTab>) {
|
function SharePanelEmbedTabRenderer({ model }: SceneComponentProps<SharePanelEmbedTab>) {
|
||||||
const { panelRef, dashboardRef } = model.useState();
|
const { panelRef } = model.useState();
|
||||||
const p = panelRef.resolve();
|
const p = panelRef.resolve();
|
||||||
|
|
||||||
const dash = dashboardRef.resolve();
|
const dash = getDashboardSceneFor(model);
|
||||||
const { uid: dashUid } = dash.useState();
|
const { uid: dashUid } = dash.useState();
|
||||||
const id = getPanelIdForVizPanel(p);
|
const id = getPanelIdForVizPanel(p);
|
||||||
const timeRangeState = sceneGraph.getTimeRange(p);
|
const timeRangeState = sceneGraph.getTimeRange(p);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { getTrackingSource, shareDashboardType } from 'app/features/dashboard/co
|
|||||||
import { getDashboardSnapshotSrv, SnapshotSharingOptions } from 'app/features/dashboard/services/SnapshotSrv';
|
import { getDashboardSnapshotSrv, SnapshotSharingOptions } from 'app/features/dashboard/services/SnapshotSrv';
|
||||||
import { dispatch } from 'app/store/store';
|
import { dispatch } from 'app/store/store';
|
||||||
|
|
||||||
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { transformSceneToSaveModel, trimDashboardForSnapshot } from '../serialization/transformSceneToSaveModel';
|
import { transformSceneToSaveModel, trimDashboardForSnapshot } from '../serialization/transformSceneToSaveModel';
|
||||||
import { DashboardInteractions } from '../utils/interactions';
|
import { DashboardInteractions } from '../utils/interactions';
|
||||||
|
|
||||||
@@ -48,10 +49,10 @@ const getDefaultExpireOption = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface ShareSnapshotTabState extends SceneShareTabState {
|
export interface ShareSnapshotTabState extends SceneShareTabState {
|
||||||
|
dashboardRef: SceneObjectRef<DashboardScene>;
|
||||||
panelRef?: SceneObjectRef<VizPanel>;
|
panelRef?: SceneObjectRef<VizPanel>;
|
||||||
snapshotName: string;
|
snapshotName: string;
|
||||||
selectedExpireOption: SelectableValue<number>;
|
selectedExpireOption: SelectableValue<number>;
|
||||||
|
|
||||||
snapshotSharingOptions?: SnapshotSharingOptions;
|
snapshotSharingOptions?: SnapshotSharingOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { ConfigPublicDashboardBase } from 'app/features/dashboard/components/Sha
|
|||||||
import { PublicDashboard } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
import { PublicDashboard } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
||||||
import { AccessControlAction } from 'app/types';
|
import { AccessControlAction } from 'app/types';
|
||||||
|
|
||||||
|
import { shareDashboardType } from '../../../dashboard/components/ShareModal/utils';
|
||||||
|
import { getDashboardSceneFor } from '../../utils/utils';
|
||||||
import { ShareModal } from '../ShareModal';
|
import { ShareModal } from '../ShareModal';
|
||||||
|
|
||||||
import { ConfirmModal } from './ConfirmModal';
|
import { ConfirmModal } from './ConfirmModal';
|
||||||
@@ -25,8 +27,8 @@ export function ConfigPublicDashboard({ model, publicDashboard, isGetLoading }:
|
|||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const hasWritePermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPublicWrite);
|
const hasWritePermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPublicWrite);
|
||||||
const { dashboardRef } = model.useState();
|
|
||||||
const dashboard = dashboardRef.resolve();
|
const dashboard = getDashboardSceneFor(model);
|
||||||
const { isDirty } = dashboard.useState();
|
const { isDirty } = dashboard.useState();
|
||||||
const [deletePublicDashboard] = useDeletePublicDashboardMutation();
|
const [deletePublicDashboard] = useDeletePublicDashboardMutation();
|
||||||
const hasTemplateVariables = (dashboard.state.$variables?.state.variables.length ?? 0) > 0;
|
const hasTemplateVariables = (dashboard.state.$variables?.state.variables.length ?? 0) > 0;
|
||||||
@@ -52,7 +54,7 @@ export function ConfigPublicDashboard({ model, publicDashboard, isGetLoading }:
|
|||||||
</p>
|
</p>
|
||||||
),
|
),
|
||||||
onDismiss: () => {
|
onDismiss: () => {
|
||||||
dashboard.showModal(new ShareModal({ dashboardRef, activeTab: 'Public Dashboard' }));
|
dashboard.showModal(new ShareModal({ activeTab: shareDashboardType.publicDashboard }));
|
||||||
},
|
},
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
deletePublicDashboard({ dashboard, dashboardUid: dashboard.state.uid!, uid: publicDashboard!.uid });
|
deletePublicDashboard({ dashboard, dashboardUid: dashboard.state.uid!, uid: publicDashboard!.uid });
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import React from 'react';
|
|||||||
import { SceneComponentProps } from '@grafana/scenes';
|
import { SceneComponentProps } from '@grafana/scenes';
|
||||||
import { CreatePublicDashboardBase } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/CreatePublicDashboard';
|
import { CreatePublicDashboardBase } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/CreatePublicDashboard';
|
||||||
|
|
||||||
|
import { getDashboardSceneFor } from '../../utils/utils';
|
||||||
|
|
||||||
import { SharePublicDashboardTab } from './SharePublicDashboardTab';
|
import { SharePublicDashboardTab } from './SharePublicDashboardTab';
|
||||||
import { useUnsupportedDatasources } from './hooks';
|
import { useUnsupportedDatasources } from './hooks';
|
||||||
|
|
||||||
export function CreatePublicDashboard({ model }: SceneComponentProps<SharePublicDashboardTab>) {
|
export function CreatePublicDashboard({ model }: SceneComponentProps<SharePublicDashboardTab>) {
|
||||||
const { dashboardRef } = model.useState();
|
const dashboard = getDashboardSceneFor(model);
|
||||||
const dashboard = dashboardRef.resolve();
|
|
||||||
const unsupportedDataSources = useUnsupportedDatasources(dashboard);
|
const unsupportedDataSources = useUnsupportedDatasources(dashboard);
|
||||||
const hasTemplateVariables = (dashboard.state.$variables?.state.variables.length ?? 0) > 0;
|
const hasTemplateVariables = (dashboard.state.$variables?.state.variables.length ?? 0) > 0;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Loader } from 'app/features/dashboard/components/ShareModal/SharePublic
|
|||||||
import { publicDashboardPersisted } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
import { publicDashboardPersisted } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
||||||
import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
|
import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
|
||||||
|
|
||||||
|
import { getDashboardSceneFor } from '../../utils/utils';
|
||||||
import { SceneShareTabState } from '../types';
|
import { SceneShareTabState } from '../types';
|
||||||
|
|
||||||
import { ConfigPublicDashboard } from './ConfigPublicDashboard';
|
import { ConfigPublicDashboard } from './ConfigPublicDashboard';
|
||||||
@@ -23,7 +24,7 @@ export class SharePublicDashboardTab extends SceneObjectBase<SceneShareTabState>
|
|||||||
|
|
||||||
function SharePublicDashboardTabRenderer({ model }: SceneComponentProps<SharePublicDashboardTab>) {
|
function SharePublicDashboardTabRenderer({ model }: SceneComponentProps<SharePublicDashboardTab>) {
|
||||||
const { data: publicDashboard, isLoading: isGetLoading } = useGetPublicDashboardQuery(
|
const { data: publicDashboard, isLoading: isGetLoading } = useGetPublicDashboardQuery(
|
||||||
model.state.dashboardRef.resolve().state.uid!
|
getDashboardSceneFor(model).state.uid!
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import { SceneObject, SceneObjectRef, SceneObjectState } from '@grafana/scenes';
|
import { SceneObject, SceneObjectRef, SceneObjectState } from '@grafana/scenes';
|
||||||
|
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
|
||||||
|
|
||||||
export interface ModalSceneObjectLike {
|
export interface ModalSceneObjectLike {
|
||||||
onDismiss: () => void;
|
onDismiss: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SceneShareTabState extends SceneObjectState {
|
export interface SceneShareTabState extends SceneObjectState {
|
||||||
dashboardRef: SceneObjectRef<DashboardScene>;
|
|
||||||
modalRef?: SceneObjectRef<ModalSceneObjectLike>;
|
modalRef?: SceneObjectRef<ModalSceneObjectLike>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -797,6 +797,18 @@
|
|||||||
"success": "Library panel saved"
|
"success": "Library panel saved"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"link": {
|
||||||
|
"share": {
|
||||||
|
"config-alert-description": "Updating your settings will modify the default copy link to include these changes.",
|
||||||
|
"config-alert-title": "Link configuration",
|
||||||
|
"config-description": "Create a personalized, direct link to share your dashboard within your organization, with the following customization settings:",
|
||||||
|
"copy-link-button": "Copy link",
|
||||||
|
"copy-to-clipboard": "Link copied to clipboard",
|
||||||
|
"short-url-label": "Shorten link",
|
||||||
|
"time-range-description": "Change the current relative time range to an absolute time range",
|
||||||
|
"time-range-label": "Lock time range"
|
||||||
|
}
|
||||||
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"error": {
|
"error": {
|
||||||
"blocked": "You have exceeded the number of login attempts for this user. Please try again later.",
|
"blocked": "You have exceeded the number of login attempts for this user. Please try again later.",
|
||||||
@@ -1701,7 +1713,9 @@
|
|||||||
"share-internally-description": "Advanced settings",
|
"share-internally-description": "Advanced settings",
|
||||||
"share-internally-title": "Share internally",
|
"share-internally-title": "Share internally",
|
||||||
"share-snapshot-title": "Share snapshot"
|
"share-snapshot-title": "Share snapshot"
|
||||||
}
|
},
|
||||||
|
"share-button": "Share",
|
||||||
|
"share-button-tooltip": "Copy shortened link"
|
||||||
},
|
},
|
||||||
"share-drawer": {
|
"share-drawer": {
|
||||||
"confirm-action": {
|
"confirm-action": {
|
||||||
|
|||||||
@@ -797,6 +797,18 @@
|
|||||||
"success": "Ŀįþřäřy päʼnęľ şävęđ"
|
"success": "Ŀįþřäřy päʼnęľ şävęđ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"link": {
|
||||||
|
"share": {
|
||||||
|
"config-alert-description": "Ůpđäŧįʼnģ yőūř şęŧŧįʼnģş ŵįľľ mőđįƒy ŧĥę đęƒäūľŧ čőpy ľįʼnĸ ŧő įʼnčľūđę ŧĥęşę čĥäʼnģęş.",
|
||||||
|
"config-alert-title": "Ŀįʼnĸ čőʼnƒįģūřäŧįőʼn",
|
||||||
|
"config-description": "Cřęäŧę ä pęřşőʼnäľįžęđ, đįřęčŧ ľįʼnĸ ŧő şĥäřę yőūř đäşĥþőäřđ ŵįŧĥįʼn yőūř őřģäʼnįžäŧįőʼn, ŵįŧĥ ŧĥę ƒőľľőŵįʼnģ čūşŧőmįžäŧįőʼn şęŧŧįʼnģş:",
|
||||||
|
"copy-link-button": "Cőpy ľįʼnĸ",
|
||||||
|
"copy-to-clipboard": "Ŀįʼnĸ čőpįęđ ŧő čľįpþőäřđ",
|
||||||
|
"short-url-label": "Ŝĥőřŧęʼn ľįʼnĸ",
|
||||||
|
"time-range-description": "Cĥäʼnģę ŧĥę čūřřęʼnŧ řęľäŧįvę ŧįmę řäʼnģę ŧő äʼn äþşőľūŧę ŧįmę řäʼnģę",
|
||||||
|
"time-range-label": "Ŀőčĸ ŧįmę řäʼnģę"
|
||||||
|
}
|
||||||
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"error": {
|
"error": {
|
||||||
"blocked": "Ÿőū ĥävę ęχčęęđęđ ŧĥę ʼnūmþęř őƒ ľőģįʼn äŧŧęmpŧş ƒőř ŧĥįş ūşęř. Pľęäşę ŧřy äģäįʼn ľäŧęř.",
|
"blocked": "Ÿőū ĥävę ęχčęęđęđ ŧĥę ʼnūmþęř őƒ ľőģįʼn äŧŧęmpŧş ƒőř ŧĥįş ūşęř. Pľęäşę ŧřy äģäįʼn ľäŧęř.",
|
||||||
@@ -1701,7 +1713,9 @@
|
|||||||
"share-internally-description": "Åđväʼnčęđ şęŧŧįʼnģş",
|
"share-internally-description": "Åđväʼnčęđ şęŧŧįʼnģş",
|
||||||
"share-internally-title": "Ŝĥäřę įʼnŧęřʼnäľľy",
|
"share-internally-title": "Ŝĥäřę įʼnŧęřʼnäľľy",
|
||||||
"share-snapshot-title": "Ŝĥäřę şʼnäpşĥőŧ"
|
"share-snapshot-title": "Ŝĥäřę şʼnäpşĥőŧ"
|
||||||
}
|
},
|
||||||
|
"share-button": "Ŝĥäřę",
|
||||||
|
"share-button-tooltip": "Cőpy şĥőřŧęʼnęđ ľįʼnĸ"
|
||||||
},
|
},
|
||||||
"share-drawer": {
|
"share-drawer": {
|
||||||
"confirm-action": {
|
"confirm-action": {
|
||||||
|
|||||||
Reference in New Issue
Block a user