Skip to content

Commit ab71fc0

Browse files
committed
Show mobile-app upload show analysis URL
1 parent c5d840d commit ab71fc0

File tree

7 files changed

+84
-102
lines changed

7 files changed

+84
-102
lines changed

src/api/data_types/chunking/mobile_app.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub struct ChunkedMobileAppRequest<'a> {
99
pub checksum: Digest,
1010
pub chunks: &'a [Digest],
1111
#[serde(skip_serializing_if = "Option::is_none")]
12-
pub git_sha: Option<&'a str>,
12+
pub head_sha: Option<&'a str>,
1313
#[serde(skip_serializing_if = "Option::is_none")]
1414
pub build_configuration: Option<&'a str>,
1515
}
@@ -20,4 +20,5 @@ pub struct AssembleMobileAppResponse {
2020
pub state: ChunkedFileState,
2121
pub missing_chunks: Vec<Digest>,
2222
pub detail: Option<String>,
23+
pub artifact_id: Option<String>,
2324
}

src/api/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,7 +1036,7 @@ impl<'a> AuthenticatedApi<'a> {
10361036
project: &str,
10371037
checksum: Digest,
10381038
chunks: &[Digest],
1039-
git_sha: Option<&str>,
1039+
head_sha: Option<&str>,
10401040
build_configuration: Option<&str>,
10411041
) -> ApiResult<AssembleMobileAppResponse> {
10421042
let url = format!(
@@ -1049,7 +1049,7 @@ impl<'a> AuthenticatedApi<'a> {
10491049
.with_json_body(&ChunkedMobileAppRequest {
10501050
checksum,
10511051
chunks,
1052-
git_sha,
1052+
head_sha,
10531053
build_configuration,
10541054
})?
10551055
.send()?

src/commands/mobile_app/upload.rs

Lines changed: 70 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ use clap::{Arg, ArgAction, ArgMatches, Command};
1111
use indicatif::ProgressStyle;
1212
use itertools::Itertools as _;
1313
use log::{debug, info, warn};
14-
use sha1_smol::Digest;
1514
use symbolic::common::ByteView;
1615
use zip::write::SimpleFileOptions;
1716
use zip::{DateTime, ZipWriter};
1817

1918
use crate::api::{Api, AuthenticatedApi, ChunkUploadCapability};
2019
use crate::config::Config;
2120
use crate::utils::args::ArgExt as _;
22-
use crate::utils::chunks::{upload_chunks, Chunk, ASSEMBLE_POLL_INTERVAL};
21+
use crate::utils::chunks::{upload_chunks, Chunk};
2322
use crate::utils::fs::get_sha1_checksums;
2423
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
2524
use crate::utils::fs::TempDir;
@@ -129,9 +128,10 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
129128

130129
let config = Config::current();
131130
let (org, project) = config.get_org_and_project(matches)?;
131+
let base_url = config.get_base_url()?;
132132

133-
let mut uploaded_paths = vec![];
134-
let mut errored_paths = vec![];
133+
let mut uploaded_paths_and_ids = vec![];
134+
let mut errored_paths_and_reasons = vec![];
135135
for (path, zip) in normalized_zips {
136136
info!("Uploading file: {}", path.display());
137137
let bytes = ByteView::open(zip.path())?;
@@ -143,41 +143,50 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
143143
sha.as_deref(),
144144
build_configuration,
145145
) {
146-
Ok(_) => {
146+
Ok(artifact_id) => {
147147
info!("Successfully uploaded file: {}", path.display());
148-
uploaded_paths.push(path.to_path_buf());
148+
uploaded_paths_and_ids.push((path.to_path_buf(), artifact_id));
149149
}
150150
Err(e) => {
151151
debug!("Failed to upload file at path {}: {}", path.display(), e);
152-
errored_paths.push(path.to_path_buf());
152+
errored_paths_and_reasons.push((path.to_path_buf(), e));
153153
}
154154
}
155155
}
156156

157-
if !errored_paths.is_empty() {
157+
if !errored_paths_and_reasons.is_empty() {
158158
warn!(
159159
"Failed to upload {} file{}:",
160-
errored_paths.len(),
161-
if errored_paths.len() == 1 { "" } else { "s" }
160+
errored_paths_and_reasons.len(),
161+
if errored_paths_and_reasons.len() == 1 {
162+
""
163+
} else {
164+
"s"
165+
}
162166
);
163-
for path in errored_paths {
164-
warn!(" - {}", path.display());
165-
}
166-
}
167-
168-
println!(
169-
"Successfully uploaded {} file{} to Sentry",
170-
uploaded_paths.len(),
171-
if uploaded_paths.len() == 1 { "" } else { "s" }
172-
);
173-
if uploaded_paths.len() < 3 {
174-
for path in &uploaded_paths {
175-
println!(" - {}", path.display());
167+
for (path, reason) in errored_paths_and_reasons {
168+
warn!(" - {} ({})", path.display(), reason);
176169
}
177170
}
178171

179-
if uploaded_paths.is_empty() {
172+
if uploaded_paths_and_ids.is_empty() {
180173
bail!("Failed to upload any files");
174+
} else {
175+
println!(
176+
"Successfully uploaded {} file{} to Sentry",
177+
uploaded_paths_and_ids.len(),
178+
if uploaded_paths_and_ids.len() == 1 {
179+
""
180+
} else {
181+
"s"
182+
}
183+
);
184+
if uploaded_paths_and_ids.len() < 3 {
185+
for (path, artifact_id) in &uploaded_paths_and_ids {
186+
let url = format!("{base_url}/{org}/preprod/{project}/{artifact_id}");
187+
println!(" - {} {url}", path.display());
188+
}
189+
}
181190
}
182191
Ok(())
183192
}
@@ -337,14 +346,15 @@ fn normalize_directory(path: &Path) -> Result<TempFile> {
337346
Ok(temp_file)
338347
}
339348

349+
/// Returns artifact id if upload was successful.
340350
fn upload_file(
341351
api: &AuthenticatedApi,
342352
bytes: &[u8],
343353
org: &str,
344354
project: &str,
345355
sha: Option<&str>,
346356
build_configuration: Option<&str>,
347-
) -> Result<()> {
357+
) -> Result<String> {
348358
const SELF_HOSTED_ERROR_HINT: &str = "If you are using a self-hosted Sentry server, \
349359
update to the latest version of Sentry to use the mobile-app upload command.";
350360

@@ -371,7 +381,7 @@ fn upload_file(
371381
}
372382

373383
let progress_style =
374-
ProgressStyle::default_spinner().template("{spinner} Optimizing bundle for upload...");
384+
ProgressStyle::default_spinner().template("{spinner} Preparing for upload...");
375385
let pb = ProgressBar::new_spinner();
376386
pb.enable_steady_tick(100);
377387
pb.set_style(progress_style);
@@ -384,76 +394,46 @@ fn upload_file(
384394
.map(|(data, checksum)| Chunk((*checksum, data)))
385395
.collect::<Vec<_>>();
386396

387-
pb.finish_with_duration("Finishing upload");
388-
389-
let response =
390-
api.assemble_mobile_app(org, project, checksum, &checksums, sha, build_configuration)?;
391-
chunks.retain(|Chunk((digest, _))| response.missing_chunks.contains(digest));
392-
393-
if !chunks.is_empty() {
394-
let upload_progress_style = ProgressStyle::default_bar().template(
395-
"{prefix:.dim} Uploading files...\
396-
\n{wide_bar} {bytes}/{total_bytes} ({eta})",
397-
);
398-
upload_chunks(&chunks, &chunk_upload_options, upload_progress_style)?;
399-
} else {
400-
println!("Nothing to upload, all files are on the server");
401-
}
402-
403-
poll_assemble(
404-
api,
405-
checksum,
406-
&checksums,
407-
org,
408-
project,
409-
sha,
410-
build_configuration,
411-
)?;
412-
Ok(())
413-
}
414-
415-
fn poll_assemble(
416-
api: &AuthenticatedApi,
417-
checksum: Digest,
418-
chunks: &[Digest],
419-
org: &str,
420-
project: &str,
421-
sha: Option<&str>,
422-
build_configuration: Option<&str>,
423-
) -> Result<()> {
424-
debug!("Polling assemble for checksum: {}", checksum);
425-
426-
let progress_style = ProgressStyle::default_spinner().template("{spinner} Processing files...");
427-
let pb = ProgressBar::new_spinner();
428-
429-
pb.enable_steady_tick(100);
430-
pb.set_style(progress_style);
431-
432-
let response = loop {
397+
pb.finish_with_duration("Preparing for upload");
398+
399+
// In the normal case we go through this loop exactly twice:
400+
// 1. state=not_found
401+
// server tells us the we need to send every chunk and we do so
402+
// 2. artifact_id set so we're done (likely state=created)
403+
//
404+
// In the case where all the chunks are already on the server we go
405+
// through only once:
406+
// 1. state=ok, artifact_id set
407+
//
408+
// In the case where something went wrong (which could be on either
409+
// iteration of the loop) we get:
410+
// n. state=err, artifact_id unset
411+
let result = loop {
433412
let response =
434-
api.assemble_mobile_app(org, project, checksum, chunks, sha, build_configuration)?;
413+
api.assemble_mobile_app(org, project, checksum, &checksums, sha, build_configuration)?;
414+
chunks.retain(|Chunk((digest, _))| response.missing_chunks.contains(digest));
415+
416+
if !chunks.is_empty() {
417+
let upload_progress_style = ProgressStyle::default_bar().template(
418+
"{prefix:.dim} Uploading files...\
419+
\n{wide_bar} {bytes}/{total_bytes} ({eta})",
420+
);
421+
upload_chunks(&chunks, &chunk_upload_options, upload_progress_style)?;
422+
}
435423

436-
if response.state.is_finished() {
437-
break response;
424+
// is_err() is not_found or error and is_finished() is ok or error, so if both the the
425+
// state is error
426+
if response.state.is_finished() && response.state.is_err() {
427+
let message = response.detail.as_deref().unwrap_or("unknown error");
428+
bail!("Failed to process uploaded files: {}", message);
438429
}
439430

440-
std::thread::sleep(ASSEMBLE_POLL_INTERVAL);
431+
if let Some(artifact_id) = response.artifact_id {
432+
break Ok(artifact_id);
433+
}
441434
};
442435

443-
pb.finish_with_duration("Processing");
444-
445-
if response.state.is_err() {
446-
let message = response.detail.as_deref().unwrap_or("unknown error");
447-
bail!("Failed to process uploaded files: {}", message);
448-
}
449-
450-
if response.state.is_pending() {
451-
info!("File upload complete (processing pending on server)");
452-
} else {
453-
info!("File processing complete");
454-
}
455-
456-
Ok(())
436+
result
457437
}
458438

459439
#[cfg(not(windows))]

tests/integration/_cases/mobile_app/mobile_app-upload-apk-all-uploaded.trycmd

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
$ sentry-cli mobile-app upload tests/integration/_fixtures/mobile_app/apk.apk
33
? success
44
[..]WARN[..]EXPERIMENTAL: The mobile-app subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release.
5-
Nothing to upload, all files are on the server
65
Successfully uploaded 1 file to Sentry
7-
- tests/integration/_fixtures/mobile_app/apk.apk
6+
- tests/integration/_fixtures/mobile_app/apk.apk http[..]/wat-org/preprod/wat-project/42
87

98
```

tests/integration/_cases/mobile_app/mobile_app-upload-apk.trycmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ $ sentry-cli mobile-app upload tests/integration/_fixtures/mobile_app/apk.apk --
33
? success
44
[..]WARN[..]EXPERIMENTAL: The mobile-app subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release.
55
Successfully uploaded 1 file to Sentry
6-
- tests/integration/_fixtures/mobile_app/apk.apk
6+
- tests/integration/_fixtures/mobile_app/apk.apk http[..]/wat-org/preprod/wat-project/42
77

88
```

tests/integration/_cases/mobile_app/mobile_app-upload-ipa.trycmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ $ sentry-cli mobile-app upload tests/integration/_fixtures/mobile_app/ipa.ipa --
33
? success
44
[..]WARN[..]EXPERIMENTAL: The mobile-app subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release.
55
Successfully uploaded 1 file to Sentry
6-
- tests/integration/_fixtures/mobile_app/ipa.ipa
6+
- tests/integration/_fixtures/mobile_app/ipa.ipa http[..]/wat-org/preprod/wat-project/some-text-id
77

88
```

tests/integration/mobile_app/upload.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ fn command_mobile_app_upload_apk_all_uploaded() {
8686
"POST",
8787
"/api/0/projects/wat-org/wat-project/files/preprodartifacts/assemble/",
8888
)
89-
.with_response_body(r#"{"state":"ok","missingChunks":[]}"#),
89+
.with_response_body(r#"{"state":"ok","missingChunks":[],"artifactId":"42"}"#),
9090
)
9191
.register_trycmd_test("mobile_app/mobile_app-upload-apk-all-uploaded.trycmd")
9292
.with_default_token();
@@ -159,7 +159,7 @@ fn command_mobile_app_upload_apk_chunked() {
159159
"/api/0/projects/wat-org/wat-project/files/preprodartifacts/assemble/",
160160
)
161161
.with_header_matcher("content-type", "application/json")
162-
.with_matcher(r#"{"checksum":"18e40e6e932d0b622d631e887be454cc2003dbb5","chunks":["18e40e6e932d0b622d631e887be454cc2003dbb5"],"git_sha":"test_sha"}"#)
162+
.with_matcher(r#"{"checksum":"18e40e6e932d0b622d631e887be454cc2003dbb5","chunks":["18e40e6e932d0b622d631e887be454cc2003dbb5"],"head_sha":"test_sha"}"#)
163163
.with_response_fn(move |_| {
164164
if is_first_assemble_call.swap(false, Ordering::Relaxed) {
165165
r#"{
@@ -169,7 +169,8 @@ fn command_mobile_app_upload_apk_chunked() {
169169
} else {
170170
r#"{
171171
"state": "ok",
172-
"missingChunks": []
172+
"missingChunks": [],
173+
"artifactId": "42"
173174
}"#
174175
}
175176
.into()
@@ -213,7 +214,7 @@ fn command_mobile_app_upload_ipa_chunked() {
213214
"/api/0/projects/wat-org/wat-project/files/preprodartifacts/assemble/",
214215
)
215216
.with_header_matcher("content-type", "application/json")
216-
.with_matcher(r#"{"checksum":"ed9da71e3688261875db21b266da84ffe004a8a4","chunks":["ed9da71e3688261875db21b266da84ffe004a8a4"],"git_sha":"test_sha"}"#)
217+
.with_matcher(r#"{"checksum":"ed9da71e3688261875db21b266da84ffe004a8a4","chunks":["ed9da71e3688261875db21b266da84ffe004a8a4"],"head_sha":"test_sha"}"#)
217218
.with_response_fn(move |_| {
218219
if is_first_assemble_call.swap(false, Ordering::Relaxed) {
219220
r#"{
@@ -223,7 +224,8 @@ fn command_mobile_app_upload_ipa_chunked() {
223224
} else {
224225
r#"{
225226
"state": "ok",
226-
"missingChunks": []
227+
"missingChunks": [],
228+
"artifactId": "some-text-id"
227229
}"#
228230
}
229231
.into()

0 commit comments

Comments
 (0)