From d1434fad3a68bc1d2b49725be31e45526e6ad349 Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Fri, 3 May 2024 12:02:18 -0300 Subject: [PATCH] ShareModal: Share link redesign under `newDashboardSharingComponent` FF (#87011) --- .../feature-toggles/index.md | 1 + .../src/types/featureToggles.gen.ts | 1 + .../src/selectors/pages.ts | 9 +++ pkg/services/featuremgmt/registry.go | 7 ++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 + pkg/services/featuremgmt/toggles_gen.json | 15 +++- public/app/core/utils/shortLinks.test.ts | 4 +- public/app/core/utils/shortLinks.ts | 57 ++++++++++++++- .../scene/NavToolbarActions.test.tsx | 23 +++++- .../scene/NavToolbarActions.tsx | 12 ++- .../sharing/ShareButton/ShareButton.test.tsx | 73 +++++++++++++++++++ .../sharing/ShareButton/ShareButton.tsx | 42 +++++++++++ .../sharing/ShareButton/ShareMenu.test.tsx | 57 +++++++++++++++ .../sharing/ShareButton/ShareMenu.tsx | 28 +++++++ .../sharing/ShareLinkTab.test.tsx | 1 + .../dashboard-scene/sharing/ShareLinkTab.tsx | 35 ++------- 17 files changed, 334 insertions(+), 36 deletions(-) create mode 100644 public/app/features/dashboard-scene/sharing/ShareButton/ShareButton.test.tsx create mode 100644 public/app/features/dashboard-scene/sharing/ShareButton/ShareButton.tsx create mode 100644 public/app/features/dashboard-scene/sharing/ShareButton/ShareMenu.test.tsx create mode 100644 public/app/features/dashboard-scene/sharing/ShareButton/ShareMenu.tsx diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index b1710745b66d..36958c862c90 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -180,6 +180,7 @@ Experimental features might be changed or removed without prior notice. | `queryLibrary` | Enables Query Library feature in Explore | | `autofixDSUID` | Automatically migrates invalid datasource UIDs | | `logsExploreTableDefaultVisualization` | Sets the logs table as default visualisation in logs explore | +| `newDashboardSharingComponent` | Enables the new sharing drawer design | ## Development feature toggles diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index a1f6325cd6e8..ae217e8cb6c1 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -183,4 +183,5 @@ export interface FeatureToggles { queryLibrary?: boolean; autofixDSUID?: boolean; logsExploreTableDefaultVisualization?: boolean; + newDashboardSharingComponent?: boolean; } diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index 102d7d98b8b5..b9efadec03e3 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -58,6 +58,15 @@ export const Pages = { publicDashboardTag: 'data-testid public dashboard tag', shareButton: 'data-testid share-button', scrollContainer: 'data-testid Dashboard canvas scroll container', + newShareButton: { + container: 'data-testid new share button', + shareLink: 'data-testid new share link-button', + arrowMenu: 'data-testid new share button arrow menu', + menu: { + container: 'data-testid new share button menu', + shareInternally: 'data-testid new share button share internally', + }, + }, playlistControls: { prev: 'data-testid playlist previous dashboard button', stop: 'data-testid playlist stop dashboard button', diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 8e8ef9cf2854..e0ac258893c3 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1233,6 +1233,13 @@ var ( Owner: grafanaObservabilityLogsSquad, FrontendOnly: true, }, + { + Name: "newDashboardSharingComponent", + Description: "Enables the new sharing drawer design", + Stage: FeatureStageExperimental, + Owner: grafanaSharingSquad, + FrontendOnly: true, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 6b30739004e8..4d88057f3451 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -164,3 +164,4 @@ grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,fa queryLibrary,experimental,@grafana/explore-squad,false,false,false autofixDSUID,experimental,@grafana/plugins-platform-backend,false,false,false logsExploreTableDefaultVisualization,experimental,@grafana/observability-logs,false,false,true +newDashboardSharingComponent,experimental,@grafana/sharing-squad,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 7cb51a9b3041..765016a0b03f 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -666,4 +666,8 @@ const ( // FlagLogsExploreTableDefaultVisualization // Sets the logs table as default visualisation in logs explore FlagLogsExploreTableDefaultVisualization = "logsExploreTableDefaultVisualization" + + // FlagNewDashboardSharingComponent + // Enables the new sharing drawer design + FlagNewDashboardSharingComponent = "newDashboardSharingComponent" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index b7148bc41486..0278d3da405d 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2136,6 +2136,19 @@ "codeowner": "@grafana/observability-logs", "frontend": true } + }, + { + "metadata": { + "name": "newDashboardSharingComponent", + "resourceVersion": "1713982966391", + "creationTimestamp": "2024-04-24T18:22:46Z" + }, + "spec": { + "description": "Enables the new sharing drawer design", + "stage": "experimental", + "codeowner": "@grafana/sharing-squad", + "frontend": true + } } ] -} \ No newline at end of file +} diff --git a/public/app/core/utils/shortLinks.test.ts b/public/app/core/utils/shortLinks.test.ts index bbfdd4cf2386..3c012af00b42 100644 --- a/public/app/core/utils/shortLinks.test.ts +++ b/public/app/core/utils/shortLinks.test.ts @@ -1,6 +1,7 @@ import { createShortLink, createAndCopyShortLink } from './shortLinks'; jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), getBackendSrv: () => { return { post: () => { @@ -8,9 +9,6 @@ jest.mock('@grafana/runtime', () => ({ }, }; }, - config: { - appSubUrl: '', - }, })); describe('createShortLink', () => { diff --git a/public/app/core/utils/shortLinks.ts b/public/app/core/utils/shortLinks.ts index 30b6beb1c249..74ebbfbeb44e 100644 --- a/public/app/core/utils/shortLinks.ts +++ b/public/app/core/utils/shortLinks.ts @@ -1,8 +1,12 @@ import memoizeOne from 'memoize-one'; -import { getBackendSrv, config } from '@grafana/runtime'; +import { UrlQueryMap } from '@grafana/data'; +import { getBackendSrv, config, locationService } from '@grafana/runtime'; +import { sceneGraph, SceneTimeRangeLike, VizPanel } from '@grafana/scenes'; import { notifyApp } from 'app/core/actions'; import { createErrorNotification, createSuccessNotification } from 'app/core/copy/appNotification'; +import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; +import { getDashboardUrl } from 'app/features/dashboard-scene/utils/urlBuilders'; import { dispatch } from 'app/store/store'; import { copyStringToClipboard } from './explore'; @@ -37,3 +41,54 @@ export const createAndCopyShortLink = async (path: string) => { dispatch(notifyApp(createErrorNotification('Error generating shortened link'))); } }; + +export const createAndCopyDashboardShortLink = async ( + dashboard: DashboardScene, + opts: { useAbsoluteTimeRange: boolean; theme: string }, + panel?: VizPanel +) => { + const shareUrl = await createDashboardShareUrl(dashboard, opts, panel); + await createAndCopyShortLink(shareUrl); +}; + +export const createDashboardShareUrl = async ( + dashboard: DashboardScene, + opts: { useAbsoluteTimeRange: boolean; theme: string }, + panel?: VizPanel +) => { + const location = locationService.getLocation(); + const timeRange = sceneGraph.getTimeRange(panel ?? dashboard); + + const urlParamsUpdate = getShareUrlParams(opts, timeRange, panel); + + return getDashboardUrl({ + uid: dashboard.state.uid, + slug: dashboard.state.meta.slug, + currentQueryParams: location.search, + updateQuery: urlParamsUpdate, + absolute: true, + }); +}; + +export const getShareUrlParams = ( + opts: { useAbsoluteTimeRange: boolean; theme: string }, + timeRange: SceneTimeRangeLike, + panel?: VizPanel +) => { + const urlParamsUpdate: UrlQueryMap = {}; + + if (panel) { + urlParamsUpdate.viewPanel = panel.state.key; + } + + if (opts.useAbsoluteTimeRange) { + urlParamsUpdate.from = timeRange.state.value.from.toISOString(); + urlParamsUpdate.to = timeRange.state.value.to.toISOString(); + } + + if (opts.theme !== 'current') { + urlParamsUpdate.theme = opts.theme; + } + + return urlParamsUpdate; +}; diff --git a/public/app/features/dashboard-scene/scene/NavToolbarActions.test.tsx b/public/app/features/dashboard-scene/scene/NavToolbarActions.test.tsx index 7037971a2e65..996d44df9848 100644 --- a/public/app/features/dashboard-scene/scene/NavToolbarActions.test.tsx +++ b/public/app/features/dashboard-scene/scene/NavToolbarActions.test.tsx @@ -5,6 +5,7 @@ import { TestProvider } from 'test/helpers/TestProvider'; import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock'; import { selectors } from '@grafana/e2e-selectors'; +import { config } from '@grafana/runtime'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; @@ -25,7 +26,7 @@ jest.mock('app/features/playlist/PlaylistSrv', () => ({ })); describe('NavToolbarActions', () => { - describe('Give an already saved dashboard', () => { + describe('Given an already saved dashboard', () => { it('Should show correct buttons when not in editing', async () => { setup(); @@ -35,7 +36,7 @@ describe('NavToolbarActions', () => { expect(await screen.findByText('Share')).toBeInTheDocument(); }); - it('Should the correct buttons when playing a playlist', async () => { + it('Should show the correct buttons when playing a playlist', async () => { jest.mocked(playlistSrv).useState.mockReturnValueOnce({ isPlaying: true }); setup(); @@ -101,6 +102,24 @@ describe('NavToolbarActions', () => { expect(screen.queryByText(selectors.pages.Dashboard.DashNav.playlistControls.next)).not.toBeInTheDocument(); }); }); + + 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(); + }); + it('Should show new share button when newDashboardSharingComponent FF is enabled', async () => { + config.featureToggles.newDashboardSharingComponent = true; + setup(); + + expect(screen.queryByTestId(selectors.pages.Dashboard.DashNav.shareButton)).not.toBeInTheDocument(); + const newShareButton = screen.getByTestId(selectors.pages.Dashboard.DashNav.newShareButton.container); + expect(newShareButton).toBeInTheDocument(); + }); + }); }); let cleanUp = () => {}; diff --git a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx index c9fba9cf2e44..743af1b03810 100644 --- a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx +++ b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx @@ -23,6 +23,7 @@ import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { PanelEditor } from '../panel-edit/PanelEditor'; +import ShareButton from '../sharing/ShareButton/ShareButton'; import { ShareModal } from '../sharing/ShareModal'; import { DashboardInteractions } from '../utils/interactions'; import { DynamicDashNavButtonModel, dynamicDashNavActions } from '../utils/registerDynamicDashNavAction'; @@ -303,9 +304,10 @@ export function ToolbarActions({ dashboard }: Props) { ), }); + const showShareButton = uid && !isEditing && !meta.isSnapshot && !isPlaying; toolbarActions.push({ group: 'main-buttons', - condition: uid && !isEditing && !meta.isSnapshot && !isPlaying, + condition: !config.featureToggles.newDashboardSharingComponent && showShareButton, render: () => ( + +