Share Button: Added ClipboardItem route after Safari issues (#107925)

* Share Button: Added ClipboardItem route after Safari issues

* Removed console log

* Feedback fixes

* Test feedback fix

* Removed export
This commit is contained in:
Collin Fingar
2025-07-10 12:59:41 -04:00
committed by GitHub
parent 7e0848294e
commit 0c76b52e8b
4 changed files with 59 additions and 8 deletions

View File

@ -15,6 +15,17 @@ jest.mock('@grafana/runtime', () => ({
},
}));
beforeEach(() => {
Object.assign(navigator, {
clipboard: {
write: jest.fn().mockResolvedValue(undefined),
writeText: jest.fn().mockResolvedValue(undefined),
},
});
document.execCommand = jest.fn();
});
describe('createShortLink', () => {
it('creates short link', async () => {
const shortUrl = await createShortLink('www.verylonglinkwehavehere.com');
@ -23,11 +34,34 @@ describe('createShortLink', () => {
});
describe('createAndCopyShortLink', () => {
it('copies short link to clipboard', async () => {
it('copies short link to clipboard via document.execCommand when navigator.clipboard is undefined', async () => {
Object.assign(navigator, {
clipboard: {
write: undefined,
},
});
document.execCommand = jest.fn();
await createAndCopyShortLink('www.verylonglinkwehavehere.com');
expect(document.execCommand).toHaveBeenCalledWith('copy');
});
it('copies short link to clipboard via navigator.clipboard.writeText when ClipboardItem is undefined', async () => {
window.isSecureContext = true;
await createAndCopyShortLink('www.verylonglinkwehavehere.com');
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('www.short.com');
});
it('copies short link to clipboard via navigator.clipboard.write and ClipboardItem when it is defined', async () => {
global.ClipboardItem = jest.fn().mockImplementation(() => ({
type: 'text/plain',
size: 0,
slice: jest.fn(),
supports: jest.fn().mockReturnValue(true),
// eslint-disable-next-line
})) as any;
await createAndCopyShortLink('www.verylonglinkwehavehere.com');
expect(navigator.clipboard.write).toHaveBeenCalled();
});
});
describe('getLogsPermalinkRange', () => {

View File

@ -35,13 +35,30 @@ export const createShortLink = memoizeOne(async function (path: string) {
}
});
/**
* Creates a ClipboardItem for the shortened link. This is used due to clipboard issues in Safari after making async calls.
* See https://github.com/grafana/grafana/issues/106889
* @param path - The long path to share.
* @returns A ClipboardItem for the shortened link.
*/
const createShortLinkClipboardItem = (path: string) => {
return new ClipboardItem({
'text/plain': createShortLink(path),
});
};
export const createAndCopyShortLink = async (path: string) => {
const shortLink = await createShortLink(path);
if (shortLink) {
copyStringToClipboard(shortLink);
if (typeof ClipboardItem !== 'undefined' && navigator.clipboard.write) {
navigator.clipboard.write([createShortLinkClipboardItem(path)]);
dispatch(notifyApp(createSuccessNotification('Shortened link copied to clipboard')));
} else {
dispatch(notifyApp(createErrorNotification('Error generating shortened link')));
const shortLink = await createShortLink(path);
if (shortLink) {
copyStringToClipboard(shortLink);
dispatch(notifyApp(createSuccessNotification('Shortened link copied to clipboard')));
} else {
dispatch(notifyApp(createErrorNotification('Error generating shortened link')));
}
}
};

View File

@ -15,7 +15,7 @@ const newShareButtonSelector = e2eSelectors.pages.Dashboard.DashNav.newShareButt
export const ShareDashboardButton = ({ dashboard }: ToolbarActionProps) => {
const [_, buildUrl] = useAsyncFn(async () => {
DashboardInteractions.toolbarShareClick();
return await buildShareUrl(dashboard);
await buildShareUrl(dashboard);
}, [dashboard]);
return (

View File

@ -22,8 +22,8 @@ export default function ShareButton({ dashboard, panel }: { dashboard: Dashboard
const [_, buildUrl] = useAsyncFn(async () => {
DashboardInteractions.toolbarShareClick();
return await buildShareUrl(dashboard, panel);
}, [dashboard]);
await buildShareUrl(dashboard, panel);
}, [dashboard, panel]);
const onMenuClick = useCallback((isOpen: boolean) => {
if (isOpen) {