From b42482a1e0f8449a6e6d0c60899be838c58d0969 Mon Sep 17 00:00:00 2001 From: Yrjan Fraschetti Date: Fri, 20 Mar 2026 09:45:47 +0100 Subject: [PATCH 1/6] Add posthog analytics - Wrap application in `ConsentProvider` in `src/index.js`, to ensure the user has consented before analytics is initialized. - Add tracking for key user interactions: - Search performed (`search_performed`) - Metadata view (`metadata_detail_viewed`) - Search result clicks (`search_result_clicked`) - Service URL copied (`service_url_copied`) - Download interactions (`download_link_clicked`, `download_added_to_basket`) - Application button clicks (`application_link_clicked`) - Map interactions (`map_item_added`, `map_item_removed`) - Facet filtering (`facet_filter_applied`) --- .nvmrc | 2 +- default.env | 2 + package.json | 2 + .../partials/Buttons/ApplicationButton.js | 8 + .../partials/Buttons/DownloadButton.js | 17 + src/components/partials/Buttons/MapButton.js | 14 + src/components/partials/FacetFilter/Facet.js | 28 ++ src/components/partials/SearchResults.js | 12 +- .../SearchResults/MetadataSearchResult.js | 28 +- src/components/routes/Home.js | 39 ++- src/components/routes/Metadata.js | 11 + src/components/routes/OidcCallback.js | 3 + src/index.js | 10 +- src/utils/consentContext.js | 104 +++++++ yarn.lock | 294 ++++++++++++++++-- 15 files changed, 538 insertions(+), 36 deletions(-) create mode 100644 src/utils/consentContext.js diff --git a/.nvmrc b/.nvmrc index 28193ca7..18c92ea9 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v12.13.1 +v24 \ No newline at end of file diff --git a/default.env b/default.env index ced4bc0a..a707977b 100644 --- a/default.env +++ b/default.env @@ -2,6 +2,8 @@ HTTPS=true REACT_APP_ENVIRONMENT=dev REACT_APP_ACCESSIBILITY_STATEMENT_URL=https://uustatus.no/nb/erklaringer/publisert/698f231b-2177-4e76-bd93-628b24564dd8 +REACT_APP_POSTHOG_KEY= +REACT_APP_POSTHOG_HOST=https://eu.i.posthog.com REACT_APP_GEOID_CLIENT_ID= REACT_APP_GEOID_AUTHORITY= REACT_APP_GEOID_ISSUER= diff --git a/package.json b/package.json index 9cbb0178..aa18d6b9 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@fortawesome/pro-solid-svg-icons": "^6.1.1", "@fortawesome/react-fontawesome": "^0.2.0", "@kartverket/geonorge-web-components": "^4.0.10", + "@posthog/react": "^1.8.2", "@uiw/react-md-editor": "^3.20.5", "classnames": "^2.2.6", "connected-react-router": "^6.5.2", @@ -21,6 +22,7 @@ "moment": "^2.24.0", "moment-timezone": "^0.5.23", "oidc-client-ts": "^3.3.0", + "posthog-js": "^1.360.0", "r_map": "https://github.com/kartverket/r_map.git", "react": "^18.1.0", "react-app-polyfill": "^3.0.0", diff --git a/src/components/partials/Buttons/ApplicationButton.js b/src/components/partials/Buttons/ApplicationButton.js index 08709e71..08be193c 100644 --- a/src/components/partials/Buttons/ApplicationButton.js +++ b/src/components/partials/Buttons/ApplicationButton.js @@ -3,6 +3,7 @@ import React from "react"; import PropTypes from "prop-types"; import { useDispatch } from "react-redux"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { usePostHog } from "@posthog/react"; // Actions import { getResource } from "actions/ResourceActions"; @@ -15,6 +16,7 @@ import style from "components/partials/Buttons/Buttons.module.scss"; const ApplicationButton = (props) => { const dispatch = useDispatch(); + const posthog = usePostHog(); const handleButtonClick = () => { const tagData = { @@ -29,6 +31,12 @@ const ApplicationButton = (props) => { metadata: tagData }) ); + posthog?.capture("application_link_clicked", { + title: props.metadata.Title, + uuid: props.metadata.Uuid, + distribution_url: props.metadata.DistributionUrl || props.metadata.DownloadUrl, + organization: props.metadata.Organization, + }); }; const isApplication = () => { diff --git a/src/components/partials/Buttons/DownloadButton.js b/src/components/partials/Buttons/DownloadButton.js index 1030e51b..95763bf3 100644 --- a/src/components/partials/Buttons/DownloadButton.js +++ b/src/components/partials/Buttons/DownloadButton.js @@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react"; import PropTypes from "prop-types"; import { useDispatch, useSelector } from "react-redux"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { usePostHog } from "@posthog/react"; // Utils import userManager from "utils/userManager"; @@ -25,6 +26,7 @@ import style from "components/partials/Buttons/Buttons.module.scss"; const DownloadButton = (props) => { const dispatch = useDispatch(); + const posthog = usePostHog(); // Redux store const itemsToDownload = useSelector((state) => state.itemsToDownload); @@ -159,11 +161,26 @@ const DownloadButton = (props) => { metadata: tagData }) ); + posthog?.capture("download_link_clicked", { + title: props.metadata.Title, + uuid: props.metadata.Uuid, + distribution_url: props.metadata.DistributionUrl, + protocol: props.metadata.Protocol, + organization: props.metadata.Organization, + }); }; const addToDownloadListAction = () => { const metadata = props.metadata; setIsLoading(true); + posthog?.capture("download_added_to_basket", { + title: metadata.Title, + uuid: metadata.Uuid, + type_name: metadata.TypeName, + organization: metadata.Organization || metadata.ContactMetadata?.Organization, + access_is_open_data: metadata.AccessIsOpendata, + access_is_restricted: metadata.AccessIsRestricted, + }); if (metadata.TypeName === "series_historic" || metadata.TypeName === "series_collection") { if (metadata.SerieDatasets) { diff --git a/src/components/partials/Buttons/MapButton.js b/src/components/partials/Buttons/MapButton.js index 4d13e8dd..a8cc39ed 100644 --- a/src/components/partials/Buttons/MapButton.js +++ b/src/components/partials/Buttons/MapButton.js @@ -3,6 +3,7 @@ import React, { useEffect, useState } from "react"; import PropTypes from "prop-types"; import { useDispatch, useSelector } from "react-redux"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { usePostHog } from "@posthog/react"; // Actions import { removeMapItem, addMapItem } from "actions/MapItemActions"; @@ -15,6 +16,7 @@ import style from "components/partials/Buttons/Buttons.module.scss"; const MapButton = (props) => { const dispatch = useDispatch(); + const posthog = usePostHog(); // Redux store const mapItems = useSelector((state) => state.mapItems); @@ -119,12 +121,24 @@ const MapButton = (props) => { const addToMap = (mapItem) => { if (mapItem?.length) { dispatch(addMapItem(mapItem)); + posthog?.capture("map_item_added", { + title: props.metadata.Title, + uuid: props.metadata.Uuid, + protocol: props.metadata.DistributionProtocol || props.metadata.Protocol, + organization: props.metadata.Organization, + }); } }; const removeFromMap = (mapItem) => { if (mapItem?.length) { dispatch(removeMapItem(mapItem)); + posthog?.capture("map_item_removed", { + title: props.metadata.Title, + uuid: props.metadata.Uuid, + protocol: props.metadata.DistributionProtocol || props.metadata.Protocol, + organization: props.metadata.Organization, + }); } }; diff --git a/src/components/partials/FacetFilter/Facet.js b/src/components/partials/FacetFilter/Facet.js index fc27a433..d8915691 100644 --- a/src/components/partials/FacetFilter/Facet.js +++ b/src/components/partials/FacetFilter/Facet.js @@ -5,6 +5,7 @@ import { useDispatch } from "react-redux"; import classNames from "classnames/bind"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link } from "react-router-dom"; +import { usePostHog } from "@posthog/react"; // Reducers import { pushToDataLayer } from "reducers/TagManagerReducer"; @@ -20,6 +21,7 @@ import style from "components/partials/FacetFilter/Facet.module.scss"; const Facet = (props) => { const dispatch = useDispatch(); + const posthog = usePostHog(); // State const [checked, setChecked] = useState(false); @@ -139,6 +141,32 @@ const Facet = (props) => { facet: props.facet }) ); + + const selectedFacets = props?.searchData?.selectedFacets || {}; + const activeFilters = []; + const flattenFacets = (facets, fieldName) => { + Object.values(facets).forEach((facet) => { + activeFilters.push(`${fieldName}: ${facet.NameTranslated || facet.Name}`); + if (facet.facets && Object.keys(facet.facets).length > 0) { + flattenFacets(facet.facets, fieldName); + } + }); + }; + Object.keys(selectedFacets).forEach((fieldKey) => { + if (selectedFacets[fieldKey].facets) { + flattenFacets(selectedFacets[fieldKey].facets, fieldKey); + } + }); + + posthog?.capture("facet_filter_applied", { + facet_field: props.facetField, + facet_field_name: props.facetFieldNameTranslated, + facet_name: props.facet.Name, + facet_name_translated: props.facet.NameTranslated, + facet_count: props.facet.Count, + action: checked ? "remove" : "add", + active_filters: activeFilters, + }); }; const renderFacet = () => { diff --git a/src/components/partials/SearchResults.js b/src/components/partials/SearchResults.js index 989edf65..7a6997d0 100644 --- a/src/components/partials/SearchResults.js +++ b/src/components/partials/SearchResults.js @@ -3,6 +3,7 @@ import React from "react"; import { useDispatch } from "react-redux"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link } from "react-router-dom"; +import { usePostHog } from "@posthog/react"; // Geonorge WebComponents // eslint-disable-next-line no-unused-vars @@ -22,6 +23,7 @@ import style from "components/partials/SearchResults.module.scss"; export const SearchResults = ({ searchData, searchResultsType }) => { const dispatch = useDispatch(); + const posthog = usePostHog(); const getShowMoreLink = () => { const newOffset = searchData?.offset + 25; @@ -72,11 +74,19 @@ export const SearchResults = ({ searchData, searchResultsType }) => { return localStorage.getItem("urlDownloadCsv"); }; + const handleShowMoreClick = () => { + posthog?.capture("show_more_results_clicked", { + results_type: searchResultsType, + current_offset: searchData?.offset, + search_string: searchData?.searchString, + }); + }; + const renderShowMoreLink = () => { return (
- + {dispatch(getResource("ShowMoreResults", "Vis flere"))} diff --git a/src/components/partials/SearchResults/MetadataSearchResult.js b/src/components/partials/SearchResults/MetadataSearchResult.js index 41b14f7a..d8a823c1 100644 --- a/src/components/partials/SearchResults/MetadataSearchResult.js +++ b/src/components/partials/SearchResults/MetadataSearchResult.js @@ -5,6 +5,7 @@ import { useDispatch } from "react-redux"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link } from "react-router-dom"; import { CopyToClipboard } from "react-copy-to-clipboard"; +import { usePostHog } from "@posthog/react"; // Actions import { getResource } from "actions/ResourceActions"; @@ -27,6 +28,7 @@ import style from "components/partials/SearchResults/MetadataSearchResult.module const MetadataSearchResult = (props) => { const dispatch = useDispatch(); + const posthog = usePostHog(); // State const [copied, setCopied] = useState(false); @@ -170,21 +172,43 @@ const MetadataSearchResult = (props) => { ); }; + const handleResultClick = () => { + posthog?.capture("search_result_clicked", { + title: props.searchResult.Title, + uuid: props.searchResult.Uuid, + type: props.searchResult.Type, + organization: props.searchResult.Organization, + is_open_data: props.searchResult.IsOpenData || props.searchResult.AccessIsOpendata, + }); + }; + const renderLink = () => { return props.metadata?.Uuid === props.searchResult.Uuid ? ( {props.searchResult.Title} ) : ( - + {props.searchResult.Title} ); }; + const handleCopyUrl = () => { + setCopied(true); + posthog?.capture("service_url_copied", { + title: props.searchResult.Title, + uuid: props.searchResult.Uuid, + get_capabilities_url: props.searchResult.GetCapabilitiesUrl, + }); + }; + const renderCopyUrl = () => { return (props.searchResult.Type === "service" || props.searchResult.Type === "Tjeneste") && props.searchResult.GetCapabilitiesUrl !== undefined ? ( - setCopied(true)} text={props.searchResult.GetCapabilitiesUrl}> + Kopier lenke {" "} {copied ? Lenke kopiert til utklippstavle : null} diff --git a/src/components/routes/Home.js b/src/components/routes/Home.js index 27499d0f..5b1d33d3 100644 --- a/src/components/routes/Home.js +++ b/src/components/routes/Home.js @@ -1,9 +1,10 @@ // Dependencies -import React, { useEffect } from "react"; +import React, { useEffect, useRef } from "react"; import { Helmet } from "react-helmet-async"; import { useDispatch, useSelector } from "react-redux"; import { Link, useRouteLoaderData } from "react-router-dom"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { usePostHog } from "@posthog/react"; // Geonorge WebComponents // eslint-disable-next-line no-unused-vars @@ -23,12 +24,48 @@ import style from "./Home.module.scss"; const Home = () => { const dispatch = useDispatch(); const { searchData, params } = useRouteLoaderData("root"); + const posthog = usePostHog(); + const lastCapturedSearchString = useRef(null); // Redux store const auth = useSelector((state) => state.auth); const environment = useSelector((state) => state.environment); const selectedLanguage = useSelector((state) => state.selectedLanguage); + useEffect(() => { + const searchString = searchData?.searchString || ""; + const selectedFacets = searchData?.selectedFacets || {}; + + // Helper to extract active filters + const activeFilters = []; + const flattenFacets = (facets, fieldName) => { + Object.values(facets).forEach((facet) => { + activeFilters.push(`${fieldName}: ${facet.NameTranslated || facet.Name}`); + if (facet.facets && Object.keys(facet.facets).length > 0) { + flattenFacets(facet.facets, fieldName); + } + }); + }; + Object.keys(selectedFacets).forEach((fieldKey) => { + if (selectedFacets[fieldKey].facets) { + flattenFacets(selectedFacets[fieldKey].facets, fieldKey); + } + }); + + if (posthog && searchString !== lastCapturedSearchString.current) { + lastCapturedSearchString.current = searchString; + if (searchString.length > 0) { + posthog.capture("search_performed", { + search_string: searchString, + results_type: params?.searchResultsType || "metadata", + num_found_metadata: searchData?.results?.metadata?.NumFound || 0, + num_found_articles: searchData?.results?.articles?.NumFound || 0, + active_filters: activeFilters, + }); + } + } + }, [searchData, params, posthog]); + useEffect(() => { const isLoggedIn = !!auth?.user?.access_token?.length; // Todo fix problem navigation from MainNavigationContainer https://medium.com/@fabrizio.azzarri/fixing-the-next-js-15-react-19-removechild-dom-error-a33b57cbc3b1 diff --git a/src/components/routes/Metadata.js b/src/components/routes/Metadata.js index 480ad810..80f44d11 100644 --- a/src/components/routes/Metadata.js +++ b/src/components/routes/Metadata.js @@ -4,6 +4,7 @@ import { Helmet } from "react-helmet-async"; import Moment from "react-moment"; import { useDispatch, useSelector } from "react-redux"; import { useLoaderData, useLocation, useParams } from "react-router-dom"; +import { usePostHog } from "@posthog/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import DatePicker from "react-datepicker"; import MDEditor from "@uiw/react-md-editor"; @@ -54,6 +55,7 @@ import { scrollToTop } from "helpers/GuiHelpers"; const Metadata = () => { const dispatch = useDispatch(); + const posthog = usePostHog(); const params = useParams(); const location = useLocation(); @@ -234,6 +236,15 @@ const Metadata = () => { metadata: tagData }) ); + posthog?.capture("metadata_detail_viewed", { + title: getTitle(), + uuid: metadata.Uuid, + type: metadata.Type, + access_is_open_data: metadata.AccessIsOpendata, + access_is_restricted: metadata.AccessIsRestricted, + organization: metadata.ContactMetadata?.Organization || null, + theme: metadata.Theme || null, + }); }; const renderDatasetLanguage = () => { diff --git a/src/components/routes/OidcCallback.js b/src/components/routes/OidcCallback.js index c13ca3be..898ee568 100644 --- a/src/components/routes/OidcCallback.js +++ b/src/components/routes/OidcCallback.js @@ -3,17 +3,20 @@ import { useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useDispatch } from "react-redux"; import { userLoaded } from "reducers/authActions"; +import posthog from "posthog-js"; const processSigninResponse = async (userManager, navigate, dispatch) => { try { const user = await userManager.signinRedirectCallback(); console.log(user); dispatch(userLoaded(user)); // Dispatch to Redux + posthog?.capture("user_logged_in"); const autoRedirectPath = sessionStorage?.autoRedirectPath || "/"; sessionStorage.removeItem("autoRedirectPath"); navigate(autoRedirectPath); } catch (error) { console.error("Sign-in response error:", error); + posthog?.captureException(error); navigate("/"); } }; diff --git a/src/index.js b/src/index.js index 5675aa6b..a9e8e24d 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ import React from "react"; import { createRoot } from "react-dom/client"; import * as serviceWorker from "serviceWorker"; import "react-app-polyfill/ie11"; +import { ConsentProvider } from "utils/consentContext"; // Components import App from "App"; @@ -15,7 +16,7 @@ import { convertSearchParams, convertPath } from "helpers/UrlHelpers"; import "index.css"; import "@kartverket/geonorge-web-components/index.css"; - var pathName = window.location.pathname; +var pathName = window.location.pathname; if (pathName !== "/login-oidc" && window.location.search !== convertSearchParams(window.location.search)) { window.location.href = window.location.origin + convertSearchParams(window.location.search); @@ -30,7 +31,12 @@ if (pathName !== "/login-oidc" && window.location.search !== convertSearchParams const container = document.getElementById("root"); const root = createRoot(container); - root.render(); + + root.render( + + + + ); serviceWorker.unregister(); } diff --git a/src/utils/consentContext.js b/src/utils/consentContext.js new file mode 100644 index 00000000..7b2f1edb --- /dev/null +++ b/src/utils/consentContext.js @@ -0,0 +1,104 @@ +import { createContext, useContext, useEffect, useState, useRef } from "react"; +import posthog from "posthog-js"; +import { PostHogErrorBoundary, PostHogProvider } from "@posthog/react"; + +const defaultConsent = { + analytics: false, + functional: false, + performance: false, + advertisement: false, +}; + +const ConsentContext = createContext(defaultConsent); + +export function ConsentProvider({ children }) { + const [consent, setConsent] = useState(defaultConsent); + const hasInitPosthog = useRef(false); + + useEffect(() => { + if (!hasInitPosthog.current && !posthog.__loaded) { + posthog.init(process.env.REACT_APP_POSTHOG_KEY, { + api_host: process.env.REACT_APP_POSTHOG_HOST, + ui_host: "https://eu.posthog.com", + autocapture: false, + capture_pageview: false, + opt_out_capturing_by_default: true, + session_idle_timeout_seconds: 60 * 10, + capture_exceptions: window.location.hostname !== "localhost", + session_recording: { + session_idle_threshold_ms: 3 * 60 * 1000 + } + }); + hasInitPosthog.current = true; + } + }, []); + + useEffect(() => { + if (consent.analytics) { + posthog.opt_in_capturing({ + autocapture: true, + capture_pageview: true + }); + } else { + posthog.opt_out_capturing({ + autocapture: false, + capture_pageview: false + }); + } + }, [consent.analytics]); + + useEffect(() => { + function handleBannerLoad(event) { + const { categories } = event.detail; + setConsent({ + analytics: categories.analytics, + functional: categories.functional, + performance: categories.performance, + advertisement: categories.advertisement, + }); + } + + function handleConsentUpdate(event) { + const { accepted } = event.detail; + setConsent({ + analytics: accepted.includes("analytics"), + functional: accepted.includes("functional"), + performance: accepted.includes("performance"), + advertisement: accepted.includes("advertisement"), + }); + } + + document.addEventListener("cookieyes_banner_load", handleBannerLoad); + document.addEventListener("cookieyes_consent_update", handleConsentUpdate); + + // Handle the case where banner_load already fired before React mounted + if (window.getCkyConsent) { + const existing = window.getCkyConsent(); + if (existing?.isUserActionCompleted) { + setConsent({ + analytics: existing.categories.analytics, + functional: existing.categories.functional, + performance: existing.categories.performance, + advertisement: existing.categories.advertisement, + }); + } + } + + return () => { + document.removeEventListener("cookieyes_banner_load", handleBannerLoad); + document.removeEventListener("cookieyes_consent_update", handleConsentUpdate); + }; + }, []); + + return ( + + + + {children} + + + + ); +} + +export const useConsent = () => useContext(ConsentContext); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 6da19cba..3aef8b18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1647,6 +1647,111 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@opentelemetry/api-logs@0.208.0", "@opentelemetry/api-logs@^0.208.0": + version "0.208.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz#56d3891010a1fa1cf600ba8899ed61b43ace511c" + integrity sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg== + dependencies: + "@opentelemetry/api" "^1.3.0" + +"@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" + integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== + +"@opentelemetry/core@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.2.0.tgz#2f857d7790ff160a97db3820889b5f4cade6eaee" + integrity sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw== + dependencies: + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/core@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.6.0.tgz#719c829ed98bd7af808a2d2c83374df1fd1f3c66" + integrity sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg== + dependencies: + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/exporter-logs-otlp-http@^0.208.0": + version "0.208.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.208.0.tgz#198d6e735e961a79352a3d032a28da295db802dc" + integrity sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg== + dependencies: + "@opentelemetry/api-logs" "0.208.0" + "@opentelemetry/core" "2.2.0" + "@opentelemetry/otlp-exporter-base" "0.208.0" + "@opentelemetry/otlp-transformer" "0.208.0" + "@opentelemetry/sdk-logs" "0.208.0" + +"@opentelemetry/otlp-exporter-base@0.208.0": + version "0.208.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.208.0.tgz#1a932355628087555a317b7207637d4e893c1a5d" + integrity sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA== + dependencies: + "@opentelemetry/core" "2.2.0" + "@opentelemetry/otlp-transformer" "0.208.0" + +"@opentelemetry/otlp-transformer@0.208.0": + version "0.208.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.208.0.tgz#c59f48a569d17766d91c61807db7b04e4be490ac" + integrity sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ== + dependencies: + "@opentelemetry/api-logs" "0.208.0" + "@opentelemetry/core" "2.2.0" + "@opentelemetry/resources" "2.2.0" + "@opentelemetry/sdk-logs" "0.208.0" + "@opentelemetry/sdk-metrics" "2.2.0" + "@opentelemetry/sdk-trace-base" "2.2.0" + protobufjs "^7.3.0" + +"@opentelemetry/resources@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.2.0.tgz#b90a950ad98551295b76ea8a0e7efe45a179badf" + integrity sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A== + dependencies: + "@opentelemetry/core" "2.2.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/resources@^2.2.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.6.0.tgz#1a945dbb8986043d8b593c358d5d8e3de6becf5a" + integrity sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ== + dependencies: + "@opentelemetry/core" "2.6.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/sdk-logs@0.208.0", "@opentelemetry/sdk-logs@^0.208.0": + version "0.208.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-logs/-/sdk-logs-0.208.0.tgz#013494e23412c1594a694a358211cd150144c525" + integrity sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA== + dependencies: + "@opentelemetry/api-logs" "0.208.0" + "@opentelemetry/core" "2.2.0" + "@opentelemetry/resources" "2.2.0" + +"@opentelemetry/sdk-metrics@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz#3824133f0d681d778aff0f52b02a87ec6750fc2d" + integrity sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw== + dependencies: + "@opentelemetry/core" "2.2.0" + "@opentelemetry/resources" "2.2.0" + +"@opentelemetry/sdk-trace-base@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz#ddef9a0afd01a623d8625a3529f2137b05e67d0b" + integrity sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw== + dependencies: + "@opentelemetry/core" "2.2.0" + "@opentelemetry/resources" "2.2.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/semantic-conventions@^1.29.0": + version "1.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz#10b2944ca559386590683392022a897eefd011d3" + integrity sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw== + "@parcel/watcher-android-arm64@2.5.1": version "2.5.1" resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1" @@ -1764,6 +1869,76 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== +"@posthog/core@1.23.2": + version "1.23.2" + resolved "https://registry.yarnpkg.com/@posthog/core/-/core-1.23.2.tgz#1b55307755a2b9838d308039e2972395f8d0fd40" + integrity sha512-zTDdda9NuSHrnwSOfFMxX/pyXiycF4jtU1kTr8DL61dHhV+7LF6XF1ndRZZTuaGGbfbb/GJYkEsjEX9SXfNZeQ== + dependencies: + cross-spawn "^7.0.6" + +"@posthog/react@^1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@posthog/react/-/react-1.8.2.tgz#ba3855986a3eb7f698bc70455e5a8b7d536212df" + integrity sha512-KzUuXIcAR8fAjU7IeDq+XfEcUTNvzgEGB381WRrFUUsu7jFTcKZZ6crx/ukHRCzTnoEuy5EJDkL7b7sJecPlCg== + +"@posthog/types@1.360.0": + version "1.360.0" + resolved "https://registry.yarnpkg.com/@posthog/types/-/types-1.360.0.tgz#b52dfee15099104ac07feecdb5c7897b216ac62e" + integrity sha512-roypbiJ49V3jWlV/lzhXGf0cKLLRj69L4H4ZHW6YsITHlnjQ12cgdPhPS88Bb9nW9xZTVSGWWDjfNGsdgAxsNg== + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@remix-run/router@1.23.0": version "1.23.0" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.23.0.tgz#35390d0e7779626c026b11376da6789eb8389242" @@ -2234,6 +2409,13 @@ dependencies: undici-types "~7.8.0" +"@types/node@>=13.7.0": + version "25.3.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.3.5.tgz#beccb5915561f7a9970ace547ad44d6cdbf39b46" + integrity sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA== + dependencies: + undici-types "~7.18.0" + "@types/normalize-package-data@^2.4.0": version "2.4.4" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" @@ -2349,7 +2531,7 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== -"@types/trusted-types@^2.0.2": +"@types/trusted-types@^2.0.2", "@types/trusted-types@^2.0.7": version "2.0.7" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== @@ -3896,6 +4078,11 @@ core-js@^3.19.2: resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.43.0.tgz#f7258b156523208167df35dea0cfd6b6ecd4ee88" integrity sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA== +core-js@^3.38.1: + version "3.48.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.48.0.tgz#1f813220a47bbf0e667e3885c36cd6f0593bf14d" + integrity sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -4496,6 +4683,13 @@ domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" +dompurify@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.3.2.tgz#58c515d0f8508b8749452a028aa589ad80b36325" + integrity sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ== + optionalDependencies: + "@types/trusted-types" "^2.0.7" + domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" @@ -5359,6 +5553,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fflate@^0.4.8: + version "0.4.8" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -7680,6 +7879,11 @@ log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" +long@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + longest-streak@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" @@ -9568,6 +9772,30 @@ postcss@^8.3.5, postcss@^8.4.33, postcss@^8.4.4, postcss@^8.4.47: picocolors "^1.1.1" source-map-js "^1.2.1" +posthog-js@^1.360.0: + version "1.360.0" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.360.0.tgz#947a54bd84430d46af3d2fc1c22156a7df246f49" + integrity sha512-jkyO+T97yi6RuiexOaXC7AnEGiC+yIfGU5DIUzI5rqBH6MltmtJw/ve2Oxc4jeua2WDr5sXMzo+SS+acbpueAA== + dependencies: + "@opentelemetry/api" "^1.9.0" + "@opentelemetry/api-logs" "^0.208.0" + "@opentelemetry/exporter-logs-otlp-http" "^0.208.0" + "@opentelemetry/resources" "^2.2.0" + "@opentelemetry/sdk-logs" "^0.208.0" + "@posthog/core" "1.23.2" + "@posthog/types" "1.360.0" + core-js "^3.38.1" + dompurify "^3.3.2" + fflate "^0.4.8" + preact "^10.28.2" + query-selector-shadow-dom "^1.0.1" + web-vitals "^5.1.0" + +preact@^10.28.2: + version "10.28.4" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.28.4.tgz#8ffab01c5c0590535bdaecdd548801f44c6e483a" + integrity sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -9678,6 +9906,24 @@ property-information@^6.0.0: resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== +protobufjs@^7.3.0: + version "7.5.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.4.tgz#885d31fe9c4b37f25d1bb600da30b1c5b37d286a" + integrity sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + protocol-buffers-schema@^3.3.1: version "3.6.0" resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03" @@ -9730,6 +9976,11 @@ qs@6.13.0: dependencies: side-channel "^1.0.6" +query-selector-shadow-dom@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz#1c7b0058eff4881ac44f45d8f84ede32e9a2f349" + integrity sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw== + query-string@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/query-string/-/query-string-2.4.2.tgz#7db0666420804baa92ae9f268962855a76143dfb" @@ -11234,16 +11485,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -11360,14 +11602,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11906,6 +12141,11 @@ underscore@1.12.1: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.1.tgz#7bb8cc9b3d397e201cf8553336d262544ead829e" integrity sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw== +undici-types@~7.18.0: + version "7.18.2" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9" + integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== + undici-types@~7.8.0: version "7.8.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.8.0.tgz#de00b85b710c54122e44fbfd911f8d70174cd294" @@ -12243,6 +12483,11 @@ web-namespaces@^2.0.0: resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== +web-vitals@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-5.1.0.tgz#2f117e92c8c4eeb107cb163cbb482ac20d685ebd" + integrity sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg== + web-worker@^1.2.0: version "1.5.0" resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.5.0.tgz#71b2b0fbcc4293e8f0aa4f6b8a3ffebff733dcc5" @@ -12702,16 +12947,7 @@ workbox-window@6.6.1: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 112a68d31c50b16c49dce526eaa0bc2581158355 Mon Sep 17 00:00:00 2001 From: Yrjan Fraschetti Date: Fri, 20 Mar 2026 12:56:50 +0100 Subject: [PATCH 2/6] Fix PostHog consent initialization and shared facet analytics --- public/config.template.js | 4 +- src/components/partials/FacetFilter/Facet.js | 17 +---- src/components/routes/Home.js | 20 +---- src/helpers/FacetFilterHelpers.js | 22 ++++++ src/utils/consentContext.js | 77 ++++++++++++-------- 5 files changed, 76 insertions(+), 64 deletions(-) diff --git a/public/config.template.js b/public/config.template.js index 174fb789..434ad035 100644 --- a/public/config.template.js +++ b/public/config.template.js @@ -14,5 +14,7 @@ window.__APP_CONFIG__ = { REACT_APP_GEOID_JWKS_URI: "${REACT_APP_GEOID_JWKS_URI}", REACT_APP_GEOID_KID: "${REACT_APP_GEOID_KID}", REACT_APP_GEOID_N: "${REACT_APP_GEOID_N}", - REACT_APP_GEOID_BAATAUTHZ_APIURL: "${REACT_APP_GEOID_BAATAUTHZ_APIURL}" + REACT_APP_GEOID_BAATAUTHZ_APIURL: "${REACT_APP_GEOID_BAATAUTHZ_APIURL}", + REACT_APP_POSTHOG_KEY: "${REACT_APP_POSTHOG_KEY}", + REACT_APP_POSTHOG_HOST: "${REACT_APP_POSTHOG_HOST}" }; diff --git a/src/components/partials/FacetFilter/Facet.js b/src/components/partials/FacetFilter/Facet.js index d8915691..51d86adb 100644 --- a/src/components/partials/FacetFilter/Facet.js +++ b/src/components/partials/FacetFilter/Facet.js @@ -11,7 +11,7 @@ import { usePostHog } from "@posthog/react"; import { pushToDataLayer } from "reducers/TagManagerReducer"; // Helpers -import { getQueryStringFromFacets } from "helpers/FacetFilterHelpers"; +import { getActiveFiltersFromSelectedFacets, getQueryStringFromFacets } from "helpers/FacetFilterHelpers"; // Components import { ErrorBoundary } from "components/ErrorBoundary"; @@ -143,20 +143,7 @@ const Facet = (props) => { ); const selectedFacets = props?.searchData?.selectedFacets || {}; - const activeFilters = []; - const flattenFacets = (facets, fieldName) => { - Object.values(facets).forEach((facet) => { - activeFilters.push(`${fieldName}: ${facet.NameTranslated || facet.Name}`); - if (facet.facets && Object.keys(facet.facets).length > 0) { - flattenFacets(facet.facets, fieldName); - } - }); - }; - Object.keys(selectedFacets).forEach((fieldKey) => { - if (selectedFacets[fieldKey].facets) { - flattenFacets(selectedFacets[fieldKey].facets, fieldKey); - } - }); + const activeFilters = getActiveFiltersFromSelectedFacets(selectedFacets); posthog?.capture("facet_filter_applied", { facet_field: props.facetField, diff --git a/src/components/routes/Home.js b/src/components/routes/Home.js index 5b1d33d3..cd60a8ed 100644 --- a/src/components/routes/Home.js +++ b/src/components/routes/Home.js @@ -13,6 +13,9 @@ import { BreadcrumbList, HeadingText, GnShortcutButton } from "@kartverket/geono // Actions import { getResource } from "actions/ResourceActions"; +// Helpers +import { getActiveFiltersFromSelectedFacets } from "helpers/FacetFilterHelpers"; + // Components import SelectedFacets from "components/partials/SelectedFacets"; import SearchResults from "components/partials/SearchResults"; @@ -35,22 +38,7 @@ const Home = () => { useEffect(() => { const searchString = searchData?.searchString || ""; const selectedFacets = searchData?.selectedFacets || {}; - - // Helper to extract active filters - const activeFilters = []; - const flattenFacets = (facets, fieldName) => { - Object.values(facets).forEach((facet) => { - activeFilters.push(`${fieldName}: ${facet.NameTranslated || facet.Name}`); - if (facet.facets && Object.keys(facet.facets).length > 0) { - flattenFacets(facet.facets, fieldName); - } - }); - }; - Object.keys(selectedFacets).forEach((fieldKey) => { - if (selectedFacets[fieldKey].facets) { - flattenFacets(selectedFacets[fieldKey].facets, fieldKey); - } - }); + const activeFilters = getActiveFiltersFromSelectedFacets(selectedFacets); if (posthog && searchString !== lastCapturedSearchString.current) { lastCapturedSearchString.current = searchString; diff --git a/src/helpers/FacetFilterHelpers.js b/src/helpers/FacetFilterHelpers.js index 03fea6f1..d40b6a64 100644 --- a/src/helpers/FacetFilterHelpers.js +++ b/src/helpers/FacetFilterHelpers.js @@ -16,6 +16,28 @@ const getChildFacetsName = (facet, facetField, options = {}) => { return queryString; }; +export const getActiveFiltersFromSelectedFacets = (selectedFacets = {}) => { + const activeFilters = []; + + const flattenFacets = (facets, fieldName) => { + Object.values(facets || {}).forEach((facet) => { + activeFilters.push(`${fieldName}: ${facet.NameTranslated || facet.Name}`); + + if (facet.facets && Object.keys(facet.facets).length > 0) { + flattenFacets(facet.facets, fieldName); + } + }); + }; + + Object.keys(selectedFacets || {}).forEach((fieldKey) => { + if (selectedFacets[fieldKey]?.facets) { + flattenFacets(selectedFacets[fieldKey].facets, fieldKey); + } + }); + + return activeFilters; +}; + export const getQueryStringFromFacets = (selectedFacets = {}, searchString, options = {}) => { let queryStringFromFacets = searchString ? `?text=${searchString}` : ''; diff --git a/src/utils/consentContext.js b/src/utils/consentContext.js index 7b2f1edb..38d94c30 100644 --- a/src/utils/consentContext.js +++ b/src/utils/consentContext.js @@ -1,6 +1,7 @@ import { createContext, useContext, useEffect, useState, useRef } from "react"; import posthog from "posthog-js"; import { PostHogErrorBoundary, PostHogProvider } from "@posthog/react"; +import { getConfig } from "utils/runtimeConfig"; const defaultConsent = { analytics: false, @@ -11,35 +12,59 @@ const defaultConsent = { const ConsentContext = createContext(defaultConsent); +const getInitialConsent = () => { + if (typeof window === "undefined" || !window.getCkyConsent) { + return defaultConsent; + } + + const existingConsent = window.getCkyConsent(); + + if (!existingConsent?.isUserActionCompleted) { + return defaultConsent; + } + + return { + analytics: !!existingConsent.categories?.analytics, + functional: !!existingConsent.categories?.functional, + performance: !!existingConsent.categories?.performance, + advertisement: !!existingConsent.categories?.advertisement, + }; +}; + export function ConsentProvider({ children }) { - const [consent, setConsent] = useState(defaultConsent); + const [consent, setConsent] = useState(getInitialConsent); const hasInitPosthog = useRef(false); - useEffect(() => { - if (!hasInitPosthog.current && !posthog.__loaded) { - posthog.init(process.env.REACT_APP_POSTHOG_KEY, { - api_host: process.env.REACT_APP_POSTHOG_HOST, - ui_host: "https://eu.posthog.com", - autocapture: false, - capture_pageview: false, - opt_out_capturing_by_default: true, - session_idle_timeout_seconds: 60 * 10, - capture_exceptions: window.location.hostname !== "localhost", - session_recording: { - session_idle_threshold_ms: 3 * 60 * 1000 - } - }); - hasInitPosthog.current = true; - } - }, []); - useEffect(() => { if (consent.analytics) { + const posthogKey = getConfig("REACT_APP_POSTHOG_KEY", ""); + const posthogHost = getConfig("REACT_APP_POSTHOG_HOST", ""); + + if (!posthogKey || !posthogHost) { + return; + } + + if (!hasInitPosthog.current) { + posthog.init(posthogKey, { + api_host: posthogHost, + ui_host: "https://eu.posthog.com", + autocapture: false, + capture_pageview: false, + opt_out_capturing_by_default: true, + session_idle_timeout_seconds: 60 * 10, + capture_exceptions: window.location.hostname !== "localhost", + session_recording: { + session_idle_threshold_ms: 3 * 60 * 1000 + } + }); + hasInitPosthog.current = true; + } + posthog.opt_in_capturing({ autocapture: true, capture_pageview: true }); - } else { + } else if (hasInitPosthog.current) { posthog.opt_out_capturing({ autocapture: false, capture_pageview: false @@ -71,18 +96,6 @@ export function ConsentProvider({ children }) { document.addEventListener("cookieyes_banner_load", handleBannerLoad); document.addEventListener("cookieyes_consent_update", handleConsentUpdate); - // Handle the case where banner_load already fired before React mounted - if (window.getCkyConsent) { - const existing = window.getCkyConsent(); - if (existing?.isUserActionCompleted) { - setConsent({ - analytics: existing.categories.analytics, - functional: existing.categories.functional, - performance: existing.categories.performance, - advertisement: existing.categories.advertisement, - }); - } - } return () => { document.removeEventListener("cookieyes_banner_load", handleBannerLoad); From e21f5a148b0fe8ae34a0c1adcbcf5812b07c2c8c Mon Sep 17 00:00:00 2001 From: Yrjan Fraschetti Date: Mon, 23 Mar 2026 09:24:05 +0100 Subject: [PATCH 3/6] Refine facet analytics tracking and simplify toggle helpers. Downgrade node version to v18 in .nvmrc --- .nvmrc | 2 +- src/actions/FacetFilterActions.js | 10 +- src/components/partials/FacetFilter/Facet.js | 23 +++- src/components/routes/OidcCallback.js | 2 +- src/helpers/FacetFilterHelpers.js | 135 +++++++++++++++++++ 5 files changed, 159 insertions(+), 13 deletions(-) diff --git a/.nvmrc b/.nvmrc index 18c92ea9..3f430af8 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v24 \ No newline at end of file +v18 diff --git a/src/actions/FacetFilterActions.js b/src/actions/FacetFilterActions.js index 00311455..6d503a5a 100644 --- a/src/actions/FacetFilterActions.js +++ b/src/actions/FacetFilterActions.js @@ -23,17 +23,19 @@ export const updateSelectedFacets = (facets) => (dispatch) => { const addSelectedChildFacetsToAnalytics = (facet, facetType) => (dispatch) => { dispatch( pushToDataLayer({ - event: "updateSelectedFacets", + event: "selected_facets_updated", category: "facets", - activity: "addFacetType", + activity: "facet_type_toggled", + action: "add", facet: facetType }) ); dispatch( pushToDataLayer({ - event: "updateSelectedFacets", + event: "selected_facets_updated", category: "facets", - activity: "addFacet", + activity: "facet_toggled", + action: "add", facet: facet }) ); diff --git a/src/components/partials/FacetFilter/Facet.js b/src/components/partials/FacetFilter/Facet.js index 51d86adb..61386f2c 100644 --- a/src/components/partials/FacetFilter/Facet.js +++ b/src/components/partials/FacetFilter/Facet.js @@ -11,7 +11,11 @@ import { usePostHog } from "@posthog/react"; import { pushToDataLayer } from "reducers/TagManagerReducer"; // Helpers -import { getActiveFiltersFromSelectedFacets, getQueryStringFromFacets } from "helpers/FacetFilterHelpers"; +import { + getActiveFiltersFromSelectedFacets, + getNextSelectedFacetsFromFacetToggle, + getQueryStringFromFacets +} from "helpers/FacetFilterHelpers"; // Components import { ErrorBoundary } from "components/ErrorBoundary"; @@ -125,25 +129,30 @@ const Facet = (props) => { }; const handleFacetClick = () => { + const action = checked ? "remove" : "add"; + dispatch( pushToDataLayer({ - event: "updateSelectedFacets", + event: "selected_facets_updated", category: "facets", - activity: "addFacetType", + activity: "facet_type_toggled", + action, facet: { NameTranslated: props.facetFieldNameTranslated } }) ); dispatch( pushToDataLayer({ - event: "updateSelectedFacets", + event: "selected_facets_updated", category: "facets", - activity: "addFacet", + activity: "facet_toggled", + action, facet: props.facet }) ); const selectedFacets = props?.searchData?.selectedFacets || {}; - const activeFilters = getActiveFiltersFromSelectedFacets(selectedFacets); + const nextSelectedFacets = getNextSelectedFacetsFromFacetToggle(selectedFacets, props.facetField, props.facet, action); + const activeFilters = getActiveFiltersFromSelectedFacets(nextSelectedFacets); posthog?.capture("facet_filter_applied", { facet_field: props.facetField, @@ -151,7 +160,7 @@ const Facet = (props) => { facet_name: props.facet.Name, facet_name_translated: props.facet.NameTranslated, facet_count: props.facet.Count, - action: checked ? "remove" : "add", + action, active_filters: activeFilters, }); }; diff --git a/src/components/routes/OidcCallback.js b/src/components/routes/OidcCallback.js index 898ee568..d456db22 100644 --- a/src/components/routes/OidcCallback.js +++ b/src/components/routes/OidcCallback.js @@ -16,7 +16,7 @@ const processSigninResponse = async (userManager, navigate, dispatch) => { navigate(autoRedirectPath); } catch (error) { console.error("Sign-in response error:", error); - posthog?.captureException(error); + posthog?.captureException?.(error); navigate("/"); } }; diff --git a/src/helpers/FacetFilterHelpers.js b/src/helpers/FacetFilterHelpers.js index d40b6a64..8cfa3367 100644 --- a/src/helpers/FacetFilterHelpers.js +++ b/src/helpers/FacetFilterHelpers.js @@ -16,6 +16,66 @@ const getChildFacetsName = (facet, facetField, options = {}) => { return queryString; }; +const getFacetParentChain = (facet) => { + const parents = []; + let currentParent = facet?.parent; + + while (currentParent?.facet) { + parents.unshift(currentParent.facet); + currentParent = currentParent.facet?.parent; + } + + return parents; +}; + +const createSelectedFacetNode = (facet, facets) => { + return { + Name: facet.Name, + NameTranslated: facet.NameTranslated, + Count: facet.Count, + ...(facets ? { facets } : {}) + }; +}; + +const getNextSelectedFacetState = (selectedFacets = {}, facetField, options = {}) => { + const { createIfMissing = false } = options; + const nextSelectedFacets = { ...selectedFacets }; + const existingFacetSelection = selectedFacets[facetField]; + + if (!existingFacetSelection) { + if (!createIfMissing) { + return { + nextSelectedFacets, + facetSelection: null + }; + } + + const facetSelection = { + facetField, + facets: {} + }; + + nextSelectedFacets[facetField] = facetSelection; + + return { + nextSelectedFacets, + facetSelection + }; + } + + const facetSelection = { + ...existingFacetSelection, + facets: existingFacetSelection.facets ? { ...existingFacetSelection.facets } : {} + }; + + nextSelectedFacets[facetField] = facetSelection; + + return { + nextSelectedFacets, + facetSelection + }; +}; + export const getActiveFiltersFromSelectedFacets = (selectedFacets = {}) => { const activeFilters = []; @@ -38,6 +98,81 @@ export const getActiveFiltersFromSelectedFacets = (selectedFacets = {}) => { return activeFilters; }; +export const getNextSelectedFacetsFromFacetToggle = (selectedFacets = {}, facetField, facet, action) => { + const parentChain = getFacetParentChain(facet); + + if (action === 'add') { + const { nextSelectedFacets, facetSelection } = getNextSelectedFacetState(selectedFacets, facetField, { createIfMissing: true }); + let currentFacets = facetSelection.facets; + + parentChain.forEach((parentFacet) => { + const existingParentNode = currentFacets[parentFacet.Name]; + + currentFacets[parentFacet.Name] = existingParentNode + ? { + ...existingParentNode, + facets: existingParentNode.facets ? { ...existingParentNode.facets } : {} + } + : createSelectedFacetNode(parentFacet, {}); + + currentFacets = currentFacets[parentFacet.Name].facets; + }); + + currentFacets[facet.Name] = createSelectedFacetNode(facet, currentFacets[facet.Name]?.facets); + + return nextSelectedFacets; + } + + if (action === 'remove') { + const { nextSelectedFacets, facetSelection } = getNextSelectedFacetState(selectedFacets, facetField); + + if (!facetSelection) { + return nextSelectedFacets; + } + + const facetContainers = [facetSelection.facets]; + let currentFacets = facetSelection.facets; + + for (const parentFacet of parentChain) { + const existingParentNode = currentFacets[parentFacet.Name]; + + if (!existingParentNode?.facets) { + return nextSelectedFacets; + } + + currentFacets[parentFacet.Name] = { + ...existingParentNode, + facets: { ...existingParentNode.facets } + }; + + currentFacets = currentFacets[parentFacet.Name].facets; + facetContainers.push(currentFacets); + } + + delete currentFacets[facet.Name]; + + for (let index = parentChain.length - 1; index >= 0; index -= 1) { + const parentContainer = facetContainers[index]; + const parentFacetName = parentChain[index].Name; + const parentNode = parentContainer[parentFacetName]; + + if (parentNode?.facets && !Object.keys(parentNode.facets).length) { + delete parentContainer[parentFacetName]; + } else { + break; + } + } + + if (!Object.keys(facetSelection.facets).length) { + delete nextSelectedFacets[facetField]; + } + + return nextSelectedFacets; + } + + return selectedFacets; +}; + export const getQueryStringFromFacets = (selectedFacets = {}, searchString, options = {}) => { let queryStringFromFacets = searchString ? `?text=${searchString}` : ''; From 18a3d920e1381d077d7d5f50616fe4cd7cf7bd9e Mon Sep 17 00:00:00 2001 From: Yrjan Fraschetti Date: Mon, 23 Mar 2026 14:10:04 +0100 Subject: [PATCH 4/6] Harden CookieYes consent parsing and clean up consent context --- src/utils/consentContext.js | 40 ++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/utils/consentContext.js b/src/utils/consentContext.js index 38d94c30..6848dc93 100644 --- a/src/utils/consentContext.js +++ b/src/utils/consentContext.js @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useState, useRef } from "react"; +import { createContext, useEffect, useState, useRef } from "react"; import posthog from "posthog-js"; import { PostHogErrorBoundary, PostHogProvider } from "@posthog/react"; import { getConfig } from "utils/runtimeConfig"; @@ -11,6 +11,19 @@ const defaultConsent = { }; const ConsentContext = createContext(defaultConsent); +const consentCategoryKeys = Object.keys(defaultConsent); + +const normalizeConsent = ({ categories = {}, accepted } = {}) => { + const acceptedValues = Array.isArray(accepted) ? accepted : null; + + return consentCategoryKeys.reduce((normalizedConsent, categoryKey) => { + normalizedConsent[categoryKey] = acceptedValues + ? acceptedValues.includes(categoryKey) + : !!categories?.[categoryKey]; + + return normalizedConsent; + }, { ...defaultConsent }); +}; const getInitialConsent = () => { if (typeof window === "undefined" || !window.getCkyConsent) { @@ -23,12 +36,7 @@ const getInitialConsent = () => { return defaultConsent; } - return { - analytics: !!existingConsent.categories?.analytics, - functional: !!existingConsent.categories?.functional, - performance: !!existingConsent.categories?.performance, - advertisement: !!existingConsent.categories?.advertisement, - }; + return normalizeConsent({ categories: existingConsent.categories }); }; export function ConsentProvider({ children }) { @@ -74,23 +82,11 @@ export function ConsentProvider({ children }) { useEffect(() => { function handleBannerLoad(event) { - const { categories } = event.detail; - setConsent({ - analytics: categories.analytics, - functional: categories.functional, - performance: categories.performance, - advertisement: categories.advertisement, - }); + setConsent(normalizeConsent({ categories: event?.detail?.categories })); } function handleConsentUpdate(event) { - const { accepted } = event.detail; - setConsent({ - analytics: accepted.includes("analytics"), - functional: accepted.includes("functional"), - performance: accepted.includes("performance"), - advertisement: accepted.includes("advertisement"), - }); + setConsent(normalizeConsent({ accepted: event?.detail?.accepted })); } document.addEventListener("cookieyes_banner_load", handleBannerLoad); @@ -113,5 +109,3 @@ export function ConsentProvider({ children }) { ); } - -export const useConsent = () => useContext(ConsentContext); \ No newline at end of file From 31e49fa2bc754c70207af1970146b9b9b4abb990 Mon Sep 17 00:00:00 2001 From: Yrjan Fraschetti Date: Mon, 23 Mar 2026 14:16:07 +0100 Subject: [PATCH 5/6] Remove changes to FacetFilterAction and Facets in pushToDataLayer, as these are used by tag manager --- src/actions/FacetFilterActions.js | 10 ++++------ src/components/partials/FacetFilter/Facet.js | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/actions/FacetFilterActions.js b/src/actions/FacetFilterActions.js index 6d503a5a..00311455 100644 --- a/src/actions/FacetFilterActions.js +++ b/src/actions/FacetFilterActions.js @@ -23,19 +23,17 @@ export const updateSelectedFacets = (facets) => (dispatch) => { const addSelectedChildFacetsToAnalytics = (facet, facetType) => (dispatch) => { dispatch( pushToDataLayer({ - event: "selected_facets_updated", + event: "updateSelectedFacets", category: "facets", - activity: "facet_type_toggled", - action: "add", + activity: "addFacetType", facet: facetType }) ); dispatch( pushToDataLayer({ - event: "selected_facets_updated", + event: "updateSelectedFacets", category: "facets", - activity: "facet_toggled", - action: "add", + activity: "addFacet", facet: facet }) ); diff --git a/src/components/partials/FacetFilter/Facet.js b/src/components/partials/FacetFilter/Facet.js index 61386f2c..ce15202f 100644 --- a/src/components/partials/FacetFilter/Facet.js +++ b/src/components/partials/FacetFilter/Facet.js @@ -133,19 +133,17 @@ const Facet = (props) => { dispatch( pushToDataLayer({ - event: "selected_facets_updated", + event: "updateSelectedFacets", category: "facets", - activity: "facet_type_toggled", - action, + activity: "addFacetType", facet: { NameTranslated: props.facetFieldNameTranslated } }) ); dispatch( pushToDataLayer({ - event: "selected_facets_updated", + event: "updateSelectedFacets", category: "facets", - activity: "facet_toggled", - action, + activity: "addFacet", facet: props.facet }) ); From 06bd12a996da5f8bf1ac2989920abe68391caa9b Mon Sep 17 00:00:00 2001 From: Yrjan Fraschetti Date: Tue, 24 Mar 2026 08:42:29 +0100 Subject: [PATCH 6/6] Add posthog capture for csv-link click --- src/components/partials/SearchResults.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/partials/SearchResults.js b/src/components/partials/SearchResults.js index 7a6997d0..80eb6c17 100644 --- a/src/components/partials/SearchResults.js +++ b/src/components/partials/SearchResults.js @@ -82,6 +82,14 @@ export const SearchResults = ({ searchData, searchResultsType }) => { }); }; + const handleCsvDownloadClick = () => { + posthog?.capture("search_results_csv_clicked", { + results_type: searchResultsType, + search_string: searchData?.searchString, + csv_url: downloadAsCsvUrl(), + }); + }; + const renderShowMoreLink = () => { return (
@@ -107,7 +115,7 @@ export const SearchResults = ({ searchData, searchResultsType }) => {