Skip to content

Commit

Permalink
feat(messaging): ui (#25811)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
mariusandra and github-actions[bot] authored Oct 25, 2024
1 parent f749943 commit d1f4eba
Show file tree
Hide file tree
Showing 33 changed files with 1,405 additions and 261 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.
10 changes: 10 additions & 0 deletions frontend/src/layout/navigation-3000/navigationLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
IconHome,
IconLive,
IconLogomark,
IconMegaphone,
IconNotebook,
IconPeople,
IconPieChart,
Expand Down Expand Up @@ -526,6 +527,15 @@ export const navigation3000Logic = kea<navigation3000LogicType>([
to: urls.pipeline(),
}
: null,
featureFlags[FEATURE_FLAGS.MESSAGING] && hasOnboardedAnyProduct
? {
identifier: Scene.MessagingBroadcasts,
label: 'Messaging',
icon: <IconMegaphone />,
to: urls.messagingBroadcasts(),
tag: 'alpha' as const,
}
: null,
].filter(isNotNil),
]
},
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ export const FEATURE_FLAGS = {
LEGACY_ACTION_WEBHOOKS: 'legacy-action-webhooks', // owner: @mariusandra #team-cdp
SESSION_REPLAY_URL_TRIGGER: 'session-replay-url-trigger', // owner: @richard-better #team-replay
REPLAY_TEMPLATES: 'replay-templates', // owner: @raquelmsmith #team-replay
MESSAGING: 'messaging', // owner @mariusandra #team-cdp
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]

Expand Down
2 changes: 2 additions & 0 deletions frontend/src/scenes/appScenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,6 @@ export const appScenes: Record<Scene, () => any> = {
[Scene.Heatmaps]: () => import('./heatmaps/HeatmapsScene'),
[Scene.SessionAttributionExplorer]: () =>
import('scenes/web-analytics/SessionAttributionExplorer/SessionAttributionExplorerScene'),
[Scene.MessagingProviders]: () => import('./messaging/Providers'),
[Scene.MessagingBroadcasts]: () => import('./messaging/Broadcasts'),
}
43 changes: 43 additions & 0 deletions frontend/src/scenes/messaging/Broadcasts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { IconPlusSmall } from '@posthog/icons'
import { useValues } from 'kea'
import { PageHeader } from 'lib/components/PageHeader'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { broadcastsLogic } from 'scenes/messaging/broadcastsLogic'
import { FunctionsTable } from 'scenes/messaging/FunctionsTable'
import { MessagingTabs } from 'scenes/messaging/MessagingTabs'
import { HogFunctionConfiguration } from 'scenes/pipeline/hogfunctions/HogFunctionConfiguration'
import { SceneExport } from 'scenes/sceneTypes'
import { urls } from 'scenes/urls'

export function Broadcasts(): JSX.Element {
const { broadcastId } = useValues(broadcastsLogic)
return broadcastId ? (
<HogFunctionConfiguration
id={broadcastId === 'new' ? null : broadcastId}
templateId={broadcastId === 'new' ? 'template-new-broadcast' : ''}
/>
) : (
<>
<MessagingTabs key="tabs" />
<PageHeader
caption="Send one time communications to your users"
buttons={
<LemonButton
data-attr="new-broadcast"
to={urls.messagingBroadcastNew()}
type="primary"
icon={<IconPlusSmall />}
>
New broadcast
</LemonButton>
}
/>
<FunctionsTable type="broadcast" />
</>
)
}

export const scene: SceneExport = {
component: Broadcasts,
logic: broadcastsLogic,
}
125 changes: 125 additions & 0 deletions frontend/src/scenes/messaging/FunctionsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { LemonInput, LemonTable, LemonTableColumn, Link, Tooltip } from '@posthog/lemon-ui'
import { BindLogic, useActions, useValues } from 'kea'
import { More } from 'lib/lemon-ui/LemonButton/More'
import { LemonMenuOverlay } from 'lib/lemon-ui/LemonMenu/LemonMenu'
import { updatedAtColumn } from 'lib/lemon-ui/LemonTable/columnUtils'
import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink'
import { functionsTableLogic } from 'scenes/messaging/functionsTableLogic'
import { hogFunctionUrl } from 'scenes/pipeline/hogfunctions/urls'

import { HogFunctionType, HogFunctionTypeType } from '~/types'

import { HogFunctionIcon } from '../pipeline/hogfunctions/HogFunctionIcon'
import { HogFunctionStatusIndicator } from '../pipeline/hogfunctions/HogFunctionStatusIndicator'

export interface FunctionsTableProps {
type?: HogFunctionTypeType
}

export function FunctionsTableFilters(): JSX.Element | null {
const { filters } = useValues(functionsTableLogic)
const { setFilters } = useActions(functionsTableLogic)

return (
<div className="space-y-2">
<div className="flex items-center gap-2">
<LemonInput
type="search"
placeholder="Search..."
value={filters.search ?? ''}
onChange={(e) => setFilters({ search: e })}
/>
</div>
</div>
)
}

