From e3171ffd7afccef18112003dc05f4d61a6863258 Mon Sep 17 00:00:00 2001 From: Juraj Majerik Date: Tue, 10 Oct 2023 14:56:20 +0200 Subject: [PATCH 1/3] add rating question results bar chart --- .../insights/views/LineGraph/LineGraph.tsx | 4 +- frontend/src/scenes/surveys/SurveyView.tsx | 53 ++++++--- frontend/src/scenes/surveys/surveyLogic.tsx | 49 +++++++++ frontend/src/scenes/surveys/surveyViewViz.tsx | 104 +++++++++++++++--- 4 files changed, 177 insertions(+), 33 deletions(-) diff --git a/frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx b/frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx index 34f683d268e75..2c01232be06d8 100644 --- a/frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx +++ b/frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx @@ -215,6 +215,7 @@ export interface LineGraphProps { showValueOnSeries?: boolean | null showPercentStackView?: boolean | null supportsPercentStackView?: boolean + hideAnnotations?: boolean } export const LineGraph = (props: LineGraphProps): JSX.Element => { @@ -245,6 +246,7 @@ export function LineGraph_({ showValueOnSeries, showPercentStackView, supportsPercentStackView, + hideAnnotations, }: LineGraphProps): JSX.Element { let datasets = _datasets @@ -272,7 +274,7 @@ export function LineGraph_({ const isBar = [GraphType.Bar, GraphType.HorizontalBar, GraphType.Histogram].includes(type) const isBackgroundBasedGraphType = [GraphType.Bar, GraphType.HorizontalBar].includes(type) const isPercentStackView = !!supportsPercentStackView && !!showPercentStackView - const showAnnotations = isTrends && !isHorizontal + const showAnnotations = isTrends && !isHorizontal && !hideAnnotations const shouldAutoResize = isHorizontal && !inCardView // Remove tooltip element on unmount diff --git a/frontend/src/scenes/surveys/SurveyView.tsx b/frontend/src/scenes/surveys/SurveyView.tsx index af4208913bb97..50df0d98113a2 100644 --- a/frontend/src/scenes/surveys/SurveyView.tsx +++ b/frontend/src/scenes/surveys/SurveyView.tsx @@ -30,7 +30,7 @@ import { dayjs } from 'lib/dayjs' import { defaultSurveyAppearance, SURVEY_EVENT_NAME } from './constants' import { FEATURE_FLAGS } from 'lib/constants' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' -import { Summary } from './surveyViewViz' +import { RatingQuestionBarChart, Summary } from './surveyViewViz' export function SurveyView({ id }: { id: string }): JSX.Element { const { survey, surveyLoading, surveyPlugin, showSurveyAppWarning } = useValues(surveyLogic) @@ -264,6 +264,8 @@ export function SurveyResult({ disableEventsTable }: { disableEventsTable?: bool currentQuestionIndexAndType, surveyUserStats, surveyUserStatsLoading, + surveyRatingResults, + surveyRatingResultsReady, } = useValues(surveyLogic) const { setCurrentQuestionIndexAndType } = useActions(surveyLogic) const { featureFlags } = useValues(featureFlagLogic) @@ -271,9 +273,24 @@ export function SurveyResult({ disableEventsTable }: { disableEventsTable?: bool return ( <> {featureFlags[FEATURE_FLAGS.SURVEYS_RESULTS_VISUALIZATIONS] && ( - + <> + + {survey.questions.map((question, i) => { + if (question.type === 'rating') { + return ( + + ) + } + })} + )} - {surveyMetricsQueries && ( + {surveyMetricsQueries && !featureFlags[FEATURE_FLAGS.SURVEYS_RESULTS_VISUALIZATIONS] && (
@@ -283,7 +300,7 @@ export function SurveyResult({ disableEventsTable }: { disableEventsTable?: bool
)} - {survey.questions.length > 1 && ( + {survey.questions.length > 1 && !featureFlags[FEATURE_FLAGS.SURVEYS_RESULTS_VISUALIZATIONS] && (
)} - {currentQuestionIndexAndType.type === SurveyQuestionType.Rating && ( -
- - {featureFlags[FEATURE_FLAGS.SURVEY_NPS_RESULTS] && - (survey.questions[currentQuestionIndexAndType.idx] as RatingSurveyQuestion).scale === 10 && ( - <> - -

NPS Score

- - - )} -
- )} + {currentQuestionIndexAndType.type === SurveyQuestionType.Rating && + !featureFlags[FEATURE_FLAGS.SURVEYS_RESULTS_VISUALIZATIONS] && ( +
+ + {featureFlags[FEATURE_FLAGS.SURVEY_NPS_RESULTS] && + (survey.questions[currentQuestionIndexAndType.idx] as RatingSurveyQuestion).scale === + 10 && ( + <> + +

NPS Score

+ + + )} +
+ )} {(currentQuestionIndexAndType.type === SurveyQuestionType.SingleChoice || currentQuestionIndexAndType.type === SurveyQuestionType.MultipleChoice) && (
diff --git a/frontend/src/scenes/surveys/surveyLogic.tsx b/frontend/src/scenes/surveys/surveyLogic.tsx index 7dc20e94e1854..5b112b9f3e7e6 100644 --- a/frontend/src/scenes/surveys/surveyLogic.tsx +++ b/frontend/src/scenes/surveys/surveyLogic.tsx @@ -16,6 +16,7 @@ import { SurveyQuestionType, SurveyType, SurveyUrlMatchType, + RatingSurveyQuestion, } from '~/types' import type { surveyLogicType } from './surveyLogicType' import { DataTableNode, InsightVizNode, HogQLQuery, NodeKind } from '~/queries/schema' @@ -170,6 +171,46 @@ export const surveyLogic = kea([ } }, }, + surveyRatingResults: { + loadSurveyRatingResults: async ({ + questionIndex, + question, + }: { + questionIndex: number + question: RatingSurveyQuestion + }): Promise<{ [key: string]: number[] }> => { + const { survey } = values + 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 surveyResponseField = + questionIndex === 0 ? '$survey_response' : `$survey_response_${questionIndex}` + + const query: HogQLQuery = { + kind: NodeKind.HogQLQuery, + query: ` + SELECT properties.${surveyResponseField} AS survey_response, COUNT(survey_response) + FROM events + WHERE event = 'survey sent' + AND properties.$survey_id = '${props.id}' + AND timestamp >= '${startDate}' + AND timestamp <= '${endDate}' + GROUP BY survey_response + `, + } + const responseJSON = await api.query(query) + const { results } = responseJSON + + const resultArr = new Array(question.scale).fill(0) + results?.forEach(([value, count]) => { + resultArr[value - 1] = count + }) + + return { ...values.surveyRatingResults, [`question_${questionIndex}`]: resultArr } + }, + }, })), listeners(({ actions }) => ({ createSurveySuccess: ({ survey }) => { @@ -259,6 +300,14 @@ export const surveyLogic = kea([ setCurrentQuestionIndexAndType: (_, { idx, type }) => ({ idx, type }), }, ], + surveyRatingResultsReady: [ + {}, + { + loadSurveyRatingResultsSuccess: (state, { payload }) => { + return { ...state, [`question_${payload?.questionIndex}`]: true } + }, + }, + ], }), selectors({ isSurveyRunning: [ diff --git a/frontend/src/scenes/surveys/surveyViewViz.tsx b/frontend/src/scenes/surveys/surveyViewViz.tsx index 4969dff248da4..41084d904933d 100644 --- a/frontend/src/scenes/surveys/surveyViewViz.tsx +++ b/frontend/src/scenes/surveys/surveyViewViz.tsx @@ -1,6 +1,16 @@ import { LemonTable } from '@posthog/lemon-ui' -import { SurveyUserStats } from './surveyLogic' +import { surveyLogic, SurveyUserStats } from './surveyLogic' +import { useActions, BindLogic } from 'kea' import { Tooltip } from 'lib/lemon-ui/Tooltip' +import { GraphType } from '~/types' +import { LineGraph } from 'scenes/insights/views/LineGraph/LineGraph' +import { insightLogic } from 'scenes/insights/insightLogic' +import { InsightLogicProps, RatingSurveyQuestion } from '~/types' +import { useEffect } from 'react' + +const insightProps: InsightLogicProps = { + dashboardItemId: `new-survey`, +} const formatCount = (count: number, total: number): string => { if ((count / total) * 100 < 3) { @@ -48,8 +58,8 @@ export function UsersStackedBar({ surveyUserStats }: { surveyUserStats: SurveyUs { count: seen, label: 'Viewed', - classes: `bg-primary rounded-l ${dismissed === 0 && sent === 0 ? 'rounded-r' : ''}`, - style: { width: `${seenPercentage}%` }, + classes: `rounded-l ${dismissed === 0 && sent === 0 ? 'rounded-r' : ''}`, + style: { backgroundColor: '#1D4AFF', width: `${seenPercentage}%` }, }, { count: dismissed, @@ -92,12 +102,12 @@ export function UsersStackedBar({ surveyUserStats }: { surveyUserStats: SurveyUs
{[ - { count: seen, label: 'Viewed', color: 'bg-primary' }, - { count: dismissed, label: 'Dismissed', color: 'bg-warning' }, - { count: sent, label: 'Submitted', color: 'bg-success' }, - ].map(({ count, label, color }) => ( + { count: seen, label: 'Viewed', style: { backgroundColor: '#1D4AFF' } }, + { count: dismissed, label: 'Dismissed', style: { backgroundColor: '#E3A506' } }, + { count: sent, label: 'Submitted', style: { backgroundColor: '#529B08' } }, + ].map(({ count, label, style }) => (
-
+
{`${label} (${( (count / total) * 100 @@ -119,20 +129,84 @@ export function Summary({ surveyUserStats: SurveyUserStats surveyUserStatsLoading: boolean }): JSX.Element { - if (!surveyUserStats) { - return <> - } - return ( -
+
{surveyUserStatsLoading ? ( ) : ( <> - - + {!surveyUserStats ? null : ( + <> + + + + )} )}
) } + +export function RatingQuestionBarChart({ + questionIndex, + question, + surveyRatingResults, + surveyRatingResultsReady, +}: { + questionIndex: number + question: RatingSurveyQuestion + surveyRatingResults: any + surveyRatingResultsReady: any +}): JSX.Element { + const { loadSurveyRatingResults } = useActions(surveyLogic) + + useEffect(() => { + loadSurveyRatingResults({ questionIndex, question }) + }, [question]) + + return ( +
+ {!surveyRatingResultsReady[`question_${questionIndex}`] ? ( + + ) : ( +
+
{`1-${question.scale} rating`}
+
{question.question}
+
+ + (i + 1).toString()).map( + (n) => n + )} + /> + +
+
+
{question.lowerBoundLabel}
+
{question.upperBoundLabel}
+
+
+ )} +
+ ) +} From 313c03e43d7a7f6d91b698c0e866ca9acc0a48dd Mon Sep 17 00:00:00 2001 From: Juraj Majerik Date: Wed, 11 Oct 2023 09:48:15 +0200 Subject: [PATCH 2/3] add bar chart border --- frontend/src/scenes/surveys/surveyViewViz.tsx | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/frontend/src/scenes/surveys/surveyViewViz.tsx b/frontend/src/scenes/surveys/surveyViewViz.tsx index 41084d904933d..020287b5e5502 100644 --- a/frontend/src/scenes/surveys/surveyViewViz.tsx +++ b/frontend/src/scenes/surveys/surveyViewViz.tsx @@ -171,37 +171,39 @@ export function RatingQuestionBarChart({ ) : (
{`1-${question.scale} rating`}
-
{question.question}
-
- - (i + 1).toString()).map( - (n) => n - )} - /> - +
{question.question}
+
+
+ + (i + 1).toString()).map( + (n) => n + )} + /> + +
-
+
{question.lowerBoundLabel}
{question.upperBoundLabel}
From da4ce7723ff5cbb87640c99d9466eb0943af6fa5 Mon Sep 17 00:00:00 2001 From: Juraj Majerik Date: Wed, 11 Oct 2023 09:54:04 +0200 Subject: [PATCH 3/3] add survey rating results type --- frontend/src/scenes/surveys/surveyLogic.tsx | 8 ++++++++ frontend/src/scenes/surveys/surveyViewViz.tsx | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/frontend/src/scenes/surveys/surveyLogic.tsx b/frontend/src/scenes/surveys/surveyLogic.tsx index e02599cac98eb..c56ce8ab4cb9f 100644 --- a/frontend/src/scenes/surveys/surveyLogic.tsx +++ b/frontend/src/scenes/surveys/surveyLogic.tsx @@ -52,6 +52,14 @@ export interface SurveyUserStats { sent: number } +export interface SurveyRatingResults { + [key: string]: number[] +} + +export interface SurveyRatingResultsReady { + [key: string]: boolean +} + export const surveyLogic = kea([ props({} as SurveyLogicProps), key(({ id }) => id), diff --git a/frontend/src/scenes/surveys/surveyViewViz.tsx b/frontend/src/scenes/surveys/surveyViewViz.tsx index 020287b5e5502..460536bb35b6d 100644 --- a/frontend/src/scenes/surveys/surveyViewViz.tsx +++ b/frontend/src/scenes/surveys/surveyViewViz.tsx @@ -1,5 +1,5 @@ import { LemonTable } from '@posthog/lemon-ui' -import { surveyLogic, SurveyUserStats } from './surveyLogic' +import { surveyLogic, SurveyRatingResults, SurveyRatingResultsReady, SurveyUserStats } from './surveyLogic' import { useActions, BindLogic } from 'kea' import { Tooltip } from 'lib/lemon-ui/Tooltip' import { GraphType } from '~/types' @@ -155,8 +155,8 @@ export function RatingQuestionBarChart({ }: { questionIndex: number question: RatingSurveyQuestion - surveyRatingResults: any - surveyRatingResultsReady: any + surveyRatingResults: SurveyRatingResults + surveyRatingResultsReady: SurveyRatingResultsReady }): JSX.Element { const { loadSurveyRatingResults } = useActions(surveyLogic)