diff --git a/components/defaultFunnelChart.js b/components/defaultFunnelChart.js new file mode 100644 index 0000000..916dc8a --- /dev/null +++ b/components/defaultFunnelChart.js @@ -0,0 +1,40 @@ +import React from "react" +import { ResponsiveBar } from "@nivo/bar" + +const xAxisKey = "label" + +function findKeysFromData(data) { + return data.reduce((keys, row) => { + Object.keys(row).forEach((key) => { + if (key !== xAxisKey && !keys.includes(key)) { + keys.push(key) + } + }) + return keys + }, []) +} + +export const DefaultFunnelChart = ({ data, dataTestid }) => { + const keys = findKeysFromData(data) + + return ( +
+ +
+ ) +} diff --git a/components/header.js b/components/header.js index c8621b1..79af7c0 100644 --- a/components/header.js +++ b/components/header.js @@ -6,6 +6,7 @@ const menu = [ { url: "/survey", label: "Résultats de sondage" }, { url: "/behaviours", label: "Comportements utilisateur" }, { url: "/pages", label: "Statistiques de visite" }, + { url: "/funnel", label: "Tunnel de conversion" }, ] function closeMenu() { diff --git a/cypress/e2e/funnel.cy.js b/cypress/e2e/funnel.cy.js new file mode 100644 index 0000000..ab55c6b --- /dev/null +++ b/cypress/e2e/funnel.cy.js @@ -0,0 +1,19 @@ +import { interceptAidesJeunesStatistics } from "../support/utils.js" + +describe("Funnel Page", () => { + beforeEach(() => { + const interceptIdentifier = interceptAidesJeunesStatistics() + cy.visitFromHome("/funnel") + cy.wait(interceptIdentifier) + }) + + it("displays the menu and title", () => { + cy.checkMenu() + cy.checkTitle() + }) + + it("displays funnel results chart", () => { + cy.get(".responsive-chart").should("be.visible") + cy.checkGraph("funnel-visits", "Visites", "125821") + }) +}) diff --git a/cypress/e2e/survey.cy.js b/cypress/e2e/survey.cy.js index ba989b0..a01a2a7 100644 --- a/cypress/e2e/survey.cy.js +++ b/cypress/e2e/survey.cy.js @@ -1,8 +1,8 @@ -import { interceptSurveyStatistics } from "../support/utils.js" +import { interceptAidesJeunesStatistics } from "../support/utils.js" describe("Survey Page", () => { beforeEach(() => { - const interceptIdentifier = interceptSurveyStatistics() + const interceptIdentifier = interceptAidesJeunesStatistics() cy.visitFromHome("/survey") cy.wait(interceptIdentifier) }) diff --git a/cypress/fixtures/surveyStatistics.json b/cypress/fixtures/aidesJeunesStatistics.json similarity index 85% rename from cypress/fixtures/surveyStatistics.json rename to cypress/fixtures/aidesJeunesStatistics.json index bf9d782..52a900d 100644 --- a/cypress/fixtures/surveyStatistics.json +++ b/cypress/fixtures/aidesJeunesStatistics.json @@ -105,5 +105,18 @@ "nothing": 9 } } + }, + "funnel": { + "2023-06": { + "visits": 125821, + "nbUniqVisitors": 97525, + "simulationCount": 25, + "followupWithOptinCount": 16, + "followupWithoutOptinCount": 0, + "followupWithSurveyCount": 2, + "followupWithSurveyRepliedCount": 2, + "showAccompanimentCount": 66, + "clickAccompanimentCount": 37 + } } } diff --git a/cypress/support/utils.js b/cypress/support/utils.js index 9199a67..009d026 100644 --- a/cypress/support/utils.js +++ b/cypress/support/utils.js @@ -16,10 +16,10 @@ export const interceptUsageStatistics = createFetchInterceptor( "usageStatistics.json", ) -export const interceptSurveyStatistics = createFetchInterceptor( - "interceptSurveyStatistics", - configuration.env.surveyStatisticsURL, - "surveyStatistics.json", +export const interceptAidesJeunesStatistics = createFetchInterceptor( + "interceptAidesJeunesStatistics", + configuration.env.aidesJeunesStatisticsURL, + "aidesJeunesStatistics.json", ) export const interceptRecorderStatistics = createFetchInterceptor( diff --git a/next.config.js b/next.config.js index 5fe97e7..bea4735 100644 --- a/next.config.js +++ b/next.config.js @@ -13,7 +13,7 @@ const configuration = { "https://stats.data.gouv.fr/index.php?date=2021-01-01,yesterday&expanded=1&filter_limit=100&force_api_session=1&format=JSON&format_metrics=1&idSite=165&method=API.get&module=API&period=month&token_auth=anonymous", observatoryURL: "https://observatoire.numerique.gouv.fr/Demarches/3135?view-mode=statistics&date-debut=2020-07-01&date-fin=", - surveyStatisticsURL: + aidesJeunesStatisticsURL: "https://mes-aides.1jeune1solution.beta.gouv.fr/documents/stats.json", benefitsURL: `${aidesJeunesUrl}api/benefits`, pagesStatsURL: diff --git a/pages/funnel.js b/pages/funnel.js new file mode 100644 index 0000000..15ff12d --- /dev/null +++ b/pages/funnel.js @@ -0,0 +1,73 @@ +import { Component } from "react" + +import { fetchFunnelData } from "../services/funnelService.js" +import { DefaultFunnelChart } from "../components/defaultFunnelChart.js" + +class Funnel extends Component { + state = { + chartsData: null, + loading: true, + selectedMonth: null, + } + + async componentDidMount() { + await this.fetchData() + } + + async fetchData() { + const { availableMonths, chartsData } = await fetchFunnelData() + + const selectedMonth = availableMonths[0] + + this.setState({ + chartsData, + selectedMonth, + loading: false, + }) + } + + render() { + const { loading, selectedMonth, chartsData } = this.state + + if (loading) { + return

Chargement...

+ } + + const { visitToRecap, surveyData, accompanimentData } = + chartsData[selectedMonth] + + return ( + <> +

Metriques de parcours {selectedMonth}

+ + <> +
+
+

Visites - Emails Récapitulatifs

+ {visitToRecap && ( + + )} +
+ +
+

Sondages Envoyés - Répondus

+ {surveyData && } +
+ +
+

Accompagnement

+ {accompanimentData && ( + + )} +
+
+ + + ) + } +} + +export default Funnel diff --git a/public/static/style.css b/public/static/style.css index 1aa1932..8cba4e7 100644 --- a/public/static/style.css +++ b/public/static/style.css @@ -396,3 +396,23 @@ table .sortable-asc:after { display: none; } } + +.funnel-charts { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + +.funnel-charts .funnel-chart { + flex: 1; + max-width: calc(100% / 3); +} + +@media screen and (max-width: 1024px) { + .funnel-charts { + flex-direction: column; + } + .funnel-charts .funnel-chart { + max-width: 100%; + } +} diff --git a/services/fetch.js b/services/fetch.js index 4cff8b8..6bd6cea 100644 --- a/services/fetch.js +++ b/services/fetch.js @@ -6,7 +6,7 @@ export default class Fetch { static async getSurveyStatistics() { const surveyStatiastics = await this.getJSON( - process.env.surveyStatisticsURL, + process.env.aidesJeunesStatisticsURL, ) const { benefitInstitutionMapping, institutions } = await this.getBenefitsAndInstitutions() diff --git a/services/funnelService.js b/services/funnelService.js new file mode 100644 index 0000000..40fe38c --- /dev/null +++ b/services/funnelService.js @@ -0,0 +1,49 @@ +import Fetch from "./fetch" + +import configuration from "../next.config.js" + +function formatFunnelData(data) { + return { + visitToRecap: [ + { label: "Visites", total: data.visits }, + { label: "Visites Uniques", total: data.nbUniqVisitors }, + { label: "Simulations terminées", total: data.simulationCount }, + { + label: "Emails de recap envoyés", + "Avec recontact": data.followupWithOptinCount, + "Sans recontact": data.followupWithoutOptinCount, + }, + ], + surveyData: [ + { + label: "Email de sondage envoyés", + total: data.followupWithSurveyCount, + }, + { + label: "Sondages répondus", + total: data.followupWithSurveyRepliedCount, + }, + ], + accompanimentData: [ + { label: "RDV affiché", total: data.showAccompanimentCount }, + { label: "RDV cliqué", total: data.clickAccompanimentCount }, + ], + } +} + +export const fetchFunnelData = async () => { + const statsResponse = await Fetch.getJSON( + configuration.env.aidesJeunesStatisticsURL, + ) + + const funnelData = statsResponse.funnel + const chartsData = {} + for (const [month, data] of Object.entries(funnelData)) { + chartsData[month] = formatFunnelData(data) + } + + return { + availableMonths: Object.keys(funnelData), + chartsData, + } +}