From ec256cf9ce831c2edf70e809da22d8d00c36e5b1 Mon Sep 17 00:00:00 2001 From: Neil Kakkar Date: Fri, 6 Oct 2023 14:00:06 +0100 Subject: [PATCH 1/3] feat(surveys): Add question html support --- frontend/src/scenes/surveys/Survey.tsx | 86 +++++++++++++++++-- .../src/scenes/surveys/SurveyAppearance.tsx | 8 +- frontend/src/scenes/surveys/surveyLogic.tsx | 7 ++ package.json | 2 + pnpm-lock.yaml | 22 ++++- 5 files changed, 117 insertions(+), 8 deletions(-) diff --git a/frontend/src/scenes/surveys/Survey.tsx b/frontend/src/scenes/surveys/Survey.tsx index 7e107fb46c074..8f9e1978ed350 100644 --- a/frontend/src/scenes/surveys/Survey.tsx +++ b/frontend/src/scenes/surveys/Survey.tsx @@ -5,12 +5,14 @@ import { Form, Group } from 'kea-forms' import { PageHeader } from 'lib/components/PageHeader' import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton' import { + LemonBanner, LemonButton, LemonCheckbox, LemonCollapse, LemonDivider, LemonInput, LemonSelect, + LemonTabs, LemonTextArea, Link, } from '@posthog/lemon-ui' @@ -36,6 +38,7 @@ import { featureFlagLogic } from 'scenes/feature-flags/featureFlagLogic' import { defaultSurveyFieldValues, defaultSurveyAppearance, NewSurvey, SurveyUrlMatchTypeLabels } from './constants' import { FEATURE_FLAGS } from 'lib/constants' import { FeatureFlagReleaseConditions } from 'scenes/feature-flags/FeatureFlagReleaseConditions' +import { CodeEditor } from 'lib/components/CodeEditors' export const scene: SceneExport = { component: SurveyComponent, @@ -62,9 +65,16 @@ export function SurveyComponent({ id }: { id?: string } = {}): JSX.Element { } export function SurveyForm({ id }: { id: string }): JSX.Element { - const { survey, surveyLoading, isEditingSurvey, hasTargetingFlag, urlMatchTypeValidationError } = - useValues(surveyLogic) - const { loadSurvey, editingSurvey, setSurveyValue, setDefaultForQuestionType } = useActions(surveyLogic) + const { + survey, + surveyLoading, + isEditingSurvey, + hasTargetingFlag, + urlMatchTypeValidationError, + writingHTMLDescription, + } = useValues(surveyLogic) + const { loadSurvey, editingSurvey, setSurveyValue, setDefaultForQuestionType, setWritingHTMLDescription } = + useActions(surveyLogic) const { featureFlags } = useValues(enabledFeaturesLogic) return ( @@ -147,7 +157,7 @@ export function SurveyForm({ id }: { id: string }): JSX.Element { ), content: ( - <> +
{ @@ -202,7 +212,71 @@ export function SurveyForm({ id }: { id: string }): JSX.Element { )} - + {({ value, onChange }) => ( + <> + + setWritingHTMLDescription(key === 'html') + } + tabs={[ + { + key: 'text', + label: ( + Text + ), + content: ( + onChange(v)} + /> + ), + }, + { + key: 'html', + label: ( + HTML + ), + content: ( +
+ + onChange(v ?? '') + } + height={150} + options={{ + minimap: { enabled: false }, + wordWrap: 'on', + scrollBeyondLastLine: false, + automaticLayout: true, + fixedOverflowWidgets: true, + lineNumbers: 'off', + glyphMargin: false, + folding: false, + }} + /> +
+ ), + }, + ]} + /> + {question.description && + question.description + ?.toLowerCase() + .includes(' + Scripts won't run in the survey popup. Use + the API question mode to run your own + scripts in surveys. + + )} + + )}
{question.type === SurveyQuestionType.Rating && (
@@ -317,7 +391,7 @@ export function SurveyForm({ id }: { id: string }): JSX.Element {
)} - +
), }, ]} diff --git a/frontend/src/scenes/surveys/SurveyAppearance.tsx b/frontend/src/scenes/surveys/SurveyAppearance.tsx index b9199f0906eb5..4ab57e95ed773 100644 --- a/frontend/src/scenes/surveys/SurveyAppearance.tsx +++ b/frontend/src/scenes/surveys/SurveyAppearance.tsx @@ -24,6 +24,7 @@ import { useValues } from 'kea' import { useEffect, useRef, useState } from 'react' import { FEATURE_FLAGS } from 'lib/constants' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' +import { sanitize } from 'dompurify' interface SurveyAppearanceProps { type: SurveyQuestionType @@ -270,7 +271,12 @@ function BaseAppearance({
{question}
- {description &&
{description}
} + {/* Using dangerouslySetInnerHTML is safe here, because it's taking the user's input and showing it to the same user. + They can try passing in arbitrary scripts, but it would show up only for them, so it's like trying to XSS yourself, where + you already have all the data. Furthermore, sanitization should catch all obvious attempts */} + {description && ( +
+ )} {type === SurveyQuestionType.Open && (