PublicDashboards: Add react-hook-form for Public Dashboard modal (#60249)

This commit is contained in:
juanicabanas 2022-12-14 09:12:09 -03:00 committed by GitHub
parent 55d2d872ec
commit 666f69ca14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 92 additions and 82 deletions

View File

@ -1,18 +1,17 @@
import React from 'react';
import { UseFormRegister } from 'react-hook-form';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { Checkbox, FieldSet, HorizontalGroup, LinkButton, VerticalGroup } from '@grafana/ui/src';
import { Acknowledgements } from './SharePublicDashboardUtils';
import { SharePublicDashboardInputs } from './SharePublicDashboard';
export const AcknowledgeCheckboxes = ({
disabled,
acknowledgements,
onAcknowledge,
register,
}: {
disabled: boolean;
acknowledgements: Acknowledgements;
onAcknowledge: (key: string, val: boolean) => void;
register: UseFormRegister<SharePublicDashboardInputs>;
}) => {
const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
@ -23,11 +22,11 @@ export const AcknowledgeCheckboxes = ({
<VerticalGroup spacing="md">
<HorizontalGroup spacing="none">
<Checkbox
{...register('publicAcknowledgment')}
label="Your entire dashboard will be public"
value={acknowledgements.public}
data-testid={selectors.WillBePublicCheckbox}
onChange={(e) => onAcknowledge('public', e.currentTarget.checked)}
/>
<LinkButton
variant="primary"
href="https://grafana.com/docs/grafana/latest/dashboards/dashboard-public/"
@ -40,10 +39,9 @@ export const AcknowledgeCheckboxes = ({
</HorizontalGroup>
<HorizontalGroup spacing="none">
<Checkbox
{...register('dataSourcesAcknowledgment')}
label="Publishing currently only works with a subset of datasources"
value={acknowledgements.datasources}
data-testid={selectors.LimitedDSCheckbox}
onChange={(e) => onAcknowledge('datasources', e.currentTarget.checked)}
/>
<LinkButton
variant="primary"
@ -57,10 +55,9 @@ export const AcknowledgeCheckboxes = ({
</HorizontalGroup>
<HorizontalGroup spacing="none">
<Checkbox
{...register('usageAcknowledgment')}
label="Making your dashboard public will cause queries to run each time the dashboard is viewed which may increase costs"
value={acknowledgements.usage}
data-testid={selectors.CostIncreaseCheckbox}
onChange={(e) => onAcknowledge('usage', e.currentTarget.checked)}
/>
<LinkButton
variant="primary"

View File

@ -1,5 +1,6 @@
import { css } from '@emotion/css';
import React from 'react';
import { UseFormRegister } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data/src';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
@ -10,20 +11,16 @@ import { DashboardModel } from 'app/features/dashboard/state';
import { useIsDesktop } from 'app/features/dashboard/utils/screen';
import { getTimeRange } from 'app/features/dashboard/utils/timeRange';
import { SharePublicDashboardInputs } from './SharePublicDashboard';
export const Configuration = ({
isAnnotationsEnabled,
disabled,
isPubDashEnabled,
onToggleEnabled,
onToggleAnnotations,
dashboard,
register,
}: {
isAnnotationsEnabled: boolean;
disabled: boolean;
isPubDashEnabled?: boolean;
onToggleEnabled: () => void;
onToggleAnnotations: () => void;
dashboard: DashboardModel;
register: UseFormRegister<SharePublicDashboardInputs>;
}) => {
const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
const styles = useStyles2(getStyles);
@ -33,7 +30,7 @@ export const Configuration = ({
return (
<>
<h4 className="share-modal-info-text">Public dashboard configuration</h4>
<h4 className={styles.title}>Public dashboard configuration</h4>
<FieldSet disabled={disabled} className={styles.dashboardConfig}>
<VerticalGroup spacing="md">
<Layout orientation={isDesktop ? 0 : 1} spacing="xs" justify="space-between">
@ -43,28 +40,29 @@ export const Configuration = ({
<Layout orientation={isDesktop ? 0 : 1} spacing="xs" justify="space-between">
<Label description="Show annotations on public dashboard">Show annotations</Label>
<Switch
data-testid={selectors.EnableAnnotationsSwitch}
value={isAnnotationsEnabled}
onChange={() => {
{...register('isAnnotationsEnabled')}
onChange={(e) => {
const { onChange } = register('isAnnotationsEnabled');
reportInteraction('grafana_dashboards_annotations_clicked', {
action: isAnnotationsEnabled ? 'disable' : 'enable',
action: e.currentTarget.checked ? 'enable' : 'disable',
});
onToggleAnnotations();
onChange(e);
}}
data-testid={selectors.EnableAnnotationsSwitch}
/>
</Layout>
<Layout orientation={isDesktop ? 0 : 1} spacing="xs" justify="space-between">
<Label description="Configures whether current dashboard can be available publicly">Enabled</Label>
<Switch
data-testid={selectors.EnableSwitch}
value={isPubDashEnabled}
onChange={() => {
{...register('enabledSwitch')}
onChange={(e) => {
const { onChange } = register('enabledSwitch');
reportInteraction('grafana_dashboards_public_enable_clicked', {
action: isPubDashEnabled ? 'disable' : 'enable',
action: e.currentTarget.checked ? 'enable' : 'disable',
});
onToggleEnabled();
onChange(e);
}}
data-testid={selectors.EnableSwitch}
/>
</Layout>
</VerticalGroup>
@ -74,7 +72,16 @@ export const Configuration = ({
};
const getStyles = (theme: GrafanaTheme2) => ({
title: css`
margin-bottom: ${theme.spacing(2)};
`,
dashboardConfig: css`
margin: ${theme.spacing(0, 0, 3, 0)};
`,
timeRange: css`
margin-bottom: ${theme.spacing(0)};
`,
timeRangeDisabledText: css`
font-size: ${theme.typography.bodySmall.fontSize};
`,
});

View File

@ -1,5 +1,6 @@
import { css } from '@emotion/css';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import React, { useContext, useEffect, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { Subscription } from 'rxjs';
import { GrafanaTheme2 } from '@grafana/data/src';
@ -28,7 +29,6 @@ import { AcknowledgeCheckboxes } from 'app/features/dashboard/components/ShareMo
import { Configuration } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/Configuration';
import { Description } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/Description';
import {
Acknowledgements,
dashboardHasTemplateVariables,
generatePublicDashboardUrl,
getUnsupportedDashboardDatasources,
@ -45,6 +45,17 @@ import { ShareModal } from '../ShareModal';
interface Props extends ShareModalTabProps {}
type SharePublicDashboardAcknowledgmentInputs = {
publicAcknowledgment: boolean;
dataSourcesAcknowledgment: boolean;
usageAcknowledgment: boolean;
};
export type SharePublicDashboardInputs = {
isAnnotationsEnabled: boolean;
enabledSwitch: boolean;
} & SharePublicDashboardAcknowledgmentInputs;
export const SharePublicDashboard = (props: Props) => {
const forceUpdate = useForceUpdate();
const styles = useStyles2(getStyles);
@ -65,20 +76,25 @@ export const SharePublicDashboard = (props: Props) => {
skip: !hasPublicDashboard,
});
const {
reset,
handleSubmit,
watch,
register,
formState: { dirtyFields },
} = useForm<SharePublicDashboardInputs>({
defaultValues: {
publicAcknowledgment: false,
dataSourcesAcknowledgment: false,
usageAcknowledgment: false,
isAnnotationsEnabled: false,
enabledSwitch: false,
},
});
const [createPublicDashboard, { isLoading: isSaveLoading }] = useCreatePublicDashboardMutation();
const [updatePublicDashboard, { isLoading: isUpdateLoading }] = useUpdatePublicDashboardMutation();
const [acknowledgements, setAcknowledgements] = useState<Acknowledgements>({
public: false,
datasources: false,
usage: false,
});
const [enabledSwitch, setEnabledSwitch] = useState({
isEnabled: false,
wasTouched: false,
});
const [annotationsEnabled, setAnnotationsEnabled] = useState(false);
useEffect(() => {
const eventSubs = new Subscription();
eventSubs.add(props.dashboard.events.subscribe(DashboardMetaChangedEvent, forceUpdate));
@ -88,21 +104,21 @@ export const SharePublicDashboard = (props: Props) => {
}, [props.dashboard.events, forceUpdate]);
useEffect(() => {
if (publicDashboardPersisted(publicDashboard)) {
setAcknowledgements({
public: true,
datasources: true,
usage: true,
});
setAnnotationsEnabled(!!publicDashboard?.annotationsEnabled);
}
setEnabledSwitch((prevState) => ({ ...prevState, isEnabled: !!publicDashboard?.isEnabled }));
}, [publicDashboard]);
const isPublicDashboardPersisted = publicDashboardPersisted(publicDashboard);
reset({
publicAcknowledgment: isPublicDashboardPersisted,
dataSourcesAcknowledgment: isPublicDashboardPersisted,
usageAcknowledgment: isPublicDashboardPersisted,
isAnnotationsEnabled: publicDashboard?.annotationsEnabled,
enabledSwitch: publicDashboard?.isEnabled,
});
}, [publicDashboard, reset]);
const isLoading = isGetLoading || isSaveLoading || isUpdateLoading;
const hasWritePermissions = contextSrv.hasAccess(AccessControlAction.DashboardsPublicWrite, isOrgAdmin());
const acknowledged = acknowledgements.public && acknowledgements.datasources && acknowledgements.usage;
const acknowledged =
watch('publicAcknowledgment') && watch('dataSourcesAcknowledgment') && watch('usageAcknowledgment');
const isSaveDisabled = useMemo(
() =>
!hasWritePermissions ||
@ -111,36 +127,37 @@ export const SharePublicDashboard = (props: Props) => {
isLoading ||
isFetching ||
isGetError ||
(!publicDashboardPersisted(publicDashboard) && !enabledSwitch.wasTouched),
(!publicDashboardPersisted(publicDashboard) && !dirtyFields.enabledSwitch),
[
hasWritePermissions,
acknowledged,
props.dashboard,
isLoading,
isGetError,
enabledSwitch,
publicDashboard,
isFetching,
dirtyFields.enabledSwitch,
]
);
const isDeleteDisabled = isLoading || isFetching || isGetError;
const onSavePublicConfig = async () => {
const onSavePublicConfig = async (values: SharePublicDashboardInputs) => {
reportInteraction('grafana_dashboards_public_create_clicked');
const req = {
dashboard: props.dashboard,
payload: { ...publicDashboard!, isEnabled: enabledSwitch.isEnabled, annotationsEnabled },
payload: {
...publicDashboard!,
isEnabled: values.enabledSwitch,
annotationsEnabled: values.isAnnotationsEnabled,
},
};
// create or update based on whether we have existing uid
hasPublicDashboard ? updatePublicDashboard(req) : createPublicDashboard(req);
};
const onAcknowledge = (field: string, checked: boolean) => {
setAcknowledgements((prevState) => ({ ...prevState, [field]: checked }));
};
const onDismissDelete = () => {
showModal(ShareModal, {
dashboard: props.dashboard,
@ -188,28 +205,22 @@ export const SharePublicDashboard = (props: Props) => {
This dashboard cannot be made public because it has template variables
</Alert>
) : (
<>
<form onSubmit={handleSubmit(onSavePublicConfig)}>
<Description />
<hr />
<div className={styles.checkboxes}>
<AcknowledgeCheckboxes
disabled={publicDashboardPersisted(publicDashboard) || !hasWritePermissions || isLoading || isGetError}
acknowledgements={acknowledgements}
onAcknowledge={onAcknowledge}
register={register}
/>
</div>
<hr />
<Configuration
isAnnotationsEnabled={annotationsEnabled}
register={register}
dashboard={props.dashboard}
disabled={!hasWritePermissions || isLoading || isGetError}
isPubDashEnabled={enabledSwitch.isEnabled}
onToggleEnabled={() =>
setEnabledSwitch((prevState) => ({ isEnabled: !prevState.isEnabled, wasTouched: true }))
}
onToggleAnnotations={() => setAnnotationsEnabled((prevState) => !prevState)}
/>
{publicDashboardPersisted(publicDashboard) && enabledSwitch.isEnabled && (
{publicDashboardPersisted(publicDashboard) && watch('enabledSwitch') && (
<Field label="Link URL" className={styles.publicUrl}>
<Input
disabled={isLoading}
@ -248,11 +259,12 @@ export const SharePublicDashboard = (props: Props) => {
)}
<HorizontalGroup>
<Layout orientation={isDesktop ? 0 : 1}>
<Button disabled={isSaveDisabled} onClick={onSavePublicConfig} data-testid={selectors.SaveConfigButton}>
<Button type="submit" disabled={isSaveDisabled} data-testid={selectors.SaveConfigButton}>
{hasPublicDashboard ? 'Save public dashboard' : 'Create public dashboard'}
</Button>
{publicDashboard && hasWritePermissions && (
<DeletePublicDashboardButton
type="button"
disabled={isDeleteDisabled}
data-testid={selectors.DeleteButton}
onDismiss={onDismissDelete}
@ -270,7 +282,7 @@ export const SharePublicDashboard = (props: Props) => {
</Layout>
{(isSaveLoading || isFetching) && <Spinner />}
</HorizontalGroup>
</>
</form>
)}
</div>
</>

View File

@ -20,12 +20,6 @@ export interface DashboardResponse {
meta: DashboardMeta;
}
export interface Acknowledgements {
public: boolean;
datasources: boolean;
usage: boolean;
}
// Instance methods
export const dashboardHasTemplateVariables = (variables: VariableModel[]): boolean => {
return variables.length > 0;