export function FunctionsTable({ type }: FunctionsTableProps): JSX.Element {
const { hogFunctions, filteredHogFunctions, loading } = useValues(functionsTableLogic({ type }))
const { deleteHogFunction, resetFilters } = useActions(functionsTableLogic({ type }))

return (
<BindLogic logic={functionsTableLogic} props={{ type }}>
<div className="space-y-2">
<FunctionsTableFilters />

<LemonTable
dataSource={filteredHogFunctions}
size="small"
loading={loading}
columns={[
{
title: 'App',
width: 0,
render: function RenderAppInfo(_, hogFucntion) {
return <HogFunctionIcon src={hogFucntion.icon_url} size="small" />
},
},
{
title: 'Name',
sticky: true,
sorter: true,
key: 'name',
dataIndex: 'name',
render: function RenderPluginName(_, hogFunction) {
return (
<LemonTableLink
to={hogFunctionUrl(hogFunction.type, hogFunction.id)}
title={
<>
<Tooltip title="Click to update configuration, view metrics, and more">
<span>{hogFunction.name}</span>
</Tooltip>
</>
}
description={hogFunction.description}
/>
)
},
},

updatedAtColumn() as LemonTableColumn<HogFunctionType, any>,
{
title: 'Status',
key: 'enabled',
sorter: (a) => (a.enabled ? 1 : -1),
width: 0,
render: function RenderStatus(_, hogFunction) {
return <HogFunctionStatusIndicator hogFunction={hogFunction} />
},
},
{
width: 0,
render: function Render(_, hogFunction) {
return (
<More
overlay={
<LemonMenuOverlay
items={[
{
label: 'Delete',
status: 'danger' as const, // for typechecker happiness
onClick: () => deleteHogFunction(hogFunction),
},
]}
/>
}
/>
)
},
},
]}
emptyState={
hogFunctions.length === 0 && !loading ? (
'Nothing found'
) : (
<>
Nothing matches filters. <Link onClick={() => resetFilters()}>Clear filters</Link>{' '}
</>
)
}
/>
</div>
</BindLogic>
)
}
25 changes: 25 additions & 0 deletions frontend/src/scenes/messaging/MessagingTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useActions, useValues } from 'kea'
import { LemonTabs } from 'lib/lemon-ui/LemonTabs'
import { SceneExport } from 'scenes/sceneTypes'

import { MessagingTab, messagingTabsLogic } from './messagingTabsLogic'

export function MessagingTabs(): JSX.Element {
const { currentTab } = useValues(messagingTabsLogic)
const { setTab } = useActions(messagingTabsLogic)
return (
<LemonTabs
activeKey={currentTab}
onChange={(tab) => setTab(tab as MessagingTab)}
tabs={[
{ key: 'broadcasts', label: 'Broadcasts' },
{ key: 'providers', label: 'Providers' },
]}
/>
)
}

export const scene: SceneExport = {
component: MessagingTabs,
logic: messagingTabsLogic,
}
32 changes: 32 additions & 0 deletions frontend/src/scenes/messaging/Providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useValues } from 'kea'
import { PageHeader } from 'lib/components/PageHeader'
import { FunctionsTable } from 'scenes/messaging/FunctionsTable'
import { MessagingTabs } from 'scenes/messaging/MessagingTabs'
import { providersLogic } from 'scenes/messaging/providersLogic'
import { HogFunctionConfiguration } from 'scenes/pipeline/hogfunctions/HogFunctionConfiguration'
import { HogFunctionTemplateList } from 'scenes/pipeline/hogfunctions/list/HogFunctionTemplateList'
import { SceneExport } from 'scenes/sceneTypes'

export function Providers(): JSX.Element {
const { providerId, templateId } = useValues(providersLogic)
return providerId ? (
<HogFunctionConfiguration id={providerId} templateId={templateId} />
) : (
<>
<MessagingTabs key="tabs" />
<PageHeader caption="Configure e-mail, SMS and other messaging providers here" />
<FunctionsTable type="email" />
<div className="mt-4" />
<h2>Add Provider</h2>
<HogFunctionTemplateList defaultFilters={{}} type="email" />
<div className="mt-2 text-muted">
Note: to add a provider that's not in the list, select one that's similar and edit its source to point
to the right API URLs
</div>
</>
)
}
export const scene: SceneExport = {
component: Providers,
logic: providersLogic,
}
65 changes: 65 additions & 0 deletions frontend/src/scenes/messaging/broadcastsLogic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { actions, kea, path, reducers, selectors } from 'kea'
import { urlToAction } from 'kea-router'
import { Scene } from 'scenes/sceneTypes'
import { urls } from 'scenes/urls'

import { Breadcrumb } from '~/types'

import type { broadcastsLogicType } from './broadcastsLogicType'

export const broadcastsLogic = kea<broadcastsLogicType>([
path(['scenes', 'messaging', 'broadcastsLogic']),
actions({
editBroadcast: (id: string | null) => ({ id }),
}),
reducers({
broadcastId: [null as string | null, { editBroadcast: (_, { id }) => id }],
}),
selectors({
breadcrumbs: [
(s) => [s.broadcastId],
(broadcastId): Breadcrumb[] => {
return [
{
key: Scene.MessagingBroadcasts,
name: 'Messaging',
path: urls.messagingBroadcasts(),
},
{
key: 'broadcasts',
name: 'Broadcasts',
path: urls.messagingBroadcasts(),
},
...(broadcastId === 'new'
? [
{
key: 'new-broadcast',
name: 'New broadcast',
path: urls.messagingBroadcastNew(),
},
]
: broadcastId
? [
{
key: 'edit-broadcast',
name: 'Edit broadcast',
path: urls.messagingBroadcast(broadcastId),
},
]
: []),
]
},
],
}),
urlToAction(({ actions }) => ({
'/messaging/broadcasts/new': () => {
actions.editBroadcast('new')
},
'/messaging/broadcasts/:id': ({ id }) => {
actions.editBroadcast(id ?? null)
},
'/messaging/broadcasts': () => {
actions.editBroadcast(null)
},
})),
])
Loading

0 comments on commit d1f4eba

Please sign in to comment.