Compare commits

...

12 commits

8 changed files with 75 additions and 74 deletions

View file

@ -30,6 +30,9 @@ jobs:
- name: 🔗 Artifactview PR comment - name: 🔗 Artifactview PR comment
if: ${{ always() && github.event_name == 'pull_request' }} if: ${{ always() && github.event_name == 'pull_request' }}
run: | run: |
echo "Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER" if [[ "$GITEA_ACTIONS" == "true" ]]; then RUN_NUMBER="$GITHUB_RUN_NUMBER"; else RUN_NUMBER="$GITHUB_RUN_ID"; fi
echo "Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$RUN_NUMBER"
echo "Pull: ${{ github.event.number }}" echo "Pull: ${{ github.event.number }}"
curl -SsL --fail-with-body -w "\n" -X POST https://av.thetadev.de/.well-known/api/prComment -H "Content-Type: application/json" --data "{\"url\": \"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER\", \"pr\": ${{ github.event.number }}, \"artifact_titles\": {\"test\":\"🧪 Test report\"}, \"artifact_paths\": {\"test\":\"/junit.xml?viewer=1\"}}"
curl -SsL --fail-with-body -w "\n" -X POST https://av.thetadev.de/.well-known/api/prComment -H "Content-Type: application/json" \
--data '{"url": "'"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$RUN_NUMBER"'", "pr": ${{ github.event.number }}, "artifact_titles": {"test":"🧪 Test report"}, "artifact_paths": {"test":"/junit.xml?viewer=1"}}'

View file

