Skip to content

Commit

Permalink
Scenes: Refactor original snapshot button in a new component (grafana…
Browse files Browse the repository at this point in the history
  • Loading branch information
evictorero authored Feb 13, 2024
1 parent ccb4533 commit dbde08b
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ Sensitive information stripped: queries (metric, template,annotation) and panel
| `key` | string | **Yes** | | Optional, defined the unique key of the snapshot, required if external is true |
| `name` | string | **Yes** | | Optional, name of the snapshot |
| `orgId` | uint32 | **Yes** | | org id of the snapshot |
| `originalUrl` | string | **Yes** | | original url, url of the dashboard that was snapshotted |
| `updated` | string | **Yes** | | last time when the snapshot was updated |
| `userId` | uint32 | **Yes** | | user id of the snapshot creator |
| `url` | string | No | | url of the snapshot, if snapshot was shared internally |
Expand Down
2 changes: 2 additions & 0 deletions kinds/dashboard/dashboard_kind.cue
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,8 @@ lineage: schemas: [{
external: bool @grafanamaturity(NeedsExpertReview)
// external url, if snapshot was shared in external grafana instance
externalUrl: string @grafanamaturity(NeedsExpertReview)
// original url, url of the dashboard that was snapshotted
originalUrl: string @grafanamaturity(NeedsExpertReview)
// Unique identifier of the snapshot
id: uint32 @grafanamaturity(NeedsExpertReview)
// Optional, defined the unique key of the snapshot, required if external is true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,10 @@ export interface Dashboard {
* external url, if snapshot was shared in external grafana instance
*/
externalUrl: string;
/**
* original url, url of the dashboard that was snapshotted
*/
originalUrl: string;
/**
* Unique identifier of the snapshot
*/
Expand Down
3 changes: 3 additions & 0 deletions pkg/kinds/dashboard/dashboard_spec_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/kindsysreport/codegen/report.json
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@
0
],
"description": "A Grafana dashboard.",
"grafanaMaturityCount": 103,
"grafanaMaturityCount": 105,
"lineageIsGroup": false,
"links": {
"docs": "https://grafana.com/docs/grafana/next/developers/kinds/core/dashboard/schema-reference",
Expand Down
60 changes: 0 additions & 60 deletions public/app/features/dashboard-scene/scene/DashboardScene.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { CoreApp } from '@grafana/data';
import { config, locationService } from '@grafana/runtime';
import {
sceneGraph,
SceneGridItem,
Expand All @@ -12,12 +11,10 @@ import {
VizPanel,
} from '@grafana/scenes';
import { Dashboard } from '@grafana/schema';
import { ConfirmModal } from '@grafana/ui';
import appEvents from 'app/core/app_events';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { VariablesChanged } from 'app/features/variables/types';

import { ShowModalReactEvent } from '../../../types/events';
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
import { DecoratedRevisionModel } from '../settings/VersionsEditView';
import { historySrv } from '../settings/version-history/HistorySrv';
Expand Down Expand Up @@ -208,63 +205,6 @@ describe('DashboardScene', () => {
}
});
});

describe('when opening a dashboard from a snapshot', () => {
let scene: DashboardScene;
beforeEach(async () => {
scene = buildTestScene();
locationService.push('/');
// mockLocationHref('http://snapshots.grafana.com/snapshots/dashboard/abcdefghi/my-dash');
const location = window.location;

//@ts-ignore
delete window.location;
window.location = {
...location,
href: 'http://snapshots.grafana.com/snapshots/dashboard/abcdefghi/my-dash',
};
jest.spyOn(appEvents, 'publish');
});

config.appUrl = 'http://snapshots.grafana.com/';

it('redirects to the original dashboard', () => {
scene.setInitialSaveModel({
// @ts-ignore
snapshot: { originalUrl: '/d/c0d2742f-b827-466d-9269-fb34d6af24ff' },
});

// Call the function
scene.onOpenSnapshotOriginalDashboard();

// Assertions
expect(appEvents.publish).toHaveBeenCalledTimes(0);
expect(locationService.getLocation().pathname).toEqual('/d/c0d2742f-b827-466d-9269-fb34d6af24ff');
expect(window.location.href).toBe('http://snapshots.grafana.com/snapshots/dashboard/abcdefghi/my-dash');
});

it('opens a confirmation modal', () => {
scene.setInitialSaveModel({
// @ts-ignore
snapshot: { originalUrl: 'http://www.anotherdomain.com/' },
});

// Call the function
scene.onOpenSnapshotOriginalDashboard();

// Assertions
expect(appEvents.publish).toHaveBeenCalledTimes(1);
expect(appEvents.publish).toHaveBeenCalledWith(
new ShowModalReactEvent(
expect.objectContaining({
component: ConfirmModal,
})
)
);
expect(locationService.getLocation().pathname).toEqual('/');
expect(window.location.href).toBe('http://snapshots.grafana.com/snapshots/dashboard/abcdefghi/my-dash');
});
});
});

