From f351459b680e87c1fe05bbc684a9646cd8172efd Mon Sep 17 00:00:00 2001 From: Alexander Polyankin <alexander.polyankin@metabase.com> Date: Fri, 22 Oct 2021 19:47:52 +0300 Subject: [PATCH] Add snowplow tracking (#18293) --- .../auth/components/SSOButton.jsx | 4 +- .../src/metabase/admin/databases/database.js | 28 ++-- .../datamodel/components/FieldRemapping.jsx | 18 ++- .../components/database/ColumnItem.jsx | 8 +- .../containers/MetadataEditorApp.jsx | 4 +- .../admin/datamodel/containers/MetricApp.jsx | 6 +- .../admin/datamodel/containers/SegmentApp.jsx | 6 +- .../src/metabase/admin/datamodel/field.js | 6 +- .../src/metabase/admin/datamodel/table.js | 6 +- .../admin/people/components/GroupsListing.jsx | 8 +- frontend/src/metabase/admin/people/people.js | 6 +- .../metabase/admin/permissions/permissions.js | 14 +- .../settings/components/SettingsEmailForm.jsx | 14 +- .../settings/components/SettingsSlackForm.jsx | 20 ++- .../components/widgets/EmbeddingLegalese.jsx | 4 +- .../components/widgets/PublicLinksListing.jsx | 4 +- .../settings/containers/SettingsEditorApp.jsx | 6 +- frontend/src/metabase/app.js | 20 +-- frontend/src/metabase/auth/auth.js | 8 +- .../auth/containers/PasswordResetApp.jsx | 4 +- .../AddSeriesModal/AddSeriesModal.jsx | 16 +- .../AddSeriesModal/QuestionList.jsx | 8 +- .../dashboard/components/DashboardGrid.jsx | 4 +- .../components/DashboardSidebars.jsx | 4 +- .../components/RemoveFromDashboardModal.jsx | 4 +- .../containers/AutomaticDashboardApp.jsx | 9 +- .../DashboardSharingEmbeddingModal.jsx | 4 +- .../dashboard/hoc/DashboardControls.jsx | 4 +- frontend/src/metabase/entities/users.js | 15 +- frontend/src/metabase/lib/CODENOTIFY | 3 + frontend/src/metabase/lib/analytics.js | 150 +++++++++++------- frontend/src/metabase/lib/redux.js | 4 +- .../components/widgets/EmbedModalContent.jsx | 7 +- .../public/components/widgets/SharingPane.jsx | 6 +- .../metabase/pulse/components/PulseEdit.jsx | 10 +- .../pulse/components/PulseEditCards.jsx | 4 +- .../pulse/components/PulseEditChannels.jsx | 8 +- .../pulse/components/RecipientPicker.jsx | 4 +- .../src/metabase/query_builder/actions.js | 25 +-- .../query_builder/components/AlertModals.jsx | 10 +- .../components/ExtendedOptions.jsx | 10 +- .../components/filters/FilterOptions.jsx | 9 +- .../template_tags/TagEditorSidebar.jsx | 4 +- .../containers/QuestionEmbedWidget.jsx | 4 +- frontend/src/metabase/redux/undo.js | 6 +- .../metrics/MetricDetailContainer.jsx | 4 +- frontend/src/metabase/reference/reference.js | 6 +- frontend/src/metabase/setup/actions.js | 4 +- .../components/DatabaseConnectionStep.jsx | 14 +- .../components/DatabaseSchedulingStep.jsx | 8 +- .../setup/components/PreferencesStep.jsx | 4 +- .../src/metabase/setup/components/Setup.jsx | 6 +- .../metabase/setup/components/UserStep.jsx | 10 +- .../components/CardRenderer.jsx | 7 +- .../components/ChartClickActions.jsx | 17 +- .../components/ChartSettings.jsx | 4 +- .../components/Visualization.jsx | 4 +- .../settings/ChartSettingsTableFormatting.jsx | 8 +- .../metabase/visualizations/lib/settings.js | 4 +- frontend/test/__support__/mocks.js | 1 + package.json | 1 + .../inline_js/index_ganalytics.js | 9 -- src/metabase/server/middleware/security.clj | 3 + yarn.lock | 50 ++++++ 64 files changed, 424 insertions(+), 266 deletions(-) create mode 100644 frontend/src/metabase/lib/CODENOTIFY diff --git a/enterprise/frontend/src/metabase-enterprise/auth/components/SSOButton.jsx b/enterprise/frontend/src/metabase-enterprise/auth/components/SSOButton.jsx index 400a6140784..70da9969f58 100644 --- a/enterprise/frontend/src/metabase-enterprise/auth/components/SSOButton.jsx +++ b/enterprise/frontend/src/metabase-enterprise/auth/components/SSOButton.jsx @@ -2,7 +2,7 @@ import React from "react"; import { IFRAMED } from "metabase/lib/dom"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseSettings from "metabase/lib/settings"; import AuthProviderButton from "metabase/auth/components/AuthProviderButton"; @@ -17,7 +17,7 @@ export default class SSOButton extends React.Component { handleClick = () => { const { redirect } = this.props.location.query; - MetabaseAnalytics.trackEvent("Auth", "SSO Login Start"); + MetabaseAnalytics.trackStructEvent("Auth", "SSO Login Start"); // use `window.location` instead of `push` since it's not a frontend route window.location = MetabaseSettings.get("site-url") + diff --git a/frontend/src/metabase/admin/databases/database.js b/frontend/src/metabase/admin/databases/database.js index 72a4d9e1c11..296e68a0cfc 100644 --- a/frontend/src/metabase/admin/databases/database.js +++ b/frontend/src/metabase/admin/databases/database.js @@ -6,7 +6,7 @@ import { } from "metabase/lib/redux"; import { push } from "react-router-redux"; import { t } from "ttag"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseSettings from "metabase/lib/settings"; import { MetabaseApi } from "metabase/services"; @@ -151,7 +151,7 @@ export const addSampleDataset = createThunkAction( reload: true, }), ); - MetabaseAnalytics.trackEvent("Databases", "Add Sample Data"); + MetabaseAnalytics.trackStructEvent("Databases", "Add Sample Data"); return sampleDataset; } catch (error) { console.error("error adding sample dataset", error); @@ -201,13 +201,17 @@ export const createDatabase = function(database) { dispatch.action(CREATE_DATABASE_STARTED, {}); const action = await dispatch(Databases.actions.create(database)); const createdDatabase = Databases.HACK_getObjectFromAction(action); - MetabaseAnalytics.trackEvent("Databases", "Create", database.engine); + MetabaseAnalytics.trackStructEvent( + "Databases", + "Create", + database.engine, + ); dispatch.action(CREATE_DATABASE); dispatch(push("/admin/databases?created=" + createdDatabase.id)); } catch (error) { console.error("error creating a database", error); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Databases", "Create Failed", database.engine, @@ -223,11 +227,15 @@ export const updateDatabase = function(database) { dispatch.action(UPDATE_DATABASE_STARTED, { database }); const action = await dispatch(Databases.actions.update(database)); const savedDatabase = Databases.HACK_getObjectFromAction(action); - MetabaseAnalytics.trackEvent("Databases", "Update", database.engine); + MetabaseAnalytics.trackStructEvent( + "Databases", + "Update", + database.engine, + ); dispatch.action(UPDATE_DATABASE, { database: savedDatabase }); } catch (error) { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Databases", "Update Failed", database.engine, @@ -257,7 +265,7 @@ export const deleteDatabase = function(databaseId, isDetailView = true) { dispatch.action(DELETE_DATABASE_STARTED, { databaseId }); dispatch(push("/admin/databases/")); await dispatch(Databases.actions.delete({ id: databaseId })); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Databases", "Delete", isDetailView ? "Using Detail" : "Using List", @@ -277,7 +285,7 @@ export const syncDatabaseSchema = createThunkAction( return async function(dispatch, getState) { try { const call = await MetabaseApi.db_sync_schema({ dbId: databaseId }); - MetabaseAnalytics.trackEvent("Databases", "Manual Sync"); + MetabaseAnalytics.trackStructEvent("Databases", "Manual Sync"); return call; } catch (error) { console.log("error syncing database", error); @@ -293,7 +301,7 @@ export const rescanDatabaseFields = createThunkAction( return async function(dispatch, getState) { try { const call = await MetabaseApi.db_rescan_values({ dbId: databaseId }); - MetabaseAnalytics.trackEvent("Databases", "Manual Sync"); + MetabaseAnalytics.trackStructEvent("Databases", "Manual Sync"); return call; } catch (error) { console.log("error syncing database", error); @@ -309,7 +317,7 @@ export const discardSavedFieldValues = createThunkAction( return async function(dispatch, getState) { try { const call = await MetabaseApi.db_discard_values({ dbId: databaseId }); - MetabaseAnalytics.trackEvent("Databases", "Manual Sync"); + MetabaseAnalytics.trackStructEvent("Databases", "Manual Sync"); return call; } catch (error) { console.log("error syncing database", error); diff --git a/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx b/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx index edbcca87c83..dfee9af9ff0 100644 --- a/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx +++ b/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx @@ -15,7 +15,7 @@ import ButtonWithStatus from "metabase/components/ButtonWithStatus"; import SelectSeparator from "../components/SelectSeparator"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import Dimension, { FieldDimension } from "metabase-lib/lib/Dimension"; import Question from "metabase-lib/lib/Question"; @@ -113,7 +113,7 @@ export default class FieldRemapping extends React.Component { this.clearEditingStates(); if (mappingType.type === "original") { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Data Model", "Change Remapping Type", "No Remapping", @@ -125,7 +125,7 @@ export default class FieldRemapping extends React.Component { const entityNameFieldId = this.getFKTargetTableEntityNameOrNull(); if (entityNameFieldId) { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Data Model", "Change Remapping Type", "Foreign Key", @@ -146,7 +146,7 @@ export default class FieldRemapping extends React.Component { }); } } else if (mappingType.type === "custom") { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Data Model", "Change Remapping Type", "Custom Remappings", @@ -182,7 +182,10 @@ export default class FieldRemapping extends React.Component { // TODO Atte Keinänen 7/10/17: Use Dimension class when migrating to metabase-lib const dimension = Dimension.parseMBQL(foreignKeyClause); if (dimension && dimension instanceof FieldDimension && dimension.fk()) { - MetabaseAnalytics.trackEvent("Data Model", "Update FK Remapping Target"); + MetabaseAnalytics.trackStructEvent( + "Data Model", + "Update FK Remapping Target", + ); await updateFieldDimension( { id: field.id }, { @@ -390,7 +393,10 @@ export class ValueRemappings extends React.Component { } onSaveClick = () => { - MetabaseAnalytics.trackEvent("Data Model", "Update Custom Remappings"); + MetabaseAnalytics.trackStructEvent( + "Data Model", + "Update Custom Remappings", + ); // Returns the promise so that ButtonWithStatus can show the saving status return this.props.updateRemappings(this.state.editingRemappings); }; diff --git a/frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx b/frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx index 8180734d084..7bfcd8eed4b 100644 --- a/frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx +++ b/frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx @@ -18,7 +18,7 @@ import _ from "underscore"; import cx from "classnames"; import type { Field } from "metabase-types/types/Field"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; @withRouter export default class Column extends Component { @@ -152,7 +152,7 @@ export class SemanticTypeAndTargetPicker extends Component { await updateField({ semantic_type }); } - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Data Model", "Update Field Special-Type", semantic_type, @@ -167,7 +167,7 @@ export class SemanticTypeAndTargetPicker extends Component { currency, }, }); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Data Model", "Update Currency Type", currency, @@ -176,7 +176,7 @@ export class SemanticTypeAndTargetPicker extends Component { handleChangeTarget = async ({ target: { value: fk_target_field_id } }) => { await this.props.updateField({ fk_target_field_id }); - MetabaseAnalytics.trackEvent("Data Model", "Update Field Target"); + MetabaseAnalytics.trackStructEvent("Data Model", "Update Field Target"); }; render() { diff --git a/frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx b/frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx index 4d93baede74..296e992d5ad 100644 --- a/frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx +++ b/frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx @@ -4,7 +4,7 @@ import { connect } from "react-redux"; import { push, replace } from "react-router-redux"; import { t } from "ttag"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import AdminEmptyText from "metabase/components/AdminEmptyText"; import MetadataHeader from "../components/database/MetadataHeader"; @@ -73,7 +73,7 @@ class MetadataEditor extends Component { toggleShowSchema() { this.setState({ isShowingSchema: !this.state.isShowingSchema }); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Data Model", "Show OG Schema", !this.state.isShowingSchema, diff --git a/frontend/src/metabase/admin/datamodel/containers/MetricApp.jsx b/frontend/src/metabase/admin/datamodel/containers/MetricApp.jsx index a982ff24619..60cccd99d3f 100644 --- a/frontend/src/metabase/admin/datamodel/containers/MetricApp.jsx +++ b/frontend/src/metabase/admin/datamodel/containers/MetricApp.jsx @@ -3,7 +3,7 @@ import React, { Component } from "react"; import { connect } from "react-redux"; import { push } from "react-router-redux"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import Metrics from "metabase/entities/metrics"; import { updatePreviewSummary } from "../datamodel"; @@ -25,7 +25,7 @@ const mapStateToProps = (state, props) => ({ class UpdateMetricForm extends Component { onSubmit = async metric => { await this.props.updateMetric(metric); - MetabaseAnalytics.trackEvent("Data Model", "Metric Updated"); + MetabaseAnalytics.trackStructEvent("Data Model", "Metric Updated"); this.props.onChangeLocation(`/admin/datamodel/metrics`); }; @@ -47,7 +47,7 @@ class CreateMetricForm extends Component { ...metric, table_id: metric.definition["source-table"], }); - MetabaseAnalytics.trackEvent("Data Model", "Metric Updated"); + MetabaseAnalytics.trackStructEvent("Data Model", "Metric Updated"); this.props.onChangeLocation(`/admin/datamodel/metrics`); }; diff --git a/frontend/src/metabase/admin/datamodel/containers/SegmentApp.jsx b/frontend/src/metabase/admin/datamodel/containers/SegmentApp.jsx index 88218b75b5c..e601eb2e72e 100644 --- a/frontend/src/metabase/admin/datamodel/containers/SegmentApp.jsx +++ b/frontend/src/metabase/admin/datamodel/containers/SegmentApp.jsx @@ -3,7 +3,7 @@ import React, { Component } from "react"; import { connect } from "react-redux"; import { push } from "react-router-redux"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import Segments from "metabase/entities/segments"; import { updatePreviewSummary } from "../datamodel"; @@ -25,7 +25,7 @@ const mapStateToProps = (state, props) => ({ class UpdateSegmentForm extends Component { onSubmit = async segment => { await this.props.updateSegment(segment); - MetabaseAnalytics.trackEvent("Data Model", "Segment Updated"); + MetabaseAnalytics.trackStructEvent("Data Model", "Segment Updated"); this.props.onChangeLocation(`/admin/datamodel/segments`); }; @@ -47,7 +47,7 @@ class CreateSegmentForm extends Component { ...segment, table_id: segment.definition["source-table"], }); - MetabaseAnalytics.trackEvent("Data Model", "Segment Updated"); + MetabaseAnalytics.trackStructEvent("Data Model", "Segment Updated"); this.props.onChangeLocation(`/admin/datamodel/segments`); }; diff --git a/frontend/src/metabase/admin/datamodel/field.js b/frontend/src/metabase/admin/datamodel/field.js index 99d65b71bae..d1748c2c2df 100644 --- a/frontend/src/metabase/admin/datamodel/field.js +++ b/frontend/src/metabase/admin/datamodel/field.js @@ -1,6 +1,6 @@ import { createThunkAction } from "metabase/lib/redux"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { MetabaseApi } from "metabase/services"; export const RESCAN_FIELD_VALUES = "metabase/admin/fields/RESCAN_FIELD_VALUES"; @@ -13,7 +13,7 @@ export const rescanFieldValues = createThunkAction( return async function(dispatch, getState) { try { const call = await MetabaseApi.field_rescan_values({ fieldId }); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Data Model", "Manual Re-scan Field Values", ); @@ -31,7 +31,7 @@ export const discardFieldValues = createThunkAction( return async function(dispatch, getState) { try { const call = await MetabaseApi.field_discard_values({ fieldId }); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Data Model", "Manual Discard Field Values", ); diff --git a/frontend/src/metabase/admin/datamodel/table.js b/frontend/src/metabase/admin/datamodel/table.js index 16174df80fc..c327319e075 100644 --- a/frontend/src/metabase/admin/datamodel/table.js +++ b/frontend/src/metabase/admin/datamodel/table.js @@ -1,6 +1,6 @@ import { createThunkAction } from "metabase/lib/redux"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { MetabaseApi } from "metabase/services"; export const RESCAN_TABLE_VALUES = "metabase/admin/tables/RESCAN_TABLE_VALUES"; @@ -13,7 +13,7 @@ export const rescanTableFieldValues = createThunkAction( return async function(dispatch, getState) { try { const call = await MetabaseApi.table_rescan_values({ tableId }); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Data Model", "Manual Re-scan Field Values for Table", ); @@ -31,7 +31,7 @@ export const discardTableFieldValues = createThunkAction( return async function(dispatch, getState) { try { const call = await MetabaseApi.table_discard_values({ tableId }); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Data Model", "Manual Discard Field Values for Table", ); diff --git a/frontend/src/metabase/admin/people/components/GroupsListing.jsx b/frontend/src/metabase/admin/people/components/GroupsListing.jsx index 188a55cdf73..dbeae7f2af1 100644 --- a/frontend/src/metabase/admin/people/components/GroupsListing.jsx +++ b/frontend/src/metabase/admin/people/components/GroupsListing.jsx @@ -5,7 +5,7 @@ import { Link } from "react-router"; import _ from "underscore"; import cx from "classnames"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { isDefaultGroup, isAdminGroup, @@ -268,7 +268,7 @@ export default class GroupsListing extends Component { // TODO: move this to Redux async onAddGroupCreateButtonClicked() { - MetabaseAnalytics.trackEvent("People Groups", "Group Added"); + MetabaseAnalytics.trackStructEvent("People Groups", "Group Added"); try { await this.props.create({ name: this.state.text }); @@ -329,7 +329,7 @@ export default class GroupsListing extends Component { this.setState({ groupBeingEdited: null }); } else { // ok, fire off API call to change the group - MetabaseAnalytics.trackEvent("People Groups", "Group Updated"); + MetabaseAnalytics.trackStructEvent("People Groups", "Group Updated"); try { await this.props.update({ id: group.id, name: group.name }); this.setState({ groupBeingEdited: null }); @@ -344,7 +344,7 @@ export default class GroupsListing extends Component { // TODO: move this to Redux async onDeleteGroupClicked(group) { - MetabaseAnalytics.trackEvent("People Groups", "Group Deleted"); + MetabaseAnalytics.trackStructEvent("People Groups", "Group Deleted"); try { await this.props.delete(group); } catch (error) { diff --git a/frontend/src/metabase/admin/people/people.js b/frontend/src/metabase/admin/people/people.js index f1f6b09ad7d..a772ceb7ac5 100644 --- a/frontend/src/metabase/admin/people/people.js +++ b/frontend/src/metabase/admin/people/people.js @@ -4,7 +4,7 @@ import { combineReducers, } from "metabase/lib/redux"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { PermissionsApi } from "metabase/services"; @@ -38,7 +38,7 @@ export const createMembership = createAction( user_id: userId, group_id: groupId, }); - MetabaseAnalytics.trackEvent("People Groups", "Membership Added"); + MetabaseAnalytics.trackStructEvent("People Groups", "Membership Added"); return { user_id: userId, group_id: groupId, @@ -51,7 +51,7 @@ export const deleteMembership = createAction( DELETE_MEMBERSHIP, async ({ membershipId }) => { await PermissionsApi.deleteMembership({ id: membershipId }); - MetabaseAnalytics.trackEvent("People Groups", "Membership Deleted"); + MetabaseAnalytics.trackStructEvent("People Groups", "Membership Deleted"); return membershipId; }, ); diff --git a/frontend/src/metabase/admin/permissions/permissions.js b/frontend/src/metabase/admin/permissions/permissions.js index 6870696dac8..3ab1833a5a5 100644 --- a/frontend/src/metabase/admin/permissions/permissions.js +++ b/frontend/src/metabase/admin/permissions/permissions.js @@ -11,7 +11,7 @@ import { import { CollectionsApi, PermissionsApi } from "metabase/services"; import Group from "metabase/entities/groups"; import Tables from "metabase/entities/tables"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { inferAndUpdateEntityPermissions, updateFieldsPermission, @@ -122,7 +122,7 @@ const SAVE_DATA_PERMISSIONS = export const saveDataPermissions = createThunkAction( SAVE_DATA_PERMISSIONS, () => async (_dispatch, getState) => { - MetabaseAnalytics.trackEvent("Permissions", "save"); + MetabaseAnalytics.trackStructEvent("Permissions", "save"); const { dataPermissions, dataPermissionsRevision, @@ -147,7 +147,7 @@ const SAVE_COLLECTION_PERMISSIONS = export const saveCollectionPermissions = createThunkAction( SAVE_COLLECTION_PERMISSIONS, namespace => async (_dispatch, getState) => { - MetabaseAnalytics.trackEvent("Permissions", "save"); + MetabaseAnalytics.trackStructEvent("Permissions", "save"); const { collectionPermissions, collectionPermissionsRevision, @@ -209,7 +209,7 @@ const dataPermissions = handleActions( const { value, groupId, entityId, metadata, permission } = payload; if (entityId.tableId != null) { - MetabaseAnalytics.trackEvent("Permissions", "fields", value); + MetabaseAnalytics.trackStructEvent("Permissions", "fields", value); const updatedPermissions = updateFieldsPermission( state, groupId, @@ -224,7 +224,7 @@ const dataPermissions = handleActions( metadata, ); } else if (entityId.schemaName != null) { - MetabaseAnalytics.trackEvent("Permissions", "tables", value); + MetabaseAnalytics.trackStructEvent("Permissions", "tables", value); return updateTablesPermission( state, groupId, @@ -233,7 +233,7 @@ const dataPermissions = handleActions( metadata, ); } else if (permission.name === "native") { - MetabaseAnalytics.trackEvent("Permissions", "native", value); + MetabaseAnalytics.trackStructEvent("Permissions", "native", value); return updateNativePermission( state, groupId, @@ -242,7 +242,7 @@ const dataPermissions = handleActions( metadata, ); } else { - MetabaseAnalytics.trackEvent("Permissions", "schemas", value); + MetabaseAnalytics.trackStructEvent("Permissions", "schemas", value); return updateSchemasPermission( state, groupId, diff --git a/frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx b/frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx index 37a3bbb539d..99a5b5afe69 100644 --- a/frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx +++ b/frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx @@ -9,7 +9,7 @@ import MarginHostingCTA from "metabase/admin/settings/components/widgets/MarginH import SettingsBatchForm from "./SettingsBatchForm"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseSettings from "metabase/lib/settings"; import { @@ -56,12 +56,20 @@ export default class SettingsEmailForm extends Component { try { await this.props.sendTestEmail(); this.setState({ sendingEmail: "success" }); - MetabaseAnalytics.trackEvent("Email Settings", "Test Email", "success"); + MetabaseAnalytics.trackStructEvent( + "Email Settings", + "Test Email", + "success", + ); // show a confirmation for 3 seconds, then return to normal setTimeout(() => this.setState({ sendingEmail: "default" }), 3000); } catch (error) { - MetabaseAnalytics.trackEvent("Email Settings", "Test Email", "error"); + MetabaseAnalytics.trackStructEvent( + "Email Settings", + "Test Email", + "error", + ); this.setState({ sendingEmail: "default" }); // NOTE: reaching into form component is not ideal this._form.setFormErrors(this._form.handleFormErrors(error)); diff --git a/frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx b/frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx index 490b4617c6d..30e66eb253b 100644 --- a/frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx +++ b/frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx @@ -2,7 +2,7 @@ import React, { Component } from "react"; import { connect } from "react-redux"; import PropTypes from "prop-types"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseUtils from "metabase/lib/utils"; import SettingsSetting from "./SettingsSetting"; import { updateSlackSettings } from "../settings"; @@ -126,7 +126,11 @@ export default class SettingsSlackForm extends Component { }); if (element.key === "metabot-enabled") { - MetabaseAnalytics.trackEvent("Slack Settings", "Toggle Metabot", value); + MetabaseAnalytics.trackStructEvent( + "Slack Settings", + "Toggle Metabot", + value, + ); } } @@ -163,7 +167,11 @@ export default class SettingsSlackForm extends Component { submitting: "success", }); - MetabaseAnalytics.trackEvent("Slack Settings", "Update", "success"); + MetabaseAnalytics.trackStructEvent( + "Slack Settings", + "Update", + "success", + ); // show a confirmation for 3 seconds, then return to normal setTimeout(() => this.setState({ submitting: "default" }), 3000); @@ -174,7 +182,11 @@ export default class SettingsSlackForm extends Component { formErrors: this.handleFormErrors(error), }); - MetabaseAnalytics.trackEvent("Slack Settings", "Update", "error"); + MetabaseAnalytics.trackStructEvent( + "Slack Settings", + "Update", + "error", + ); }, ); } diff --git a/frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx b/frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx index d77f8ef4af0..82f5aceaf24 100644 --- a/frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx +++ b/frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx @@ -1,6 +1,6 @@ /* eslint-disable react/prop-types */ import React from "react"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { t } from "ttag"; import ExternalLink from "metabase/components/ExternalLink"; @@ -24,7 +24,7 @@ const EmbeddingLegalese = ({ onChange }) => ( <button className="Button Button--primary" onClick={() => { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Admin Embed Settings", "Embedding Enable Click", ); diff --git a/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx b/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx index 5368228bd0d..a5a7ca271da 100644 --- a/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx +++ b/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx @@ -9,7 +9,7 @@ import { t } from "ttag"; import { CardApi, DashboardApi } from "metabase/services"; import * as Urls from "metabase/lib/urls"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; type PublicLink = { id: string, @@ -69,7 +69,7 @@ export default class PublicLinksListing extends Component { } trackEvent(label: string) { - MetabaseAnalytics.trackEvent(`Admin ${this.props.type}`, label); + MetabaseAnalytics.trackStructEvent(`Admin ${this.props.type}`, label); } render() { diff --git a/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx b/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx index ac18a2408b2..e0187897602 100644 --- a/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx +++ b/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx @@ -6,7 +6,7 @@ import { connect } from "react-redux"; import { t } from "ttag"; import title from "metabase/hoc/Title"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseSettings from "metabase/lib/settings"; import AdminLayout from "metabase/components/AdminLayout"; import { NotFound } from "metabase/containers/ErrorPages"; @@ -94,7 +94,7 @@ export default class SettingsEditorApp extends Component { const value = prepareAnalyticsValue(setting); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "General Settings", setting.display_name || setting.key, value, @@ -105,7 +105,7 @@ export default class SettingsEditorApp extends Component { const message = error && (error.message || (error.data && error.data.message)); this.saveStatusRef.current.setSaveError(message); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "General Settings", setting.display_name, "error", diff --git a/frontend/src/metabase/app.js b/frontend/src/metabase/app.js index d665a75024c..ed77382dbc3 100644 --- a/frontend/src/metabase/app.js +++ b/frontend/src/metabase/app.js @@ -33,9 +33,7 @@ import ReactDOM from "react-dom"; import { Provider } from "react-redux"; import { ThemeProvider } from "styled-components"; -import MetabaseAnalytics, { - registerAnalyticsClickListener, -} from "metabase/lib/analytics"; +import { trackPageView, createTracker } from "metabase/lib/analytics"; import MetabaseSettings from "metabase/lib/settings"; import api from "metabase/lib/api"; @@ -85,26 +83,16 @@ function _init(reducers, getRoutes, callback) { document.getElementById("root"), ); - // listen for location changes and use that as a trigger for page view tracking - history.listen(location => { - MetabaseAnalytics.trackPageView(location.pathname); - }); + createTracker(store); + trackPageView(location.pathname); + history.listen(location => trackPageView(location.pathname)); registerVisualizations(); initializeEmbedding(store); - registerAnalyticsClickListener(); - store.dispatch(refreshSiteSettings()); - // enable / disable GA based on opt-out of anonymous tracking - MetabaseSettings.on("anon-tracking-enabled", () => { - window[ - "ga-disable-" + MetabaseSettings.get("ga-code") - ] = MetabaseSettings.trackingEnabled() ? null : true; - }); - MetabaseSettings.on("user-locale", async locale => { // reload locale definition and site settings with the new locale await Promise.all([ diff --git a/frontend/src/metabase/auth/auth.js b/frontend/src/metabase/auth/auth.js index 2a507cff9a3..2354852db5d 100644 --- a/frontend/src/metabase/auth/auth.js +++ b/frontend/src/metabase/auth/auth.js @@ -6,7 +6,7 @@ import { import { push } from "react-router-redux"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { clearGoogleAuthCredentials, deleteSession } from "metabase/lib/auth"; import { refreshSiteSettings } from "metabase/redux/settings"; @@ -21,7 +21,7 @@ export const login = createThunkAction( // NOTE: this request will return a Set-Cookie header for the session await SessionApi.create(credentials); - MetabaseAnalytics.trackEvent("Auth", "Login"); + MetabaseAnalytics.trackStructEvent("Auth", "Login"); // unable to use a top-level `import` here because of a circular dependency const { refreshCurrentUser } = require("metabase/redux/user"); @@ -47,7 +47,7 @@ export const loginGoogle = createThunkAction(LOGIN_GOOGLE, function( token: googleUser.getAuthResponse().id_token, }); - MetabaseAnalytics.trackEvent("Auth", "Google Auth Login"); + MetabaseAnalytics.trackStructEvent("Auth", "Google Auth Login"); // unable to use a top-level `import` here because of a circular dependency const { refreshCurrentUser } = require("metabase/redux/user"); @@ -74,7 +74,7 @@ export const logout = createThunkAction(LOGOUT, function() { // clear Google auth credentials if any are present await clearGoogleAuthCredentials(); - MetabaseAnalytics.trackEvent("Auth", "Logout"); + MetabaseAnalytics.trackStructEvent("Auth", "Logout"); dispatch(push("/auth/login")); diff --git a/frontend/src/metabase/auth/containers/PasswordResetApp.jsx b/frontend/src/metabase/auth/containers/PasswordResetApp.jsx index 0feb1f0403a..36746418104 100644 --- a/frontend/src/metabase/auth/containers/PasswordResetApp.jsx +++ b/frontend/src/metabase/auth/containers/PasswordResetApp.jsx @@ -9,7 +9,7 @@ import Form from "metabase/containers/Form"; import Icon from "metabase/components/Icon"; import MetabaseSettings from "metabase/lib/settings"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { SessionApi } from "metabase/services"; @@ -51,7 +51,7 @@ export default class PasswordResetApp extends Component { password: password, }); - MetabaseAnalytics.trackEvent("Auth", "Password Reset"); + MetabaseAnalytics.trackStructEvent("Auth", "Password Reset"); this.setState({ resetSuccess: true }); }; diff --git a/frontend/src/metabase/dashboard/components/AddSeriesModal/AddSeriesModal.jsx b/frontend/src/metabase/dashboard/components/AddSeriesModal/AddSeriesModal.jsx index c8956935d4d..3a890d811f0 100644 --- a/frontend/src/metabase/dashboard/components/AddSeriesModal/AddSeriesModal.jsx +++ b/frontend/src/metabase/dashboard/components/AddSeriesModal/AddSeriesModal.jsx @@ -8,7 +8,7 @@ import { createSelector } from "reselect"; import Visualization from "metabase/visualizations/components/Visualization"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { color } from "metabase/lib/colors"; import Questions from "metabase/entities/questions"; @@ -90,7 +90,7 @@ export default class AddSeriesModal extends Component { series: this.state.series.concat(card), }); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Dashboard", "Add Series", card.display + ", success", @@ -102,7 +102,7 @@ export default class AddSeriesModal extends Component { }); setTimeout(() => this.setState({ state: null }), 2000); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Dashboard", "Add Series", card.dataset_query.type + ", " + card.display + ", fail", @@ -113,7 +113,7 @@ export default class AddSeriesModal extends Component { series: this.state.series.filter(c => c.id !== card.id), }); - MetabaseAnalytics.trackEvent("Dashboard", "Remove Series"); + MetabaseAnalytics.trackStructEvent("Dashboard", "Remove Series"); } } catch (e) { console.error("AddSeriesModal handleQuestionChange", e); @@ -127,7 +127,7 @@ export default class AddSeriesModal extends Component { handleRemoveSeries(card) { this.setState({ series: this.state.series.filter(c => c.id !== card.id) }); - MetabaseAnalytics.trackEvent("Dashboard", "Remove Series"); + MetabaseAnalytics.trackStructEvent("Dashboard", "Remove Series"); } handleDone = () => { @@ -136,7 +136,11 @@ export default class AddSeriesModal extends Component { attributes: { series: this.state.series }, }); this.props.onClose(); - MetabaseAnalytics.trackEvent("Dashboard", "Edit Series Modal", "done"); + MetabaseAnalytics.trackStructEvent( + "Dashboard", + "Edit Series Modal", + "done", + ); }; handleLoadMetadata = async queries => { diff --git a/frontend/src/metabase/dashboard/components/AddSeriesModal/QuestionList.jsx b/frontend/src/metabase/dashboard/components/AddSeriesModal/QuestionList.jsx index 85da156dfe2..719a4cad89f 100644 --- a/frontend/src/metabase/dashboard/components/AddSeriesModal/QuestionList.jsx +++ b/frontend/src/metabase/dashboard/components/AddSeriesModal/QuestionList.jsx @@ -5,7 +5,7 @@ import { AutoSizer, List } from "react-virtualized"; import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper"; import Icon from "metabase/components/Icon"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { useDebouncedValue } from "metabase/hooks/use-debounced-value"; import { SEARCH_DEBOUNCE_DURATION } from "metabase/lib/constants"; import EmptyState from "metabase/components/EmptyState"; @@ -55,7 +55,11 @@ export const QuestionList = React.memo(function QuestionList({ ); const handleSearchFocus = () => { - MetabaseAnalytics.trackEvent("Dashboard", "Edit Series Modal", "search"); + MetabaseAnalytics.trackStructEvent( + "Dashboard", + "Edit Series Modal", + "search", + ); }; const filteredQuestions = useMemo(() => { diff --git a/frontend/src/metabase/dashboard/components/DashboardGrid.jsx b/frontend/src/metabase/dashboard/components/DashboardGrid.jsx index bb54ab3cbe0..9b949ae426e 100644 --- a/frontend/src/metabase/dashboard/components/DashboardGrid.jsx +++ b/frontend/src/metabase/dashboard/components/DashboardGrid.jsx @@ -9,7 +9,7 @@ import Modal from "metabase/components/Modal"; import { PLUGIN_COLLECTIONS } from "metabase/plugins"; import { getVisualizationRaw } from "metabase/visualizations"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { color } from "metabase/lib/colors"; import { @@ -112,7 +112,7 @@ export default class DashboardGrid extends Component { if (changes.length > 0) { setMultipleDashCardAttributes(changes); - MetabaseAnalytics.trackEvent("Dashboard", "Layout Changed"); + MetabaseAnalytics.trackStructEvent("Dashboard", "Layout Changed"); } }; diff --git a/frontend/src/metabase/dashboard/components/DashboardSidebars.jsx b/frontend/src/metabase/dashboard/components/DashboardSidebars.jsx index d0af4208822..63ebd3a9402 100644 --- a/frontend/src/metabase/dashboard/components/DashboardSidebars.jsx +++ b/frontend/src/metabase/dashboard/components/DashboardSidebars.jsx @@ -9,7 +9,7 @@ import ParameterSidebar from "metabase/parameters/components/ParameterSidebar"; import SharingSidebar from "metabase/sharing/components/SharingSidebar"; import { AddCardSidebar } from "./add-card-sidebar/AddCardSidebar"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; DashboardSidebars.propTypes = { dashboard: PropTypes.object, @@ -75,7 +75,7 @@ export function DashboardSidebars({ dashId: dashboard.id, cardId: cardId, }); - MetabaseAnalytics.trackEvent("Dashboard", "Add Card"); + MetabaseAnalytics.trackStructEvent("Dashboard", "Add Card"); }, [addCardToDashboard, dashboard.id], ); diff --git a/frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx b/frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx index aeb6bd2a88c..8253d169d7e 100644 --- a/frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx +++ b/frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx @@ -1,7 +1,7 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import { t } from "ttag"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import Button from "metabase/components/Button"; import ModalContent from "metabase/components/ModalContent"; @@ -21,7 +21,7 @@ export default class RemoveFromDashboardModal extends Component { }); this.props.onClose(); - MetabaseAnalytics.trackEvent("Dashboard", "Remove Card"); + MetabaseAnalytics.trackStructEvent("Dashboard", "Remove Card"); } render() { diff --git a/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx b/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx index 86ac8bec089..1211f9cd695 100644 --- a/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx +++ b/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx @@ -25,7 +25,7 @@ import { getMetadata } from "metabase/selectors/metadata"; import Dashboards from "metabase/entities/dashboards"; import * as Urls from "metabase/lib/urls"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import * as Q from "metabase/lib/query/query"; import Dimension from "metabase-lib/lib/Dimension"; import { color } from "metabase/lib/colors"; @@ -80,7 +80,7 @@ class AutomaticDashboardApp extends React.Component { ); this.setState({ savedDashboardId: newDashboard.id }); - MetabaseAnalytics.trackEvent("AutoDashboard", "Save"); + MetabaseAnalytics.trackStructEvent("AutoDashboard", "Save"); }; UNSAFE_componentWillReceiveProps(nextProps) { @@ -164,7 +164,10 @@ class AutomaticDashboardApp extends React.Component { to={more} className="ml2" onClick={() => - MetabaseAnalytics.trackEvent("AutoDashboard", "ClickMore") + MetabaseAnalytics.trackStructEvent( + "AutoDashboard", + "ClickMore", + ) } > <Button iconRight="chevronright">{t`Show more about this`}</Button> diff --git a/frontend/src/metabase/dashboard/containers/DashboardSharingEmbeddingModal.jsx b/frontend/src/metabase/dashboard/containers/DashboardSharingEmbeddingModal.jsx index e859f5cd016..3612560901d 100644 --- a/frontend/src/metabase/dashboard/containers/DashboardSharingEmbeddingModal.jsx +++ b/frontend/src/metabase/dashboard/containers/DashboardSharingEmbeddingModal.jsx @@ -8,7 +8,7 @@ import ModalWithTrigger from "metabase/components/ModalWithTrigger"; import EmbedModalContent from "metabase/public/components/widgets/EmbedModalContent"; import * as Urls from "metabase/lib/urls"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { createPublicLink, @@ -60,7 +60,7 @@ class DashboardSharingEmbeddingModal extends Component { aria-disabled={!isLinkEnabled} onClick={() => { if (isLinkEnabled) { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Sharing / Embedding", "dashboard", "Sharing Link Clicked", diff --git a/frontend/src/metabase/dashboard/hoc/DashboardControls.jsx b/frontend/src/metabase/dashboard/hoc/DashboardControls.jsx index 97d8a12b32d..886b70fed4e 100644 --- a/frontend/src/metabase/dashboard/hoc/DashboardControls.jsx +++ b/frontend/src/metabase/dashboard/hoc/DashboardControls.jsx @@ -3,7 +3,7 @@ import React, { Component } from "react"; import { connect } from "react-redux"; import { replace } from "react-router-redux"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { parseHashOptions, stringifyHashOptions } from "metabase/lib/browser"; import screenfull from "screenfull"; @@ -140,7 +140,7 @@ export default (ComposedComponent: React.Class) => ); this.setState({ refreshPeriod }); this.setRefreshElapsed(0); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Dashboard", "Set Refresh", refreshPeriod, diff --git a/frontend/src/metabase/entities/users.js b/frontend/src/metabase/entities/users.js index c756146d89c..b0a00f3ea84 100644 --- a/frontend/src/metabase/entities/users.js +++ b/frontend/src/metabase/entities/users.js @@ -1,6 +1,6 @@ import { assocIn } from "icepick"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseSettings from "metabase/lib/settings"; import MetabaseUtils from "metabase/lib/utils"; @@ -71,12 +71,12 @@ const Users = createEntity({ objectActions: { resentInvite: async ({ id }) => { - MetabaseAnalytics.trackEvent("People Admin", "Resent Invite"); + MetabaseAnalytics.trackStructEvent("People Admin", "Resent Invite"); await UserApi.send_invite({ id }); return { type: RESEND_INVITE }; }, passwordResetEmail: async ({ email }) => { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "People Admin", "Trigger User Password Reset", ); @@ -87,18 +87,21 @@ const Users = createEntity({ { id }, password = MetabaseUtils.generatePassword(), ) => { - MetabaseAnalytics.trackEvent("People Admin", "Manual Password Reset"); + MetabaseAnalytics.trackStructEvent( + "People Admin", + "Manual Password Reset", + ); await UserApi.update_password({ id, password }); return { type: PASSWORD_RESET_MANUAL, payload: { id, password } }; }, deactivate: async ({ id }) => { - MetabaseAnalytics.trackEvent("People Admin", "User Removed"); + MetabaseAnalytics.trackStructEvent("People Admin", "User Removed"); // TODO: move these APIs from services to this file await UserApi.delete({ userId: id }); return { type: DEACTIVATE, payload: { id } }; }, reactivate: async ({ id }) => { - MetabaseAnalytics.trackEvent("People Admin", "User Reactivated"); + MetabaseAnalytics.trackStructEvent("People Admin", "User Reactivated"); // TODO: move these APIs from services to this file const user = await UserApi.reactivate({ userId: id }); return { type: REACTIVATE, payload: user }; diff --git a/frontend/src/metabase/lib/CODENOTIFY b/frontend/src/metabase/lib/CODENOTIFY new file mode 100644 index 00000000000..9e492b788ab --- /dev/null +++ b/frontend/src/metabase/lib/CODENOTIFY @@ -0,0 +1,3 @@ +# See https://github.com/sourcegraph/codenotify for documentation. + +analytics.js @alxnddr @ranquild diff --git a/frontend/src/metabase/lib/analytics.js b/frontend/src/metabase/lib/analytics.js index c9eb1797456..1cdd60313e1 100644 --- a/frontend/src/metabase/lib/analytics.js +++ b/frontend/src/metabase/lib/analytics.js @@ -1,67 +1,107 @@ -/*global ga*/ +import * as Snowplow from "@snowplow/browser-tracker"; +import Settings from "metabase/lib/settings"; +import { isProduction } from "metabase/env"; +import { getUserId } from "metabase/selectors/user"; -import MetabaseSettings from "metabase/lib/settings"; +export const createTracker = store => { + if (isTrackingEnabled()) { + createGoogleAnalyticsTracker(); + createSnowplowTracker(store); + document.body.addEventListener("click", handleStructEventClick, true); + } +}; -import { DEBUG } from "metabase/lib/debug"; +export const trackPageView = url => { + if (isTrackingEnabled() && url) { + trackGoogleAnalyticsPageView(url); + trackSnowplowPageView(url); + } +}; -// Simple module for in-app analytics. Currently sends data to GA but could be extended to anything else. -const MetabaseAnalytics = { - // track a pageview (a.k.a. route change) - trackPageView: function(url: string) { - if (url) { - // scrub query builder urls to remove serialized json queries from path - url = url.lastIndexOf("/q/", 0) === 0 ? "/q/" : url; +export const trackStructEvent = (category, action, label, value) => { + if (isTrackingEnabled() && category && label) { + trackGoogleAnalyticsStructEvent(category, action, label, value); + } +}; - const { tag } = MetabaseSettings.get("version") || {}; +export const trackSchemaEvent = (schema, data) => { + if (isTrackingEnabled() && schema) { + trackSnowplowSchemaEvent(schema, data); + } +}; - if (typeof ga === "function") { - ga("set", "dimension1", tag); - ga("set", "page", url); - ga("send", "pageview", url); - } - } - }, +const isTrackingEnabled = () => { + return isProduction && Settings.trackingEnabled(); +}; - // track an event - trackEvent: function( - category: string, - action?: ?string, - label?: ?(string | number | boolean), - value?: ?number, - ) { - const { tag } = MetabaseSettings.get("version") || {}; +const createGoogleAnalyticsTracker = () => { + const code = Settings.get("ga-code"); + window.ga?.("create", code, "auto"); - // category & action are required, rest are optional - if (typeof ga === "function" && category && action) { - ga("set", "dimension1", tag); - ga("send", "event", category, action, label, value); - } - if (DEBUG) { - console.log("trackEvent", { category, action, label, value }); - } - }, + Settings.on("anon-tracking-enabled", enabled => { + window[`ga-disable-${code}`] = enabled ? null : true; + }); }; -export default MetabaseAnalytics; +const trackGoogleAnalyticsPageView = url => { + const version = Settings.get("version"); + window.ga?.("set", "dimension1", version?.tag); + window.ga?.("set", "page", url); + window.ga?.("send", "pageview", url); +}; -export function registerAnalyticsClickListener() { - document.body.addEventListener( - "click", - function(e) { - let node = e.target; +const trackGoogleAnalyticsStructEvent = (category, action, label, value) => { + const version = Settings.get("version"); + window.ga?.("set", "dimension1", version?.tag); + window.ga?.("send", "event", category, action, label, value); +}; - // check the target and all parent elements - while (node) { - if (node.dataset && node.dataset.metabaseEvent) { - // we expect our event to be a semicolon delimited string - const parts = node.dataset.metabaseEvent - .split(";") - .map(p => p.trim()); - MetabaseAnalytics.trackEvent(...parts); - } - node = node.parentNode; - } +const createSnowplowTracker = store => { + Snowplow.newTracker("sp", "https://sp.metabase.com", { + appId: "metabase", + platform: "web", + cookieSameSite: "Lax", + discoverRootDomain: true, + contexts: { + webPage: true, }, - true, - ); -} + plugins: [createSnowplowPlugin(store)], + }); +}; + +const createSnowplowPlugin = store => { + return { + beforeTrack: () => { + const userId = getUserId(store.getState()); + userId && Snowplow.setUserId(String(userId)); + }, + }; +}; + +const trackSnowplowPageView = url => { + Snowplow.setReferrerUrl("#"); + Snowplow.setCustomUrl(url); + Snowplow.trackPageView(); +}; + +const trackSnowplowSchemaEvent = (schema, version, data) => { + Snowplow.trackSelfDescribingEvent({ + event: { + schema: `iglu:com.metabase/${schema}/jsonschema/${version}`, + data, + }, + }); +}; + +const handleStructEventClick = event => { + if (!isTrackingEnabled()) { + return; + } + + for (let node = event.target; node != null; node = node.parentNode) { + if (node.dataset && node.dataset.metabaseEvent) { + const parts = node.dataset.metabaseEvent.split(";").map(p => p.trim()); + trackStructEvent(...parts); + } + } +}; diff --git a/frontend/src/metabase/lib/redux.js b/frontend/src/metabase/lib/redux.js index e8313b2e0dc..13284271e05 100644 --- a/frontend/src/metabase/lib/redux.js +++ b/frontend/src/metabase/lib/redux.js @@ -300,7 +300,7 @@ function withCachedData(getExistingStatePath, getRequestStatePath) { }; } -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; export function withAnalytics(categoryOrFn, actionOrFn, labelOrFn, valueOrFn) { // thunk decorator: @@ -319,7 +319,7 @@ export function withAnalytics(categoryOrFn, actionOrFn, labelOrFn, valueOrFn) { const action = get(actionOrFn, { category }); const label = get(labelOrFn, { category, action }); const value = get(valueOrFn, { category, action, label }); - MetabaseAnalytics.trackEvent(category, action, label, value); + MetabaseAnalytics.trackStructEvent(category, action, label, value); } catch (error) { console.warn("withAnalytics threw an error:", error); } diff --git a/frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx b/frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx index 40bca0a0d4f..ca9a4351efc 100644 --- a/frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx +++ b/frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx @@ -23,7 +23,7 @@ import { } from "metabase/selectors/settings"; import { getUserIsAdmin } from "metabase/selectors/user"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import type { Parameter, ParameterId } from "metabase-types/types/Parameter"; import type { @@ -202,7 +202,10 @@ export default class EmbedModalContent extends Component { name="close" size={24} onClick={() => { - MetabaseAnalytics.trackEvent("Sharing Modal", "Modal Closed"); + MetabaseAnalytics.trackStructEvent( + "Sharing Modal", + "Modal Closed", + ); onClose(); }} /> diff --git a/frontend/src/metabase/public/components/widgets/SharingPane.jsx b/frontend/src/metabase/public/components/widgets/SharingPane.jsx index 525a5bda74a..8117756ab0c 100644 --- a/frontend/src/metabase/public/components/widgets/SharingPane.jsx +++ b/frontend/src/metabase/public/components/widgets/SharingPane.jsx @@ -12,7 +12,7 @@ import cx from "classnames"; import type { EmbedType } from "./EmbedModalContent"; import type { EmbeddableResource } from "metabase/public/lib/types"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; type Props = { resourceType: string, @@ -72,7 +72,7 @@ export default class SharingPane extends Component { title={t`Disable this public link?`} content={t`This will cause the existing link to stop working. You can re-enable it, but when you do it will be a different link.`} action={() => { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Sharing Modal", "Public Link Disabled", resourceType, @@ -86,7 +86,7 @@ export default class SharingPane extends Component { <Toggle value={false} onChange={() => { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Sharing Modal", "Public Link Enabled", resourceType, diff --git a/frontend/src/metabase/pulse/components/PulseEdit.jsx b/frontend/src/metabase/pulse/components/PulseEdit.jsx index 056994e1485..6b5bdcd4ebd 100644 --- a/frontend/src/metabase/pulse/components/PulseEdit.jsx +++ b/frontend/src/metabase/pulse/components/PulseEdit.jsx @@ -15,7 +15,7 @@ import ActionButton from "metabase/components/ActionButton"; import Button from "metabase/components/Button"; import DeleteModalWithConfirm from "metabase/components/DeleteModalWithConfirm"; import Icon from "metabase/components/Icon"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import ModalWithTrigger from "metabase/components/ModalWithTrigger"; import ModalContent from "metabase/components/ModalContent"; import Subhead from "metabase/components/type/Subhead"; @@ -62,7 +62,7 @@ export default class PulseEdit extends Component { ); this.props.fetchPulseFormInput(); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( this.props.pulseId ? "PulseEdit" : "PulseCreate", "Start", ); @@ -73,7 +73,7 @@ export default class PulseEdit extends Component { await this.props.updateEditingPulse(pulse); await this.props.saveEditingPulse(); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( this.props.pulseId ? "PulseEdit" : "PulseCreate", "Complete", this.props.pulse.cards.length, @@ -88,7 +88,7 @@ export default class PulseEdit extends Component { handleArchive = async () => { await this.props.setPulseArchived(this.props.pulse, true); - MetabaseAnalytics.trackEvent("PulseArchive", "Complete"); + MetabaseAnalytics.trackStructEvent("PulseArchive", "Complete"); this.props.onChangeLocation(Urls.collection(this.props.collection)); }; @@ -97,7 +97,7 @@ export default class PulseEdit extends Component { await this.props.setPulseArchived(this.props.pulse, false); this.setPulse({ ...this.props.pulse, archived: false }); - MetabaseAnalytics.trackEvent("PulseUnarchive", "Complete"); + MetabaseAnalytics.trackStructEvent("PulseUnarchive", "Complete"); }; setPulse = pulse => { diff --git a/frontend/src/metabase/pulse/components/PulseEditCards.jsx b/frontend/src/metabase/pulse/components/PulseEditCards.jsx index 253b16ea9fb..f4212ac3005 100644 --- a/frontend/src/metabase/pulse/components/PulseEditCards.jsx +++ b/frontend/src/metabase/pulse/components/PulseEditCards.jsx @@ -9,7 +9,7 @@ import PulseCardPreview from "./PulseCardPreview"; import QuestionSelect from "metabase/containers/QuestionSelect"; // import Query from "metabase/lib/query"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { color } from "metabase/lib/colors"; @@ -56,7 +56,7 @@ export default class PulseEditCards extends Component { } trackPulseEvent = (eventName: string, eventValue: string) => { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( this.props.pulseId ? "PulseEdit" : "PulseCreate", eventName, eventValue, diff --git a/frontend/src/metabase/pulse/components/PulseEditChannels.jsx b/frontend/src/metabase/pulse/components/PulseEditChannels.jsx index 2c5d4d1e4c4..41134c31da7 100644 --- a/frontend/src/metabase/pulse/components/PulseEditChannels.jsx +++ b/frontend/src/metabase/pulse/components/PulseEditChannels.jsx @@ -14,7 +14,7 @@ import Toggle from "metabase/components/Toggle"; import Icon from "metabase/components/Icon"; import ChannelSetupMessage from "metabase/components/ChannelSetupMessage"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { channelIsValid, createChannel } from "metabase/lib/pulse"; @@ -59,7 +59,7 @@ export default class PulseEditChannels extends Component { this.props.setPulse({ ...pulse, channels: pulse.channels.concat(channel) }); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( this.props.pulseId ? "PulseEdit" : "PulseCreate", "AddChannel", type, @@ -86,7 +86,7 @@ export default class PulseEditChannels extends Component { const { pulse } = this.props; const channels = [...pulse.channels]; - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( this.props.pulseId ? "PulseEdit" : "PulseCreate", channels[index].channel_type + ":" + changedProp.name, changedProp.value, @@ -123,7 +123,7 @@ export default class PulseEditChannels extends Component { ), ); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( this.props.pulseId ? "PulseEdit" : "PulseCreate", "RemoveChannel", type, diff --git a/frontend/src/metabase/pulse/components/RecipientPicker.jsx b/frontend/src/metabase/pulse/components/RecipientPicker.jsx index f5f3751718f..98f6130bd32 100644 --- a/frontend/src/metabase/pulse/components/RecipientPicker.jsx +++ b/frontend/src/metabase/pulse/components/RecipientPicker.jsx @@ -3,7 +3,7 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import { t } from "ttag"; import { recipientIsValid } from "metabase/lib/pulse"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseSettings from "metabase/lib/settings"; import MetabaseUtils from "metabase/lib/utils"; import TokenField from "metabase/components/TokenField"; @@ -41,7 +41,7 @@ export default class RecipientPicker extends Component { [...next].filter(r => !previous.has(r))[0] || [...previous].filter(r => !next.has(r))[0]; - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( isNewPulse ? "PulseCreate" : "PulseEdit", newRecipients.length > recipients.length ? "AddRecipient" diff --git a/frontend/src/metabase/query_builder/actions.js b/frontend/src/metabase/query_builder/actions.js index c81b3bc3f16..8186c7f7eb2 100644 --- a/frontend/src/metabase/query_builder/actions.js +++ b/frontend/src/metabase/query_builder/actions.js @@ -13,7 +13,7 @@ import { push, replace } from "react-router-redux"; import { setErrorPage } from "metabase/redux/app"; import { loadMetadataForQuery } from "metabase/redux/metadata"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { startTimer } from "metabase/lib/performance"; import { loadCard, @@ -412,7 +412,7 @@ export const initializeQB = (location, params, queryParams) => { } } - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "QueryBuilder", "Query Loaded", card.dataset_query.type, @@ -424,7 +424,7 @@ export const initializeQB = (location, params, queryParams) => { // if this is the users first time loading a saved card on the QB then show them the newb modal if (cardId && currentUser.is_qbnewb) { uiControls.isShowingNewbModal = true; - MetabaseAnalytics.trackEvent("QueryBuilder", "Show Newb Modal"); + MetabaseAnalytics.trackStructEvent("QueryBuilder", "Show Newb Modal"); } if (card.archived) { @@ -483,7 +483,7 @@ export const initializeQB = (location, params, queryParams) => { } } - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "QueryBuilder", "Query Started", card.dataset_query.type, @@ -567,7 +567,7 @@ export const initializeQB = (location, params, queryParams) => { export const TOGGLE_DATA_REFERENCE = "metabase/qb/TOGGLE_DATA_REFERENCE"; export const toggleDataReference = createAction(TOGGLE_DATA_REFERENCE, () => { - MetabaseAnalytics.trackEvent("QueryBuilder", "Toggle Data Reference"); + MetabaseAnalytics.trackStructEvent("QueryBuilder", "Toggle Data Reference"); }); export const TOGGLE_TEMPLATE_TAGS_EDITOR = @@ -575,7 +575,10 @@ export const TOGGLE_TEMPLATE_TAGS_EDITOR = export const toggleTemplateTagsEditor = createAction( TOGGLE_TEMPLATE_TAGS_EDITOR, () => { - MetabaseAnalytics.trackEvent("QueryBuilder", "Toggle Template Tags Editor"); + MetabaseAnalytics.trackStructEvent( + "QueryBuilder", + "Toggle Template Tags Editor", + ); }, ); @@ -588,7 +591,7 @@ export const setIsShowingTemplateTagsEditor = isShowingTemplateTagsEditor => ({ export const TOGGLE_SNIPPET_SIDEBAR = "metabase/qb/TOGGLE_SNIPPET_SIDEBAR"; export const toggleSnippetSidebar = createAction(TOGGLE_SNIPPET_SIDEBAR, () => { - MetabaseAnalytics.trackEvent("QueryBuilder", "Toggle Snippet Sidebar"); + MetabaseAnalytics.trackStructEvent("QueryBuilder", "Toggle Snippet Sidebar"); }); export const SET_IS_SHOWING_SNIPPET_SIDEBAR = @@ -657,7 +660,7 @@ export const closeQbNewbModal = createThunkAction(CLOSE_QB_NEWB_MODAL, () => { // persist the fact that this user has seen the NewbModal const { currentUser } = getState(); await UserApi.update_qbnewb({ id: currentUser.id }); - MetabaseAnalytics.trackEvent("QueryBuilder", "Close Newb Modal"); + MetabaseAnalytics.trackStructEvent("QueryBuilder", "Close Newb Modal"); }; }); @@ -1021,7 +1024,7 @@ export const apiCreateQuestion = question => { dispatch(setRequestUnloaded(["entities", "databases"])); dispatch(updateUrl(createdQuestion.card(), { dirty: false })); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "QueryBuilder", "Create Card", createdQuestion.query().datasetQuery().type, @@ -1061,7 +1064,7 @@ export const apiUpdateQuestion = question => { // so we want the databases list to be re-fetched next time we hit "New Question" so it shows up dispatch(setRequestUnloaded(["entities", "databases"])); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "QueryBuilder", "Update Card", updatedQuestion.query().datasetQuery().type, @@ -1126,7 +1129,7 @@ export const runQuestionQuery = ({ }) .then(queryResults => { queryTimer(duration => - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "QueryBuilder", "Run Query", question.query().datasetQuery().type, diff --git a/frontend/src/metabase/query_builder/components/AlertModals.jsx b/frontend/src/metabase/query_builder/components/AlertModals.jsx index 2b0a4df3fc2..a9f517bdfed 100644 --- a/frontend/src/metabase/query_builder/components/AlertModals.jsx +++ b/frontend/src/metabase/query_builder/components/AlertModals.jsx @@ -45,7 +45,7 @@ import { getDefaultAlert, } from "metabase-lib/lib/Alert"; import MetabaseCookies from "metabase/lib/cookies"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; // types import type { AlertType } from "metabase-lib/lib/Alert"; @@ -129,7 +129,11 @@ export class CreateAlertModalContent extends Component { await updateUrl(question.card(), { dirty: false }); onAlertCreated(); - MetabaseAnalytics.trackEvent("Alert", "Create", alert.alert_condition); + MetabaseAnalytics.trackStructEvent( + "Alert", + "Create", + alert.alert_condition, + ); }; proceedFromEducationalScreen = () => { @@ -329,7 +333,7 @@ export class UpdateAlertModalContent extends Component { await updateUrl(question.card(), { dirty: false }); onAlertUpdated(); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Alert", "Update", modifiedAlert.alert_condition, diff --git a/frontend/src/metabase/query_builder/components/ExtendedOptions.jsx b/frontend/src/metabase/query_builder/components/ExtendedOptions.jsx index e98de1d2557..7f476db23e8 100644 --- a/frontend/src/metabase/query_builder/components/ExtendedOptions.jsx +++ b/frontend/src/metabase/query_builder/components/ExtendedOptions.jsx @@ -11,7 +11,7 @@ import LimitWidget from "./LimitWidget"; import SortWidget from "./SortWidget"; import Popover from "metabase/components/Popover"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery"; import type { DatasetQuery } from "metabase-types/types/Card"; @@ -58,7 +58,7 @@ export class ExtendedOptionsPopover extends Component { .updateExpression(name, expression, previousName) .update(setDatasetQuery); this.setState({ editExpression: null }); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "QueryBuilder", "Set Expression", !_.isEmpty(previousName), @@ -70,13 +70,13 @@ export class ExtendedOptionsPopover extends Component { query.removeExpression(name).update(setDatasetQuery); this.setState({ editExpression: null }); - MetabaseAnalytics.trackEvent("QueryBuilder", "Remove Expression"); + MetabaseAnalytics.trackStructEvent("QueryBuilder", "Remove Expression"); } setLimit = limit => { const { query, setDatasetQuery } = this.props; query.updateLimit(limit).update(setDatasetQuery); - MetabaseAnalytics.trackEvent("QueryBuilder", "Set Limit", limit); + MetabaseAnalytics.trackStructEvent("QueryBuilder", "Set Limit", limit); if (this.props.onClose) { this.props.onClose(); } @@ -146,7 +146,7 @@ export class ExtendedOptionsPopover extends Component { onAddExpression={() => this.setState({ editExpression: true })} onEditExpression={name => { this.setState({ editExpression: name }); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "QueryBuilder", "Show Edit Custom Field", ); diff --git a/frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx b/frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx index 180a59f53eb..802b84c10c9 100644 --- a/frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx +++ b/frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx @@ -5,7 +5,7 @@ import { t, jt } from "ttag"; import { getFilterOptions, setFilterOptions } from "metabase/lib/query/filter"; import CheckBox from "metabase/components/CheckBox"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import type { FieldFilter } from "metabase-types/types/Query"; @@ -79,7 +79,12 @@ export default class FilterOptions extends Component { [name]: !options[name], }), ); - MetabaseAnalytics.trackEvent("QueryBuilder", "Filter", "SetOption", name); + MetabaseAnalytics.trackStructEvent( + "QueryBuilder", + "Filter", + "SetOption", + name, + ); } toggleOptionValue(name) { diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx index 379d475efd1..640b9fe7c45 100644 --- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx +++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx @@ -11,7 +11,7 @@ import CardTagEditor from "./CardTagEditor"; import TagEditorHelp from "./TagEditorHelp"; import SidebarContent from "metabase/query_builder/components/SidebarContent"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import NativeQuery from "metabase-lib/lib/queries/NativeQuery"; import type { DatasetQuery } from "metabase-types/types/Card"; @@ -54,7 +54,7 @@ export default class TagEditorSidebar extends React.Component { setSection(section) { this.setState({ section: section }); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "QueryBuilder", "Template Tag Editor Section Change", section, diff --git a/frontend/src/metabase/query_builder/containers/QuestionEmbedWidget.jsx b/frontend/src/metabase/query_builder/containers/QuestionEmbedWidget.jsx index 8b5683b55b3..15f4b3bd381 100644 --- a/frontend/src/metabase/query_builder/containers/QuestionEmbedWidget.jsx +++ b/frontend/src/metabase/query_builder/containers/QuestionEmbedWidget.jsx @@ -10,7 +10,7 @@ import EmbedModalContent from "metabase/public/components/widgets/EmbedModalCont import * as Urls from "metabase/lib/urls"; import MetabaseSettings from "metabase/lib/settings"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { getParametersFromCard } from "metabase/meta/Card"; import { @@ -99,7 +99,7 @@ export function QuestionEmbedWidgetTrigger({ onClick }) { tooltip={t`Sharing`} className="mx1 hide sm-show text-brand-hover cursor-pointer" onClick={() => { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Sharing / Embedding", "question", "Sharing Link Clicked", diff --git a/frontend/src/metabase/redux/undo.js b/frontend/src/metabase/redux/undo.js index a889791c2d9..0276f58a639 100644 --- a/frontend/src/metabase/redux/undo.js +++ b/frontend/src/metabase/redux/undo.js @@ -1,7 +1,7 @@ import _ from "underscore"; import { createAction, createThunkAction } from "metabase/lib/redux"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; const ADD_UNDO = "metabase/questions/ADD_UNDO"; const DISMISS_UNDO = "metabase/questions/DISMISS_UNDO"; @@ -21,7 +21,7 @@ export const dismissUndo = createAction( DISMISS_UNDO, (undoId, track = true) => { if (track) { - MetabaseAnalytics.trackEvent("Undo", "Dismiss Undo"); + MetabaseAnalytics.trackStructEvent("Undo", "Dismiss Undo"); } return undoId; }, @@ -29,7 +29,7 @@ export const dismissUndo = createAction( export const performUndo = createThunkAction(PERFORM_UNDO, undoId => { return (dispatch, getState) => { - MetabaseAnalytics.trackEvent("Undo", "Perform Undo"); + MetabaseAnalytics.trackStructEvent("Undo", "Perform Undo"); const undo = _.findWhere(getState().undo, { id: undoId }); if (undo) { undo.actions.map(action => dispatch(action)); diff --git a/frontend/src/metabase/reference/metrics/MetricDetailContainer.jsx b/frontend/src/metabase/reference/metrics/MetricDetailContainer.jsx index 757d7420442..8e184af2f8e 100644 --- a/frontend/src/metabase/reference/metrics/MetricDetailContainer.jsx +++ b/frontend/src/metabase/reference/metrics/MetricDetailContainer.jsx @@ -3,7 +3,7 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import { connect } from "react-redux"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import MetricSidebar from "./MetricSidebar"; import SidebarLayout from "metabase/components/SidebarLayout"; @@ -56,7 +56,7 @@ export default class MetricDetailContainer extends Component { startEditing() { const { metric, router } = this.props; router.replace(`/reference/metrics/${metric.id}/edit`); - MetabaseAnalytics.trackEvent("Data Reference", "Started Editing"); + MetabaseAnalytics.trackStructEvent("Data Reference", "Started Editing"); } endEditing() { diff --git a/frontend/src/metabase/reference/reference.js b/frontend/src/metabase/reference/reference.js index 38062f744a2..c6569684edc 100644 --- a/frontend/src/metabase/reference/reference.js +++ b/frontend/src/metabase/reference/reference.js @@ -2,7 +2,7 @@ import { assoc } from "icepick"; import { handleActions, createAction } from "metabase/lib/redux"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { filterUntouchedFields, isEmptyObject } from "./utils.js"; @@ -26,11 +26,11 @@ export const startLoading = createAction(START_LOADING); export const endLoading = createAction(END_LOADING); export const startEditing = createAction(START_EDITING, () => { - MetabaseAnalytics.trackEvent("Data Reference", "Started Editing"); + MetabaseAnalytics.trackStructEvent("Data Reference", "Started Editing"); }); export const endEditing = createAction(END_EDITING, () => { - MetabaseAnalytics.trackEvent("Data Reference", "Ended Editing"); + MetabaseAnalytics.trackStructEvent("Data Reference", "Ended Editing"); }); export const expandFormula = createAction(EXPAND_FORMULA); diff --git a/frontend/src/metabase/setup/actions.js b/frontend/src/metabase/setup/actions.js index 422f172840b..38cd0aaea1a 100644 --- a/frontend/src/metabase/setup/actions.js +++ b/frontend/src/metabase/setup/actions.js @@ -1,7 +1,7 @@ import { createAction } from "redux-actions"; import { createThunkAction } from "metabase/lib/redux"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseSettings from "metabase/lib/settings"; import { SetupApi, UtilApi } from "metabase/services"; @@ -70,7 +70,7 @@ export const submitSetup = createThunkAction(SUBMIT_SETUP, function() { return null; } catch (error) { - MetabaseAnalytics.trackEvent("Setup", "Error", "save"); + MetabaseAnalytics.trackStructEvent("Setup", "Error", "save"); return error; } diff --git a/frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx b/frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx index 93641dc7c23..a7d0771e533 100644 --- a/frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx +++ b/frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx @@ -8,7 +8,7 @@ import { Box } from "grid-styled"; import StepTitle from "./StepTitle"; import CollapsedStep from "./CollapsedStep"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { DEFAULT_SCHEDULES } from "metabase/admin/databases/database"; import Databases from "metabase/entities/databases"; @@ -27,7 +27,7 @@ export default class DatabaseConnectionStep extends Component { chooseDatabaseEngine = e => { // FIXME: - // MetabaseAnalytics.trackEvent("Setup", "Choose Database", engine); + // MetabaseAnalytics.trackStructEvent("Setup", "Choose Database", engine); }; handleSubmit = async database => { @@ -49,7 +49,7 @@ export default class DatabaseConnectionStep extends Component { } if (formError) { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Setup", "Error", "database validation: " + database.engine, @@ -81,7 +81,11 @@ export default class DatabaseConnectionStep extends Component { details: database, }); - MetabaseAnalytics.trackEvent("Setup", "Database Step", database.engine); + MetabaseAnalytics.trackStructEvent( + "Setup", + "Database Step", + database.engine, + ); } }; @@ -91,7 +95,7 @@ export default class DatabaseConnectionStep extends Component { details: null, }); - MetabaseAnalytics.trackEvent("Setup", "Database Step"); + MetabaseAnalytics.trackStructEvent("Setup", "Database Step"); }; render() { diff --git a/frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx b/frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx index 7a12febc115..6f3ff24825b 100644 --- a/frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx +++ b/frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx @@ -10,7 +10,7 @@ import Icon from "metabase/components/Icon"; import Databases from "metabase/entities/databases"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; export default class DatabaseSchedulingStep extends Component { static propTypes = { @@ -28,7 +28,11 @@ export default class DatabaseSchedulingStep extends Component { details: database, }); - MetabaseAnalytics.trackEvent("Setup", "Database Step", this.state.engine); + MetabaseAnalytics.trackStructEvent( + "Setup", + "Database Step", + this.state.engine, + ); }; render() { diff --git a/frontend/src/metabase/setup/components/PreferencesStep.jsx b/frontend/src/metabase/setup/components/PreferencesStep.jsx index 67e764c5c96..37904d8c402 100644 --- a/frontend/src/metabase/setup/components/PreferencesStep.jsx +++ b/frontend/src/metabase/setup/components/PreferencesStep.jsx @@ -3,7 +3,7 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import { t, jt } from "ttag"; import { Box } from "grid-styled"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseSettings from "metabase/lib/settings"; import Toggle from "metabase/components/Toggle"; @@ -42,7 +42,7 @@ export default class PreferencesStep extends Component { payload && payload.data ? getErrorMessage(payload.data) : null; this.setState({ errorMessage }); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Setup", "Preferences Step", this.props.allowTracking, diff --git a/frontend/src/metabase/setup/components/Setup.jsx b/frontend/src/metabase/setup/components/Setup.jsx index 86255c7ef9d..39323b8c13c 100644 --- a/frontend/src/metabase/setup/components/Setup.jsx +++ b/frontend/src/metabase/setup/components/Setup.jsx @@ -4,7 +4,7 @@ import PropTypes from "prop-types"; import { t } from "ttag"; import { color } from "metabase/lib/colors"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseSettings from "metabase/lib/settings"; import { b64hash_to_utf8 } from "metabase/lib/encoding"; @@ -50,7 +50,7 @@ export default class Setup extends Component { completeWelcome() { this.props.setActiveStep(LANGUAGE_STEP_NUMBER); - MetabaseAnalytics.trackEvent("Setup", "Welcome"); + MetabaseAnalytics.trackStructEvent("Setup", "Welcome"); } componentDidMount() { @@ -117,7 +117,7 @@ export default class Setup extends Component { } if (!prevProps.setupComplete && this.props.setupComplete) { - MetabaseAnalytics.trackEvent("Setup", "Complete"); + MetabaseAnalytics.trackStructEvent("Setup", "Complete"); } } diff --git a/frontend/src/metabase/setup/components/UserStep.jsx b/frontend/src/metabase/setup/components/UserStep.jsx index 8e3ea0e9e64..1f2eccc7797 100644 --- a/frontend/src/metabase/setup/components/UserStep.jsx +++ b/frontend/src/metabase/setup/components/UserStep.jsx @@ -3,7 +3,7 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import { Flex, Box } from "grid-styled"; import { t } from "ttag"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import User from "metabase/entities/users"; @@ -29,7 +29,11 @@ export default class UserStep extends Component { await this.props.validatePassword(values.password); return {}; } catch (error) { - MetabaseAnalytics.trackEvent("Setup", "Error", "password validation"); + MetabaseAnalytics.trackStructEvent( + "Setup", + "Error", + "password validation", + ); return error.data.errors; } }; @@ -40,7 +44,7 @@ export default class UserStep extends Component { details: _.omit(values, "password_confirm"), }); - MetabaseAnalytics.trackEvent("Setup", "User Details Step"); + MetabaseAnalytics.trackStructEvent("Setup", "User Details Step"); }; render() { diff --git a/frontend/src/metabase/visualizations/components/CardRenderer.jsx b/frontend/src/metabase/visualizations/components/CardRenderer.jsx index 201b5d244a6..b996c9997d5 100644 --- a/frontend/src/metabase/visualizations/components/CardRenderer.jsx +++ b/frontend/src/metabase/visualizations/components/CardRenderer.jsx @@ -5,7 +5,7 @@ import ReactDOM from "react-dom"; import _ from "underscore"; import ExplicitSize from "metabase/components/ExplicitSize"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { startTimer } from "metabase/lib/performance"; import { isSameSeries } from "metabase/visualizations/lib/utils"; @@ -21,7 +21,10 @@ type Props = VisualizationProps & { // We track this as part of the render loop. // It's throttled to prevent pounding GA on every prop update. -const trackEventThrottled = _.throttle(MetabaseAnalytics.trackEvent, 10000); +const trackEventThrottled = _.throttle( + MetabaseAnalytics.trackStructEvent, + 10000, +); @ExplicitSize({ wrapped: true }) export default class CardRenderer extends Component { diff --git a/frontend/src/metabase/visualizations/components/ChartClickActions.jsx b/frontend/src/metabase/visualizations/components/ChartClickActions.jsx index 779eb324c1c..b1cfacf96d5 100644 --- a/frontend/src/metabase/visualizations/components/ChartClickActions.jsx +++ b/frontend/src/metabase/visualizations/components/ChartClickActions.jsx @@ -10,7 +10,7 @@ import Tooltip from "metabase/components/Tooltip"; import "./ChartClickActions.css"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { performAction } from "metabase/visualizations/lib/action"; @@ -104,7 +104,7 @@ export default class ChartClickActions extends Component { handleClickAction = (action: ClickAction) => { const { dispatch, onChangeCardAndRun } = this.props; if (action.popover) { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Actions", "Open Click Action Popover", getGALabelForAction(action), @@ -116,7 +116,7 @@ export default class ChartClickActions extends Component { onChangeCardAndRun, }); if (didPerform) { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Actions", "Executed Click Action", getGALabelForAction(action), @@ -143,7 +143,7 @@ export default class ChartClickActions extends Component { <PopoverContent onChangeCardAndRun={({ nextCard }) => { if (popoverAction) { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Action", "Executed Click Action", getGALabelForAction(popoverAction), @@ -152,7 +152,7 @@ export default class ChartClickActions extends Component { onChangeCardAndRun({ nextCard }); }} onClose={() => { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Action", "Dismissed Click Action Menu", getGALabelForAction(popoverAction), @@ -194,7 +194,10 @@ export default class ChartClickActions extends Component { target={clicked.element} targetEvent={clicked.event} onClose={() => { - MetabaseAnalytics.trackEvent("Action", "Dismissed Click Action Menu"); + MetabaseAnalytics.trackStructEvent( + "Action", + "Dismissed Click Action Menu", + ); this.close(); }} verticalAttachments={["top", "bottom"]} @@ -310,7 +313,7 @@ export const ChartClickAction = ({ to={action.url()} className={className} onClick={() => - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Actions", "Executed Click Action", getGALabelForAction(action), diff --git a/frontend/src/metabase/visualizations/components/ChartSettings.jsx b/frontend/src/metabase/visualizations/components/ChartSettings.jsx index 0a193f84bf1..7c480323825 100644 --- a/frontend/src/metabase/visualizations/components/ChartSettings.jsx +++ b/frontend/src/metabase/visualizations/components/ChartSettings.jsx @@ -13,7 +13,7 @@ import Visualization from "metabase/visualizations/components/Visualization"; import ChartSettingsWidget from "./ChartSettingsWidget"; import { getSettingsWidgetsForSeries } from "metabase/visualizations/lib/settings/visualization"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { getVisualizationTransformed, extractRemappings, @@ -92,7 +92,7 @@ class ChartSettings extends Component { }; handleResetSettings = () => { - MetabaseAnalytics.trackEvent("Chart Settings", "Reset Settings"); + MetabaseAnalytics.trackStructEvent("Chart Settings", "Reset Settings"); const settings = getClickBehaviorSettings(this._getSettings()); this.props.onChange(settings); diff --git a/frontend/src/metabase/visualizations/components/Visualization.jsx b/frontend/src/metabase/visualizations/components/Visualization.jsx index ef75b79850d..3633e3d7afd 100644 --- a/frontend/src/metabase/visualizations/components/Visualization.jsx +++ b/frontend/src/metabase/visualizations/components/Visualization.jsx @@ -9,7 +9,7 @@ import Icon from "metabase/components/Icon"; import Tooltip from "metabase/components/Tooltip"; import { t, jt } from "ttag"; import { duration, formatNumber } from "metabase/lib/formatting"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { getVisualizationTransformed, @@ -317,7 +317,7 @@ export default class Visualization extends React.PureComponent { handleVisualizationClick = (clicked: ClickObject) => { if (clicked) { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Actions", "Clicked", `${clicked.column ? "column" : ""} ${clicked.value ? "value" : ""} ${ diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx index fead4d55d43..6b765b24191 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx +++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx @@ -19,7 +19,7 @@ import { SortableElement, } from "metabase/components/sortable"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; import { isNumeric, isString } from "metabase/lib/schema_metadata"; import _ from "underscore"; @@ -145,7 +145,7 @@ export default class ChartSettingsTableFormatting extends React.Component { }} onRemove={index => { onChange([...value.slice(0, index), ...value.slice(index + 1)]); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Chart Settings", "Table Formatting", "Remove Rule", @@ -155,7 +155,7 @@ export default class ChartSettingsTableFormatting extends React.Component { const newValue = [...value]; newValue.splice(to, 0, newValue.splice(from, 1)[0]); onChange(newValue); - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Chart Settings", "Table Formatting", "Move Rule", @@ -397,7 +397,7 @@ const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => { <ColorRangePicker value={rule.colors} onChange={colors => { - MetabaseAnalytics.trackEvent( + MetabaseAnalytics.trackStructEvent( "Chart Settings", "Table Formatting", "Select Range Colors", diff --git a/frontend/src/metabase/visualizations/lib/settings.js b/frontend/src/metabase/visualizations/lib/settings.js index d2217084fd9..ab684079e56 100644 --- a/frontend/src/metabase/visualizations/lib/settings.js +++ b/frontend/src/metabase/visualizations/lib/settings.js @@ -15,7 +15,7 @@ import ChartSettingFieldsPartition from "metabase/visualizations/components/sett import ChartSettingColorPicker from "metabase/visualizations/components/settings/ChartSettingColorPicker"; import ChartSettingColorsPicker from "metabase/visualizations/components/settings/ChartSettingColorsPicker"; -import MetabaseAnalytics from "metabase/lib/analytics"; +import * as MetabaseAnalytics from "metabase/lib/analytics"; export type SettingId = string; @@ -251,7 +251,7 @@ export function updateSettings( changedSettings: Settings, ): Settings { for (const key of Object.keys(changedSettings)) { - MetabaseAnalytics.trackEvent("Chart Settings", "Change Setting", key); + MetabaseAnalytics.trackStructEvent("Chart Settings", "Change Setting", key); } const newSettings = { ...storedSettings, diff --git a/frontend/test/__support__/mocks.js b/frontend/test/__support__/mocks.js index 0ab0ba88e41..f85aa87319a 100644 --- a/frontend/test/__support__/mocks.js +++ b/frontend/test/__support__/mocks.js @@ -1,6 +1,7 @@ /* eslint-disable no-import-assign*/ global.ga = () => {}; +global.snowplow = () => {}; global.ace.define = () => {}; global.ace.require = () => {}; diff --git a/package.json b/package.json index 28a9592466a..080c36e9e6e 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "yarn": ">=1.12.3" }, "dependencies": { + "@snowplow/browser-tracker": "^3.1.6", "@visx/axis": "1.8.0", "@visx/grid": "1.16.0", "@visx/group": "1.7.0", diff --git a/resources/frontend_client/inline_js/index_ganalytics.js b/resources/frontend_client/inline_js/index_ganalytics.js index 7923de834ff..f825107dbd6 100644 --- a/resources/frontend_client/inline_js/index_ganalytics.js +++ b/resources/frontend_client/inline_js/index_ganalytics.js @@ -1,12 +1,3 @@ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); - -// if we are not doing tracking then go ahead and disable GA now so we never even track the initial pageview -const tracking = window.MetabaseBootstrap["anon-tracking-enabled"]; -const ga_code = window.MetabaseBootstrap["ga-code"]; -if (!tracking) { - window['ga-disable-'+ga_code] = true; -} - -ga('create', ga_code, 'auto'); diff --git a/src/metabase/server/middleware/security.clj b/src/metabase/server/middleware/security.clj index e10e2b31ce1..4c0e9ac1202 100644 --- a/src/metabase/server/middleware/security.clj +++ b/src/metabase/server/middleware/security.clj @@ -80,6 +80,9 @@ ;; Google analytics (when (public-settings/anon-tracking-enabled) "www.google-analytics.com") + ;; Snowplow analytics + (when (public-settings/anon-tracking-enabled) + "sp.metabase.com") ;; Webpack dev server (when config/is-dev? "localhost:8080 ws://localhost:8080")] diff --git a/yarn.lock b/yarn.lock index 5477d5bee17..a022133e538 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2147,6 +2147,33 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@snowplow/browser-tracker-core@3.1.6": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@snowplow/browser-tracker-core/-/browser-tracker-core-3.1.6.tgz#660e62311d5aafa9769d3e8ac315bba1939ff0ca" + integrity sha512-IOsQaI5EOaaOHxxgIGAeSZIR7oM8sre0dDjgBwqsUFpQgpha2/c5mP0ui59hXhBzGFlXA+10wml50OXx/YwMCw== + dependencies: + "@snowplow/tracker-core" "3.1.6" + sha1 "^1.1.1" + tslib "^2.3.0" + uuid "^3.4.0" + +"@snowplow/browser-tracker@^3.1.6": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@snowplow/browser-tracker/-/browser-tracker-3.1.6.tgz#f8f41b12c79e9886fda7d10924a6a487112d5cf6" + integrity sha512-6VY/3cgEvRzE6u52n0OSZR9pSjFzud4cICNIZrH2YVcF8/lCrHKscatBVffALrLShSglSB4FZPG9CtfPCAivow== + dependencies: + "@snowplow/browser-tracker-core" "3.1.6" + "@snowplow/tracker-core" "3.1.6" + tslib "^2.3.0" + +"@snowplow/tracker-core@3.1.6": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@snowplow/tracker-core/-/tracker-core-3.1.6.tgz#f1f87782e64a2d59ab881cbb7e6e1a1e43972283" + integrity sha512-b2O1xXEqQRzuhIol+3mhk9Xvz/smiW+6pQ3W1fVWonXrYoKtTyak2K+98phj2e0V+H/jUXeviH8adLEdbRkCYA== + dependencies: + tslib "^2.3.0" + uuid "^3.4.0" + "@testing-library/cypress@^5.0.2": version "5.3.1" resolved "https://registry.yarnpkg.com/@testing-library/cypress/-/cypress-5.3.1.tgz#96c9bd0f72eb2330b4b5154dd71e81d2c644ce62" @@ -4259,6 +4286,11 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== +"charenc@>= 0.0.1": + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + check-more-types@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" @@ -5219,6 +5251,11 @@ crossfilter@^1.3.12: resolved "https://registry.yarnpkg.com/crossfilter/-/crossfilter-1.3.12.tgz#147d7236a98c45c69f78bdc3a99d6fb00f70930c" integrity sha1-FH1yNqmMRcafeL3DqZ1vsA9wkww= +"crypt@>= 0.0.1": + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + crypto-browserify@^3.11.0, crypto-browserify@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -13997,6 +14034,14 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" +sha1@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/sha1/-/sha1-1.1.1.tgz#addaa7a93168f393f19eb2b15091618e2700f848" + integrity sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg= + dependencies: + charenc ">= 0.0.1" + crypt ">= 0.0.1" + shadow-cljs-jar@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b" @@ -15160,6 +15205,11 @@ tslib@^2.0.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== +tslib@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" -- GitLab