diff --git a/src/lib/util/state.ts b/src/lib/util/state.ts index 81df4a72..9237c44f 100644 --- a/src/lib/util/state.ts +++ b/src/lib/util/state.ts @@ -1,6 +1,6 @@ import { writable, get, derived } from 'svelte/store'; import { persist, localStorage } from './persist'; -import { saveStatistics } from './stats'; +import { saveStatistics, countLines } from './stats'; import { serializeState, deserializeState } from './serde'; import { cmdKey } from './util'; import mermaid from 'mermaid'; @@ -121,8 +121,8 @@ export const updateCode = ( resetPanZoom = false }: { updateDiagram?: boolean; resetPanZoom?: boolean } = {} ): void => { + const lines = countLines(code); saveStatistics(code); - const lines = (code.match(/\n/g) || '').length + 1; if (lines > 50 && !prompted && get(stateStore).autoSync) { const turnOff = confirm( diff --git a/src/lib/util/stats.test.ts b/src/lib/util/stats.test.ts new file mode 100644 index 00000000..09db84f5 --- /dev/null +++ b/src/lib/util/stats.test.ts @@ -0,0 +1,19 @@ +import { describe, it, expect } from 'vitest'; +import { detectType } from './stats'; +describe('diagram detection', () => { + it('should detect diagrams correctly', () => { + expect( + detectType(`%%{{ + graph`) + ).toBe('graph'); + expect(detectType(`gitGraph`)).toBe('gitGraph'); + expect( + detectType(`%%{{ + + + flowChart + graph`) + ).toBe('flowChart'); + expect(detectType(`loki -> thor`)).toBe(undefined); + }); +}); diff --git a/src/lib/util/stats.ts b/src/lib/util/stats.ts index 21edcdcc..b138044c 100644 --- a/src/lib/util/stats.ts +++ b/src/lib/util/stats.ts @@ -1,6 +1,5 @@ import { browser } from '$app/environment'; import type { AnalyticsInstance } from 'analytics'; - export let analytics: AnalyticsInstance; export const initAnalytics = async (): Promise => { @@ -28,27 +27,69 @@ export const initAnalytics = async (): Promise => { } }; -const detectType = (text: string): string => { - return text +export const detectType = (text: string): string => { + const possibleDiagramTypes = [ + 'classDiagram', + 'erDiagram', + 'flowChart', + 'gantt', + 'gitGraph', + 'graph', + 'journey', + 'pie', + 'stateDiagram' + ]; + const firstLine = text .replace(/^\s*%%.*\n/g, '\n') .trimStart() - .split(' ')[0]; + .split(' ')[0] + .toLowerCase(); + const detectedDiagram = possibleDiagramTypes.find((d) => firstLine.includes(d.toLowerCase())); + return detectedDiagram; +}; + +export const countLines = (code: string): number => { + return (code.match(/\n/g) || '').length + 1; }; export const saveStatistics = (graph: string): void => { const graphType = detectType(graph); - console.debug(`ga: send event: render ${graphType}`); - logEvent('render', { graphType }); + const length = countLines(graph); + logEvent('render', { graphType, length }); }; -// manual debounce to only send analytics event every 5 seconds if same event is repeated frequently. +const minutesToMilliSeconds = (minutes: number): number => { + return minutes * 60_000; +}; + +const defaultDelay = minutesToMilliSeconds(1); +const delaysPerEvent = { + render: minutesToMilliSeconds(5), + panZoom: minutesToMilliSeconds(10), + copyClipboard: defaultDelay, + download: defaultDelay, + copyMarkdown: defaultDelay, + loadGist: defaultDelay, + loadSampleDiagram: defaultDelay, + renderDiagram: defaultDelay, + history: defaultDelay, + migration: defaultDelay, + themeChange: defaultDelay +}; +export type AnalyticsEvent = keyof typeof delaysPerEvent; const timeouts: Record = {}; -export const logEvent = (name: string, data?: unknown): void => { - if (analytics) { - const key = data ? JSON.stringify({ name, data }) : name; +// manual debounce to reduce the number of events sent to analytics +export const logEvent = (name: AnalyticsEvent, data?: unknown): void => { + if (!analytics) { + return; + } + const key = data ? JSON.stringify({ name, data }) : name; + if (timeouts[key] === undefined) { + void analytics.track(name, data); + } else { clearTimeout(timeouts[key]); - timeouts[key] = window.setTimeout(() => { - void analytics.track(name, data); - }, 5000); } + timeouts[key] = window.setTimeout(() => { + delete timeouts[key]; + }, delaysPerEvent[name]); };