Skip to content

Commit 4ca3d2c

Browse files
committed
feat(surveys): cross-sell with experiments
1 parent 7b0ab07 commit 4ca3d2c

File tree

10 files changed

+342
-166
lines changed

10 files changed

+342
-166
lines changed

frontend/src/lib/constants.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ export const FEATURE_FLAGS = {
350350
HOME_FEED_TAB: 'home-feed-tab', // owner: @ksvat #team-replay
351351
SURVEYS_FF_CROSS_SELL: 'surveys-ff-cross-sell', // owner: @adboio #team-surveys
352352
SURVEYS_INSIGHT_BUTTON_EXPERIMENT: 'ask-users-why-ai-vs-quickcreate', // owner: @adboio #team-surveys multivariate
353+
SURVEYS_EXPERIMENTS_CROSS_SELL: 'surveys-experiments-cross-sell', // owner: @adboio #team-surveys
353354
} as const
354355
export type FeatureFlagLookupKey = keyof typeof FEATURE_FLAGS
355356
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useValues } from 'kea'
2+
3+
import { IconArrowRight } from '@posthog/icons'
4+
import { Link, Spinner } from '@posthog/lemon-ui'
5+
6+
import { FeedbackTabContent } from 'scenes/surveys/FeedbackTabContent'
7+
import { QuickSurveyType } from 'scenes/surveys/quick-create/types'
8+
import { SurveysTabs, surveysLogic } from 'scenes/surveys/surveysLogic'
9+
import { urls } from 'scenes/urls'
10+
11+
import { Experiment } from '~/types'
12+
13+
export function ExperimentFeedbackTab({ experiment }: { experiment: Experiment }): JSX.Element {
14+
const { data, dataLoading } = useValues(surveysLogic)
15+
16+
const linkedFlagId = experiment.feature_flag?.id
17+
const surveysForExperiment = linkedFlagId ? data.surveys.filter((s) => s.linked_flag_id === linkedFlagId) : []
18+
19+
if (dataLoading) {
20+
return (
21+
<div className="flex items-center justify-center py-8">
22+
<Spinner className="text-2xl" />
23+
</div>
24+
)
25+
}
26+
27+
return (
28+
<FeedbackTabContent
29+
surveys={surveysForExperiment}
30+
context={{
31+
type: QuickSurveyType.EXPERIMENT,
32+
experiment,
33+
}}
34+
emptyStateBannerMessage="Gather qualitative feedback from users participating in this experiment"
35+
multipleSurveysBannerMessage={
36+
<>
37+
Showing only surveys associated with this experiment.{' '}
38+
<Link to={urls.surveys(SurveysTabs.Active)}>
39+
See all surveys <IconArrowRight />
40+
</Link>
41+
</>
42+
}
43+
/>
44+
)
45+
}

frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useActions, useValues } from 'kea'
22

3-
import { LemonTabs } from '@posthog/lemon-ui'
3+
import { LemonTabs, LemonTag } from '@posthog/lemon-ui'
44

55
import { ActivityLog } from 'lib/components/ActivityLog/ActivityLog'
66
import { FEATURE_FLAGS } from 'lib/constants'
@@ -39,6 +39,7 @@ import { experimentSceneLogic } from '../experimentSceneLogic'
3939
import { getExperimentStatus } from '../experimentsLogic'
4040
import { isLegacyExperiment, isLegacyExperimentQuery, removeMetricFromOrderingArray } from '../utils'
4141
import { DistributionModal, DistributionTable } from './DistributionTable'
42+
import { ExperimentFeedbackTab } from './ExperimentFeedbackTab'
4243
import { ExperimentHeader } from './ExperimentHeader'
4344
import { ExposureCriteriaModal } from './ExposureCriteria'
4445
import { Exposures } from './Exposures'
@@ -300,6 +301,22 @@ export function ExperimentView({ tabId }: Pick<ExperimentSceneLogicProps, 'tabId
300301
label: 'History',
301302
content: <ActivityLog scope={ActivityScope.EXPERIMENT} id={experimentId} />,
302303
},
304+
...(experiment.feature_flag && featureFlags[FEATURE_FLAGS.SURVEYS_EXPERIMENTS_CROSS_SELL]
305+
? [
306+
{
307+
key: 'feedback',
308+
label: (
309+
<div className="flex flex-row">
310+
<div>User feedback</div>
311+
<LemonTag className="ml-2 float-right uppercase" type="primary">
312+
New
313+
</LemonTag>
314+
</div>
315+
),
316+
content: <ExperimentFeedbackTab experiment={experiment} />,
317+
},
318+
]
319+
: []),
303320
]}
304321
/>
305322

Lines changed: 21 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,32 @@
1-
import { BindLogic, useValues } from 'kea'
2-
31
import { IconArrowRight } from '@posthog/icons'
4-
import { LemonBanner, LemonTable, LemonTableColumn, Link, Spinner } from '@posthog/lemon-ui'
2+
import { Link } from '@posthog/lemon-ui'
53

