Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions experimental/ssh/internal/client/client_internal_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package client

import (
"encoding/json"
"errors"
"strings"
"testing"
Expand Down Expand Up @@ -482,3 +483,16 @@ func TestBuildSshTunnelEvent(t *testing.T) {
})
}
}

// A failed first attempt is the case the telemetry exists to measure, so assert
// the outcome fields reach the payload as an explicit false rather than being
// dropped as zero values.
func TestBuildSshTunnelEventReportsFailure(t *testing.T) {
got := buildSshTunnelEvent(ClientOptions{ClusterID: "abc-123"}, false, false, 0)

assert.False(t, got.IsSuccess)

b, err := json.Marshal(got)
require.NoError(t, err)
assert.Contains(t, string(b), `"is_success":false`)
}
17 changes: 12 additions & 5 deletions libs/telemetry/protos/ssh_tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ const (

// SshTunnelEvent is emitted when a user establishes an SSH tunnel connection
// via the Databricks CLI.
//
// Every bool below is populated on every event, so none of them carry
// omitempty: a genuine false must stay distinguishable from an older CLI that
// did not report the field. Events sent before CLI v1.14.0 omitted false
// entirely and land in the table as NULL, so queries spanning that cutover must
// count NULL as false (e.g. a failed connection is `is_success IS NULL` for
// pre-v1.14.0 rows and `is_success = false` after).
type SshTunnelEvent struct {
// Type of compute: dedicated cluster or serverless.
ComputeType SshTunnelComputeType `json:"compute_type,omitempty"`
Expand All @@ -33,24 +40,24 @@ type SshTunnelEvent struct {
ClientMode SshTunnelClientMode `json:"client_mode,omitempty"`

// Whether this is a reconnection to an existing session.
IsReconnect bool `json:"is_reconnect,omitempty"`
IsReconnect bool `json:"is_reconnect"`

// Whether the cluster was auto-started by the CLI.
AutoStartCluster bool `json:"auto_start_cluster,omitempty"`
AutoStartCluster bool `json:"auto_start_cluster"`

// Whether a custom base environment was set via --base-environment.
// Only the presence is recorded: the flag value can be an env.yaml path
// or display name carrying PII, so the value itself is not logged.
HasBaseEnvironment bool `json:"has_base_environment,omitempty"`
HasBaseEnvironment bool `json:"has_base_environment"`

// Time in milliseconds spent starting the SSH server.
// Zero if server was already running.
ServerStartTimeMs int64 `json:"server_start_time_ms"`

// Whether the connection was successful.
IsSuccess bool `json:"is_success,omitempty"`
IsSuccess bool `json:"is_success"`

// Whether a serverless usage policy was set via --usage-policy-id.
// Only the presence is recorded, not the policy ID itself.
HasUsagePolicy bool `json:"has_usage_policy,omitempty"`
HasUsagePolicy bool `json:"has_usage_policy"`
}
45 changes: 45 additions & 0 deletions libs/telemetry/protos/ssh_tunnel_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package protos

import (
"encoding/json"
"reflect"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// A failed connection is the zero value of every bool, so omitempty would drop
// the fields entirely and make the failure indistinguishable from an unreported
// one. This pins the wire payload for that case.
func TestSshTunnelEventEncodesFailureExplicitly(t *testing.T) {
b, err := json.Marshal(SshTunnelEvent{})
require.NoError(t, err)

var got map[string]any
require.NoError(t, json.Unmarshal(b, &got))

for _, field := range []string{
"is_success",
"is_reconnect",
"auto_start_cluster",
"has_base_environment",
"has_usage_policy",
} {
assert.Equal(t, false, got[field], "%s must be sent as false, not omitted", field)
}
}

// Guards fields added later: a bool that can legitimately be false must not
// carry omitempty, or its false case arrives as NULL and cannot be counted.
func TestSshTunnelEventBoolFieldsOmitOmitempty(t *testing.T) {
typ := reflect.TypeFor[SshTunnelEvent]()
for field := range typ.Fields() {
if field.Type.Kind() != reflect.Bool {
continue
}
tag := field.Tag.Get("json")
assert.NotContains(t, tag, "omitempty",
"%s has omitempty; a false value would be indistinguishable from not reported", field.Name)
}
}
Loading