BrowseDashboardsPage: added new pull request banner on new folder creation (#107596)
* BrowseDashboardsPage: added ProvisionedFolderPreviewBanner to display new provisioned folder on branch alert * add comment * fix test * i18n * Added test for PreviewBannerViewPR * fix test, i18n fix
This commit is contained in:
@@ -163,17 +163,17 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
|
|||||||
|
|
||||||
describe('at the root level', () => {
|
describe('at the root level', () => {
|
||||||
it('displays "Dashboards" as the page title', async () => {
|
it('displays "Dashboards" as the page title', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('heading', { name: 'Dashboards' })).toBeInTheDocument();
|
expect(await screen.findByRole('heading', { name: 'Dashboards' })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays a search input', async () => {
|
it('displays a search input', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByPlaceholderText('Search for dashboards and folders')).toBeInTheDocument();
|
expect(await screen.findByPlaceholderText('Search for dashboards and folders')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the "New" button', async () => {
|
it('shows the "New" button', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('button', { name: 'New' })).toBeInTheDocument();
|
expect(await screen.findByRole('button', { name: 'New' })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -185,25 +185,25 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
|
|||||||
canCreateFolders: false,
|
canCreateFolders: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('heading', { name: 'Dashboards' })).toBeInTheDocument();
|
expect(await screen.findByRole('heading', { name: 'Dashboards' })).toBeInTheDocument();
|
||||||
expect(screen.queryByRole('button', { name: 'New' })).not.toBeInTheDocument();
|
expect(screen.queryByRole('button', { name: 'New' })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not show "Folder actions"', async () => {
|
it('does not show "Folder actions"', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('heading', { name: 'Dashboards' })).toBeInTheDocument();
|
expect(await screen.findByRole('heading', { name: 'Dashboards' })).toBeInTheDocument();
|
||||||
expect(screen.queryByRole('button', { name: 'Folder actions' })).not.toBeInTheDocument();
|
expect(screen.queryByRole('button', { name: 'Folder actions' })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not show an "Edit title" button', async () => {
|
it('does not show an "Edit title" button', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('heading', { name: 'Dashboards' })).toBeInTheDocument();
|
expect(await screen.findByRole('heading', { name: 'Dashboards' })).toBeInTheDocument();
|
||||||
expect(screen.queryByRole('button', { name: 'Edit title' })).not.toBeInTheDocument();
|
expect(screen.queryByRole('button', { name: 'Edit title' })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not show any tabs', async () => {
|
it('does not show any tabs', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('heading', { name: 'Dashboards' })).toBeInTheDocument();
|
expect(await screen.findByRole('heading', { name: 'Dashboards' })).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.queryByRole('tab', { name: 'Dashboards' })).not.toBeInTheDocument();
|
expect(screen.queryByRole('tab', { name: 'Dashboards' })).not.toBeInTheDocument();
|
||||||
@@ -212,7 +212,7 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('displays the filters and hides the actions initially', async () => {
|
it('displays the filters and hides the actions initially', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
await screen.findByPlaceholderText('Search for dashboards and folders');
|
await screen.findByPlaceholderText('Search for dashboards and folders');
|
||||||
|
|
||||||
expect(await screen.findByText('Sort')).toBeInTheDocument();
|
expect(await screen.findByText('Sort')).toBeInTheDocument();
|
||||||
@@ -223,7 +223,7 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('selecting an item hides the filters and shows the actions instead', async () => {
|
it('selecting an item hides the filters and shows the actions instead', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
|
|
||||||
const checkbox = await screen.findByTestId(selectors.pages.BrowseDashboards.table.checkbox(dashbdD.item.uid));
|
const checkbox = await screen.findByTestId(selectors.pages.BrowseDashboards.table.checkbox(dashbdD.item.uid));
|
||||||
await userEvent.click(checkbox);
|
await userEvent.click(checkbox);
|
||||||
@@ -238,7 +238,7 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('navigating into a child item resets the selected state', async () => {
|
it('navigating into a child item resets the selected state', async () => {
|
||||||
const { rerender } = render(<BrowseDashboardsPage />);
|
const { rerender } = render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
|
|
||||||
const checkbox = await screen.findByTestId(selectors.pages.BrowseDashboards.table.checkbox(folderA.item.uid));
|
const checkbox = await screen.findByTestId(selectors.pages.BrowseDashboards.table.checkbox(folderA.item.uid));
|
||||||
await userEvent.click(checkbox);
|
await userEvent.click(checkbox);
|
||||||
@@ -248,7 +248,7 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
|
|||||||
expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument();
|
expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument();
|
||||||
|
|
||||||
(useParams as jest.Mock).mockReturnValue({ uid: folderA.item.uid });
|
(useParams as jest.Mock).mockReturnValue({ uid: folderA.item.uid });
|
||||||
rerender(<BrowseDashboardsPage />);
|
rerender(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
|
|
||||||
// Check the filters are now visible again
|
// Check the filters are now visible again
|
||||||
expect(await screen.findByText('Filter by tag')).toBeInTheDocument();
|
expect(await screen.findByText('Filter by tag')).toBeInTheDocument();
|
||||||
@@ -266,17 +266,17 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('shows the folder name as the page title', async () => {
|
it('shows the folder name as the page title', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('heading', { name: folderA.item.title })).toBeInTheDocument();
|
expect(await screen.findByRole('heading', { name: folderA.item.title })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays a search input', async () => {
|
it('displays a search input', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByPlaceholderText('Search for dashboards and folders')).toBeInTheDocument();
|
expect(await screen.findByPlaceholderText('Search for dashboards and folders')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the "New" button', async () => {
|
it('shows the "New" button', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('button', { name: 'New' })).toBeInTheDocument();
|
expect(await screen.findByRole('button', { name: 'New' })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -288,13 +288,13 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
|
|||||||
canCreateFolders: false,
|
canCreateFolders: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('heading', { name: folderA.item.title })).toBeInTheDocument();
|
expect(await screen.findByRole('heading', { name: folderA.item.title })).toBeInTheDocument();
|
||||||
expect(screen.queryByRole('button', { name: 'New' })).not.toBeInTheDocument();
|
expect(screen.queryByRole('button', { name: 'New' })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the "Folder actions" button', async () => {
|
it('shows the "Folder actions" button', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('button', { name: 'Folder actions' })).toBeInTheDocument();
|
expect(await screen.findByRole('button', { name: 'Folder actions' })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -308,13 +308,13 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
|
|||||||
canViewPermissions: false,
|
canViewPermissions: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('heading', { name: folderA.item.title })).toBeInTheDocument();
|
expect(await screen.findByRole('heading', { name: folderA.item.title })).toBeInTheDocument();
|
||||||
expect(screen.queryByRole('button', { name: 'Folder actions' })).not.toBeInTheDocument();
|
expect(screen.queryByRole('button', { name: 'Folder actions' })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows an "Edit title" button', async () => {
|
it('shows an "Edit title" button', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('button', { name: 'Edit title' })).toBeInTheDocument();
|
expect(await screen.findByRole('button', { name: 'Edit title' })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -325,13 +325,13 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
|
|||||||
canEditFolders: false,
|
canEditFolders: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('heading', { name: folderA.item.title })).toBeInTheDocument();
|
expect(await screen.findByRole('heading', { name: folderA.item.title })).toBeInTheDocument();
|
||||||
expect(screen.queryByRole('button', { name: 'Edit title' })).not.toBeInTheDocument();
|
expect(screen.queryByRole('button', { name: 'Edit title' })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays all the folder tabs and shows the "Dashboards" tab as selected', async () => {
|
it('displays all the folder tabs and shows the "Dashboards" tab as selected', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
expect(await screen.findByRole('tab', { name: 'Dashboards' })).toBeInTheDocument();
|
expect(await screen.findByRole('tab', { name: 'Dashboards' })).toBeInTheDocument();
|
||||||
expect(await screen.findByRole('tab', { name: 'Dashboards' })).toHaveAttribute('aria-selected', 'true');
|
expect(await screen.findByRole('tab', { name: 'Dashboards' })).toHaveAttribute('aria-selected', 'true');
|
||||||
|
|
||||||
@@ -343,7 +343,7 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('displays the filters and hides the actions initially', async () => {
|
it('displays the filters and hides the actions initially', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
await screen.findByPlaceholderText('Search for dashboards and folders');
|
await screen.findByPlaceholderText('Search for dashboards and folders');
|
||||||
|
|
||||||
expect(await screen.findByText('Sort')).toBeInTheDocument();
|
expect(await screen.findByText('Sort')).toBeInTheDocument();
|
||||||
@@ -354,7 +354,7 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('selecting an item hides the filters and shows the actions instead', async () => {
|
it('selecting an item hides the filters and shows the actions instead', async () => {
|
||||||
render(<BrowseDashboardsPage />);
|
render(<BrowseDashboardsPage queryParams={{}} />);
|
||||||
|
|
||||||
const checkbox = await screen.findByTestId(
|
const checkbox = await screen.findByTestId(
|
||||||
selectors.pages.BrowseDashboards.table.checkbox(folderA_folderA.item.uid)
|
selectors.pages.BrowseDashboards.table.checkbox(folderA_folderA.item.uid)
|
||||||
|
|||||||
@@ -25,13 +25,14 @@ import { BrowseFilters } from './components/BrowseFilters';
|
|||||||
import { BrowseView } from './components/BrowseView';
|
import { BrowseView } from './components/BrowseView';
|
||||||
import CreateNewButton from './components/CreateNewButton';
|
import CreateNewButton from './components/CreateNewButton';
|
||||||
import { FolderActionsButton } from './components/FolderActionsButton';
|
import { FolderActionsButton } from './components/FolderActionsButton';
|
||||||
|
import { ProvisionedFolderPreviewBanner } from './components/ProvisionedFolderPreviewBanner';
|
||||||
import { SearchView } from './components/SearchView';
|
import { SearchView } from './components/SearchView';
|
||||||
import { getFolderPermissions } from './permissions';
|
import { getFolderPermissions } from './permissions';
|
||||||
import { useHasSelection } from './state/hooks';
|
import { useHasSelection } from './state/hooks';
|
||||||
import { setAllSelection } from './state/slice';
|
import { setAllSelection } from './state/slice';
|
||||||
|
|
||||||
// New Browse/Manage/Search Dashboards views for nested folders
|
// New Browse/Manage/Search Dashboards views for nested folders
|
||||||
const BrowseDashboardsPage = memo(() => {
|
const BrowseDashboardsPage = memo(({ queryParams }: { queryParams: Record<string, string> }) => {
|
||||||
const { uid: folderUID } = useParams();
|
const { uid: folderUID } = useParams();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
@@ -159,6 +160,7 @@ const BrowseDashboardsPage = memo(() => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Page.Contents className={styles.pageContents}>
|
<Page.Contents className={styles.pageContents}>
|
||||||
|
<ProvisionedFolderPreviewBanner queryParams={queryParams} />
|
||||||
<div>
|
<div>
|
||||||
<FilterInput
|
<FilterInput
|
||||||
placeholder={getSearchPlaceholder(searchState.includePanels)}
|
placeholder={getSearchPlaceholder(searchState.includePanels)}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ describe('NewProvisionedFolderForm', () => {
|
|||||||
(getAppEvents as jest.Mock).mockReturnValue(mockAppEvents);
|
(getAppEvents as jest.Mock).mockReturnValue(mockAppEvents);
|
||||||
|
|
||||||
// Mock usePullRequestParam
|
// Mock usePullRequestParam
|
||||||
(usePullRequestParam as jest.Mock).mockReturnValue(null);
|
(usePullRequestParam as jest.Mock).mockReturnValue({});
|
||||||
|
|
||||||
// Mock useCreateRepositoryFilesWithPathMutation
|
// Mock useCreateRepositoryFilesWithPathMutation
|
||||||
const mockCreate = jest.fn();
|
const mockCreate = jest.fn();
|
||||||
@@ -409,7 +409,7 @@ describe('NewProvisionedFolderForm', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should show PR link when PR URL is available', () => {
|
it('should show PR link when PR URL is available', () => {
|
||||||
(usePullRequestParam as jest.Mock).mockReturnValue('https://github.com/grafana/grafana/pull/1234');
|
(usePullRequestParam as jest.Mock).mockReturnValue({ prURL: 'https://github.com/grafana/grafana/pull/1234' });
|
||||||
|
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FormContent({ initialValues, repository, workflowOptions, folder, isGitHub, onDismiss }: FormProps) {
|
function FormContent({ initialValues, repository, workflowOptions, folder, isGitHub, onDismiss }: FormProps) {
|
||||||
const prURL = usePullRequestParam();
|
const { prURL } = usePullRequestParam();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [create, request] = useCreateRepositoryFilesWithPathMutation();
|
const [create, request] = useCreateRepositoryFilesWithPathMutation();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
import { CommonBannerProps } from 'app/features/dashboard-scene/saving/provisioned/DashboardPreviewBanner';
|
||||||
|
import { PreviewBannerViewPR } from 'app/features/dashboard-scene/saving/provisioned/PreviewBannerViewPR';
|
||||||
|
import { usePullRequestParam } from 'app/features/provisioning/hooks/usePullRequestParam';
|
||||||
|
|
||||||
|
export function ProvisionedFolderPreviewBanner({ queryParams }: CommonBannerProps) {
|
||||||
|
const provisioningEnabled = config.featureToggles.provisioning;
|
||||||
|
const { prURL, newPrURL } = usePullRequestParam();
|
||||||
|
|
||||||
|
if (!provisioningEnabled || 'kiosk' in queryParams) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prURL) {
|
||||||
|
return <PreviewBannerViewPR prParam={prURL} isFolder />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPrURL) {
|
||||||
|
return <PreviewBannerViewPR prParam={newPrURL} isFolder isNewPr />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import { textUtil } from '@grafana/data';
|
|
||||||
import { Trans, t } from '@grafana/i18n';
|
import { Trans, t } from '@grafana/i18n';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { Alert, Icon, Stack } from '@grafana/ui';
|
import { Alert } from '@grafana/ui';
|
||||||
import { useGetRepositoryFilesWithPathQuery } from 'app/api/clients/provisioning/v0alpha1';
|
import { useGetRepositoryFilesWithPathQuery } from 'app/api/clients/provisioning/v0alpha1';
|
||||||
import { DashboardPageRouteSearchParams } from 'app/features/dashboard/containers/types';
|
import { DashboardPageRouteSearchParams } from 'app/features/dashboard/containers/types';
|
||||||
import { usePullRequestParam } from 'app/features/provisioning/hooks/usePullRequestParam';
|
import { usePullRequestParam } from 'app/features/provisioning/hooks/usePullRequestParam';
|
||||||
import { DashboardRoutes } from 'app/types';
|
import { DashboardRoutes } from 'app/types';
|
||||||
|
|
||||||
interface CommonBannerProps {
|
import { PreviewBannerViewPR } from './PreviewBannerViewPR';
|
||||||
|
|
||||||
|
export interface CommonBannerProps {
|
||||||
queryParams: DashboardPageRouteSearchParams;
|
queryParams: DashboardPageRouteSearchParams;
|
||||||
path?: string;
|
path?: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
@@ -19,13 +20,13 @@ interface DashboardPreviewBannerProps extends CommonBannerProps {
|
|||||||
|
|
||||||
interface DashboardPreviewBannerContentProps extends Required<Omit<CommonBannerProps, 'route'>> {}
|
interface DashboardPreviewBannerContentProps extends Required<Omit<CommonBannerProps, 'route'>> {}
|
||||||
|
|
||||||
const commonAlertProps = {
|
export const commonAlertProps = {
|
||||||
severity: 'info' as const,
|
severity: 'info' as const,
|
||||||
style: { flex: 0 } as const,
|
style: { flex: 0 } as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
function DashboardPreviewBannerContent({ queryParams, slug, path }: DashboardPreviewBannerContentProps) {
|
function DashboardPreviewBannerContent({ queryParams, slug, path }: DashboardPreviewBannerContentProps) {
|
||||||
const prParam = usePullRequestParam();
|
const { prURL } = usePullRequestParam();
|
||||||
const file = useGetRepositoryFilesWithPathQuery({ name: slug, path, ref: queryParams.ref });
|
const file = useGetRepositoryFilesWithPathQuery({ name: slug, path, ref: queryParams.ref });
|
||||||
|
|
||||||
if (file.data?.errors) {
|
if (file.data?.errors) {
|
||||||
@@ -43,56 +44,14 @@ function DashboardPreviewBannerContent({ queryParams, slug, path }: DashboardPre
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This page was loaded with a `pull_request_url` in the URL
|
// This page was loaded with a `pull_request_url` in the URL
|
||||||
if (prParam?.length) {
|
if (prURL?.length) {
|
||||||
return (
|
return <PreviewBannerViewPR prParam={prURL} />;
|
||||||
<Alert
|
|
||||||
{...commonAlertProps}
|
|
||||||
title={t(
|
|
||||||
'dashboard-scene.dashboard-preview-banner.title-dashboard-loaded-request-git-hub',
|
|
||||||
'This dashboard is loaded from a pull request in GitHub.'
|
|
||||||
)}
|
|
||||||
buttonContent={
|
|
||||||
<Stack alignItems="center">
|
|
||||||
<Trans i18nKey="dashboard-scene.dashboard-preview-banner.view-pull-request-in-git-hub">
|
|
||||||
View pull request in GitHub
|
|
||||||
</Trans>
|
|
||||||
<Icon name="external-link-alt" />
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
onRemove={() => window.open(textUtil.sanitizeUrl(prParam), '_blank')}
|
|
||||||
>
|
|
||||||
<Trans i18nKey="dashboard-scene.dashboard-preview-banner.value-not-saved">
|
|
||||||
The value is not yet saved in the Grafana database
|
|
||||||
</Trans>
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a GitHub link
|
// Check if this is a GitHub link
|
||||||
const githubURL = file.data?.urls?.newPullRequestURL ?? file.data?.urls?.compareURL;
|
const githubURL = file.data?.urls?.newPullRequestURL ?? file.data?.urls?.compareURL;
|
||||||
if (githubURL) {
|
if (githubURL) {
|
||||||
return (
|
return <PreviewBannerViewPR prParam={githubURL} isNewPr />;
|
||||||
<Alert
|
|
||||||
{...commonAlertProps}
|
|
||||||
title={t(
|
|
||||||
'dashboard-scene.dashboard-preview-banner.title-dashboard-loaded-branch-git-hub',
|
|
||||||
'This dashboard is loaded from a branch in GitHub.'
|
|
||||||
)}
|
|
||||||
buttonContent={
|
|
||||||
<Stack alignItems="center">
|
|
||||||
<Trans i18nKey="dashboard-scene.dashboard-preview-banner.open-pull-request-in-git-hub">
|
|
||||||
Open pull request in GitHub
|
|
||||||
</Trans>
|
|
||||||
<Icon name="external-link-alt" />
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
onRemove={() => window.open(textUtil.sanitizeUrl(githubURL), '_blank')}
|
|
||||||
>
|
|
||||||
<Trans i18nKey="dashboard-scene.dashboard-preview-banner.not-saved">
|
|
||||||
The value is not yet saved in the Grafana database
|
|
||||||
</Trans>
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
import { textUtil } from '@grafana/data';
|
||||||
|
|
||||||
|
import { PreviewBannerViewPR } from './PreviewBannerViewPR';
|
||||||
|
|
||||||
|
jest.mock('@grafana/data', () => ({
|
||||||
|
...jest.requireActual('@grafana/data'),
|
||||||
|
textUtil: {
|
||||||
|
sanitizeUrl: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@grafana/i18n', () => ({
|
||||||
|
t: jest.fn((key: string, defaultValue: string) => defaultValue),
|
||||||
|
Trans: ({ children }: { children: React.ReactNode }) => children,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockTextUtil = jest.mocked(textUtil);
|
||||||
|
|
||||||
|
function setup(props: { prParam: string; isFolder?: boolean; isNewPr?: boolean } = { prParam: 'test-url' }) {
|
||||||
|
const defaultProps = {
|
||||||
|
isFolder: false,
|
||||||
|
isNewPr: false,
|
||||||
|
...props,
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderResult = render(<PreviewBannerViewPR {...defaultProps} />);
|
||||||
|
|
||||||
|
return { renderResult, props: defaultProps };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('PreviewBannerViewPR', () => {
|
||||||
|
let windowOpenSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
Object.defineProperty(window, 'open', {
|
||||||
|
writable: true,
|
||||||
|
value: jest.fn(),
|
||||||
|
});
|
||||||
|
windowOpenSpy = jest.spyOn(window, 'open');
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockTextUtil.sanitizeUrl.mockImplementation((url) => url);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
windowOpenSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Dashboard scenarios', () => {
|
||||||
|
it('should render correct text for new PR dashboard', () => {
|
||||||
|
setup({ prParam: 'test-url', isFolder: false, isNewPr: true });
|
||||||
|
|
||||||
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('This dashboard is loaded from a branch in GitHub.')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correct text for existing PR dashboard', () => {
|
||||||
|
setup({ prParam: 'test-url', isFolder: false, isNewPr: false });
|
||||||
|
|
||||||
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('This dashboard is loaded from a pull request in GitHub.')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correct button text for new PR dashboard', () => {
|
||||||
|
setup({ prParam: 'test-url', isFolder: false, isNewPr: true });
|
||||||
|
|
||||||
|
expect(screen.getByText('Open pull request in GitHub')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correct button text for existing PR dashboard', () => {
|
||||||
|
setup({ prParam: 'test-url', isFolder: false, isNewPr: false });
|
||||||
|
|
||||||
|
expect(screen.getByText('View pull request in GitHub')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Folder scenarios', () => {
|
||||||
|
it('should render correct text for new PR folder', () => {
|
||||||
|
setup({ prParam: 'test-url', isFolder: true, isNewPr: true });
|
||||||
|
|
||||||
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('A new folder has been created in a branch in GitHub.')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correct text for existing PR folder', () => {
|
||||||
|
setup({ prParam: 'test-url', isFolder: true, isNewPr: false });
|
||||||
|
|
||||||
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('A new folder has been created in a pull request in GitHub.')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correct button text for new PR folder', () => {
|
||||||
|
setup({ prParam: 'test-url', isFolder: true, isNewPr: true });
|
||||||
|
|
||||||
|
expect(screen.getByText('Open pull request in GitHub')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correct button text for existing PR folder', () => {
|
||||||
|
setup({ prParam: 'test-url', isFolder: true, isNewPr: false });
|
||||||
|
|
||||||
|
expect(screen.getByText('View pull request in GitHub')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Button functionality', () => {
|
||||||
|
it('should open URL in new tab when button is clicked', async () => {
|
||||||
|
const testUrl = 'https://github.com/test/repo/pull/123';
|
||||||
|
setup({ prParam: testUrl });
|
||||||
|
|
||||||
|
const button = screen.getByRole('button', { name: /close alert/i });
|
||||||
|
await userEvent.click(button);
|
||||||
|
|
||||||
|
expect(windowOpenSpy).toHaveBeenCalledWith(testUrl, '_blank');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { textUtil } from '@grafana/data';
|
||||||
|
import { Trans, t } from '@grafana/i18n';
|
||||||
|
import { Alert, Icon, Stack } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { commonAlertProps } from './DashboardPreviewBanner';
|
||||||
|
|
||||||
|
// TODO: We have this https://github.com/grafana/git-ui-sync-project/issues/166 to add more details about the PR.
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
prParam: string;
|
||||||
|
isFolder?: boolean;
|
||||||
|
isNewPr?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description This component is used to display a banner when a provisioned dashboard/folder is created or loaded from a new branch in Github.
|
||||||
|
*/
|
||||||
|
export function PreviewBannerViewPR({ prParam, isFolder = false, isNewPr }: Props) {
|
||||||
|
const titleText = isFolder
|
||||||
|
? isNewPr
|
||||||
|
? t(
|
||||||
|
'provisioned-resource-preview-banner.title-folder-created-branch-git-hub',
|
||||||
|
'A new folder has been created in a branch in GitHub.'
|
||||||
|
)
|
||||||
|
: t(
|
||||||
|
'provisioned-resource-preview-banner.title-folder-created-pull-request-git-hub',
|
||||||
|
'A new folder has been created in a pull request in GitHub.'
|
||||||
|
)
|
||||||
|
: isNewPr
|
||||||
|
? t(
|
||||||
|
'provisioned-resource-preview-banner.title-dashboard-loaded-branch-git-hub',
|
||||||
|
'This dashboard is loaded from a branch in GitHub.'
|
||||||
|
)
|
||||||
|
: t(
|
||||||
|
'provisioned-resource-preview-banner.title-dashboard-loaded-pull-request-git-hub',
|
||||||
|
'This dashboard is loaded from a pull request in GitHub.'
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
{...commonAlertProps}
|
||||||
|
title={titleText}
|
||||||
|
buttonContent={
|
||||||
|
<Stack alignItems="center">
|
||||||
|
{isNewPr
|
||||||
|
? t(
|
||||||
|
'provisioned-resource-preview-banner.preview-banner.open-pull-request-in-git-hub',
|
||||||
|
'Open pull request in GitHub'
|
||||||
|
)
|
||||||
|
: t(
|
||||||
|
'provisioned-resource-preview-banner.preview-banner.view-pull-request-in-git-hub',
|
||||||
|
'View pull request in GitHub'
|
||||||
|
)}
|
||||||
|
<Icon name="external-link-alt" />
|
||||||
|
</Stack>
|
||||||
|
}
|
||||||
|
onRemove={() => window.open(textUtil.sanitizeUrl(prParam), '_blank')}
|
||||||
|
>
|
||||||
|
<Trans i18nKey="provisioned-resource-preview-banner.preview-banner.not-saved">
|
||||||
|
The value is not yet saved in the Grafana database
|
||||||
|
</Trans>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,10 +4,10 @@ import { useUrlParams } from 'app/core/navigation/hooks';
|
|||||||
export const usePullRequestParam = () => {
|
export const usePullRequestParam = () => {
|
||||||
const [params] = useUrlParams();
|
const [params] = useUrlParams();
|
||||||
const prParam = params.get('pull_request_url');
|
const prParam = params.get('pull_request_url');
|
||||||
|
const newPrParam = params.get('new_pull_request_url');
|
||||||
|
|
||||||
if (!prParam) {
|
return {
|
||||||
return undefined;
|
prURL: prParam ? textUtil.sanitizeUrl(prParam) : undefined,
|
||||||
}
|
newPrURL: newPrParam ? textUtil.sanitizeUrl(newPrParam) : undefined,
|
||||||
|
};
|
||||||
return textUtil.sanitizeUrl(decodeURIComponent(prParam));
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5472,15 +5472,9 @@
|
|||||||
"type": "Type"
|
"type": "Type"
|
||||||
},
|
},
|
||||||
"dashboard-preview-banner": {
|
"dashboard-preview-banner": {
|
||||||
"not-saved": "The value is not yet saved in the Grafana database",
|
|
||||||
"not-yet-saved": "The value is not saved in the Grafana database",
|
"not-yet-saved": "The value is not saved in the Grafana database",
|
||||||
"open-pull-request-in-git-hub": "Open pull request in GitHub",
|
|
||||||
"title-dashboard-loaded-branch-git-hub": "This dashboard is loaded from a branch in GitHub.",
|
|
||||||
"title-dashboard-loaded-external-repository": "This dashboard is loaded from an external repository",
|
"title-dashboard-loaded-external-repository": "This dashboard is loaded from an external repository",
|
||||||
"title-dashboard-loaded-request-git-hub": "This dashboard is loaded from a pull request in GitHub.",
|
"title-error-loading-dashboard": "Error loading dashboard"
|
||||||
"title-error-loading-dashboard": "Error loading dashboard",
|
|
||||||
"value-not-saved": "The value is not yet saved in the Grafana database",
|
|
||||||
"view-pull-request-in-git-hub": "View pull request in GitHub"
|
|
||||||
},
|
},
|
||||||
"dashboard-scene": {
|
"dashboard-scene": {
|
||||||
"text": {
|
"text": {
|
||||||
@@ -10312,6 +10306,17 @@
|
|||||||
"label-workflow": "Workflow"
|
"label-workflow": "Workflow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"provisioned-resource-preview-banner": {
|
||||||
|
"preview-banner": {
|
||||||
|
"not-saved": "The value is not yet saved in the Grafana database",
|
||||||
|
"open-pull-request-in-git-hub": "Open pull request in GitHub",
|
||||||
|
"view-pull-request-in-git-hub": "View pull request in GitHub"
|
||||||
|
},
|
||||||
|
"title-dashboard-loaded-branch-git-hub": "This dashboard is loaded from a branch in GitHub.",
|
||||||
|
"title-dashboard-loaded-pull-request-git-hub": "This dashboard is loaded from a pull request in GitHub.",
|
||||||
|
"title-folder-created-branch-git-hub": "A new folder has been created in a branch in GitHub.",
|
||||||
|
"title-folder-created-pull-request-git-hub": "A new folder has been created in a pull request in GitHub."
|
||||||
|
},
|
||||||
"provisioning": {
|
"provisioning": {
|
||||||
"banner": {
|
"banner": {
|
||||||
"message": "This feature is currently under active development. For the best experience and latest improvements, we recommend using the <2>nightly build</2> of Grafana."
|
"message": "This feature is currently under active development. For the best experience and latest improvements, we recommend using the <2>nightly build</2> of Grafana."
|
||||||
|
|||||||
Reference in New Issue
Block a user