Skip to content

Commit

Permalink
feat(cdp): Easy setup of destinations from other sources (#24394)
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
benjackwhite and github-actions[bot] authored Aug 28, 2024
1 parent e7e4006 commit 4bb6c50
Show file tree
Hide file tree
Showing 32 changed files with 1,480 additions and 202 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.
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export const FEATURE_FLAGS = {
SETTINGS_PERSONS_ON_EVENTS_HIDDEN: 'settings-persons-on-events-hidden', // owner: @Twixes
HOG: 'hog', // owner: @mariusandra
HOG_FUNCTIONS: 'hog-functions', // owner: #team-cdp
HOG_FUNCTIONS_LINKED: 'hog-functions-linked', // owner: #team-cdp
PERSONLESS_EVENTS_NOT_SUPPORTED: 'personless-events-not-supported', // owner: @raquelmsmith
ALERTS: 'alerts', // owner: github.com/nikitaevg
ERROR_TRACKING: 'error-tracking', // owner: #team-replay
Expand Down
53 changes: 14 additions & 39 deletions frontend/src/scenes/actions/ActionHogFunctions.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,34 @@
import { LemonButton } from '@posthog/lemon-ui'
import { useValues } from 'kea'
import { useFeatureFlag } from 'lib/hooks/useFeatureFlag'
import { actionLogic } from 'scenes/actions/actionLogic'
import { DestinationsTable } from 'scenes/pipeline/destinations/Destinations'
import { PipelineBackend } from 'scenes/pipeline/types'
import { urls } from 'scenes/urls'
import { LinkedHogFunctions } from 'scenes/pipeline/hogfunctions/list/LinkedHogFunctions'

import { PipelineStage } from '~/types'
import { HogFunctionFiltersType } from '~/types'

export function ActionHogFunctions(): JSX.Element | null {
const { action } = useValues(actionLogic)

const hogFunctionsEnabled = useFeatureFlag('HOG_FUNCTIONS')

if (!action || !hogFunctionsEnabled) {
return null
}

const filters: HogFunctionFiltersType = {
actions: [
{
id: `${action?.id}`,
name: action?.name,
type: 'actions',
},
],
}

return (
<div className="my-4 space-y-2">
<div className="flex items-center gap-2">
<h2 className="flex-1 subtitle">Connected destinations</h2>

<LemonButton
type="primary"
size="small"
to={
urls.pipelineNodeNew(PipelineStage.Destination) +
`?kind=hog_function#configuration=${JSON.stringify({
filters: {
actions: [
{
id: `${action?.id}`,
name: `${action?.name}`,
type: 'actions',
},
],
},
})}`
}
>
New destination
</LemonButton>
</div>
<h2 className="flex-1 subtitle">Connected destinations</h2>
<p>Actions can be used a filters for destinations such as Slack or Webhook delivery</p>

<DestinationsTable
defaultFilters={{
onlyActive: true,
}}
forceFilters={{
kind: PipelineBackend.HogFunction,
filters: { actions: [{ id: `${action.id}` }] },
}}
/>
<LinkedHogFunctions filters={filters} />
</div>
)
}
22 changes: 22 additions & 0 deletions frontend/src/scenes/data-management/definition/DefinitionView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { ObjectTags } from 'lib/components/ObjectTags/ObjectTags'
import { PageHeader } from 'lib/components/PageHeader'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import { UserActivityIndicator } from 'lib/components/UserActivityIndicator/UserActivityIndicator'
import { useFeatureFlag } from 'lib/hooks/useFeatureFlag'
import { IconPlayCircle } from 'lib/lemon-ui/icons'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { LemonDialog } from 'lib/lemon-ui/LemonDialog'
import { SpinnerOverlay } from 'lib/lemon-ui/Spinner/Spinner'
import { getFilterLabel } from 'lib/taxonomy'
import { definitionLogic, DefinitionLogicProps } from 'scenes/data-management/definition/definitionLogic'
import { EventDefinitionProperties } from 'scenes/data-management/events/EventDefinitionProperties'
import { LinkedHogFunctions } from 'scenes/pipeline/hogfunctions/list/LinkedHogFunctions'
import { SceneExport } from 'scenes/sceneTypes'
import { urls } from 'scenes/urls'

Expand All @@ -35,6 +37,7 @@ export function DefinitionView(props: DefinitionLogicProps = {}): JSX.Element {
const { definition, definitionLoading, definitionMissing, hasTaxonomyFeatures, singular, isEvent, isProperty } =
useValues(logic)
const { deleteDefinition } = useActions(logic)
const hogFunctionsEnabled = useFeatureFlag('HOG_FUNCTIONS')

if (definitionLoading) {
return <SpinnerOverlay sceneLevel />
Expand Down Expand Up @@ -196,6 +199,25 @@ export function DefinitionView(props: DefinitionLogicProps = {}): JSX.Element {
{isEvent && definition.id !== 'new' && (
<>
<EventDefinitionProperties definition={definition} />

{hogFunctionsEnabled && (
<>
<LemonDivider className="my-6" />
<h2 className="flex-1 subtitle">Connected destinations</h2>
<p>Get notified via Slack, webhooks or more whenever this event is captured.</p>

<LinkedHogFunctions
filters={{
events: [
{
id: `${definition.name}`,
type: 'events',
},
],
}}
/>
</>
)}
<LemonDivider className="my-6" />
<h3>Matching events</h3>
<p>This is the list of recent events that match this definition.</p>
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/scenes/early-access-features/EarlyAccessFeature.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import { router } from 'kea-router'
import { FlagSelector } from 'lib/components/FlagSelector'
import { NotFound } from 'lib/components/NotFound'
import { PageHeader } from 'lib/components/PageHeader'
import { useFeatureFlag } from 'lib/hooks/useFeatureFlag'
import { LemonDialog } from 'lib/lemon-ui/LemonDialog'
import { LemonField } from 'lib/lemon-ui/LemonField'
import { LemonTabs } from 'lib/lemon-ui/LemonTabs'
import { useState } from 'react'
import { LinkedHogFunctions } from 'scenes/pipeline/hogfunctions/list/LinkedHogFunctions'
import { SceneExport } from 'scenes/sceneTypes'
import { urls } from 'scenes/urls'

Expand All @@ -21,6 +23,7 @@ import {
EarlyAccessFeatureTabs,
EarlyAccessFeatureType,
FilterLogicalOperator,
HogFunctionFiltersType,
PersonPropertyFilter,
PropertyFilterType,
PropertyOperator,
Expand Down Expand Up @@ -58,6 +61,7 @@ export function EarlyAccessFeature({ id }: { id?: string } = {}): JSX.Element {
} = useActions(earlyAccessFeatureLogic)

const isNewEarlyAccessFeature = id === 'new' || id === undefined
const showLinkedHogFunctions = useFeatureFlag('HOG_FUNCTIONS_LINKED')

if (earlyAccessFeatureMissing) {
return <NotFound object="early access feature" />
Expand All @@ -67,6 +71,26 @@ export function EarlyAccessFeature({ id }: { id?: string } = {}): JSX.Element {
return <LemonSkeleton active />
}

const destinationFilters: HogFunctionFiltersType | null =
!isEditingFeature && !isNewEarlyAccessFeature && 'id' in earlyAccessFeature && showLinkedHogFunctions
? {
events: [
{
id: '$feature_enrollment_update',
type: 'events',
properties: [
{
key: '$feature_flag',
value: [earlyAccessFeature.feature_flag.key],
operator: PropertyOperator.Exact,
type: PropertyFilterType.Event,
},
],
},
],
}
: null

return (
<Form id="early-access-feature" formKey="earlyAccessFeature" logic={earlyAccessFeatureLogic}>
<PageHeader
Expand Down Expand Up @@ -301,6 +325,17 @@ export function EarlyAccessFeature({ id }: { id?: string } = {}): JSX.Element {
</div>
</div>
</div>
{destinationFilters && (
<>
<LemonDivider className="my-8" />
<h3>Notifications</h3>
<p>Get notified when people opt in or out of your feature.</p>
<LinkedHogFunctions
filters={destinationFilters}
subTemplateId="early_access_feature_enrollment"
/>
</>
)}
{!isEditingFeature && !isNewEarlyAccessFeature && 'id' in earlyAccessFeature && (
<>
<LemonDivider className="my-8" />
Expand Down
37 changes: 1 addition & 36 deletions frontend/src/scenes/pipeline/AppMetricSparkLine.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { useActions, useValues } from 'kea'
import { useValues } from 'kea'
import { Sparkline, SparklineTimeSeries } from 'lib/components/Sparkline'
import { useEffect } from 'react'

import { pipelineNodeMetricsLogic } from './pipelineNodeMetricsLogic'
import { pipelineNodeMetricsV2Logic } from './pipelineNodeMetricsV2Logic'
import { PipelineNode } from './types'

export function AppMetricSparkLine({ pipelineNode }: { pipelineNode: PipelineNode }): JSX.Element {
Expand Down Expand Up @@ -42,36 +40,3 @@ export function AppMetricSparkLine({ pipelineNode }: { pipelineNode: PipelineNod
/>
)
}

export function AppMetricSparkLineV2({ pipelineNode }: { pipelineNode: PipelineNode }): JSX.Element {
const logic = pipelineNodeMetricsV2Logic({ id: `${pipelineNode.id}`.replace('hog-', '') })
const { appMetrics, appMetricsLoading } = useValues(logic)
const { loadMetrics } = useActions(logic)

useEffect(() => {
loadMetrics()
}, [])

const displayData: SparklineTimeSeries[] = [
{
color: 'success',
name: 'Success',
values: appMetrics?.series.find((s) => s.name === 'succeeded')?.values || [],
},
{
color: 'danger',
name: 'Failures',
values: appMetrics?.series.find((s) => s.name === 'failed')?.values || [],
},
]

return (
<Sparkline
loading={appMetricsLoading}
labels={appMetrics?.labels}
data={displayData}
className="max-w-24 h-8"
maximumIndicator={false}
/>
)
}
4 changes: 2 additions & 2 deletions frontend/src/scenes/pipeline/PipelineNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import { urls } from 'scenes/urls'
import { ActivityScope, PipelineNodeTab, PipelineStage, PipelineTab } from '~/types'

import { BatchExportRuns } from './BatchExportRuns'
import { AppMetricsV2 } from './metrics/AppMetricsV2'
import { PipelineNodeConfiguration } from './PipelineNodeConfiguration'
import { pipelineNodeLogic, PipelineNodeLogicProps } from './pipelineNodeLogic'
import { PipelineNodeMetrics } from './PipelineNodeMetrics'
import { PipelineNodeMetricsV2 } from './PipelineNodeMetricsV2'
import { PipelineBackend } from './types'

export const PIPELINE_TAB_TO_NODE_STAGE: Partial<Record<PipelineTab, PipelineStage>> = {
Expand Down Expand Up @@ -67,7 +67,7 @@ export function PipelineNode(params: { stage?: string; id?: string } = {}): JSX.
[PipelineNodeTab.Configuration]: <PipelineNodeConfiguration />,
[PipelineNodeTab.Metrics]:
node.backend === PipelineBackend.HogFunction ? (
<PipelineNodeMetricsV2 />
<AppMetricsV2 id={node.id} />
) : (
<PipelineNodeMetrics id={id} />
),
Expand Down
69 changes: 35 additions & 34 deletions frontend/src/scenes/pipeline/destinations/Destinations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ import { urls } from 'scenes/urls'

import { AvailableFeature, PipelineNodeTab, PipelineStage, ProductKey } from '~/types'

import { AppMetricSparkLine, AppMetricSparkLineV2 } from '../AppMetricSparkLine'
import { AppMetricSparkLine } from '../AppMetricSparkLine'
import { HogFunctionIcon } from '../hogfunctions/HogFunctionIcon'
import { AppMetricSparkLineV2 } from '../metrics/AppMetricsV2Sparkline'
import { NewButton } from '../NewButton'
import { pipelineAccessLogic } from '../pipelineAccessLogic'
import { Destination, PipelineBackend } from '../types'
Expand Down Expand Up @@ -55,8 +56,9 @@ export function Destinations(): JSX.Element {
}

export function DestinationsTable({ ...props }: PipelineDestinationsLogicProps): JSX.Element {
const { canConfigurePlugins, canEnableDestination } = useValues(pipelineAccessLogic)
const { loading, filteredDestinations, filters, destinations } = useValues(pipelineDestinationsLogic(props))
const { setFilters, resetFilters } = useActions(pipelineDestinationsLogic(props))
const { setFilters, resetFilters, toggleNode, deleteNode } = useActions(pipelineDestinationsLogic(props))

const hasHogFunctions = !!useFeatureFlag('HOG_FUNCTIONS')

Expand Down Expand Up @@ -168,7 +170,7 @@ export function DestinationsTable({ ...props }: PipelineDestinationsLogicProps):
)}
>
{destination.backend === PipelineBackend.HogFunction ? (
<AppMetricSparkLineV2 pipelineNode={destination} />
<AppMetricSparkLineV2 id={destination.hog_function.id} />
) : (
<AppMetricSparkLine pipelineNode={destination} />
)}
Expand Down Expand Up @@ -201,7 +203,36 @@ export function DestinationsTable({ ...props }: PipelineDestinationsLogicProps):
{
width: 0,
render: function Render(_, destination) {
return <More overlay={<DestinationMoreOverlay destination={destination} />} />
return (
<More
overlay={
<LemonMenuOverlay
items={[
{
label: destination.enabled
? 'Pause destination'
: 'Unpause destination',
onClick: () => toggleNode(destination, !destination.enabled),
disabledReason: !canConfigurePlugins
? 'You do not have permission to toggle destinations.'
: !canEnableDestination(destination) && !destination.enabled
? 'Data pipelines add-on is required for enabling new destinations'
: undefined,
},
...pipelineNodeMenuCommonItems(destination),
{
label: 'Delete destination',
status: 'danger' as const, // for typechecker happiness
onClick: () => deleteNode(destination),
disabledReason: canConfigurePlugins
? undefined
: 'You do not have permission to delete destinations.',
},
]}
/>
}
/>
)
},
},
]}
Expand All @@ -220,33 +251,3 @@ export function DestinationsTable({ ...props }: PipelineDestinationsLogicProps):
</>
)
}

const DestinationMoreOverlay = ({ destination }: { destination: Destination }): JSX.Element => {
const { canConfigurePlugins, canEnableDestination } = useValues(pipelineAccessLogic)
const { toggleNode, deleteNode } = useActions(pipelineDestinationsLogic)

return (
<LemonMenuOverlay
items={[
{
label: destination.enabled ? 'Pause destination' : 'Unpause destination',
onClick: () => toggleNode(destination, !destination.enabled),
disabledReason: !canConfigurePlugins
? 'You do not have permission to toggle destinations.'
: !canEnableDestination(destination) && !destination.enabled
? 'Data pipelines add-on is required for enabling new destinations'
: undefined,
},
...pipelineNodeMenuCommonItems(destination),
{
label: 'Delete destination',
status: 'danger' as const, // for typechecker happiness
onClick: () => deleteNode(destination),
disabledReason: canConfigurePlugins
? undefined
: 'You do not have permission to delete destinations.',
},
]}
/>
)
}
Loading

0 comments on commit 4bb6c50

Please sign in to comment.