mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PublicDashboards: Add react-hook-form for Public Dashboard modal (#60249)
This commit is contained in:
parent
55d2d872ec
commit
666f69ca14
@ -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"
|
||||
|
@ -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};
|
||||
`,
|
||||
});
|
||||
|
@ -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,
|
||||
const isPublicDashboardPersisted = publicDashboardPersisted(publicDashboard);
|
||||
reset({
|
||||
publicAcknowledgment: isPublicDashboardPersisted,
|
||||
dataSourcesAcknowledgment: isPublicDashboardPersisted,
|
||||
usageAcknowledgment: isPublicDashboardPersisted,
|
||||
isAnnotationsEnabled: publicDashboard?.annotationsEnabled,
|
||||
enabledSwitch: publicDashboard?.isEnabled,
|
||||
});
|
||||
setAnnotationsEnabled(!!publicDashboard?.annotationsEnabled);
|
||||
}
|
||||
|
||||
setEnabledSwitch((prevState) => ({ ...prevState, isEnabled: !!publicDashboard?.isEnabled }));
|
||||
}, [publicDashboard]);
|
||||
}, [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>
|
||||
</>
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user