@ -3,6 +3,27 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [v0.4.4](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.3..v0.4.4) - 2024-06-22
### 🐛 Bug Fixes
- Use forge aliases for PR comment links - ([3690b02](https://codeberg.org/ThetaDev/artifactview/commit/3690b0244cf47d0d73511f5f69f5d12abe0f1837))
## [v0.4.3](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.2..v0.4.3) - 2024-06-22
### 🐛 Bug Fixes
- 404 error on GitHub comment creation - ([d8c3ab4](https://codeberg.org/ThetaDev/artifactview/commit/d8c3ab4f36727f118b31683db87d287d9945ee14))
## [v0.4.2](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.1..v0.4.2) - 2024-06-22
### 🐛 Bug Fixes
- PR comment emoji prefix detection - ([1e36edf](https://codeberg.org/ThetaDev/artifactview/commit/1e36edf49978e8ba24a85d4663ff3ebaf9642a29))
## [v0.4.1](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.0..v0.4.1) - 2024-06-22 ## [v0.4.1](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.0..v0.4.1) - 2024-06-22
### 🚀 Features ### 🚀 Features

44
Cargo.lock generated
View file

@ -141,7 +141,7 @@ dependencies = [
[[package]] [[package]]
name = "artifactview" name = "artifactview"
version = "0.4.1" version = "0.4.4"
dependencies = [ dependencies = [
"async_zip", "async_zip",
"axum", "axum",
@ -187,7 +187,6 @@ dependencies = [
"tower-http", "tower-http",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"unic-emoji-char",
"url", "url",
"yarte", "yarte",
"yarte_helpers", "yarte_helpers",
@ -3145,47 +3144,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unic-char-property"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
dependencies = [
"unic-char-range",
]
[[package]]
name = "unic-char-range"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
[[package]]
name = "unic-common"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
[[package]]
name = "unic-emoji-char"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d"
dependencies = [
"unic-char-property",
"unic-char-range",
"unic-ucd-version",
]
[[package]]
name = "unic-ucd-version"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
dependencies = [
"unic-common",
]
[[package]] [[package]]
name = "unicase" name = "unicase"
version = "2.7.0" version = "2.7.0"

View file

@ -1,6 +1,6 @@
[package] [package]
name = "artifactview" name = "artifactview"
version = "0.4.1" version = "0.4.4"
edition = "2021" edition = "2021"
authors = ["ThetaDev <thetadev@magenta.de>"] authors = ["ThetaDev <thetadev@magenta.de>"]
license = "MIT" license = "MIT"
@ -73,7 +73,6 @@ tokio-util = { version = "0.7.11", features = ["io"] }
tower-http = { version = "0.5.2", features = ["trace", "set-header"] } tower-http = { version = "0.5.2", features = ["trace", "set-header"] }
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = "0.3.18" tracing-subscriber = "0.3.18"
unic-emoji-char = "0.9.0"
url = "2.5.0" url = "2.5.0"
yarte = { version = "0.15.7", features = ["json"] } yarte = { version = "0.15.7", features = ["json"] }

View file

@ -1,5 +1,7 @@
# Artifactview # Artifactview
**This is a test PR**: 4
View CI build artifacts from Forgejo/GitHub using your web browser! View CI build artifacts from Forgejo/GitHub using your web browser!
Forgejo and GitHub's CI systems allow you to upload files and directories as Forgejo and GitHub's CI systems allow you to upload files and directories as
@ -82,7 +84,8 @@ artifacts).
- name: 🔗 Artifactview PR comment - name: 🔗 Artifactview PR comment
if: ${{ always() && github.event_name == 'pull_request' }} if: ${{ always() && github.event_name == 'pull_request' }}
run: | run: |
curl -SsL --fail-with-body -w "\n" -X POST https://av.thetadev.de/.well-known/api/prComment -H "Content-Type: application/json" --data "{\"url\": \"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER\", \"pr\": ${{ github.event.number }}}" if [[ "$GITEA_ACTIONS" == "true" ]]; then RUN_NUMBER="$GITHUB_RUN_NUMBER"; else RUN_NUMBER="$GITHUB_RUN_ID"; fi
curl -SsL --fail-with-body -w "\n" -X POST https://av.thetadev.de/.well-known/api/prComment -H "Content-Type: application/json" --data "{\"url\": \"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$RUN_NUMBER\", \"pr\": ${{ github.event.number }}}"
``` ```
## API ## API
@ -254,11 +257,12 @@ Example list: `foo;bar`, example map: `foo=>f1;bar=>b1`
### Access tokens ### Access tokens
GitHub does not allow downloading artifacts for public repositories for unauthenticated GitHub does not allow downloading artifacts for public repositories for unauthenticated
users. So you need to setup an access token to use Artifactview with GitHub. These are users. So you need to setup an access token to use Artifactview with GitHub.
the permissions that need to be enabled:
- Repository access: All repositories If you are not using the `prComment` feature, you can use a fine-grained access token
- Repository permissions: Pull requests (Read and write) with the "Public repositories (read-only)" permission. If you want to create pull
request comments, you have to use a classic token with the "public_repo" scope enabled
(the fine-grained tokens did not work in my test).
Forgejo does not require access tokens to download artifacts on public repositories, so Forgejo does not require access tokens to download artifacts on public repositories, so
you only need to create a token if you want to use the `prComment`-API. In this case, you only need to create a token if you want to use the `prComment`-API. In this case,

View file

@ -1,5 +1,4 @@
use std::{ use std::{
borrow::Cow,
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
fmt::Write, fmt::Write,
net::{IpAddr, SocketAddr}, net::{IpAddr, SocketAddr},
@ -673,7 +672,7 @@ impl App {
.extract::<Json<PrCommentReq>, _>() .extract::<Json<PrCommentReq>, _>()
.await .await
.map_err(|e| Error::BadRequest(e.body_text().into()))?; .map_err(|e| Error::BadRequest(e.body_text().into()))?;
let query = RunQuery::from_forge_url(&req.url)?; let query = RunQuery::from_forge_url_alias(&req.url, &state.i.cfg.load().site_aliases)?;
if let Some(limiter) = &state.i.lim_pr_comment { if let Some(limiter) = &state.i.lim_pr_comment {
limiter.check_key(&ip).map_err(Error::from)?; limiter.check_key(&ip).map_err(Error::from)?;
@ -866,20 +865,7 @@ fn pr_comment_text(p: PrCommentTextParams) -> String {
}; };
let write_link_icon = |s: &mut String, title: &str, href: &str| { let write_link_icon = |s: &mut String, title: &str, href: &str| {
// Move leading emoji into a prefix variable since including them in the link does not look good let (title_pfx, title) = util::split_icon_prefix(title);
let mut title_pfx = String::new();
let mut title = Cow::Borrowed(title);
if let Some((i, c)) = title
.char_indices()
// Some emoji use variation selectors that are not included in is_emoji
.find(|(_, c)| !unic_emoji_char::is_emoji(*c) && !('\u{fe00}'..='\u{fe0f}').contains(c))
{
if i > 0 && c == ' ' {
title[..i + 1].clone_into(&mut title_pfx);
title = title[i + 1..].to_owned().into();
}
}
_ = write!( _ = write!(
s, s,
r#"{title_pfx}<a href="{href}" target="_blank" rel="noopener noreferrer">{title}</a>"#, r#"{title_pfx}<a href="{href}" target="_blank" rel="noopener noreferrer">{title}</a>"#,

View file

@ -269,6 +269,7 @@ impl ArtifactApi {
} }
} }
#[tracing::instrument(level = "error", skip_all)]
pub async fn list(&self, query: &RunQuery, cached: bool) -> Result<Vec<Artifact>> { pub async fn list(&self, query: &RunQuery, cached: bool) -> Result<Vec<Artifact>> {
let cache_key = query.cache_key(); let cache_key = query.cache_key();
let fut = async { let fut = async {
@ -290,6 +291,7 @@ impl ArtifactApi {
} }
} }
#[tracing::instrument(level = "error", skip_all)]
pub async fn fetch(&self, query: &ArtifactQuery) -> Result<Artifact> { pub async fn fetch(&self, query: &ArtifactQuery) -> Result<Artifact> {
if query.is_github() { if query.is_github() {
self.fetch_github(query).await self.fetch_github(query).await
@ -305,6 +307,7 @@ impl ArtifactApi {
} }
} }
#[tracing::instrument(level = "error", skip_all)]
pub async fn download(&self, artifact: &Artifact, path: &Path) -> Result<()> { pub async fn download(&self, artifact: &Artifact, path: &Path) -> Result<()> {
if artifact.expired { if artifact.expired {
return Err(Error::Expired); return Err(Error::Expired);
@ -416,10 +419,9 @@ impl ArtifactApi {
if let Err(e) = resp.error_for_status_ref() { if let Err(e) = resp.error_for_status_ref() {
let status = resp.status(); let status = resp.status();
let msg = resp.json::<ApiError>().await.ok(); let msg = resp.json::<ApiError>().await.ok();
Err(Error::HttpClient( let msg_str = msg.map(|msg| msg.message).unwrap_or(e.to_string()).into();
msg.map(|msg| msg.message).unwrap_or(e.to_string()).into(), tracing::error!("API error: {msg_str}");
status, Err(Error::HttpClient(msg_str, status))
))
} else { } else {
Ok(resp) Ok(resp)
} }
@ -492,6 +494,7 @@ impl ArtifactApi {
.header(header::AUTHORIZATION, format!("token {token}"))) .header(header::AUTHORIZATION, format!("token {token}")))
} }
#[tracing::instrument(level = "error", skip_all)]
pub async fn workflow_run(&self, query: &RunQuery) -> Result<WorkflowRun> { pub async fn workflow_run(&self, query: &RunQuery) -> Result<WorkflowRun> {
if query.is_github() { if query.is_github() {
self.workflow_run_github(query).await self.workflow_run_github(query).await
@ -554,6 +557,7 @@ impl ArtifactApi {
Ok(run.into()) Ok(run.into())
} }
#[tracing::instrument(level = "error", skip_all)]
pub async fn add_comment( pub async fn add_comment(
&self, &self,
query: QueryRef<'_>, query: QueryRef<'_>,
@ -621,8 +625,8 @@ impl ArtifactApi {
) -> Result<u64> { ) -> Result<u64> {
if let Some(old_comment_id) = old_comment_id { if let Some(old_comment_id) = old_comment_id {
let url = format!( let url = format!(
"https://api.github.com/repos/{}/{}/issues/{}/comments/{}", "https://api.github.com/repos/{}/{}/issues/comments/{}",
query.user, query.repo, issue_id, old_comment_id query.user, query.repo, old_comment_id
); );
if recreate { if recreate {
Self::send_api_req_empty(self.req_github(Method::DELETE, url)?).await?; Self::send_api_req_empty(self.req_github(Method::DELETE, url)?).await?;
@ -650,6 +654,7 @@ impl ArtifactApi {
Ok(new_c.id) Ok(new_c.id)
} }
#[tracing::instrument(level = "error", skip_all)]
pub async fn find_comment( pub async fn find_comment(
&self, &self,
query: QueryRef<'_>, query: QueryRef<'_>,
@ -702,6 +707,7 @@ impl ArtifactApi {
Ok(None) Ok(None)
} }
#[tracing::instrument(level = "error", skip_all)]
pub async fn get_pr(&self, query: QueryRef<'_>, pr_id: u64) -> Result<PullRequest> { pub async fn get_pr(&self, query: QueryRef<'_>, pr_id: u64) -> Result<PullRequest> {
let req = if query.is_github() { let req = if query.is_github() {
self.get_github(format!( self.get_github(format!(

View file

@ -302,6 +302,18 @@ pub fn extract_delim<'a>(s: &'a str, start: &str, end: &str) -> Option<&'a str>
None None
} }
pub fn split_icon_prefix(s: &str) -> (&str, &str) {
if let Some((i, c)) = s
.char_indices()
.find(|(_, c)| c.is_ascii() || c.is_alphanumeric())
{
if i > 0 && c == ' ' && s.get(i + 1..).is_some() {
return (&s[..i + 1], &s[i + 1..]);
}
}
("", s)
}
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -390,4 +402,16 @@ pub(crate) mod tests {
let res = super::filename_ext(filename); let res = super::filename_ext(filename);
assert_eq!(res, expect); assert_eq!(res, expect);
} }
#[rstest]
#[case("🧪 Test", ("🧪 ", "Test"))]
#[case("🧪👨‍👩‍👦 Test", ("🧪👨‍👩‍👦 ", "Test"))]
#[case("🧪 👨‍👩‍👦 Test", ("🧪 ", "👨‍👩‍👦 Test"))]
#[case("", ("", ""))]
#[case("Test", ("", "Test"))]
#[case("運命 Test", ("", "運命 Test"))]
fn split_icon_prefix(#[case] s: &str, #[case] expect: (&str, &str)) {
let res = super::split_icon_prefix(s);
assert_eq!(res, expect);
}
} }