{
const getStyles = (theme: GrafanaTheme2) => ({
container: css`
- margin-bottom: ${theme.spacing(2)};
+ label: emailConfigContainer;
+ display: flex;
+ flex-direction: column;
+ flex-wrap: wrap;
+ gap: ${theme.spacing(3)};
+ `,
+ field: css`
+ label: field-noMargin;
+ margin-bottom: 0;
`,
emailContainer: css`
+ label: emailContainer;
display: flex;
gap: ${theme.spacing(1)};
`,
emailInput: css`
+ label: emailInput;
flex-grow: 1;
`,
table: css`
+ label: table;
display: flex;
max-height: 220px;
overflow-y: scroll;
- margin-bottom: ${theme.spacing(1)};
& tbody {
display: flex;
diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsBar.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsBar.tsx
new file mode 100644
index 00000000000..be34a9874cf
--- /dev/null
+++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsBar.tsx
@@ -0,0 +1,44 @@
+import { css } from '@emotion/css';
+import React, { useState } from 'react';
+
+import { GrafanaTheme2 } from '@grafana/data';
+import { useStyles2 } from '@grafana/ui';
+
+import { SettingsBarHeader, Props as SettingsBarHeaderProps } from './SettingsBarHeader';
+
+export interface Props extends Pick
{
+ children: React.ReactNode;
+}
+
+export function SettingsBar({ children, title, headerElement, ...rest }: Props) {
+ const styles = useStyles2(getStyles);
+ const [isContentVisible, setIsContentVisible] = useState(false);
+
+ function onRowToggle() {
+ setIsContentVisible((prevState) => !prevState);
+ }
+
+ return (
+ <>
+
+ {isContentVisible && {children}
}
+ >
+ );
+}
+
+SettingsBar.displayName = 'SettingsBar';
+
+const getStyles = (theme: GrafanaTheme2) => {
+ return {
+ content: css({
+ marginTop: theme.spacing(1),
+ marginLeft: theme.spacing(4),
+ }),
+ };
+};
diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsBarHeader.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsBarHeader.tsx
new file mode 100644
index 00000000000..29851897eee
--- /dev/null
+++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsBarHeader.tsx
@@ -0,0 +1,94 @@
+import { css } from '@emotion/css';
+import React from 'react';
+
+import { GrafanaTheme2 } from '@grafana/data';
+import { IconButton, ReactUtils, useStyles2 } from '@grafana/ui';
+
+export interface Props {
+ onRowToggle: () => void;
+ isContentVisible?: boolean;
+ title?: string;
+ headerElement?: React.ReactNode | ((props: { className?: string }) => React.ReactNode);
+}
+
+export function SettingsBarHeader({ headerElement, isContentVisible = false, onRowToggle, title, ...rest }: Props) {
+ const styles = useStyles2(getStyles);
+
+ const headerElementRendered =
+ headerElement && ReactUtils.renderOrCallToRender(headerElement, { className: styles.summaryWrapper });
+
+ return (
+
+
+
+ {title && (
+ // disabling the a11y rules here as the IconButton above handles keyboard interactions
+ // this is just to provide a better experience for mouse users
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
+
+ {title}
+
+ )}
+ {headerElementRendered}
+
+
+ );
+}
+
+SettingsBarHeader.displayName = 'SettingsBarHeader';
+
+function getStyles(theme: GrafanaTheme2) {
+ return {
+ wrapper: css({
+ label: 'header',
+ padding: theme.spacing(0.5, 0.5),
+ borderRadius: theme.shape.borderRadius(1),
+ background: theme.colors.background.secondary,
+ minHeight: theme.spacing(4),
+
+ '&:focus': {
+ outline: 'none',
+ },
+ }),
+ header: css({
+ label: 'column',
+ display: 'flex',
+ alignItems: 'center',
+ whiteSpace: 'nowrap',
+ }),
+ collapseIcon: css({
+ marginLeft: theme.spacing(0.5),
+ color: theme.colors.text.disabled,
+ }),
+ titleWrapper: css({
+ display: 'flex',
+ alignItems: 'center',
+ cursor: 'pointer',
+ overflow: 'hidden',
+ marginRight: `${theme.spacing(0.5)}`,
+ [theme.breakpoints.down('sm')]: {
+ flex: '1 1',
+ },
+ }),
+ title: css({
+ fontWeight: theme.typography.fontWeightBold,
+ marginLeft: theme.spacing(0.5),
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ }),
+ summaryWrapper: css({
+ display: 'flex',
+ flexWrap: 'wrap',
+ [theme.breakpoints.down('sm')]: {
+ flex: '2 2',
+ },
+ }),
+ };
+}
diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsSummary.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsSummary.tsx
new file mode 100644
index 00000000000..e156087ba60
--- /dev/null
+++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsSummary.tsx
@@ -0,0 +1,57 @@
+import { css, cx } from '@emotion/css';
+import React from 'react';
+
+import { GrafanaTheme2, TimeRange } from '@grafana/data';
+import { Spinner, TimeRangeLabel, useStyles2 } from '@grafana/ui';
+
+export interface Props {
+ timeRange: TimeRange;
+ className?: string;
+ isDataLoading?: boolean;
+ timeSelectionEnabled?: boolean;
+ annotationsEnabled?: boolean;
+}
+
+export function SettingsSummary({
+ className,
+ isDataLoading = false,
+ timeRange,
+ timeSelectionEnabled,
+ annotationsEnabled,
+}: Props) {
+ const styles = useStyles2(getStyles);
+
+ return isDataLoading ? (
+
+
+
+ ) : (
+
+
+ {'Time range = '}
+
+
+ {`Time range picker = ${timeSelectionEnabled ? 'enabled' : 'disabled'}`}
+ {`Annotations = ${annotationsEnabled ? 'show' : 'hide'}`}
+
+ );
+}
+
+SettingsSummary.displayName = 'SettingsSummary';
+
+const getStyles = (theme: GrafanaTheme2) => {
+ return {
+ summaryWrapper: css({
+ display: 'flex',
+ }),
+ summary: css`
+ label: collapsedText;
+ margin-left: ${theme.spacing.gridSize * 2}px;
+ font-size: ${theme.typography.bodySmall.fontSize};
+ color: ${theme.colors.text.secondary};
+ `,
+ timeRange: css({
+ display: 'inline-block',
+ }),
+ };
+};
diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/NoUpsertPermissionsAlert.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/NoUpsertPermissionsAlert.tsx
index d0245f93cb6..b850893291d 100644
--- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/NoUpsertPermissionsAlert.tsx
+++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/NoUpsertPermissionsAlert.tsx
@@ -10,6 +10,7 @@ export const NoUpsertPermissionsAlert = ({ mode }: { mode: 'create' | 'edit' })
severity="info"
title={`You don’t have permission to ${mode} a public dashboard`}
data-testid={selectors.NoUpsertPermissionsWarningAlert}
+ bottomSpacing={0}
>
Contact your admin to get permission to {mode} create public dashboards
diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/SaveDashboardChangesAlert.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/SaveDashboardChangesAlert.tsx
index 2a88bce977d..3eb5d681d8a 100644
--- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/SaveDashboardChangesAlert.tsx
+++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/SaveDashboardChangesAlert.tsx
@@ -3,5 +3,9 @@ import React from 'react';
import { Alert } from '@grafana/ui/src';
export const SaveDashboardChangesAlert = () => (
-
+
);
diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx
index 615ccc64353..59ab568675b 100644
--- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx
+++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx
@@ -16,6 +16,7 @@ export const UnsupportedDataSourcesAlert = ({ unsupportedDataSources }: { unsupp
severity="warning"
title="Unsupported data sources"
data-testid={selectors.UnsupportedDataSourcesWarningAlert}
+ bottomSpacing={0}
>
There are data sources in this dashboard that are unsupported for public dashboards. Panels that use these data
diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedTemplateVariablesAlert.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedTemplateVariablesAlert.tsx
index 07da2bc1a76..74fe2f910da 100644
--- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedTemplateVariablesAlert.tsx
+++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedTemplateVariablesAlert.tsx
@@ -10,6 +10,7 @@ export const UnsupportedTemplateVariablesAlert = () => (
severity="warning"
title="Template variables are not supported"
data-testid={selectors.TemplateVariablesWarningAlert}
+ bottomSpacing={0}
>
This public dashboard may not work since it uses template variables
diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx
index 5a63467a405..c885c83e248 100644
--- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx
+++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx
@@ -135,15 +135,28 @@ describe('SharePublic', () => {
expect(screen.getByRole('tablist')).toHaveTextContent('Link');
expect(screen.getByRole('tablist')).not.toHaveTextContent('Public dashboard');
});
- it('renders default relative time in input', async () => {
+ it('renders default relative time in settings summary when they are closed', async () => {
expect(mockDashboard.time).toEqual({ from: 'now-6h', to: 'now' });
//@ts-ignore
mockDashboard.originalTime = { from: 'now-6h', to: 'now' };
await renderSharePublicDashboard();
+ await waitFor(() => screen.getByText('Time range ='));
+
expect(screen.getByText('Last 6 hours')).toBeInTheDocument();
});
+ it('renders default relative time in settings when they are open', async () => {
+ expect(mockDashboard.time).toEqual({ from: 'now-6h', to: 'now' });
+
+ //@ts-ignore
+ mockDashboard.originalTime = { from: 'now-6h', to: 'now' };
+
+ await renderSharePublicDashboard();
+ await userEvent.click(screen.getByText('Settings'));
+
+ expect(screen.queryAllByText('Last 6 hours')).toHaveLength(2);
+ });
it('when modal is opened, then checkboxes are enabled but create button is disabled', async () => {
server.use(getNonExistentPublicDashboardResponse());
await renderSharePublicDashboard();
@@ -214,6 +227,7 @@ describe('SharePublic - Already persisted', () => {
});
it('when fetch is done, then inputs are checked and delete button is enabled', async () => {
await renderSharePublicDashboard();
+ await userEvent.click(screen.getByText('Settings'));
await waitFor(() => {
expect(screen.getByTestId(selectors.EnableTimeRangeSwitch)).toBeEnabled();
@@ -231,6 +245,7 @@ describe('SharePublic - Already persisted', () => {
it('inputs and delete button are disabled because of lack of permissions', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
await renderSharePublicDashboard();
+ await userEvent.click(screen.getByText('Settings'));
expect(await screen.findByTestId(selectors.EnableTimeRangeSwitch)).toBeDisabled();
expect(screen.getByTestId(selectors.EnableTimeRangeSwitch)).toBeChecked();
@@ -257,6 +272,7 @@ describe('SharePublic - Already persisted', () => {
);
await renderSharePublicDashboard();
+ await userEvent.click(screen.getByText('Settings'));
const enableTimeRangeSwitch = await screen.findByTestId(selectors.EnableTimeRangeSwitch);
await waitFor(() => {
@@ -320,6 +336,8 @@ describe('SharePublic - Report interactions', () => {
it('reports interaction when time range is clicked', async () => {
await renderSharePublicDashboard();
+ await userEvent.click(screen.getByText('Settings'));
+
await waitFor(() => {
expect(screen.getByTestId(selectors.EnableTimeRangeSwitch)).toBeEnabled();
});
@@ -333,6 +351,8 @@ describe('SharePublic - Report interactions', () => {
});
it('reports interaction when show annotations is clicked', async () => {
await renderSharePublicDashboard();
+ await userEvent.click(screen.getByText('Settings'));
+
await waitFor(() => {
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeEnabled();
});