Skip to content

Commit

Permalink
chore: better dashboard empty state (#18999)
Browse files Browse the repository at this point in the history
  • Loading branch information
daibhin authored Dec 1, 2023
1 parent 0e72710 commit 29dbea2
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 237 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 0 additions & 47 deletions frontend/src/scenes/dashboard/Dashboard.scss

This file was deleted.

2 changes: 0 additions & 2 deletions frontend/src/scenes/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import './Dashboard.scss'

import { IconCalendar } from '@posthog/icons'
import { LemonButton, LemonDivider } from '@posthog/lemon-ui'
import { BindLogic, useActions, useValues } from 'kea'
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/scenes/dashboard/DashboardTemplateChooser.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.DashboardTemplateChooser {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5em 0.5em;
max-width: 780px;

.TemplateItem {
width: 240px;
height: 210px;
}
}
121 changes: 121 additions & 0 deletions frontend/src/scenes/dashboard/DashboardTemplateChooser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import './DashboardTemplateChooser.scss'

import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { FallbackCoverImage } from 'lib/components/FallbackCoverImage/FallbackCoverImage'
import BlankDashboardHog from 'public/blank-dashboard-hog.png'
import { useState } from 'react'
import { dashboardTemplatesLogic } from 'scenes/dashboard/dashboards/templates/dashboardTemplatesLogic'
import { newDashboardLogic } from 'scenes/dashboard/newDashboardLogic'

import { DashboardTemplateScope, DashboardTemplateType } from '~/types'

interface DashboardTemplateChooserProps {
scope?: DashboardTemplateScope
}

export function DashboardTemplateChooser({ scope = 'global' }: DashboardTemplateChooserProps): JSX.Element {
const templatesLogic = dashboardTemplatesLogic({ scope })
const { allTemplates } = useValues(templatesLogic)

const { isLoading, newDashboardModalVisible } = useValues(newDashboardLogic)
const {
setActiveDashboardTemplate,
createDashboardFromTemplate,
addDashboard,
setIsLoading,
showVariableSelectModal,
} = useActions(newDashboardLogic)

return (
<div>
<div className="DashboardTemplateChooser">
<TemplateItem
template={{
template_name: 'Blank dashboard',
dashboard_description: 'Create a blank dashboard',
image_url: BlankDashboardHog,
}}
onClick={() => {
if (isLoading) {
return
}
setIsLoading(true)
addDashboard({
name: 'New Dashboard',
show: true,
})
}}
index={0}
data-attr="create-dashboard-blank"
/>
{allTemplates.map((template, index) => (
<TemplateItem
key={index}
template={template}
onClick={() => {
if (isLoading) {
return
}
setIsLoading(true)
// while we might receive templates from the external repository
// we need to handle templates that don't have variables
if ((template.variables || []).length === 0) {
if (template.variables === null) {
template.variables = []
}
createDashboardFromTemplate(template, template.variables || [])
} else {
if (!newDashboardModalVisible) {
showVariableSelectModal(template)
} else {
setActiveDashboardTemplate(template)
}
}
}}
index={index + 1}
data-attr="create-dashboard-from-template"
/>
))}
{/*TODO @lukeharries should we have an empty state here? When no templates let people know what to do?*/}
</div>
</div>
)
}

function TemplateItem({
template,
onClick,
index,
'data-attr': dataAttr,
}: {
template: Pick<DashboardTemplateType, 'template_name' | 'dashboard_description' | 'image_url'>
onClick: () => void
index: number
'data-attr': string
}): JSX.Element {
const [isHovering, setIsHovering] = useState(false)

return (
<div
className="cursor-pointer border rounded TemplateItem flex flex-col transition-all"
onClick={onClick}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
data-attr={dataAttr}
>
<div
className={clsx('transition-all w-full overflow-hidden', isHovering ? 'h-4 min-h-4' : 'h-30 min-h-30')}
>
<FallbackCoverImage src={template?.image_url} alt="cover photo" index={index} imageClassName="h-30" />
</div>

<h5 className="px-2 mb-1">{template?.template_name}</h5>
<div className="px-2 py-1 overflow-y-auto grow">
<p className={clsx('text-muted-alt text-xs', isHovering ? '' : 'line-clamp-2')}>
{template?.dashboard_description ?? ' '}
</p>
</div>
</div>
)
}
13 changes: 0 additions & 13 deletions frontend/src/scenes/dashboard/NewDashboardModal.scss

This file was deleted.

120 changes: 1 addition & 119 deletions frontend/src/scenes/dashboard/NewDashboardModal.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,13 @@
import './NewDashboardModal.scss'

import { LemonButton } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useMountedLogic, useValues } from 'kea'
import { FallbackCoverImage } from 'lib/components/FallbackCoverImage/FallbackCoverImage'
import { LemonModal } from 'lib/lemon-ui/LemonModal'
import { pluralize } from 'lib/utils'
import BlankDashboardHog from 'public/blank-dashboard-hog.png'
import { useState } from 'react'
import { dashboardTemplatesLogic } from 'scenes/dashboard/dashboards/templates/dashboardTemplatesLogic'
import { newDashboardLogic } from 'scenes/dashboard/newDashboardLogic'

import { DashboardTemplateScope, DashboardTemplateType } from '~/types'

