diff --git a/infra/roles/camino/templates/env b/infra/roles/camino/templates/env index 947d7ce27c9be8cb1548a7478c44d6e4bf5fa329..7d428bb0fd87960e6b1c99ca41b828978cf8c50b 100644 --- a/infra/roles/camino/templates/env +++ b/infra/roles/camino/templates/env @@ -74,9 +74,7 @@ API_INSEE_SECRET={{ api_insee_secret }} API_INSEE_URL="https://api.insee.fr" # API Matomo -API_MATOMO_URL=https://stats.data.gouv.fr -API_MATOMO_ID=70 -API_MATOMO_TOKEN={{ matomo_token }} -API_MATOMO_MONTHS=24 +API_MATOMO_URL="http://audience-sites.din.developpement-durable.gouv.fr" +API_MATOMO_ID=1667 API_SENTRY_URL={{ api_sentry_url }} \ No newline at end of file diff --git a/knip.ts b/knip.ts index bf216f5b599ed1424a5f1a8a6350f176f821ef05..f0ee1c35149a0b5b6a14e2716f364c4f36889a36 100644 --- a/knip.ts +++ b/knip.ts @@ -31,7 +31,7 @@ const config = { "project": "**/*.ts", ignoreDependencies: [ // TODO 2023-12-28 ces dépendances semblent être "shadow" par les définitions bourrines .d.ts qu'on a mise - "graphql-fields", "graphql-scalars", "html-to-text", "matomo-tracker", + "graphql-fields", "graphql-scalars", "html-to-text", "@vitest/coverage-v8", "@pgtyped/cli" ] }, diff --git a/package-lock.json b/package-lock.json index f47dfcedf05050f9833449fdefa598dd7572ad73..0fdb362172cba2a88caa0b8d793f867a7972235f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19447,14 +19447,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/matomo-tracker": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/matomo-tracker/-/matomo-tracker-2.2.4.tgz", - "integrity": "sha512-7fDy4wRhDQ1dnSxVqmnVqmmos9ACKag0fCBtBD3/Qeoqks7MFqXcO35nfS6S8xU/IXNf7534q/4Gx8fuWKYW6A==", - "engines": { - "node": ">=4.2.0" - } - }, "node_modules/md5-hex": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", @@ -29916,7 +29908,6 @@ "jsonwebtoken": "^9.0.0", "jszip": "^3.10.1", "knex": "^2.4.2", - "matomo-tracker": "^2.2.4", "node-mailjet": "^6.0.2", "objection": "github:Vincit/objection.js#dbb20aebaac2059149ec18386283b3cce4a3d7f0", "pg": "^8.11.0", @@ -42622,7 +42613,6 @@ "jsonwebtoken": "^9.0.0", "jszip": "^3.10.1", "knex": "^2.4.2", - "matomo-tracker": "^2.2.4", "node-mailjet": "^6.0.2", "objection": "github:Vincit/objection.js#dbb20aebaac2059149ec18386283b3cce4a3d7f0", "pg": "^8.11.0", @@ -50193,11 +50183,6 @@ } } }, - "matomo-tracker": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/matomo-tracker/-/matomo-tracker-2.2.4.tgz", - "integrity": "sha512-7fDy4wRhDQ1dnSxVqmnVqmmos9ACKag0fCBtBD3/Qeoqks7MFqXcO35nfS6S8xU/IXNf7534q/4Gx8fuWKYW6A==" - }, "md5-hex": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", diff --git a/packages/api/package.json b/packages/api/package.json index 29c994d97098ab8548c380e20f7676aa3b919a93..4e9ecfee54e8774c52073f21cdacea183f341df8 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -79,7 +79,6 @@ "jsonwebtoken": "^9.0.0", "jszip": "^3.10.1", "knex": "^2.4.2", - "matomo-tracker": "^2.2.4", "node-mailjet": "^6.0.2", "objection": "github:Vincit/objection.js#dbb20aebaac2059149ec18386283b3cce4a3d7f0", "pg": "^8.11.0", diff --git a/packages/api/src/@types/matomo-tracker.d.ts b/packages/api/src/@types/matomo-tracker.d.ts deleted file mode 100644 index 67cd699d5419c096a8956bb5411af5ad0e28b29f..0000000000000000000000000000000000000000 --- a/packages/api/src/@types/matomo-tracker.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'matomo-tracker' diff --git a/packages/api/src/api/graphql/resolvers/statistiques.ts b/packages/api/src/api/graphql/resolvers/statistiques.ts index fd9720deeab72b7e71a888918541c1c3e8c8c1d3..30f23576f44cf9fcbb78fb85690d3af4caef1b1a 100644 --- a/packages/api/src/api/graphql/resolvers/statistiques.ts +++ b/packages/api/src/api/graphql/resolvers/statistiques.ts @@ -1,31 +1,26 @@ -import { ITitre } from '../../../types.js' +import { Context, ITitre } from '../../../types.js' import { titreEtapePropFind } from '../../../business/rules/titre-etape-prop-find.js' import { titreValideCheck } from '../../../business/utils/titre-valide-check.js' import { titresActivitesGet } from '../../../database/queries/titres-activites.js' import { userSuper } from '../../../database/user-super.js' -import { matomoData } from '../../../tools/api-matomo/index.js' import { Statistiques } from 'camino-common/src/statistiques.js' import { TitreTypeId } from 'camino-common/src/static/titresTypes.js' import { DEMARCHES_TYPES_IDS } from 'camino-common/src/static/demarchesTypes.js' import { ACTIVITES_STATUTS_IDS } from 'camino-common/src/static/activitesStatuts.js' import { getAnnee, toCaminoDate } from 'camino-common/src/date.js' import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools.js' +import { getTitresModifiesByMonth } from '../../rest/journal.queries.js' const ACTIVITE_ANNEE_DEBUT = 2018 -export const statistiquesGlobales = async (): Promise<Statistiques> => { +export const statistiquesGlobales = async (_: unknown, { pool }: Context): Promise<Statistiques> => { try { const titresActivites = await titresActivitesGet({}, {}, userSuper) - const titresActivitesDepose = titresActivites.filter(titreActivite => titreActivite.annee >= ACTIVITE_ANNEE_DEBUT && titreActivite.activiteStatutId === ACTIVITES_STATUTS_IDS.DEPOSE).length - const titresActivitesBeneficesEntreprise = Math.round((titresActivitesDepose * 2) / 7) - const titresActivitesBeneficesAdministration = Math.round((titresActivitesDepose * 1) / 7) - - const { recherches, titresModifies, actions, sessionDuree, telechargements, signalements, reutilisations } = await matomoData() - + const titresModifies = await getTitresModifiesByMonth(pool) const demarches = titresActivites.filter(titreActivite => { const dateSaisie = titreActivite.dateSaisie @@ -35,14 +30,8 @@ export const statistiquesGlobales = async (): Promise<Statistiques> => { return { titresActivitesBeneficesEntreprise, titresActivitesBeneficesAdministration, - recherches, titresModifies, - actions, - sessionDuree, - telechargements, demarches, - signalements, - reutilisations, } } catch (e) { console.error(e) diff --git a/packages/api/src/api/rest/index.ts b/packages/api/src/api/rest/index.ts index 4694a2c3a522481dc10ae0f418b51055bc892580..a33e23169c7ee745b511121f7e80e6fa27960a55 100644 --- a/packages/api/src/api/rest/index.ts +++ b/packages/api/src/api/rest/index.ts @@ -16,7 +16,6 @@ import { titresDemarchesFormatTable } from './format/titres-demarches.js' import { titresActivitesFormatTable } from './format/titres-activites.js' import { entreprisesFormatTable } from './format/entreprises.js' -import { matomo } from '../../tools/matomo.js' import { User } from 'camino-common/src/roles.js' import { CaminoFiltre, @@ -201,30 +200,6 @@ export const titres = exhaustiveCheck(params.format) } - if (isNotNullNorUndefined(matomo)) { - const url = Object.entries({ - format: params.format, - ordre: params.ordre, - colonne: params.colonne, - typesIds: params.typesIds, - domainesIds: params.domainesIds, - statutsIds: params.statutsIds, - substancesIds: params.substancesIds, - titresIds: params.titresIds, - entreprisesIds: params.entreprisesIds, - references: params.references, - }) - .filter(param => param[1] !== undefined) - .map(param => param.join('=')) - .join('&') - - matomo.track({ - url: `${process.env.API_MATOMO_URL}/matomo.php?${url}`, - e_c: 'camino-api', - e_a: `titres-flux-${params.format}`, - }) - } - return isNotNullNorUndefined(contenu) ? { nom: fileNameCreate(`titres-${titres.length}`, params.format), diff --git a/packages/api/src/api/rest/journal.queries.ts b/packages/api/src/api/rest/journal.queries.ts index fdba6a793a418d2cc8ea19e1a3c051353ee41559..e70e2539d59b58e307244efb65b25a438adb79fb 100644 --- a/packages/api/src/api/rest/journal.queries.ts +++ b/packages/api/src/api/rest/journal.queries.ts @@ -2,10 +2,11 @@ import { sql } from '@pgtyped/runtime' import { TitreId } from 'camino-common/src/validators/titres.js' import { Redefine, dbQueryAndValidate } from '../../pg-database.js' -import { IGetLastJournalInternalQuery } from './journal.queries.types.js' +import { IGetLastJournalInternalQuery, IGetTitresModifiesByMonthDbQuery } from './journal.queries.types.js' import { CaminoDate, caminoDateValidator } from 'camino-common/src/date.js' import { z } from 'zod' import { Pool } from 'pg' +import { QuantiteParMois, quantitesParMoisValidator } from 'camino-common/src/statistiques.js' const lastJournalGetValidator = z.object({ date: caminoDateValidator }) type LastJournalGet = z.infer<typeof lastJournalGetValidator> @@ -27,3 +28,21 @@ order by date desc LIMIT 1 ` + +export const getTitresModifiesByMonth = async (pool: Pool): Promise<QuantiteParMois[]> => { + return dbQueryAndValidate(getTitresModifiesByMonthDb, undefined, pool, quantitesParMoisValidator) +} + +const getTitresModifiesByMonthDb = sql<Redefine<IGetTitresModifiesByMonthDbQuery, void, QuantiteParMois>>` +select + concat(date_part('year', date), '-', to_char(date_part('month', date), 'fm00')) AS mois, + count(titre_id) as quantite +from + journaux +where + utilisateur_id != 'super' +group by + mois +order by + mois +` diff --git a/packages/api/src/api/rest/journal.queries.types.ts b/packages/api/src/api/rest/journal.queries.types.ts index ae2af49a878b2a7ed30b7fee957d8d2c5e061d62..651d1cc76f018fd63aa333ae94c8cbd29d700491 100644 --- a/packages/api/src/api/rest/journal.queries.types.ts +++ b/packages/api/src/api/rest/journal.queries.types.ts @@ -16,3 +16,18 @@ export interface IGetLastJournalInternalQuery { result: IGetLastJournalInternalResult; } +/** 'GetTitresModifiesByMonthDb' parameters type */ +export type IGetTitresModifiesByMonthDbParams = void; + +/** 'GetTitresModifiesByMonthDb' return type */ +export interface IGetTitresModifiesByMonthDbResult { + mois: string | null; + quantite: string | null; +} + +/** 'GetTitresModifiesByMonthDb' query type */ +export interface IGetTitresModifiesByMonthDbQuery { + params: IGetTitresModifiesByMonthDbParams; + result: IGetTitresModifiesByMonthDbResult; +} + diff --git a/packages/api/src/api/rest/journal.test.integration.ts b/packages/api/src/api/rest/journal.test.integration.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d762c071ee659b16cde662dc8884bfae16bccfe --- /dev/null +++ b/packages/api/src/api/rest/journal.test.integration.ts @@ -0,0 +1,86 @@ +/* eslint-disable sql/no-unsafe-query */ +import { dbManager } from '../../../tests/db-manager.js' +import { titreCreate } from '../../database/queries/titres.js' +import { afterAll, beforeAll, test, expect, vi, describe } from 'vitest' +import type { Pool } from 'pg' +import { getTitresModifiesByMonth } from './journal.queries.js' +import { Knex } from 'knex' +import { idGenerate } from '../../database/models/_format/id-create.js' +import { userGenerate } from '../../../tests/_utils/index.js' +import { ITitre } from '../../types.js' + +console.info = vi.fn() +console.error = vi.fn() + +let dbPool: Pool +let knex: Knex + +let titre: ITitre +beforeAll(async () => { + const { pool, knex: knexInstance } = await dbManager.populateDb() + dbPool = pool + knex = knexInstance + + titre = await titreCreate( + { + nom: 'nomTitre', + typeId: 'arm', + titreStatutId: 'val', + propsTitreEtapesIds: {}, + }, + {} + ) +}) + +afterAll(async () => { + await dbManager.closeKnex() +}) + +describe('getTitresModifiesByMonth', async () => { + test('ne prend pas en compte les modifications réalisées par l’utilisateur super', async () => { + let tested = await getTitresModifiesByMonth(dbPool) + + await knex.raw( + `INSERT INTO public.journaux (id, utilisateur_id, date, element_id, operation, titre_id) VALUES ('${idGenerate()}', 'super', '2021-11-10 09:02:19.012000 +00:00', '${idGenerate()}', 'update', '${ + titre.id + }')` + ) + tested = await getTitresModifiesByMonth(dbPool) + expect(tested).toMatchInlineSnapshot('[]') + }) + + test('comptabilise chaque modifications sur chaque titres', async () => { + let tested = await getTitresModifiesByMonth(dbPool) + + const user = await userGenerate({ role: 'defaut' }) + await knex.raw( + `INSERT INTO public.journaux (id, utilisateur_id, date, element_id, operation, titre_id) VALUES ('${idGenerate()}', '${ + user.id + }', '2021-11-10 09:02:19.012000 +00:00', '${idGenerate()}', 'update', '${titre.id}')` + ) + tested = await getTitresModifiesByMonth(dbPool) + expect(tested).toMatchInlineSnapshot(` + [ + { + "mois": "2021-11", + "quantite": 1, + }, + ] + `) + + await knex.raw( + `INSERT INTO public.journaux (id, utilisateur_id, date, element_id, operation, titre_id) VALUES ('${idGenerate()}', '${ + user.id + }', '2021-11-10 09:02:19.012000 +00:00', '${idGenerate()}', 'update', '${titre.id}')` + ) + tested = await getTitresModifiesByMonth(dbPool) + expect(tested).toMatchInlineSnapshot(` + [ + { + "mois": "2021-11", + "quantite": 2, + }, + ] + `) + }) +}) diff --git a/packages/api/src/database/models/caches.ts b/packages/api/src/database/models/caches.ts deleted file mode 100644 index 4c17e6149127df47b9a8e5d1abb2017e8f5394dc..0000000000000000000000000000000000000000 --- a/packages/api/src/database/models/caches.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Model } from 'objection' - -import { ICache } from '../../types.js' - -interface Caches extends ICache {} - -class Caches extends Model { - public static tableName = 'caches' - - public static jsonSchema = { - type: 'object', - required: ['id'], - - properties: { - id: { type: 'string', maxLength: 128 }, - valeur: { type: 'object' }, - }, - } -} - -export { Caches } diff --git a/packages/api/src/database/queries/caches.ts b/packages/api/src/database/queries/caches.ts deleted file mode 100644 index 7cfafa9f3a67ed91f17a2ff63d957fee2a58733e..0000000000000000000000000000000000000000 --- a/packages/api/src/database/queries/caches.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ICache, ICacheId } from '../../types.js' - -import { Caches } from '../models/caches.js' - -const cacheGet = async (id: ICacheId) => Caches.query().findById(id) - -const cacheUpsert = async (cache: ICache) => Caches.query().upsertGraph(cache, { insertMissing: true }) - -export { cacheGet, cacheUpsert } diff --git a/packages/api/src/knex/migrations/20240219143213_delete_cache.ts b/packages/api/src/knex/migrations/20240219143213_delete_cache.ts new file mode 100644 index 0000000000000000000000000000000000000000..6eb71d1034b5cf24fb03781934a53a797b6a3a59 --- /dev/null +++ b/packages/api/src/knex/migrations/20240219143213_delete_cache.ts @@ -0,0 +1,7 @@ +import { Knex } from 'knex' + +export const up = async (knex: Knex) => { + return knex.schema.dropTable('caches') +} + +export const down = () => ({}) diff --git a/packages/api/src/scripts/daily.ts b/packages/api/src/scripts/daily.ts index 05357b66a10c6f4a58ceaed87855e068a0825355..9f58a40dbaefcd28f4c6f9b4b28f407f9f89d975 100644 --- a/packages/api/src/scripts/daily.ts +++ b/packages/api/src/scripts/daily.ts @@ -1,7 +1,6 @@ import '../init.js' import { daily } from '../business/daily.js' import { documentsCheck } from '../tools/documents/check.js' -import { matomoCacheInit } from '../tools/api-matomo/index.js' import { consoleOverride } from '../config/logger.js' import { mailjetSend } from '../tools/api-mailjet/emails.js' import { readFileSync, writeFileSync, createWriteStream } from 'fs' @@ -36,7 +35,6 @@ const tasks = async () => { if (process.env.CAMINO_STAGE) { await documentsClean(pool) await documentsCheck(pool) - await matomoCacheInit() } } catch (e) { console.error('Erreur durant le daily', e) diff --git a/packages/api/src/tools/api-matomo/index.test.ts b/packages/api/src/tools/api-matomo/index.test.ts deleted file mode 100644 index f860a67ee135520aa7a7567a07865b6fd5718af8..0000000000000000000000000000000000000000 --- a/packages/api/src/tools/api-matomo/index.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { timeFormat } from './index.js' -import { describe, expect, test } from 'vitest' - -describe('timeFormat', () => { - test('conversion', () => { - expect(timeFormat('9 min 47s')).toBe(9) - expect(timeFormat('47s')).toBe(0) - }) -}) diff --git a/packages/api/src/tools/api-matomo/index.ts b/packages/api/src/tools/api-matomo/index.ts deleted file mode 100644 index ff05cd6eea0e87e1e9f661f87a8079771025da8a..0000000000000000000000000000000000000000 --- a/packages/api/src/tools/api-matomo/index.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { cacheGet, cacheUpsert } from '../../database/queries/caches.js' -import { Statistiques } from 'camino-common/src/statistiques.js' - -interface IMatomoSectionData { - label: string - subtable?: { - label: string - nb_events: number - }[] - nb_events: number -} - -interface IMatomoResult { - [month: string]: { - nb_searches: number - nb_actions_per_visit: number - avg_time_on_site: string - nb_downloads: number - label: string - subtable: [] - } -} - -type IMatomoCache = Pick<Statistiques, 'recherches' | 'titresModifies' | 'actions' | 'sessionDuree' | 'telechargements' | 'signalements' | 'reutilisations'> - -const matomoMainDataGet = async (duree: number) => { - // Datas de la page 'Récapitulatif' des visites dans matomo - // url - const pathVisit = getPath('API.get', 'month', { - date: `previous${duree}`, - format_metrics: 1, - }) - - // Matomo retourne un objet dont - // les clés sont les mois, - // les valeurs sont des objets dont les clés utiles aux stats sont - // nb_searches : nombre de recherches (int) - // nb_actions_per_visit : nombre moyen d'actions (int) - // avg_time_on_site : temps de session moyen (string) - // nb_downloads : nombre de téléchargements - const response = await fetch(pathVisit) - const matomoVisitData: IMatomoResult = (await response.json()) as IMatomoResult - - // Les clés de l’objet sont les mois { "2020-09": ...,} - const monthsArray = Object.keys(matomoVisitData) - - // objet mois:nbr de recherches - const recherches = monthsArray.map(key => { - return { - mois: key, - quantite: matomoVisitData[key].nb_searches || 0, - } - }) - - // données du dernier mois - const dataCurrent = matomoVisitData[monthsArray[monthsArray.length - 1]] - - // nombre d'action du dernier mois - const actions = dataCurrent.nb_actions_per_visit ? dataCurrent.nb_actions_per_visit : 0 - // temps de session du dernier mois - const sessionDuree = timeFormat(dataCurrent.avg_time_on_site) - // nombre de téléchargements du dernier mois - const telechargements = dataCurrent.nb_downloads ? dataCurrent.nb_downloads : 0 - - return { recherches, actions, sessionDuree, telechargements } -} - -const nbEventsByLabelGet = async (label: string): Promise<number> => { - const tomorrowDate = new Date() - tomorrowDate.setDate(tomorrowDate.getDate() + 1) - - // ces évenements ont débuté à partir de 2020 - const pathVisit = getPath('Events.getAction', 'range', { - date: `2020-01-01,${tomorrowDate.toISOString().slice(0, 10)}`, - label, - }) - - const response = await fetch(pathVisit) - const matomoVisitData: IMatomoSectionData[] = (await response.json()) as IMatomoSectionData[] - - return matomoVisitData.length > 0 ? matomoVisitData[0].nb_events : 0 -} - -const erreursSignaleesCountGet = async () => { - const nbEvents = await nbEventsByLabelGet('titre-erreur_signaler') - - // historiquement il y a déjà eu 150 erreurs signalées et 4 erreurs avec un ancien label - return 150 + 4 + nbEvents -} - -const reutilisationsCountGet = async () => { - const nbEvents = await nbEventsByLabelGet('titres-flux-geojson') - - // historiquement il y a déjà eu 6 réutilisations - return 6 + nbEvents -} - -const nbEventsBySectionGet = (monthData: IMatomoSectionData, month: string): number => { - // Datas des évènements, catégorie 'titres-sections', actions: - // titre-editer - // titre-demarche_ajouter - // titre-demarche_editer - // titre-demarche_supprimer - // titre-etape_ajouter - // titre-etape_editer - // titre-etape_supprimer - // titre-etape-doc_ajouter - // jusqu'au 01/10/2020 - // et - // titre-xxx-enregistrer - // à partir du 01/10/2020 - const toggleDate = new Date(2020, 9, 1) - - const eventOldActionsArray = [ - 'titre-editer', - 'titre-demarche_ajouter', - 'titre-demarche_editer', - 'titre-demarche_supprimer', - 'titre-etape_ajouter', - 'titre-etape_editer', - 'titre-etape_supprimer', - 'titre-etape-doc_ajouter', - ] - - // titre- | commence par : titre- - // [ ] | matche les caractères qui sont... - // a-z | ...ou bien une lettre minuscule - // - | ...ou bien un tiret - // * | 0 ou n fois - // enregistrer | termine par : enregistrer - const eventNewActionRegex = /titre-[a-z-]*enregistrer/g - - if (monthData.label === 'titre-sections') { - return monthData.subtable!.reduce( - ( - nbEventsByAction: number, - eventAction: { - label: string - nb_events: number - } - ) => { - if (Date.parse(month) < Date.parse(toggleDate.toString())) { - if (eventOldActionsArray.includes(eventAction.label)) { - nbEventsByAction += eventAction.nb_events - } - } else if (eventAction.label.match(eventNewActionRegex)) { - nbEventsByAction += eventAction.nb_events - } - - return nbEventsByAction - }, - 0 - ) - } - - return 0 -} -const titresModifiesCountGet = async (duree: number) => { - const pathVisit = getPath('Events.getCategory', 'month', { - date: `previous${duree}`, - }) - const response = await fetch(pathVisit) - const matomoVisitData: any = await response.json() - - // Retourne un tableau par mois - return Object.keys(matomoVisitData).reduce((acc: { mois: string; quantite: number }[], month) => { - const monthDataArray = matomoVisitData[month] as IMatomoSectionData[] - - if (!monthDataArray) { - acc.push({ mois: month, quantite: 0 }) - - return acc - } - - const nbEvents = monthDataArray.reduce((nbEventsByMonth, monthData) => { - const nbEventsBySection = nbEventsBySectionGet(monthData, month) - - return nbEventsByMonth + nbEventsBySection - }, 0) - - acc.push({ mois: month, quantite: nbEvents }) - - return acc - }, []) -} - -export const matomoCacheInit = async () => { - console.info() - console.info('- - -') - console.info('rafraichissement du cache Matomo…') - // nombre de mois pour lesquels on souhaite des stats - const duree = 12 - - const matomoResults = await Promise.all([matomoMainDataGet(duree), erreursSignaleesCountGet(), reutilisationsCountGet(), titresModifiesCountGet(duree)]) - const { recherches, actions, sessionDuree, telechargements } = matomoResults[0] - - // nombre d'erreurs signalées - const signalements = matomoResults[1] - - // nombre de réutilisations - const reutilisations = matomoResults[2] - - const titresModifies = matomoResults[3] - - const matomoCacheValue: IMatomoCache = { - recherches, - titresModifies, - actions, - sessionDuree, - telechargements, - signalements, - reutilisations, - } - - await cacheUpsert({ - id: 'matomo', - valeur: matomoCacheValue, - }) - - return matomoCacheValue -} - -export const matomoData = async () => { - const matomoCache = await cacheGet('matomo') - - if (!matomoCache) { - return matomoCacheInit() - } - - return matomoCache.valeur as IMatomoCache -} - -export const timeFormat = (time: string) => { - // si le temps ne présente que des secondes, l'afficher ainsi - // sinon, ne garder que les minutes - const index = time.search('min') - - return index === -1 ? 0 : Number.parseInt(time.substring(0, index).replace(' ', ''), 10) -} - -const getPath = (method: string, period: string, params?: { [param: string]: string | number }) => { - const _params = params ? Object.entries(params).reduce((acc, param) => (acc = `${acc}&${param[0]}=${param[1]}`), '') : '' - - return `${process.env.API_MATOMO_URL}/index.php?expanded=1${_params}&filter_limit=-1&format=JSON&idSite=${process.env.API_MATOMO_ID}&method=${method}&module=API&period=${period}&token_auth=${process.env.API_MATOMO_TOKEN}` -} diff --git a/packages/api/src/tools/database-to-json/tables.ts b/packages/api/src/tools/database-to-json/tables.ts index 36048a6ab539cc300571a2e454fff0633909052b..6cc271262b710263e8a6cb12f292b1fac558abed 100644 --- a/packages/api/src/tools/database-to-json/tables.ts +++ b/packages/api/src/tools/database-to-json/tables.ts @@ -1,7 +1,5 @@ // Liste des noms des tables à sauvegarder au format json export const tables = [ - // la table 'caches' n'est pas utile dans les json - // { name: 'caches',orderBy: ['id'] }, { name: 'communes', orderBy: ['id'] }, { name: 'documents', orderBy: ['id'] }, { name: 'entreprises', orderBy: ['id'] }, diff --git a/packages/api/src/tools/matomo.ts b/packages/api/src/tools/matomo.ts deleted file mode 100644 index cb074879ae1c2466c36f13612dc469939fc3c16a..0000000000000000000000000000000000000000 --- a/packages/api/src/tools/matomo.ts +++ /dev/null @@ -1,6 +0,0 @@ -import MatomoTracker from 'matomo-tracker' - -export const matomo = - process.env.API_MATOMO_ID && Number(process.env.API_MATOMO_ID) && process.env.API_MATOMO_URL - ? new MatomoTracker(Number(process.env.API_MATOMO_ID), `${process.env.API_MATOMO_URL}/matomo.php`, false) - : null diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index b8ea6513481384d6de95ac51614b96bceb34d0b0..c7bd0bab571af2b6a2e4fe6defd6a1513a00b203 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -340,13 +340,6 @@ interface ITitreEtapeFiltre { dateFin?: string } -type ICacheId = 'matomo' - -interface ICache { - id: ICacheId - valeur: any -} - interface ITitreType { id: TitreTypeId domaineId: DomaineId @@ -482,8 +475,6 @@ export { IPropsTitreEtapesIds, IHeritageProps, IHeritageContenu, - ICache, - ICacheId, IJournaux, IDecisionAnnexeContenu, } diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 0c15d560565ed0bbfa26e54a5f7c0454204764f4..3f119945251a3eb5aaf6a5b4a452ab4268f5f89f 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -22,8 +22,7 @@ "noEmit": true, "paths": { "graphql-fields": ["./src/@types/graphql-fields"], - "html-to-text": ["./src/@types/html-to-text"], - "matomo-tracker": ["./src/@types/matomo-tracker"] + "html-to-text": ["./src/@types/html-to-text"] }, } } diff --git a/packages/common/src/statistiques.ts b/packages/common/src/statistiques.ts index 42c555a70649d82a185f6d3dbb95bced3c5dcc86..95f20509fb01a7071feca74109ea6788dcbb3c62 100644 --- a/packages/common/src/statistiques.ts +++ b/packages/common/src/statistiques.ts @@ -7,22 +7,21 @@ import { SUBSTANCES_FISCALES_IDS, SubstanceFiscaleId } from './static/substances import { TitresTypes } from './static/titresTypes.js' import { CaminoStatistiquesDataGouvId } from './static/statistiques.js' -export interface QuantiteParMois { - mois: string - quantite: number -} +export const yearMonthValidator = z + .string() + .regex(/^\d{4}-\d{2}$/) + .brand<'Year-Month'>() +export const quantitesParMoisValidator = z.object({ + mois: yearMonthValidator, + quantite: z.coerce.number(), +}) +export type QuantiteParMois = z.infer<typeof quantitesParMoisValidator> export interface Statistiques { titresActivitesBeneficesEntreprise: number titresActivitesBeneficesAdministration: number - recherches: QuantiteParMois[] - titresModifies: QuantiteParMois[] - actions: number - sessionDuree: number - telechargements: number demarches: number - signalements: number - reutilisations: number + titresModifies: QuantiteParMois[] } export const substancesFiscalesStats = [ diff --git a/packages/ui/src/app.tsx b/packages/ui/src/app.tsx index 0de794dc01707541a1ad00f2b8bc015ed1db755a..d746e63968154280a774665be19d8fec2488e5e6 100644 --- a/packages/ui/src/app.tsx +++ b/packages/ui/src/app.tsx @@ -2,7 +2,7 @@ import '@gouvfr/dsfr/dist/core/core.module' import '@gouvfr/dsfr/dist/component/navigation/navigation.module' import '@gouvfr/dsfr/dist/component/tab/tab.module' -import { defineComponent, Transition, computed, inject } from 'vue' +import { defineComponent, Transition, computed } from 'vue' import { Messages } from './components/_ui/messages' import { Header } from './components/page/header' import { Footer } from './components/page/footer' @@ -12,12 +12,10 @@ import { IconSprite } from './components/_ui/iconSprite' import { CaminoError } from './components/error' import { useStore } from 'vuex' import { RouterView, useRoute } from 'vue-router' -import { TrackEventFunction } from '@/utils/matomo' import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools' export const App = defineComponent({ setup: () => { const store = useStore() - const matomo = inject<{ trackEvent: TrackEventFunction } | null>('matomo', null) const route = useRoute() const user = computed(() => store.state.user.element) @@ -36,26 +34,12 @@ export const App = defineComponent({ const currentMenuSection = computed(() => route.meta?.menuSection) - if (matomo) { - // @ts-ignore - matomo.customVariableVisitUser(user) - // @ts-ignore - matomo.trackPageView() - } - - // TODO 2023-03-16 typer l’instance matomo dans un .d.ts - const trackEvent: TrackEventFunction = (segment, subSegment, event) => { - if (matomo) { - matomo.trackEvent(segment, subSegment, event) - } - } - return () => ( <div class="page relative"> <MapPattern /> <IconSprite /> - <Header user={user.value} currentMenuSection={currentMenuSection.value} trackEvent={trackEvent} routePath={route.fullPath} /> + <Header user={user.value} currentMenuSection={currentMenuSection.value} routePath={route.fullPath} /> <main class="main" role="main"> <div class="container">{isNotNullNorUndefined(error.value) ? <CaminoError couleur={error.value.type} message={error.value.value} /> : <>{loaded.value ? <RouterView /> : null}</>}</div> diff --git a/packages/ui/src/components/_common/downloads.tsx b/packages/ui/src/components/_common/downloads.tsx index a8216bfde31c63d3ffc094059011f1b35980c733..7f6b13420e5d015df395b26a05c2832adc1284c2 100644 --- a/packages/ui/src/components/_common/downloads.tsx +++ b/packages/ui/src/components/_common/downloads.tsx @@ -1,4 +1,4 @@ -import { inject, ref, defineComponent, HTMLAttributes, watch } from 'vue' +import { ref, defineComponent, HTMLAttributes, watch } from 'vue' import { useRoute, RouteLocationNormalized, LocationQuery } from 'vue-router' import { DsfrSelect, Item } from '../_ui/dsfr-select' import { DownloadRestRoutes, DownloadFormat, CaminoRestParams } from 'camino-common/src/rest' @@ -7,11 +7,10 @@ import { getDownloadRestRoute } from '@/api/client-rest' import { saveAs } from 'file-saver' import { DsfrButtonIcon } from '../_ui/dsfr-button' -export const Downloads = defineComponent(<T extends DownloadRestRoutes>(props: Omit<Props<T>, 'route' | 'matomo'>) => { +export const Downloads = defineComponent(<T extends DownloadRestRoutes>(props: Omit<Props<T>, 'route'>) => { const route = useRoute() - const matomo = inject('matomo', undefined) - return () => <PureDownloads {...props} route={route} matomo={matomo} /> + return () => <PureDownloads {...props} route={route} /> }) // @ts-ignore waiting for https://github.com/vuejs/core/issues/7833 @@ -24,7 +23,6 @@ export interface Props<T extends DownloadRestRoutes> { downloadRoute: T params: CaminoRestParams<T> route: Pick<RouteLocationNormalized, 'query'> - matomo?: { trackLink: (url: string, params: string) => void } downloadTitle?: string } @@ -75,16 +73,12 @@ export const PureDownloads = defineComponent(<T extends DownloadRestRoutes>(prop }) // @ts-ignore waiting for https://github.com/vuejs/core/issues/7833 -PureDownloads.props = ['formats', 'downloadRoute', 'params', 'route', 'matomo', 'id', 'downloadTitle', 'class'] +PureDownloads.props = ['formats', 'downloadRoute', 'params', 'route', 'id', 'downloadTitle', 'class'] async function download<T extends DownloadRestRoutes>(selectedFormat: DownloadFormat | null, query: LocationQuery, props: Omit<Props<T>, 'formats' | 'route'>) { if (selectedFormat !== null) { const url = getDownloadRestRoute(props.downloadRoute, props.params, { format: selectedFormat, ...query }) saveAs(url) - - if (props.matomo) { - props.matomo.trackLink(`${window.location.origin}${url}`, 'download') - } } } diff --git a/packages/ui/src/components/activite-edition.tsx b/packages/ui/src/components/activite-edition.tsx index 83ada9582efa74e90ac5a67f7c2e1a41b50b9a6a..aab9df8d67e12a51c72947a16305d4133d1259b7 100644 --- a/packages/ui/src/components/activite-edition.tsx +++ b/packages/ui/src/components/activite-edition.tsx @@ -1,6 +1,6 @@ import { ActiviteDocumentsEdit } from './activite/activite-documents-edit' import { getPeriode } from 'camino-common/src/static/frequence' -import { computed, defineComponent, inject, onBeforeUnmount, onMounted, ref } from 'vue' +import { computed, defineComponent, onBeforeUnmount, onMounted, ref } from 'vue' import { AsyncData } from '@/api/client-rest' import { Activite, ActiviteDocumentId, ActiviteId, ActiviteIdOrSlug, TempActiviteDocument, activiteIdOrSlugValidator } from 'camino-common/src/activite' import { useRouter } from 'vue-router' @@ -13,10 +13,8 @@ import { SectionsEdit } from './_common/new-sections-edit' import { DsfrButton, DsfrLink } from './_ui/dsfr-button' import { capitalize } from 'camino-common/src/strings' import { Alert } from './_ui/alert' -import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools' export const ActiviteEdition = defineComponent(() => { - const matomo = inject('matomo', null) const router = useRouter() const activiteId = computed<ActiviteIdOrSlug>(() => { @@ -25,16 +23,7 @@ export const ActiviteEdition = defineComponent(() => { return () => ( <PureActiviteEdition - apiClient={{ - ...apiClient, - updateActivite: async (activiteId, activiteTypeId, sectionsWithValue, activiteDocumentIds, newTempDocuments) => { - await apiClient.updateActivite(activiteId, activiteTypeId, sectionsWithValue, activiteDocumentIds, newTempDocuments) - if (isNotNullNorUndefined(matomo)) { - // @ts-ignore - matomo.trackEvent('activite', 'activite-enregistrer', ActivitesTypes[activiteTypeId].nom) - } - }, - }} + apiClient={apiClient} goBack={(activiteId: ActiviteId): void => { router.push({ name: 'activite', params: { activiteId } }) }} diff --git a/packages/ui/src/components/activite/depose-popup.tsx b/packages/ui/src/components/activite/depose-popup.tsx index 666f0232d3715ba293ef96d00ad45d89a6c5ef16..5a7f928ba84195dd295bf6ecf4da3e87a60ed69d 100644 --- a/packages/ui/src/components/activite/depose-popup.tsx +++ b/packages/ui/src/components/activite/depose-popup.tsx @@ -4,7 +4,6 @@ import { ActiviteApiClient } from './activite-api-client' import { Activite } from 'camino-common/src/activite' import { Alert } from '../_ui/alert' import { ActivitesTypes } from 'camino-common/src/static/activitesTypes' -import { inject } from 'vue' interface Props { close: () => void @@ -12,8 +11,6 @@ interface Props { activite: Pick<Activite, 'id' | 'titre' | 'type_id'> } export const ActiviteDeposePopup = caminoDefineComponent<Props>(['close', 'apiClient', 'activite'], props => { - const matomo = inject('matomo', null) - const content = () => ( <Alert type="warning" @@ -36,10 +33,6 @@ export const ActiviteDeposePopup = caminoDefineComponent<Props>(['close', 'apiCl validate={{ action: async () => { await props.apiClient.deposerActivite(props.activite.id) - if (matomo !== null) { - // @ts-ignore - matomo.trackEvent('titre-activite', 'titre-activite_depot', props.activite.id) - } }, text: 'Déposer', }} diff --git a/packages/ui/src/components/document/edit-popup.vue b/packages/ui/src/components/document/edit-popup.vue index 16a1d48d0a297a8d42d46b552955ca9bfc03a12f..0fd28e402cb6a1b2f0599acd1868117c032df615 100644 --- a/packages/ui/src/components/document/edit-popup.vue +++ b/packages/ui/src/components/document/edit-popup.vue @@ -127,12 +127,6 @@ export default { route: this.route, action: this.action, }) - - this.eventTrack({ - categorie: 'titre-sections', - action: 'titre-etape-doc-enregistrer', - nom: this.document.titreEtapeId, - }) }, cancel() { @@ -151,12 +145,6 @@ export default { } }, - eventTrack(event) { - if (this.$matomo) { - this.$matomo.trackEvent(event.categorie, event.action, event.nom) - } - }, - errorsRemove() {}, }, } diff --git a/packages/ui/src/components/etape-edition.vue b/packages/ui/src/components/etape-edition.vue index 0d65e27e8aabcfed01a4a52f1d0042b0e4173095..b6c31df70c30d33d166ca637a1bb03f558d392bf 100644 --- a/packages/ui/src/components/etape-edition.vue +++ b/packages/ui/src/components/etape-edition.vue @@ -261,12 +261,6 @@ export default { if (reroute) { await this.reroute(titreEtapeId) } - - this.eventTrack({ - categorie: 'titre-etape', - action: 'titre-etape-enregistrer', - nom: titreEtapeId, - }) } return titreEtapeId } @@ -285,11 +279,6 @@ export default { etapeId, onDepotDone: async () => { await this.reroute(etapeId) - this.eventTrack({ - categorie: 'titre-etape', - action: 'titre-etape_depot', - nom: this.$route.params.id, - }) }, }, }) @@ -297,12 +286,6 @@ export default { } }, - eventTrack(event) { - if (this.$matomo) { - this.$matomo.trackEvent(event.categorie, event.action, event.nom) - } - }, - keyUp(e) { if ((e.which || e.keyCode) === 13 && this.complete && !this.isPopupOpen) { if (this.dateIsVisible && this.newDate) { diff --git a/packages/ui/src/components/page/header.stories.tsx b/packages/ui/src/components/page/header.stories.tsx index 3b0d918558df843a1273b3ee82654639c5811d1c..ea36b846e594e9b8e70f0cc340d8f90eaa148f3f 100644 --- a/packages/ui/src/components/page/header.stories.tsx +++ b/packages/ui/src/components/page/header.stories.tsx @@ -26,24 +26,20 @@ const meta: Meta = { } export default meta -export const Super: StoryFn = () => <Header trackEvent={() => ({})} currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'super' }} routePath="/titres?domainesIds=['m']" /> +export const Super: StoryFn = () => <Header currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'super' }} routePath="/titres?domainesIds=['m']" /> export const AdminONF: StoryFn = () => ( - <Header trackEvent={() => ({})} currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'admin', administrationId: 'ope-onf-973-01' }} routePath="/titres?domainesIds=['m']" /> + <Header currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'admin', administrationId: 'ope-onf-973-01' }} routePath="/titres?domainesIds=['m']" /> ) export const AdminDGTM: StoryFn = () => ( - <Header trackEvent={() => ({})} currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'admin', administrationId: 'dea-guyane-01' }} routePath="/titres?domainesIds=['m']" /> + <Header currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'admin', administrationId: 'dea-guyane-01' }} routePath="/titres?domainesIds=['m']" /> ) export const Editeur: StoryFn = () => ( - <Header trackEvent={() => ({})} currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'editeur', administrationId: 'ope-onf-973-01' }} routePath="/titres?domainesIds=['m']" /> + <Header currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'editeur', administrationId: 'ope-onf-973-01' }} routePath="/titres?domainesIds=['m']" /> ) export const Lecteur: StoryFn = () => ( - <Header trackEvent={() => ({})} currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'lecteur', administrationId: 'ope-onf-973-01' }} routePath="/titres?domainesIds=['m']" /> + <Header currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'lecteur', administrationId: 'ope-onf-973-01' }} routePath="/titres?domainesIds=['m']" /> ) -export const Entreprise: StoryFn = () => ( - <Header trackEvent={() => ({})} currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'entreprise', entreprises: [] }} routePath="/titres?domainesIds=['m']" /> -) -export const BureauDEtudes: StoryFn = () => ( - <Header trackEvent={() => ({})} currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'bureau d’études', entreprises: [] }} routePath="/titres?domainesIds=['m']" /> -) -export const Defaut: StoryFn = () => <Header trackEvent={() => ({})} currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'defaut' }} routePath="/titres?domainesIds=['m']" /> -export const Deconnecte: StoryFn = () => <Header trackEvent={() => ({})} currentMenuSection={'utilisateurs'} user={undefined} routePath="/titres?domainesIds=['m']" /> +export const Entreprise: StoryFn = () => <Header currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'entreprise', entreprises: [] }} routePath="/titres?domainesIds=['m']" /> +export const BureauDEtudes: StoryFn = () => <Header currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'bureau d’études', entreprises: [] }} routePath="/titres?domainesIds=['m']" /> +export const Defaut: StoryFn = () => <Header currentMenuSection={'utilisateurs'} user={{ ...testBlankUser, role: 'defaut' }} routePath="/titres?domainesIds=['m']" /> +export const Deconnecte: StoryFn = () => <Header currentMenuSection={'utilisateurs'} user={undefined} routePath="/titres?domainesIds=['m']" /> diff --git a/packages/ui/src/components/page/header.tsx b/packages/ui/src/components/page/header.tsx index 1ac8190d502aaa36616be94f4d552a7899021a31..9be0c37e3586c248630c48a0c9e273417361b2d3 100644 --- a/packages/ui/src/components/page/header.tsx +++ b/packages/ui/src/components/page/header.tsx @@ -3,14 +3,13 @@ import { Role, User } from 'camino-common/src/roles' import { canReadActivites } from 'camino-common/src/permissions/activites' import { QuickAccessTitre } from '@/components/page/quick-access-titre' import { caminoDefineComponent } from '@/utils/vue-tsx-utils' -import { MenuSection, TrackEventFunction } from '@/utils/matomo' import { DsfrButtonIcon } from '../_ui/dsfr-button' import { isNotNullNorUndefinedNorEmpty } from 'camino-common/src/typescript-tools' +import { MenuSection } from '../../router' interface Props { user: User currentMenuSection: MenuSection | null - trackEvent: TrackEventFunction routePath: string } @@ -34,18 +33,10 @@ const isDirectLink = (link: Link | LinkList): link is Link => Object.prototype.h const ANNUAIRE = { label: 'Annuaire', sublinks: [links.ENTREPRISES, links.ADMINISTRATIONS, links.UTILISATEURS] } -const HeaderLinks: FunctionalComponent<Pick<Props, 'user' | 'trackEvent' | 'routePath'> & { userLinkClicked: () => void }> = props => { +const HeaderLinks: FunctionalComponent<Pick<Props, 'user' | 'routePath'> & { userLinkClicked: () => void }> = props => { const loginUrl = '/oauth2/sign_in?rd=' + encodeURIComponent(`${window.location.origin}${props.routePath}`) const logoutUrl = '/apiUrl/deconnecter' - const logout = () => { - props.trackEvent('menu-utilisateur', 'menu-utilisateur', 'deconnexion') - } - - const login = () => { - props.trackEvent('menu', 'bouton', 'utilisateur') - } - return ( <div class="fr-btns-group"> {props.user ? ( @@ -56,14 +47,14 @@ const HeaderLinks: FunctionalComponent<Pick<Props, 'user' | 'trackEvent' | 'rout </router-link> </div> <div> - <a class="fr-btn fr-icon-lock-line" href={logoutUrl} onClick={logout}> + <a class="fr-btn fr-icon-lock-line" href={logoutUrl}> Se déconnecter </a> </div> </> ) : ( <div> - <a class="fr-btn fr-icon-lock-fill" href={loginUrl} onClick={login}> + <a class="fr-btn fr-icon-lock-fill" href={loginUrl}> Se connecter / S’enregistrer </a> </div> @@ -72,7 +63,7 @@ const HeaderLinks: FunctionalComponent<Pick<Props, 'user' | 'trackEvent' | 'rout ) } -export const Header = caminoDefineComponent<Props>(['user', 'currentMenuSection', 'trackEvent', 'routePath'], props => { +export const Header = caminoDefineComponent<Props>(['user', 'currentMenuSection', 'routePath'], props => { const getAriaCurrent = (link: LinkList): { 'aria-current'?: true } => (link.sublinks.some(({ path }) => path === props.currentMenuSection) ? { 'aria-current': true } : {}) const getAriaPage = (link: Link): { 'aria-current'?: 'page' } => { @@ -94,15 +85,12 @@ export const Header = caminoDefineComponent<Props>(['user', 'currentMenuSection' const searchModalId = 'headerSearchModalId' const navigationId = 'headerNavigationId' - const linkClick = (path: Link['path']) => { + const linkClick = () => { // On ferme la modale modalMenuOpened.value = false - if (path !== 'journaux') { - props.trackEvent('menu-sections', 'menu-section', path) - } } - const sublinkClick = (path: Link['path']) => { + const sublinkClick = () => { // On ferme le menu déroulant d’annuaire const navigationElement = document.getElementById(navigationId) if (navigationElement) { @@ -111,7 +99,7 @@ export const Header = caminoDefineComponent<Props>(['user', 'currentMenuSection' members[0].conceal() } } - linkClick(path) + linkClick() } const linksByRole = computed<Record<Role, (Link | LinkList)[]>>(() => { const linkActivites = canReadActivites(props.user) ? [links.ACTIVITES] : [] @@ -176,7 +164,7 @@ export const Header = caminoDefineComponent<Props>(['user', 'currentMenuSection' </div> <div class="fr-header__tools"> <div class="fr-header__tools-links"> - <HeaderLinks user={props.user} trackEvent={props.trackEvent} routePath={props.routePath} userLinkClicked={closeModals} /> + <HeaderLinks user={props.user} routePath={props.routePath} userLinkClicked={closeModals} /> </div> <div class={`fr-header__search fr-modal ${modalSearchOpened.value ? 'fr-modal--opened' : ''}`} id={searchModalId} aria-labelledby="button-search" aria-label="Recherche dans le site"> <div class="fr-container"> @@ -208,14 +196,14 @@ export const Header = caminoDefineComponent<Props>(['user', 'currentMenuSection' class="fr-btn--close" /> <div class="fr-header__menu-links"> - <HeaderLinks user={props.user} trackEvent={props.trackEvent} routePath={props.routePath} userLinkClicked={closeModals} /> + <HeaderLinks user={props.user} routePath={props.routePath} userLinkClicked={closeModals} /> </div> <nav class="fr-nav" id={navigationId} role="navigation" aria-label="Menu principal"> <ul class="fr-nav__list"> {linksByRole.value[props.user?.role ?? 'defaut'].map((link, index) => ( <li key={link.label} class="fr-nav__item"> {isDirectLink(link) ? ( - <router-link class="fr-nav__link" to={{ name: link.path }} target="_self" onClick={() => linkClick(link.path)} {...getAriaPage(link)}> + <router-link class="fr-nav__link" to={{ name: link.path }} target="_self" onClick={linkClick} {...getAriaPage(link)}> {link.label} </router-link> ) : ( @@ -227,7 +215,7 @@ export const Header = caminoDefineComponent<Props>(['user', 'currentMenuSection' <ul class="fr-menu__list"> {link.sublinks.map((sublink, subIndex) => ( <li key={sublink.label}> - <router-link onClick={() => sublinkClick(sublink.path)} class="fr-nav__link" to={{ name: sublink.path }} target="_self" id={`nav-${index}-${subIndex}`}> + <router-link onClick={sublinkClick} class="fr-nav__link" to={{ name: sublink.path }} target="_self" id={`nav-${index}-${subIndex}`}> {sublink.label} </router-link> </li> diff --git a/packages/ui/src/components/page/quick-access-titre.tsx b/packages/ui/src/components/page/quick-access-titre.tsx index 79695772d962351fb70314300098cde687b8829f..9ee84c34362bec70759dd3d125deeff6b579feb1 100644 --- a/packages/ui/src/components/page/quick-access-titre.tsx +++ b/packages/ui/src/components/page/quick-access-titre.tsx @@ -3,7 +3,7 @@ import { TitresTypesTypes } from 'camino-common/src/static/titresTypesTypes' import { getDomaineId, getTitreTypeType } from 'camino-common/src/static/titresTypes' import { titresRechercherByReferences } from '@/api/titres' import { useRouter } from 'vue-router' -import { ref, inject, FunctionalComponent } from 'vue' +import { ref, FunctionalComponent } from 'vue' import { caminoDefineComponent } from '@/utils/vue-tsx-utils' import { titreApiClient, TitreForTitresRerchercherByNom } from '../titre/titre-api-client' import { capitalize } from 'camino-common/src/strings' @@ -15,7 +15,6 @@ export const QuickAccessTitre = caminoDefineComponent<{ id: string; onSelectTitr const router = useRouter() const titres = ref<TitreForTitresRerchercherByNom[]>([]) - const matomo = inject('matomo', null) const search = async (searchTerm: string): Promise<void> => { const intervalle = 10 @@ -32,10 +31,6 @@ export const QuickAccessTitre = caminoDefineComponent<{ id: string; onSelectTitr const onSelectedTitre = (titre: TitreForTitresRerchercherByNom | undefined) => { if (titre) { - if (isNotNullNorUndefined(matomo)) { - // @ts-ignore - matomo.trackEvent('navigation', 'navigation-rapide', titre.id) - } router.push({ name: 'titre', params: { id: titre.id } }) props.onSelectTitre() } diff --git a/packages/ui/src/components/statistiques.tsx b/packages/ui/src/components/statistiques.tsx index af3cef00f2f2ec6101516ef9de3491c09b8cf1ac..b8602ec951680fcede87a2a1df899344be1a0573 100644 --- a/packages/ui/src/components/statistiques.tsx +++ b/packages/ui/src/components/statistiques.tsx @@ -1,4 +1,4 @@ -import { defineComponent, inject, onMounted } from 'vue' +import { defineComponent, onMounted } from 'vue' import { Tab, Tabs } from './_ui/tabs' import { useRoute, useRouter, RouterView } from 'vue-router' import { NonEmptyArray, getEntriesHardcore } from 'camino-common/src/typescript-tools' @@ -35,14 +35,8 @@ type TabId = keyof typeof routeToTab export const Statistiques = defineComponent(() => { const route = useRoute() const router = useRouter() - const matomo = inject('matomo', null) onMounted(() => { - if (matomo) { - // @ts-ignore - matomo.trackEvent('menu-sections', 'menu-section', 'statistiques') - } - if (route.name === 'statistiques') { router.replace({ name: 'statistiques-globales' }) } diff --git a/packages/ui/src/components/statistiques/globales.stories.tsx b/packages/ui/src/components/statistiques/globales.stories.tsx index 2920f7a450503cbc4e517fbf178c7d12f98f2e15..4e06814d7a4b6576beaae3e734fb6c678ba4016a 100644 --- a/packages/ui/src/components/statistiques/globales.stories.tsx +++ b/packages/ui/src/components/statistiques/globales.stories.tsx @@ -1,3 +1,4 @@ +import { yearMonthValidator } from 'camino-common/src/statistiques' import { PureGlobales } from './globales' import { Meta, StoryFn } from '@storybook/vue3' @@ -13,40 +14,21 @@ export const DefaultNoSnapshot: StoryFn = () => ( statistiques={{ titresActivitesBeneficesEntreprise: 678, titresActivitesBeneficesAdministration: 339, - recherches: [ - { mois: '2021-05', quantite: 0 }, - { mois: '2021-06', quantite: 0 }, - { mois: '2021-07', quantite: 3657 }, - { mois: '2021-08', quantite: 2787 }, - { mois: '2021-09', quantite: 2505 }, - { mois: '2021-10', quantite: 3905 }, - { mois: '2021-11', quantite: 4644 }, - { mois: '2021-12', quantite: 3022 }, - { mois: '2022-01', quantite: 5358 }, - { mois: '2022-02', quantite: 5162 }, - { mois: '2022-03', quantite: 6769 }, - { mois: '2022-04', quantite: 2612 }, - ], titresModifies: [ - { mois: '2021-05', quantite: 0 }, - { mois: '2021-06', quantite: 54 }, - { mois: '2021-07', quantite: 320 }, - { mois: '2021-08', quantite: 90 }, - { mois: '2021-09', quantite: 152 }, - { mois: '2021-10', quantite: 259 }, - { mois: '2021-11', quantite: 107 }, - { mois: '2021-12', quantite: 310 }, - { mois: '2022-01', quantite: 178 }, - { mois: '2022-02', quantite: 189 }, - { mois: '2022-03', quantite: 223 }, - { mois: '2022-04', quantite: 147 }, + { mois: yearMonthValidator.parse('2021-05'), quantite: 0 }, + { mois: yearMonthValidator.parse('2021-06'), quantite: 54 }, + { mois: yearMonthValidator.parse('2021-07'), quantite: 320 }, + { mois: yearMonthValidator.parse('2021-08'), quantite: 90 }, + { mois: yearMonthValidator.parse('2021-09'), quantite: 152 }, + { mois: yearMonthValidator.parse('2021-10'), quantite: 259 }, + { mois: yearMonthValidator.parse('2021-11'), quantite: 107 }, + { mois: yearMonthValidator.parse('2021-12'), quantite: 310 }, + { mois: yearMonthValidator.parse('2022-01'), quantite: 178 }, + { mois: yearMonthValidator.parse('2022-02'), quantite: 189 }, + { mois: yearMonthValidator.parse('2022-03'), quantite: 223 }, + { mois: yearMonthValidator.parse('2022-04'), quantite: 147 }, ], - actions: 27.6, - sessionDuree: 8, - telechargements: 139, demarches: 366, - signalements: 352, - reutilisations: 6, "Nombre d'utilisateurs rattachés à une Autorité": 5, "Nombre d'utilisateurs rattachés à une Dréal": 38, "Nombre d'utilisateurs rattachés à une Déal": 8, diff --git a/packages/ui/src/components/statistiques/globales.tsx b/packages/ui/src/components/statistiques/globales.tsx index 52581942b89e6b10b706bfcaad84a803e0d6767c..88794fb2229193945dbec86c10fc3b4d13a1aacc 100644 --- a/packages/ui/src/components/statistiques/globales.tsx +++ b/packages/ui/src/components/statistiques/globales.tsx @@ -101,10 +101,6 @@ interface Props { } export const PureGlobales: FunctionalComponent<Props> = props => { - const recherchesStats = props.statistiques.recherches - - const recherches = recherchesStats[recherchesStats.length - 1].quantite - const utilisateursAdmin = props.statistiques["Nombre d'utilisateurs rattachés à un ministère"] + props.statistiques["Nombre d'utilisateurs rattachés à une Autorité"] + @@ -146,40 +142,6 @@ export const PureGlobales: FunctionalComponent<Props> = props => { return ( <div> - <div id="engagement" class="mb-xxl"> - <h2 class="mt">Engagement général sur le site</h2> - <span class="separator" /> - <p class="mb-xl">Les données retenues ici témoignent du comportement général des utilisateurs sur le site et de leur engagement auprès du service</p> - <div class="tablet-float-blobs clearfix"> - <div class="tablet-float-blob-1-3"> - <div class="mb-xl mt"> - <p class={['fr-display--xs', styles['donnee-importante']]}>{numberFormat(recherches)}</p> - <p class="bold text-center">recherches effectuées le mois dernier</p> - <p>Le nombre de recherches mensuelles est l'indicateur clé de l'utilisation du service de "cadastre minier"</p> - </div> - - <div class="mb-xl"> - <p class={['fr-display--xs', styles['donnee-importante']]}>{Math.round(props.statistiques.actions)}</p> - <p class="bold text-center">nombre moyen d'actions effectuées par utilisateur</p> - </div> - - <div class="mb-xl"> - <p class={['fr-display--xs', styles['donnee-importante']]}>{props.statistiques.sessionDuree} min</p> - <p class="bold text-center">temps de session moyen par utilisateur</p> - </div> - </div> - <div class="tablet-float-blob-2-3 mb-xxl"> - <ConfigurableChart - chartConfiguration={lineConfiguration( - statsLineFormat({ - stats: props.statistiques.recherches, - labelY: 'recherches', - }) - )} - /> - </div> - </div> - </div> <div id="utilisateurs" class="mb-xxl content"> <h2>Les différents profils des utilisateurs de Camino</h2> <span class="separator" /> @@ -247,20 +209,6 @@ export const PureGlobales: FunctionalComponent<Props> = props => { /> </div> </div> - <div class="desktop-blobs"> - <div class="desktop-blob-1-3 mb-xl"> - <p class={['fr-display--xs', styles['donnee-importante']]}>{props.statistiques.telechargements}</p> - <p class="bold text-center">téléchargements de pièces relatives à la bonne instruction des titres et autorisations miniers le mois dernier</p> - </div> - <div class="desktop-blob-1-3 mb-xl"> - <p class={['fr-display--xs', styles['donnee-importante']]}>{props.statistiques.signalements}</p> - <p class="bold text-center">erreurs corrigées sur les bases de données de l'État grâce à la participation des utilisateurs</p> - </div> - <div class="desktop-blob-1-3 mb-xl"> - <p class={['fr-display--xs', styles['donnee-importante']]}>{props.statistiques.reutilisations}</p> - <p class="bold text-center">réutilisations connues des données ouvertes distribuées</p> - </div> - </div> </div> <div id="gains" class="mb-xxl"> diff --git a/packages/ui/src/components/titres.tsx b/packages/ui/src/components/titres.tsx index e792a4cc0ff043409a8eca612b0cd5696df5d788..ccaccc95f4326c609de67896d99c0ad7aa6870de 100644 --- a/packages/ui/src/components/titres.tsx +++ b/packages/ui/src/components/titres.tsx @@ -1,4 +1,4 @@ -import { defineComponent, defineAsyncComponent, computed, onMounted, inject, ref } from 'vue' +import { defineComponent, defineAsyncComponent, computed, onMounted, ref } from 'vue' import { useStore } from 'vuex' import { User, isAdministration } from 'camino-common/src/roles' import { TitreFiltresParams, TitresFiltres, getInitialTitresFiltresParams } from './titres/filtres' @@ -19,7 +19,6 @@ import { TableRow } from './_ui/table' import { titresDownloadFormats } from 'camino-common/src/filters' import { TitresStatutIds } from 'camino-common/src/static/titresStatuts' import { DemandeTitreButton } from './_common/demande-titre-button' -import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools' const defaultFilterByAdministrationUser: Pick<TitreFiltresParams, 'domainesIds' | 'typesIds' | 'statutsIds'> = { domainesIds: ['m', 'w', 'g'], @@ -38,7 +37,6 @@ export const Titres = defineComponent({ return CaminoTitresMap }) - const matomo = inject('matomo', null) const store = useStore() const router = useRouter() const user = computed<User>(() => store.state.user.element) @@ -222,10 +220,6 @@ export const Titres = defineComponent({ paramsForCarte.value = null reloadTitres(newTabId) } - if (isNotNullNorUndefined(matomo)) { - // @ts-ignore - matomo.trackEvent('titres-vue', 'titres-vueId', tabId.value) - } } }} /> diff --git a/packages/ui/src/components/utilisateur.tsx b/packages/ui/src/components/utilisateur.tsx index 95acb4e49e1ca913951728f33cd5ffa6343166c8..6efc5207118834a8ffeb9cbc8c61d040a304f1a2 100644 --- a/packages/ui/src/components/utilisateur.tsx +++ b/packages/ui/src/components/utilisateur.tsx @@ -1,4 +1,4 @@ -import { computed, defineComponent, inject, onMounted, ref, watch } from 'vue' +import { computed, defineComponent, onMounted, ref, watch } from 'vue' import { Card } from './_ui/card' import { User } from 'camino-common/src/roles' import { QGisToken } from './utilisateur/qgis-token' @@ -20,7 +20,6 @@ export const Utilisateur = defineComponent({ const store = useStore() const route = useRoute() const router = useRouter() - const matomo = inject('matomo', null) const user = computed<User>(() => { return store.state.user.element @@ -29,10 +28,6 @@ export const Utilisateur = defineComponent({ const deleteUtilisateur = async (userId: string) => { const isMe: boolean = (user.value && userId === user.value.id) ?? false if (isMe) { - if (matomo) { - // @ts-ignore - matomo.trackEvent('menu-utilisateur', 'menu-utilisateur', 'deconnexion') - } // TODO 2023-10-23 type window.location pour s'appuyer sur nos routes rest et pas sur n'importe quoi window.location.replace(`/apiUrl/rest/utilisateurs/${userId}/delete`) } else { diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index aa007d1032075675534489bc230634c185c5c565..ed8e755c3fbf03cc75b488392f64bc7672d1dfc7 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -4,13 +4,13 @@ import { sync } from 'vuex-router-sync' import * as Sentry from '@sentry/vue' import { BrowserTracing } from '@sentry/browser' -import VueMatomo from './stats' import { App } from './app' import router from './router' import store from './store' import { CaminoConfig } from 'camino-common/src/static/config' import { getWithJson } from './api/client-rest' +import { initMatomo } from './stats/matomo' // Le Timeout du sse côté backend est mis à 30 secondes, toujours avoir une valeur plus haute ici const sseTimeoutInSeconds = 45 @@ -82,21 +82,12 @@ Promise.resolve().then(async (): Promise<void> => { try { if (!configFromJson.matomoHost || !configFromJson.matomoSiteId || !configFromJson.environment) throw new Error('host et/ou siteId manquant(s)') - const matomo = await VueMatomo({ + await initMatomo({ host: configFromJson.matomoHost, siteId: configFromJson.matomoSiteId, environnement: configFromJson.environment, router, - store, - requireConsent: false, - disableCookies: true, - trackInitialView: true, - trackerFileName: 'piwik', - enableHeartBeatTimer: true, - enableLinkTracking: true, }) - app.provide('matomo', matomo) - app.config.globalProperties.$matomo = matomo } catch (e) { console.error('erreur : matomo :', e) } diff --git a/packages/ui/src/router/index.ts b/packages/ui/src/router/index.ts index 4529ca6475a68c675960fa40ba0ca55b97125d0d..d12c826e8ed7ef80ea936e23823711e8b4ef8e52 100644 --- a/packages/ui/src/router/index.ts +++ b/packages/ui/src/router/index.ts @@ -1,6 +1,5 @@ import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' import store from '../store' -import type { MenuSection } from '@/utils/matomo' import { Dashboard } from '../components/dashboard' import { DGTMStatsFull } from '../components/dashboard/dgtm-stats-full' @@ -126,6 +125,8 @@ const About = async () => { return About } +export type MenuSection = 'dashboard' | 'titres' | 'demarches' | 'travaux' | 'activites' | 'administrations' | 'entreprises' | 'utilisateurs' | 'metas' | 'statistiques' | 'journaux' + declare module 'vue-router' { interface RouteMeta { menuSection: MenuSection | null diff --git a/packages/ui/src/stats/bootstrap.js b/packages/ui/src/stats/bootstrap.js deleted file mode 100755 index 491bdeb3cc7597213442ecf5763e8b1861e6fedb..0000000000000000000000000000000000000000 --- a/packages/ui/src/stats/bootstrap.js +++ /dev/null @@ -1,19 +0,0 @@ -export default function (options) { - const { host, trackerFileName } = options - const filename = `${host}/${trackerFileName}.js` - - return new Promise((resolve, reject) => { - const script = document.createElement('script') - script.async = true - script.defer = true - script.src = filename - - const head = document.head || document.getElementsByTagName('head')[0] - head.appendChild(script) - - script.onload = resolve - script.onerror = reject - }).catch(error => { - console.info(`Warning: ${error.target.src}. If the file exists, you may have a tracking blocker enabled.`) - }) -} diff --git a/packages/ui/src/stats/custom-variables.js b/packages/ui/src/stats/custom-variables.js deleted file mode 100644 index 4e3eb60b54cfa5095e2e6d50130c8e4dbd42618c..0000000000000000000000000000000000000000 --- a/packages/ui/src/stats/custom-variables.js +++ /dev/null @@ -1,29 +0,0 @@ -import { isAdministration } from 'camino-common/src/roles' - -const visitUser = matomo => user => { - if (user) { - if (isAdministration(user)) { - matomo.setCustomVariable(1, 'administrationId', user.administrationId, 'visit') - } - - if (user.entreprises && user.entreprises.length) { - user.entreprises.forEach(entreprise => { - matomo.setCustomVariable(1, 'entreprisesIds', entreprise.id, 'visit') - }) - } - - if (user.role) { - matomo.setCustomVariable(5, 'role', user.role, 'visit') - } - } -} - -const pageTitre = matomo => titre => { - if (titre) { - matomo.setCustomVariable(1, 'domaineId', titre.domaine.id, 'page') - matomo.setCustomVariable(2, 'typeId', titre.type.typeId, 'page') - matomo.setCustomVariable(3, 'statutId', titre.titreStatutId, 'page') - } -} - -export { visitUser, pageTitre } diff --git a/packages/ui/src/stats/index.js b/packages/ui/src/stats/index.js deleted file mode 100755 index 8675921ecf344c8fb8c8edeb5124ba4c1084e14b..0000000000000000000000000000000000000000 --- a/packages/ui/src/stats/index.js +++ /dev/null @@ -1,84 +0,0 @@ -import bootstrap from './bootstrap' -import { visitUser, pageTitre } from './custom-variables' - -const defaultOptions = { - requireConsent: false, - disableCookies: true, - trackInitialView: true, - trackerFileName: 'piwik', - enableHeartBeatTimer: false, - enableLinkTracking: false, - heartBeatTimerInterval: 60, - environnement: 'dev', -} - -const install = (setupOptions = {}) => { - const options = Object.assign({}, defaultOptions, setupOptions) - - return bootstrap(options) - .then(() => { - const matomo = window.Piwik.getTracker(`${options.host}/${options.trackerFileName}.php`, options.siteId) - - // dimension d'environnement : https://stats.data.gouv.fr/index.php?module=CustomDimensions&action=manage&idSite=70&period=day&date=yesterday#?idDimension=1&scope=visit - matomo.setCustomDimension(1, options.environnement) - matomo.customVariableVisitUser = visitUser(matomo) - matomo.customVariablePageTitre = pageTitre(matomo) - - if (options.requireConsent) { - matomo.requireConsent() - } - - if (options.enableHeartBeatTimer) { - matomo.enableHeartBeatTimer() - } - - if (options.disableCookies) { - matomo.disableCookies() - } - - if (options.enableLinkTracking) { - matomo.enableLinkTracking(options.enableLinkTracking) - matomo.setDownloadExtensions('csv|odt|xlsx|geojson') - } - - if (options.router) { - options.router.afterEach((to, from) => { - // Unfortunately the window location is not yet updated here - // We need to make our own ulr using the data provided by the router - const loc = window.location - - // Protocol may or may not contain a colon - let protocol = loc.protocol - if (protocol.slice(-1) !== ':') { - protocol += ':' - } - - const url = protocol + '//' + loc.host + to.path - matomo.setCustomUrl(url) - - matomo.customVariableVisitUser(options.store.state.user.element) - matomo.trackPageView(name) - - if (to.name !== from.name) { - // nombre d'affichage de la page - // titre, titres, entreprises, activites, demarches, utilisateurs - matomo.trackEvent(`page-${to.name}`, `page-${to.name}_acceder`) - - if (to.name === 'titre') { - // nombre d'affichage de la page 'titres' - let action = `page-titre-from-${from.name}` - action += from.query.vue ? `-${from.query.vue}` : '' - - matomo.trackEvent('page-titre', action, to.params.id) - } - } - }) - } - return matomo - }) - .catch(e => { - console.error('error during matomo initialization', e) - }) -} - -export default install diff --git a/packages/ui/src/stats/matomo.ts b/packages/ui/src/stats/matomo.ts new file mode 100755 index 0000000000000000000000000000000000000000..4dca711963bef6ee77c2c504bc21d6568910d805 --- /dev/null +++ b/packages/ui/src/stats/matomo.ts @@ -0,0 +1,48 @@ +import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools' +import { Router } from 'vue-router' + +export const initMatomo = async (options: { router: Router; host: string; siteId: string; environnement: string }) => { + const trackerFileName = 'piwik' + + await bootstrap(options.host, trackerFileName) + // @ts-ignore + const matomo = window.Piwik.getTracker(`${options.host}/${trackerFileName}.php`, options.siteId) + + // dimension d'environnement : https://stats.data.gouv.fr/index.php?module=CustomDimensions&action=manage&idSite=70&period=day&date=yesterday#?idDimension=1&scope=visit + matomo.setCustomDimension(1, options.environnement) + + matomo.enableHeartBeatTimer() + matomo.disableCookies() + matomo.enableLinkTracking(true) + matomo.setDownloadExtensions('csv|odt|xlsx|geojson') + + options.router.afterEach((to, from) => { + const url = options.router.resolve(to.fullPath).href + const referrerUrl: string | null = isNotNullNorUndefined(from) && isNotNullNorUndefined(from.fullPath) ? options.router.resolve(from.fullPath).href : null + + if (referrerUrl !== null) { + matomo.setReferrerUrl(window.location.origin + referrerUrl) + } + matomo.setCustomUrl(window.location.origin + url) + matomo.trackPageView(to.name) + }) +} + +const bootstrap = (host: string, trackerFileName: string): Promise<unknown> => { + const filename = `${host}/${trackerFileName}.js` + + return new Promise((resolve, reject) => { + const script = document.createElement('script') + script.async = true + script.defer = true + script.src = filename + + const head = document.head || document.getElementsByTagName('head')[0] + head.appendChild(script) + + script.onload = resolve + script.onerror = reject + }).catch(error => { + console.info(`Warning: ${error.target.src}. If the file exists, you may have a tracking blocker enabled.`) + }) +} diff --git a/packages/ui/src/utils/matomo.ts b/packages/ui/src/utils/matomo.ts deleted file mode 100644 index 92869106a71fdc800ab626c3746530888dd95129..0000000000000000000000000000000000000000 --- a/packages/ui/src/utils/matomo.ts +++ /dev/null @@ -1,21 +0,0 @@ -type MatomoSegment = 'menu-utilisateur' | 'menu-sections' | 'menu' -type MatomoSubsegment = 'menu-utilisateur' | 'menu-section' | 'bouton' -type MatomoEvent = 'deconnexion' | 'dashboard' | 'titres' | 'demarches' | 'travaux' | 'activites' | 'administrations' | 'entreprises' | 'utilisateurs' | 'metas' | 'utilisateur' | 'statistiques' - -const MatomoParams = { - 'menu-utilisateur': { - 'menu-utilisateur': ['deconnexion'], - }, - 'menu-sections': { - 'menu-section': ['dashboard', 'titres', 'demarches', 'travaux', 'activites', 'administrations', 'entreprises', 'utilisateurs', 'metas', 'statistiques'], - }, - menu: { - bouton: ['utilisateur'], - }, -} as const satisfies Record<MatomoSegment, { [key in MatomoSubsegment]?: readonly MatomoEvent[] }> - -type MatomoEventParam<T extends MatomoSegment, U extends keyof (typeof MatomoParams)[T]> = Extract<(typeof MatomoParams)[T][U], readonly MatomoEvent[]>[number] -export type MenuSection = MatomoEventParam<'menu-sections', 'menu-section'> | 'journaux' - -// TODO 2023-03-16 typer l’instance matomo dans un .d.ts -export type TrackEventFunction = <T extends MatomoSegment, U extends keyof (typeof MatomoParams)[T]>(segment: T, subSegment: U, event: MatomoEventParam<T, U>) => void