Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,18 @@ Edit an issue alert rule
|-------|------|-------------|
| `id` | string | Alert rule ID |
| `name` | string | Alert rule name |
| `organizationId` | string | |
| `createdBy` | string \| null | |
| `dateCreated` | string | |
| `dateUpdated` | string | |
| `triggers` | object \| null | |
| `actionFilters` | array \| null | |
| `environment` | string \| null | |
| `config` | object | |
| `detectorIds` | array \| null | |
| `enabled` | boolean | |
| `lastTriggered` | string \| null | |
| `owner` | string \| null | |
| `organizationId` | string | ID of the organization the alert belongs to |
| `createdBy` | string \| null | ID of the user who created the alert |
| `dateCreated` | string | When the alert was created (ISO 8601) |
| `dateUpdated` | string | When the alert was last updated (ISO 8601) |
| `triggers` | object \| null | Conditions on which the alert triggers |
| `actionFilters` | array \| null | Filters to run before actions fire, each with the actions to fire |
| `environment` | string \| null | Name of the environment the alert is limited to. Null for all environments |
| `config` | object | Configuration of the alert, such as frequency in minutes |
| `detectorIds` | array \| null | IDs of the monitors connected to the alert |
| `enabled` | boolean | Whether the alert is enabled |
| `lastTriggered` | string \| null | When the alert last fired (ISO 8601) |
| `owner` | string \| null | Team or user that owns the alert, such as user:123 or team:456 |
| `org` | string | Organization slug |
| `project` | string | Project slug |
| `status` | string | Rule status (active, disabled) |
Expand Down Expand Up @@ -389,21 +389,21 @@ Edit a metric alert rule

