Skip to content

Commit 06d724e

Browse files
committed
feat(cli): Wire up API integration for code-mappings upload
Add bulk_upload_code_mappings() API method and BulkCodeMappingsRequest/ Response data types. Wire up the upload command to authenticate, call the bulk endpoint, and display results in a table with summary counts. Exit with code 1 if any mappings fail.
1 parent 70ddd86 commit 06d724e

9 files changed

Lines changed: 152 additions & 13 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//! Data types for the bulk code mappings API.
2+
3+
use serde::{Deserialize, Serialize};
4+
5+
#[derive(Debug, Serialize)]
6+
#[serde(rename_all = "camelCase")]
7+
pub struct BulkCodeMappingsRequest {
8+
pub project: String,
9+
pub repository: String,
10+
pub default_branch: String,
11+
pub mappings: Vec<BulkCodeMapping>,
12+
}
13+
14+
#[derive(Debug, Deserialize, Serialize)]
15+
#[serde(rename_all = "camelCase")]
16+
pub struct BulkCodeMapping {
17+
pub stack_root: String,
18+
pub source_root: String,
19+
}
20+
21+
#[derive(Debug, Deserialize)]
22+
pub struct BulkCodeMappingsResponse {
23+
pub created: u64,
24+
pub updated: u64,
25+
pub errors: u64,
26+
pub mappings: Vec<BulkCodeMappingResult>,
27+
}
28+
29+
#[derive(Debug, Deserialize)]
30+
#[serde(rename_all = "camelCase")]
31+
pub struct BulkCodeMappingResult {
32+
pub stack_root: String,
33+
pub source_root: String,
34+
pub status: String,
35+
#[serde(default)]
36+
pub detail: Option<String>,
37+
}

src/api/data_types/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
//! Data types used in the api module
22
33
mod chunking;
4+
mod code_mappings;
45
mod deploy;
56
mod snapshots;
67

78
pub use self::chunking::*;
9+
pub use self::code_mappings::*;
810
pub use self::deploy::*;
911
pub use self::snapshots::*;

src/api/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,17 @@ impl AuthenticatedApi<'_> {
978978
Ok(rv)
979979
}
980980

