From 6becc1a78f0b6c913e1c7af60e0934cd063d90ba Mon Sep 17 00:00:00 2001 From: Thomas Schmidt <somtom91@gmail.com> Date: Thu, 29 Aug 2024 17:35:53 +0200 Subject: [PATCH] Add new generic analytics event and bring types to trackSchemaEvent (#46972) --- .github/CODEOWNERS | 1 + .../scenarios/onboarding/uploads.cy.spec.js | 2 +- .../src/metabase-types/analytics/account.ts | 20 +++ .../src/metabase-types/analytics/action.ts | 44 ++++++ .../metabase-types/analytics/browse_data.ts | 24 +++ .../src/metabase-types/analytics/cleanup.ts | 20 +++ .../metabase-types/analytics/csv-upload.ts | 55 +++++++ .../src/metabase-types/analytics/dashboard.ts | 142 ++++++++++++++++++ .../src/metabase-types/analytics/database.ts | 34 +++++ .../src/metabase-types/analytics/downloads.ts | 25 +++ .../metabase-types/analytics/embed-flow.ts | 97 ++++++++++++ .../metabase-types/analytics/embed-share.ts | 27 ++++ .../analytics/embedding-homepage.ts | 33 ++++ .../src/metabase-types/analytics/event.ts | 20 +++ .../src/metabase-types/analytics/index.ts | 23 +++ .../src/metabase-types/analytics/invite.ts | 18 +++ .../src/metabase-types/analytics/metabot.ts | 37 +++++ .../src/metabase-types/analytics/model.ts | 16 ++ .../src/metabase-types/analytics/question.ts | 116 ++++++++++++++ .../src/metabase-types/analytics/schema.ts | 51 +++++++ .../src/metabase-types/analytics/search.ts | 65 ++++++++ .../metabase-types/analytics/serialization.ts | 38 +++++ .../src/metabase-types/analytics/settings.ts | 29 ++++ .../src/metabase-types/analytics/setup.ts | 64 ++++++++ .../src/metabase-types/analytics/timeline.ts | 22 +++ .../src/metabase-types/analytics/upsell.ts | 24 +++ .../src/metabase/admin/settings/analytics.ts | 4 +- .../admin/upsells/components/analytics.ts | 4 +- frontend/src/metabase/browse/analytics.ts | 10 +- frontend/src/metabase/dashboard/analytics.ts | 84 +++++++---- .../components/EmbedHomepage/analytics.ts | 7 +- .../{analytics.js => analytics-untyped.js} | 62 ++------ frontend/src/metabase/lib/analytics.ts | 62 ++++++++ frontend/src/metabase/metabot/analytics.ts | 9 +- .../SidebarItems/UploadCSV/analytics.ts | 6 +- .../StaticEmbedSetupPane.tsx | 4 +- frontend/src/metabase/public/lib/analytics.ts | 20 +-- .../src/metabase/query_builder/analytics.js | 14 +- frontend/src/metabase/querying/analytics.ts | 10 +- .../src/metabase/redux/downloads-analytics.ts | 5 +- frontend/src/metabase/search/analytics.ts | 10 +- frontend/src/metabase/setup/analytics.ts | 14 +- .../visualizations/lib/settings/analytics.ts | 4 +- .../com.metabase/csvupload/jsonschema/1-0-4 | 94 ------------ .../com.metabase/event/jsonschema/1-0-0 | 64 ++++++++ 45 files changed, 1297 insertions(+), 237 deletions(-) create mode 100644 frontend/src/metabase-types/analytics/account.ts create mode 100644 frontend/src/metabase-types/analytics/action.ts create mode 100644 frontend/src/metabase-types/analytics/browse_data.ts create mode 100644 frontend/src/metabase-types/analytics/cleanup.ts create mode 100644 frontend/src/metabase-types/analytics/csv-upload.ts create mode 100644 frontend/src/metabase-types/analytics/dashboard.ts create mode 100644 frontend/src/metabase-types/analytics/database.ts create mode 100644 frontend/src/metabase-types/analytics/downloads.ts create mode 100644 frontend/src/metabase-types/analytics/embed-flow.ts create mode 100644 frontend/src/metabase-types/analytics/embed-share.ts create mode 100644 frontend/src/metabase-types/analytics/embedding-homepage.ts create mode 100644 frontend/src/metabase-types/analytics/event.ts create mode 100644 frontend/src/metabase-types/analytics/index.ts create mode 100644 frontend/src/metabase-types/analytics/invite.ts create mode 100644 frontend/src/metabase-types/analytics/metabot.ts create mode 100644 frontend/src/metabase-types/analytics/model.ts create mode 100644 frontend/src/metabase-types/analytics/question.ts create mode 100644 frontend/src/metabase-types/analytics/schema.ts create mode 100644 frontend/src/metabase-types/analytics/search.ts create mode 100644 frontend/src/metabase-types/analytics/serialization.ts create mode 100644 frontend/src/metabase-types/analytics/settings.ts create mode 100644 frontend/src/metabase-types/analytics/setup.ts create mode 100644 frontend/src/metabase-types/analytics/timeline.ts create mode 100644 frontend/src/metabase-types/analytics/upsell.ts rename frontend/src/metabase/lib/{analytics.js => analytics-untyped.js} (72%) create mode 100644 frontend/src/metabase/lib/analytics.ts delete mode 100644 snowplow/iglu-client-embedded/schemas/com.metabase/csvupload/jsonschema/1-0-4 create mode 100644 snowplow/iglu-client-embedded/schemas/com.metabase/event/jsonschema/1-0-0 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 76e63ec3350..07cc4b2c354 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -12,3 +12,4 @@ enterprise/backend/src/metabase_enterprise/task/truncate_audit_table.clj @noahmo enterprise/backend/src/metabase_enterprise/internal_user.clj @noahmoss src/metabase/**/*permissions* @noahmoss src/metabase/integrations @noahmoss +snowplow/* @metabase/data diff --git a/e2e/test/scenarios/onboarding/uploads.cy.spec.js b/e2e/test/scenarios/onboarding/uploads.cy.spec.js index 20efb357193..a3c0841400a 100644 --- a/e2e/test/scenarios/onboarding/uploads.cy.spec.js +++ b/e2e/test/scenarios/onboarding/uploads.cy.spec.js @@ -47,7 +47,7 @@ describeWithSnowplow( // Snowplow expectGoodSnowplowEvent({ event: "csv_upload_clicked", - source: "left_nav", + triggered_from: "left_nav", }); expectGoodSnowplowEvent({ event: testFile.valid ? "csv_upload_successful" : "csv_upload_failed", diff --git a/frontend/src/metabase-types/analytics/account.ts b/frontend/src/metabase-types/analytics/account.ts new file mode 100644 index 00000000000..fd177540cba --- /dev/null +++ b/frontend/src/metabase-types/analytics/account.ts @@ -0,0 +1,20 @@ +type AccountEventSchema = { + event: string; + version?: string | null; + link?: string | null; +}; + +type ValidateEvent< + T extends AccountEventSchema & + Record<Exclude<keyof T, keyof AccountEventSchema>, never>, +> = T; + +export type NewUserCreatedEvent = ValidateEvent<{ + event: "new_user_created"; +}>; + +export type NewInstanceCreatedEvent = ValidateEvent<{ + event: "new_instance_created"; +}>; + +export type AccountEvent = NewUserCreatedEvent | NewInstanceCreatedEvent; diff --git a/frontend/src/metabase-types/analytics/action.ts b/frontend/src/metabase-types/analytics/action.ts new file mode 100644 index 00000000000..c64157dad97 --- /dev/null +++ b/frontend/src/metabase-types/analytics/action.ts @@ -0,0 +1,44 @@ +type ActionEventSchema = { + event: string; + type: string; + action_id: number; + num_parameters?: number | null; + context?: string | null; +}; + +type ValidateEvent< + T extends ActionEventSchema & + Record<Exclude<keyof T, keyof ActionEventSchema>, never>, +> = T; + +type ActionType = "http" | "query" | "implicit"; + +export type ActionCreatedEvent = ValidateEvent<{ + event: "action_created"; + type: ActionType; + action_id: number; +}>; + +export type ActionUpdatedEvent = ValidateEvent<{ + event: "action_updated"; + type: ActionType; + action_id: number; +}>; + +export type ActionDeletedEvent = ValidateEvent<{ + event: "action_deleted"; + type: ActionType; + action_id: number; +}>; + +export type ActionExecutedEvent = ValidateEvent<{ + event: "action_executed"; + type: ActionType; + action_id: number; +}>; + +export type ActionEvent = + | ActionCreatedEvent + | ActionUpdatedEvent + | ActionDeletedEvent + | ActionExecutedEvent; diff --git a/frontend/src/metabase-types/analytics/browse_data.ts b/frontend/src/metabase-types/analytics/browse_data.ts new file mode 100644 index 00000000000..6cccb87bf2f --- /dev/null +++ b/frontend/src/metabase-types/analytics/browse_data.ts @@ -0,0 +1,24 @@ +type BrowseDataEventSchema = { + event: string; + model_id?: number | null; + table_id?: number | null; +}; + +type ValidateEvent< + T extends BrowseDataEventSchema & + Record<Exclude<keyof T, keyof BrowseDataEventSchema>, never>, +> = T; + +export type BrowseDataModelClickedEvent = ValidateEvent<{ + event: "browse_data_model_clicked"; + model_id: number; +}>; + +export type BrowseDataTableClickedEvent = ValidateEvent<{ + event: "browse_data_table_clicked"; + table_id: number; +}>; + +export type BrowseDataEvent = + | BrowseDataModelClickedEvent + | BrowseDataTableClickedEvent; diff --git a/frontend/src/metabase-types/analytics/cleanup.ts b/frontend/src/metabase-types/analytics/cleanup.ts new file mode 100644 index 00000000000..204aa243853 --- /dev/null +++ b/frontend/src/metabase-types/analytics/cleanup.ts @@ -0,0 +1,20 @@ +type CleanupEventSchema = { + event: string; + collection_id?: number | null; + total_stale_items_found?: number | null; + cutoff_date?: string | null; +}; + +type ValidateEvent< + T extends CleanupEventSchema & + Record<Exclude<keyof T, keyof CleanupEventSchema>, never>, +> = T; + +export type StaleItemsReadEvent = ValidateEvent<{ + event: "stale_items_read"; + collection_id: number | null; + total_stale_items_found: number; + cutoff_date: string; +}>; + +export type CleanupEvent = StaleItemsReadEvent; diff --git a/frontend/src/metabase-types/analytics/csv-upload.ts b/frontend/src/metabase-types/analytics/csv-upload.ts new file mode 100644 index 00000000000..3fbbfe308e7 --- /dev/null +++ b/frontend/src/metabase-types/analytics/csv-upload.ts @@ -0,0 +1,55 @@ +type CsvUploadEventSchema = { + event: string; + model_id?: number | null; + upload_seconds?: number | null; + size_mb: number | null; + num_columns: number | null; + num_rows: number | null; + generated_columns: number | null; +}; + +type ValidateEvent< + T extends CsvUploadEventSchema & + Record<Exclude<keyof T, keyof CsvUploadEventSchema>, never>, +> = T; + +export type CsvUploadSuccessfulEvent = ValidateEvent<{ + event: "csv_upload_successful"; + model_id: number; + size_mb: number; + num_columns: number; + num_rows: number; + generated_columns: number; + upload_seconds: number; +}>; + +export type CsvUploadFailedEvent = ValidateEvent<{ + event: "csv_upload_failed"; + size_mb: number; + num_columns: number; + num_rows: number; + generated_columns: number; +}>; + +export type CsvAppendSuccessfulEvent = ValidateEvent<{ + event: "csv_append_successful"; + size_mb: number; + num_columns: number; + num_rows: number; + generated_columns: number; + upload_seconds: number; +}>; + +export type CsvAppendFailedEvent = ValidateEvent<{ + event: "csv_append_failed"; + size_mb: number; + num_columns: number; + num_rows: number; + generated_columns: number; +}>; + +export type CsvUploadEvent = + | CsvUploadSuccessfulEvent + | CsvUploadFailedEvent + | CsvAppendSuccessfulEvent + | CsvAppendFailedEvent; diff --git a/frontend/src/metabase-types/analytics/dashboard.ts b/frontend/src/metabase-types/analytics/dashboard.ts new file mode 100644 index 00000000000..ca7badda503 --- /dev/null +++ b/frontend/src/metabase-types/analytics/dashboard.ts @@ -0,0 +1,142 @@ +type DashboardEventSchema = { + event: string; + dashboard_id: number; + question_id?: number | null; + num_tabs?: number | null; + total_num_tabs?: number | null; + duration_milliseconds?: number | null; + section_layout?: string | null; + full_width?: boolean | null; + dashboard_accessed_via?: string | null; +}; + +type ValidateEvent< + T extends DashboardEventSchema & + Record<Exclude<keyof T, keyof DashboardEventSchema>, never>, +> = T; + +export type DashboardCreatedEvent = ValidateEvent<{ + event: "dashboard_created"; + dashboard_id: number; +}>; + +export type DashboardSavedEvent = ValidateEvent<{ + event: "dashboard_saved"; + dashboard_id: number; + duration_milliseconds: number; +}>; + +export type QuestionAddedToDashboardEvent = ValidateEvent<{ + event: "question_added_to_dashboard"; + dashboard_id: number; + question_id: number; +}>; + +export type AutoApplyFiltersDisabledEvent = ValidateEvent<{ + event: "auto_apply_filters_disabled"; + dashboard_id: number; +}>; + +export type DashboardTabCreatedEvent = ValidateEvent<{ + event: "dashboard_tab_created"; + dashboard_id: number; +}>; + +export type DashboardTabDeletedEvent = ValidateEvent<{ + event: "dashboard_tab_deleted"; + dashboard_id: number; +}>; + +export type DashboardTabDuplicatedEvent = ValidateEvent<{ + event: "dashboard_tab_duplicated"; + dashboard_id: number; +}>; + +export type NewTextCardCreatedEvent = ValidateEvent<{ + event: "new_text_card_created"; + dashboard_id: number; +}>; + +export type NewHeadingCardCreatedEvent = ValidateEvent<{ + event: "new_heading_card_created"; + dashboard_id: number; +}>; + +export type NewLinkCardCreatedEvent = ValidateEvent<{ + event: "new_link_card_created"; + dashboard_id: number; +}>; + +export type NewActionCardCreatedEvent = ValidateEvent<{ + event: "new_action_card_created"; + dashboard_id: number; +}>; + +export type CardSetToHideWhenNoResultsEvent = ValidateEvent<{ + event: "card_set_to_hide_when_no_results"; + dashboard_id: number; +}>; + +export type DashboardPdfExportedEvent = ValidateEvent<{ + event: "dashboard_pdf_exported"; + dashboard_id: number; + dashboard_accessed_via: + | "internal" + | "public-link" + | "static-embed" + | "interactive-iframe-embed" + | "sdk-embed"; +}>; + +export type CardMovedToTabEvent = ValidateEvent<{ + event: "card_moved_to_tab"; + dashboard_id: number; +}>; + +export type DashboardCardDuplicatedEvent = ValidateEvent<{ + event: "dashboard_card_duplicated"; + dashboard_id: number; +}>; + +export type DashboardCardReplacedEvent = ValidateEvent<{ + event: "dashboard_card_replaced"; + dashboard_id: number; +}>; + +export type DashboardSectionAddedEvent = ValidateEvent<{ + event: "dashboard_section_added"; + dashboard_id: number; + section_layout: string; +}>; + +export type DashboardWidthToggledEvent = ValidateEvent<{ + event: "dashboard_width_toggled"; + dashboard_id: number; + full_width: boolean; +}>; + +export type DashboardFilterRequiredEvent = ValidateEvent<{ + event: "dashboard_filter_required"; + dashboard_id: number; +}>; + +export type DashboardEvent = + | DashboardCreatedEvent + | DashboardSavedEvent + | QuestionAddedToDashboardEvent + | AutoApplyFiltersDisabledEvent + | DashboardTabCreatedEvent + | DashboardTabDeletedEvent + | DashboardTabDuplicatedEvent + | NewTextCardCreatedEvent + | NewHeadingCardCreatedEvent + | NewLinkCardCreatedEvent + | NewActionCardCreatedEvent + | CardSetToHideWhenNoResultsEvent + | DashboardPdfExportedEvent + | CardMovedToTabEvent + | DashboardCardDuplicatedEvent + | DashboardCardReplacedEvent + | DashboardSectionAddedEvent + | DashboardWidthToggledEvent + | DashboardFilterRequiredEvent; diff --git a/frontend/src/metabase-types/analytics/database.ts b/frontend/src/metabase-types/analytics/database.ts new file mode 100644 index 00000000000..06a58439d6b --- /dev/null +++ b/frontend/src/metabase-types/analytics/database.ts @@ -0,0 +1,34 @@ +type DatabaseEventSchema = { + event: string; + database?: string | null; + database_id?: number | null; + error_type?: string | null; + source?: string | null; + dbms_version?: string | null; +}; + +type ValidateEvent< + T extends DatabaseEventSchema & + Record<Exclude<keyof T, keyof DatabaseEventSchema>, never>, +> = T; + +export type DatabaseConnectionSuccessfulEvent = ValidateEvent<{ + event: "database_connection_successful"; + database: string; + database_id: number; + source: "setup" | "admin"; + dbms_version: string; +}>; + +export type DatabaseConnectionFailedEvent = ValidateEvent<{ + event: "database_connection_failed"; + database: string; + database_id: number; + error_type: string; + source: "setup" | "admin"; + dbms_version: string; +}>; + +export type DatabaseEvent = + | DatabaseConnectionSuccessfulEvent + | DatabaseConnectionFailedEvent; diff --git a/frontend/src/metabase-types/analytics/downloads.ts b/frontend/src/metabase-types/analytics/downloads.ts new file mode 100644 index 00000000000..1e4d765a8e8 --- /dev/null +++ b/frontend/src/metabase-types/analytics/downloads.ts @@ -0,0 +1,25 @@ +type DownloadsEventSchema = { + event: string; + resource_type?: string | null; + accessed_via?: string | null; + export_type?: string | null; +}; + +type ValidateEvent< + T extends DownloadsEventSchema & + Record<Exclude<keyof T, keyof DownloadsEventSchema>, never>, +> = T; + +export type DownloadResultsClickedEvent = ValidateEvent<{ + event: "download_results_clicked"; + resource_type: "question" | "dashcard" | "ad-hoc-question"; + accessed_via: + | "internal" + | "public-link" + | "static-embed" + | "interactive-iframe-embed" + | "sdk-embed"; + export_type: "csv" | "xlsx" | "json" | "png"; +}>; + +export type DownloadsEvent = DownloadResultsClickedEvent; diff --git a/frontend/src/metabase-types/analytics/embed-flow.ts b/frontend/src/metabase-types/analytics/embed-flow.ts new file mode 100644 index 00000000000..480e6824627 --- /dev/null +++ b/frontend/src/metabase-types/analytics/embed-flow.ts @@ -0,0 +1,97 @@ +type EmbedFlowParams = { + locked?: number; + enabled?: number; + disabled?: number; +}; + +type EmbedFlowAppearance = { + background?: boolean; + titled?: boolean; + bordered?: boolean; + theme?: string; + font?: string; + downloads?: boolean | null; + hide_download_button?: boolean | null; +}; + +type EmbedFlowEventSchema = { + event: string; + artifact: string; + new_embed?: boolean | null; + params?: EmbedFlowParams | null; + first_published_at?: string | null; + language?: string | null; + location?: string | null; + code?: string | null; + appearance?: EmbedFlowAppearance | null; + format?: string | null; + source?: string | null; + time_since_creation?: number | null; + time_since_initial_publication?: number | null; + is_example_dashboard?: boolean | null; +}; + +type ValidateEvent< + T extends EmbedFlowEventSchema & + Record<Exclude<keyof T, keyof EmbedFlowEventSchema>, never>, +> = T; + +type EmbedFlowArtifact = "dashboard" | "question"; + +export type StaticEmbedDiscardedEvent = ValidateEvent<{ + event: "static_embed_discarded"; + artifact: EmbedFlowArtifact; +}>; + +export type StaticEmbedPublishedEvent = ValidateEvent<{ + event: "static_embed_published"; + artifact: EmbedFlowArtifact; + params: EmbedFlowParams; + new_embed: boolean; + time_since_creation: number; + time_since_initial_publication: number | null; + is_example_dashboard: boolean; +}>; + +export type StaticEmbedUnpublishedEvent = ValidateEvent<{ + event: "static_embed_unpublished"; + artifact: EmbedFlowArtifact; + time_since_creation: number; + time_since_initial_publication: number | null; +}>; + +export type StaticEmbedCodeCopiedEvent = ValidateEvent<{ + event: "static_embed_code_copied"; + artifact: EmbedFlowArtifact; + language: string; + location: "code_overview" | "code_params" | "code_appearance"; + code: "backend" | "view"; + appearance: EmbedFlowAppearance; +}>; + +export type PublicLinkCopiedEvent = ValidateEvent<{ + event: "public_link_copied"; + artifact: EmbedFlowArtifact; + format: "csv" | "xlsx" | "json" | "html" | null; +}>; + +export type PublicEmbedCodeCopiedEvent = ValidateEvent<{ + event: "public_embed_code_copied"; + artifact: EmbedFlowArtifact; + source: "public-embed" | "public-share"; +}>; + +export type PublicLinkRemovedEvent = ValidateEvent<{ + event: "public_link_removed"; + artifact: EmbedFlowArtifact; + source: "public-embed" | "public-share"; +}>; + +export type EmbedFlowEvent = + | StaticEmbedDiscardedEvent + | StaticEmbedPublishedEvent + | StaticEmbedUnpublishedEvent + | StaticEmbedCodeCopiedEvent + | PublicLinkCopiedEvent + | PublicEmbedCodeCopiedEvent + | PublicLinkRemovedEvent; diff --git a/frontend/src/metabase-types/analytics/embed-share.ts b/frontend/src/metabase-types/analytics/embed-share.ts new file mode 100644 index 00000000000..979772b888f --- /dev/null +++ b/frontend/src/metabase-types/analytics/embed-share.ts @@ -0,0 +1,27 @@ +type EmbedShareEventSchema = { + event: string; + authorized_origins_set?: boolean | null; + number_embedded_questions?: number | null; + number_embedded_dashboards?: number | null; +}; + +type ValidateEvent< + T extends EmbedShareEventSchema & + Record<Exclude<keyof T, keyof EmbedShareEventSchema>, never>, +> = T; + +export type EmbeddingEnabledEvent = ValidateEvent<{ + event: "embedding_enabled"; + authorized_origins_set: boolean; + number_embedded_questions: number; + number_embedded_dashboards: number; +}>; + +export type EmbeddingDisabledEvent = ValidateEvent<{ + event: "embedding_disabled"; + authorized_origins_set: boolean; + number_embedded_questions: number; + number_embedded_dashboards: number; +}>; + +export type EmbedShareEvent = EmbeddingEnabledEvent | EmbeddingDisabledEvent; diff --git a/frontend/src/metabase-types/analytics/embedding-homepage.ts b/frontend/src/metabase-types/analytics/embedding-homepage.ts new file mode 100644 index 00000000000..973a22f2536 --- /dev/null +++ b/frontend/src/metabase-types/analytics/embedding-homepage.ts @@ -0,0 +1,33 @@ +type EmbeddingHomepageEventSchema = { + event: string; + dismiss_reason?: string | null; + initial_tab?: string | null; +}; + +type ValidateEvent< + T extends EmbeddingHomepageEventSchema & + Record<Exclude<keyof T, keyof EmbeddingHomepageEventSchema>, never>, +> = T; + +export type EmbeddingHomepageDismissedEvent = ValidateEvent<{ + event: "embedding_homepage_dismissed"; + dismiss_reason: + | "dismissed-done" + | "dismissed-run-into-issues" + | "dismissed-not-interested-now"; +}>; + +export type EmbeddingHomepageQuickstartClickEvent = ValidateEvent<{ + event: "embedding_homepage_quickstart_click"; + initial_tab: "static" | "interactive"; +}>; + +export type EmbeddingHomepageExampleDashboardClickEvent = ValidateEvent<{ + event: "embedding_homepage_example_dashboard_click"; + initial_tab: "static" | "interactive"; +}>; + +export type EmbeddingHomepageEvent = + | EmbeddingHomepageDismissedEvent + | EmbeddingHomepageQuickstartClickEvent + | EmbeddingHomepageExampleDashboardClickEvent; diff --git a/frontend/src/metabase-types/analytics/event.ts b/frontend/src/metabase-types/analytics/event.ts new file mode 100644 index 00000000000..9a9b54ca913 --- /dev/null +++ b/frontend/src/metabase-types/analytics/event.ts @@ -0,0 +1,20 @@ +type SimpleEventSchema = { + event: string; + target_id?: number | null; + triggered_from?: string | null; + duration_ms?: number | null; + result?: string | null; + event_detail?: string | null; +}; + +type ValidateEvent< + T extends SimpleEventSchema & + Record<Exclude<keyof T, keyof SimpleEventSchema>, never>, +> = T; + +export type CsvUploadClickedEvent = ValidateEvent<{ + event: "csv_upload_clicked"; + triggered_from: "left_nav"; +}>; + +export type SimpleEvent = CsvUploadClickedEvent; diff --git a/frontend/src/metabase-types/analytics/index.ts b/frontend/src/metabase-types/analytics/index.ts new file mode 100644 index 00000000000..d207f4d560e --- /dev/null +++ b/frontend/src/metabase-types/analytics/index.ts @@ -0,0 +1,23 @@ +export * from "./account"; +export * from "./action"; +export * from "./browse_data"; +export * from "./cleanup"; +export * from "./csv-upload"; +export * from "./dashboard"; +export * from "./database"; +export * from "./downloads"; +export * from "./embed-flow"; +export * from "./embed-share"; +export * from "./embedding-homepage"; +export * from "./event"; +export * from "./invite"; +export * from "./metabot"; +export * from "./model"; +export * from "./question"; +export * from "./serialization"; +export * from "./schema"; +export * from "./search"; +export * from "./settings"; +export * from "./setup"; +export * from "./timeline"; +export * from "./upsell"; diff --git a/frontend/src/metabase-types/analytics/invite.ts b/frontend/src/metabase-types/analytics/invite.ts new file mode 100644 index 00000000000..d23dc565c85 --- /dev/null +++ b/frontend/src/metabase-types/analytics/invite.ts @@ -0,0 +1,18 @@ +type InviteEventSchema = { + event: string; + invited_user_id: number; + source?: string | null; +}; + +type ValidateEvent< + T extends InviteEventSchema & + Record<Exclude<keyof T, keyof InviteEventSchema>, never>, +> = T; + +export type InviteSentEvent = ValidateEvent<{ + event: "invite_sent"; + invited_user_id: number; + source: "setup" | "admin"; +}>; + +export type InviteEvent = InviteSentEvent; diff --git a/frontend/src/metabase-types/analytics/metabot.ts b/frontend/src/metabase-types/analytics/metabot.ts new file mode 100644 index 00000000000..66f831f1d44 --- /dev/null +++ b/frontend/src/metabase-types/analytics/metabot.ts @@ -0,0 +1,37 @@ +type MetabotEventSchema = { + event: string; + entity_type?: string | null; + feedback_type?: string | null; + result_type?: string | null; + prompt_template_versions?: string[] | null; + visualization_type?: string | null; + is_rerun?: boolean | null; +}; + +type ValidateEvent< + T extends MetabotEventSchema & + Record<Exclude<keyof T, keyof MetabotEventSchema>, never>, +> = T; + +export type MetabotQueryRunEvent = ValidateEvent<{ + event: "metabot_query_run"; + entity_type: "database" | "model" | null; + result_type: "success" | "failure" | "bad-sql"; + prompt_template_versions: string[] | null; + visualization_type: string | null; + is_rerun: boolean; +}>; + +export type MetabotFeedbackReceivedEvent = ValidateEvent<{ + event: "metabot_feedback_received"; + entity_type: "database" | "model" | null; + feedback_type: + | "great" + | "wrong_data" + | "incorrect_result" + | "invalid_sql" + | null; + prompt_template_versions: string[] | null; +}>; + +export type MetabotEvent = MetabotQueryRunEvent | MetabotFeedbackReceivedEvent; diff --git a/frontend/src/metabase-types/analytics/model.ts b/frontend/src/metabase-types/analytics/model.ts new file mode 100644 index 00000000000..0cab99a25ce --- /dev/null +++ b/frontend/src/metabase-types/analytics/model.ts @@ -0,0 +1,16 @@ +type ModelEventSchema = { + event: string; + model_id: number; +}; + +type ValidateEvent< + T extends ModelEventSchema & + Record<Exclude<keyof T, keyof ModelEventSchema>, never>, +> = T; + +export type IndexModelEntitiesEnabledEvent = ValidateEvent<{ + event: "index_model_entities_enabled"; + model_id: number; +}>; + +export type ModelEvent = IndexModelEntitiesEnabledEvent; diff --git a/frontend/src/metabase-types/analytics/question.ts b/frontend/src/metabase-types/analytics/question.ts new file mode 100644 index 00000000000..43aadbaed06 --- /dev/null +++ b/frontend/src/metabase-types/analytics/question.ts @@ -0,0 +1,116 @@ +type QuestionEventSchema = { + event: string; + question_id: number; + type?: string | null; + method?: string | null; + visualization_type?: string | null; + database_id?: number | null; + custom_expressions_used?: string[] | null; +}; + +type ValidateEvent< + T extends QuestionEventSchema & + Record<Exclude<keyof T, keyof QuestionEventSchema>, never>, +> = T; + +export type NewQuestionSavedEvent = ValidateEvent<{ + event: "new_question_saved"; + question_id: number; + database_id: number | null; + type: "simple_question" | "custom_question" | "native_question"; + method: "from_scratch" | "existing_question"; + visualization_type: string; +}>; + +export type TurnIntoModelClickedEvent = ValidateEvent<{ + event: "turn_into_model_clicked"; + question_id: number; +}>; + +export type NotebookNativePreviewShownEvent = ValidateEvent<{ + event: "notebook_native_preview_shown"; + question_id: number; +}>; + +export type NotebookNativePreviewHiddenEvent = ValidateEvent<{ + event: "notebook_native_preview_hidden"; + question_id: number; +}>; + +export type ColumnCombineViaShortcutEvent = ValidateEvent<{ + event: "column_combine_via_shortcut"; + question_id: number; + database_id: number | null; + custom_expressions_used: string[]; +}>; + +export type ColumnCombineViaColumnHeaderEvent = ValidateEvent<{ + event: "column_combine_via_column_header"; + question_id: number; + database_id: number | null; + custom_expressions_used: string[]; +}>; + +export type ColumnCombineViaPlusModalEvent = ValidateEvent<{ + event: "column_combine_via_plus_modal"; + question_id: number; + database_id: number | null; + custom_expressions_used: string[]; +}>; + +export type ColumnCompareViaShortcutEvent = ValidateEvent<{ + event: "column_compare_via_shortcut"; + question_id: number; + database_id: number | null; + custom_expressions_used: string[]; +}>; + +export type ColumnCompareViaColumnHeaderEvent = ValidateEvent<{ + event: "column_compare_via_column_header"; + question_id: number; + database_id: number | null; + custom_expressions_used: string[]; +}>; + +export type ColumnCompareViaPlusModalEvent = ValidateEvent<{ + event: "column_compare_via_plus_modal"; + question_id: number; + database_id: number | null; + custom_expressions_used: string[]; +}>; + +export type ColumnExtractViaShortcutEvent = ValidateEvent<{ + event: "column_extract_via_shortcut"; + question_id: number; + database_id: number | null; + custom_expressions_used: string[]; +}>; + +export type ColumnExtractViaColumnHeaderEvent = ValidateEvent<{ + event: "column_extract_via_column_header"; + question_id: number; + database_id: number | null; + custom_expressions_used: string[]; +}>; + +export type ColumnExtractViaPlusModalEvent = ValidateEvent<{ + event: "column_extract_via_plus_modal"; + question_id: number; + database_id: number | null; + custom_expressions_used: string[]; +}>; + +export type QuestionEvent = + | NewQuestionSavedEvent + | TurnIntoModelClickedEvent + | NotebookNativePreviewShownEvent + | NotebookNativePreviewHiddenEvent + | ColumnCombineViaShortcutEvent + | ColumnCombineViaColumnHeaderEvent + | ColumnCombineViaPlusModalEvent + | ColumnCompareViaShortcutEvent + | ColumnCompareViaColumnHeaderEvent + | ColumnCompareViaPlusModalEvent + | ColumnExtractViaShortcutEvent + | ColumnExtractViaColumnHeaderEvent + | ColumnExtractViaPlusModalEvent; diff --git a/frontend/src/metabase-types/analytics/schema.ts b/frontend/src/metabase-types/analytics/schema.ts new file mode 100644 index 00000000000..da1111cc606 --- /dev/null +++ b/frontend/src/metabase-types/analytics/schema.ts @@ -0,0 +1,51 @@ +import type { AccountEvent } from "./account"; +import type { ActionEvent } from "./action"; +import type { BrowseDataEvent } from "./browse_data"; +import type { CleanupEvent } from "./cleanup"; +import type { CsvUploadEvent } from "./csv-upload"; +import type { DashboardEvent } from "./dashboard"; +import type { DatabaseEvent } from "./database"; +import type { DownloadsEvent } from "./downloads"; +import type { EmbedFlowEvent } from "./embed-flow"; +import type { EmbedShareEvent } from "./embed-share"; +import type { EmbeddingHomepageEvent } from "./embedding-homepage"; +import type { SimpleEvent } from "./event"; +import type { InviteEvent } from "./invite"; +import type { MetabotEvent } from "./metabot"; +import type { ModelEvent } from "./model"; +import type { QuestionEvent } from "./question"; +import type { SearchEvent } from "./search"; +import type { SerializationEvent } from "./serialization"; +import type { SettingsEvent } from "./settings"; +import type { SetupEvent } from "./setup"; +import type { TimelineEvent } from "./timeline"; +import type { UpsellEvent } from "./upsell"; + +export type SchemaEventMap = { + account: AccountEvent; + action: ActionEvent; + browse_data: BrowseDataEvent; + cleanup: CleanupEvent; + csvupload: CsvUploadEvent; + dashboard: DashboardEvent; + database: DatabaseEvent; + downloads: DownloadsEvent; + embed_flow: EmbedFlowEvent; + embed_share: EmbedShareEvent; + embedding_homepage: EmbeddingHomepageEvent; + event: SimpleEvent; + invite: InviteEvent; + metabot: MetabotEvent; + model: ModelEvent; + question: QuestionEvent; + search: SearchEvent; + serialization: SerializationEvent; + settings: SettingsEvent; + setup: SetupEvent; + timeline: TimelineEvent; + upsell: UpsellEvent; +}; + +export type SchemaType = keyof SchemaEventMap; + +export type SchemaEvent = SchemaEventMap[SchemaType]; diff --git a/frontend/src/metabase-types/analytics/search.ts b/frontend/src/metabase-types/analytics/search.ts new file mode 100644 index 00000000000..0483de67bc5 --- /dev/null +++ b/frontend/src/metabase-types/analytics/search.ts @@ -0,0 +1,65 @@ +type SearchEventSchema = { + event: string; + runtime_milliseconds?: number | null; + context?: string | null; + total_results?: number | null; + page_results?: number | null; + position?: number | null; + target_type?: string | null; + content_type?: string[] | null; + creator?: boolean | null; + last_editor?: boolean | null; + creation_date?: boolean | null; + last_edit_date?: boolean | null; + verified_items?: boolean | null; + search_native_queries?: boolean | null; + search_archived?: boolean | null; +}; + +type ValidateEvent< + T extends SearchEventSchema & + Record<Exclude<keyof T, keyof SearchEventSchema>, never>, +> = T; + +type SearchContentType = + | "dashboard" + | "card" + | "dataset" + | "segment" + | "metric" + | "collection" + | "database" + | "table" + | "action" + | "indexed-entity"; + +type SearchContext = + | "search-app" + | "search-bar" + | "command-palette" + | "entity-picker"; + +export type SearchQueryEvent = ValidateEvent<{ + event: "search_query"; + runtime_milliseconds: number; + context: SearchContext | null; + total_results: number; + page_results: number | null; + content_type: SearchContentType[] | null; + creator: boolean; + last_editor: boolean; + creation_date: boolean; + last_edit_date: boolean; + verified_items: boolean; + search_native_queries: boolean; + search_archived: boolean; +}>; + +export type SearchClickEvent = ValidateEvent<{ + event: "search_click"; + position: number; + target_type: "item" | "view_more"; + context: SearchContext | null; +}>; + +export type SearchEvent = SearchQueryEvent | SearchClickEvent; diff --git a/frontend/src/metabase-types/analytics/serialization.ts b/frontend/src/metabase-types/analytics/serialization.ts new file mode 100644 index 00000000000..0ad1c05f83e --- /dev/null +++ b/frontend/src/metabase-types/analytics/serialization.ts @@ -0,0 +1,38 @@ +type SerializationEventSchema = { + event: string; + source: string; + success: boolean; + direction?: string | null; + duration_ms?: number | null; + error_message?: string | null; + count?: number | null; + error_count?: number | null; + models?: string | null; + collection?: string | null; + all_collections?: boolean | null; + settings?: boolean | null; + field_values?: boolean | null; + secrets?: boolean | null; +}; + +type ValidateEvent< + T extends SerializationEventSchema & + Record<Exclude<keyof T, keyof SerializationEventSchema>, never>, +> = T; + +export type SerializationEvent = ValidateEvent<{ + event: "serialization"; + direction: "import" | "export"; + source: "cli" | "api"; + duration_ms: number; + success: boolean; + error_message: string | null; + count: number; + error_count: number; + models: string; + collection: string | null; + all_collections: boolean; + settings: boolean; + field_values: boolean; + secrets: boolean; +}>; diff --git a/frontend/src/metabase-types/analytics/settings.ts b/frontend/src/metabase-types/analytics/settings.ts new file mode 100644 index 00000000000..6a17d965f59 --- /dev/null +++ b/frontend/src/metabase-types/analytics/settings.ts @@ -0,0 +1,29 @@ +type SettingsEventSchema = { + event: string; + source?: string | null; +}; + +type ValidateEvent< + T extends SettingsEventSchema & + Record<Exclude<keyof T, keyof SettingsEventSchema>, never>, +> = T; + +export type TrackingPermissionEnabledEvent = ValidateEvent<{ + event: "tracking_permission_enabled"; + source: "setup" | "admin"; +}>; + +export type TrackingPermissionDisabledEvent = ValidateEvent<{ + event: "tracking_permission_disabled"; + source: "setup" | "admin"; +}>; + +export type HomepageDashboardEnabledEvent = ValidateEvent<{ + event: "homepage_dashboard_enabled"; + source: "admin" | "homepage"; +}>; + +export type SettingsEvent = + | TrackingPermissionEnabledEvent + | TrackingPermissionDisabledEvent + | HomepageDashboardEnabledEvent; diff --git a/frontend/src/metabase-types/analytics/setup.ts b/frontend/src/metabase-types/analytics/setup.ts new file mode 100644 index 00000000000..b7161b291e3 --- /dev/null +++ b/frontend/src/metabase-types/analytics/setup.ts @@ -0,0 +1,64 @@ +type SetupEventSchema = { + event: string; + version: string; + step?: string | null; + step_number?: number | null; + usage_reason?: string | null; + database?: string | null; + valid_token_present?: boolean | null; + source?: string | null; +}; + +type ValidateEvent< + T extends SetupEventSchema & + Record<Exclude<keyof T, keyof SetupEventSchema>, never>, +> = T; + +type SetupVersion = "1.3.0"; + +export type StepSeenEvent = ValidateEvent<{ + event: "step_seen"; + version: SetupVersion; + step: + | "welcome" + | "language" + | "user_info" + | "db_connection" + | "usage_question" + | "license_token" + | "db_scheduling" + | "data_usage" + | "completed"; + step_number: number; +}>; + +export type UsageReasonSelectedEvent = ValidateEvent<{ + event: "usage_reason_selected"; + version: SetupVersion; + usage_reason: "self-service-analytics" | "embedding" | "both" | "not-sure"; +}>; + +export type LicenseTokenStepSubmittedEvent = ValidateEvent<{ + event: "license_token_step_submitted"; + version: SetupVersion; + valid_token_present: boolean; +}>; + +export type DatabaseSelectedEvent = ValidateEvent<{ + event: "database_selected"; + version: SetupVersion; + database: string; +}>; + +export type AddDataLaterClickedEvent = ValidateEvent<{ + event: "add_data_later_clicked"; + version: SetupVersion; + source: "pre_selection" | "post_selection"; +}>; + +export type SetupEvent = + | StepSeenEvent + | UsageReasonSelectedEvent + | LicenseTokenStepSubmittedEvent + | DatabaseSelectedEvent + | AddDataLaterClickedEvent; diff --git a/frontend/src/metabase-types/analytics/timeline.ts b/frontend/src/metabase-types/analytics/timeline.ts new file mode 100644 index 00000000000..d54711e3d8f --- /dev/null +++ b/frontend/src/metabase-types/analytics/timeline.ts @@ -0,0 +1,22 @@ +type TimelineEventSchema = { + event: string; + source?: string | null; + question_id?: number | null; + collection_id?: number | null; + time_matters?: boolean | null; +}; + +type ValidateEvent< + T extends TimelineEventSchema & + Record<Exclude<keyof T, keyof TimelineEventSchema>, never>, +> = T; + +export type NewEventCreatedEvent = ValidateEvent<{ + event: "new_event_created"; + source: "questions" | "collections" | "api"; + question_id: number | null; + collection_id: number | null; + time_matters: boolean; +}>; + +export type TimelineEvent = NewEventCreatedEvent; diff --git a/frontend/src/metabase-types/analytics/upsell.ts b/frontend/src/metabase-types/analytics/upsell.ts new file mode 100644 index 00000000000..159f562eb8c --- /dev/null +++ b/frontend/src/metabase-types/analytics/upsell.ts @@ -0,0 +1,24 @@ +type UpsellEventSchema = { + event: string; + promoted_feature?: string | null; + upsell_location?: string | null; +}; + +type ValidateEvent< + T extends UpsellEventSchema & + Record<Exclude<keyof T, keyof UpsellEventSchema>, never>, +> = T; + +export type UpsellViewedEvent = ValidateEvent<{ + event: "upsell_viewed"; + promoted_feature: string; + upsell_location: string; +}>; + +export type UpsellClickedEvent = ValidateEvent<{ + event: "upsell_clicked"; + promoted_feature: string; + upsell_location: string; +}>; + +export type UpsellEvent = UpsellViewedEvent | UpsellClickedEvent; diff --git a/frontend/src/metabase/admin/settings/analytics.ts b/frontend/src/metabase/admin/settings/analytics.ts index 16d222fdf70..1c69bcb9b52 100644 --- a/frontend/src/metabase/admin/settings/analytics.ts +++ b/frontend/src/metabase/admin/settings/analytics.ts @@ -1,7 +1,7 @@ import { trackSchemaEvent } from "metabase/lib/analytics"; export const trackTrackingPermissionChanged = (isEnabled: boolean) => { - trackSchemaEvent("settings", "1-0-1", { + trackSchemaEvent("settings", { event: isEnabled ? "tracking_permission_enabled" : "tracking_permission_disabled", @@ -12,7 +12,7 @@ export const trackTrackingPermissionChanged = (isEnabled: boolean) => { export const trackCustomHomepageDashboardEnabled = ( source: "admin" | "homepage", ) => { - trackSchemaEvent("settings", "1-0-2", { + trackSchemaEvent("settings", { event: "homepage_dashboard_enabled", source, }); diff --git a/frontend/src/metabase/admin/upsells/components/analytics.ts b/frontend/src/metabase/admin/upsells/components/analytics.ts index f36129c8203..9cc13ba9d39 100644 --- a/frontend/src/metabase/admin/upsells/components/analytics.ts +++ b/frontend/src/metabase/admin/upsells/components/analytics.ts @@ -6,7 +6,7 @@ type UpsellEventProps = { }; export const trackUpsellViewed = ({ source, campaign }: UpsellEventProps) => { - trackSchemaEvent("upsell", "1-0-0", { + trackSchemaEvent("upsell", { event: "upsell_viewed", promoted_feature: campaign, upsell_location: source, @@ -14,7 +14,7 @@ export const trackUpsellViewed = ({ source, campaign }: UpsellEventProps) => { }; export const trackUpsellClicked = ({ source, campaign }: UpsellEventProps) => { - trackSchemaEvent("upsell", "1-0-0", { + trackSchemaEvent("upsell", { event: "upsell_clicked", promoted_feature: campaign, upsell_location: source, diff --git a/frontend/src/metabase/browse/analytics.ts b/frontend/src/metabase/browse/analytics.ts index 7569a89615f..ffa3dcd1c64 100644 --- a/frontend/src/metabase/browse/analytics.ts +++ b/frontend/src/metabase/browse/analytics.ts @@ -1,14 +1,14 @@ import { trackSchemaEvent } from "metabase/lib/analytics"; -import type { SearchResultId } from "metabase-types/api"; +import type { CardId, ConcreteTableId } from "metabase-types/api"; -export const trackModelClick = (modelId: SearchResultId) => - trackSchemaEvent("browse_data", "1-0-0", { +export const trackModelClick = (modelId: CardId) => + trackSchemaEvent("browse_data", { event: "browse_data_model_clicked", model_id: modelId, }); -export const trackTableClick = (tableId: number) => - trackSchemaEvent("browse_data", "1-0-0", { +export const trackTableClick = (tableId: ConcreteTableId) => + trackSchemaEvent("browse_data", { event: "browse_data_table_clicked", table_id: tableId, }); diff --git a/frontend/src/metabase/dashboard/analytics.ts b/frontend/src/metabase/dashboard/analytics.ts index 53f4c70f374..eed0975e40a 100644 --- a/frontend/src/metabase/dashboard/analytics.ts +++ b/frontend/src/metabase/dashboard/analytics.ts @@ -3,12 +3,17 @@ import type { DashboardId, DashboardWidth } from "metabase-types/api"; import type { SectionId } from "./sections"; -const DASHBOARD_SCHEMA_VERSION = "1-1-5"; +const getDashboardId = (dashboardId: DashboardId | undefined) => { + // We made dashboard_id optional because we don't want to send + // UUIDs or JWTs when in public or static embed scenarios. + // Because the field is still required in the snowplow table we send 0. + return typeof dashboardId === "number" ? dashboardId : 0; +}; export const trackAutoApplyFiltersDisabled = (dashboardId: DashboardId) => { - trackSchemaEvent("dashboard", DASHBOARD_SCHEMA_VERSION, { + trackSchemaEvent("dashboard", { event: "auto_apply_filters_disabled", - dashboard_id: dashboardId, + dashboard_id: getDashboardId(dashboardId), }); }; @@ -26,12 +31,9 @@ export const trackExportDashboardToPDF = ({ dashboardId?: DashboardId; dashboardAccessedVia: DashboardAccessedVia; }) => { - trackSchemaEvent("dashboard", DASHBOARD_SCHEMA_VERSION, { + trackSchemaEvent("dashboard", { event: "dashboard_pdf_exported", - // We made dashboard_id optional because we don't want to send - // UUIDs or JWTs when in public or static embed scenarios. - // Because the field is still required in the snowplow table we send 0. - dashboard_id: typeof dashboardId === "number" ? dashboardId : 0, + dashboard_id: getDashboardId(dashboardId), dashboard_accessed_via: dashboardAccessedVia, }); }; @@ -40,35 +42,51 @@ export const trackDashboardWidthChange = ( dashboardId: DashboardId, width: DashboardWidth, ) => { - trackSchemaEvent("dashboard", DASHBOARD_SCHEMA_VERSION, { + trackSchemaEvent("dashboard", { event: "dashboard_width_toggled", - dashboard_id: dashboardId, + dashboard_id: getDashboardId(dashboardId), full_width: width === "full", }); }; type CardTypes = "text" | "heading" | "link" | "action"; -export const trackCardCreated = ( - type: CardTypes, - dashboard_id: DashboardId, -) => { - if (!type) { - return; +export const trackCardCreated = (type: CardTypes, dashboardId: DashboardId) => { + switch (type) { + case "text": + trackSchemaEvent("dashboard", { + event: `new_text_card_created`, + dashboard_id: getDashboardId(dashboardId), + }); + break; + case "heading": + trackSchemaEvent("dashboard", { + event: `new_heading_card_created`, + dashboard_id: getDashboardId(dashboardId), + }); + break; + case "link": + trackSchemaEvent("dashboard", { + event: `new_link_card_created`, + dashboard_id: getDashboardId(dashboardId), + }); + break; + case "action": + trackSchemaEvent("dashboard", { + event: `new_action_card_created`, + dashboard_id: getDashboardId(dashboardId), + }); + break; } - trackSchemaEvent("dashboard", DASHBOARD_SCHEMA_VERSION, { - event: `new_${type}_card_created`, - dashboard_id, - }); }; export const trackSectionAdded = ( dashboardId: DashboardId, sectionId: SectionId, ) => { - trackSchemaEvent("dashboard", DASHBOARD_SCHEMA_VERSION, { + trackSchemaEvent("dashboard", { event: "dashboard_section_added", - dashboard_id: dashboardId, + dashboard_id: getDashboardId(dashboardId), section_layout: sectionId, }); }; @@ -80,7 +98,7 @@ export const trackDashboardSaved = ({ dashboard_id: number; duration_milliseconds: number; }) => { - trackSchemaEvent("dashboard", DASHBOARD_SCHEMA_VERSION, { + trackSchemaEvent("dashboard", { event: "dashboard_saved", dashboard_id, duration_milliseconds, @@ -88,36 +106,36 @@ export const trackDashboardSaved = ({ }; export const trackCardMoved = (dashboardId: DashboardId) => { - trackSchemaEvent("dashboard", DASHBOARD_SCHEMA_VERSION, { + trackSchemaEvent("dashboard", { event: `card_moved_to_tab`, - dashboard_id: dashboardId, + dashboard_id: getDashboardId(dashboardId), }); }; export const trackQuestionReplaced = (dashboardId: DashboardId) => { - trackSchemaEvent("dashboard", DASHBOARD_SCHEMA_VERSION, { + trackSchemaEvent("dashboard", { event: "dashboard_card_replaced", - dashboard_id: dashboardId, + dashboard_id: getDashboardId(dashboardId), }); }; export const trackDashcardDuplicated = (dashboardId: DashboardId) => { - trackSchemaEvent("dashboard", DASHBOARD_SCHEMA_VERSION, { + trackSchemaEvent("dashboard", { event: "dashboard_card_duplicated", - dashboard_id: dashboardId, + dashboard_id: getDashboardId(dashboardId), }); }; export const trackTabDuplicated = (dashboardId: DashboardId) => { - trackSchemaEvent("dashboard", DASHBOARD_SCHEMA_VERSION, { + trackSchemaEvent("dashboard", { event: "dashboard_tab_duplicated", - dashboard_id: dashboardId, + dashboard_id: getDashboardId(dashboardId), }); }; export const trackFilterRequired = (dashboardId: DashboardId) => { - trackSchemaEvent("dashboard", DASHBOARD_SCHEMA_VERSION, { + trackSchemaEvent("dashboard", { event: "dashboard_filter_required", - dashboard_id: dashboardId, + dashboard_id: getDashboardId(dashboardId), }); }; diff --git a/frontend/src/metabase/home/components/EmbedHomepage/analytics.ts b/frontend/src/metabase/home/components/EmbedHomepage/analytics.ts index 489083a6154..b0cec91d063 100644 --- a/frontend/src/metabase/home/components/EmbedHomepage/analytics.ts +++ b/frontend/src/metabase/home/components/EmbedHomepage/analytics.ts @@ -4,12 +4,11 @@ import type { EmbeddingHomepageDismissReason } from "metabase-types/api"; import type { EmbeddingHomepageInitialTab } from "./types"; const SCHEMA_NAME = "embedding_homepage"; -const SCHEMA_VERSION = "1-0-0"; export const trackEmbeddingHomepageDismissed = ( dismiss_reason: EmbeddingHomepageDismissReason, ) => { - trackSchemaEvent(SCHEMA_NAME, SCHEMA_VERSION, { + trackSchemaEvent(SCHEMA_NAME, { event: "embedding_homepage_dismissed", dismiss_reason, }); @@ -18,7 +17,7 @@ export const trackEmbeddingHomepageDismissed = ( export const trackEmbeddingHomepageQuickstartClick = ( initial_tab: EmbeddingHomepageInitialTab, ) => { - trackSchemaEvent(SCHEMA_NAME, SCHEMA_VERSION, { + trackSchemaEvent(SCHEMA_NAME, { event: "embedding_homepage_quickstart_click", initial_tab, }); @@ -27,7 +26,7 @@ export const trackEmbeddingHomepageQuickstartClick = ( export const trackEmbeddingHomepageExampleDashboardClick = ( initial_tab: EmbeddingHomepageInitialTab, ) => { - trackSchemaEvent(SCHEMA_NAME, SCHEMA_VERSION, { + trackSchemaEvent(SCHEMA_NAME, { event: "embedding_homepage_example_dashboard_click", initial_tab, }); diff --git a/frontend/src/metabase/lib/analytics.js b/frontend/src/metabase/lib/analytics-untyped.js similarity index 72% rename from frontend/src/metabase/lib/analytics.js rename to frontend/src/metabase/lib/analytics-untyped.js index c281abcec82..3951b270341 100644 --- a/frontend/src/metabase/lib/analytics.js +++ b/frontend/src/metabase/lib/analytics-untyped.js @@ -1,15 +1,8 @@ import * as Snowplow from "@snowplow/browser-tracker"; -import { shouldLogAnalytics } from "metabase/env"; import Settings from "metabase/lib/settings"; import { getUserId } from "metabase/selectors/user"; -export const createTracker = store => { - if (Settings.snowplowEnabled()) { - createSnowplowTracker(store); - } -}; - export const trackPageView = url => { if (!url || !Settings.trackingEnabled()) { return; @@ -19,41 +12,12 @@ export const trackPageView = url => { trackSnowplowPageView(getSanitizedUrl(url)); } }; - -export const trackSchemaEvent = (schema, version, data) => { - if (shouldLogAnalytics) { - const { event, ...other } = data; - // eslint-disable-next-line no-console - console.log( - `%c[SNOWPLOW EVENT]%c, ${event}`, - "background: #222; color: #bada55", - "color: ", - other, - ); - } - - if (!schema || !Settings.trackingEnabled()) { - return; - } - +export const createTracker = store => { if (Settings.snowplowEnabled()) { - trackSnowplowSchemaEvent(schema, version, data); + createSnowplowTracker(store); } }; -const createSnowplowTracker = store => { - Snowplow.newTracker("sp", Settings.snowplowUrl(), { - appId: "metabase", - platform: "web", - eventMethod: "post", - discoverRootDomain: true, - contexts: { webPage: true }, - anonymousTracking: { withServerAnonymisation: true }, - stateStorageStrategy: "none", - plugins: [createSnowplowPlugin(store)], - }); -}; - const createSnowplowPlugin = store => { return { beforeTrack: () => { @@ -83,21 +47,25 @@ const createSnowplowPlugin = store => { }; }; +const createSnowplowTracker = store => { + Snowplow.newTracker("sp", Settings.snowplowUrl(), { + appId: "metabase", + platform: "web", + eventMethod: "post", + discoverRootDomain: true, + contexts: { webPage: true }, + anonymousTracking: { withServerAnonymisation: true }, + stateStorageStrategy: "none", + plugins: [createSnowplowPlugin(store)], + }); +}; + 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 getSanitizedUrl = url => { const urlWithoutSlug = url.replace(/(\/\d+)-[^\/]+$/, (match, path) => path); const urlWithoutHost = new URL(urlWithoutSlug, Settings.snowplowUrl()); diff --git a/frontend/src/metabase/lib/analytics.ts b/frontend/src/metabase/lib/analytics.ts new file mode 100644 index 00000000000..bda14811755 --- /dev/null +++ b/frontend/src/metabase/lib/analytics.ts @@ -0,0 +1,62 @@ +import * as Snowplow from "@snowplow/browser-tracker"; + +import { shouldLogAnalytics } from "metabase/env"; +import Settings from "metabase/lib/settings"; +import type { + SchemaEvent, + SchemaType, + SimpleEvent, +} from "metabase-types/analytics"; + +export * from "./analytics-untyped"; + +const VERSIONS: Record<SchemaType, string> = { + account: "1-0-2", + action: "1-0-0", + browse_data: "1-0-0", + cleanup: "1-0-0", + csvupload: "1-0-3", + dashboard: "1-1-5", + database: "1-0-1", + downloads: "1-0-0", + embed_flow: "1-0-2", + embed_share: "1-0-0", + embedding_homepage: "1-0-0", + event: "1-0-0", + invite: "1-0-1", + metabot: "1-0-1", + model: "1-0-0", + question: "1-0-6", + search: "1-1-0", + serialization: "1-0-1", + settings: "1-0-2", + setup: "1-0-3", + timeline: "1-0-0", + upsell: "1-0-0", +}; + +export function trackEvent(event: SimpleEvent) { + trackSchemaEvent("event", event); +} + +export function trackSchemaEvent(schema: SchemaType, event: SchemaEvent): void { + if (Settings.trackingEnabled() && Settings.snowplowEnabled()) { + if (shouldLogAnalytics) { + const { event: type, ...other } = event; + // eslint-disable-next-line no-console + console.log( + `%c[SNOWPLOW EVENT]%c, ${type}`, + "background: #222; color: #bada55", + "color: ", + other, + ); + } + + Snowplow.trackSelfDescribingEvent({ + event: { + schema: `iglu:com.metabase/${schema}/jsonschema/${VERSIONS[schema]}`, + data: event, + }, + }); + } +} diff --git a/frontend/src/metabase/metabot/analytics.ts b/frontend/src/metabase/metabot/analytics.ts index 14a99cfda46..9ed0216c750 100644 --- a/frontend/src/metabase/metabot/analytics.ts +++ b/frontend/src/metabase/metabot/analytics.ts @@ -5,20 +5,19 @@ import type { MetabotEntityType } from "metabase-types/store"; export type MetabotQueryRunResult = "success" | "failure" | "bad-sql"; const SCHEMA_NAME = "metabot"; -const TEMPLATE_VERSION = "1-0-1"; export const trackMetabotQueryRun = ( entity_type: MetabotEntityType | null, prompt_template_versions: string[] | null, - result: MetabotQueryRunResult, + result_type: MetabotQueryRunResult, visualization_type: string | null, is_rerun: boolean, ) => { - trackSchemaEvent(SCHEMA_NAME, TEMPLATE_VERSION, { + trackSchemaEvent(SCHEMA_NAME, { event: "metabot_query_run", entity_type, prompt_template_versions, - result, + result_type, visualization_type, is_rerun, }); @@ -29,7 +28,7 @@ export const trackMetabotFeedbackReceived = ( prompt_template_versions: string[] | null, feedback_type: MetabotFeedbackType | null, ) => { - trackSchemaEvent(SCHEMA_NAME, TEMPLATE_VERSION, { + trackSchemaEvent(SCHEMA_NAME, { event: "metabot_feedback_received", entity_type, prompt_template_versions, diff --git a/frontend/src/metabase/nav/containers/MainNavbar/SidebarItems/UploadCSV/analytics.ts b/frontend/src/metabase/nav/containers/MainNavbar/SidebarItems/UploadCSV/analytics.ts index f4af17c643d..4e7cde4ca99 100644 --- a/frontend/src/metabase/nav/containers/MainNavbar/SidebarItems/UploadCSV/analytics.ts +++ b/frontend/src/metabase/nav/containers/MainNavbar/SidebarItems/UploadCSV/analytics.ts @@ -1,8 +1,8 @@ -import { trackSchemaEvent } from "metabase/lib/analytics"; +import { trackEvent } from "metabase/lib/analytics"; export const trackButtonClicked = () => { - trackSchemaEvent("csvupload", "1-0-4", { + trackEvent({ event: "csv_upload_clicked", - source: "left_nav", + triggered_from: "left_nav", }); }; diff --git a/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/StaticEmbedSetupPane.tsx b/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/StaticEmbedSetupPane.tsx index 59dddbed7fe..973be57cfa5 100644 --- a/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/StaticEmbedSetupPane.tsx +++ b/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/StaticEmbedSetupPane.tsx @@ -41,7 +41,9 @@ import { getDefaultDisplayOptions } from "./config"; import { EMBED_MODAL_TABS } from "./tabs"; import type { ActivePreviewPane, EmbedCodePaneVariant } from "./types"; -const countEmbeddingParameterOptions = (embeddingParams: EmbeddingParameters) => +const countEmbeddingParameterOptions = ( + embeddingParams: EmbeddingParameters, +): Record<EmbeddingParameterVisibility, number> => Object.values(embeddingParams).reduce( (acc, value) => { acc[value] += 1; diff --git a/frontend/src/metabase/public/lib/analytics.ts b/frontend/src/metabase/public/lib/analytics.ts index e119079854c..e8ebc7abb34 100644 --- a/frontend/src/metabase/public/lib/analytics.ts +++ b/frontend/src/metabase/public/lib/analytics.ts @@ -6,10 +6,10 @@ import type { EmbedResource, EmbedResourceType, EmbeddingDisplayOptions, + EmbeddingParameterVisibility, } from "./types"; const SCHEMA_NAME = "embed_flow"; -const SCHEMA_VERSION = "1-0-2"; // We changed the UI to `Look and Feel` now type Appearance = { @@ -26,7 +26,7 @@ export const trackStaticEmbedDiscarded = ({ }: { artifact: EmbedResourceType; }): void => { - trackSchemaEvent(SCHEMA_NAME, SCHEMA_VERSION, { + trackSchemaEvent(SCHEMA_NAME, { event: "static_embed_discarded", artifact, }); @@ -40,11 +40,11 @@ export const trackStaticEmbedPublished = ({ }: { artifact: EmbedResourceType; resource: EmbedResource; - params: Record<string, number>; + params: Record<EmbeddingParameterVisibility, number>; isExampleDashboard: boolean; }): void => { const now = Date.now(); - trackSchemaEvent(SCHEMA_NAME, SCHEMA_VERSION, { + trackSchemaEvent(SCHEMA_NAME, { event: "static_embed_published", artifact, new_embed: !resource.initially_published_at, @@ -71,7 +71,7 @@ export const trackStaticEmbedUnpublished = ({ resource: EmbedResource; }): void => { const now = Date.now(); - trackSchemaEvent(SCHEMA_NAME, SCHEMA_VERSION, { + trackSchemaEvent(SCHEMA_NAME, { event: "static_embed_unpublished", artifact, time_since_creation: toSecond( @@ -96,7 +96,7 @@ export const trackStaticEmbedCodeCopied = ({ code: "backend" | "view"; displayOptions: EmbeddingDisplayOptions; }): void => { - trackSchemaEvent(SCHEMA_NAME, SCHEMA_VERSION, { + trackSchemaEvent(SCHEMA_NAME, { event: "static_embed_code_copied", artifact, language, @@ -126,10 +126,10 @@ export const trackPublicLinkCopied = ({ artifact: EmbedResourceType; format?: ExportFormatType | null; }): void => { - trackSchemaEvent(SCHEMA_NAME, SCHEMA_VERSION, { + trackSchemaEvent(SCHEMA_NAME, { event: "public_link_copied", artifact, - format, + format: format as any, // ExportFormatType is untyped }); }; @@ -140,7 +140,7 @@ export const trackPublicEmbedCodeCopied = ({ artifact: EmbedResourceType; source: "public-embed" | "public-share"; }): void => { - trackSchemaEvent(SCHEMA_NAME, SCHEMA_VERSION, { + trackSchemaEvent(SCHEMA_NAME, { event: "public_embed_code_copied", artifact, source, @@ -154,7 +154,7 @@ export const trackPublicLinkRemoved = ({ artifact: EmbedResourceType; source: "public-embed" | "public-share"; }): void => { - trackSchemaEvent(SCHEMA_NAME, SCHEMA_VERSION, { + trackSchemaEvent(SCHEMA_NAME, { event: "public_link_removed", artifact, source, diff --git a/frontend/src/metabase/query_builder/analytics.js b/frontend/src/metabase/query_builder/analytics.js index 4445e377d2f..03dcf4dbb6f 100644 --- a/frontend/src/metabase/query_builder/analytics.js +++ b/frontend/src/metabase/query_builder/analytics.js @@ -6,7 +6,7 @@ export const trackNewQuestionSaved = ( createdQuestion, isBasedOnExistingQuestion, ) => { - trackSchemaEvent("question", "1-0-2", { + trackSchemaEvent("question", { event: "new_question_saved", question_id: createdQuestion.id(), database_id: createdQuestion.databaseId(), @@ -17,14 +17,14 @@ export const trackNewQuestionSaved = ( }; export const trackTurnIntoModelClicked = question => { - trackSchemaEvent("question", "1-0-2", { + trackSchemaEvent("question", { event: "turn_into_model_clicked", question_id: question.id(), }); }; export const trackNotebookNativePreviewShown = (question, isShown) => { - trackSchemaEvent("question", "1-0-3", { + trackSchemaEvent("question", { event: isShown ? "notebook_native_preview_shown" : "notebook_native_preview_hidden", @@ -34,7 +34,7 @@ export const trackNotebookNativePreviewShown = (question, isShown) => { }; export const trackColumnCombineViaShortcut = (query, question) => { - trackSchemaEvent("question", "1-0-4", { + trackSchemaEvent("question", { event: "column_combine_via_shortcut", custom_expressions_used: ["concat"], database_id: Lib.databaseID(query), @@ -43,7 +43,7 @@ export const trackColumnCombineViaShortcut = (query, question) => { }; export const trackColumnCombineViaPlusModal = (query, question) => { - trackSchemaEvent("question", "1-0-5", { + trackSchemaEvent("question", { event: "column_combine_via_plus_modal", custom_expressions_used: ["concat"], database_id: Lib.databaseID(query), @@ -57,7 +57,7 @@ export const trackColumnExtractViaShortcut = ( extraction, question, ) => { - trackSchemaEvent("question", "1-0-4", { + trackSchemaEvent("question", { event: "column_extract_via_shortcut", custom_expressions_used: Lib.functionsUsedByExtraction( query, @@ -75,7 +75,7 @@ export const trackColumnExtractViaPlusModal = ( extraction, question, ) => { - trackSchemaEvent("question", "1-0-5", { + trackSchemaEvent("question", { event: "column_extract_via_plus_modal", custom_expressions_used: Lib.functionsUsedByExtraction( query, diff --git a/frontend/src/metabase/querying/analytics.ts b/frontend/src/metabase/querying/analytics.ts index 2fa721253a2..3492a699356 100644 --- a/frontend/src/metabase/querying/analytics.ts +++ b/frontend/src/metabase/querying/analytics.ts @@ -6,7 +6,7 @@ export const trackColumnCombineViaColumnHeader = ( query: Lib.Query, question?: Question, ) => { - trackSchemaEvent("question", "1-0-4", { + trackSchemaEvent("question", { event: "column_combine_via_column_header", custom_expressions_used: ["concat"], database_id: Lib.databaseID(query), @@ -20,7 +20,7 @@ export const trackColumnExtractViaHeader = ( extraction: Lib.ColumnExtraction, question?: Question, ) => { - trackSchemaEvent("question", "1-0-4", { + trackSchemaEvent("question", { event: "column_extract_via_column_header", custom_expressions_used: Lib.functionsUsedByExtraction( query, @@ -38,7 +38,7 @@ export const trackColumnCompareViaColumnHeader = ( expressions: Lib.ExpressionClause[], questionId?: number, ) => { - trackSchemaEvent("question", "1-0-6", { + trackSchemaEvent("question", { event: "column_compare_via_column_header", custom_expressions_used: expressions.flatMap(expression => Lib.functionsUsedByExpression(query, stageIndex, expression), @@ -54,7 +54,7 @@ export const trackColumnCompareViaPlusModal = ( expressions: Lib.ExpressionClause[], questionId?: number, ) => { - trackSchemaEvent("question", "1-0-6", { + trackSchemaEvent("question", { event: "column_compare_via_plus_modal", custom_expressions_used: expressions.flatMap(expression => Lib.functionsUsedByExpression(query, stageIndex, expression), @@ -70,7 +70,7 @@ export const trackColumnCompareViaShortcut = ( expressions: Lib.ExpressionClause[], questionId?: number, ) => { - trackSchemaEvent("question", "1-0-6", { + trackSchemaEvent("question", { event: "column_compare_via_shortcut", custom_expressions_used: expressions.flatMap(expression => Lib.functionsUsedByExpression(query, stageIndex, expression), diff --git a/frontend/src/metabase/redux/downloads-analytics.ts b/frontend/src/metabase/redux/downloads-analytics.ts index 508652e90ed..4d080e581d1 100644 --- a/frontend/src/metabase/redux/downloads-analytics.ts +++ b/frontend/src/metabase/redux/downloads-analytics.ts @@ -2,7 +2,6 @@ import { trackSchemaEvent } from "metabase/lib/analytics"; import type { ResourceAccessedVia, ResourceType } from "./downloads"; -const SCHEMA_VERSION = "1-0-0"; const SCHEMA = "downloads"; export const trackDownloadResults = ({ @@ -14,10 +13,10 @@ export const trackDownloadResults = ({ accessedVia: ResourceAccessedVia; exportType: string; }) => { - trackSchemaEvent(SCHEMA, SCHEMA_VERSION, { + trackSchemaEvent(SCHEMA, { event: "download_results_clicked", resource_type: resourceType, accessed_via: accessedVia, - export_type: exportType, + export_type: exportType as any, }); }; diff --git a/frontend/src/metabase/search/analytics.ts b/frontend/src/metabase/search/analytics.ts index 76e4d297fd4..27bfeec0458 100644 --- a/frontend/src/metabase/search/analytics.ts +++ b/frontend/src/metabase/search/analytics.ts @@ -20,9 +20,9 @@ export const trackSearchRequest = ( searchResponse: SearchResponse, duration: number, ) => { - trackSchemaEvent("search", "1-1-0", { + trackSchemaEvent("search", { event: "search_query", - content_type: searchRequest.models, + content_type: searchRequest.models ?? null, creator: !!searchRequest.created_by, creation_date: !!searchRequest.created_at, last_edit_date: !!searchRequest.last_edited_at, @@ -30,7 +30,7 @@ export const trackSearchRequest = ( verified_items: !!searchRequest.verified, search_native_queries: !!searchRequest.search_native_query, search_archived: !!searchRequest.archived, - context: searchRequest.context, + context: searchRequest.context ?? null, runtime_milliseconds: duration, total_results: searchResponse.total, page_results: searchResponse.limit, @@ -42,10 +42,10 @@ export const trackSearchClick = ( position: number, context: SearchRequest["context"], ) => { - trackSchemaEvent("search", "1-1-0", { + trackSchemaEvent("search", { event: "search_click", position, target_type: itemType, - context, + context: context ?? null, }); }; diff --git a/frontend/src/metabase/setup/analytics.ts b/frontend/src/metabase/setup/analytics.ts index a9e074b6cc9..b395acd9a52 100644 --- a/frontend/src/metabase/setup/analytics.ts +++ b/frontend/src/metabase/setup/analytics.ts @@ -4,8 +4,6 @@ import type { UsageReason } from "metabase-types/api"; import type { SetupStep } from "./types"; const ONBOARDING_VERSION = "1.3.0"; -const SETUP_SCHEMA_VERSION = "1-0-3"; -const SETTINGS_SCHEMA_VERSION = "1-0-2"; export const trackStepSeen = ({ stepName, @@ -14,7 +12,7 @@ export const trackStepSeen = ({ stepName: SetupStep; stepNumber: number; }) => { - trackSchemaEvent("setup", SETUP_SCHEMA_VERSION, { + trackSchemaEvent("setup", { event: "step_seen", version: ONBOARDING_VERSION, step: stepName, @@ -23,7 +21,7 @@ export const trackStepSeen = ({ }; export const trackUsageReasonSelected = (usageReason: UsageReason) => { - trackSchemaEvent("setup", SETUP_SCHEMA_VERSION, { + trackSchemaEvent("setup", { event: "usage_reason_selected", version: ONBOARDING_VERSION, usage_reason: usageReason, @@ -31,7 +29,7 @@ export const trackUsageReasonSelected = (usageReason: UsageReason) => { }; export const trackLicenseTokenStepSubmitted = (validTokenPresent: boolean) => { - trackSchemaEvent("setup", SETUP_SCHEMA_VERSION, { + trackSchemaEvent("setup", { event: "license_token_step_submitted", valid_token_present: validTokenPresent, version: ONBOARDING_VERSION, @@ -39,7 +37,7 @@ export const trackLicenseTokenStepSubmitted = (validTokenPresent: boolean) => { }; export const trackDatabaseSelected = (engine: string) => { - trackSchemaEvent("setup", SETUP_SCHEMA_VERSION, { + trackSchemaEvent("setup", { event: "database_selected", version: ONBOARDING_VERSION, database: engine, @@ -47,7 +45,7 @@ export const trackDatabaseSelected = (engine: string) => { }; export const trackAddDataLaterClicked = (engine?: string) => { - trackSchemaEvent("setup", SETUP_SCHEMA_VERSION, { + trackSchemaEvent("setup", { event: "add_data_later_clicked", version: ONBOARDING_VERSION, source: engine ? "post_selection" : "pre_selection", @@ -55,7 +53,7 @@ export const trackAddDataLaterClicked = (engine?: string) => { }; export const trackTrackingChanged = (isTrackingAllowed: boolean) => { - trackSchemaEvent("settings", SETTINGS_SCHEMA_VERSION, { + trackSchemaEvent("settings", { event: isTrackingAllowed ? "tracking_permission_enabled" : "tracking_permission_disabled", diff --git a/frontend/src/metabase/visualizations/lib/settings/analytics.ts b/frontend/src/metabase/visualizations/lib/settings/analytics.ts index 8d6b4303cdb..1bc56d36d06 100644 --- a/frontend/src/metabase/visualizations/lib/settings/analytics.ts +++ b/frontend/src/metabase/visualizations/lib/settings/analytics.ts @@ -2,8 +2,8 @@ import { trackSchemaEvent } from "metabase/lib/analytics"; import type { DashboardId } from "metabase-types/api"; export const trackCardSetToHideWhenNoResults = (dashboardId: DashboardId) => { - trackSchemaEvent("dashboard", "1-1-1", { + trackSchemaEvent("dashboard", { event: "card_set_to_hide_when_no_results", - dashboard_id: dashboardId, + dashboard_id: typeof dashboardId === "number" ? dashboardId : 0, }); }; diff --git a/snowplow/iglu-client-embedded/schemas/com.metabase/csvupload/jsonschema/1-0-4 b/snowplow/iglu-client-embedded/schemas/com.metabase/csvupload/jsonschema/1-0-4 deleted file mode 100644 index 7288557d876..00000000000 --- a/snowplow/iglu-client-embedded/schemas/com.metabase/csvupload/jsonschema/1-0-4 +++ /dev/null @@ -1,94 +0,0 @@ -{ - "$schema": "http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#", - "self": { - "vendor": "com.metabase", - "name": "csvupload", - "format": "jsonschema", - "version": "1-0-4" - }, - "description": "CSV upload events", - "properties": { - "event": { - "description": "Event name", - "type": "string", - "enum": [ - "csv_upload_successful", - "csv_upload_failed", - "csv_append_successful", - "csv_append_failed", - "csv_upload_clicked" - ], - "maxLength": 128 - }, - "model_id": { - "description": "Unique identifier for the newly created model", - "type": [ - "integer", - "null" - ], - "minimum": 0, - "maximum": 2147483647 - }, - "upload_seconds": { - "description": "Number of seconds the csv took to upload", - "type": [ - "number", - "null" - ], - "minimum": 0, - "maximum": 2147483647 - }, - "size_mb": { - "description": "File size of the CSV in megabytes", - "type": [ - "number", - "null" - ], - "minimum": 0, - "maximum": 2147483647 - }, - "num_columns": { - "description": "Number of columns in the CSV file (does not include generated columns)", - "type": [ - "integer", - "null" - ], - "minimum": 0, - "maximum": 2147483647 - }, - "num_rows": { - "description": "Number of rows in the CSV", - "type": [ - "integer", - "null" - ], - "minimum": 0, - "maximum": 2147483647 - }, - "generated_columns": { - "description": "Number of new columns we added to the CSV (e.g., a PK)", - "type": [ - "integer", - "null" - ], - "minimum": 0, - "maximum": 2147483647 - }, - "source": { - "description": "String identifying the location where the event took place", - "type": [ - "string", - "null" - ], - "enum": [ - "left_nav" - ], - "maxLength": 1024 - } - }, - "additionalProperties": true, - "type": "object", - "required": [ - "event" - ] -} diff --git a/snowplow/iglu-client-embedded/schemas/com.metabase/event/jsonschema/1-0-0 b/snowplow/iglu-client-embedded/schemas/com.metabase/event/jsonschema/1-0-0 new file mode 100644 index 00000000000..72195be93ee --- /dev/null +++ b/snowplow/iglu-client-embedded/schemas/com.metabase/event/jsonschema/1-0-0 @@ -0,0 +1,64 @@ +{ + "$schema": "http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#", + "description": "Generic event to track interactions and actions that happen within Metabase. This should be used as a default schema for tracking events. If more specific context is needed, we can use custom context entities (https://docs.snowplow.io/docs/understanding-your-pipeline/entities/).", + "self": { + "vendor": "com.metabase", + "name": "event", + "format": "jsonschema", + "version": "1-0-0" + }, + "type": "object", + "properties": { + "event": { + "description": "Name of the action. Noun (target) + Verb in the past (action) which define the action taken - e.g. question_created, dashboard_updated, dashboard_auto_apply_filter_enabled", + "type": "string", + "maxLength": 1024 + }, + "target_id": { + "description": "(Optional) ID of the entity that the action was performed on. E.g. the ID of the question that was created in a question_created event.", + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 2147483647 + }, + "triggered_from": { + "description": "(Optional) From where the action was taken/triggered. This can be generic like 'dashboard' or also more specific like 'dashboard_top_nav'.", + "type": [ + "string", + "null" + ], + "maxLength": 1024 + }, + "duration_ms": { + "description": "(Optional) Duration in milliseconds", + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 2147483647 + }, + "result": { + "description": "(Optional) The outcome of the action (e.g. success, failure, …)", + "type": [ + "string", + "null" + ], + "maxLength": 1024 + }, + "event_detail": { + "description": "(Optional) String that can be used for additional details that describe the event, e.g. the type of question that was created in a question_created event. We should NOT pass JSON here.", + "type": [ + "string", + "null" + ], + "maxLength": 1024 + } + }, + "required": [ + "event" + ], + "additionalProperties": true + } -- GitLab