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
22 changes: 22 additions & 0 deletions apps/cli-go/internal/telemetry/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,28 @@ const (
// example "pretty", "json", "yaml"), after agent auto-detection and any
// command-local override are applied.
PropOutputFormat = "output_format"
// PropErrorKind classifies failed commands into the CLI error actionability
// taxonomy. Values are user_actionable, internal_bug, external_service, or
// unknown.
PropErrorKind = "error_kind"
// PropErrorCategory is the sanitized CLI error category used for KPI
// reporting. It must never contain raw error text or user-specific data.
PropErrorCategory = "error_category"
// PropErrorFingerprint is a stable sanitized identifier for grouping repeated
// failures. It must never contain raw error text or user-specific data.
PropErrorFingerprint = "error_fingerprint"
Comment thread
jgoux marked this conversation as resolved.
// PropHasSuggestion indicates whether the failed command had a known
// remediation suggestion.
PropHasSuggestion = "has_suggestion"
// PropSuggestionType is the sanitized kind of remediation suggestion shown or
// known for the failure.
PropSuggestionType = "suggestion_type"
// PropSuggestedCommand is a safe command-only remediation hint when one is
// known, such as "supabase login". It must not include user-provided values.
PropSuggestedCommand = "suggested_command"
// PropWorkflow is an optional safe workflow label for future mapped recovery
// analysis.
PropWorkflow = "workflow"
)

