mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ShareDrawer: Test coverage (#93111)
This commit is contained in:
parent
c87b3c4bbf
commit
c56870e511
70
e2e/scenes/dashboards-suite/dashboard-export-json.spec.ts
Normal file
70
e2e/scenes/dashboards-suite/dashboard-export-json.spec.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { e2e } from '../utils';
|
||||
import '../../utils/support/clipboard';
|
||||
|
||||
describe('Export as JSON', () => {
|
||||
beforeEach(() => {
|
||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
|
||||
});
|
||||
|
||||
it('Export for internal and external use', () => {
|
||||
// Opening a dashboard
|
||||
cy.intercept({
|
||||
pathname: '/api/ds/query',
|
||||
}).as('query');
|
||||
e2e.flows.openDashboard({
|
||||
uid: 'ZqZnVvFZz',
|
||||
queryParams: { '__feature.scenes': true, '__feature.newDashboardSharingComponent': true },
|
||||
});
|
||||
cy.wait('@query');
|
||||
|
||||
// cy.wrap(
|
||||
// Cypress.automation('remote:debugger:protocol', {
|
||||
// command: 'Browser.grantPermissions',
|
||||
// params: {
|
||||
// permissions: ['clipboardReadWrite', 'clipboardSanitizedWrite'],
|
||||
// origin: window.location.origin,
|
||||
// },
|
||||
// })
|
||||
// );
|
||||
|
||||
// Open the export drawer
|
||||
e2e.pages.Dashboard.DashNav.NewExportButton.arrowMenu().click();
|
||||
e2e.pages.Dashboard.DashNav.NewExportButton.Menu.exportAsJson().click();
|
||||
|
||||
cy.url().should('include', 'shareView=export');
|
||||
|
||||
// Export as JSON
|
||||
e2e.pages.ExportDashboardDrawer.ExportAsJson.container().should('be.visible');
|
||||
e2e.pages.ExportDashboardDrawer.ExportAsJson.exportExternallyToggle().should('not.be.checked');
|
||||
e2e.pages.ExportDashboardDrawer.ExportAsJson.codeEditor().should('exist');
|
||||
|
||||
e2e.pages.ExportDashboardDrawer.ExportAsJson.saveToFileButton().should('exist');
|
||||
e2e.pages.ExportDashboardDrawer.ExportAsJson.copyToClipboardButton().should('exist');
|
||||
e2e.pages.ExportDashboardDrawer.ExportAsJson.cancelButton().should('exist');
|
||||
|
||||
//TODO Failing in CI/CD. Fix it
|
||||
// Copy link button should be visible
|
||||
// e2e.pages.ExportDashboardDrawer.ExportAsJson.copyToClipboardButton()
|
||||
// .click()
|
||||
// .then(() => {
|
||||
// cy.copyFromClipboard().then((url) => {
|
||||
// cy.wrap(url).should('not.include', '__inputs');
|
||||
// });
|
||||
// });
|
||||
|
||||
e2e.pages.ExportDashboardDrawer.ExportAsJson.exportExternallyToggle().click({ force: true });
|
||||
|
||||
//TODO Failing in CI/CD. Fix it
|
||||
// e2e.pages.ExportDashboardDrawer.ExportAsJson.copyToClipboardButton()
|
||||
// .click()
|
||||
// .then(() => {
|
||||
// cy.copyFromClipboard().then((url) => {
|
||||
// cy.wrap(url).should('include', '__inputs');
|
||||
// });
|
||||
// });
|
||||
|
||||
e2e.pages.ExportDashboardDrawer.ExportAsJson.cancelButton().click();
|
||||
|
||||
cy.url().should('not.include', 'shareView=export');
|
||||
});
|
||||
});
|
@ -0,0 +1,180 @@
|
||||
import { PublicDashboard } from '../../../public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
||||
import { e2e } from '../utils';
|
||||
import '../../utils/support/clipboard';
|
||||
|
||||
describe('Shared dashboards', () => {
|
||||
beforeEach(() => {
|
||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
|
||||
});
|
||||
|
||||
it('Close share externally drawer', () => {
|
||||
openDashboard();
|
||||
|
||||
// Open share externally drawer
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.arrowMenu().click();
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.menu.shareExternally().click();
|
||||
|
||||
cy.url().should('include', 'shareView=public_dashboard');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.container().should('be.visible');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Creation.PublicShare.cancelButton().click();
|
||||
|
||||
cy.url().should('not.include', 'shareView=public_dashboard');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.container().should('not.exist');
|
||||
});
|
||||
|
||||
it('Create a shared dashboard and check API', () => {
|
||||
openDashboard();
|
||||
|
||||
// Open share externally drawer
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.arrowMenu().click();
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.menu.shareExternally().click();
|
||||
|
||||
// Create button should be disabled
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Creation.PublicShare.createButton().should('be.disabled');
|
||||
|
||||
// Create flow shouldn't show these elements
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.enableTimeRangeSwitch().should('not.exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.enableAnnotationsSwitch().should('not.exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.copyUrlButton().should('not.exist');
|
||||
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.revokeAccessButton().should('not.exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.toggleAccessButton().should('not.exist');
|
||||
|
||||
// Acknowledge checkbox
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Creation.willBePublicCheckbox()
|
||||
.should('be.enabled')
|
||||
.click({ force: true });
|
||||
|
||||
// Create shared dashboard
|
||||
cy.intercept('POST', '/api/dashboards/uid/edediimbjhdz4b/public-dashboards').as('create');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Creation.PublicShare.createButton().should('be.enabled').click();
|
||||
cy.wait('@create')
|
||||
.its('response.body')
|
||||
.then((body: PublicDashboard) => {
|
||||
cy.log(JSON.stringify(body));
|
||||
cy.clearCookies()
|
||||
.request(getPublicDashboardAPIUrl(body.accessToken))
|
||||
.then((resp) => {
|
||||
expect(resp.status).to.eq(200);
|
||||
});
|
||||
});
|
||||
|
||||
// These elements shouldn't be rendered after creating public dashboard
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Creation.willBePublicCheckbox().should('not.exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Creation.PublicShare.createButton().should('not.exist');
|
||||
|
||||
// These elements should be rendered
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.enableTimeRangeSwitch().should('exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.enableAnnotationsSwitch().should('exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.copyUrlButton().should('exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.revokeAccessButton().should('exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.toggleAccessButton().should('exist');
|
||||
});
|
||||
|
||||
// Skipping as clipboard permissions are failing in CI. Public dashboard creation is checked in previous test on purpose
|
||||
it.skip('Open a shared dashboard', () => {
|
||||
openDashboard();
|
||||
|
||||
cy.wrap(
|
||||
Cypress.automation('remote:debugger:protocol', {
|
||||
command: 'Browser.grantPermissions',
|
||||
params: {
|
||||
permissions: ['clipboardReadWrite', 'clipboardSanitizedWrite'],
|
||||
origin: window.location.origin,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Tag indicating a dashboard is public
|
||||
e2e.pages.Dashboard.DashNav.publicDashboardTag().should('exist');
|
||||
|
||||
// Open share externally drawer
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.arrowMenu().click();
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.menu.shareExternally().click();
|
||||
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.enableTimeRangeSwitch().should('exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.enableAnnotationsSwitch().should('exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.copyUrlButton().should('exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.revokeAccessButton().should('exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.toggleAccessButton().should('exist');
|
||||
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.copyUrlButton()
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.copyFromClipboard().then((url) => {
|
||||
cy.clearCookies()
|
||||
.request(getPublicDashboardAPIUrl(String(url)))
|
||||
.then((resp) => {
|
||||
expect(resp.status).to.eq(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Disable a shared dashboard', () => {
|
||||
openDashboard();
|
||||
|
||||
//TODO Failing in CI/CD. Fix it
|
||||
// cy.wrap(
|
||||
// Cypress.automation('remote:debugger:protocol', {
|
||||
// command: 'Browser.grantPermissions',
|
||||
// params: {
|
||||
// permissions: ['clipboardReadWrite', 'clipboardSanitizedWrite'],
|
||||
// origin: window.location.origin,
|
||||
// },
|
||||
// })
|
||||
// );
|
||||
|
||||
// Open share externally drawer
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.arrowMenu().click();
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.menu.shareExternally().click();
|
||||
|
||||
// Save public dashboard
|
||||
cy.intercept('PATCH', '/api/dashboards/uid/edediimbjhdz4b/public-dashboards/*').as('update');
|
||||
|
||||
// Switch off enabling toggle
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.toggleAccessButton()
|
||||
.should('be.enabled')
|
||||
.click({ force: true });
|
||||
|
||||
cy.wait('@update')
|
||||
.its('response')
|
||||
.then((rs) => {
|
||||
expect(rs.statusCode).eq(200);
|
||||
const publicDashboard: PublicDashboard = rs.body;
|
||||
cy.clearCookies()
|
||||
.request({ url: getPublicDashboardAPIUrl(publicDashboard.accessToken), failOnStatusCode: false })
|
||||
.then((resp) => {
|
||||
expect(resp.status).to.eq(403);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.toggleAccessButton().contains('Resume access');
|
||||
e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.copyUrlButton().should('be.enabled');
|
||||
});
|
||||
|
||||
//TODO Failing in CI/CD. Fix it
|
||||
// e2e.pages.ShareDashboardDrawer.ShareExternally.Configuration.copyUrlButton()
|
||||
// .click()
|
||||
// .then(() => {
|
||||
// cy.copyFromClipboard().then((url) => {
|
||||
// cy.clearCookies()
|
||||
// .request({ url: getPublicDashboardAPIUrl(String(url)), failOnStatusCode: false })
|
||||
// .then((resp) => {
|
||||
// expect(resp.status).to.eq(403);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
});
|
||||
});
|
||||
|
||||
const openDashboard = () => {
|
||||
e2e.flows.openDashboard({
|
||||
uid: 'edediimbjhdz4b',
|
||||
queryParams: { '__feature.scenes': true, '__feature.newDashboardSharingComponent': true },
|
||||
});
|
||||
};
|
||||
|
||||
const getPublicDashboardAPIUrl = (accessToken: string): string => {
|
||||
return `/api/public/dashboards/${accessToken}`;
|
||||
};
|
246
e2e/scenes/dashboards-suite/dashboard-share-internally.spec.ts
Normal file
246
e2e/scenes/dashboards-suite/dashboard-share-internally.spec.ts
Normal file
@ -0,0 +1,246 @@
|
||||
import { ShareLinkConfiguration } from '../../../public/app/features/dashboard-scene/sharing/ShareButton/utils';
|
||||
import { e2e } from '../utils';
|
||||
import '../../utils/support/clipboard';
|
||||
|
||||
describe('Share internally', () => {
|
||||
beforeEach(() => {
|
||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
|
||||
cy.window().then((win) => {
|
||||
win.localStorage.removeItem('grafana.dashboard.link.shareConfiguration');
|
||||
});
|
||||
});
|
||||
|
||||
it('Create a locked time range short link', () => {
|
||||
cy.intercept({
|
||||
pathname: '/api/ds/query',
|
||||
}).as('query');
|
||||
openDashboard();
|
||||
cy.wait('@query');
|
||||
|
||||
//TODO Failing in CI/CD. Fix it
|
||||
// cy.wrap(
|
||||
// Cypress.automation('remote:debugger:protocol', {
|
||||
// command: 'Browser.grantPermissions',
|
||||
// params: {
|
||||
// permissions: ['clipboardReadWrite', 'clipboardSanitizedWrite'],
|
||||
// origin: window.location.origin,
|
||||
// },
|
||||
// })
|
||||
// );
|
||||
|
||||
// Open share externally drawer
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.arrowMenu().click();
|
||||
|
||||
cy.intercept('POST', '/api/short-urls').as('create');
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.menu.shareInternally().click();
|
||||
|
||||
cy.url().should('include', 'shareView=link');
|
||||
|
||||
e2e.pages.ShareDashboardDrawer.ShareInternally.lockTimeRangeSwitch().should('exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareInternally.shortenUrlSwitch().should('exist');
|
||||
e2e.pages.ShareDashboardDrawer.ShareInternally.copyUrlButton().should('exist');
|
||||
e2e.components.RadioButton.container().should('have.length', 3);
|
||||
|
||||
cy.window().then((win) => {
|
||||
const shareConfiguration = win.localStorage.getItem('grafana.dashboard.link.shareConfiguration');
|
||||
expect(shareConfiguration).equal(null);
|
||||
});
|
||||
cy.wait('@create')
|
||||
.its('response')
|
||||
.then((rs) => {
|
||||
expect(rs.statusCode).eq(200);
|
||||
const body: { url: string; uid: string } = rs.body;
|
||||
expect(body.url).contain('goto');
|
||||
|
||||
// const url = fromBaseUrl(getShortLinkUrl(body.uid));
|
||||
// cy.intercept('GET', url).as('get');
|
||||
// cy.visit(url, { retryOnNetworkFailure: true });
|
||||
// cy.wait('@get');
|
||||
//
|
||||
// cy.url().should('not.include', 'from=now-6h&to=now');
|
||||
});
|
||||
});
|
||||
|
||||
it('Create a relative time range short link', () => {
|
||||
cy.intercept({
|
||||
pathname: '/api/ds/query',
|
||||
}).as('query');
|
||||
openDashboard();
|
||||
cy.wait('@query');
|
||||
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.arrowMenu().click();
|
||||
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.menu.shareInternally().click();
|
||||
|
||||
cy.intercept('POST', '/api/short-urls').as('update');
|
||||
e2e.pages.ShareDashboardDrawer.ShareInternally.lockTimeRangeSwitch().click({ force: true });
|
||||
|
||||
cy.window().then((win) => {
|
||||
const shareConfiguration = win.localStorage.getItem('grafana.dashboard.link.shareConfiguration');
|
||||
const { useAbsoluteTimeRange, useShortUrl, theme }: ShareLinkConfiguration = JSON.parse(shareConfiguration);
|
||||
expect(useAbsoluteTimeRange).eq(false);
|
||||
expect(useShortUrl).eq(true);
|
||||
expect(theme).eq('current');
|
||||
});
|
||||
|
||||
cy.wait('@update')
|
||||
.its('response')
|
||||
.then((rs) => {
|
||||
expect(rs.statusCode).eq(200);
|
||||
const body: { url: string; uid: string } = rs.body;
|
||||
expect(body.url).contain('goto');
|
||||
|
||||
// const url = fromBaseUrl(getShortLinkUrl(body.uid));
|
||||
// cy.intercept('GET', url).as('get');
|
||||
// cy.visit(url, { retryOnNetworkFailure: true });
|
||||
// cy.wait('@get');
|
||||
//
|
||||
// cy.url().should('include', 'from=now-6h&to=now');
|
||||
});
|
||||
|
||||
//
|
||||
// e2e.pages.ShareDashboardDrawer.ShareInternally.shortenUrlSwitch().click({ force: true });
|
||||
//
|
||||
// cy.window().then((win) => {
|
||||
// const shareConfiguration = win.localStorage.getItem('grafana.dashboard.link.shareConfiguration');
|
||||
// const { useAbsoluteTimeRange, useShortUrl, theme }: ShareLinkConfiguration = JSON.parse(shareConfiguration);
|
||||
// expect(useAbsoluteTimeRange).eq(true);
|
||||
// expect(useShortUrl).eq(false);
|
||||
// expect(theme).eq('current');
|
||||
// });
|
||||
|
||||
// e2e.pages.ShareDashboardDrawer.ShareInternally.copyUrlButton().should('exist');
|
||||
|
||||
// e2e.pages.ShareDashboardDrawer.ShareInternally.copyUrlButton()
|
||||
// .click()
|
||||
// .then(() => {
|
||||
// cy.copyFromClipboard().then((url) => {
|
||||
// cy.wrap(url).should('include', 'from=now-6h&to=now');
|
||||
// cy.wrap(url).should('not.include', 'goto');
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
it('Create a relative time range short link', () => {
|
||||
cy.intercept({
|
||||
pathname: '/api/ds/query',
|
||||
}).as('query');
|
||||
openDashboard();
|
||||
cy.wait('@query');
|
||||
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.arrowMenu().click();
|
||||
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.menu.shareInternally().click();
|
||||
|
||||
cy.intercept('POST', '/api/short-urls').as('update');
|
||||
e2e.pages.ShareDashboardDrawer.ShareInternally.lockTimeRangeSwitch().click({ force: true });
|
||||
|
||||
cy.window().then((win) => {
|
||||
const shareConfiguration = win.localStorage.getItem('grafana.dashboard.link.shareConfiguration');
|
||||
const { useAbsoluteTimeRange, useShortUrl, theme }: ShareLinkConfiguration = JSON.parse(shareConfiguration);
|
||||
expect(useAbsoluteTimeRange).eq(false);
|
||||
expect(useShortUrl).eq(true);
|
||||
expect(theme).eq('current');
|
||||
});
|
||||
|
||||
cy.wait('@update')
|
||||
.its('response')
|
||||
.then((rs) => {
|
||||
expect(rs.statusCode).eq(200);
|
||||
const body: { url: string; uid: string } = rs.body;
|
||||
expect(body.url).contain('goto');
|
||||
|
||||
// const url = fromBaseUrl(getShortLinkUrl(body.uid));
|
||||
// cy.intercept('GET', url).as('get');
|
||||
// cy.visit(url, { retryOnNetworkFailure: true });
|
||||
// cy.wait('@get');
|
||||
//
|
||||
// cy.url().should('include', 'from=now-6h&to=now');
|
||||
});
|
||||
});
|
||||
|
||||
//TODO Failing in CI/CD. Fix it
|
||||
it.skip('Share button gets configured link', () => {
|
||||
cy.intercept({
|
||||
pathname: '/api/ds/query',
|
||||
}).as('query');
|
||||
openDashboard();
|
||||
cy.wait('@query');
|
||||
|
||||
// cy.wrap(
|
||||
// Cypress.automation('remote:debugger:protocol', {
|
||||
// command: 'Browser.grantPermissions',
|
||||
// params: {
|
||||
// permissions: ['clipboardReadWrite', 'clipboardSanitizedWrite'],
|
||||
// origin: window.location.origin,
|
||||
// },
|
||||
// })
|
||||
// );
|
||||
|
||||
//TODO Failing in CI/CD. Fix it
|
||||
// e2e.pages.Dashboard.DashNav.newShareButton
|
||||
// .shareLink()
|
||||
// .click()
|
||||
// .then(() => {
|
||||
// cy.window()
|
||||
// .then((win) => {
|
||||
// return win.navigator.clipboard.readText().then((url) => {
|
||||
// cy.wrap(url).as('url');
|
||||
// });
|
||||
// })
|
||||
// .then(() => {
|
||||
// cy.get('@url').then((url) => {
|
||||
// cy.wrap(url).should('not.include', 'from=now-6h&to=now');
|
||||
// cy.wrap(url).should('include', 'goto');
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
// Open share externally drawer
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.arrowMenu().click();
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.menu.shareInternally().click();
|
||||
|
||||
cy.window().then((win) => {
|
||||
const shareConfiguration = win.localStorage.getItem('grafana.dashboard.link.shareConfiguration');
|
||||
expect(shareConfiguration).equal(null);
|
||||
});
|
||||
|
||||
e2e.pages.ShareDashboardDrawer.ShareInternally.shortenUrlSwitch().click({ force: true });
|
||||
e2e.pages.ShareDashboardDrawer.ShareInternally.lockTimeRangeSwitch().click({ force: true });
|
||||
|
||||
e2e.components.Drawer.General.close().click();
|
||||
|
||||
cy.url().should('not.include', 'shareView=link');
|
||||
|
||||
//TODO Failing in CI/CD. Fix it
|
||||
// e2e.pages.Dashboard.DashNav.newShareButton
|
||||
// .shareLink()
|
||||
// .click()
|
||||
// .then(() => {
|
||||
// cy.window()
|
||||
// .then((win) => {
|
||||
// return win.navigator.clipboard.readText().then((url) => {
|
||||
// cy.wrap(url).as('url');
|
||||
// });
|
||||
// })
|
||||
// .then(() => {
|
||||
// cy.get('@url').then((url) => {
|
||||
// cy.wrap(url).should('include', 'from=now-6h&to=now');
|
||||
// cy.wrap(url).should('not.include', 'goto');
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
});
|
||||
});
|
||||
|
||||
const openDashboard = () => {
|
||||
e2e.flows.openDashboard({
|
||||
uid: 'ZqZnVvFZz',
|
||||
queryParams: { '__feature.scenes': true, '__feature.newDashboardSharingComponent': true },
|
||||
timeRange: { from: 'now-6h', to: 'now' },
|
||||
});
|
||||
};
|
||||
|
||||
// const getShortLinkUrl = (uid: string): string => {
|
||||
// return `/goto/${uid}`;
|
||||
// };
|
@ -0,0 +1,98 @@
|
||||
import { SnapshotCreateResponse } from '../../../public/app/features/dashboard/services/SnapshotSrv';
|
||||
import { fromBaseUrl } from '../../utils/support/url';
|
||||
import { e2e } from '../utils';
|
||||
import '../../utils/support/clipboard';
|
||||
|
||||
describe('Snapshots', () => {
|
||||
beforeEach(() => {
|
||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
|
||||
});
|
||||
|
||||
it('Create a snapshot dashboard', () => {
|
||||
// Opening a dashboard
|
||||
cy.intercept({
|
||||
pathname: '/api/ds/query',
|
||||
}).as('query');
|
||||
e2e.flows.openDashboard({
|
||||
uid: 'ZqZnVvFZz',
|
||||
queryParams: { '__feature.scenes': true, '__feature.newDashboardSharingComponent': true },
|
||||
});
|
||||
cy.wait('@query');
|
||||
|
||||
//TODO Failing in CI/CD. Fix it
|
||||
// cy.wrap(
|
||||
// Cypress.automation('remote:debugger:protocol', {
|
||||
// command: 'Browser.grantPermissions',
|
||||
// params: {
|
||||
// permissions: ['clipboardReadWrite', 'clipboardSanitizedWrite'],
|
||||
// origin: window.location.origin,
|
||||
// },
|
||||
// })
|
||||
// );
|
||||
|
||||
const panelsToCheck = [
|
||||
'Raw Data Graph',
|
||||
'Last non-null',
|
||||
'min',
|
||||
'Max',
|
||||
'The data from graph above with seriesToColumns transform',
|
||||
];
|
||||
|
||||
// Open the sharing drawer
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.arrowMenu().click();
|
||||
e2e.pages.Dashboard.DashNav.newShareButton.menu.shareSnapshot().click();
|
||||
|
||||
// Publish snapshot
|
||||
cy.intercept('POST', '/api/snapshots').as('create');
|
||||
e2e.pages.ShareDashboardDrawer.ShareSnapshot.publishSnapshot().click();
|
||||
cy.wait('@create')
|
||||
.its('response')
|
||||
.then((rs) => {
|
||||
expect(rs.statusCode).eq(200);
|
||||
const body: SnapshotCreateResponse = rs.body;
|
||||
cy.visit(fromBaseUrl(getSnapshotUrl(body.key)));
|
||||
|
||||
// Validate the dashboard controls are rendered
|
||||
e2e.pages.Dashboard.Controls().should('exist');
|
||||
|
||||
// Validate the panels are rendered
|
||||
for (const title of panelsToCheck) {
|
||||
e2e.components.Panels.Panel.title(title).should('be.visible');
|
||||
}
|
||||
});
|
||||
|
||||
// Copy link button should be visible
|
||||
// e2e.pages.ShareDashboardDrawer.ShareSnapshot.copyUrlButton().should('exist');
|
||||
|
||||
//TODO Failing in CI/CD. Fix it
|
||||
// Copy the snapshot URL form the clipboard and open the snapshot
|
||||
// e2e.pages.ShareDashboardDrawer.ShareSnapshot.copyUrlButton()
|
||||
// .click()
|
||||
// .then(() => {
|
||||
// cy.copyFromClipboard().then((url) => {
|
||||
// cy.wrap(url).as('url');
|
||||
// });
|
||||
// })
|
||||
// .then(() => {
|
||||
// cy.get('@url').then((url) => {
|
||||
// e2e.pages.ShareDashboardDrawer.ShareSnapshot.visit(getSnapshotKey(String(url)));
|
||||
// });
|
||||
//
|
||||
// // Validate the dashboard controls are rendered
|
||||
// e2e.pages.Dashboard.Controls().should('exist');
|
||||
//
|
||||
// // Validate the panels are rendered
|
||||
// for (const title of panelsToCheck) {
|
||||
// e2e.components.Panels.Panel.title(title).should('be.visible');
|
||||
// }
|
||||
// });
|
||||
});
|
||||
});
|
||||
|
||||
const getSnapshotUrl = (uid: string): string => {
|
||||
return `/dashboard/snapshot/${uid}`;
|
||||
};
|
||||
|
||||
// const getSnapshotKey = (url: string): string => {
|
||||
// return url.split('/').pop();
|
||||
// };
|
29
e2e/utils/support/clipboard.ts
Normal file
29
e2e/utils/support/clipboard.ts
Normal file
@ -0,0 +1,29 @@
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
copyToClipboard(): Chainable;
|
||||
copyFromClipboard(): Chainable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add('copyFromClipboard', () => {
|
||||
return cy.window().then((win) => {
|
||||
return cy.wrap(win.navigator.clipboard.readText());
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add(
|
||||
'copyToClipboard',
|
||||
{
|
||||
prevSubject: [],
|
||||
},
|
||||
(subject: string) => {
|
||||
return cy.window().then((win) => {
|
||||
return cy.wrap(win.navigator.clipboard.writeText(subject));
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export {};
|
@ -1,5 +1,3 @@
|
||||
import { e2e } from '../index';
|
||||
|
||||
const getBaseUrl = () => Cypress.env('BASE_URL') || Cypress.config().baseUrl || 'http://localhost:3000';
|
||||
|
||||
export const fromBaseUrl = (url = '') => new URL(url, getBaseUrl()).href;
|
||||
|
@ -67,6 +67,7 @@ export const Pages = {
|
||||
shareInternally: 'data-testid new share button share internally',
|
||||
shareExternally: 'data-testid new share button share externally',
|
||||
shareSnapshot: 'data-testid new share button share snapshot',
|
||||
scheduleReport: 'data-testid new share button schedule report',
|
||||
},
|
||||
},
|
||||
NewExportButton: {
|
||||
@ -286,23 +287,51 @@ export const Pages = {
|
||||
},
|
||||
},
|
||||
ShareDashboardDrawer: {
|
||||
ShareInternally: {
|
||||
container: 'data-testid share internally drawer container',
|
||||
lockTimeRangeSwitch: 'data-testid share internally lock time range switch',
|
||||
shortenUrlSwitch: 'data-testid share internally shorten url switch',
|
||||
copyUrlButton: 'data-testid share internally copy url button',
|
||||
},
|
||||
ShareExternally: {
|
||||
container: 'data-testid share externally drawer container',
|
||||
copyUrlButton: 'data-testid share externally copy url button',
|
||||
publicAlert: 'data-testid public share alert',
|
||||
emailSharingAlert: 'data-testid email share alert',
|
||||
shareTypeSelect: 'data-testid share externally share type select',
|
||||
Creation: {
|
||||
PublicShare: {
|
||||
createButton: 'data-testid public share dashboard create button',
|
||||
cancelButton: 'data-testid public share dashboard cancel button',
|
||||
},
|
||||
EmailShare: {
|
||||
createButton: 'data-testid email share dashboard create button',
|
||||
cancelButton: 'data-testid email share dashboard cancel button',
|
||||
},
|
||||
willBePublicCheckbox: 'data-testid share dashboard will be public checkbox',
|
||||
},
|
||||
Configuration: {
|
||||
enableTimeRangeSwitch: 'data-testid share externally enable time range switch',
|
||||
enableAnnotationsSwitch: 'data-testid share externally enable annotations switch',
|
||||
copyUrlButton: 'data-testid share externally copy url button',
|
||||
revokeAccessButton: 'data-testid share externally revoke access button',
|
||||
toggleAccessButton: 'data-testid share externally pause or resume access button',
|
||||
},
|
||||
},
|
||||
ShareSnapshot: {
|
||||
url: (key: string) => `/dashboard/snapshot/${key}`,
|
||||
container: 'data-testid share snapshot drawer container',
|
||||
publishSnapshot: 'data-testid share snapshot publish button',
|
||||
copyUrlButton: 'data-testid share snapshot copy url button',
|
||||
},
|
||||
},
|
||||
ExportDashboardDrawer: {
|
||||
ExportAsJson: {
|
||||
container: 'data-testid export as Json drawer container',
|
||||
codeEditor: 'data-testid export as Json code editor',
|
||||
exportExternallyToggle: 'data-testid export externally toggle type select',
|
||||
saveToFileButton: 'data-testid save to file button',
|
||||
copyToClipboardButton: 'data-testid copy to clipboard button',
|
||||
cancelButton: 'data-testid cancel button',
|
||||
container: 'data-testid export as json drawer container',
|
||||
codeEditor: 'data-testid export as json code editor',
|
||||
exportExternallyToggle: 'data-testid export as json externally switch',
|
||||
saveToFileButton: 'data-testid export as json save to file button',
|
||||
copyToClipboardButton: 'data-testid export as json copy to clipboard button',
|
||||
cancelButton: 'data-testid export as json cancel button',
|
||||
},
|
||||
},
|
||||
PublicDashboard: {
|
||||
|
@ -42,7 +42,7 @@ function ExportAsJsonRenderer({ model }: SceneComponentProps<ExportAsJson>) {
|
||||
const switchLabel = t('export.json.export-externally-label', 'Export the dashboard to use in another instance');
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testid={selector.container}>
|
||||
<p>
|
||||
<Trans i18nKey="export.json.info-text">
|
||||
Copy or download a JSON file containing the JSON of your dashboard
|
||||
@ -107,7 +107,7 @@ function ExportAsJsonRenderer({ model }: SceneComponentProps<ExportAsJson>) {
|
||||
</Button>
|
||||
</Stack>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -24,10 +24,14 @@ export interface ShareDrawerMenuItem {
|
||||
onClick: (d: DashboardScene) => void;
|
||||
}
|
||||
|
||||
const customShareDrawerItem: ShareDrawerMenuItem[] = [];
|
||||
let customShareDrawerItems: ShareDrawerMenuItem[] = [];
|
||||
|
||||
export function addDashboardShareDrawerItem(item: ShareDrawerMenuItem) {
|
||||
customShareDrawerItem.push(item);
|
||||
customShareDrawerItems.push(item);
|
||||
}
|
||||
|
||||
export function resetDashboardShareDrawerItems() {
|
||||
customShareDrawerItems = [];
|
||||
}
|
||||
|
||||
export default function ShareMenu({ dashboard, panel }: { dashboard: DashboardScene; panel?: VizPanel }) {
|
||||
@ -59,7 +63,7 @@ export default function ShareMenu({ dashboard, panel }: { dashboard: DashboardSc
|
||||
},
|
||||
});
|
||||
|
||||
customShareDrawerItem.forEach((d) => menuItems.push(d));
|
||||
customShareDrawerItems.forEach((d) => menuItems.push(d));
|
||||
|
||||
menuItems.push({
|
||||
shareId: shareDashboardType.snapshot,
|
||||
@ -88,7 +92,7 @@ export default function ShareMenu({ dashboard, panel }: { dashboard: DashboardSc
|
||||
<Menu data-testid={newShareButtonSelector.container}>
|
||||
{buildMenuItems().map((item) => (
|
||||
<Menu.Item
|
||||
key={item.label}
|
||||
key={item.shareId}
|
||||
testId={item.testId}
|
||||
label={item.label}
|
||||
icon={item.icon}
|
||||
|
@ -2,6 +2,7 @@ import { css } from '@emotion/css';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { Button, Checkbox, FieldSet, Spinner, Stack } from '@grafana/ui';
|
||||
import { useStyles2 } from '@grafana/ui/';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
@ -14,6 +15,8 @@ import { AccessControlAction } from 'app/types';
|
||||
import { EmailSharingPricingAlert } from '../../../../../dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/EmailSharingPricingAlert';
|
||||
import { useShareDrawerContext } from '../../../ShareDrawer/ShareDrawerContext';
|
||||
|
||||
const selectors = e2eSelectors.pages.ShareDashboardDrawer.ShareExternally.Creation;
|
||||
|
||||
export const CreateEmailSharing = ({ hasError }: { hasError: boolean }) => {
|
||||
const { dashboard, onDismiss } = useShareDrawerContext();
|
||||
const styles = useStyles2(getStyles);
|
||||
@ -46,10 +49,10 @@ export const CreateEmailSharing = ({ hasError }: { hasError: boolean }) => {
|
||||
/>
|
||||
</div>
|
||||
<Stack direction="row" gap={1} alignItems="center">
|
||||
<Button type="submit" disabled={!isValid}>
|
||||
<Button type="submit" disabled={!isValid} data-testid={selectors.EmailShare.createButton}>
|
||||
<Trans i18nKey="public-dashboard.email-sharing.accept-button">Accept</Trans>
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={onDismiss}>
|
||||
<Button variant="secondary" onClick={onDismiss} data-testid={selectors.EmailShare.cancelButton}>
|
||||
<Trans i18nKey="public-dashboard.email-sharing.cancel-button">Cancel</Trans>
|
||||
</Button>
|
||||
{isLoading && <Spinner />}
|
||||
|
@ -14,7 +14,7 @@ import { AccessControlAction } from 'app/types';
|
||||
import { PublicDashboardAlert } from '../../../../../dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/PublicDashboardAlert';
|
||||
import { useShareDrawerContext } from '../../../ShareDrawer/ShareDrawerContext';
|
||||
|
||||
const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
|
||||
const selectors = e2eSelectors.pages.ShareDashboardDrawer.ShareExternally.Creation;
|
||||
|
||||
export default function CreatePublicSharing({ hasError }: { hasError: boolean }) {
|
||||
const { dashboard, onDismiss } = useShareDrawerContext();
|
||||
@ -48,13 +48,14 @@ export default function CreatePublicSharing({ hasError }: { hasError: boolean })
|
||||
'public-dashboard.public-sharing.public-ack',
|
||||
'I understand that this entire dashboard will be public.*'
|
||||
)}
|
||||
data-testid={selectors.willBePublicCheckbox}
|
||||
/>
|
||||
</div>
|
||||
<Stack direction="row" gap={1} alignItems="center">
|
||||
<Button type="submit" disabled={!isValid} data-testid={selectors.CreateButton}>
|
||||
<Button type="submit" disabled={!isValid} data-testid={selectors.PublicShare.createButton}>
|
||||
<Trans i18nKey="public-dashboard.public-sharing.accept-button">Accept</Trans>
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={onDismiss}>
|
||||
<Button variant="secondary" onClick={onDismiss} data-testid={selectors.PublicShare.cancelButton}>
|
||||
<Trans i18nKey="public-dashboard.public-sharing.cancel-button">Cancel</Trans>
|
||||
</Button>
|
||||
{isLoading && <Spinner />}
|
||||
|
@ -15,7 +15,7 @@ import { AccessControlAction } from 'app/types';
|
||||
|
||||
import { useShareDrawerContext } from '../../ShareDrawer/ShareDrawerContext';
|
||||
|
||||
const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
|
||||
const selectors = e2eSelectors.pages.ShareDashboardDrawer.ShareExternally.Configuration;
|
||||
|
||||
type FormInput = Omit<ConfigPublicDashboardForm, 'isPaused'>;
|
||||
|
||||
@ -72,7 +72,7 @@ export default function ShareConfiguration() {
|
||||
render={({ field: { ref, ...field } }) => (
|
||||
<Switch
|
||||
{...field}
|
||||
data-testid={selectors.EnableTimeRangeSwitch}
|
||||
data-testid={selectors.enableTimeRangeSwitch}
|
||||
onChange={(e) => {
|
||||
DashboardInteractions.publicDashboardTimeSelectionChanged({
|
||||
enabled: e.currentTarget.checked,
|
||||
@ -99,7 +99,7 @@ export default function ShareConfiguration() {
|
||||
render={({ field: { ref, ...field } }) => (
|
||||
<Switch
|
||||
{...field}
|
||||
data-testid={selectors.EnableAnnotationsSwitch}
|
||||
data-testid={selectors.enableAnnotationsSwitch}
|
||||
onChange={(e) => {
|
||||
DashboardInteractions.publicDashboardAnnotationsSelectionChanged({
|
||||
enabled: e.currentTarget.checked,
|
||||
|
@ -0,0 +1,132 @@
|
||||
import { screen, waitForElementToBeRemoved } from '@testing-library/react';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
import { getDefaultTimeRange, LoadingState } from '@grafana/data';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { config, setPluginImportUtils } from '@grafana/runtime';
|
||||
import {
|
||||
CustomVariable,
|
||||
SceneGridLayout,
|
||||
SceneQueryRunner,
|
||||
SceneTimeRange,
|
||||
SceneVariableSet,
|
||||
VizPanel,
|
||||
VizPanelState,
|
||||
} from '@grafana/scenes';
|
||||
|
||||
import { contextSrv } from '../../../../../core/services/context_srv';
|
||||
import * as sharePublicDashboardUtils from '../../../../dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
||||
import { shareDashboardType } from '../../../../dashboard/components/ShareModal/utils';
|
||||
import { DashboardGridItem } from '../../../scene/DashboardGridItem';
|
||||
import { DashboardScene, DashboardSceneState } from '../../../scene/DashboardScene';
|
||||
import { activateFullSceneTree } from '../../../utils/test-utils';
|
||||
import { ShareDrawer } from '../../ShareDrawer/ShareDrawer';
|
||||
|
||||
const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
|
||||
const shareExternallySelector = e2eSelectors.pages.ShareDashboardDrawer.ShareExternally;
|
||||
|
||||
setPluginImportUtils({
|
||||
importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})),
|
||||
getPanelPluginFromCache: (id: string) => undefined,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
config.featureToggles.newDashboardSharingComponent = true;
|
||||
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
|
||||
jest.spyOn(contextSrv, 'hasRole').mockReturnValue(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('Alerts', () => {
|
||||
it('when share type is public, warning is shown', async () => {
|
||||
await buildAndRenderScenario({});
|
||||
expect(screen.queryByTestId(shareExternallySelector.publicAlert)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId(shareExternallySelector.emailSharingAlert)).not.toBeInTheDocument();
|
||||
});
|
||||
it('when user has no write permissions, warning is shown', async () => {
|
||||
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
|
||||
|
||||
await buildAndRenderScenario({});
|
||||
expect(screen.queryByTestId(selectors.NoUpsertPermissionsWarningAlert)).toBeInTheDocument();
|
||||
});
|
||||
it('when dashboard has template variables, warning is shown', async () => {
|
||||
jest.spyOn(sharePublicDashboardUtils, 'dashboardHasTemplateVariables').mockReturnValue(true);
|
||||
|
||||
await buildAndRenderScenario({
|
||||
overrides: {
|
||||
$variables: new SceneVariableSet({
|
||||
variables: [
|
||||
new CustomVariable({ name: 'custom', query: 'A,B,C', value: ['A', 'B', 'C'], text: ['A', 'B', 'C'] }),
|
||||
],
|
||||
}),
|
||||
},
|
||||
});
|
||||
expect(screen.queryByTestId(selectors.TemplateVariablesWarningAlert)).toBeInTheDocument();
|
||||
});
|
||||
it('when dashboard has unsupported datasources, warning is shown', async () => {
|
||||
await buildAndRenderScenario({
|
||||
panelOverrides: {
|
||||
$data: new SceneQueryRunner({
|
||||
data: {
|
||||
state: LoadingState.Done,
|
||||
series: [],
|
||||
timeRange: getDefaultTimeRange(),
|
||||
},
|
||||
datasource: { uid: 'my-uid' },
|
||||
queries: [{ query: 'QueryA', refId: 'A' }],
|
||||
}),
|
||||
},
|
||||
});
|
||||
expect(await screen.findByTestId(selectors.UnsupportedDataSourcesWarningAlert)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
async function buildAndRenderScenario({
|
||||
overrides,
|
||||
panelOverrides,
|
||||
}: {
|
||||
overrides?: Partial<DashboardSceneState>;
|
||||
panelOverrides?: Partial<VizPanelState>;
|
||||
}) {
|
||||
const drawer = new ShareDrawer({ shareView: shareDashboardType.publicDashboard });
|
||||
|
||||
const scene = new DashboardScene({
|
||||
title: 'hello',
|
||||
uid: 'dash-1',
|
||||
meta: {
|
||||
canEdit: true,
|
||||
},
|
||||
$timeRange: new SceneTimeRange({}),
|
||||
body: new SceneGridLayout({
|
||||
children: [
|
||||
new DashboardGridItem({
|
||||
key: 'griditem-1',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 10,
|
||||
height: 12,
|
||||
body: new VizPanel({
|
||||
title: 'Panel A',
|
||||
pluginId: 'table',
|
||||
key: 'panel-12',
|
||||
...panelOverrides,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
overlay: drawer,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
activateFullSceneTree(scene);
|
||||
|
||||
render(<drawer.Component model={drawer} />);
|
||||
|
||||
await waitForElementToBeRemoved(screen.getByText('Loading configuration'));
|
||||
|
||||
return drawer.Component;
|
||||
}
|
@ -31,31 +31,10 @@ import { EmailSharing } from './EmailShare/EmailSharing';
|
||||
import { PublicSharing } from './PublicShare/PublicSharing';
|
||||
import ShareAlerts from './ShareAlerts';
|
||||
import ShareTypeSelect from './ShareTypeSelect';
|
||||
import { getAnyOneWithTheLinkShareOption, getOnlySpecificPeopleShareOption } from './utils';
|
||||
|
||||
const selectors = e2eSelectors.pages.ShareDashboardDrawer.ShareExternally;
|
||||
|
||||
export const getAnyOneWithTheLinkShareOption = () => {
|
||||
return {
|
||||
label: t('public-dashboard.share-externally.public-share-type-option-label', 'Anyone with the link'),
|
||||
description: t(
|
||||
'public-dashboard.share-externally.public-share-type-option-description',
|
||||
'Anyone with the link can access dashboard'
|
||||
),
|
||||
value: PublicDashboardShareType.PUBLIC,
|
||||
icon: 'globe',
|
||||
};
|
||||
};
|
||||
|
||||
const getOnlySpecificPeopleShareOption = () => ({
|
||||
label: t('public-dashboard.share-externally.email-share-type-option-label', 'Only specific people'),
|
||||
description: t(
|
||||
'public-dashboard.share-externally.email-share-type-option-description',
|
||||
'Only people with the link can access dashboard'
|
||||
),
|
||||
value: PublicDashboardShareType.EMAIL,
|
||||
icon: 'users-alt',
|
||||
});
|
||||
|
||||
const getShareExternallyOptions = () => {
|
||||
return isEmailSharingEnabled()
|
||||
? [getOnlySpecificPeopleShareOption(), getAnyOneWithTheLinkShareOption()]
|
||||
@ -190,7 +169,7 @@ function Actions({ publicDashboard, onRevokeClick }: { publicDashboard: PublicDa
|
||||
<div className={styles.actionsContainer}>
|
||||
<Stack gap={1} flex={1} direction={{ xs: 'column', sm: 'row' }}>
|
||||
<ClipboardButton
|
||||
data-testid={selectors.copyUrlButton}
|
||||
data-testid={selectors.Configuration.copyUrlButton}
|
||||
variant="primary"
|
||||
fill="outline"
|
||||
icon="link"
|
||||
@ -205,6 +184,7 @@ function Actions({ publicDashboard, onRevokeClick }: { publicDashboard: PublicDa
|
||||
fill="outline"
|
||||
disabled={isUpdateLoading || !hasWritePermissions}
|
||||
onClick={onRevokeClick}
|
||||
data-testid={selectors.Configuration.revokeAccessButton}
|
||||
>
|
||||
<Trans i18nKey="public-dashboard.share-externally.revoke-access-button">Revoke access</Trans>
|
||||
</Button>
|
||||
@ -222,6 +202,7 @@ function Actions({ publicDashboard, onRevokeClick }: { publicDashboard: PublicDa
|
||||
}
|
||||
onClick={onPauseOrResumeClick}
|
||||
disabled={isUpdateLoading || !hasWritePermissions}
|
||||
data-testid={selectors.Configuration.toggleAccessButton}
|
||||
>
|
||||
{publicDashboard.isEnabled ? (
|
||||
<Trans i18nKey="public-dashboard.share-externally.pause-access-button">Pause access</Trans>
|
||||
|
@ -18,7 +18,7 @@ import { AccessControlAction } from 'app/types';
|
||||
|
||||
import { useShareDrawerContext } from '../../ShareDrawer/ShareDrawerContext';
|
||||
|
||||
import { getAnyOneWithTheLinkShareOption } from './ShareExternally';
|
||||
import { getAnyOneWithTheLinkShareOption } from './utils';
|
||||
|
||||
const selectors = e2eSelectors.pages.ShareDashboardDrawer.ShareExternally;
|
||||
export default function ShareTypeSelect({
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { PublicDashboardShareType } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
||||
|
||||
import { t } from '../../../../../core/internationalization';
|
||||
|
||||
export const getAnyOneWithTheLinkShareOption = () => {
|
||||
return {
|
||||
label: t('public-dashboard.share-externally.public-share-type-option-label', 'Anyone with the link'),
|
||||
description: t(
|
||||
'public-dashboard.share-externally.public-share-type-option-description',
|
||||
'Anyone with the link can access dashboard'
|
||||
),
|
||||
value: PublicDashboardShareType.PUBLIC,
|
||||
icon: 'globe',
|
||||
};
|
||||
};
|
||||
|
||||
export const getOnlySpecificPeopleShareOption = () => ({
|
||||
label: t('public-dashboard.share-externally.email-share-type-option-label', 'Only specific people'),
|
||||
description: t(
|
||||
'public-dashboard.share-externally.email-share-type-option-description',
|
||||
'Only people with the link can access dashboard'
|
||||
),
|
||||
value: PublicDashboardShareType.EMAIL,
|
||||
icon: 'users-alt',
|
||||
});
|
@ -1,6 +1,7 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { SceneComponentProps } from '@grafana/scenes';
|
||||
import { Alert, ClipboardButton, Divider, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
@ -9,6 +10,8 @@ import ShareInternallyConfiguration from '../../ShareInternallyConfiguration';
|
||||
import { ShareLinkTab, ShareLinkTabState } from '../../ShareLinkTab';
|
||||
import { getShareLinkConfiguration, updateShareLinkConfiguration } from '../utils';
|
||||
|
||||
const selectors = e2eSelectors.pages.ShareDashboardDrawer.ShareInternally;
|
||||
|
||||
export class ShareInternally extends ShareLinkTab {
|
||||
static Component = ShareInternallyRenderer;
|
||||
|
||||
@ -65,7 +68,7 @@ function ShareInternallyRenderer({ model }: SceneComponentProps<ShareInternally>
|
||||
const { useLockedTime, useShortUrl, selectedTheme, isBuildUrlLoading } = model.useState();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={selectors.container}>
|
||||
<Alert severity="info" title={t('link.share.config-alert-title', 'Link settings')}>
|
||||
<Trans i18nKey="link.share.config-alert-description">
|
||||
Updating your settings will modify the default copy link to include these changes. Please note that these
|
||||
@ -99,11 +102,12 @@ function ShareInternallyRenderer({ model }: SceneComponentProps<ShareInternally>
|
||||
getText={model.getShareUrl}
|
||||
onClipboardCopy={model.onCopy}
|
||||
className={styles.copyButtonContainer}
|
||||
data-testid={selectors.copyUrlButton}
|
||||
>
|
||||
<Trans i18nKey="link.share.copy-link-button">Copy link</Trans>
|
||||
</ClipboardButton>
|
||||
</Stack>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,12 @@ const CreateSnapshotActions = ({
|
||||
onCreateClick: (isExternal?: boolean) => void;
|
||||
}) => (
|
||||
<Stack gap={1} flex={1} direction={{ xs: 'column', sm: 'row' }}>
|
||||
<Button variant="primary" disabled={isLoading} onClick={() => onCreateClick()}>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={isLoading}
|
||||
onClick={() => onCreateClick()}
|
||||
data-testid={selectors.publishSnapshot}
|
||||
>
|
||||
<Trans i18nKey="snapshot.share.local-button">Publish snapshot</Trans>
|
||||
</Button>
|
||||
{sharingOptions?.externalEnabled && (
|
||||
@ -154,7 +159,13 @@ const UpsertSnapshotActions = ({
|
||||
onNewSnapshotClick: () => void;
|
||||
}) => (
|
||||
<Stack justifyContent="flex-start" gap={1} direction={{ xs: 'column', sm: 'row' }}>
|
||||
<ClipboardButton icon="link" variant="primary" fill="outline" getText={() => url}>
|
||||
<ClipboardButton
|
||||
icon="link"
|
||||
variant="primary"
|
||||
fill="outline"
|
||||
getText={() => url}
|
||||
data-testid={selectors.copyUrlButton}
|
||||
>
|
||||
<Trans i18nKey="snapshot.share.copy-link-button">Copy link</Trans>
|
||||
</ClipboardButton>
|
||||
<Button icon="trash-alt" variant="destructive" fill="outline" onClick={onDeleteClick}>
|
||||
|
@ -0,0 +1,77 @@
|
||||
import { act, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { locationService, setPluginImportUtils } from '@grafana/runtime';
|
||||
import { SceneGridLayout, SceneTimeRange, UrlSyncContextProvider } from '@grafana/scenes';
|
||||
|
||||
import { render } from '../../../../../test/test-utils';
|
||||
import { shareDashboardType } from '../../../dashboard/components/ShareModal/utils';
|
||||
import { DashboardScene } from '../../scene/DashboardScene';
|
||||
import { activateFullSceneTree } from '../../utils/test-utils';
|
||||
|
||||
import { ShareDrawer } from './ShareDrawer';
|
||||
|
||||
setPluginImportUtils({
|
||||
importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})),
|
||||
getPanelPluginFromCache: (id: string) => undefined,
|
||||
});
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
useChromeHeaderHeight: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn().mockReturnValue({
|
||||
pathname: '/d/dash-1',
|
||||
hash: '',
|
||||
state: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('ShareDrawer', () => {
|
||||
it('removes shareView query param from url when it is closed', async () => {
|
||||
const { dashboard } = await buildAndRenderScenario();
|
||||
|
||||
render(
|
||||
<UrlSyncContextProvider scene={dashboard}>
|
||||
<dashboard.Component model={dashboard} />
|
||||
</UrlSyncContextProvider>
|
||||
);
|
||||
|
||||
act(() => locationService.partial({ shareView: 'link' }));
|
||||
|
||||
expect(locationService.getSearch().get('shareView')).toBe('link');
|
||||
expect(await screen.findByText('Share externally')).toBeInTheDocument();
|
||||
const closeButton = await screen.findByTestId(selectors.components.Drawer.General.close);
|
||||
await act(() => userEvent.click(closeButton));
|
||||
|
||||
expect(locationService.getSearch().get('shareView')).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
async function buildAndRenderScenario() {
|
||||
const drawer = new ShareDrawer({ shareView: shareDashboardType.publicDashboard });
|
||||
|
||||
const dashboard = new DashboardScene({
|
||||
title: 'hello',
|
||||
uid: 'dash-1',
|
||||
meta: {
|
||||
canEdit: true,
|
||||
},
|
||||
$timeRange: new SceneTimeRange({}),
|
||||
body: new SceneGridLayout({
|
||||
children: [],
|
||||
}),
|
||||
overlay: drawer,
|
||||
});
|
||||
|
||||
drawer.activate();
|
||||
activateFullSceneTree(dashboard);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
return { dashboard };
|
||||
}
|
@ -19,7 +19,7 @@ import { ShareDrawerContext } from './ShareDrawerContext';
|
||||
export interface ShareDrawerState extends SceneObjectState {
|
||||
panelRef?: SceneObjectRef<VizPanel>;
|
||||
shareView: string;
|
||||
activeShare: ShareView;
|
||||
activeShare?: ShareView;
|
||||
}
|
||||
|
||||
type CustomShareViewType = { id: string; shareOption: new (...args: SceneShareTabState[]) => ShareView };
|
||||
@ -33,7 +33,7 @@ export class ShareDrawer extends SceneObjectBase<ShareDrawerState> implements Mo
|
||||
static Component = ShareDrawerRenderer;
|
||||
|
||||
constructor(state: Omit<ShareDrawerState, 'activeShare'>) {
|
||||
super({ ...state, activeShare: new ShareInternally({}) });
|
||||
super({ ...state });
|
||||
this.addActivationHandler(() => this.buildActiveShare(state.shareView!));
|
||||
}
|
||||
|
||||
@ -63,9 +63,9 @@ function ShareDrawerRenderer({ model }: SceneComponentProps<ShareDrawer>) {
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
|
||||
return (
|
||||
<Drawer title={activeShare.getTabLabel()} onClose={model.onDismiss} size="md">
|
||||
<Drawer title={activeShare?.getTabLabel()} onClose={model.onDismiss} size="md">
|
||||
<ShareDrawerContext.Provider value={{ dashboard, onDismiss: model.onDismiss }}>
|
||||
{<activeShare.Component model={activeShare} />}
|
||||
{activeShare && <activeShare.Component model={activeShare} />}
|
||||
</ShareDrawerContext.Provider>
|
||||
</Drawer>
|
||||
);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { Label, Spinner, Stack, Switch } from '@grafana/ui';
|
||||
|
||||
import { t, Trans } from '../../../core/internationalization';
|
||||
@ -13,6 +14,8 @@ interface Props {
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const selectors = e2eSelectors.pages.ShareDashboardDrawer.ShareInternally;
|
||||
|
||||
export default function ShareInternallyConfiguration({
|
||||
useLockedTime,
|
||||
onToggleLockedTime,
|
||||
@ -32,6 +35,7 @@ export default function ShareInternallyConfiguration({
|
||||
id="share-current-time-range"
|
||||
value={useLockedTime}
|
||||
onChange={onToggleLockedTime}
|
||||
data-testid={selectors.lockTimeRangeSwitch}
|
||||
/>
|
||||
<Label
|
||||
description={t(
|
||||
@ -48,6 +52,7 @@ export default function ShareInternallyConfiguration({
|
||||
value={useShortUrl}
|
||||
label={t('link.share.short-url-label', 'Shorten link')}
|
||||
onChange={onUrlShorten}
|
||||
data-testid={selectors.shortenUrlSwitch}
|
||||
/>
|
||||
<Label>
|
||||
<Trans i18nKey="link.share.short-url-label">Shorten link</Trans>
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { Alert, Button, Stack } from '@grafana/ui';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
|
||||
const EMAIL_SHARING_URL = 'https://grafana.com/docs/grafana/latest/dashboards/dashboard-public/#email-sharing';
|
||||
|
||||
const selectors = e2eSelectors.pages.ShareDashboardDrawer.ShareExternally;
|
||||
|
||||
export function EmailSharingPricingAlert() {
|
||||
return (
|
||||
<Alert title="" severity="info" bottomSpacing={0}>
|
||||
<Alert title="" severity="info" bottomSpacing={0} data-testid={selectors.emailSharingAlert}>
|
||||
<Stack justifyContent="space-between" gap={2} alignItems="center">
|
||||
<Trans i18nKey="public-dashboard.email-sharing.alert-text">
|
||||
Sharing dashboards by email is billed per user for the duration of the 30-day token, regardless of how many
|
||||
|
@ -1,10 +1,14 @@
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { Alert, Button, Stack } from '@grafana/ui';
|
||||
|
||||
import { Trans } from '../../../../../../core/internationalization';
|
||||
|
||||
const PUBLIC_DASHBOARD_URL = 'https://grafana.com/docs/grafana/latest/dashboards/dashboard-public/';
|
||||
|
||||
const selectors = e2eSelectors.pages.ShareDashboardDrawer.ShareExternally;
|
||||
|
||||
export const PublicDashboardAlert = () => (
|
||||
<Alert title="" severity="info" bottomSpacing={0}>
|
||||
<Alert title="" severity="info" bottomSpacing={0} data-testid={selectors.publicAlert}>
|
||||
<Stack justifyContent="space-between" gap={2} alignItems="center">
|
||||
<Trans i18nKey="public-dashboard.public-sharing.alert-text">
|
||||
Sharing this dashboard externally makes it entirely accessible to anyone with the link.
|
||||
|
Loading…
Reference in New Issue
Block a user