diff --git a/cypress/e2e/surveys.cy.ts b/cypress/e2e/surveys.cy.ts index e3efcd4c48a0f..fd3a60ca6bc1a 100644 --- a/cypress/e2e/surveys.cy.ts +++ b/cypress/e2e/surveys.cy.ts @@ -221,4 +221,64 @@ describe('Surveys', () => { cy.get('[data-attr=surveys-table]').should('contain', name) cy.get(`[data-row-key="${name}"]`).contains(name).click() }) + + it('duplicates a survey', () => { + // create survey + cy.get('[data-attr=new-survey]').click() + cy.get('[data-attr=new-blank-survey]').click() + cy.get('[data-attr=survey-name]').focus().type(name).should('have.value', name) + + // Add user targetting criteria + cy.get('.LemonCollapsePanel').contains('Targeting').click() + cy.contains('All users').click() + cy.get('.Popover__content').contains('Users who match').click() + cy.contains('Add user targeting').click() + cy.get('[data-attr="property-select-toggle-0"]').click() + cy.get('[data-attr="prop-filter-person_properties-0"]').click() + cy.get('[data-attr=prop-val] .LemonInput').click({ force: true }) + cy.get('[data-attr=prop-val-0]').click({ force: true }) + cy.get('[data-attr="rollout-percentage"]').type('100') + + cy.get('[data-attr=save-survey]').first().click() + + // Launch the survey first, the duplicated one should be in draft + cy.get('[data-attr="launch-survey"]').click() + + // try to duplicate survey + cy.get('[data-attr=more-button]').click() + cy.get('[data-attr=duplicate-survey]').click() + + // if the survey is duplicated, try to view it & verify a copy is created + cy.get('[data-attr=success-toast]').contains('duplicated').should('exist').siblings('button').click() + cy.get('[data-attr=top-bar-name]').contains(`${name} (copy)`).should('exist') + + // check if it launched in a draft state + cy.get('button[data-attr="launch-survey"]').should('have.text', 'Launch') + + // check if targetting criteria is copied + cy.contains('Release conditions summary').should('exist') + cy.get('.FeatureConditionCard').should('exist').should('contain.text', 'is_demo equals true') + cy.get('.FeatureConditionCard').should('contain.text', 'Rolled out to 100% of users in this set.') + + // delete the duplicated survey + cy.get('[data-attr=more-button]').click() + cy.get('[data-attr=delete-survey]').click() + + // Archive the original survey + cy.clickNavMenu('surveys') + cy.get('[data-attr=surveys-table]').find(`[data-row-key="${name}"]`).find('a').click() + cy.get('[data-attr=stop-survey]').click() + cy.get('[data-attr=more-button]').click() + cy.get('[data-attr=archive-survey]').click() + + // check if the duplicated survey is created with draft state + cy.get('[data-attr=more-button]').click() + cy.get('[data-attr=duplicate-survey]').click() + cy.clickNavMenu('surveys') + cy.get('[data-attr=surveys-table]') + .find(`[data-row-key="${name} (copy)"]`) + .find('[data-attr=status]') + .contains('DRAFT') + .should('exist') + }) }) diff --git a/frontend/src/lib/utils/eventUsageLogic.ts b/frontend/src/lib/utils/eventUsageLogic.ts index 78bd3fe7110e4..ec1f079597ad8 100644 --- a/frontend/src/lib/utils/eventUsageLogic.ts +++ b/frontend/src/lib/utils/eventUsageLogic.ts @@ -488,7 +488,7 @@ export const eventUsageLogic = kea([ reportSurveyViewed: (survey: Survey) => ({ survey, }), - reportSurveyCreated: (survey: Survey) => ({ survey }), + reportSurveyCreated: (survey: Survey, isDuplicate?: boolean) => ({ survey, isDuplicate }), reportSurveyEdited: (survey: Survey) => ({ survey }), reportSurveyLaunched: (survey: Survey) => ({ survey }), reportSurveyStopped: (survey: Survey) => ({ survey }), @@ -1156,13 +1156,14 @@ export const eventUsageLogic = kea([ language, }) }, - reportSurveyCreated: ({ survey }) => { + reportSurveyCreated: ({ survey, isDuplicate }) => { posthog.capture('survey created', { name: survey.name, id: survey.id, survey_type: survey.type, questions_length: survey.questions.length, question_types: survey.questions.map((question) => question.type), + is_duplicate: isDuplicate ?? false, }) }, reportSurveyLaunched: ({ survey }) => { diff --git a/frontend/src/scenes/surveys/SurveyView.tsx b/frontend/src/scenes/surveys/SurveyView.tsx index 95c2ca6df47cf..cb04b053d5a6d 100644 --- a/frontend/src/scenes/surveys/SurveyView.tsx +++ b/frontend/src/scenes/surveys/SurveyView.tsx @@ -34,8 +34,16 @@ import { export function SurveyView({ id }: { id: string }): JSX.Element { const { survey, surveyLoading, selectedQuestion, targetingFlagFilters } = useValues(surveyLogic) - const { editingSurvey, updateSurvey, launchSurvey, stopSurvey, archiveSurvey, resumeSurvey, setSelectedQuestion } = - useActions(surveyLogic) + const { + editingSurvey, + updateSurvey, + launchSurvey, + stopSurvey, + archiveSurvey, + resumeSurvey, + setSelectedQuestion, + duplicateSurvey, + } = useActions(surveyLogic) const { deleteSurvey } = useActions(surveysLogic) const [tabKey, setTabKey] = useState(survey.start_date ? 'results' : 'overview') @@ -43,6 +51,8 @@ export function SurveyView({ id }: { id: string }): JSX.Element { useEffect(() => { if (survey.start_date) { setTabKey('results') + } else { + setTabKey('overview') } }, [survey.start_date]) @@ -66,10 +76,21 @@ export function SurveyView({ id }: { id: string }): JSX.Element { > Edit + + Duplicate + {survey.end_date && !survey.archived && ( - archiveSurvey()} fullWidth> + archiveSurvey()} + fullWidth + > Archive )} @@ -101,7 +122,12 @@ export function SurveyView({ id }: { id: string }): JSX.Element { ) : ( !survey.archived && ( - stopSurvey()}> + stopSurvey()} + > Stop ) diff --git a/frontend/src/scenes/surveys/Surveys.tsx b/frontend/src/scenes/surveys/Surveys.tsx index cc90337a44947..362f0e3907e36 100644 --- a/frontend/src/scenes/surveys/Surveys.tsx +++ b/frontend/src/scenes/surveys/Surveys.tsx @@ -62,7 +62,7 @@ export function Surveys(): JSX.Element { const { user } = useValues(userLogic) - const [tab, setSurveyTab] = useState(SurveysTabs.Active) + const [tab, setSurveyTab] = useState(filters.archived ? SurveysTabs.Archived : SurveysTabs.Active) const shouldShowEmptyState = !surveysLoading && surveys.length === 0 return ( @@ -368,7 +368,7 @@ export function StatusTag({ survey }: { survey: Survey }): JSX.Element { } as Record const status = getSurveyStatus(survey) return ( - + {status.toUpperCase()} ) diff --git a/frontend/src/scenes/surveys/surveyLogic.tsx b/frontend/src/scenes/surveys/surveyLogic.tsx index 557f19289d976..cbe549fff5a6c 100644 --- a/frontend/src/scenes/surveys/surveyLogic.tsx +++ b/frontend/src/scenes/surveys/surveyLogic.tsx @@ -87,6 +87,19 @@ export interface QuestionResultsReady { const getResponseField = (i: number): string => (i === 0 ? '$survey_response' : `$survey_response_${i}`) +function duplicateExistingSurvey(survey: Survey | NewSurvey): Partial { + return { + ...survey, + id: NEW_SURVEY.id, + name: `${survey.name} (copy)`, + archived: false, + start_date: null, + end_date: null, + targeting_flag_filters: survey.targeting_flag?.filters ?? NEW_SURVEY.targeting_flag_filters, + linked_flag_id: survey.linked_flag?.id ?? NEW_SURVEY.linked_flag_id, + } +} + export const surveyLogic = kea([ props({} as SurveyLogicProps), key(({ id }) => id), @@ -171,6 +184,26 @@ export const surveyLogic = kea([ return await api.surveys.update(props.id, { end_date: null }) }, }, + duplicatedSurvey: { + duplicateSurvey: async () => { + const { survey } = values + const payload = duplicateExistingSurvey(survey) + const createdSurvey = await api.surveys.create(sanitizeQuestions(payload)) + + lemonToast.success('Survey duplicated.', { + toastId: `survey-duplicated-${createdSurvey.id}`, + button: { + label: 'View Survey', + action: () => { + router.actions.push(urls.survey(createdSurvey.id)) + }, + }, + }) + + actions.reportSurveyCreated(createdSurvey, true) + return survey + }, + }, surveyUserStats: { loadSurveyUserStats: async (): Promise => { const { survey } = values @@ -413,6 +446,9 @@ export const surveyLogic = kea([ actions.reportSurveyEdited(survey) actions.loadSurveys() }, + duplicateSurveySuccess: () => { + actions.loadSurveys() + }, launchSurveySuccess: ({ survey }) => { lemonToast.success(<>Survey {survey.name} launched) actions.loadSurveys()