Compare commits
3 commits
9ce25467f1
...
dba33f86f3
Author | SHA1 | Date | |
---|---|---|---|
|
dba33f86f3 | ||
|
f9a4bc7c58 | ||
|
ba29d361eb |
8 changed files with 75 additions and 74 deletions
|
@ -30,9 +30,6 @@ jobs:
|
|||
- name: 🔗 Artifactview PR comment
|
||||
if: ${{ always() && github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
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 "Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_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/$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/$GITHUB_RUN_NUMBER\", \"pr\": ${{ github.event.number }}, \"artifact_titles\": {\"test\":\"🧪 Test report\"}, \"artifact_paths\": {\"test\":\"/junit.xml?viewer=1\"}}"
|
||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -3,27 +3,6 @@
|
|||
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
|
||||
|
||||
### 🚀 Features
|
||||
|
|
44
Cargo.lock
generated
44
Cargo.lock
generated
|
@ -141,7 +141,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "artifactview"
|
||||
version = "0.4.4"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"async_zip",
|
||||
"axum",
|
||||
|
@ -187,6 +187,7 @@ dependencies = [
|
|||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"unic-emoji-char",
|
||||
"url",
|
||||
"yarte",
|
||||
"yarte_helpers",
|
||||
|
@ -3144,6 +3145,47 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "artifactview"
|
||||
version = "0.4.4"
|
||||
version = "0.4.1"
|
||||
edition = "2021"
|
||||
authors = ["ThetaDev <thetadev@magenta.de>"]
|
||||
license = "MIT"
|
||||
|
@ -73,6 +73,7 @@ tokio-util = { version = "0.7.11", features = ["io"] }
|
|||
tower-http = { version = "0.5.2", features = ["trace", "set-header"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
unic-emoji-char = "0.9.0"
|
||||
url = "2.5.0"
|
||||
yarte = { version = "0.15.7", features = ["json"] }
|
||||
|
||||
|
|
14
README.md
14
README.md
|
@ -1,6 +1,6 @@
|
|||
# Artifactview
|
||||
|
||||
**This is a test PR**: 4
|
||||
**This is a test PR**: 3
|
||||
|
||||
View CI build artifacts from Forgejo/GitHub using your web browser!
|
||||
|
||||
|
@ -84,8 +84,7 @@ artifacts).
|
|||
- name: 🔗 Artifactview PR comment
|
||||
if: ${{ always() && github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
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 }}}"
|
||||
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 }}}"
|
||||
```
|
||||
|
||||
## API
|
||||
|
@ -257,12 +256,11 @@ Example list: `foo;bar`, example map: `foo=>f1;bar=>b1`
|
|||
### Access tokens
|
||||
|
||||
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.
|
||||
users. So you need to setup an access token to use Artifactview with GitHub. These are
|
||||
the permissions that need to be enabled:
|
||||
|
||||
If you are not using the `prComment` feature, you can use a fine-grained access token
|
||||
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).
|
||||
- Repository access: All repositories
|
||||
- Repository permissions: Pull requests (Read and write)
|
||||
|
||||
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,
|
||||
|
|
18
src/app.rs
18
src/app.rs
|
@ -1,4 +1,5 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{BTreeMap, HashMap},
|
||||
fmt::Write,
|
||||
net::{IpAddr, SocketAddr},
|
||||
|
@ -672,7 +673,7 @@ impl App {
|
|||
.extract::<Json<PrCommentReq>, _>()
|
||||
.await
|
||||
.map_err(|e| Error::BadRequest(e.body_text().into()))?;
|
||||
let query = RunQuery::from_forge_url_alias(&req.url, &state.i.cfg.load().site_aliases)?;
|
||||
let query = RunQuery::from_forge_url(&req.url)?;
|
||||
|
||||
if let Some(limiter) = &state.i.lim_pr_comment {
|
||||
limiter.check_key(&ip).map_err(Error::from)?;
|
||||
|
@ -865,7 +866,20 @@ fn pr_comment_text(p: PrCommentTextParams) -> String {
|
|||
};
|
||||
|
||||
let write_link_icon = |s: &mut String, title: &str, href: &str| {
|
||||
let (title_pfx, title) = util::split_icon_prefix(title);
|
||||
// Move leading emoji into a prefix variable since including them in the link does not look good
|
||||
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!(
|
||||
s,
|
||||
r#"{title_pfx}<a href="{href}" target="_blank" rel="noopener noreferrer">{title}</a>"#,
|
||||
|
|
|
@ -269,7 +269,6 @@ impl ArtifactApi {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "error", skip_all)]
|
||||
pub async fn list(&self, query: &RunQuery, cached: bool) -> Result<Vec<Artifact>> {
|
||||
let cache_key = query.cache_key();
|
||||
let fut = async {
|
||||
|
@ -291,7 +290,6 @@ impl ArtifactApi {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "error", skip_all)]
|
||||
pub async fn fetch(&self, query: &ArtifactQuery) -> Result<Artifact> {
|
||||
if query.is_github() {
|
||||
self.fetch_github(query).await
|
||||
|
@ -307,7 +305,6 @@ impl ArtifactApi {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "error", skip_all)]
|
||||
pub async fn download(&self, artifact: &Artifact, path: &Path) -> Result<()> {
|
||||
if artifact.expired {
|
||||
return Err(Error::Expired);
|
||||
|
@ -419,9 +416,10 @@ impl ArtifactApi {
|
|||
if let Err(e) = resp.error_for_status_ref() {
|
||||
let status = resp.status();
|
||||
let msg = resp.json::<ApiError>().await.ok();
|
||||
let msg_str = msg.map(|msg| msg.message).unwrap_or(e.to_string()).into();
|
||||
tracing::error!("API error: {msg_str}");
|
||||
Err(Error::HttpClient(msg_str, status))
|
||||
Err(Error::HttpClient(
|
||||
msg.map(|msg| msg.message).unwrap_or(e.to_string()).into(),
|
||||
status,
|
||||
))
|
||||
} else {
|
||||
Ok(resp)
|
||||
}
|
||||
|
@ -494,7 +492,6 @@ impl ArtifactApi {
|
|||
.header(header::AUTHORIZATION, format!("token {token}")))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "error", skip_all)]
|
||||
pub async fn workflow_run(&self, query: &RunQuery) -> Result<WorkflowRun> {
|
||||
if query.is_github() {
|
||||
self.workflow_run_github(query).await
|
||||
|
@ -557,7 +554,6 @@ impl ArtifactApi {
|
|||
Ok(run.into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "error", skip_all)]
|
||||
pub async fn add_comment(
|
||||
&self,
|
||||
query: QueryRef<'_>,
|
||||
|
@ -625,8 +621,8 @@ impl ArtifactApi {
|
|||
) -> Result<u64> {
|
||||
if let Some(old_comment_id) = old_comment_id {
|
||||
let url = format!(
|
||||
"https://api.github.com/repos/{}/{}/issues/comments/{}",
|
||||
query.user, query.repo, old_comment_id
|
||||
"https://api.github.com/repos/{}/{}/issues/{}/comments/{}",
|
||||
query.user, query.repo, issue_id, old_comment_id
|
||||
);
|
||||
if recreate {
|
||||
Self::send_api_req_empty(self.req_github(Method::DELETE, url)?).await?;
|
||||
|
@ -654,7 +650,6 @@ impl ArtifactApi {
|
|||
Ok(new_c.id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "error", skip_all)]
|
||||
pub async fn find_comment(
|
||||
&self,
|
||||
query: QueryRef<'_>,
|
||||
|
@ -707,7 +702,6 @@ impl ArtifactApi {
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "error", skip_all)]
|
||||
pub async fn get_pr(&self, query: QueryRef<'_>, pr_id: u64) -> Result<PullRequest> {
|
||||
let req = if query.is_github() {
|
||||
self.get_github(format!(
|
||||
|
|
24
src/util.rs
24
src/util.rs
|
@ -302,18 +302,6 @@ pub fn extract_delim<'a>(s: &'a str, start: &str, end: &str) -> Option<&'a str>
|
|||
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)]
|
||||
pub(crate) mod tests {
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -402,16 +390,4 @@ pub(crate) mod tests {
|
|||
let res = super::filename_ext(filename);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue