2022-09-22 07:35:04 -05:00
import { css } from '@emotion/css' ;
2022-12-14 06:12:09 -06:00
import React , { useContext , useEffect , useMemo } from 'react' ;
import { useForm } from 'react-hook-form' ;
2022-11-04 13:08:50 -05:00
import { Subscription } from 'rxjs' ;
2022-09-22 07:35:04 -05:00
import { GrafanaTheme2 } from '@grafana/data/src' ;
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src' ;
import { reportInteraction } from '@grafana/runtime/src' ;
2022-11-04 13:08:50 -05:00
import {
Alert ,
Button ,
ClipboardButton ,
Field ,
HorizontalGroup ,
Input ,
useStyles2 ,
Spinner ,
ModalsContext ,
useForceUpdate ,
} from '@grafana/ui/src' ;
import { Layout } from '@grafana/ui/src/components/Layout/Layout' ;
2022-09-22 07:35:04 -05:00
import { contextSrv } from 'app/core/services/context_srv' ;
2022-11-03 14:30:12 -05:00
import {
useGetPublicDashboardQuery ,
useCreatePublicDashboardMutation ,
useUpdatePublicDashboardMutation ,
} from 'app/features/dashboard/api/publicDashboardApi' ;
2022-09-22 07:35:04 -05:00
import { AcknowledgeCheckboxes } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/AcknowledgeCheckboxes' ;
import { Configuration } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/Configuration' ;
import { Description } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/Description' ;
import {
dashboardHasTemplateVariables ,
generatePublicDashboardUrl ,
2022-12-01 10:02:10 -06:00
getUnsupportedDashboardDatasources ,
2022-09-22 07:35:04 -05:00
publicDashboardPersisted ,
} from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils' ;
import { ShareModalTabProps } from 'app/features/dashboard/components/ShareModal/types' ;
2022-11-04 13:08:50 -05:00
import { useIsDesktop } from 'app/features/dashboard/utils/screen' ;
import { DeletePublicDashboardButton } from 'app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardButton' ;
2022-09-22 07:35:04 -05:00
import { isOrgAdmin } from 'app/features/plugins/admin/permissions' ;
import { AccessControlAction } from 'app/types' ;
2022-11-04 13:08:50 -05:00
import { DashboardMetaChangedEvent } from '../../../../../types/events' ;
import { ShareModal } from '../ShareModal' ;
2022-09-22 07:35:04 -05:00
interface Props extends ShareModalTabProps { }
2022-12-14 06:12:09 -06:00
type SharePublicDashboardAcknowledgmentInputs = {
publicAcknowledgment : boolean ;
dataSourcesAcknowledgment : boolean ;
usageAcknowledgment : boolean ;
} ;
export type SharePublicDashboardInputs = {
isAnnotationsEnabled : boolean ;
2022-12-19 08:38:37 -06:00
isTimeRangeEnabled : boolean ;
2022-12-14 06:12:09 -06:00
enabledSwitch : boolean ;
} & SharePublicDashboardAcknowledgmentInputs ;
2022-09-22 07:35:04 -05:00
export const SharePublicDashboard = ( props : Props ) = > {
2022-11-04 13:08:50 -05:00
const forceUpdate = useForceUpdate ( ) ;
2022-09-22 07:35:04 -05:00
const styles = useStyles2 ( getStyles ) ;
2022-11-04 13:08:50 -05:00
const { showModal , hideModal } = useContext ( ModalsContext ) ;
const isDesktop = useIsDesktop ( ) ;
2022-09-22 07:35:04 -05:00
2022-11-04 13:08:50 -05:00
const dashboardVariables = props . dashboard . getVariables ( ) ;
const selectors = e2eSelectors . pages . ShareDashboardModal . PublicDashboard ;
const { hasPublicDashboard } = props . dashboard . meta ;
2022-11-03 14:30:12 -05:00
2022-09-22 07:35:04 -05:00
const {
2022-11-04 13:08:50 -05:00
isLoading : isGetLoading ,
2022-09-22 07:35:04 -05:00
data : publicDashboard ,
2022-11-04 13:08:50 -05:00
isError : isGetError ,
isFetching ,
2022-11-03 14:30:12 -05:00
} = useGetPublicDashboardQuery ( props . dashboard . uid , {
// if we don't have a public dashboard, don't try to load public dashboard
skip : ! hasPublicDashboard ,
} ) ;
2022-09-22 07:35:04 -05:00
2022-12-14 06:12:09 -06:00
const {
reset ,
handleSubmit ,
watch ,
register ,
formState : { dirtyFields } ,
} = useForm < SharePublicDashboardInputs > ( {
defaultValues : {
publicAcknowledgment : false ,
dataSourcesAcknowledgment : false ,
usageAcknowledgment : false ,
isAnnotationsEnabled : false ,
2022-12-19 08:38:37 -06:00
isTimeRangeEnabled : false ,
2022-12-14 06:12:09 -06:00
enabledSwitch : false ,
} ,
} ) ;
2022-11-03 14:30:12 -05:00
const [ createPublicDashboard , { isLoading : isSaveLoading } ] = useCreatePublicDashboardMutation ( ) ;
const [ updatePublicDashboard , { isLoading : isUpdateLoading } ] = useUpdatePublicDashboardMutation ( ) ;
2022-09-22 07:35:04 -05:00
useEffect ( ( ) = > {
2022-11-04 13:08:50 -05:00
const eventSubs = new Subscription ( ) ;
eventSubs . add ( props . dashboard . events . subscribe ( DashboardMetaChangedEvent , forceUpdate ) ) ;
2022-09-22 07:35:04 -05:00
reportInteraction ( 'grafana_dashboards_public_share_viewed' ) ;
2022-11-04 13:08:50 -05:00
return ( ) = > eventSubs . unsubscribe ( ) ;
} , [ props . dashboard . events , forceUpdate ] ) ;
2022-09-22 07:35:04 -05:00
useEffect ( ( ) = > {
2022-12-14 06:12:09 -06:00
const isPublicDashboardPersisted = publicDashboardPersisted ( publicDashboard ) ;
reset ( {
publicAcknowledgment : isPublicDashboardPersisted ,
dataSourcesAcknowledgment : isPublicDashboardPersisted ,
usageAcknowledgment : isPublicDashboardPersisted ,
isAnnotationsEnabled : publicDashboard?.annotationsEnabled ,
2022-12-19 08:38:37 -06:00
isTimeRangeEnabled : publicDashboard?.timeSelectionEnabled ,
2022-12-14 06:12:09 -06:00
enabledSwitch : publicDashboard?.isEnabled ,
} ) ;
} , [ publicDashboard , reset ] ) ;
2022-09-22 07:35:04 -05:00
2022-11-04 13:08:50 -05:00
const isLoading = isGetLoading || isSaveLoading || isUpdateLoading ;
2022-09-22 07:35:04 -05:00
const hasWritePermissions = contextSrv . hasAccess ( AccessControlAction . DashboardsPublicWrite , isOrgAdmin ( ) ) ;
2022-12-14 06:12:09 -06:00
const acknowledged =
watch ( 'publicAcknowledgment' ) && watch ( 'dataSourcesAcknowledgment' ) && watch ( 'usageAcknowledgment' ) ;
2022-11-04 13:08:50 -05:00
const isSaveDisabled = useMemo (
2022-09-22 07:35:04 -05:00
( ) = >
! hasWritePermissions ||
! acknowledged ||
props . dashboard . hasUnsavedChanges ( ) ||
isLoading ||
2022-11-04 13:08:50 -05:00
isFetching ||
isGetError ||
2022-12-14 06:12:09 -06:00
( ! publicDashboardPersisted ( publicDashboard ) && ! dirtyFields . enabledSwitch ) ,
2022-11-04 13:08:50 -05:00
[
hasWritePermissions ,
acknowledged ,
props . dashboard ,
isLoading ,
isGetError ,
publicDashboard ,
isFetching ,
2022-12-14 06:12:09 -06:00
dirtyFields . enabledSwitch ,
2022-11-04 13:08:50 -05:00
]
2022-09-22 07:35:04 -05:00
) ;
2022-12-14 06:12:09 -06:00
2022-11-04 13:08:50 -05:00
const isDeleteDisabled = isLoading || isFetching || isGetError ;
2022-09-22 07:35:04 -05:00
2022-12-14 06:12:09 -06:00
const onSavePublicConfig = async ( values : SharePublicDashboardInputs ) = > {
2022-09-22 07:35:04 -05:00
reportInteraction ( 'grafana_dashboards_public_create_clicked' ) ;
2022-11-03 14:30:12 -05:00
const req = {
2022-09-22 07:35:04 -05:00
dashboard : props.dashboard ,
2022-12-14 06:12:09 -06:00
payload : {
. . . publicDashboard ! ,
isEnabled : values.enabledSwitch ,
annotationsEnabled : values.isAnnotationsEnabled ,
2022-12-19 08:38:37 -06:00
timeSelectionEnabled : values.isTimeRangeEnabled ,
2022-12-14 06:12:09 -06:00
} ,
2022-11-03 14:30:12 -05:00
} ;
// create or update based on whether we have existing uid
2022-11-04 13:08:50 -05:00
hasPublicDashboard ? updatePublicDashboard ( req ) : createPublicDashboard ( req ) ;
2022-09-22 07:35:04 -05:00
} ;
2022-11-04 13:08:50 -05:00
const onDismissDelete = ( ) = > {
showModal ( ShareModal , {
dashboard : props.dashboard ,
onDismiss : hideModal ,
activeTab : 'share' ,
} ) ;
} ;
2022-09-22 07:35:04 -05:00
return (
< >
< HorizontalGroup >
< p
className = { css `
margin : 0 ;
` }
>
Welcome to Grafana public dashboards alpha !
< / p >
2022-11-04 13:08:50 -05:00
{ ( isGetLoading || isFetching ) && < Spinner / > }
2022-09-22 07:35:04 -05:00
< / HorizontalGroup >
< div className = { styles . content } >
2022-12-01 10:02:10 -06:00
{ getUnsupportedDashboardDatasources ( props . dashboard . panels ) . length > 0 ? (
< Alert
severity = "warning"
title = "Unsupported Datasources"
data - testid = { selectors . UnsupportedDatasourcesWarningAlert }
>
< div >
{ ` There are datasources in this dashboard that are unsupported for public dashboards. Panels that use these datasources may not function properly: ${ getUnsupportedDashboardDatasources (
props . dashboard . panels
) . join ( ', ' ) } . See the ` }
< a href = "https://grafana.com/docs/grafana/latest/dashboards/dashboard-public/" className = "text-link" >
docs
< / a > { ' ' }
for supported datasources .
< / div >
< / Alert >
) : null }
2022-09-28 13:34:53 -05:00
{ dashboardHasTemplateVariables ( dashboardVariables ) && ! publicDashboardPersisted ( publicDashboard ) ? (
2022-09-22 07:35:04 -05:00
< Alert
severity = "warning"
title = "dashboard cannot be public"
data - testid = { selectors . TemplateVariablesWarningAlert }
>
This dashboard cannot be made public because it has template variables
< / Alert >
) : (
2022-12-14 06:12:09 -06:00
< form onSubmit = { handleSubmit ( onSavePublicConfig ) } >
2022-09-22 07:35:04 -05:00
< Description / >
< hr / >
< div className = { styles . checkboxes } >
< AcknowledgeCheckboxes
2022-11-04 13:08:50 -05:00
disabled = { publicDashboardPersisted ( publicDashboard ) || ! hasWritePermissions || isLoading || isGetError }
2022-12-14 06:12:09 -06:00
register = { register }
2022-09-22 07:35:04 -05:00
/ >
< / div >
< hr / >
< Configuration
2022-12-14 06:12:09 -06:00
register = { register }
2022-09-22 07:35:04 -05:00
dashboard = { props . dashboard }
2022-11-04 13:08:50 -05:00
disabled = { ! hasWritePermissions || isLoading || isGetError }
2022-09-22 07:35:04 -05:00
/ >
2022-12-14 06:12:09 -06:00
{ publicDashboardPersisted ( publicDashboard ) && watch ( 'enabledSwitch' ) && (
2022-09-22 07:35:04 -05:00
< Field label = "Link URL" className = { styles . publicUrl } >
< Input
disabled = { isLoading }
value = { generatePublicDashboardUrl ( publicDashboard ! ) }
readOnly
data - testid = { selectors . CopyUrlInput }
addonAfter = {
< ClipboardButton
data - testid = { selectors . CopyUrlButton }
variant = "primary"
icon = "copy"
getText = { ( ) = > generatePublicDashboardUrl ( publicDashboard ! ) }
>
Copy
< / ClipboardButton >
}
/ >
< / Field >
) }
{ hasWritePermissions ? (
2022-09-28 13:34:53 -05:00
props . dashboard . hasUnsavedChanges ( ) ? (
2022-09-22 07:35:04 -05:00
< Alert
title = "Please save your dashboard changes before updating the public configuration"
severity = "warning"
/ >
2022-09-28 13:34:53 -05:00
) : (
dashboardHasTemplateVariables ( dashboardVariables ) && (
< Alert
title = "This public dashboard may not work since it uses template variables"
severity = "warning"
/ >
)
2022-09-22 07:35:04 -05:00
)
) : (
< Alert title = "You don't have permissions to create or update a public dashboard" severity = "warning" / >
) }
< HorizontalGroup >
2022-11-04 13:08:50 -05:00
< Layout orientation = { isDesktop ? 0 : 1 } >
2022-12-14 06:12:09 -06:00
< Button type = "submit" disabled = { isSaveDisabled } data - testid = { selectors . SaveConfigButton } >
2022-11-04 13:08:50 -05:00
{ hasPublicDashboard ? 'Save public dashboard' : 'Create public dashboard' }
< / Button >
{ publicDashboard && hasWritePermissions && (
< DeletePublicDashboardButton
2022-12-14 06:12:09 -06:00
type = "button"
2022-11-04 13:08:50 -05:00
disabled = { isDeleteDisabled }
data - testid = { selectors . DeleteButton }
onDismiss = { onDismissDelete }
variant = "destructive"
dashboard = { props . dashboard }
publicDashboard = { {
uid : publicDashboard.uid ,
dashboardUid : props.dashboard.uid ,
title : props.dashboard.title ,
} }
>
Delete public dashboard
< / DeletePublicDashboardButton >
) }
< / Layout >
{ ( isSaveLoading || isFetching ) && < Spinner / > }
2022-09-22 07:35:04 -05:00
< / HorizontalGroup >
2022-12-14 06:12:09 -06:00
< / form >
2022-09-22 07:35:04 -05:00
) }
< / div >
< / >
) ;
} ;
const getStyles = ( theme : GrafanaTheme2 ) = > ( {
content : css `
margin : $ { theme . spacing ( 1 , 0 , 0 , 0 ) } ;
` ,
checkboxes : css `
margin : $ { theme . spacing ( 2 , 0 ) } ;
` ,
timeRange : css `
padding : $ { theme . spacing ( 1 , 1 ) } ;
margin : $ { theme . spacing ( 0 , 0 , 2 , 0 ) } ;
` ,
publicUrl : css `
width : 100 % ;
margin - bottom : $ { theme . spacing ( 0 , 0 , 3 , 0 ) } ;
` ,
} ) ;