// Group identifiers associate events with higher-level entities in PostHog.
Expand Down
10 changes: 7 additions & 3 deletions apps/cli/src/legacy/commands/branches/create/create.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import {
encodeYaml,
} from "../../../shared/legacy-go-output.encoders.ts";
import { mapLegacyHttpError } from "../../../shared/legacy-http-errors.ts";
import { legacySuggestUpgrade } from "../../../shared/legacy-upgrade-suggest.ts";
import {
legacyMarkUpgradeSuggested,
legacySuggestUpgrade,
} from "../../../shared/legacy-upgrade-suggest.ts";
import {
LegacyBranchesCreateCancelledError,
LegacyBranchesCreateNetworkError,
Expand Down Expand Up @@ -106,12 +109,13 @@ export const legacyBranchesCreate = Effect.fn("legacy.branches.create")(function
HttpClientError.isHttpClientError(cause) && cause.response !== undefined
? cause.response.status
: 0;
yield* legacySuggestUpgrade({
const upgradeSuggested = yield* legacySuggestUpgrade({
projectRef: ref,
featureKey: "branching_limit",
statusCode: status,
});
return yield* mapCreateErrorRaw(cause);
const mapped = yield* Effect.flip(mapCreateErrorRaw(cause));
return yield* Effect.fail(legacyMarkUpgradeSuggested(mapped, upgradeSuggested));
}),
),
);
Expand Down
10 changes: 7 additions & 3 deletions apps/cli/src/legacy/commands/branches/update/update.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import {
encodeYaml,
} from "../../../shared/legacy-go-output.encoders.ts";
import { mapLegacyHttpError } from "../../../shared/legacy-http-errors.ts";
import { legacySuggestUpgrade } from "../../../shared/legacy-upgrade-suggest.ts";
import {
legacyMarkUpgradeSuggested,
legacySuggestUpgrade,
} from "../../../shared/legacy-upgrade-suggest.ts";
import {
LegacyBranchesUpdateNetworkError,
LegacyBranchesUpdateUnexpectedStatusError,
Expand Down Expand Up @@ -78,12 +81,13 @@ export const legacyBranchesUpdate = Effect.fn("legacy.branches.update")(function
: 0;
// Mirrors Go's `update.go:26` — pass the resolved branch's project
// ref so the entitlements check is scoped to the branch's org.
yield* legacySuggestUpgrade({
const upgradeSuggested = yield* legacySuggestUpgrade({
projectRef: branchRef,
featureKey: "branching_persistent",
statusCode: status,
});
return yield* mapUpdateError(cause);
const mapped = yield* Effect.flip(mapUpdateError(cause));
return yield* Effect.fail(legacyMarkUpgradeSuggested(mapped, upgradeSuggested));
}),
),
);
Expand Down
25 changes: 17 additions & 8 deletions apps/cli/src/legacy/commands/sso/add/add.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import { sanitizeLegacyErrorBody } from "../../../shared/legacy-http-errors.ts";
import { resolveLegacyAccessToken } from "../../../shared/legacy-resolve-token.ts";
import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts";
import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts";
import { legacySuggestUpgrade } from "../../../shared/legacy-upgrade-suggest.ts";
import {
legacyMarkUpgradeSuggested,
legacySuggestUpgrade,
} from "../../../shared/legacy-upgrade-suggest.ts";
import {
LegacySsoAddAttributeMappingFileError,
LegacySsoAddMetadataFileError,
Expand Down Expand Up @@ -137,23 +140,29 @@ export const legacySsoAdd = Effect.fn("legacy.sso.add")(function* (flags: Legacy
// mapper uses (`mapLegacyHttpError`) so error output stays bounded and
// shell-safe — the raw-HTTP path must not skip these defences.
const bodyText = sanitizeLegacyErrorBody(rawBody);
yield* legacySuggestUpgrade({
const upgradeSuggested = yield* legacySuggestUpgrade({
projectRef: ref,
featureKey: "auth.saml_2",
statusCode: response.status,
});
yield* creating?.fail() ?? Effect.void;
if (response.status === 404) {
return yield* Effect.fail(
new LegacySsoAddSamlDisabledError({ message: SAML_DISABLED_MESSAGE }),
legacyMarkUpgradeSuggested(
new LegacySsoAddSamlDisabledError({ message: SAML_DISABLED_MESSAGE }),
upgradeSuggested,
),
);
}
return yield* Effect.fail(
new LegacySsoAddUnexpectedStatusError({
status: response.status,
body: bodyText,
message: `Unexpected error adding identity provider: ${bodyText}`,
}),
legacyMarkUpgradeSuggested(
new LegacySsoAddUnexpectedStatusError({
status: response.status,
body: bodyText,
message: `Unexpected error adding identity provider: ${bodyText}`,
}),
upgradeSuggested,
),
);
}

Expand Down
13 changes: 10 additions & 3 deletions apps/cli/src/legacy/commands/sso/list/list.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import {
import { mapLegacyHttpError } from "../../../shared/legacy-http-errors.ts";
import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts";
import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts";
import { legacySuggestUpgrade } from "../../../shared/legacy-upgrade-suggest.ts";
import {
legacyMarkUpgradeSuggested,
legacySuggestUpgrade,
} from "../../../shared/legacy-upgrade-suggest.ts";
import {
LegacySsoListNetworkError,
LegacySsoListSamlDisabledError,
Expand All @@ -37,16 +40,20 @@ const handleListError = (ref: string, cause: SupabaseApiError) =>
Effect.gen(function* () {
const mapped = yield* Effect.flip(mapStatusOrNetwork(cause));
if (mapped._tag === "LegacySsoListUnexpectedStatusError") {
yield* legacySuggestUpgrade({
const upgradeSuggested = yield* legacySuggestUpgrade({
projectRef: ref,
featureKey: "auth.saml_2",
statusCode: mapped.status,
});
if (mapped.status === 404) {
return yield* Effect.fail(
new LegacySsoListSamlDisabledError({ message: SAML_DISABLED_MESSAGE }),
legacyMarkUpgradeSuggested(
new LegacySsoListSamlDisabledError({ message: SAML_DISABLED_MESSAGE }),
upgradeSuggested,
),
);
}
return yield* Effect.fail(legacyMarkUpgradeSuggested(mapped, upgradeSuggested));
}
return yield* Effect.fail(mapped);
});
Expand Down
17 changes: 12 additions & 5 deletions apps/cli/src/legacy/commands/sso/remove/remove.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { encodeGoJson, encodeToml, encodeYaml } from "../../../shared/legacy-go-
import { mapLegacyHttpError } from "../../../shared/legacy-http-errors.ts";
import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts";
import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts";
import { legacySuggestUpgrade } from "../../../shared/legacy-upgrade-suggest.ts";
import {
legacyMarkUpgradeSuggested,
legacySuggestUpgrade,
} from "../../../shared/legacy-upgrade-suggest.ts";
import {
LegacySsoRemoveNetworkError,
LegacySsoRemoveNotFoundError,
Expand All @@ -29,18 +32,22 @@ const handleRemoveError = (ref: string, providerId: string, cause: SupabaseApiEr
Effect.gen(function* () {
const mapped = yield* Effect.flip(mapStatusOrNetwork(cause));
if (mapped._tag === "LegacySsoRemoveUnexpectedStatusError") {
yield* legacySuggestUpgrade({
const upgradeSuggested = yield* legacySuggestUpgrade({
projectRef: ref,
featureKey: "auth.saml_2",
statusCode: mapped.status,
});
if (mapped.status === 404) {
return yield* Effect.fail(
new LegacySsoRemoveNotFoundError({
message: `An identity provider with ID ${JSON.stringify(providerId)} could not be found.`,
}),
legacyMarkUpgradeSuggested(
new LegacySsoRemoveNotFoundError({
message: `An identity provider with ID ${JSON.stringify(providerId)} could not be found.`,
}),
upgradeSuggested,
),
);
}
return yield* Effect.fail(legacyMarkUpgradeSuggested(mapped, upgradeSuggested));
}
return yield* Effect.fail(mapped);
});
Expand Down
32 changes: 21 additions & 11 deletions apps/cli/src/legacy/commands/sso/update/update.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import { mapLegacyHttpError, sanitizeLegacyErrorBody } from "../../../shared/leg
import { resolveLegacyAccessToken } from "../../../shared/legacy-resolve-token.ts";
import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts";
import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts";
import { legacySuggestUpgrade } from "../../../shared/legacy-upgrade-suggest.ts";
import {
legacyMarkUpgradeSuggested,
legacySuggestUpgrade,
} from "../../../shared/legacy-upgrade-suggest.ts";
import {
LegacySsoMutexFlagError,
LegacySsoUpdateAttributeMappingFileError,
Expand Down Expand Up @@ -52,18 +55,22 @@ const handleGetError = (ref: string, providerId: string, cause: SupabaseApiError
Effect.gen(function* () {
const mapped = yield* Effect.flip(mapGetStatusOrNetwork(cause));
if (mapped._tag === "LegacySsoUpdateUnexpectedStatusError") {
yield* legacySuggestUpgrade({
const upgradeSuggested = yield* legacySuggestUpgrade({
projectRef: ref,
featureKey: "auth.saml_2",
statusCode: mapped.status,
Comment thread
jgoux marked this conversation as resolved.
});
if (mapped.status === 404) {
return yield* Effect.fail(
new LegacySsoUpdateNotFoundError({
message: `An identity provider with ID ${JSON.stringify(providerId)} could not be found.`,
}),
legacyMarkUpgradeSuggested(
new LegacySsoUpdateNotFoundError({
message: `An identity provider with ID ${JSON.stringify(providerId)} could not be found.`,
}),
upgradeSuggested,
),
);
}
return yield* Effect.fail(legacyMarkUpgradeSuggested(mapped, upgradeSuggested));
}
return yield* Effect.fail(mapped);
});
Expand Down Expand Up @@ -209,19 +216,22 @@ export const legacySsoUpdate = Effect.fn("legacy.sso.update")(function* (
// Cap + sanitise to match `mapLegacyHttpError`'s defences — see add handler
// for the rationale; the raw-HTTP path must not bypass these.
const bodyText = sanitizeLegacyErrorBody(rawBody);
yield* legacySuggestUpgrade({
const upgradeSuggested = yield* legacySuggestUpgrade({
projectRef: ref,
featureKey: "auth.saml_2",
statusCode: response.status,
});
yield* fetching?.fail() ?? Effect.void;
return yield* Effect.fail(
// Go reuses the GET error message even for PUT (see `update.go:133`).
new LegacySsoUpdateUnexpectedStatusError({
status: response.status,
body: bodyText,
message: `unexpected error fetching identity provider: ${bodyText}`,
}),
legacyMarkUpgradeSuggested(
new LegacySsoUpdateUnexpectedStatusError({
status: response.status,
body: bodyText,
message: `unexpected error fetching identity provider: ${bodyText}`,
}),
upgradeSuggested,
),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { Effect, Option } from "effect";

import { LegacyPlatformApi } from "../../../auth/legacy-platform-api.service.ts";
import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts";
import { legacySuggestUpgrade } from "../../../shared/legacy-upgrade-suggest.ts";
import {
legacyMarkUpgradeSuggested,
legacySuggestUpgrade,
} from "../../../shared/legacy-upgrade-suggest.ts";
import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts";
import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts";
import { LegacyOutputFlag } from "../../../../shared/legacy/global-flags.ts";
Expand Down Expand Up @@ -57,11 +60,12 @@ export const legacyVanitySubdomainsActivate = Effect.fn("legacy.vanity-subdomain
// tagged error before deciding whether to suggest an upgrade, then re-fail.
const mapped = yield* Effect.flip(mapActivateError(cause));
if (mapped._tag === "LegacyVanitySubdomainsActivateUnexpectedStatusError") {
yield* legacySuggestUpgrade({
const upgradeSuggested = yield* legacySuggestUpgrade({
projectRef: ref,
featureKey: "vanity_subdomain",
statusCode: mapped.status,
});
return yield* Effect.fail(legacyMarkUpgradeSuggested(mapped, upgradeSuggested));
}
return yield* Effect.fail(mapped);
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { Effect, Option } from "effect";

import { LegacyPlatformApi } from "../../../auth/legacy-platform-api.service.ts";
import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts";
import { legacySuggestUpgrade } from "../../../shared/legacy-upgrade-suggest.ts";
import {
legacyMarkUpgradeSuggested,
legacySuggestUpgrade,
} from "../../../shared/legacy-upgrade-suggest.ts";
import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts";
import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts";
import { LegacyOutputFlag } from "../../../../shared/legacy/global-flags.ts";
Expand Down Expand Up @@ -60,12 +63,13 @@ export const legacyVanitySubdomainsCheckAvailability = Effect.fn(
if (mapped._tag === "LegacyVanitySubdomainsCheckUnexpectedStatusError") {
// Go's check command calls SuggestUpgradeOnError without a following
// TrackUpgradeSuggested, so suppress the analytics event for parity.
yield* legacySuggestUpgrade({
const upgradeSuggested = yield* legacySuggestUpgrade({
projectRef: ref,
featureKey: "vanity_subdomain",
statusCode: mapped.status,
trackAnalytics: false,
});
return yield* Effect.fail(legacyMarkUpgradeSuggested(mapped, upgradeSuggested));
}
return yield* Effect.fail(mapped);
}),
Expand Down
Loading
Loading