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