function buildTestScene(overrides?: Partial<DashboardSceneState>) {
Expand Down
50 changes: 3 additions & 47 deletions public/app/features/dashboard-scene/scene/DashboardScene.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { css } from '@emotion/css';
import * as H from 'history';
import React from 'react';
import { Unsubscribable } from 'rxjs';

import { AppEvents, CoreApp, DataQueryRequest, NavIndex, NavModelItem, locationUtil, textUtil } from '@grafana/data';
import { locationService, config } from '@grafana/runtime';
import { AppEvents, CoreApp, DataQueryRequest, NavIndex, NavModelItem, locationUtil } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import {
dataLayers,
getUrlSyncManager,
Expand All @@ -25,7 +23,6 @@ import {
VizPanel,
} from '@grafana/scenes';
import { Dashboard, DashboardLink } from '@grafana/schema';
import { ConfirmModal } from '@grafana/ui';
import appEvents from 'app/core/app_events';
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
import { getNavModel } from 'app/core/selectors/navModel';
Expand All @@ -35,7 +32,7 @@ import { DashboardModel } from 'app/features/dashboard/state';
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
import { VariablesChanged } from 'app/features/variables/types';
import { DashboardDTO, DashboardMeta, SaveDashboardResponseDTO } from 'app/types';
import { ShowModalReactEvent, ShowConfirmModalEvent } from 'app/types/events';
import { ShowConfirmModalEvent } from 'app/types/events';

import { PanelEditor } from '../panel-edit/PanelEditor';
import { SaveDashboardDrawer } from '../saving/SaveDashboardDrawer';
Expand Down Expand Up @@ -503,47 +500,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
}
}

public onOpenSnapshotOriginalDashboard = () => {
// @ts-ignore
const relativeURL = this.getInitialSaveModel()?.snapshot?.originalUrl ?? '';
const sanitizedRelativeURL = textUtil.sanitizeUrl(relativeURL);
try {
const sanitizedAppUrl = new URL(sanitizedRelativeURL, config.appUrl);
const appUrl = new URL(config.appUrl);
if (sanitizedAppUrl.host !== appUrl.host) {
appEvents.publish(
new ShowModalReactEvent({
component: ConfirmModal,
props: {
title: 'Proceed to external site?',
modalClass: css({
width: 'max-content',
maxWidth: '80vw',
}),
body: (
<>
<p>
{`This link connects to an external website at`} <code>{relativeURL}</code>
</p>
<p>{"Are you sure you'd like to proceed?"}</p>
</>
),
confirmVariant: 'primary',
confirmText: 'Proceed',
onConfirm: () => {
window.location.href = sanitizedAppUrl.href;
},
},
})
);
} else {
locationService.push(sanitizedRelativeURL);
}
} catch (err) {
console.error('Failed to open original dashboard', err);
}
};

