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 21a016c7eb3a3..c56ce8ab4cb9f 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' @@ -51,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), @@ -172,6 +181,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 }) => { @@ -261,6 +310,14 @@ export const surveyLogic = kea([ setCurrentQuestionIndexAndType: (_, { idx, type }) => ({ idx, type }), }, ], + surveyRatingResultsReady: [ + {}, + { + loadSurveyRatingResultsSuccess: (state, { payload }) => { + return { ...state, [`question_${payload?.questionIndex}`]: true } + }, + }, + ], writingHTMLDescription: [ false, { diff --git a/frontend/src/scenes/surveys/surveyViewViz.tsx b/frontend/src/scenes/surveys/surveyViewViz.tsx index 4969dff248da4..460536bb35b6d 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, SurveyRatingResults, SurveyRatingResultsReady, 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,86 @@ 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: SurveyRatingResults + surveyRatingResultsReady: SurveyRatingResultsReady +}): 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}
+
+
+ )} +
+ ) +}