6-
import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink'
7-
import { createdAtColumn } from 'lib/lemon-ui/LemonTable/columnUtils'
8-
import stringWithWBR from 'lib/utils/stringWithWBR'
9-
import { SurveyResult } from 'scenes/surveys/SurveyView'
10-
import { SurveyStatusTag } from 'scenes/surveys/components/SurveyStatusTag'
11-
import { QuickSurveyForm } from 'scenes/surveys/quick-create/QuickSurveyModal'
4+
import { FeedbackTabContent } from 'scenes/surveys/FeedbackTabContent'
125
import { QuickSurveyType } from 'scenes/surveys/quick-create/types'
13-
import { surveyLogic } from 'scenes/surveys/surveyLogic'
14-
import { SurveysTabs, surveysLogic } from 'scenes/surveys/surveysLogic'
6+
import { SurveysTabs } from 'scenes/surveys/surveysLogic'
157
import { urls } from 'scenes/urls'
168

17-
import { FeatureFlagType, Survey } from '~/types'
9+
import { FeatureFlagType } from '~/types'
1810

1911
export function FeedbackTab({ featureFlag }: { featureFlag: FeatureFlagType }): JSX.Element {
20-
const { surveysResponsesCountLoading, surveysResponsesCount } = useValues(surveysLogic)
21-
const surveysForFlag = featureFlag.surveys || []
22-
23-
if (surveysForFlag.length === 0) {
24-
return (
25-
<div className="flex flex-col items-center pt-5">
26-
<div className="w-full max-w-5xl">
27-
<LemonBanner type="info" className="mb-6">
28-
Gather valuable insights by automatically displaying a survey to users in this feature flag
29-
</LemonBanner>
30-
<div className="border rounded p-6 bg-bg-light">
31-
<QuickSurveyForm
32-
context={{
33-
type: QuickSurveyType.FEATURE_FLAG,
34-
flag: featureFlag,
35-
}}
36-
/>
37-
</div>
38-
</div>
39-
</div>
40-
)
41-
}
42-
43-
if (surveysForFlag.length === 1) {
44-
return (
45-
<BindLogic logic={surveyLogic} props={{ id: surveysForFlag[0].id }}>
46-
<div className="">
47-
<LemonBanner type="info" className="mb-6">
48-
Showing results for survey "{surveysForFlag[0].name}".{' '}
49-
<Link to={urls.survey(surveysForFlag[0].id)}>
50-
Manage in surveys <IconArrowRight />
51-
</Link>
52-
</LemonBanner>
53-
<SurveyResult />
54-
</div>
55-
</BindLogic>
56-
)
57-
}
12+
const surveys = featureFlag.surveys || []
5813

5914
return (
60-
<div className="space-y-6">
61-
<LemonBanner type="info" className="">
62-
Showing only surveys associated with this feature flag.{' '}
63-
<Link to={urls.surveys(SurveysTabs.Active)}>
64-
See all surveys <IconArrowRight />
65-
</Link>
66-
</LemonBanner>
67-
68-
<LemonTable
69-
dataSource={surveysForFlag}
70-
defaultSorting={{
71-
columnKey: 'created_at',
72-
order: -1,
73-
}}
74-
rowKey="name"
75-
nouns={['survey', 'surveys']}
76-
data-attr="surveys-table"
77-
columns={[
78-
{
79-
dataIndex: 'name',
80-
title: 'Name',
81-
render: function RenderName(_, survey) {
82-
return <LemonTableLink to={urls.survey(survey.id)} title={stringWithWBR(survey.name, 17)} />
83-
},
84-
},
85-
{
86-
title: 'Responses',
87-
dataIndex: 'id',
88-
render: function RenderResponses(_, survey) {
89-
return (
90-
<>
91-
{surveysResponsesCountLoading ? (
92-
<Spinner />
93-
) : (
94-
<div>{surveysResponsesCount[survey.id] ?? 0}</div>
95-
)}
96-
</>
97-
)
98-
},
99-
sorter: (surveyA, surveyB) => {
100-
const countA = surveysResponsesCount[surveyA.id] ?? 0
101-
const countB = surveysResponsesCount[surveyB.id] ?? 0
102-
return countA - countB
103-
},
104-
},
105-
createdAtColumn<Survey>() as LemonTableColumn<Survey, keyof Survey | undefined>,
106-
{
107-
title: 'Status',
108-
width: 100,
109-
render: function Render(_: any, survey: Survey) {
110-
return <SurveyStatusTag survey={survey} />
111-
},
112-
},
113-
]}
114-
/>
115-
</div>
15+
<FeedbackTabContent
16+
surveys={surveys}
17+
context={{
18+
type: QuickSurveyType.FEATURE_FLAG,
19+
flag: featureFlag,
20+
}}
21+
emptyStateBannerMessage="Gather valuable insights by automatically displaying a survey to users in this feature flag"
22+
multipleSurveysBannerMessage={
23+
<>
24+
Showing only surveys associated with this feature flag.{' '}
25+
<Link to={urls.surveys(SurveysTabs.Active)}>
26+
See all surveys <IconArrowRight />
27+
</Link>
28+
</>
29+
}
30+
/>
11631
)
11732
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { BindLogic, useValues } from 'kea'
2+
3+
import { IconArrowRight } from '@posthog/icons'
4+
import { LemonBanner, LemonTable, LemonTableColumn, Link, Spinner } from '@posthog/lemon-ui'
5+
6+
import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink'
7+
import { createdAtColumn } from 'lib/lemon-ui/LemonTable/columnUtils'
8+
import stringWithWBR from 'lib/utils/stringWithWBR'
9+
import { SurveyResult } from 'scenes/surveys/SurveyView'
10+
import { SurveyStatusTag } from 'scenes/surveys/components/SurveyStatusTag'
11+
import { QuickSurveyForm } from 'scenes/surveys/quick-create/QuickSurveyModal'
12+
import { QuickSurveyContext } from 'scenes/surveys/quick-create/types'
13+
import { surveyLogic } from 'scenes/surveys/surveyLogic'
14+
import { surveysLogic } from 'scenes/surveys/surveysLogic'
15+
import { urls } from 'scenes/urls'
16+
17+
import { Survey } from '~/types'
18+
19+
export interface FeedbackTabContentProps {
20+
surveys: Survey[]
21+
context: QuickSurveyContext
22+
emptyStateBannerMessage: string
23+
multipleSurveysBannerMessage: React.ReactNode
24+
}
25+
26+
export function FeedbackTabContent({
27+
surveys,
28+
context,
29+
emptyStateBannerMessage,
30+
multipleSurveysBannerMessage,
31+
}: FeedbackTabContentProps): JSX.Element {
32+
const { surveysResponsesCountLoading, surveysResponsesCount } = useValues(surveysLogic)
33+
34+
if (surveys.length === 0) {
35+
return (
36+
<div className="flex flex-col items-center pt-5">
37+
<div className="w-full max-w-5xl">
38+
<LemonBanner type="info" className="mb-6">
39+
{emptyStateBannerMessage}
40+
</LemonBanner>
41+
<div className="border rounded p-6 bg-bg-light">
42+
<QuickSurveyForm context={context} />
43+
</div>
44+
</div>
45+
</div>
46+
)
47+
}
48+
49+
if (surveys.length === 1) {
50+
const survey = surveys[0]
51+
return (
52+
<BindLogic logic={surveyLogic} props={{ id: survey.id }}>
53+
<div>
54+
<LemonBanner type="info" className="mb-6">
55+
Showing results for survey "{survey.name}".{' '}
56+
<Link to={urls.survey(survey.id)}>
57+
Manage in surveys <IconArrowRight />
58+
</Link>
59+
</LemonBanner>
60+
<SurveyResult />
61+
</div>
62+
</BindLogic>
63+
)
64+
}
65+
66+
return (
67+
<div className="space-y-6">
68+
<LemonBanner type="info">{multipleSurveysBannerMessage}</LemonBanner>
69+
70+
<LemonTable
71+
dataSource={surveys}
72+
defaultSorting={{
73+
columnKey: 'created_at',
74+
order: -1,
75+
}}
76+
rowKey="name"
77+
nouns={['survey', 'surveys']}
78+
data-attr="surveys-table"
79+
columns={[
80+
{
81+
dataIndex: 'name',
82+
title: 'Name',
83+
render: function RenderName(_, survey) {
84+
return <LemonTableLink to={urls.survey(survey.id)} title={stringWithWBR(survey.name, 17)} />
85+
},
86+
},
87+
{
88+
title: 'Responses',
89+
dataIndex: 'id',
90+
render: function RenderResponses(_, survey) {
91+
return (
92+
<>
93+
{surveysResponsesCountLoading ? (
94+
<Spinner />
95+
) : (
96+
<div>{surveysResponsesCount[survey.id] ?? 0}</div>
97+
)}
98+
</>
99+
)
100+
},
101+
sorter: (surveyA, surveyB) => {
102+
const countA = surveysResponsesCount[surveyA.id] ?? 0
103+
const countB = surveysResponsesCount[surveyB.id] ?? 0
104+
return countA - countB
105+
},
106+
},
107+
createdAtColumn<Survey>() as LemonTableColumn<Survey, keyof Survey | undefined>,
108+
{
109+
title: 'Status',
110+
width: 100,
111+
render: function Render(_: any, survey: Survey) {
112+
return <SurveyStatusTag survey={survey} />
113+
},
114+
},
115+
]}
116+
/>
117+
</div>
118+
)
119+
}

0 commit comments

Comments
 (0)