import { DashboardTemplateChooser } from './DashboardTemplateChooser'
import { DashboardTemplateVariables } from './DashboardTemplateVariables'
import { dashboardTemplateVariablesLogic } from './dashboardTemplateVariablesLogic'

function TemplateItem({
template,
onClick,
index,
'data-attr': dataAttr,
}: {
template: Pick<DashboardTemplateType, 'template_name' | 'dashboard_description' | 'image_url'>
onClick: () => void
index: number
'data-attr': string
}): JSX.Element {
const [isHovering, setIsHovering] = useState(false)

return (
<div
className="cursor-pointer border rounded TemplateItem flex flex-col transition-all"
onClick={onClick}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
data-attr={dataAttr}
>
<div
className={clsx('transition-all w-full overflow-hidden', isHovering ? 'h-4 min-h-4' : 'h-30 min-h-30')}
>
<FallbackCoverImage src={template?.image_url} alt="cover photo" index={index} imageClassName="h-30" />
</div>

<h5 className="px-2 mb-1">{template?.template_name}</h5>
<div className="px-2 py-1 overflow-y-auto grow">
<p className={clsx('text-muted-alt text-xs', isHovering ? '' : 'line-clamp-2')}>
{template?.dashboard_description ?? ' '}
</p>
</div>
</div>
)
}

export function DashboardTemplatePreview(): JSX.Element {
const { activeDashboardTemplate, variableSelectModalVisible } = useValues(newDashboardLogic)
const { variables } = useValues(dashboardTemplateVariablesLogic)
Expand Down Expand Up @@ -83,79 +38,6 @@ export function DashboardTemplatePreview(): JSX.Element {
)
}

interface DashboardTemplateChooserProps {
scope?: DashboardTemplateScope
}

export function DashboardTemplateChooser({ scope = 'global' }: DashboardTemplateChooserProps): JSX.Element {
const templatesLogic = dashboardTemplatesLogic({ scope })
const { allTemplates } = useValues(templatesLogic)

const { isLoading, newDashboardModalVisible } = useValues(newDashboardLogic)
const {
setActiveDashboardTemplate,
createDashboardFromTemplate,
addDashboard,
setIsLoading,
showVariableSelectModal,
} = useActions(newDashboardLogic)

return (
<div>
<div className="DashboardTemplateChooser">
<TemplateItem
template={{
template_name: 'Blank dashboard',
dashboard_description: 'Create a blank dashboard',
image_url: BlankDashboardHog,
}}
onClick={() => {
if (isLoading) {
return
}
setIsLoading(true)
addDashboard({
name: 'New Dashboard',
show: true,
})
}}
index={0}
data-attr="create-dashboard-blank"
/>
{allTemplates.map((template, index) => (
<TemplateItem
key={index}
template={template}
onClick={() => {
if (isLoading) {
return
}
setIsLoading(true)
// while we might receive templates from the external repository
// we need to handle templates that don't have variables
if ((template.variables || []).length === 0) {
if (template.variables === null) {
template.variables = []
}
createDashboardFromTemplate(template, template.variables || [])
} else {
if (!newDashboardModalVisible) {
showVariableSelectModal(template)
} else {
setActiveDashboardTemplate(template)
}
}
}}
index={index + 1}
data-attr="create-dashboard-from-template"
/>
))}
{/*TODO @lukeharries should we have an empty state here? When no templates let people know what to do?*/}
</div>
</div>
)
}

export function NewDashboardModal(): JSX.Element {
const builtLogic = useMountedLogic(newDashboardLogic)
const { hideNewDashboardModal } = useActions(newDashboardLogic)
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/scenes/dashboard/dashboards/Dashboards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { LemonTab, LemonTabs } from 'lib/lemon-ui/LemonTabs'
import { inAppPromptLogic } from 'lib/logic/inAppPrompt/inAppPromptLogic'
import { dashboardsLogic, DashboardsTab } from 'scenes/dashboard/dashboards/dashboardsLogic'
import { DashboardsTableContainer } from 'scenes/dashboard/dashboards/DashboardsTable'
import { NoDashboards } from 'scenes/dashboard/dashboards/NoDashboards'
import { DashboardTemplatesTable } from 'scenes/dashboard/dashboards/templates/DashboardTemplatesTable'
import { DeleteDashboardModal } from 'scenes/dashboard/DeleteDashboardModal'
import { DuplicateDashboardModal } from 'scenes/dashboard/DuplicateDashboardModal'
Expand All @@ -15,6 +14,8 @@ import { SceneExport } from 'scenes/sceneTypes'

import { dashboardsModel } from '~/models/dashboardsModel'

import { DashboardTemplateChooser } from '../DashboardTemplateChooser'

export const scene: SceneExport = {
component: Dashboards,
logic: dashboardsLogic,
Expand Down Expand Up @@ -64,7 +65,10 @@ export function Dashboards(): JSX.Element {
) : dashboardsLoading || dashboards.length > 0 || isFiltering ? (
<DashboardsTableContainer />
) : (
<NoDashboards />
<div className="mt-4">
<p>Create your first dashboard:</p>
<DashboardTemplateChooser />
</div>
)}
</div>
)
Expand Down
Loading

0 comments on commit 29dbea2

Please sign in to comment.