Share: Remove new share drawer feature flag (#111048)

This commit is contained in:
Ezequiel Victorero 2025-09-16 10:57:49 -03:00 committed by GitHub
parent 0baacd8d5a
commit 6811cc1aa9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 240 additions and 804 deletions

View File

@ -56,7 +56,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `newPDFRendering` | New implementation for the dashboard-to-PDF rendering | Yes |
| `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes |
| `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes |
| `newDashboardSharingComponent` | Enables the new sharing drawer design | Yes |
| `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | |
| `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars | Yes |
| `pinNavItems` | Enables pinning of nav items | Yes |

View File

@ -5,7 +5,6 @@ const DASHBOARD_UID = 'ZqZnVvFZz';
test.use({
featureToggles: {
scenes: true,
newDashboardSharingComponent: true,
sharingDashboardImage: true, // Enable the export image feature
kubernetesDashboards: process.env.KUBERNETES_DASHBOARDS === 'true',
},

View File

@ -2,8 +2,8 @@ import { test, expect } from '@grafana/plugin-e2e';
test.use({
featureToggles: {
newDashboardSharingComponent: false,
kubernetesDashboards: process.env.KUBERNETES_DASHBOARDS === 'true',
dashboardScene: false, // this test is for the old sharing modal only used when scenes is turned off
},
});
@ -18,10 +18,10 @@ test.describe(
let dashboardPage = await gotoDashboardPage({ uid: 'ZqZnVvFZz' });
// Open sharing modal
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.shareDashboard).click();
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.DashNav.shareButton).click();
// Select public dashboards tab
await dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('Public Dashboard')).click();
await dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('Public dashboard')).click();
// Create button should be disabled
await expect(
@ -120,10 +120,10 @@ test.describe(
).toBeVisible();
// Open sharing modal
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.shareDashboard).click();
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.DashNav.shareButton).click();
// Select public dashboards tab
await dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('Public Dashboard')).click();
await dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('Public dashboard')).click();
await expect(
dashboardPage.getByGrafanaSelector(selectors.pages.ShareDashboardModal.PublicDashboard.CopyUrlInput)
@ -171,10 +171,10 @@ test.describe(
dashboardPage = await gotoDashboardPage({ uid: 'ZqZnVvFZz' });
// Open sharing modal
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.shareDashboard).click();
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.DashNav.shareButton).click();
// Select public dashboards tab
await dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('Public Dashboard')).click();
await dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('Public dashboard')).click();
// Save url before disabling public dashboard
copyUrlInput = dashboardPage.getByGrafanaSelector(

View File

@ -2,8 +2,8 @@ import { test, expect } from '@grafana/plugin-e2e';
test.use({
featureToggles: {
newDashboardSharingComponent: false,
kubernetesDashboards: process.env.KUBERNETES_DASHBOARDS === 'true',
dashboardScene: false, // this test is for the old sharing modal only used when scenes is turned off
},
});
@ -23,10 +23,10 @@ test.describe(
});
// Open sharing modal
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.shareDashboard).click();
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.DashNav.shareButton).click();
// Select public dashboards tab
await dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('Public Dashboard')).click();
await dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('Public dashboard')).click();
// Warning Alert dashboard cannot be made public because it has template variables
await expect(

View File

@ -3,7 +3,6 @@ import { test, expect } from '@grafana/plugin-e2e';
test.use({
featureToggles: {
scenes: true,
newDashboardSharingComponent: true,
kubernetesDashboards: process.env.KUBERNETES_DASHBOARDS === 'true',
},
});

View File

@ -3,7 +3,6 @@ import { test, expect } from '@grafana/plugin-e2e';
test.use({
featureToggles: {
scenes: true,
newDashboardSharingComponent: true,
kubernetesDashboards: process.env.KUBERNETES_DASHBOARDS === 'true',
},
});

View File

@ -5,7 +5,6 @@ import { SnapshotCreateResponse } from '../../public/app/features/dashboard/serv
test.use({
featureToggles: {
scenes: true,
newDashboardSharingComponent: true,
kubernetesDashboards: process.env.KUBERNETES_DASHBOARDS === 'true',
},
});

View File

@ -4,8 +4,8 @@ const DASHBOARD_UID = 'ZqZnVvFZz';
test.use({
featureToggles: {
newDashboardSharingComponent: false, // Use legacy sharing component for this test
kubernetesDashboards: process.env.KUBERNETES_DASHBOARDS === 'true',
dashboardScene: false, // this test is for the old sharing modal only used when scenes is turned off
},
});
@ -29,7 +29,7 @@ test.describe(
];
// Open the sharing modal
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.shareDashboard).click();
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.DashNav.shareButton).click();
// Select the snapshot tab
await dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('Snapshot')).click();
@ -54,9 +54,6 @@ test.describe(
const snapshotKey = getSnapshotKey(snapshotUrl);
await page.goto(`/dashboard/snapshot/${snapshotKey}`);
// Validate the dashboard controls are rendered
await expect(dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Controls)).toBeVisible();
// Validate the panels are rendered
for (const title of panelsToCheck) {
await expect(dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(title))).toBeVisible();

View File

