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
37 changes: 37 additions & 0 deletions src/api/data_types/code_mappings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! Data types for the bulk code mappings API.

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BulkCodeMappingsRequest<'a> {
pub project: &'a str,
pub repository: &'a str,
pub default_branch: &'a str,
pub mappings: &'a [BulkCodeMapping],
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BulkCodeMapping {
pub stack_root: String,
pub source_root: String,
}

#[derive(Debug, Deserialize)]
pub struct BulkCodeMappingsResponse {
pub created: u64,
pub updated: u64,
pub errors: u64,
pub mappings: Vec<BulkCodeMappingResult>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BulkCodeMappingResult {
pub stack_root: String,
pub source_root: String,
pub status: String,
#[serde(default)]
pub detail: Option<String>,
}
2 changes: 2 additions & 0 deletions src/api/data_types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Data types used in the api module

mod chunking;
mod code_mappings;
mod deploy;
mod snapshots;

pub use self::chunking::*;
pub use self::code_mappings::*;
pub use self::deploy::*;
pub use self::snapshots::*;
11 changes: 11 additions & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,17 @@ impl AuthenticatedApi<'_> {
Ok(rv)
}

/// Bulk uploads code mappings for an organization.
pub fn bulk_upload_code_mappings(
&self,
org: &str,
body: &BulkCodeMappingsRequest,
) -> ApiResult<BulkCodeMappingsResponse> {
let path = format!("/organizations/{}/code-mappings/bulk/", PathArg(org));
self.post(&path, body)?
.convert_rnf(ApiErrorKind::OrganizationNotFound)
}

/// Creates a preprod snapshot artifact for the given project.
pub fn create_preprod_snapshot<S: Serialize>(
&self,
Expand Down
70 changes: 58 additions & 12 deletions src/commands/code_mappings/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@ use std::fs;
use anyhow::{bail, Context as _, Result};
use clap::{Arg, ArgMatches, Command};
use log::debug;
use serde::Deserialize;

use crate::api::{Api, BulkCodeMapping, BulkCodeMappingResult, BulkCodeMappingsRequest};
use crate::config::Config;
use crate::utils::formatting::Table;
use crate::utils::vcs;

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CodeMapping {
stack_root: String,
source_root: String,
}

pub fn make_command(command: Command) -> Command {
command
.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).")
Expand All @@ -39,12 +33,16 @@ pub fn make_command(command: Command) -> Command {
}

pub fn execute(matches: &ArgMatches) -> Result<()> {
let config = Config::current();
let org = config.get_org(matches)?;
let project = config.get_project(matches)?;

let path = matches
.get_one::<String>("path")
.expect("path is a required argument");
let data = fs::read(path).with_context(|| format!("Failed to read mappings file '{path}'"))?;

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

if mappings.is_empty() {
Expand Down Expand Up @@ -73,9 +71,33 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
git_repo.as_ref(),
)?;

println!("Found {} code mapping(s) in {path}", mappings.len());
println!("Repository: {repo_name}");
println!("Default branch: {default_branch}");
let mapping_count = mappings.len();
let request = BulkCodeMappingsRequest {
project: &project,
repository: &repo_name,
default_branch: &default_branch,
mappings: &mappings,
};

println!("Uploading {mapping_count} code mapping(s)...");

let api = Api::current();
let response = api
.authenticated()?
.bulk_upload_code_mappings(&org, &request)?;

print_results_table(response.mappings);
println!(
"Created: {}, Updated: {}, Errors: {}",
response.created, response.updated, response.errors
);

if response.errors > 0 {
bail!(
"{} mapping(s) failed to upload. See errors above.",
response.errors
);
}

Ok(())
}
Expand Down Expand Up @@ -174,6 +196,30 @@ fn infer_default_branch(git_repo: Option<&git2::Repository>, remote_name: Option
})
}

fn print_results_table(mappings: Vec<BulkCodeMappingResult>) {
let mut table = Table::new();
table
.title_row()
.add("Stack Root")
.add("Source Root")
.add("Status");

for result in mappings {
let status = match result.detail {
Some(detail) if result.status == "error" => format!("error: {detail}"),
_ => result.status,
};
table
.add_row()
.add(&result.stack_root)
.add(&result.source_root)
.add(&status);
}

table.print();
println!();
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
```
$ sentry-cli code-mappings upload tests/integration/_fixtures/code_mappings/mappings.json --org wat-org --project wat-project --repo owner/repo --default-branch main
? success
Uploading 2 code mapping(s)...
+------------------+---------------------------------------------+---------+
| Stack Root | Source Root | Status |
+------------------+---------------------------------------------+---------+
| com/example/core | modules/core/src/main/java/com/example/core | created |
| com/example/maps | modules/maps/src/main/java/com/example/maps | created |
+------------------+---------------------------------------------+---------+

Created: 2, Updated: 0, Errors: 0

```
4 changes: 4 additions & 0 deletions tests/integration/_fixtures/code_mappings/mappings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
{"stackRoot": "com/example/core", "sourceRoot": "modules/core/src/main/java/com/example/core"},
{"stackRoot": "com/example/maps", "sourceRoot": "modules/maps/src/main/java/com/example/maps"}
]
9 changes: 9 additions & 0 deletions tests/integration/_responses/code_mappings/post-bulk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"created": 2,
"updated": 0,
"errors": 0,
"mappings": [
{"stackRoot": "com/example/core", "sourceRoot": "modules/core/src/main/java/com/example/core", "status": "created"},
{"stackRoot": "com/example/maps", "sourceRoot": "modules/maps/src/main/java/com/example/maps", "status": "created"}
]
}
2 changes: 2 additions & 0 deletions tests/integration/code_mappings/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::integration::TestManager;

mod upload;

#[test]
fn command_code_mappings_help() {
TestManager::new().register_trycmd_test("code_mappings/code-mappings-help.trycmd");
Expand Down
12 changes: 12 additions & 0 deletions tests/integration/code_mappings/upload.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::integration::{MockEndpointBuilder, TestManager};

#[test]
fn command_code_mappings_upload() {
TestManager::new()
.mock_endpoint(
MockEndpointBuilder::new("POST", "/api/0/organizations/wat-org/code-mappings/bulk/")
.with_response_file("code_mappings/post-bulk.json"),
)
.register_trycmd_test("code_mappings/code-mappings-upload.trycmd")
.with_default_token();
}
Loading