Dashboard: Hide export options in collapsible row (#116155)
* Introduce export options * Reset keys * Introduce a new key * Generate new keys * Rename the label * re-generate key * Fix the spacing * Remove debuggers * Add subtitle * refactor component * update labels * faield tests * Update tooltip * Linting issue
This commit is contained in:
committed by
GitHub
parent
5e68b07cac
commit
87f5d5e741
@@ -25,6 +25,10 @@ export class ExportAsCode extends ShareExportTab {
|
||||
public getTabLabel(): string {
|
||||
return t('export.json.title', 'Export dashboard');
|
||||
}
|
||||
|
||||
public getSubtitle(): string | undefined {
|
||||
return t('export.json.info-text', 'Copy or download a file containing the definition of your dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
function ExportAsCodeRenderer({ model }: SceneComponentProps<ExportAsCode>) {
|
||||
@@ -53,12 +57,6 @@ function ExportAsCodeRenderer({ model }: SceneComponentProps<ExportAsCode>) {
|
||||
|
||||
return (
|
||||
<div data-testid={selector.container} className={styles.container}>
|
||||
<p>
|
||||
<Trans i18nKey="export.json.info-text">
|
||||
Copy or download a file containing the definition of your dashboard
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
{config.featureToggles.kubernetesDashboards ? (
|
||||
<ResourceExport
|
||||
dashboardJson={dashboardJson}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { AsyncState } from 'react-use/lib/useAsync';
|
||||
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2';
|
||||
|
||||
import { ExportMode, ResourceExport } from './ResourceExport';
|
||||
|
||||
type DashboardJsonState = AsyncState<{
|
||||
json: Dashboard | DashboardV2Spec | { error: unknown };
|
||||
hasLibraryPanels?: boolean;
|
||||
initialSaveModelVersion: 'v1' | 'v2';
|
||||
}>;
|
||||
|
||||
const selector = e2eSelectors.pages.ExportDashboardDrawer.ExportAsJson;
|
||||
|
||||
const createDefaultProps = (overrides?: Partial<Parameters<typeof ResourceExport>[0]>) => {
|
||||
const defaultProps: Parameters<typeof ResourceExport>[0] = {
|
||||
dashboardJson: {
|
||||
loading: false,
|
||||
value: {
|
||||
json: { title: 'Test Dashboard' } as Dashboard,
|
||||
hasLibraryPanels: false,
|
||||
initialSaveModelVersion: 'v1',
|
||||
},
|
||||
} as DashboardJsonState,
|
||||
isSharingExternally: false,
|
||||
exportMode: ExportMode.Classic,
|
||||
isViewingYAML: false,
|
||||
onExportModeChange: jest.fn(),
|
||||
onShareExternallyChange: jest.fn(),
|
||||
onViewYAML: jest.fn(),
|
||||
};
|
||||
|
||||
return { ...defaultProps, ...overrides };
|
||||
};
|
||||
|
||||
const createV2DashboardJson = (hasLibraryPanels = false): DashboardJsonState => ({
|
||||
loading: false,
|
||||
value: {
|
||||
json: {
|
||||
title: 'Test V2 Dashboard',
|
||||
spec: {
|
||||
elements: {},
|
||||
},
|
||||
} as unknown as DashboardV2Spec,
|
||||
hasLibraryPanels,
|
||||
initialSaveModelVersion: 'v2',
|
||||
},
|
||||
});
|
||||
|
||||
const expandOptions = async () => {
|
||||
const button = screen.getByRole('button', { expanded: false });
|
||||
await userEvent.click(button);
|
||||
};
|
||||
|
||||
describe('ResourceExport', () => {
|
||||
describe('export mode options for v1 dashboard', () => {
|
||||
it('should show three export mode options in correct order: Classic, V1 Resource, V2 Resource', async () => {
|
||||
render(<ResourceExport {...createDefaultProps()} />);
|
||||
await expandOptions();
|
||||
|
||||
const radioGroup = screen.getByRole('radiogroup', { name: /model/i });
|
||||
const labels = within(radioGroup)
|
||||
.getAllByRole('radio')
|
||||
.map((radio) => radio.parentElement?.textContent?.trim());
|
||||
|
||||
expect(labels).toHaveLength(3);
|
||||
expect(labels).toEqual(['Classic', 'V1 Resource', 'V2 Resource']);
|
||||
});
|
||||
|
||||
it('should have first option selected by default when exportMode is Classic', async () => {
|
||||
render(<ResourceExport {...createDefaultProps({ exportMode: ExportMode.Classic })} />);
|
||||
await expandOptions();
|
||||
|
||||
const radioGroup = screen.getByRole('radiogroup', { name: /model/i });
|
||||
const radios = within(radioGroup).getAllByRole('radio');
|
||||
expect(radios[0]).toBeChecked();
|
||||
});
|
||||
|
||||
it('should call onExportModeChange when export mode is changed', async () => {
|
||||
const onExportModeChange = jest.fn();
|
||||
render(<ResourceExport {...createDefaultProps({ onExportModeChange })} />);
|
||||
await expandOptions();
|
||||
|
||||
const radioGroup = screen.getByRole('radiogroup', { name: /model/i });
|
||||
const radios = within(radioGroup).getAllByRole('radio');
|
||||
await userEvent.click(radios[1]); // V1 Resource
|
||||
expect(onExportModeChange).toHaveBeenCalledWith(ExportMode.V1Resource);
|
||||
});
|
||||
});
|
||||
|
||||
describe('export mode options for v2 dashboard', () => {
|
||||
it('should not show export mode options', async () => {
|
||||
render(<ResourceExport {...createDefaultProps({ dashboardJson: createV2DashboardJson() })} />);
|
||||
await expandOptions();
|
||||
|
||||
expect(screen.queryByRole('radiogroup', { name: /model/i })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('format options', () => {
|
||||
it('should not show format options when export mode is Classic', async () => {
|
||||
render(<ResourceExport {...createDefaultProps({ exportMode: ExportMode.Classic })} />);
|
||||
await expandOptions();
|
||||
|
||||
expect(screen.getByRole('radiogroup', { name: /model/i })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('radiogroup', { name: /format/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([ExportMode.V1Resource, ExportMode.V2Resource])(
|
||||
'should show format options when export mode is %s',
|
||||
async (exportMode) => {
|
||||
render(<ResourceExport {...createDefaultProps({ exportMode })} />);
|
||||
await expandOptions();
|
||||
|
||||
expect(screen.getByRole('radiogroup', { name: /model/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('radiogroup', { name: /format/i })).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
it('should have first format option selected when isViewingYAML is false', async () => {
|
||||
render(<ResourceExport {...createDefaultProps({ exportMode: ExportMode.V1Resource, isViewingYAML: false })} />);
|
||||
await expandOptions();
|
||||
|
||||
const formatGroup = screen.getByRole('radiogroup', { name: /format/i });
|
||||
const formatRadios = within(formatGroup).getAllByRole('radio');
|
||||
expect(formatRadios[0]).toBeChecked(); // JSON
|
||||
});
|
||||
|
||||
it('should have second format option selected when isViewingYAML is true', async () => {
|
||||
render(<ResourceExport {...createDefaultProps({ exportMode: ExportMode.V1Resource, isViewingYAML: true })} />);
|
||||
await expandOptions();
|
||||
|
||||
const formatGroup = screen.getByRole('radiogroup', { name: /format/i });
|
||||
const formatRadios = within(formatGroup).getAllByRole('radio');
|
||||
expect(formatRadios[1]).toBeChecked(); // YAML
|
||||
});
|
||||
|
||||
it('should call onViewYAML when format is changed', async () => {
|
||||
const onViewYAML = jest.fn();
|
||||
render(<ResourceExport {...createDefaultProps({ exportMode: ExportMode.V1Resource, onViewYAML })} />);
|
||||
await expandOptions();
|
||||
|
||||
const formatGroup = screen.getByRole('radiogroup', { name: /format/i });
|
||||
const formatRadios = within(formatGroup).getAllByRole('radio');
|
||||
await userEvent.click(formatRadios[1]); // YAML
|
||||
expect(onViewYAML).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('share externally switch', () => {
|
||||
it('should show share externally switch for Classic mode', () => {
|
||||
render(<ResourceExport {...createDefaultProps({ exportMode: ExportMode.Classic })} />);
|
||||
|
||||
expect(screen.getByTestId(selector.exportExternallyToggle)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show share externally switch for V2Resource mode with V2 dashboard', () => {
|
||||
render(
|
||||
<ResourceExport
|
||||
{...createDefaultProps({
|
||||
dashboardJson: createV2DashboardJson(),
|
||||
exportMode: ExportMode.V2Resource,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId(selector.exportExternallyToggle)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onShareExternallyChange when switch is toggled', async () => {
|
||||
const onShareExternallyChange = jest.fn();
|
||||
render(<ResourceExport {...createDefaultProps({ exportMode: ExportMode.Classic, onShareExternallyChange })} />);
|
||||
|
||||
const switchElement = screen.getByTestId(selector.exportExternallyToggle);
|
||||
await userEvent.click(switchElement);
|
||||
expect(onShareExternallyChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reflect isSharingExternally value in switch', () => {
|
||||
render(<ResourceExport {...createDefaultProps({ exportMode: ExportMode.Classic, isSharingExternally: true })} />);
|
||||
|
||||
expect(screen.getByTestId(selector.exportExternallyToggle)).toBeChecked();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,8 @@ import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2';
|
||||
import { Alert, Label, RadioButtonGroup, Stack, Switch } from '@grafana/ui';
|
||||
import { Alert, Icon, Label, RadioButtonGroup, Stack, Switch, Box, Tooltip } from '@grafana/ui';
|
||||
import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow';
|
||||
import { DashboardJson } from 'app/features/manage-dashboards/types';
|
||||
|
||||
import { ExportableResource } from '../ShareExportTab';
|
||||
@@ -48,80 +49,90 @@ export function ResourceExport({
|
||||
|
||||
const switchExportLabel =
|
||||
exportMode === ExportMode.V2Resource
|
||||
? t('export.json.export-remove-ds-refs', 'Remove deployment details')
|
||||
: t('share-modal.export.share-externally-label', `Export for sharing externally`);
|
||||
? t('dashboard-scene.resource-export.share-externally', 'Share dashboard with another instance')
|
||||
: t('share-modal.export.share-externally-label', 'Export for sharing externally');
|
||||
const switchExportTooltip = t(
|
||||
'dashboard-scene.resource-export.share-externally-tooltip',
|
||||
'Removes all instance-specific metadata and data source references from the resource before export.'
|
||||
);
|
||||
const switchExportModeLabel = t('export.json.export-mode', 'Model');
|
||||
const switchExportFormatLabel = t('export.json.export-format', 'Format');
|
||||
|
||||
const exportResourceOptions = [
|
||||
{
|
||||
label: t('dashboard-scene.resource-export.label.classic', 'Classic'),
|
||||
value: ExportMode.Classic,
|
||||
},
|
||||
{
|
||||
label: t('dashboard-scene.resource-export.label.v1-resource', 'V1 Resource'),
|
||||
value: ExportMode.V1Resource,
|
||||
},
|
||||
{
|
||||
label: t('dashboard-scene.resource-export.label.v2-resource', 'V2 Resource'),
|
||||
value: ExportMode.V2Resource,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack gap={2} direction="column">
|
||||
<Stack gap={1} direction="column">
|
||||
{initialSaveModelVersion === 'v1' && (
|
||||
<Stack alignItems="center">
|
||||
<Label>{switchExportModeLabel}</Label>
|
||||
<RadioButtonGroup
|
||||
options={[
|
||||
{ label: t('dashboard-scene.resource-export.label.classic', 'Classic'), value: ExportMode.Classic },
|
||||
{
|
||||
label: t('dashboard-scene.resource-export.label.v1-resource', 'V1 Resource'),
|
||||
value: ExportMode.V1Resource,
|
||||
},
|
||||
{
|
||||
label: t('dashboard-scene.resource-export.label.v2-resource', 'V2 Resource'),
|
||||
value: ExportMode.V2Resource,
|
||||
},
|
||||
]}
|
||||
value={exportMode}
|
||||
onChange={(value) => onExportModeChange(value)}
|
||||
/>
|
||||
<>
|
||||
<QueryOperationRow
|
||||
id="Advanced options"
|
||||
index={0}
|
||||
title={t('dashboard-scene.resource-export.label.advanced-options', 'Advanced options')}
|
||||
isOpen={false}
|
||||
>
|
||||
<Box marginTop={2}>
|
||||
<Stack gap={1} direction="column">
|
||||
{initialSaveModelVersion === 'v1' && (
|
||||
<Stack gap={1} alignItems="center">
|
||||
<Label>{switchExportModeLabel}</Label>
|
||||
<RadioButtonGroup
|
||||
options={exportResourceOptions}
|
||||
value={exportMode}
|
||||
onChange={(value) => onExportModeChange(value)}
|
||||
aria-label={switchExportModeLabel}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{exportMode !== ExportMode.Classic && (
|
||||
<Stack gap={1} alignItems="center">
|
||||
<Label>{switchExportFormatLabel}</Label>
|
||||
<RadioButtonGroup
|
||||
options={[
|
||||
{ label: t('dashboard-scene.resource-export.label.json', 'JSON'), value: 'json' },
|
||||
{ label: t('dashboard-scene.resource-export.label.yaml', 'YAML'), value: 'yaml' },
|
||||
]}
|
||||
value={isViewingYAML ? 'yaml' : 'json'}
|
||||
onChange={onViewYAML}
|
||||
aria-label={switchExportFormatLabel}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
{initialSaveModelVersion === 'v2' && (
|
||||
<Stack alignItems="center">
|
||||
<Label>{switchExportModeLabel}</Label>
|
||||
<RadioButtonGroup
|
||||
options={[
|
||||
{
|
||||
label: t('dashboard-scene.resource-export.label.v2-resource', 'V2 Resource'),
|
||||
value: ExportMode.V2Resource,
|
||||
},
|
||||
{
|
||||
label: t('dashboard-scene.resource-export.label.v1-resource', 'V1 Resource'),
|
||||
value: ExportMode.V1Resource,
|
||||
},
|
||||
]}
|
||||
value={exportMode}
|
||||
onChange={(value) => onExportModeChange(value)}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{exportMode !== ExportMode.Classic && (
|
||||
<Stack gap={1} alignItems="center">
|
||||
<Label>{switchExportFormatLabel}</Label>
|
||||
<RadioButtonGroup
|
||||
options={[
|
||||
{ label: t('dashboard-scene.resource-export.label.json', 'JSON'), value: 'json' },
|
||||
{ label: t('dashboard-scene.resource-export.label.yaml', 'YAML'), value: 'yaml' },
|
||||
]}
|
||||
value={isViewingYAML ? 'yaml' : 'json'}
|
||||
onChange={onViewYAML}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{(isV2Dashboard ||
|
||||
exportMode === ExportMode.Classic ||
|
||||
(initialSaveModelVersion === 'v2' && exportMode === ExportMode.V1Resource)) && (
|
||||
<Stack gap={1} alignItems="start">
|
||||
<Label>{switchExportLabel}</Label>
|
||||
<Switch
|
||||
label={switchExportLabel}
|
||||
value={isSharingExternally}
|
||||
onChange={onShareExternallyChange}
|
||||
data-testid={selector.exportExternallyToggle}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</QueryOperationRow>
|
||||
|
||||
{(isV2Dashboard ||
|
||||
exportMode === ExportMode.Classic ||
|
||||
(initialSaveModelVersion === 'v2' && exportMode === ExportMode.V1Resource)) && (
|
||||
<Stack gap={1} alignItems="start">
|
||||
<Label>
|
||||
<Stack gap={0.5} alignItems="center">
|
||||
<Tooltip content={switchExportTooltip} placement="bottom">
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
{switchExportLabel}
|
||||
</Stack>
|
||||
</Label>
|
||||
<Switch
|
||||
label={switchExportLabel}
|
||||
value={isSharingExternally}
|
||||
onChange={onShareExternallyChange}
|
||||
data-testid={selector.exportExternallyToggle}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{showV2LibPanelAlert && (
|
||||
<Alert
|
||||
@@ -130,6 +141,7 @@ export function ResourceExport({
|
||||
'Library panels will be converted to regular panels'
|
||||
)}
|
||||
severity="warning"
|
||||
topSpacing={2}
|
||||
>
|
||||
<Trans i18nKey="dashboard-scene.save-dashboard-form.schema-v2-library-panels-export">
|
||||
Due to limitations in the new dashboard schema (V2), library panels will be converted to regular panels with
|
||||
@@ -137,6 +149,6 @@ export function ResourceExport({
|
||||
</Trans>
|
||||
</Alert>
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,12 @@ function ShareDrawerRenderer({ model }: SceneComponentProps<ShareDrawer>) {
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
|
||||
return (
|
||||
<Drawer title={activeShare?.getTabLabel()} onClose={model.onDismiss} size="md">
|
||||
<Drawer
|
||||
title={activeShare?.getTabLabel()}
|
||||
subtitle={activeShare?.getSubtitle?.()}
|
||||
onClose={model.onDismiss}
|
||||
size="md"
|
||||
>
|
||||
<ShareDrawerContext.Provider value={{ dashboard, onDismiss: model.onDismiss }}>
|
||||
{activeShare && <activeShare.Component model={activeShare} />}
|
||||
</ShareDrawerContext.Provider>
|
||||
|
||||
@@ -66,6 +66,10 @@ export class ShareExportTab extends SceneObjectBase<ShareExportTabState> impleme
|
||||
return t('share-modal.tab-title.export', 'Export');
|
||||
}
|
||||
|
||||
public getSubtitle(): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public onShareExternallyChange = () => {
|
||||
this.setState({
|
||||
isSharingExternally: !this.state.isSharingExternally,
|
||||
|
||||
@@ -15,5 +15,6 @@ export interface SceneShareTab<T extends SceneShareTabState = SceneShareTabState
|
||||
|
||||
export interface ShareView extends SceneObject {
|
||||
getTabLabel(): string;
|
||||
getSubtitle?(): string | undefined;
|
||||
onDismiss?: () => void;
|
||||
}
|
||||
|
||||
@@ -6383,12 +6383,15 @@
|
||||
},
|
||||
"resource-export": {
|
||||
"label": {
|
||||
"advanced-options": "Advanced options",
|
||||
"classic": "Classic",
|
||||
"json": "JSON",
|
||||
"v1-resource": "V1 Resource",
|
||||
"v2-resource": "V2 Resource",
|
||||
"yaml": "YAML"
|
||||
}
|
||||
},
|
||||
"share-externally": "Share dashboard with another instance",
|
||||
"share-externally-tooltip": "Removes all instance-specific metadata and data source references from the resource before export."
|
||||
},
|
||||
"revert-dashboard-modal": {
|
||||
"body-restore-version": "Are you sure you want to restore the dashboard to version {{version}}? All unsaved changes will be lost.",
|
||||
@@ -7842,7 +7845,6 @@
|
||||
"export-externally-label": "Export the dashboard to use in another instance",
|
||||
"export-format": "Format",
|
||||
"export-mode": "Model",
|
||||
"export-remove-ds-refs": "Remove deployment details",
|
||||
"info-text": "Copy or download a file containing the definition of your dashboard",
|
||||
"title": "Export dashboard"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user