1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
910//! Telemetry utilities.
1112use chrono::{DateTime, Utc};
13use mz_audit_log::ObjectType;
14use mz_sql::catalog::EnvironmentId;
15use mz_sql_parser::ast::StatementKind;
16use serde::{Deserialize, Serialize};
17use serde_json::json;
18use uuid::Uuid;
1920/// Details to attach to a Segment event.
21#[derive(Debug, Clone, Default)]
22pub struct EventDetails<'a> {
23/// The ID of the user that triggered the event, if any.
24pub user_id: Option<Uuid>,
25/// The value of the `application_name` parameter for the session that
26 /// triggered the event, if any.
27pub application_name: Option<&'a str>,
28/// The timestamp at which the event occurred, if not the current time.
29pub timestamp: Option<DateTime<Utc>>,
30}
3132/// Extension trait for [`mz_segment::Client`].
33pub trait SegmentClientExt {
34/// Tracks an event associated with an environment.
35fn environment_track<S>(
36&self,
37 environment_id: &EnvironmentId,
38 event: S,
39 properties: serde_json::Value,
40 details: EventDetails<'_>,
41 ) where
42S: Into<String>;
43}
4445impl SegmentClientExt for mz_segment::Client {
46/// Tracks an event associated with an environment.
47 ///
48 /// Various metadata about the environment is automatically attached
49 /// using canonical field names.
50 ///
51 /// # Panics
52 ///
53 /// Panics if `properties` is not a [`serde_json::Value::Object`].
54fn environment_track<S>(
55&self,
56 environment_id: &EnvironmentId,
57 event: S,
58mut properties: serde_json::Value,
59 EventDetails {
60 user_id,
61 timestamp,
62 application_name,
63 }: EventDetails<'_>,
64 ) where
65S: Into<String>,
66 {
67 {
68let properties = match &mut properties {
69 serde_json::Value::Object(map) => map,
70_ => {
71panic!("SegmentClientExt::environment_track called with non-object properties")
72 }
73 };
74 properties.insert("event_source".into(), json!("environment"));
75 properties.insert(
76"cloud_provider".into(),
77json!(environment_id.cloud_provider().to_string()),
78 );
79 properties.insert(
80"cloud_provider_region".into(),
81json!(environment_id.cloud_provider_region()),
82 );
83if let Some(application_name) = application_name {
84 properties.insert("application_name".into(), json!(application_name));
85 }
86 }
8788// "Context" is a defined dictionary of extra information related to a datapoint. Please
89 // consult the docs before adding anything here: https://segment.com/docs/connections/spec/common/#context
90let context = json!({
91"group_id": environment_id.organization_id()
92 });
9394// We use the organization ID as the user ID for events that are not
95 // associated with a particular user.
96let user_id = user_id.unwrap_or_else(|| environment_id.organization_id());
9798self.track(user_id, event, properties, Some(context), timestamp);
99 }
100}
101102/// Describes a way in which DDL statement execution can fail.
103#[derive(Clone, Debug, Serialize, Deserialize)]
104#[serde(rename_all = "kebab-case")]
105pub enum StatementFailureType {
106/// The statement failed to parse.
107ParseFailure,
108}
109110impl StatementFailureType {
111/// Renders the name of failure type as a title case string.
112pub fn as_title_case(&self) -> &'static str {
113match self {
114 StatementFailureType::ParseFailure => "Parse Failed",
115 }
116 }
117}
118119serde_plain::derive_display_from_serialize!(StatementFailureType);
120121/// Describes the action of a DDL statement.
122#[derive(Serialize, Deserialize)]
123pub enum StatementAction {
124/// The statement alters an object.
125Alter,
126/// The statement creates an object.
127Create,
128}
129130impl StatementAction {
131/// Renders the DDL action as a title case string.
132pub fn as_title_case(&self) -> &'static str {
133match self {
134 StatementAction::Alter => "Alter",
135 StatementAction::Create => "Create",
136 }
137 }
138}
139140/// Analyzes the action and object type of DDL statement kinds.
141///
142/// Returns `None` if the given statement kind is not a tracked DDL `StatementKind`. Tracked
143/// statements are those which are written to the audit log.
144pub fn analyze_audited_statement(
145 statement: StatementKind,
146) -> Option<(StatementAction, ObjectType)> {
147match statement {
148 StatementKind::AlterConnection => Some((StatementAction::Alter, ObjectType::Connection)),
149 StatementKind::AlterIndex => Some((StatementAction::Alter, ObjectType::Index)),
150 StatementKind::AlterRole => Some((StatementAction::Alter, ObjectType::Role)),
151 StatementKind::AlterSecret => Some((StatementAction::Alter, ObjectType::Secret)),
152 StatementKind::AlterSource => Some((StatementAction::Alter, ObjectType::Source)),
153 StatementKind::CreateCluster => Some((StatementAction::Create, ObjectType::Cluster)),
154 StatementKind::CreateClusterReplica => {
155Some((StatementAction::Create, ObjectType::ClusterReplica))
156 }
157 StatementKind::CreateConnection => Some((StatementAction::Create, ObjectType::Connection)),
158 StatementKind::CreateDatabase => Some((StatementAction::Create, ObjectType::Database)),
159 StatementKind::CreateIndex => Some((StatementAction::Create, ObjectType::Index)),
160 StatementKind::CreateMaterializedView => {
161Some((StatementAction::Create, ObjectType::MaterializedView))
162 }
163 StatementKind::CreateRole => Some((StatementAction::Create, ObjectType::Role)),
164 StatementKind::CreateSchema => Some((StatementAction::Create, ObjectType::Schema)),
165 StatementKind::CreateSecret => Some((StatementAction::Create, ObjectType::Secret)),
166 StatementKind::CreateSink => Some((StatementAction::Create, ObjectType::Sink)),
167 StatementKind::CreateSource => Some((StatementAction::Create, ObjectType::Source)),
168 StatementKind::CreateTable => Some((StatementAction::Create, ObjectType::Table)),
169 StatementKind::CreateView => Some((StatementAction::Create, ObjectType::View)),
170_ => None,
171 }
172}