public onOpenSettings = () => {
locationService.partial({ editview: 'settings' });
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';

import { config, locationService } from '@grafana/runtime';
import { ConfirmModal } from '@grafana/ui';

import appEvents from '../../../core/app_events';
import { ShowModalReactEvent } from '../../../types/events';

import { GoToSnapshotOriginButton } from './GoToSnapshotOriginButton';

describe('GoToSnapshotOriginButton component', () => {
beforeEach(async () => {
locationService.push('/');
const location = window.location;
//@ts-ignore
delete window.location;
window.location = {
...location,
href: 'http://snapshots.grafana.com/snapshots/dashboard/abcdefghi/my-dash',
};
jest.spyOn(appEvents, 'publish');
});
config.appUrl = 'http://snapshots.grafana.com/';

it('renders button and triggers onClick redirects to the original dashboard', () => {
render(<GoToSnapshotOriginButton originalURL={'/d/c0d2742f-b827-466d-9269-fb34d6af24ff'} />);

// Check if the button renders with the correct testid
expect(screen.getByTestId('button-snapshot')).toBeInTheDocument();

// Simulate a button click
fireEvent.click(screen.getByTestId('button-snapshot'));

expect(appEvents.publish).toHaveBeenCalledTimes(0);
expect(locationService.getLocation().pathname).toEqual('/d/c0d2742f-b827-466d-9269-fb34d6af24ff');
expect(window.location.href).toBe('http://snapshots.grafana.com/snapshots/dashboard/abcdefghi/my-dash');
});

it('renders button and triggers onClick opens a confirmation modal', () => {
render(<GoToSnapshotOriginButton originalURL={'http://www.anotherdomain.com/'} />);

// Check if the button renders with the correct testid
expect(screen.getByTestId('button-snapshot')).toBeInTheDocument();

// Simulate a button click
fireEvent.click(screen.getByTestId('button-snapshot'));

expect(appEvents.publish).toHaveBeenCalledTimes(1);
expect(appEvents.publish).toHaveBeenCalledWith(
new ShowModalReactEvent(
expect.objectContaining({
component: ConfirmModal,
})
)
);
expect(locationService.getLocation().pathname).toEqual('/');
expect(window.location.href).toBe('http://snapshots.grafana.com/snapshots/dashboard/abcdefghi/my-dash');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { css } from '@emotion/css';
import React from 'react';

import { textUtil } from '@grafana/data';
import { config, locationService } from '@grafana/runtime';
import { ConfirmModal, ToolbarButton } from '@grafana/ui';

import appEvents from '../../../core/app_events';
import { t } from '../../../core/internationalization';
import { ShowModalReactEvent } from '../../../types/events';

export function GoToSnapshotOriginButton(props: { originalURL: string }) {
return (
<ToolbarButton
key="button-snapshot"
data-testid="button-snapshot"
tooltip={t('dashboard.toolbar.open-original', 'Open original dashboard')}
icon="link"
onClick={() => onOpenSnapshotOriginalDashboard(props.originalURL)}
/>
);
}

const onOpenSnapshotOriginalDashboard = (originalUrl: string) => {
const relativeURL = originalUrl ?? '';
const sanitizedRelativeURL = textUtil.sanitizeUrl(relativeURL);
try {
const sanitizedAppUrl = new URL(sanitizedRelativeURL, config.appUrl);
const appUrl = new URL(config.appUrl);
if (sanitizedAppUrl.host !== appUrl.host) {
appEvents.publish(
new ShowModalReactEvent({
component: ConfirmModal,
props: {
title: 'Proceed to external site?',
modalClass: css({
width: 'max-content',
maxWidth: '80vw',
}),
body: (
<>
<p>
{`This link connects to an external website at`} <code>{relativeURL}</code>
</p>
<p>{"Are you sure you'd like to proceed?"}</p>
</>
),
confirmVariant: 'primary',
confirmText: 'Proceed',
onConfirm: () => {
window.location.href = sanitizedAppUrl.href;
},
},
})
);
} else {
locationService.push(sanitizedRelativeURL);
}
} catch (err) {
console.error('Failed to open original dashboard', err);
}
};
10 changes: 2 additions & 8 deletions public/app/features/dashboard-scene/scene/NavToolbarActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { DashboardInteractions } from '../utils/interactions';
import { dynamicDashNavActions } from '../utils/registerDynamicDashNavAction';

import { DashboardScene } from './DashboardScene';
import { GoToSnapshotOriginButton } from './GoToSnapshotOriginButton';

interface Props {
dashboard: DashboardScene;
Expand Down Expand Up @@ -89,14 +90,7 @@ export function ToolbarActions({ dashboard }: Props) {
group: 'icon-actions',
condition: meta.isSnapshot && !isEditing,
render: () => (
<ToolbarButton
key="button-snapshot"
tooltip={t('dashboard.toolbar.open-original', 'Open original dashboard')}
icon="link"
onClick={() => {
dashboard.onOpenSnapshotOriginalDashboard();
}}
/>
<GoToSnapshotOriginButton originalURL={dashboard.getInitialSaveModel()?.snapshot?.originalUrl ?? ''} />
),
});

Expand Down

0 comments on commit dbde08b

Please sign in to comment.