@ -3,6 +3,7 @@ import { e2e } from '../utils';
describe('Public dashboards', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
cy.setLocalStorage('grafana.featureToggles', 'dashboardScene=false'); // this test is for the old sharing modal only used when scenes is turned off
});
it('Create a public dashboard', () => {

View File

@ -3,6 +3,7 @@ import { e2e } from '../utils';
describe('Create a public dashboard with template variables shows a template variable warning', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
cy.setLocalStorage('grafana.featureToggles', 'dashboardScene=false'); // this test is for the old sharing modal only used when scenes is turned off
});
it('Create a public dashboard with template variables shows a template variable warning', () => {

View File

@ -553,11 +553,6 @@ export interface FeatureToggles {
*/
logsExploreTableDefaultVisualization?: boolean;
/**
* Enables the new sharing drawer design
* @default true
*/
newDashboardSharingComponent?: boolean;
/**
* Enables the new alert list view design
*/
alertingListViewV2?: boolean;

View File

@ -947,14 +947,6 @@ var (
Owner: grafanaObservabilityLogsSquad,
FrontendOnly: true,
},
{
Name: "newDashboardSharingComponent",
Description: "Enables the new sharing drawer design",
Stage: FeatureStageGeneralAvailability,
Owner: grafanaSharingSquad,
FrontendOnly: true,
Expression: "true", // enabled by default
},
{
Name: "alertingListViewV2",
Description: "Enables the new alert list view design",

View File

@ -123,7 +123,6 @@ grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,fa
queryLibrary,privatePreview,@grafana/sharing-squad,false,false,false
savedQueries,preview,@grafana/sharing-squad,false,false,false
logsExploreTableDefaultVisualization,experimental,@grafana/observability-logs,false,false,true
newDashboardSharingComponent,GA,@grafana/sharing-squad,false,false,true
alertingListViewV2,privatePreview,@grafana/alerting-squad,false,false,true
alertingDisableSendAlertsExternal,experimental,@grafana/alerting-squad,false,false,false
preserveDashboardStateWhenNavigating,experimental,@grafana/dashboards-squad,false,false,false

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
123 queryLibrary privatePreview @grafana/sharing-squad false false false
124 savedQueries preview @grafana/sharing-squad false false false
125 logsExploreTableDefaultVisualization experimental @grafana/observability-logs false false true
newDashboardSharingComponent GA @grafana/sharing-squad false false true
126 alertingListViewV2 privatePreview @grafana/alerting-squad false false true
127 alertingDisableSendAlertsExternal experimental @grafana/alerting-squad false false false
128 preserveDashboardStateWhenNavigating experimental @grafana/dashboards-squad false false false

View File

@ -503,10 +503,6 @@ const (
// Sets the logs table as default visualisation in logs explore
FlagLogsExploreTableDefaultVisualization = "logsExploreTableDefaultVisualization"
// FlagNewDashboardSharingComponent
// Enables the new sharing drawer design
FlagNewDashboardSharingComponent = "newDashboardSharingComponent"
// FlagAlertingListViewV2
// Enables the new alert list view design
FlagAlertingListViewV2 = "alertingListViewV2"

View File

@ -2410,7 +2410,8 @@
"metadata": {
"name": "newDashboardSharingComponent",
"resourceVersion": "1753448760331",
"creationTimestamp": "2024-05-03T15:02:18Z"
"creationTimestamp": "2024-05-03T15:02:18Z",
"deletionTimestamp": "2025-09-12T17:27:39Z"
},
"spec": {
"description": "Enables the new sharing drawer design",

View File

@ -1,6 +1,4 @@
import { t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
// Maps the ID of the nav item to a translated phrase to later pass to <Trans />
// Because the navigation content is dynamic (defined in the backend), we can not use
// the normal inline message definition method.
@ -46,9 +44,7 @@ export function getNavTitle(navId: string | undefined) {
case 'reports':
return t('nav.reporting.title', 'Reporting');
case 'dashboards/public':
return config.featureToggles.newDashboardSharingComponent
? t('nav.shared-dashboard.title', 'Shared dashboards')
: t('nav.public.title', 'Public dashboards');
return t('nav.shared-dashboard.title', 'Shared dashboards');
case 'dashboards/recently-deleted':
return t('nav.recently-deleted.title', 'Recently deleted');
case 'dashboards/new':
@ -218,9 +214,7 @@ export function getNavSubTitle(navId: string | undefined) {
'Interactive, publically available, point-in-time representations of dashboards and panels'
);
case 'dashboards/public':
return config.featureToggles.newDashboardSharingComponent
? t('nav.shared-dashboard.subtitle', "Manage your organization's externally shared dashboards")
: undefined;
t('nav.shared-dashboard.subtitle', "Manage your organization's externally shared dashboards");
case 'dashboards/library-panels':
return t('nav.library-panels.subtitle', 'Reusable panels that can be added to multiple dashboards');
case 'dashboards/recently-deleted':

View File

@ -30,11 +30,7 @@ const selectors = e2eSelectors.pages.UserListPage;
const PublicDashboardsTab = ({ view, setView }: { view: TabView | null; setView: (v: TabView | null) => void }) => {
return (
<Tab
label={
config.featureToggles.newDashboardSharingComponent
? t('users-access-list.tabs.shared-dashboard-users-tab-title', 'Shared dashboard users')
: t('users-access-list.tabs.public-dashboard-users-tab-title', 'Public dashboard users')
}
label={t('users-access-list.tabs.shared-dashboard-users-tab-title', 'Shared dashboard users')}
active={view === TabView.PUBLIC_DASHBOARDS}
onChangeTab={() => setView(TabView.PUBLIC_DASHBOARDS)}
data-testid={selectors.tabs.publicDashboardsUsers}

View File

@ -3,7 +3,6 @@ import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { Trans, t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { Button, LoadingPlaceholder, Modal, ModalsController, useStyles2 } from '@grafana/ui';
import {
generatePublicDashboardConfigUrl,
@ -22,11 +21,7 @@ export const DashboardsListModal = ({ email, onDismiss }: { email: string; onDis
<Modal
className={styles.modal}
isOpen
title={
config.featureToggles.newDashboardSharingComponent
? t('public-dashboard-users-access-list.modal.shared-dashboard-modal-title', 'Shared dashboards')
: t('public-dashboard-users-access-list.modal.dashboard-modal-title', 'Public dashboards')
}
title={t('public-dashboard-users-access-list.modal.shared-dashboard-modal-title', 'Shared dashboards')}
onDismiss={onDismiss}
>
{isLoading ? (
@ -47,15 +42,7 @@ export const DashboardsListModal = ({ email, onDismiss }: { email: string; onDis
href={generatePublicDashboardUrl(dash.publicDashboardAccessToken)}
onClick={onDismiss}
>
{config.featureToggles.newDashboardSharingComponent ? (
<Trans i18nKey="public-dashboard-users-access-list.dashboard-modal.external-link">
External link
</Trans>
) : (
<Trans i18nKey="public-dashboard-users-access-list.dashboard-modal.public-dashboard-link">
Public dashboard URL
</Trans>
)}
<Trans i18nKey="public-dashboard-users-access-list.dashboard-modal.external-link">External link</Trans>
</a>
<span className={styles.urlsDivider}>{'•'}</span>
<a
@ -63,15 +50,9 @@ export const DashboardsListModal = ({ email, onDismiss }: { email: string; onDis
href={generatePublicDashboardConfigUrl(dash.dashboardUid, dash.slug)}
onClick={onDismiss}
>
{config.featureToggles.newDashboardSharingComponent ? (
<Trans i18nKey="public-dashboard-users-access-list.dashboard-modal.sharing-setting">
Sharing settings
</Trans>
) : (
<Trans i18nKey="public-dashboard-users-access-list.dashboard-modal.public-dashboard-setting">
Public dashboard settings
</Trans>
)}
<Trans i18nKey="public-dashboard-users-access-list.dashboard-modal.sharing-setting">
Sharing settings
</Trans>
</a>
</div>
<hr className={styles.divider} />

View File

@ -2,7 +2,6 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { Button, Modal, ModalsController, useStyles2 } from '@grafana/ui';
import { SessionUser } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
@ -30,21 +29,12 @@ const DeleteUserModal = ({ user, hideModal }: { user: SessionUser; hideModal: ()
</Trans>
</p>
<p className={styles.description}>
{config.featureToggles.newDashboardSharingComponent ? (
<Trans
i18nKey="public-dashboard-users-access-list.delete-user-shared-dashboards-modal.revoke-user-access-modal-desc-line2"
shouldUnescape
>
This action will immediately revoke {{ email: user.email }}&apos;s access to all shared dashboards.
</Trans>
) : (
<Trans
i18nKey="public-dashboard-users-access-list.delete-user-modal.revoke-user-access-modal-desc-line2"
shouldUnescape
>
This action will immediately revoke {{ email: user.email }}&apos;s access to all public dashboards.
</Trans>
)}
<Trans
i18nKey="public-dashboard-users-access-list.delete-user-shared-dashboards-modal.revoke-user-access-modal-desc-line2"
shouldUnescape
>
This action will immediately revoke {{ email: user.email }}&apos;s access to all shared dashboards.
</Trans>
</p>
<Modal.ButtonRow>
<Button type="button" variant="secondary" onClick={hideModal} fill="outline">

View File

@ -1,4 +1,3 @@
import { config } from '@grafana/runtime';
import { SceneObjectUrlSyncHandler, SceneObjectUrlValues, VizPanel } from '@grafana/scenes';
import { contextSrv } from 'app/core/core';
import { KioskMode } from 'app/types/dashboard';
@ -6,7 +5,6 @@ import { KioskMode } from 'app/types/dashboard';
import { buildPanelEditScene } from '../panel-edit/PanelEditor';
import { createDashboardEditViewFor } from '../settings/utils';
import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer';
import { ShareModal } from '../sharing/ShareModal';
import { findEditPanel, getLibraryPanelBehavior } from '../utils/utils';
import { DashboardScene, DashboardSceneState } from './DashboardScene';
@ -102,13 +100,9 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
if (typeof values.shareView === 'string') {
update.shareView = values.shareView;
update.overlay = config.featureToggles.newDashboardSharingComponent
? new ShareDrawer({
shareView: values.shareView,
})
: new ShareModal({
activeTab: values.shareView,
});
update.overlay = new ShareDrawer({
shareView: values.shareView,
});
} else if (shareView && values.shareView === null) {
update.overlay = undefined;
update.shareView = undefined;

View File

@ -4,7 +4,7 @@ import { TestProvider } from 'test/helpers/TestProvider';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
import { selectors } from '@grafana/e2e-selectors';
import { LocationServiceProvider, config, locationService } from '@grafana/runtime';
import { LocationServiceProvider, locationService } from '@grafana/runtime';
import { SceneQueryRunner, SceneTimeRange, UrlSyncContextProvider, VizPanel } from '@grafana/scenes';
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
import { DashboardMeta } from 'app/types/dashboard';
@ -145,25 +145,14 @@ describe('NavToolbarActions', () => {
});
describe('Given new sharing button', () => {
it('Should show old share button when newDashboardSharingComponent FF is disabled', async () => {
setup();
expect(await screen.findByText('Share')).toBeInTheDocument();
const newShareButton = screen.queryByTestId(selectors.pages.Dashboard.DashNav.newShareButton.container);
expect(newShareButton).not.toBeInTheDocument();
const newExportButton = screen.queryByRole('button', { name: /export dashboard/i });
expect(newExportButton).not.toBeInTheDocument();
});
it('Should show new share button when newDashboardSharingComponent FF is enabled', async () => {
config.featureToggles.newDashboardSharingComponent = true;
it('Should show new share button', async () => {
setup();
expect(await screen.queryByTestId(selectors.pages.Dashboard.DashNav.shareButton)).not.toBeInTheDocument();
const newShareButton = screen.getByTestId(selectors.pages.Dashboard.DashNav.newShareButton.container);
expect(newShareButton).toBeInTheDocument();
});
it('Should show new export button when newDashboardSharingComponent FF is enabled', async () => {
config.featureToggles.newDashboardSharingComponent = true;
it('Should show new export button', async () => {
setup();
const newExportButton = screen.getByRole('button', { name: /export dashboard/i });
expect(newExportButton).toBeInTheDocument();

View File

@ -26,7 +26,6 @@ import { useGetResourceRepositoryView } from 'app/features/provisioning/hooks/us
import { getReadOnlyTooltipText } from 'app/features/provisioning/utils/repository';
import { useSelector } from 'app/types/store';
import { shareDashboardType } from '../../dashboard/components/ShareModal/utils';
import { selectFolderRepository } from '../../provisioning/utils/selectors';
import { PanelEditor, buildPanelEditScene } from '../panel-edit/PanelEditor';
import ExportButton from '../sharing/ExportButton/ExportButton';
@ -47,7 +46,7 @@ interface Props {
}
export const NavToolbarActions = memo<Props>(({ dashboard }) => {
const hasNewToolbar = config.featureToggles.dashboardNewLayouts && config.featureToggles.newDashboardSharingComponent;
const hasNewToolbar = config.featureToggles.dashboardNewLayouts;
return hasNewToolbar ? (
<AppChromeUpdate
@ -322,26 +321,6 @@ export function ToolbarActions({ dashboard }: Props) {
});
const showShareButton = uid && !isEditing && !meta.isSnapshot && !isPlaying;
toolbarActions.push({
group: 'main-buttons',
condition: !config.featureToggles.newDashboardSharingComponent && showShareButton,
render: () => (
<Button
key="share-dashboard-button"
tooltip={t('dashboard.toolbar.share.tooltip', 'Share dashboard')}
size="sm"
className={styles.buttonWithExtraMargin}
fill="outline"
onClick={() => {
DashboardInteractions.toolbarShareClick();
locationService.partial({ shareView: shareDashboardType.link });
}}
data-testid={selectors.components.NavToolbar.shareDashboard}
>
<Trans i18nKey="dashboard.toolbar.share.label">Share</Trans>
</Button>
),
});
toolbarActions.push({
group: 'main-buttons',
@ -358,7 +337,7 @@ export function ToolbarActions({ dashboard }: Props) {
}
key="edit"
className={styles.buttonWithExtraMargin}
variant={config.featureToggles.newDashboardSharingComponent ? 'secondary' : 'primary'}
variant={'secondary'}
size="sm"
data-testid={selectors.components.NavToolbar.editDashboard.editButton}
disabled={isReadOnlyRepo}
@ -391,13 +370,13 @@ export function ToolbarActions({ dashboard }: Props) {
toolbarActions.push({
group: 'new-share-dashboard-buttons',
condition: config.featureToggles.newDashboardSharingComponent && showShareButton,
condition: showShareButton,
render: () => <ExportButton key="new-export-dashboard-button" dashboard={dashboard} />,
});
toolbarActions.push({
group: 'new-share-dashboard-buttons',
condition: config.featureToggles.newDashboardSharingComponent && showShareButton,
condition: showShareButton,
render: () => <ShareButton key="new-share-dashboard-button" dashboard={dashboard} />,
});

View File

@ -534,7 +534,7 @@ describe('panelMenuBehavior', () => {
expect(menu.state.items?.find((i) => i.text === 'Remove')).toBeDefined();
const moreMenu = menu.state.items?.find((i) => i.text === 'More...')?.subMenu;
expect(moreMenu?.find((i) => i.text === 'Duplicate')).toBeDefined();
expect(moreMenu?.find((i) => i.text === 'Create library panel')).toBeDefined();
expect(moreMenu?.find((i) => i.text === 'New library panel')).toBeDefined();
});
it('should only contain explore when embedded', async () => {

View File

@ -35,7 +35,6 @@ import { ShowConfirmModalEvent } from 'app/types/events';
import { PanelInspectDrawer } from '../inspect/PanelInspectDrawer';
import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer';
import { ShareModal } from '../sharing/ShareModal';
import { isRepeatCloneOrChildOf } from '../utils/clone';
import { DashboardInteractions } from '../utils/interactions';
import { getEditPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders';
@ -109,90 +108,79 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
});
}
if (config.featureToggles.newDashboardSharingComponent) {
const subMenu: PanelMenuItem[] = [];
const subMenu: PanelMenuItem[] = [];
subMenu.push({
text: t('share-panel.menu.share-link-title', 'Share link'),
iconClassName: 'link',
shortcut: 'p u',
onClick: () => {
DashboardInteractions.sharingCategoryClicked({
item: shareDashboardType.link,
shareResource: getTrackingSource(panel?.getRef()),
});
const drawer = new ShareDrawer({
shareView: shareDashboardType.link,
panelRef: panel.getRef(),
});
dashboard.showModal(drawer);
},
});
subMenu.push({
text: t('share-panel.menu.share-embed-title', 'Share embed'),
iconClassName: 'arrow',
shortcut: 'p e',
onClick: () => {
DashboardInteractions.sharingCategoryClicked({
item: shareDashboardType.embed,
shareResource: getTrackingSource(panel.getRef()),
});
const drawer = new ShareDrawer({
shareView: shareDashboardType.embed,
panelRef: panel.getRef(),
});
dashboard.showModal(drawer);
},
});
if (
contextSrv.isSignedIn &&
config.snapshotEnabled &&
contextSrv.hasPermission(AccessControlAction.SnapshotsCreate)
) {
subMenu.push({
text: t('share-panel.menu.share-link-title', 'Share link'),
iconClassName: 'link',
shortcut: 'p u',
text: t('share-panel.menu.share-snapshot-title', 'Share snapshot'),
iconClassName: 'camera',
shortcut: 'p s',
onClick: () => {
DashboardInteractions.sharingCategoryClicked({
item: shareDashboardType.link,
shareResource: getTrackingSource(panel?.getRef()),
});
const drawer = new ShareDrawer({
shareView: shareDashboardType.link,
panelRef: panel.getRef(),
});
dashboard.showModal(drawer);
},
});
subMenu.push({
text: t('share-panel.menu.share-embed-title', 'Share embed'),
iconClassName: 'arrow',
shortcut: 'p e',
onClick: () => {
DashboardInteractions.sharingCategoryClicked({
item: shareDashboardType.embed,
item: shareDashboardType.snapshot,
shareResource: getTrackingSource(panel.getRef()),
});
const drawer = new ShareDrawer({
shareView: shareDashboardType.embed,
shareView: shareDashboardType.snapshot,
panelRef: panel.getRef(),
});
dashboard.showModal(drawer);
},
});
if (
contextSrv.isSignedIn &&
config.snapshotEnabled &&
contextSrv.hasPermission(AccessControlAction.SnapshotsCreate)
) {
subMenu.push({
text: t('share-panel.menu.share-snapshot-title', 'Share snapshot'),
iconClassName: 'camera',
shortcut: 'p s',
onClick: () => {
DashboardInteractions.sharingCategoryClicked({
item: shareDashboardType.snapshot,
shareResource: getTrackingSource(panel.getRef()),
});
const drawer = new ShareDrawer({
shareView: shareDashboardType.snapshot,
panelRef: panel.getRef(),
});
dashboard.showModal(drawer);
},
});
}
items.push({
type: 'submenu',
text: t('panel.header-menu.share', 'Share'),
iconClassName: 'share-alt',
subMenu,
onClick: (e) => {
e.preventDefault();
},
});
} else {
items.push({
text: t('panel.header-menu.share', 'Share'),
iconClassName: 'share-alt',
onClick: () => {
dashboard.showModal(new ShareModal({ panelRef: panel.getRef() }));
},
shortcut: 'p s',
});
}
items.push({
type: 'submenu',
text: t('panel.header-menu.share', 'Share'),
iconClassName: 'share-alt',
subMenu,
onClick: (e) => {
e.preventDefault();
},
});
if (dashboard.state.isEditing && !isReadOnlyRepeat && !isEditingPanel) {
moreSubMenu.push({
text: t('panel.header-menu.duplicate', `Duplicate`),
@ -236,32 +224,18 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
},
});
} else {
if (config.featureToggles.newDashboardSharingComponent) {
moreSubMenu.push({
text: t('share-panel.menu.new-library-panel-title', 'New library panel'),
iconClassName: 'plus-square',
onClick: () => {
const drawer = new ShareDrawer({
shareView: shareDashboardType.libraryPanel,
panelRef: panel.getRef(),
});
moreSubMenu.push({
text: t('share-panel.menu.new-library-panel-title', 'New library panel'),
iconClassName: 'plus-square',
onClick: () => {
const drawer = new ShareDrawer({
shareView: shareDashboardType.libraryPanel,
panelRef: panel.getRef(),
});
dashboard.showModal(drawer);
},
});
} else {
moreSubMenu.push({
text: t('panel.header-menu.create-library-panel', `Create library panel`),
onClick: () => {
dashboard.showModal(
new ShareModal({
panelRef: panel.getRef(),
activeTab: shareDashboardType.libraryPanel,
})
);
},
});
}
dashboard.showModal(drawer);
},
});
}
}

View File

@ -10,7 +10,6 @@ import { AccessControlAction } from 'app/types/accessControl';
import { shareDashboardType } from '../../dashboard/components/ShareModal/utils';
import { PanelInspectDrawer } from '../inspect/PanelInspectDrawer';
import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer';
import { ShareModal } from '../sharing/ShareModal';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { findVizPanelByPathId } from '../utils/pathId';
import { getEditPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders';
@ -59,52 +58,43 @@ export function setupKeyboardShortcuts(scene: DashboardScene) {
});
// Panel share
if (config.featureToggles.newDashboardSharingComponent) {
keybindings.addBinding({
key: 'p u',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
const drawer = new ShareDrawer({
shareView: shareDashboardType.link,
panelRef: vizPanel.getRef(),
});
scene.showModal(drawer);
}),
});
keybindings.addBinding({
key: 'p e',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
const drawer = new ShareDrawer({
shareView: shareDashboardType.embed,
panelRef: vizPanel.getRef(),
});
scene.showModal(drawer);
}),
});
if (
contextSrv.isSignedIn &&
config.snapshotEnabled &&
contextSrv.hasPermission(AccessControlAction.SnapshotsCreate)
) {
keybindings.addBinding({
key: 'p s',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
const drawer = new ShareDrawer({
shareView: shareDashboardType.snapshot,
panelRef: vizPanel.getRef(),
});
scene.showModal(drawer);
}),
keybindings.addBinding({
key: 'p u',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
const drawer = new ShareDrawer({
shareView: shareDashboardType.link,
panelRef: vizPanel.getRef(),
});
}
} else {
scene.showModal(drawer);
}),
});
keybindings.addBinding({
key: 'p e',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
const drawer = new ShareDrawer({
shareView: shareDashboardType.embed,
panelRef: vizPanel.getRef(),
});
scene.showModal(drawer);
}),
});
if (
contextSrv.isSignedIn &&
config.snapshotEnabled &&
contextSrv.hasPermission(AccessControlAction.SnapshotsCreate)
) {
keybindings.addBinding({
key: 'p s',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
scene.showModal(new ShareModal({ panelRef: vizPanel.getRef() }));
const drawer = new ShareDrawer({
shareView: shareDashboardType.snapshot,
panelRef: vizPanel.getRef(),
});
scene.showModal(drawer);
}),
});
}

View File

@ -4,7 +4,7 @@ import { render } from 'test/test-utils';
import { getDefaultTimeRange, LoadingState } from '@grafana/data';
import { getPanelPlugin } from '@grafana/data/test';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
import { config, setPluginImportUtils } from '@grafana/runtime';
import { setPluginImportUtils } from '@grafana/runtime';
import {
CustomVariable,
SceneQueryRunner,
@ -31,7 +31,6 @@ setPluginImportUtils({
});
beforeEach(() => {
config.featureToggles.newDashboardSharingComponent = true;
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
jest.spyOn(contextSrv, 'hasRole').mockReturnValue(true);
});

View File

@ -1,5 +1,4 @@
import { t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { SceneComponentProps, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes';
import { LibraryPanel } from '@grafana/schema/dist/esm/index.gen';
import { ShareLibraryPanel } from 'app/features/dashboard/components/ShareModal/ShareLibraryPanel';
@ -22,9 +21,7 @@ export class ShareLibraryPanelTab extends SceneObjectBase<ShareLibraryPanelTabSt
static Component = ShareLibraryPanelTabRenderer;
public getTabLabel() {
return config.featureToggles.newDashboardSharingComponent
? t('share-panel.drawer.new-library-panel-title', 'New library panel')
: t('share-modal.tab-title.library-panel', 'Library panel');
return t('share-panel.drawer.new-library-panel-title', 'New library panel');
}
}

View File

@ -1,141 +0,0 @@
import { ComponentProps } from 'react';
import { t } from '@grafana/i18n';
import { config, locationService } from '@grafana/runtime';
import { SceneComponentProps, SceneObjectBase, SceneObjectRef, SceneObjectState, VizPanel } from '@grafana/scenes';
import { Modal, ModalTabsHeader, TabContent } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { isPublicDashboardsEnabled } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
import { AccessControlAction } from 'app/types/accessControl';
import { getTrackingSource } from '../../dashboard/components/ShareModal/utils';
import { DashboardInteractions } from '../utils/interactions';
import { getDashboardSceneFor, isLibraryPanel } from '../utils/utils';
import { ShareExportTab } from './ShareExportTab';
import { ShareLibraryPanelTab } from './ShareLibraryPanelTab';
import { ShareLinkTab } from './ShareLinkTab';
import { SharePanelEmbedTab } from './SharePanelEmbedTab';
import { ShareSnapshotTab } from './ShareSnapshotTab';
import { SharePublicDashboardTab } from './public-dashboards/SharePublicDashboardTab';
import { ModalSceneObjectLike, SceneShareTab, SceneShareTabState } from './types';
interface ShareModalState extends SceneObjectState {
panelRef?: SceneObjectRef<VizPanel>;
tabs?: SceneShareTab[];
activeTab: string;
}
type customDashboardTabType = new (...args: SceneShareTabState[]) => SceneShareTab;
const customDashboardTabs: customDashboardTabType[] = [];
export function addDashboardShareTab(tab: customDashboardTabType) {
customDashboardTabs.push(tab);
}
/**
* Used for full dashboard share modal and the panel level share modal
*/
export class ShareModal extends SceneObjectBase<ShareModalState> implements ModalSceneObjectLike {
static Component = SharePanelModalRenderer;
constructor(state: Omit<ShareModalState, 'activeTab'> & { activeTab?: string }) {
super({
activeTab: 'link',
...state,
});
this.addActivationHandler(() => this.buildTabs(state.activeTab));
}
private buildTabs(activeTab?: string) {
const { panelRef } = this.state;
const modalRef = this.getRef();
const tabs: SceneShareTab[] = [new ShareLinkTab({ panelRef, modalRef })];
const dashboard = getDashboardSceneFor(this);
if (!panelRef) {
tabs.push(new ShareExportTab({ modalRef }));
}
if (
contextSrv.isSignedIn &&
config.snapshotEnabled &&
contextSrv.hasPermission(AccessControlAction.SnapshotsCreate)
) {
tabs.push(new ShareSnapshotTab({ panelRef, dashboardRef: dashboard.getRef(), modalRef }));
}
if (panelRef) {
tabs.push(new SharePanelEmbedTab({ panelRef }));
const panel = panelRef.resolve();
if (panel instanceof VizPanel) {
if (!isLibraryPanel(panel)) {
tabs.push(new ShareLibraryPanelTab({ panelRef, modalRef }));
}
}
}
if (!panelRef) {
tabs.push(...customDashboardTabs.map((Tab) => new Tab({ modalRef })));
if (isPublicDashboardsEnabled()) {
tabs.push(new SharePublicDashboardTab({ modalRef }));
}
}
const at = tabs.find((t) => t.tabId === activeTab);
this.setState({ activeTab: at?.tabId ?? tabs[0].tabId, tabs });
}
onDismiss = () => {
if (this.state.panelRef) {
const dashboard = getDashboardSceneFor(this);
dashboard.closeModal();
} else {
locationService.partial({ shareView: null });
}
};
onChangeTab: ComponentProps<typeof ModalTabsHeader>['onChangeTab'] = (tab) => {
DashboardInteractions.sharingCategoryClicked({
item: tab.value,
shareResource: getTrackingSource(this.state.panelRef),
});
this.setState({ activeTab: tab.value });
};
}
function SharePanelModalRenderer({ model }: SceneComponentProps<ShareModal>) {
const { panelRef, tabs, activeTab } = model.useState();
const title = panelRef ? t('share-modal.panel.title', 'Share Panel') : t('share-modal.dashboard.title', 'Share');
if (!tabs) {
return;
}
const modalTabs = tabs?.map((tab) => ({
label: tab.getTabLabel(),
value: tab.tabId,
}));
const header = (
<ModalTabsHeader
title={title}
icon="share-alt"
tabs={modalTabs}
activeTab={activeTab}
onChangeTab={model.onChangeTab}
/>
);
const currentTab = tabs.find((t) => t.tabId === activeTab);
return (
<Modal isOpen={true} title={header} onDismiss={model.onDismiss}>
<TabContent>{currentTab && <currentTab.Component model={currentTab} />}</TabContent>
</Modal>
);
}

View File

@ -1,6 +1,5 @@
import { TimeRange } from '@grafana/data';
import { t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes';
import { ShareEmbed } from 'app/features/dashboard/components/ShareModal/ShareEmbed';
import { buildParams, shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
@ -25,9 +24,7 @@ export class SharePanelEmbedTab extends SceneObjectBase<SharePanelEmbedTabState>
}
public getTabLabel() {
return config.featureToggles.newDashboardSharingComponent
? t('share-panel.drawer.share-embed-title', 'Share embed')
: t('share-modal.tab-title.panel-embed', 'Embed');
return t('share-panel.drawer.share-embed-title', 'Share embed');
}
}

View File

@ -1,82 +0,0 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { SceneComponentProps, sceneGraph } from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { useDeletePublicDashboardMutation } from 'app/features/dashboard/api/publicDashboardApi';
import { ConfigPublicDashboardBase } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard';
import { PublicDashboard } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
import { AccessControlAction } from 'app/types/accessControl';
import { shareDashboardType } from '../../../dashboard/components/ShareModal/utils';
import { getDashboardSceneFor } from '../../utils/utils';
import { ShareModal } from '../ShareModal';
import { ConfirmModal } from './ConfirmModal';
import { SharePublicDashboardTab } from './SharePublicDashboardTab';
import { useUnsupportedDatasources } from './hooks';
interface Props extends SceneComponentProps<SharePublicDashboardTab> {
publicDashboard?: PublicDashboard;
isGetLoading?: boolean;
}
export function ConfigPublicDashboard({ model, publicDashboard, isGetLoading }: Props) {
const styles = useStyles2(getStyles);
const hasWritePermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPublicWrite);
const dashboard = getDashboardSceneFor(model);
const { isDirty } = dashboard.useState();
const [deletePublicDashboard] = useDeletePublicDashboardMutation();
const hasTemplateVariables = (dashboard.state.$variables?.state.variables.length ?? 0) > 0;
const unsupportedDataSources = useUnsupportedDatasources(dashboard);
const timeRangeState = sceneGraph.getTimeRange(model);
const timeRange = timeRangeState.useState();
return (
<ConfigPublicDashboardBase
dashboard={dashboard}
publicDashboard={publicDashboard}
unsupportedDatasources={unsupportedDataSources}
onRevoke={() => {
dashboard.showModal(
new ConfirmModal({
isOpen: true,
title: t('dashboard-scene.config-public-dashboard.title.revoke-public-url', 'Revoke public URL'),
icon: 'trash-alt',
confirmText: t(
'dashboard-scene.config-public-dashboard.confirmText.revoke-public-url',
'Revoke public URL'
),
body: (
<p className={styles.description}>
<Trans i18nKey="public-dashboard.config.revoke-body">
Are you sure you want to revoke this URL? The dashboard will no longer be public.
</Trans>
</p>
),
onDismiss: () => {
dashboard.showModal(new ShareModal({ activeTab: shareDashboardType.publicDashboard }));
},
onConfirm: () => {
deletePublicDashboard({ dashboard, dashboardUid: dashboard.state.uid!, uid: publicDashboard!.uid });
dashboard.closeModal();
},
})
);
}}
timeRange={timeRange.value}
showSaveChangesAlert={hasWritePermissions && isDirty}
hasTemplateVariables={hasTemplateVariables}
/>
);
}
const getStyles = (theme: GrafanaTheme2) => ({
description: css({
fontSize: theme.typography.body.fontSize,
}),
});

View File

@ -1,21 +0,0 @@
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 dashboard = getDashboardSceneFor(model);
const unsupportedDataSources = useUnsupportedDatasources(dashboard);
const hasTemplateVariables = (dashboard.state.$variables?.state.variables.length ?? 0) > 0;
return (
<CreatePublicDashboardBase
dashboard={dashboard}
unsupportedDatasources={unsupportedDataSources}
unsupportedTemplateVariables={hasTemplateVariables}
/>
);
}

View File

@ -1,39 +0,0 @@
import { t } from '@grafana/i18n';
import { SceneComponentProps, SceneObjectBase } from '@grafana/scenes';
import { useGetPublicDashboardQuery } from 'app/features/dashboard/api/publicDashboardApi';
import { Loader } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard';
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';
import { CreatePublicDashboard } from './CreatePublicDashboard';
export class SharePublicDashboardTab extends SceneObjectBase<SceneShareTabState> {
public tabId = shareDashboardType.publicDashboard;
static Component = SharePublicDashboardTabRenderer;
public getTabLabel() {
return t('share-modal.tab-title.public-dashboard', 'Public Dashboard');
}
}
function SharePublicDashboardTabRenderer({ model }: SceneComponentProps<SharePublicDashboardTab>) {
const { data: publicDashboard, isLoading: isGetLoading } = useGetPublicDashboardQuery(
getDashboardSceneFor(model).state.uid!
);
return (
<>
{isGetLoading ? (
<Loader />
) : !publicDashboardPersisted(publicDashboard) ? (
<CreatePublicDashboard model={model} />
) : (
<ConfigPublicDashboard model={model} publicDashboard={publicDashboard} isGetLoading={isGetLoading} />
)}
</>
);
}

View File

@ -64,13 +64,10 @@ export const publicDashboardApi = createApi({
},
async onQueryStarted({ dashboard, payload: { share } }, { dispatch, queryFulfilled }) {
const { data } = await queryFulfilled;
let message = t('public-dashboard.sharing.success-creation', 'Dashboard is public!');
if (config.featureToggles.newDashboardSharingComponent) {
message =
share === PublicDashboardShareType.PUBLIC
? t('public-dashboard.public-sharing.success-creation', 'Your dashboard is now publicly accessible')
: t('public-dashboard.email-sharing.success-creation', 'Your dashboard is ready for external sharing');
}
const message =
share === PublicDashboardShareType.PUBLIC
? t('public-dashboard.public-sharing.success-creation', 'Your dashboard is now publicly accessible')
: t('public-dashboard.email-sharing.success-creation', 'Your dashboard is ready for external sharing');
dispatch(notifyApp(createSuccessNotification(message)));
if (dashboard instanceof DashboardScene) {
@ -107,9 +104,7 @@ export const publicDashboardApi = createApi({
dispatch(
notifyApp(
createSuccessNotification(
config.featureToggles.newDashboardSharingComponent
? t('public-dashboard.configuration.success-update', 'Settings have been successfully updated')
: t('public-dashboard.configuration.success-update-old', 'Public dashboard updated!')
t('public-dashboard.configuration.success-update', 'Settings have been successfully updated')
)
)
);
@ -136,12 +131,9 @@ export const publicDashboardApi = createApi({
},
async onQueryStarted({ dashboard, payload: { isEnabled } }, { dispatch, queryFulfilled }) {
const { data } = await queryFulfilled;
let message = t('public-dashboard.configuration.success-update-old', 'Public dashboard updated!');
if (config.featureToggles.newDashboardSharingComponent) {
message = isEnabled
? t('public-dashboard.configuration.success-resume', 'Your dashboard access has been resumed')
: t('public-dashboard.configuration.success-pause', 'Your dashboard access has been paused');
}
const message = isEnabled
? t('public-dashboard.configuration.success-resume', 'Your dashboard access has been resumed')
: t('public-dashboard.configuration.success-pause', 'Your dashboard access has been paused');
dispatch(notifyApp(createSuccessNotification(message)));
if (dashboard instanceof DashboardScene) {
@ -176,20 +168,16 @@ export const publicDashboardApi = createApi({
},
async onQueryStarted({ dashboard, payload: { share } }, { dispatch, queryFulfilled }) {
await queryFulfilled;
let message = t('public-dashboard.configuration.success-update-old', 'Public dashboard updated!');
if (config.featureToggles.newDashboardSharingComponent) {
message =
share === PublicDashboardShareType.PUBLIC
? t(
'public-dashboard.public-sharing.success-share-type-change',
'Dashboard access updated: Anyone with the link can now access'
)
: t(
'public-dashboard.email-sharing.success-share-type-change',
'Dashboard access updated: Only specific people can now access with the link'
);
}
const message =
share === PublicDashboardShareType.PUBLIC
? t(
'public-dashboard.public-sharing.success-share-type-change',
'Dashboard access updated: Anyone with the link can now access'
)
: t(
'public-dashboard.email-sharing.success-share-type-change',
'Dashboard access updated: Only specific people can now access with the link'
);
dispatch(notifyApp(createSuccessNotification(message)));
},
invalidatesTags: (result, error, { payload }) => [
@ -250,9 +238,7 @@ export const publicDashboardApi = createApi({
dispatch(
notifyApp(
createSuccessNotification(
config.featureToggles.newDashboardSharingComponent
? t('public-dashboard.share.success-delete', 'Your dashboard is no longer shareable')
: t('public-dashboard.share.success-delete-old', 'Public dashboard deleted!')
t('public-dashboard.share.success-delete', 'Your dashboard is no longer shareable')
)
)
);

View File

@ -2,8 +2,7 @@ import { FormEvent, useEffect, useState } from 'react';
import { RawTimeRange, TimeRange } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { Button, ClipboardButton, Field, Label, Modal, Stack, Switch, TextArea } from '@grafana/ui';
import { Button, ClipboardButton, Field, Label, Stack, Switch, TextArea } from '@grafana/ui';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { ThemePicker } from './ThemePicker';
@ -104,16 +103,12 @@ export function ShareEmbed({
onChange={onIframeHtmlChange}
/>
</Field>
{config.featureToggles.newDashboardSharingComponent ? (
<Stack gap={1} justifyContent={'start'}>
{clipboardButton}
<Button variant="secondary" fill="outline" onClick={onCancelClick}>
<Trans i18nKey="snapshot.share.cancel-button">Cancel</Trans>
</Button>
</Stack>
) : (
<Modal.ButtonRow>{clipboardButton}</Modal.ButtonRow>
)}
<Stack gap={1} justifyContent={'start'}>
{clipboardButton}
<Button variant="secondary" fill="outline" onClick={onCancelClick}>
<Trans i18nKey="snapshot.share.cancel-button">Cancel</Trans>
</Button>
</Stack>
</>
);
}

View File

@ -1,34 +1,21 @@
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { Trans, t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { Alert } from '@grafana/ui';
const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
export const NoUpsertPermissionsAlert = ({ mode }: { mode: 'create' | 'edit' }) => {
const title = config.featureToggles.newDashboardSharingComponent
? t(
'public-dashboard.share-externally.no-upsert-perm-alert-title',
'You dont have permission to {{ action }} a shared dashboard',
{ action: mode }
)
: t(
'public-dashboard.modal-alerts.no-upsert-perm-alert-title',
'You dont have permission to {{ action }} a public dashboard',
{ action: mode }
);
const title = t(
'public-dashboard.share-externally.no-upsert-perm-alert-title',
'You dont have permission to {{ action }} a shared dashboard',
{ action: mode }
);
return (
<Alert severity="warning" title={title} data-testid={selectors.NoUpsertPermissionsWarningAlert} bottomSpacing={0}>
{config.featureToggles.newDashboardSharingComponent ? (
<Trans i18nKey="public-dashboard.share-externally.no-upsert-perm-alert-desc">
Contact your admin to get permission to {{ action: mode }} shared dashboards
</Trans>
) : (
<Trans i18nKey="public-dashboard.modal-alerts.no-upsert-perm-alert-desc">
Contact your admin to get permission to {{ action: mode }} public dashboards
</Trans>
)}
<Trans i18nKey="public-dashboard.share-externally.no-upsert-perm-alert-desc">
Contact your admin to get permission to {{ action: mode }} shared dashboards
</Trans>
</Alert>
);
};

View File

@ -4,7 +4,6 @@ import cx from 'classnames';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { Trans, t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { Alert, useStyles2 } from '@grafana/ui';
const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
@ -20,23 +19,14 @@ export const UnsupportedDataSourcesAlert = ({ unsupportedDataSources }: { unsupp
bottomSpacing={0}
>
<p className={styles.unsupportedDataSourceDescription}>
{config.featureToggles.newDashboardSharingComponent ? (
<Trans i18nKey="public-dashboard.share-externally.unsupported-data-source-alert-desc">
There are data sources in this dashboard that are unsupported for shared dashboards. Panels that use these
data sources may not function properly: {{ unsupportedDataSources }}.
</Trans>
) : (
<Trans i18nKey="public-dashboard.modal-alerts.unsupported-data-source-alert-desc">
There are data sources in this dashboard that are unsupported for public dashboards. Panels that use these
data sources may not function properly: {{ unsupportedDataSources }}.
</Trans>
)}
<Trans i18nKey="public-dashboard.share-externally.unsupported-data-source-alert-desc">
There are data sources in this dashboard that are unsupported for shared dashboards. Panels that use these
data sources may not function properly: {{ unsupportedDataSources }}.
</Trans>
</p>
<a
href={
config.featureToggles.newDashboardSharingComponent
? 'https://grafana.com/docs/grafana/next/dashboards/share-dashboards-panels/shared-dashboards/#supported-data-sources'
: 'https://grafana.com/docs/grafana/v11.2/dashboards/dashboard-public/#supported-data-sources'
'https://grafana.com/docs/grafana/next/dashboards/share-dashboards-panels/shared-dashboards/#supported-data-sources'
}
target="blank"
className={cx('text-link', styles.unsupportedDataSourceDescription)}

View File

@ -2,6 +2,7 @@ import { PureComponent } from 'react';
import * as React from 'react';
import { isEmptyObject, SelectableValue, VariableRefresh } from '@grafana/data';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { getBackendSrv } from '@grafana/runtime';
import { Button, ClipboardButton, Field, Input, LinkButton, Modal, Select, Spinner, Stack } from '@grafana/ui';
@ -30,6 +31,8 @@ interface State {
sharingButtonText: string;
}
const selectors = e2eSelectors.pages.ShareDashboardModal.SnapshotScene;
export class ShareSnapshot extends PureComponent<Props, State> {
private dashboard: DashboardModel;
private expireOptions: Array<SelectableValue<number>>;
@ -280,7 +283,12 @@ export class ShareSnapshot extends PureComponent<Props, State> {
{sharingButtonText}
</Button>
)}
<Button variant="primary" disabled={isLoading} onClick={this.createSnapshot()}>
<Button
variant="primary"
disabled={isLoading}
onClick={this.createSnapshot()}
data-testid={selectors.PublishSnapshot}
>
<Trans i18nKey="share-modal.snapshot.local-button">Publish Snapshot</Trans>
</Button>
</Modal.ButtonRow>
@ -297,9 +305,15 @@ export class ShareSnapshot extends PureComponent<Props, State> {
<Input
id="snapshot-url-input"
value={snapshotUrl}
data-testid={selectors.CopyUrlInput}
readOnly
addonAfter={
<ClipboardButton icon="copy" variant="primary" getText={this.getSnapshotUrl}>
<ClipboardButton
icon="copy"
variant="primary"
getText={this.getSnapshotUrl}
data-testid={selectors.CopyUrlButton}
>
<Trans i18nKey="share-modal.snapshot.copy-link-button">Copy</Trans>
</ClipboardButton>
}

View File

@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
import { useAsync, useDebounce } from 'react-use';
import { Trans, t } from '@grafana/i18n';
import { config, FetchError, isFetchError } from '@grafana/runtime';
import { FetchError, isFetchError } from '@grafana/runtime';
import { LibraryPanel } from '@grafana/schema/dist/esm/index.gen';
import { Button, Field, Input, Modal, Stack } from '@grafana/ui';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
@ -86,25 +86,14 @@ export const AddLibraryPanelContents = ({
>
<FolderPicker onChange={(uid) => setFolderUid(uid)} value={folderUid} />
</Field>
{config.featureToggles.newDashboardSharingComponent ? (
<Stack gap={1} justifyContent={'start'}>
<Button onClick={onCreate} disabled={invalidInput}>
<Trans i18nKey="share-panel.new-library-panel.create-button">Create library panel</Trans>
</Button>
<Button variant="secondary" onClick={onDismiss} fill="outline">
<Trans i18nKey="share-panel.new-library-panel.cancel-button">Cancel</Trans>
</Button>
</Stack>
) : (
<Modal.ButtonRow>
<Button variant="secondary" onClick={onDismiss} fill="outline">
<Trans i18nKey="library-panel.add-modal.cancel">Cancel</Trans>
</Button>
<Button onClick={onCreate} disabled={invalidInput}>
<Trans i18nKey="library-panel.add-modal.create">Create library panel</Trans>
</Button>
</Modal.ButtonRow>
)}
<Stack gap={1} justifyContent={'start'}>
<Button onClick={onCreate} disabled={invalidInput}>
<Trans i18nKey="share-panel.new-library-panel.create-button">Create library panel</Trans>
</Button>
<Button variant="secondary" onClick={onDismiss} fill="outline">
<Trans i18nKey="share-panel.new-library-panel.cancel-button">Cancel</Trans>
</Button>
</Stack>
</>
);
};

View File

@ -1,7 +1,6 @@
import * as React from 'react';
import { t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { Button, ModalsController, ButtonProps } from '@grafana/ui';
import { useDeletePublicDashboardMutation } from 'app/features/dashboard/api/publicDashboardApi';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
@ -42,9 +41,7 @@ export const DeletePublicDashboardButton = ({
return (
<ModalsController>
{({ showModal, hideModal }) => {
const translatedRevocationButtonText = config.featureToggles.newDashboardSharingComponent
? t('shared-dashboard-list.button.revoke-button-text', 'Revoke access')
: t('public-dashboard-list.button.revoke-button-text', 'Revoke public URL');
const translatedRevocationButtonText = t('shared-dashboard-list.button.revoke-button-text', 'Revoke access');
return (
<Button
aria-label={translatedRevocationButtonText}

View File

@ -2,7 +2,6 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { ConfirmModal, useStyles2 } from '@grafana/ui';
const Body = () => {
@ -10,15 +9,10 @@ const Body = () => {
return (
<p className={styles.description}>
{config.featureToggles.newDashboardSharingComponent
? t(
'shared-dashboard.delete-modal.revoke-body-text',
'Are you sure you want to revoke this access? The dashboard can no longer be shared.'
)
: t(
'public-dashboard.delete-modal.revoke-body-text',
'Are you sure you want to revoke this URL? The dashboard will no longer be public.'
)}
{t(
'shared-dashboard.delete-modal.revoke-body-text',
'Are you sure you want to revoke this access? The dashboard can no longer be shared.'
)}
</p>
);
};
@ -30,9 +24,7 @@ export const DeletePublicDashboardModal = ({
onConfirm: () => void;
onDismiss: () => void;
}) => {
const translatedRevocationModalText = config.featureToggles.newDashboardSharingComponent
? t('shared-dashboard.delete-modal.revoke-title', 'Revoke access')
: t('public-dashboard.delete-modal.revoke-title', 'Revoke public URL');
const translatedRevocationModalText = t('shared-dashboard.delete-modal.revoke-title', 'Revoke access');
return (
<ConfirmModal
isOpen

View File

@ -5,7 +5,7 @@ import { useMedia } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { Trans, t } from '@grafana/i18n';
import { config, reportInteraction } from '@grafana/runtime';
import { reportInteraction } from '@grafana/runtime';
import {
Card,
EmptyState,
@ -57,11 +57,7 @@ const PublicDashboardCard = ({ pd }: { pd: PublicDashboardListResponse }) => {
const CardActions = useMemo(() => (isMobile ? Card.Actions : Card.SecondaryActions), [isMobile]);
const isNewSharingComponentEnabled = config.featureToggles.newDashboardSharingComponent;
const translatedPauseSharingText = isNewSharingComponentEnabled
? t('shared-dashboard-list.toggle.pause-sharing-toggle-text', 'Pause access')
: t('public-dashboard-list.toggle.pause-sharing-toggle-text', 'Pause sharing');
const translatedPauseSharingText = t('shared-dashboard-list.toggle.pause-sharing-toggle-text', 'Pause access');
return (
<Card className={styles.card} href={`/d/${pd.dashboardUid}`}>
<Card.Heading className={styles.heading}>
@ -91,11 +87,7 @@ const PublicDashboardCard = ({ pd }: { pd: PublicDashboardListResponse }) => {
color={theme.colors.warning.text}
href={generatePublicDashboardUrl(pd.accessToken)}
key="public-dashboard-url"
tooltip={
isNewSharingComponentEnabled
? t('shared-dashboard-list.button.view-button-tooltip', 'View shared dashboard')
: t('public-dashboard-list.button.view-button-tooltip', 'View public dashboard')
}
tooltip={t('shared-dashboard-list.button.view-button-tooltip', 'View shared dashboard')}
data-testid={selectors.ListItem.linkButton}
/>
<LinkButton
@ -105,11 +97,7 @@ const PublicDashboardCard = ({ pd }: { pd: PublicDashboardListResponse }) => {
color={theme.colors.warning.text}
href={generatePublicDashboardConfigUrl(pd.dashboardUid, pd.slug)}
key="public-dashboard-config-url"
tooltip={
isNewSharingComponentEnabled
? t('shared-dashboard-list.button.config-button-tooltip', 'Configure shared dashboard')
: t('public-dashboard-list.button.config-button-tooltip', 'Configure public dashboard')
}
tooltip={t('shared-dashboard-list.button.config-button-tooltip', 'Configure shared dashboard')}
data-testid={selectors.ListItem.configButton}
/>
{hasWritePermissions && (
@ -118,11 +106,7 @@ const PublicDashboardCard = ({ pd }: { pd: PublicDashboardListResponse }) => {
icon="trash-alt"
variant="secondary"
publicDashboard={pd}
tooltip={
isNewSharingComponentEnabled
? t('shared-dashboard-list.button.revoke-button-tooltip', 'Revoke access')
: t('public-dashboard-list.button.revoke-button-tooltip', 'Revoke public dashboard URL')
}
tooltip={t('shared-dashboard-list.button.revoke-button-tooltip', 'Revoke access')}
loader={<Spinner />}
data-testid={selectors.ListItem.trashcanButton}
/>
@ -144,43 +128,23 @@ export const PublicDashboardListTable = () => {
{!isLoading && !isError && !!paginatedPublicDashboards && (
<div>
{paginatedPublicDashboards.publicDashboards.length === 0 ? (
config.featureToggles.newDashboardSharingComponent ? (
<EmptyState
variant="call-to-action"
message={t(
'shared-dashboard-list.empty-state.message',
"You haven't created any shared dashboards yet"
)}
>
<Trans i18nKey="shared-dashboard-list.empty-state.more-info">
Create a shared dashboard from any existing dashboard through the <b>Share</b> modal.{' '}
<TextLink
external
href="https://grafana.com/docs/grafana/latest/dashboards/share-dashboards-panels/shared-dashboards"
>
Learn more
</TextLink>
</Trans>
</EmptyState>
) : (
<EmptyState
variant="call-to-action"
message={t(
'public-dashboard-list.empty-state.message',
"You haven't created any public dashboards yet"
)}
>
<Trans i18nKey="public-dashboard-list.empty-state.more-info">
Create a public dashboard from any existing dashboard through the <b>Share</b> modal.{' '}
<TextLink
external
href="https://grafana.com/docs/grafana/latest/dashboards/dashboard-public/#make-a-dashboard-public"
>
Learn more
</TextLink>
</Trans>
</EmptyState>
)
<EmptyState
variant="call-to-action"
message={t(
'shared-dashboard-list.empty-state.message',
"You haven't created any shared dashboards yet"
)}
>
<Trans i18nKey="shared-dashboard-list.empty-state.more-info">
Create a shared dashboard from any existing dashboard through the <b>Share</b> modal.{' '}
<TextLink
external
href="https://grafana.com/docs/grafana/latest/dashboards/share-dashboards-panels/shared-dashboards"
>
Learn more
</TextLink>
</Trans>
</EmptyState>
) : (
<>
<ul className={styles.list}>

View File

@ -5407,10 +5407,6 @@
"save-dashboard-short": "Save",
"save-library-panel": "Save library panel",
"settings": "Dashboard settings",
"share": {
"label": "Share",
"tooltip": "Share dashboard"
},
"share-button": "Share",
"unlink-library-panel": "Unlink library panel",
"unmark-favorite": "Unmark as favorite"
@ -5663,14 +5659,6 @@
}
}
},
"config-public-dashboard": {
"confirmText": {
"revoke-public-url": "Revoke public URL"
},
"title": {
"revoke-public-url": "Revoke public URL"
}
},
"constant-variable-form": {
"constant-options": "Constant options",
"label-value": "Value",
@ -9273,8 +9261,6 @@
},
"library-panel": {
"add-modal": {
"cancel": "Cancel",
"create": "Create library panel",
"error": "Library panel with this name already exists",
"folder": "Save in folder",
"folder-description": "Library panel permissions are derived from the folder permissions",
@ -10343,9 +10329,6 @@
"profiles": {
"title": "Profiles"
},
"public": {
"title": "Public dashboards"
},
"recently-deleted": {
"subtitle": "Any items listed here for more than 30 days will be automatically deleted.",
"title": "Recently deleted"
@ -11765,7 +11748,6 @@
"email-share-type-option-label": "Only specified people",
"pause-sharing-dashboard-label": "Pause sharing dashboard",
"public-share-type-option-label": "Anyone with a link",
"revoke-body": "Are you sure you want to revoke this URL? The dashboard will no longer be public.",
"revoke-public-URL-button": "Revoke public URL",
"revoke-public-URL-button-title": "Revoke public URL",
"settings-title": "Settings"
@ -11779,7 +11761,6 @@
"success-pause": "Your dashboard access has been paused",
"success-resume": "Your dashboard access has been resumed",
"success-update": "Settings have been successfully updated",
"success-update-old": "Public dashboard updated!",
"time-range-label": "Time range",
"time-range-tooltip": "The shared dashboard uses the default time range settings of the dashboard"
},
@ -11788,10 +11769,6 @@
"unsupported-features-desc": "Currently, we dont support template variables or frontend data sources",
"welcome-title": "Welcome to public dashboards!"
},
"delete-modal": {
"revoke-body-text": "Are you sure you want to revoke this URL? The dashboard will no longer be public.",
"revoke-title": "Revoke public URL"
},
"email-sharing": {
"accept-button": "Accept",
"alert-text": "Sharing dashboards by email is billed per user for the duration of the 30-day token, regardless of how many dashboards are shared. Billing stops after 30 days unless you renew the token.",
@ -11821,11 +11798,8 @@
"success-share-type-change": "Dashboard access updated: Only specific people can now access with the link"
},
"modal-alerts": {
"no-upsert-perm-alert-desc": "Contact your admin to get permission to {{action}} public dashboards",
"no-upsert-perm-alert-title": "You dont have permission to {{ action }} a public dashboard",
"save-dashboard-changes-alert-title": "Please save your dashboard changes before updating the public configuration",
"unsupport-data-source-alert-readmore-link": "Read more about supported data sources",
"unsupported-data-source-alert-desc": "There are data sources in this dashboard that are unsupported for public dashboards. Panels that use these data sources may not function properly: {{unsupportedDataSources}}.",
"unsupported-data-source-alert-title": "Unsupported data sources",
"unsupported-template-variable-alert-desc": "This public dashboard may not work since it uses template variables",
"unsupported-template-variable-alert-title": "Template variables are not supported"
@ -11859,8 +11833,7 @@
"time-range-text": "Time range = "
},
"share": {
"success-delete": "Your dashboard is no longer shareable",
"success-delete-old": "Public dashboard deleted!"
"success-delete": "Your dashboard is no longer shareable"
},
"share-configuration": {
"share-type-label": "Link access"
@ -11879,24 +11852,6 @@
"revoke-access-button": "Revoke access",
"revoke-access-description": "Are you sure you want to revoke this access? The dashboard can no longer be shared.",
"unsupported-data-source-alert-desc": "There are data sources in this dashboard that are unsupported for shared dashboards. Panels that use these data sources may not function properly: {{unsupportedDataSources}}."
},
"sharing": {
"success-creation": "Dashboard is public!"
}
},
"public-dashboard-list": {
"button": {
"config-button-tooltip": "Configure public dashboard",
"revoke-button-text": "Revoke public URL",
"revoke-button-tooltip": "Revoke public dashboard URL",
"view-button-tooltip": "View public dashboard"
},
"empty-state": {
"message": "You haven't created any public dashboards yet",
"more-info": "Create a public dashboard from any existing dashboard through the <1>Share</1> modal. <4>Learn more</4>"
},
"toggle": {
"pause-sharing-toggle-text": "Pause sharing"
}
},
"public-dashboard-users-access-list": {
@ -11904,8 +11859,6 @@
"external-link": "External link",
"loading-text": "Loading...",
"open-dashboard-list-text": "Open dashboards list",
"public-dashboard-link": "Public dashboard URL",
"public-dashboard-setting": "Public dashboard settings",
"sharing-setting": "Sharing settings"
},
"delete-user-modal": {
@ -11913,14 +11866,12 @@
"delete-user-cancel-button": "Cancel",
"delete-user-revoke-access-button": "Revoke access",
"revoke-access-title": "Revoke access",
"revoke-user-access-modal-desc-line1": "Are you sure you want to revoke access for {{email}}?",
"revoke-user-access-modal-desc-line2": "This action will immediately revoke {{email}}&apos;s access to all public dashboards."
"revoke-user-access-modal-desc-line1": "Are you sure you want to revoke access for {{email}}?"
},
"delete-user-shared-dashboards-modal": {
"revoke-user-access-modal-desc-line2": "This action will immediately revoke {{email}}&apos;s access to all shared dashboards."
},
"modal": {
"dashboard-modal-title": "Public dashboards",
"shared-dashboard-modal-title": "Shared dashboards"
},
"table-body": {
@ -12422,8 +12373,6 @@
"export": "Export",
"library-panel": "Library panel",
"link": "Link",
"panel-embed": "Embed",
"public-dashboard": "Public Dashboard",
"public-dashboard-title": "Public dashboard",
"snapshot": "Snapshot"
},
@ -14019,7 +13968,6 @@
},
"users-access-list": {
"tabs": {
"public-dashboard-users-tab-title": "Public dashboard users",
"shared-dashboard-users-tab-title": "Shared dashboard users"
}
},