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 (\`./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": [
|
||||
[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 { dispatch } from 'app/store/store';
|
||||
|
||||
import { ShareLinkConfiguration } from '../../features/dashboard-scene/sharing/ShareButton/utils';
|
||||
import { t } from '../internationalization';
|
||||
|
||||
import { copyStringToClipboard } from './explore';
|
||||
|
||||
function buildHostUrl() {
|
||||
@@ -42,20 +45,21 @@ export const createAndCopyShortLink = async (path: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const createAndCopyDashboardShortLink = async (
|
||||
export const createAndCopyShareDashboardLink = async (
|
||||
dashboard: DashboardScene,
|
||||
opts: { useAbsoluteTimeRange: boolean; theme: string },
|
||||
opts: ShareLinkConfiguration,
|
||||
panel?: VizPanel
|
||||
) => {
|
||||
const shareUrl = await createDashboardShareUrl(dashboard, opts, panel);
|
||||
await createAndCopyShortLink(shareUrl);
|
||||
const shareUrl = createDashboardShareUrl(dashboard, opts, panel);
|
||||
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 (
|
||||
dashboard: DashboardScene,
|
||||
opts: { useAbsoluteTimeRange: boolean; theme: string },
|
||||
panel?: VizPanel
|
||||
) => {
|
||||
export const createDashboardShareUrl = (dashboard: DashboardScene, opts: ShareLinkConfiguration, panel?: VizPanel) => {
|
||||
const location = locationService.getLocation();
|
||||
const timeRange = sceneGraph.getTimeRange(panel ?? dashboard);
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@ export function ToolbarActions({ dashboard }: Props) {
|
||||
fill="outline"
|
||||
onClick={() => {
|
||||
DashboardInteractions.toolbarShareClick();
|
||||
dashboard.showModal(new ShareModal({ dashboardRef: dashboard.getRef() }));
|
||||
dashboard.showModal(new ShareModal({}));
|
||||
}}
|
||||
data-testid={selectors.components.NavToolbar.shareDashboard}
|
||||
>
|
||||
|
||||
@@ -84,7 +84,7 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
||||
iconClassName: 'share-alt',
|
||||
onClick: () => {
|
||||
DashboardInteractions.panelMenuItemClicked('share');
|
||||
dashboard.showModal(new ShareModal({ panelRef: panel.getRef(), dashboardRef: dashboard.getRef() }));
|
||||
dashboard.showModal(new ShareModal({ panelRef: panel.getRef() }));
|
||||
},
|
||||
shortcut: 'p s',
|
||||
});
|
||||
@@ -139,7 +139,6 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
||||
dashboard.showModal(
|
||||
new ShareModal({
|
||||
panelRef: panel.getRef(),
|
||||
dashboardRef: dashboard.getRef(),
|
||||
activeTab: shareDashboardType.libraryPanel,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -60,7 +60,7 @@ export function setupKeyboardShortcuts(scene: DashboardScene) {
|
||||
keybindings.addBinding({
|
||||
key: 'p s',
|
||||
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.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 () => {
|
||||
setup();
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useAsyncFn } from 'react-use';
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { VizPanel } from '@grafana/scenes';
|
||||
import { Button, ButtonGroup, Dropdown } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
|
||||
import { DashboardScene } from '../../scene/DashboardScene';
|
||||
import { DashboardInteractions } from '../../utils/interactions';
|
||||
@@ -32,8 +33,13 @@ export default function ShareButton({ dashboard, panel }: { dashboard: Dashboard
|
||||
|
||||
return (
|
||||
<ButtonGroup data-testid={newShareButtonSelector.container}>
|
||||
<Button data-testid={newShareButtonSelector.shareLink} size="sm" tooltip="Copy shortened URL" onClick={buildUrl}>
|
||||
Share
|
||||
<Button
|
||||
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>
|
||||
<Dropdown overlay={MenuActions} placement="bottom-end" onVisibleChange={onMenuClick}>
|
||||
<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 userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
@@ -35,14 +34,6 @@ describe('ShareMenu', () => {
|
||||
|
||||
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() {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { VizPanel } from '@grafana/scenes';
|
||||
@@ -11,15 +10,20 @@ import { DashboardScene } from '../../scene/DashboardScene';
|
||||
import { ShareDrawer } from '../ShareDrawer/ShareDrawer';
|
||||
|
||||
import { ShareExternally } from './share-externally/ShareExternally';
|
||||
import { ShareInternally } from './share-internally/ShareInternally';
|
||||
import { ShareSnapshot } from './share-snapshot/ShareSnapshot';
|
||||
import { buildShareUrl } from './utils';
|
||||
|
||||
const newShareButtonSelector = e2eSelectors.pages.Dashboard.DashNav.newShareButton.menu;
|
||||
|
||||
export default function ShareMenu({ dashboard, panel }: { dashboard: DashboardScene; panel?: VizPanel }) {
|
||||
const [_, buildUrl] = useAsyncFn(async () => {
|
||||
return await buildShareUrl(dashboard, panel);
|
||||
}, [dashboard]);
|
||||
const onShareInternallyClick = () => {
|
||||
const drawer = new ShareDrawer({
|
||||
title: t('share-dashboard.menu.share-internally-title', 'Share internally'),
|
||||
body: new ShareInternally({ panelRef: panel?.getRef() }),
|
||||
});
|
||||
|
||||
dashboard.showModal(drawer);
|
||||
};
|
||||
|
||||
const onShareExternallyClick = () => {
|
||||
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')}
|
||||
description={t('share-dashboard.menu.share-internally-description', 'Advanced settings')}
|
||||
icon="building"
|
||||
onClick={buildUrl}
|
||||
onClick={onShareInternallyClick}
|
||||
/>
|
||||
{isPublicDashboardsEnabled() && (
|
||||
<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 { createAndCopyDashboardShortLink } from 'app/core/utils/shortLinks';
|
||||
import { createAndCopyShareDashboardLink } from 'app/core/utils/shortLinks';
|
||||
import { getTrackingSource } from 'app/features/dashboard/components/ShareModal/utils';
|
||||
|
||||
import store from '../../../../core/store';
|
||||
import { DashboardScene } from '../../scene/DashboardScene';
|
||||
import { DashboardInteractions } from '../../utils/interactions';
|
||||
|
||||
export const buildShareUrl = async (dashboard: DashboardScene, panel?: VizPanel) => {
|
||||
DashboardInteractions.shareLinkCopied({
|
||||
currentTimeRange: true,
|
||||
export type ShareLinkConfiguration = {
|
||||
useAbsoluteTimeRange: boolean;
|
||||
useShortUrl: boolean;
|
||||
theme: string;
|
||||
};
|
||||
|
||||
const DEFAULT_SHARE_LINK_CONFIGURATION: ShareLinkConfiguration = {
|
||||
useAbsoluteTimeRange: true,
|
||||
useShortUrl: true,
|
||||
theme: 'current',
|
||||
shortenURL: true,
|
||||
};
|
||||
|
||||
export const buildShareUrl = async (dashboard: DashboardScene, panel?: VizPanel) => {
|
||||
const { useAbsoluteTimeRange, useShortUrl, theme } = getShareLinkConfiguration();
|
||||
DashboardInteractions.shareLinkCopied({
|
||||
currentTimeRange: useAbsoluteTimeRange,
|
||||
theme,
|
||||
shortenURL: useShortUrl,
|
||||
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 { getVariablesCompatibility } from '../utils/getVariablesCompatibility';
|
||||
import { DashboardInteractions } from '../utils/interactions';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { SceneShareTabState } from './types';
|
||||
|
||||
@@ -56,8 +57,8 @@ export class ShareExportTab extends SceneObjectBase<ShareExportTabState> {
|
||||
}
|
||||
|
||||
public async getExportableDashboardJson() {
|
||||
const { dashboardRef, isSharingExternally } = this.state;
|
||||
const saveModel = transformSceneToSaveModel(dashboardRef.resolve());
|
||||
const { isSharingExternally } = this.state;
|
||||
const saveModel = transformSceneToSaveModel(getDashboardSceneFor(this));
|
||||
|
||||
const exportable = isSharingExternally
|
||||
? await this._exporter.makeExportable(
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
|
||||
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||
import { gridItemToPanel, transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { SceneShareTabState } from './types';
|
||||
|
||||
@@ -26,7 +27,7 @@ export class ShareLibraryPanelTab extends SceneObjectBase<ShareLibraryPanelTabSt
|
||||
}
|
||||
|
||||
function ShareLibraryPanelTabRenderer({ model }: SceneComponentProps<ShareLibraryPanelTab>) {
|
||||
const { panelRef, dashboardRef, modalRef } = model.useState();
|
||||
const { panelRef, modalRef } = model.useState();
|
||||
|
||||
if (!panelRef) {
|
||||
return null;
|
||||
@@ -36,7 +37,7 @@ function ShareLibraryPanelTabRenderer({ model }: SceneComponentProps<ShareLibrar
|
||||
const parent = panel.parent;
|
||||
|
||||
if (parent instanceof DashboardGridItem) {
|
||||
const dashboardScene = dashboardRef.resolve();
|
||||
const dashboardScene = getDashboardSceneFor(model);
|
||||
const panelJson = gridItemToPanel(parent);
|
||||
const panelModel = new PanelModel(panelJson);
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ import { advanceTo, clear } from 'jest-date-mock';
|
||||
import React from 'react';
|
||||
|
||||
import { dateTime } from '@grafana/data';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
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 { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { activateFullSceneTree } from '../utils/test-utils';
|
||||
|
||||
import { ShareLinkTab } from './ShareLinkTab';
|
||||
|
||||
@@ -18,6 +20,11 @@ jest.mock('app/core/utils/shortLinks', () => ({
|
||||
createShortLink: jest.fn().mockResolvedValue(`http://localhost:3000/goto/shortend-uid`),
|
||||
}));
|
||||
|
||||
setPluginImportUtils({
|
||||
importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})),
|
||||
getPanelPluginFromCache: (id: string) => undefined,
|
||||
});
|
||||
|
||||
describe('ShareLinkTab', () => {
|
||||
const fakeCurrentDate = dateTime('2019-02-11T19:00:00.000Z').toDate();
|
||||
|
||||
@@ -48,7 +55,7 @@ describe('ShareLinkTab', () => {
|
||||
describe('with disabled locked range range', () => {
|
||||
it('should generate share url with relative time', async () => {
|
||||
const tab = buildAndRenderScenario({});
|
||||
act(() => tab.onToggleLockedTime());
|
||||
await act(() => tab.onToggleLockedTime());
|
||||
|
||||
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'
|
||||
@@ -58,7 +65,7 @@ describe('ShareLinkTab', () => {
|
||||
|
||||
it('should add theme when specified', async () => {
|
||||
const tab = buildAndRenderScenario({});
|
||||
act(() => tab.onThemeChange('light'));
|
||||
await act(() => tab.onThemeChange('light'));
|
||||
|
||||
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'
|
||||
@@ -97,8 +104,8 @@ function buildAndRenderScenario(options: ScenarioOptions) {
|
||||
pluginId: 'table',
|
||||
key: 'panel-12',
|
||||
});
|
||||
|
||||
const dashboard = new DashboardScene({
|
||||
const tab = new ShareLinkTab({ panelRef: panel.getRef() });
|
||||
const scene = new DashboardScene({
|
||||
title: 'hello',
|
||||
uid: 'dash-1',
|
||||
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} />);
|
||||
|
||||
|
||||
@@ -13,18 +13,24 @@ import { getTrackingSource, shareDashboardType } from 'app/features/dashboard/co
|
||||
|
||||
import { DashboardInteractions } from '../utils/interactions';
|
||||
import { getDashboardUrl } from '../utils/urlBuilders';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { updateShareLinkConfiguration } from './ShareButton/utils';
|
||||
import { SceneShareTabState } from './types';
|
||||
export interface ShareLinkTabState extends SceneShareTabState, ShareOptions {
|
||||
panelRef?: SceneObjectRef<VizPanel>;
|
||||
}
|
||||
|
||||
interface ShareOptions {
|
||||
export interface ShareLinkConfiguration {
|
||||
useLockedTime: boolean;
|
||||
useShortUrl: boolean;
|
||||
selectedTheme: string;
|
||||
}
|
||||
|
||||
interface ShareOptions extends ShareLinkConfiguration {
|
||||
shareUrl: string;
|
||||
imageUrl: string;
|
||||
isBuildUrlLoading: boolean;
|
||||
}
|
||||
|
||||
export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
|
||||
@@ -32,14 +38,15 @@ export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
|
||||
|
||||
static Component = ShareLinkTabRenderer;
|
||||
|
||||
constructor(state: Omit<ShareLinkTabState, keyof ShareOptions>) {
|
||||
constructor(state: Partial<ShareLinkTabState>) {
|
||||
super({
|
||||
...state,
|
||||
useLockedTime: true,
|
||||
useShortUrl: false,
|
||||
selectedTheme: 'current',
|
||||
useLockedTime: state.useLockedTime ?? true,
|
||||
useShortUrl: state.useShortUrl ?? false,
|
||||
selectedTheme: state.selectedTheme ?? 'current',
|
||||
shareUrl: '',
|
||||
imageUrl: '',
|
||||
isBuildUrlLoading: false,
|
||||
});
|
||||
|
||||
this.addActivationHandler(() => {
|
||||
@@ -48,12 +55,13 @@ export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
|
||||
}
|
||||
|
||||
async buildUrl() {
|
||||
const { panelRef, dashboardRef, useLockedTime: useAbsoluteTimeRange, useShortUrl, selectedTheme } = this.state;
|
||||
const dashboard = dashboardRef.resolve();
|
||||
this.setState({ isBuildUrlLoading: true });
|
||||
const { panelRef, useLockedTime: useAbsoluteTimeRange, useShortUrl, selectedTheme } = this.state;
|
||||
const dashboard = getDashboardSceneFor(this);
|
||||
const panel = panelRef?.resolve();
|
||||
|
||||
const opts = { useAbsoluteTimeRange, theme: selectedTheme };
|
||||
let shareUrl = await createDashboardShareUrl(dashboard, opts, panel);
|
||||
const opts = { useAbsoluteTimeRange, theme: selectedTheme, useShortUrl };
|
||||
let shareUrl = createDashboardShareUrl(dashboard, opts, panel);
|
||||
|
||||
if (useShortUrl) {
|
||||
shareUrl = await createShortLink(shareUrl);
|
||||
@@ -81,26 +89,43 @@ export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
|
||||
timeZone: getRenderTimeZone(timeRange.getTimeZone()),
|
||||
});
|
||||
|
||||
this.setState({ shareUrl, imageUrl });
|
||||
this.setState({ shareUrl, imageUrl, isBuildUrlLoading: false });
|
||||
}
|
||||
|
||||
public getTabLabel() {
|
||||
return t('share-modal.tab-title.link', 'Link');
|
||||
}
|
||||
|
||||
onToggleLockedTime = () => {
|
||||
this.setState({ useLockedTime: !this.state.useLockedTime });
|
||||
this.buildUrl();
|
||||
onToggleLockedTime = async () => {
|
||||
const useLockedTime = !this.state.useLockedTime;
|
||||
updateShareLinkConfiguration({
|
||||
useAbsoluteTimeRange: useLockedTime,
|
||||
useShortUrl: this.state.useShortUrl,
|
||||
theme: this.state.selectedTheme,
|
||||
});
|
||||
this.setState({ useLockedTime });
|
||||
await this.buildUrl();
|
||||
};
|
||||
|
||||
onUrlShorten = () => {
|
||||
this.setState({ useShortUrl: !this.state.useShortUrl });
|
||||
this.buildUrl();
|
||||
onUrlShorten = async () => {
|
||||
const useShortUrl = !this.state.useShortUrl;
|
||||
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.buildUrl();
|
||||
updateShareLinkConfiguration({
|
||||
theme: value,
|
||||
useShortUrl: this.state.useShortUrl,
|
||||
useAbsoluteTimeRange: this.state.useLockedTime,
|
||||
});
|
||||
await this.buildUrl();
|
||||
};
|
||||
|
||||
getShareUrl = () => {
|
||||
@@ -119,9 +144,9 @@ export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
|
||||
|
||||
function ShareLinkTabRenderer({ model }: SceneComponentProps<ShareLinkTab>) {
|
||||
const state = model.useState();
|
||||
const { panelRef, dashboardRef } = state;
|
||||
const { panelRef } = state;
|
||||
|
||||
const dashboard = dashboardRef.resolve();
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
const panel = panelRef?.resolve();
|
||||
|
||||
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 { getTrackingSource } from '../../dashboard/components/ShareModal/utils';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||
import { DashboardInteractions } from '../utils/interactions';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
@@ -22,7 +21,6 @@ import { SharePublicDashboardTab } from './public-dashboards/SharePublicDashboar
|
||||
import { ModalSceneObjectLike, SceneShareTab, SceneShareTabState } from './types';
|
||||
|
||||
interface ShareModalState extends SceneObjectState {
|
||||
dashboardRef: SceneObjectRef<DashboardScene>;
|
||||
panelRef?: SceneObjectRef<VizPanel>;
|
||||
tabs?: SceneShareTab[];
|
||||
activeTab: string;
|
||||
@@ -51,36 +49,36 @@ export class ShareModal extends SceneObjectBase<ShareModalState> implements Moda
|
||||
}
|
||||
|
||||
private buildTabs() {
|
||||
const { dashboardRef, panelRef } = this.state;
|
||||
const { panelRef } = this.state;
|
||||
const modalRef = this.getRef();
|
||||
|
||||
const tabs: SceneShareTab[] = [new ShareLinkTab({ dashboardRef, panelRef, modalRef })];
|
||||
const tabs: SceneShareTab[] = [new ShareLinkTab({ panelRef, modalRef })];
|
||||
const dashboard = getDashboardSceneFor(this);
|
||||
|
||||
if (!panelRef) {
|
||||
tabs.push(new ShareExportTab({ dashboardRef, modalRef }));
|
||||
tabs.push(new ShareExportTab({ modalRef }));
|
||||
}
|
||||
|
||||
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) {
|
||||
tabs.push(new SharePanelEmbedTab({ panelRef, dashboardRef }));
|
||||
tabs.push(new SharePanelEmbedTab({ panelRef }));
|
||||
const panel = panelRef.resolve();
|
||||
const isLibraryPanel = panel.parent instanceof LibraryVizPanel;
|
||||
if (panel instanceof VizPanel) {
|
||||
if (!isLibraryPanel) {
|
||||
tabs.push(new ShareLibraryPanelTab({ panelRef, dashboardRef, modalRef }));
|
||||
tabs.push(new ShareLibraryPanelTab({ panelRef, modalRef }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!panelRef) {
|
||||
tabs.push(...customDashboardTabs.map((Tab) => new Tab({ dashboardRef, modalRef })));
|
||||
tabs.push(...customDashboardTabs.map((Tab) => new Tab({ modalRef })));
|
||||
|
||||
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 { PanelTimeRange } from '../scene/PanelTimeRange';
|
||||
import { getDashboardUrl } from '../utils/urlBuilders';
|
||||
import { getPanelIdForVizPanel } from '../utils/utils';
|
||||
import { getDashboardSceneFor, getPanelIdForVizPanel } from '../utils/utils';
|
||||
|
||||
import { SceneShareTabState } from './types';
|
||||
|
||||
@@ -31,10 +31,10 @@ export class SharePanelEmbedTab extends SceneObjectBase<SharePanelEmbedTabState>
|
||||
}
|
||||
|
||||
function SharePanelEmbedTabRenderer({ model }: SceneComponentProps<SharePanelEmbedTab>) {
|
||||
const { panelRef, dashboardRef } = model.useState();
|
||||
const { panelRef } = model.useState();
|
||||
const p = panelRef.resolve();
|
||||
|
||||
const dash = dashboardRef.resolve();
|
||||
const dash = getDashboardSceneFor(model);
|
||||
const { uid: dashUid } = dash.useState();
|
||||
const id = getPanelIdForVizPanel(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 { dispatch } from 'app/store/store';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { transformSceneToSaveModel, trimDashboardForSnapshot } from '../serialization/transformSceneToSaveModel';
|
||||
import { DashboardInteractions } from '../utils/interactions';
|
||||
|
||||
@@ -48,10 +49,10 @@ const getDefaultExpireOption = () => {
|
||||
};
|
||||
|
||||
export interface ShareSnapshotTabState extends SceneShareTabState {
|
||||
dashboardRef: SceneObjectRef<DashboardScene>;
|
||||
panelRef?: SceneObjectRef<VizPanel>;
|
||||
snapshotName: string;
|
||||
selectedExpireOption: SelectableValue<number>;
|
||||
|
||||
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 { AccessControlAction } from 'app/types';
|
||||
|
||||
import { shareDashboardType } from '../../../dashboard/components/ShareModal/utils';
|
||||
import { getDashboardSceneFor } from '../../utils/utils';
|
||||
import { ShareModal } from '../ShareModal';
|
||||
|
||||
import { ConfirmModal } from './ConfirmModal';
|
||||
@@ -25,8 +27,8 @@ export function ConfigPublicDashboard({ model, publicDashboard, isGetLoading }:
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const hasWritePermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPublicWrite);
|
||||
const { dashboardRef } = model.useState();
|
||||
const dashboard = dashboardRef.resolve();
|
||||
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
const { isDirty } = dashboard.useState();
|
||||
const [deletePublicDashboard] = useDeletePublicDashboardMutation();
|
||||
const hasTemplateVariables = (dashboard.state.$variables?.state.variables.length ?? 0) > 0;
|
||||
@@ -52,7 +54,7 @@ export function ConfigPublicDashboard({ model, publicDashboard, isGetLoading }:
|
||||
</p>
|
||||
),
|
||||
onDismiss: () => {
|
||||
dashboard.showModal(new ShareModal({ dashboardRef, activeTab: 'Public Dashboard' }));
|
||||
dashboard.showModal(new ShareModal({ activeTab: shareDashboardType.publicDashboard }));
|
||||
},
|
||||
onConfirm: () => {
|
||||
deletePublicDashboard({ dashboard, dashboardUid: dashboard.state.uid!, uid: publicDashboard!.uid });
|
||||
|
||||
@@ -3,12 +3,13 @@ import React from 'react';
|
||||
import { SceneComponentProps } from '@grafana/scenes';
|
||||
import { CreatePublicDashboardBase } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/CreatePublicDashboard';
|
||||
|
||||
import { getDashboardSceneFor } from '../../utils/utils';
|
||||
|
||||
import { SharePublicDashboardTab } from './SharePublicDashboardTab';
|
||||
import { useUnsupportedDatasources } from './hooks';
|
||||
|
||||
export function CreatePublicDashboard({ model }: SceneComponentProps<SharePublicDashboardTab>) {
|
||||
const { dashboardRef } = model.useState();
|
||||
const dashboard = dashboardRef.resolve();
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
const unsupportedDataSources = useUnsupportedDatasources(dashboard);
|
||||
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 { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
|
||||
|
||||
import { getDashboardSceneFor } from '../../utils/utils';
|
||||
import { SceneShareTabState } from '../types';
|
||||
|
||||
import { ConfigPublicDashboard } from './ConfigPublicDashboard';
|
||||
@@ -23,7 +24,7 @@ export class SharePublicDashboardTab extends SceneObjectBase<SceneShareTabState>
|
||||
|
||||
function SharePublicDashboardTabRenderer({ model }: SceneComponentProps<SharePublicDashboardTab>) {
|
||||
const { data: publicDashboard, isLoading: isGetLoading } = useGetPublicDashboardQuery(
|
||||
model.state.dashboardRef.resolve().state.uid!
|
||||
getDashboardSceneFor(model).state.uid!
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { SceneObject, SceneObjectRef, SceneObjectState } from '@grafana/scenes';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
|
||||
export interface ModalSceneObjectLike {
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export interface SceneShareTabState extends SceneObjectState {
|
||||
dashboardRef: SceneObjectRef<DashboardScene>;
|
||||
modalRef?: SceneObjectRef<ModalSceneObjectLike>;
|
||||
}
|
||||
|
||||
|
||||
@@ -797,6 +797,18 @@
|
||||
"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": {
|
||||
"error": {
|
||||
"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-title": "Share internally",
|
||||
"share-snapshot-title": "Share snapshot"
|
||||
}
|
||||
},
|
||||
"share-button": "Share",
|
||||
"share-button-tooltip": "Copy shortened link"
|
||||
},
|
||||
"share-drawer": {
|
||||
"confirm-action": {
|
||||
|
||||
@@ -797,6 +797,18 @@
|
||||
"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": {
|
||||
"error": {
|
||||
"blocked": "Ÿőū ĥävę ęχčęęđęđ ŧĥę ʼnūmþęř őƒ ľőģįʼn äŧŧęmpŧş ƒőř ŧĥįş ūşęř. Pľęäşę ŧřy äģäįʼn ľäŧęř.",
|
||||
@@ -1701,7 +1713,9 @@
|
||||
"share-internally-description": "Åđväʼnčęđ şęŧŧįʼnģş",
|
||||
"share-internally-title": "Ŝĥäřę įʼnŧęřʼnäľľy",
|
||||
"share-snapshot-title": "Ŝĥäřę şʼnäpşĥőŧ"
|
||||
}
|
||||
},
|
||||
"share-button": "Ŝĥäřę",
|
||||
"share-button-tooltip": "Cőpy şĥőřŧęʼnęđ ľįʼnĸ"
|
||||
},
|
||||
"share-drawer": {
|
||||
"confirm-action": {
|
||||
|
||||
Reference in New Issue
Block a user