| Field | Type | Description |
|-------|------|-------------|
| `owner` | object \| null | |
| `createdBy` | string \| null | |
| `latestGroup` | object \| null | |
| `description` | string \| null | |
| `owner` | object \| null | Team or user that owns the monitor |
| `createdBy` | string \| null | ID of the user who created the monitor |
| `latestGroup` | object \| null | Latest issue the monitor created |
| `description` | string \| null | Description of the monitor |
| `id` | string | Alert rule ID |
| `projectId` | string \| null | |
| `projectId` | string \| null | ID of the project the monitor belongs to |
| `name` | string | Alert rule name |
| `type` | string | |
| `workflowIds` | array \| null | |
| `dateCreated` | string | |
| `dateUpdated` | string | |
| `dataSources` | array \| null | |
| `conditionGroup` | object \| null | |
| `config` | object | |
| `enabled` | boolean | |
| `type` | string | Type of the monitor, such as metric_issue or error |
| `workflowIds` | array \| null | IDs of the alerts connected to the monitor |
| `dateCreated` | string | When the monitor was created (ISO 8601) |
| `dateUpdated` | string | When the monitor was last updated (ISO 8601) |
| `dataSources` | array \| null | Data sources the monitor evaluates, such as a query subscription |
| `conditionGroup` | object \| null | Conditions that decide the monitor's status |
| `config` | object | Configuration of the monitor, which depends on its type |
| `enabled` | boolean | Whether the monitor is enabled |
| `org` | string | Organization slug |
| `status` | string | Rule status (active, disabled) |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,44 +30,44 @@ View details of one or more events

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | |
| `groupID` | string \| null | |
| `id` | string | ID of the event |
| `groupID` | string \| null | ID of the issue the event belongs to |
| `eventID` | string | UUID-format event ID |
| `projectID` | string | |
| `message` | string \| null | |
| `title` | string | |
| `location` | string \| null | |
| `user` | object \| null | |
| `tags` | array | |
| `platform` | string \| null | |
| `dateReceived` | string \| null | |
| `contexts` | object \| null | |
| `size` | number \| null | |
| `entries` | array | |
| `dist` | string \| null | |
| `sdk` | object \| null | |
| `context` | object \| null | |
| `packages` | object | |
| `type` | string | |
| `metadata` | object | |
| `errors` | array | |
| `projectID` | string | ID of the project the event belongs to |
| `message` | string \| null | Event message |
| `title` | string | Event title |
| `location` | string \| null | Source location of the event, such as a file and line |
| `user` | object \| null | User context of the event |
| `tags` | array | Tags attached to the event |
| `platform` | string \| null | Platform of the event, such as python or javascript |
| `dateReceived` | string \| null | When Sentry received the event (ISO 8601) |
| `contexts` | object \| null | Contexts of the event, such as device, os, browser and runtime |
| `size` | number \| null | Size of the event payload in bytes |
| `entries` | array | Event interfaces, such as exception, breadcrumbs and request, each with a type and data |
| `dist` | string \| null | Distribution of the release the event was sent with |
| `sdk` | object \| null | SDK that sent the event |
| `context` | object \| null | Additional data sent with the event in its extra field |
| `packages` | object | Modules loaded when the event occurred, keyed by module name with their versions |
| `type` | string | Event type, such as error, default or transaction |
| `metadata` | object | Event metadata |
| `errors` | array | Errors Sentry found while processing the event |
| `occurrence` | object \| null | |
| `_meta` | object | |
| `crashFile` | string \| null | |
| `culprit` | string \| null | |
| `dateCreated` | string | |
| `fingerprints` | array | |
| `groupingConfig` | object | |
| `startTimestamp` | number | |
| `endTimestamp` | number | |
| `measurements` | object \| null | |
| `breakdowns` | object \| null | |
| `release` | object \| null | |
| `userReport` | object \| null | |
| `sdkUpdates` | array | |
| `resolvedWith` | array | |
| `nextEventID` | string \| null | |
| `previousEventID` | string \| null | |
| `_meta` | object | Metadata about changes Sentry made to the event's fields, such as data scrubbing or trimming |
| `crashFile` | string \| null | Crash report attachment of the event, if any |
| `culprit` | string \| null | Function or module that caused the event |
| `dateCreated` | string | When the event occurred (ISO 8601) |
| `fingerprints` | array | Grouping hashes calculated for the event |
| `groupingConfig` | object | Grouping configuration used to group the event |
| `startTimestamp` | number | When the transaction started, as a Unix timestamp in seconds |
| `endTimestamp` | number | When the transaction ended, as a Unix timestamp in seconds |
| `measurements` | object \| null | Measurements recorded for the transaction, such as web vitals, keyed by name |
| `breakdowns` | object \| null | Span operation breakdowns for the transaction, keyed by breakdown name |
| `release` | object \| null | Release the event was sent with. Null when the event has no release |
| `userReport` | object \| null | User feedback submitted for the event. Null when there's none |
| `sdkUpdates` | array | Suggested updates for the SDK that sent the event |
| `resolvedWith` | array | How the event's stack frames were resolved during processing, such as which source map lookup method was used |
| `nextEventID` | string \| null | ID of the next event in the issue. Null for the latest event or when adjacentEvents is collapsed |
| `previousEventID` | string \| null | ID of the previous event in the issue. Null for the first event or when adjacentEvents is collapsed |
| `formatted` | object | Event rendered as Markdown, XML, or JSON; only returned for API requests with llmFormat |
| `trace` | object \| null | Trace context, or null when unavailable |
| `attachments` | array | Event attachments; each includes metadata and an absolute authenticated download URL |
Expand Down
17 changes: 10 additions & 7 deletions packages/cli/src/lib/api/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
createOrganizationProjectDetector,
createOrganizationWorkflow,
deleteOrganizationDetector,
deleteOrganizationWorkflow,
getOrganizationDetector,
type GetOrganizationDetectorResponse,
getOrganizationWorkflow,
Expand All @@ -35,7 +36,6 @@ import { ApiError, ValidationError } from "../errors.js";
import { resolveOrgRegion } from "../region.js";
import {
apiRequestToRegion,
apiRequestToRegionNoContent,
getOrgSdkConfig,
type PaginatedResponse,
parseLinkHeader,
Expand Down Expand Up @@ -513,12 +513,15 @@ export async function deleteIssueAlertRule(
orgSlug: string,
ruleId: string,
): Promise<void> {
const regionUrl = await resolveOrgRegion(orgSlug);
await apiRequestToRegionNoContent(
regionUrl,
`/organizations/${orgSlug}/workflows/${encodeURIComponent(ruleId)}/`,
{ method: "DELETE" },
);
const config = await getOrgSdkConfig(orgSlug);
const result = await deleteOrganizationWorkflow({
...config,
path: {
organization_id_or_slug: orgSlug,
workflow_id: Number(ruleId),
},
});
unwrapResult(result, `Failed to delete issue alert rule '${ruleId}'`);
}

/**
Expand Down
113 changes: 85 additions & 28 deletions packages/cli/src/lib/api/chunk-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,29 @@ import {
constants as zlibConstants,
zstdCompress as zstdCompressCb,
} from "node:zlib";
import {
type AssembleOrganizationArtifactBundleData,
type AssembleProjectDebugFilesData,
assembleOrganizationArtifactBundle,
assembleProjectDebugFiles,
getOrganizationChunkUploadOptions,
} from "@sentry/api";
import pLimit from "p-limit";
import {
array,
type GenericSchema,
type InferOutput,
nullish,
number,
object,
optional,
picklist,
record,
string,
} from "valibot";
import { ApiError } from "../errors.js";
import { logger } from "../logger.js";
import { resolveOrgRegion } from "../region.js";
import { getSdkConfig } from "../sentry-client.js";
import { apiRequestToRegion } from "./infrastructure.js";
import { getOrgSdkConfig, unwrapValidatedResult } from "./infrastructure.js";

const gzipAsync = promisify(gzipCb);
// zstdCompress exists only on Node.js 22.15+. On older runtimes (npm package
Expand Down Expand Up @@ -96,6 +102,11 @@ export const AssembleResponseSchema = object({

export type AssembleResponse = InferOutput<typeof AssembleResponseSchema>;

/** Response from the DIF assemble endpoint, keyed by file checksum. */
const DifAssembleResponseSchema = record(string(), AssembleResponseSchema);

export type DifAssembleResponse = InferOutput<typeof DifAssembleResponseSchema>;

// ── Types ───────────────────────────────────────────────────────────

/** Chunk metadata after splitting the ZIP for upload. */
Expand Down Expand Up @@ -145,11 +156,17 @@ export type UploadEncoding = (typeof UPLOAD_CODECS)[number];
export async function getChunkUploadOptions(
orgSlug: string,
): Promise<ChunkServerOptions> {
const regionUrl = await resolveOrgRegion(orgSlug);
const { data } = await apiRequestToRegion<ChunkServerOptions>(
regionUrl,
const config = await getOrgSdkConfig(orgSlug);
const result = await getOrganizationChunkUploadOptions({
...config,
path: { organization_id_or_slug: orgSlug },
});
// The schema also reads `maxWait`, which the generated type lacks.
const data = unwrapValidatedResult(
result,
"Failed to get chunk upload options",
ChunkServerOptionsSchema,
`organizations/${orgSlug}/chunk-upload/`,
{ schema: ChunkServerOptionsSchema },
);

// Self-hosted Sentry instances may return a chunk-upload URL that embeds
Expand Down Expand Up @@ -572,36 +589,84 @@ export async function uploadMissingBufferChunks(params: {
}

/**
* Poll an assemble endpoint until the server reports completion.
* Request DIF assembly for one batch of files.
*
* The server answers with one entry per file checksum in the body. Sending the
* same body again polls the assembly state.
*
* @param org - Organization slug
* @param project - Project slug
* @param body - Files to assemble, keyed by overall checksum
*/
export async function assembleDebugFiles(
org: string,
project: string,
body: NonNullable<AssembleProjectDebugFilesData["body"]>,
): Promise<DifAssembleResponse> {
const config = await getOrgSdkConfig(org);
const result = await assembleProjectDebugFiles({
...config,
path: { organization_id_or_slug: org, project_id_or_slug: project },
body,
});
return unwrapValidatedResult(
result,
"Failed to assemble debug files",
DifAssembleResponseSchema,
`projects/${org}/${project}/files/difs/assemble/`,
);
}

/**
* Request assembly of an artifact bundle. Sending the same body again polls
* the assembly state.
*
* Re-sends the same assemble body on each poll (the server uses
* @param org - Organization slug
* @param body - Bundle checksum, chunk checksums, projects and release
*/
export async function assembleArtifactBundle(
org: string,
body: NonNullable<AssembleOrganizationArtifactBundleData["body"]>,
): Promise<AssembleResponse> {
const config = await getOrgSdkConfig(org);
const result = await assembleOrganizationArtifactBundle({
...config,
path: { organization_id_or_slug: org },
body,
});
return unwrapValidatedResult(
result,
"Failed to assemble artifact bundle",
AssembleResponseSchema,
`organizations/${org}/artifactbundle/assemble/`,
);
}

/**
* Poll an assemble request until the server reports completion.
*
* `assemble` re-sends the same assemble body on each poll (the server uses
* the checksum to look up existing assembly state). By default returns when
* the state is `"ok"` or `"created"`; set `waitForOk` to block until the server
* finishes processing (`"ok"` only). Throws on `"error"` or timeout.
*
* @param params.regionUrl - Region base URL
* @param params.endpoint - The endpoint path to POST to
* @param params.body - The request body to send on each poll
* @param params.assemble - Sends the assemble request and returns its state
* @param params.endpoint - The assemble endpoint path, for error messages
* @param params.entityName - Human-readable name for error messages
* @param params.schema - Valibot schema for the response (defaults to {@link AssembleResponseSchema})
* @param params.waitForOk - Keep polling on `"created"`, returning only on `"ok"`
* @param params.deadlineMs - Override the default poll timeout window
*/
export async function pollAssembly(params: {
regionUrl: string;
assemble: () => Promise<AssembleResponse>;
endpoint: string;
body: unknown;
entityName: string;
schema?: GenericSchema<unknown, AssembleResponse>;
waitForOk?: boolean;
deadlineMs?: number;
}): Promise<void> {
const {
regionUrl,
assemble,
endpoint,
body,
entityName,
schema = AssembleResponseSchema,
waitForOk = false,
deadlineMs = ASSEMBLE_MAX_WAIT_MS,
} = params;
Expand All @@ -610,15 +675,7 @@ export async function pollAssembly(params: {
while (Date.now() < deadline) {
await new Promise((r) => setTimeout(r, ASSEMBLE_POLL_INTERVAL_MS));

const { data: pollResult } = await apiRequestToRegion<AssembleResponse>(
regionUrl,
endpoint,
{
method: "POST",
body,
schema,
},
);
const pollResult = await assemble();

if (pollResult.state === "ok") {
return;
Expand Down
Loading
Loading