From e7d3669772f53ec4af90dd9b9730e8998b4979f8 Mon Sep 17 00:00:00 2001 From: Oleh Omelchenko Date: Thu, 16 Oct 2025 22:59:54 +0300 Subject: [PATCH] feat: integrate GoatCounter analytics for event tracking across modals and snippets --- index.html | 5 +++++ src/js/app.js | 4 ++++ src/js/config.js | 14 ++++++++++++++ src/js/dataset-manager.js | 15 +++++++++++++++ src/js/snippet-manager.js | 27 +++++++++++++++++++++++++++ 5 files changed, 65 insertions(+) diff --git a/index.html b/index.html index b8b64d3..986e7d6 100644 --- a/index.html +++ b/index.html @@ -534,6 +534,11 @@ + + + \ No newline at end of file diff --git a/src/js/app.js b/src/js/app.js index bbc9f4f..36f05d3 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -468,6 +468,8 @@ function openHelpModal() { const modal = document.getElementById('help-modal'); if (modal) { modal.style.display = 'flex'; + // Track event + Analytics.track('modal-help', 'Open Help modal'); } } @@ -483,6 +485,8 @@ function openDonateModal() { const modal = document.getElementById('donate-modal'); if (modal) { modal.style.display = 'flex'; + // Track event + Analytics.track('modal-donate', 'Open Donate modal'); } } diff --git a/src/js/config.js b/src/js/config.js index 3304b8d..686e7ba 100644 --- a/src/js/config.js +++ b/src/js/config.js @@ -210,6 +210,20 @@ const Toast = { } }; +// Analytics utility: Track events with GoatCounter +const Analytics = { + track(eventName, title) { + // Only track if GoatCounter is loaded + if (window.goatcounter && window.goatcounter.count) { + window.goatcounter.count({ + path: eventName, + title: title || eventName, + event: true, + }); + } + } +}; + // Shared utility: Format bytes for display function formatBytes(bytes) { if (bytes === null || bytes === undefined) return 'N/A'; diff --git a/src/js/dataset-manager.js b/src/js/dataset-manager.js index f3fd023..820fb33 100644 --- a/src/js/dataset-manager.js +++ b/src/js/dataset-manager.js @@ -785,6 +785,9 @@ function openDatasetManager(updateURL = true) { if (updateURL) { URLState.update({ view: 'datasets', snippetId: null, datasetId: null }); } + + // Track event + Analytics.track('modal-datasets', 'Open Dataset Manager'); } // Close dataset manager modal @@ -1139,6 +1142,9 @@ async function saveNewDataset() { hideNewDatasetForm(); await renderDatasetList(); + + // Track event + Analytics.track('dataset-create', `Create dataset (${source})`); } catch (error) { errorEl.textContent = `Failed to save dataset: ${error.message}`; } @@ -1167,6 +1173,9 @@ async function deleteCurrentDataset() { // Show success message Toast.success('Dataset deleted'); + + // Track event + Analytics.track('dataset-delete', 'Delete dataset'); } } @@ -1295,6 +1304,9 @@ async function exportCurrentDataset() { // Show success message Toast.success(`Dataset "${dataset.name}" exported successfully`); + // Track event + Analytics.track('dataset-export', `Export dataset (${dataset.format})`); + } catch (error) { Toast.error(`Failed to export dataset: ${error.message}`); } @@ -1394,6 +1406,9 @@ async function importDatasetFromFile(fileInput) { Toast.success(`Dataset "${datasetName}" imported successfully!`); } + // Track event + Analytics.track('dataset-import', `Import dataset (${format})`); + } catch (error) { Toast.error(`Failed to import dataset: ${error.message}`); } finally { diff --git a/src/js/snippet-manager.js b/src/js/snippet-manager.js index 1a78cea..339b78c 100644 --- a/src/js/snippet-manager.js +++ b/src/js/snippet-manager.js @@ -835,6 +835,9 @@ function createNewSnippet() { renderSnippetList(); selectSnippet(newSnippet.id); + // Track event + Analytics.track('snippet-create', 'Create new snippet'); + return newSnippet; } @@ -859,6 +862,9 @@ function duplicateSnippet(snippetId) { // Show success message Toast.success('Snippet duplicated successfully'); + // Track event + Analytics.track('snippet-duplicate', 'Duplicate snippet'); + return newSnippet; } @@ -881,6 +887,9 @@ function createSnippetFromDataset(datasetName) { renderSnippetList(); selectSnippet(newSnippet.id); + // Track event + Analytics.track('snippet-from-dataset', 'Create snippet from dataset'); + return newSnippet; } @@ -992,6 +1001,9 @@ async function extractToDataset() { // Show success message Toast.success(`Dataset "${datasetName}" created successfully!`); + + // Track event + Analytics.track('dataset-extract', 'Extract inline data to dataset'); } catch (error) { errorEl.textContent = `Failed to create dataset: ${error.message}`; } @@ -1032,6 +1044,9 @@ function deleteSnippet(snippetId) { // Show success message Toast.success('Snippet deleted'); + // Track event + Analytics.track('snippet-delete', 'Delete snippet'); + return true; } @@ -1121,6 +1136,9 @@ function publishDraft() { // Show success message Toast.success('Snippet published successfully!'); + + // Track event + Analytics.track('snippet-publish', 'Publish draft'); } // Revert draft to published spec @@ -1146,6 +1164,9 @@ function revertDraft() { // Show success message Toast.success('Draft reverted to published version'); + + // Track event + Analytics.track('snippet-revert', 'Revert draft'); } } @@ -1212,6 +1233,9 @@ function exportSnippets() { // Show success message Toast.success(`Exported ${snippets.length} snippet${snippets.length !== 1 ? 's' : ''}`); + + // Track event + Analytics.track('snippets-export', `Export ${snippets.length} snippets`); } // Normalize external snippet format to Astrolabe format @@ -1296,6 +1320,9 @@ function importSnippets(fileInput) { if (SnippetStorage.saveSnippets(existingSnippets)) { Toast.success(`Successfully imported ${importedCount} snippet${importedCount !== 1 ? 's' : ''}`); renderSnippetList(); + + // Track event + Analytics.track('snippets-import', `Import ${importedCount} snippets`); } } catch (error) {