Skip to content

Commit

Permalink
feat(surveys): Implement Open Text visualization (#18016)
Browse files Browse the repository at this point in the history
  • Loading branch information
jurajmajerik authored Oct 17, 2023
1 parent 7bf256d commit 1ef8814
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 3 deletions.
12 changes: 10 additions & 2 deletions frontend/src/scenes/persons/PersonDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface PersonDisplayProps {
noLink?: boolean
noEllipsis?: boolean
noPopover?: boolean
isCentered?: boolean
}

export function PersonIcon({
Expand All @@ -37,13 +38,20 @@ export function PersonIcon({
return <ProfilePicture {...props} name={display} email={email} />
}

export function PersonDisplay({ person, withIcon, noEllipsis, noPopover, noLink }: PersonDisplayProps): JSX.Element {
export function PersonDisplay({
person,
withIcon,
noEllipsis,
noPopover,
noLink,
isCentered,
}: PersonDisplayProps): JSX.Element {
const href = asLink(person)
const display = asDisplay(person)
const [visible, setVisible] = useState(false)

let content = (
<span className="flex items-center">
<span className={clsx('flex', 'items-center', isCentered && 'justify-center')}>
{withIcon && <PersonIcon person={person} size={typeof withIcon === 'string' ? withIcon : 'md'} />}
<span className={clsx('ph-no-capture', !noEllipsis && 'truncate')}>{display}</span>
</span>
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/scenes/surveys/SurveyView.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.masonry-container {
column-count: 4;
column-gap: 10px;
}

.masonry-item {
margin: 0;
display: grid;
grid-template-rows: 1fr auto;
margin-bottom: 10px;
break-inside: avoid;
box-sizing: border-box; /* add this */
-moz-box-sizing: border-box; /* Firefox */
-webkit-box-sizing: border-box; /* Older Webkit browsers */
}

.masonry-item-text {
max-height: 305px;
overflow: scroll;
}

.masonry-item-link {
background-color: #fafaf9;
}
13 changes: 13 additions & 0 deletions frontend/src/scenes/surveys/SurveyView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ import {
RatingQuestionBarChart,
SingleChoiceQuestionPieChart,
MultipleChoiceQuestionBarChart,
OpenTextViz,
} from './surveyViewViz'
import './SurveyView.scss'

export function SurveyView({ id }: { id: string }): JSX.Element {
const { survey, surveyLoading } = useValues(surveyLogic)
Expand Down Expand Up @@ -268,6 +270,8 @@ export function SurveyResult({ disableEventsTable }: { disableEventsTable?: bool
surveySingleChoiceResultsReady,
surveyMultipleChoiceResults,
surveyMultipleChoiceResultsReady,
surveyOpenTextResults,
surveyOpenTextResultsReady,
} = useValues(surveyLogic)
const { setCurrentQuestionIndexAndType } = useActions(surveyLogic)
const { featureFlags } = useValues(featureFlagLogic)
Expand Down Expand Up @@ -305,6 +309,15 @@ export function SurveyResult({ disableEventsTable }: { disableEventsTable?: bool
questionIndex={i}
/>
)
} else if (question.type === SurveyQuestionType.Open) {
return (
<OpenTextViz
key={`survey-q-${i}`}
surveyOpenTextResults={surveyOpenTextResults}
surveyOpenTextResultsReady={surveyOpenTextResultsReady}
questionIndex={i}
/>
)
}
})}
</>
Expand Down
56 changes: 56 additions & 0 deletions frontend/src/scenes/surveys/surveyLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ export interface SurveyMultipleChoiceResults {
}
}

export interface SurveyOpenTextResults {
[key: number]: {
events: { properties: Record<string, any>; distinct_id: string }[]
}
}

export interface QuestionResultsReady {
[key: string]: boolean
}
Expand Down Expand Up @@ -331,6 +337,45 @@ export const surveyLogic = kea<surveyLogicType>([
return { ...values.surveyRatingResults, [questionIndex]: { labels, data } }
},
},
surveyOpenTextResults: {
loadSurveyOpenTextResults: async ({
questionIndex,
}: {
questionIndex: number
}): Promise<SurveyOpenTextResults> => {
const { survey } = values

const question = values.survey.questions[questionIndex]
if (question.type !== SurveyQuestionType.Open) {
throw new Error(`Survey question type must be ${SurveyQuestionType.Open}`)
}

const startDate = dayjs((survey as Survey).created_at).format('YYYY-MM-DD')
const endDate = survey.end_date
? dayjs(survey.end_date).add(1, 'day').format('YYYY-MM-DD')
: dayjs().add(1, 'day').format('YYYY-MM-DD')

const query: HogQLQuery = {
kind: NodeKind.HogQLQuery,
query: `
SELECT properties, distinct_id
FROM events
WHERE event == 'survey sent'
AND properties.$survey_id == '${survey.id}'
AND timestamp >= '${startDate}'
AND timestamp <= '${endDate}'
LIMIT 20
`,
}

const responseJSON = await api.query(query)
const { results } = responseJSON

const events = results?.map((r) => ({ properties: JSON.parse(r[0]), distinct_id: r[1] }))

return { ...values.surveyRatingResults, [questionIndex]: { events } }
},
},
})),
listeners(({ actions }) => ({
createSurveySuccess: ({ survey }) => {
Expand Down Expand Up @@ -465,6 +510,17 @@ export const surveyLogic = kea<surveyLogicType>([
},
},
],
surveyOpenTextResultsReady: [
{},
{
loadSurveyOpenTextResultsSuccess: (state, { payload }) => {
if (!payload || !payload.hasOwnProperty('questionIndex')) {
return { ...state }
}
return { ...state, [payload.questionIndex]: true }
},
},
],
writingHTMLDescription: [
false,
{
Expand Down
56 changes: 55 additions & 1 deletion frontend/src/scenes/surveys/surveyViewViz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import {
QuestionResultsReady,
SurveySingleChoiceResults,
SurveyMultipleChoiceResults,
SurveyOpenTextResults,
SurveyUserStats,
} from './surveyLogic'
import { useActions, useValues, BindLogic } from 'kea'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { GraphType } from '~/types'
import { LineGraph } from 'scenes/insights/views/LineGraph/LineGraph'
import { PieChart } from 'scenes/insights/views/LineGraph/PieChart'
import { PersonDisplay } from 'scenes/persons/PersonDisplay'
import { insightLogic } from 'scenes/insights/insightLogic'
import { InsightLogicProps, SurveyQuestionType } from '~/types'
import { useEffect } from 'react'
Expand Down Expand Up @@ -60,7 +62,7 @@ export function UsersStackedBar({ surveyUserStats }: { surveyUserStats: SurveyUs
return (
<>
{total > 0 && (
<div className="mb-6">
<div className="mb-8">
<div className="w-full mx-auto h-10 mb-4">
{[
{
Expand Down Expand Up @@ -425,3 +427,55 @@ export function MultipleChoiceQuestionBarChart({
</div>
)
}

export function OpenTextViz({
questionIndex,
surveyOpenTextResults,
surveyOpenTextResultsReady,
}: {
questionIndex: number
surveyOpenTextResults: SurveyOpenTextResults
surveyOpenTextResultsReady: QuestionResultsReady
}): JSX.Element {
const { loadSurveyOpenTextResults } = useActions(surveyLogic)
const { survey } = useValues(surveyLogic)
const surveyResponseField = questionIndex === 0 ? '$survey_response' : `$survey_response_${questionIndex}`

const question = survey.questions[questionIndex]
if (question.type !== SurveyQuestionType.Open) {
throw new Error(`Question type must be ${SurveyQuestionType.Open}`)
}

useEffect(() => {
loadSurveyOpenTextResults({ questionIndex })
}, [questionIndex])

return (
<div className="mb-4">
{!surveyOpenTextResultsReady[questionIndex] ? (
<LemonTable dataSource={[]} columns={[]} loading={true} />
) : !surveyOpenTextResults[questionIndex].events.length ? (
<></>
) : (
<>
<div className="font-semibold text-muted-alt">Open text</div>
<div className="text-xl font-bold mb-4">
{question.question}<span className="">Latest responses</span>
</div>
<div className="mt-4 mb-8 masonry-container">
{surveyOpenTextResults[questionIndex].events.map((event, i) => (
<div key={`open-text-${questionIndex}-${i}`} className="masonry-item border rounded">
<div className="masonry-item-text italic font-semibold px-5 py-4">
{event.properties[surveyResponseField]}
</div>
<div className="masonry-item-link items-center px-5 py-4 border-t rounded-b truncate w-full">
<PersonDisplay person={event} withIcon={true} noEllipsis={false} isCentered />
</div>
</div>
))}
</div>
</>
)}
</div>
)
}

0 comments on commit 1ef8814

Please sign in to comment.