981+
/// Bulk uploads code mappings for an organization.
982+
pub fn bulk_upload_code_mappings(
983+
&self,
984+
org: &str,
985+
body: &BulkCodeMappingsRequest,
986+
) -> ApiResult<BulkCodeMappingsResponse> {
987+
let path = format!("/organizations/{}/code-mappings/bulk/", PathArg(org));
988+
self.post(&path, body)?
989+
.convert_rnf(ApiErrorKind::ResourceNotFound)
990+
}
991+
981992
/// Creates a preprod snapshot artifact for the given project.
982993
pub fn create_preprod_snapshot<S: Serialize>(
983994
&self,

src/commands/code_mappings/upload.rs

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,12 @@ use std::fs;
33
use anyhow::{bail, Context as _, Result};
44
use clap::{Arg, ArgMatches, Command};
55
use log::debug;
6-
use serde::{Deserialize, Serialize};
76

7+
use crate::api::{Api, BulkCodeMapping, BulkCodeMappingsRequest};
88
use crate::config::Config;
9+
use crate::utils::formatting::Table;
910
use crate::utils::vcs;
1011

11-
#[derive(Debug, Deserialize, Serialize)]
12-
#[serde(rename_all = "camelCase")]
13-
struct CodeMapping {
14-
stack_root: String,
15-
source_root: String,
16-
}
17-
1812
pub fn make_command(command: Command) -> Command {
1913
command
2014
.about("Upload code mappings for a project from a JSON file. Each mapping pairs a stack trace root (e.g. com/example/module) with the corresponding source path in your repository (e.g. modules/module/src/main/java/com/example/module).")
@@ -39,12 +33,16 @@ pub fn make_command(command: Command) -> Command {
3933
}
4034

4135
pub fn execute(matches: &ArgMatches) -> Result<()> {
36+
let config = Config::current();
37+
let org = config.get_org(matches)?;
38+
let project = config.get_project(matches)?;
39+
4240
let path = matches
4341
.get_one::<String>("path")
4442
.expect("path is a required argument");
4543
let data = fs::read(path).with_context(|| format!("Failed to read mappings file '{path}'"))?;
4644

47-
let mappings: Vec<CodeMapping> =
45+
let mappings: Vec<BulkCodeMapping> =
4846
serde_json::from_slice(&data).context("Failed to parse mappings JSON")?;
4947

5048
if mappings.is_empty() {
@@ -73,7 +71,6 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
7371
// Prefer explicit config (SENTRY_VCS_REMOTE / ini), then inspect
7472
// the repo for the best remote (upstream > origin > first).
7573
let remote_name = git_repo.as_ref().ok().and_then(|repo| {
76-
let config = Config::current();
7774
let configured_remote = config.get_cached_vcs_remote();
7875
if vcs::git_repo_remote_url(repo, &configured_remote).is_ok() {
7976
debug!("Using configured VCS remote: {configured_remote}");
@@ -146,9 +143,57 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
146143
}
147144
};
148145

149-
println!("Found {} code mapping(s) in {path}", mappings.len());
150-
println!("Repository: {repo_name}");
151-
println!("Default branch: {default_branch}");
146+
let mapping_count = mappings.len();
147+
let request = BulkCodeMappingsRequest {
148+
project,
149+
repository: repo_name,
150+
default_branch,
151+
mappings,
152+
};
153+
154+
println!("Uploading {mapping_count} code mapping(s)...");
155+
156+
let api = Api::current();
157+
let response = api
158+
.authenticated()?
159+
.bulk_upload_code_mappings(&org, &request)?;
160+
161+
// Display results
162+
let mut table = Table::new();
163+
table
164+
.title_row()
165+
.add("Stack Root")
166+
.add("Source Root")
167+
.add("Status");
168+
169+
for result in &response.mappings {
170+
let status = match result.status.as_str() {
171+
"error" => match &result.detail {
172+
Some(detail) => format!("error: {detail}"),
173+
None => "error".to_owned(),
174+
},
175+
s => s.to_owned(),
176+
};
177+
table
178+
.add_row()
179+
.add(&result.stack_root)
180+
.add(&result.source_root)
181+
.add(&status);
182+
}
183+
184+
table.print();
185+
println!();
186+
println!(
187+
"Created: {}, Updated: {}, Errors: {}",
188+
response.created, response.updated, response.errors
189+
);
190+
191+
if response.errors > 0 {
192+
bail!(
193+
"{} mapping(s) failed to upload. See errors above.",
194+
response.errors
195+
);
196+
}
152197

153198
Ok(())
154199
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
```
2+
$ sentry-cli code-mappings upload tests/integration/_fixtures/code_mappings/mappings.json --org wat-org --project wat-project --repo owner/repo --default-branch main
3+
? success
4+
Uploading 2 code mapping(s)...
5+
+------------------+---------------------------------------------+---------+
6+
| Stack Root | Source Root | Status |
7+
+------------------+---------------------------------------------+---------+
8+
| com/example/core | modules/core/src/main/java/com/example/core | created |
9+
| com/example/maps | modules/maps/src/main/java/com/example/maps | created |
10+
+------------------+---------------------------------------------+---------+
11+
12+
Created: 2, Updated: 0, Errors: 0
13+
14+
```
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
{"stackRoot": "com/example/core", "sourceRoot": "modules/core/src/main/java/com/example/core"},
3+
{"stackRoot": "com/example/maps", "sourceRoot": "modules/maps/src/main/java/com/example/maps"}
4+
]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"created": 2,
3+
"updated": 0,
4+
"errors": 0,
5+
"mappings": [
6+
{"stackRoot": "com/example/core", "sourceRoot": "modules/core/src/main/java/com/example/core", "status": "created"},
7+
{"stackRoot": "com/example/maps", "sourceRoot": "modules/maps/src/main/java/com/example/maps", "status": "created"}
8+
]
9+
}

tests/integration/code_mappings/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::integration::TestManager;
22

3+
mod upload;
4+
35
#[test]
46
fn command_code_mappings_help() {
57
TestManager::new().register_trycmd_test("code_mappings/code-mappings-help.trycmd");
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use crate::integration::{MockEndpointBuilder, TestManager};
2+
3+
#[test]
4+
fn command_code_mappings_upload() {
5+
TestManager::new()
6+
.mock_endpoint(
7+
MockEndpointBuilder::new(
8+
"POST",
9+
"/api/0/organizations/wat-org/code-mappings/bulk/",
10+
)
11+
.with_response_file("code_mappings/post-bulk.json"),
12+
)
13+
.register_trycmd_test("code_mappings/code-mappings-upload.trycmd")
14+
.with_default_token();
15+
}

0 commit comments

Comments
 (0)