Compare commits

..

9 Commits

Author SHA1 Message Date
eb730f4ae0 Release v0.6.0
All checks were successful
Check go code / build-and-release (push) Successful in 1m4s
Release binary and debian package for cl-cli / webext-build (push) Successful in 5m46s
Release binary and debian package for cl-cli / build-and-release (push) Successful in 1m56s
2025-11-18 09:48:34 +01:00
69eafc509a Append link to gitlab to create link between open project and gitlab (#7)
All checks were successful
Check go code / build-and-release (push) Successful in 1m7s
Reviewed-on: #7
Co-authored-by: Julien Fastré <julien.fastre@champs-libres.coop>
Co-committed-by: Julien Fastré <julien.fastre@champs-libres.coop>
2025-11-18 08:45:31 +00:00
f72f03e7d4 fix link
All checks were successful
Check go code / build-and-release (push) Successful in 1m2s
2025-11-13 12:47:09 +00:00
255e7fd5f0 link to gitea personal access token page
Some checks failed
Check go code / build-and-release (push) Has been cancelled
2025-11-13 12:46:24 +00:00
58422f926e Add a link to the most recent release
All checks were successful
Check go code / build-and-release (push) Successful in 1m3s
2025-11-13 13:00:20 +01:00
986bb4e2c2 remove debug information on build-and-release workflow
All checks were successful
Check go code / build-and-release (push) Successful in 1m6s
2025-11-13 12:49:29 +01:00
606486c3fe Release v0.5.0
All checks were successful
Release binary and debian package for cl-cli / webext-build (push) Successful in 3m10s
Release binary and debian package for cl-cli / build-and-release (push) Successful in 1m51s
2025-11-13 12:47:07 +01:00
0b9099d2d3 add missing changie 2025-11-13 12:42:56 +01:00
af1b805616 Set up native messaging with debian packages (#6)
All checks were successful
Check go code / build-and-release (push) Successful in 1m2s
Reviewed-on: #6
Co-authored-by: Julien Fastré <julien.fastre@champs-libres.coop>
Co-committed-by: Julien Fastré <julien.fastre@champs-libres.coop>
2025-11-13 11:39:38 +00:00
14 changed files with 167 additions and 38 deletions

View File

@@ -1,3 +0,0 @@
kind: Added
body: Add a web extension
time: 2025-11-12T20:13:11.961655215+01:00

4
.changes/v0.5.0.md Normal file
View File

@@ -0,0 +1,4 @@
## v0.5.0 - 2025-11-13
### Added
* Add a web extension
* Install using package manager, with CI generating the package

3
.changes/v0.6.0.md Normal file
View File

@@ -0,0 +1,3 @@
## v0.6.0 - 2025-11-18
### Added
* Append work package link to gitlab description after creating a WP from a gitlab issue

View File

@@ -80,5 +80,4 @@ jobs:
md5sum: true md5sum: true
sha256sum: true sha256sum: true
body: Test release body: Test release
prerelease: true body_path: .changes/${{ github.ref_name }}.md
#body_path: .changes/${{ github.ref_name }}.md

View File

@@ -6,6 +6,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie). and is generated by [Changie](https://github.com/miniscruff/changie).
## v0.5.0 - 2025-11-13
### Added
* Add a web extension
* Install using package manager, with CI generating the package
## v0.4.1 - 2025-10-25 ## v0.4.1 - 2025-10-25
### Fixed ### Fixed
* Fix dependencies * Fix dependencies

41
Cargo.lock generated
View File

@@ -17,6 +17,15 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "android_system_properties" name = "android_system_properties"
version = "0.1.5" version = "0.1.5"
@@ -174,12 +183,13 @@ dependencies = [
[[package]] [[package]]
name = "cl-cli" name = "cl-cli"
version = "0.5.0-alpha16" version = "0.6.0"
dependencies = [ dependencies = [
"clap", "clap",
"gitlab", "gitlab",
"log", "log",
"open", "open",
"regex",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
@@ -1155,6 +1165,35 @@ dependencies = [
"getrandom 0.3.4", "getrandom 0.3.4",
] ]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.24" version = "0.12.24"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "cl-cli" name = "cl-cli"
version = "0.5.0-alpha16" version = "0.6.0"
edition = "2021" edition = "2021"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
description = "Some helpers scripts for Champs-Libres" description = "Some helpers scripts for Champs-Libres"
@@ -27,6 +27,7 @@ simple-home-dir = "0.5.2"
log = "0.4.28" log = "0.4.28"
open = "5.3.2" open = "5.3.2"
serde_json = "1.0.145" serde_json = "1.0.145"
regex = "1.12.2"
[package.metadata.deb] [package.metadata.deb]
maintainer = "Julien Fastré <julien.fastre@champs-libres.coop>" maintainer = "Julien Fastré <julien.fastre@champs-libres.coop>"

View File

@@ -6,23 +6,51 @@ Currently:
- convert a gitlab issue to a work package - convert a gitlab issue to a work package
## Install & configure ## Install
### Install There are two components to install:
Download the most recent binaries at https://gitea.champs-libres.be/julienfastre/cl-cli/releases - a firefox add-ons;
- some binaries on the host machine
Once downloaded, install it: ### Install firefox add-on
``` Using firefox, download the `.xpi` file from the [release page](https://gitea.champs-libres.be/julienfastre/cl-cli/releases) of the `julienfastre/cl-cli` repository.
# this will install the command globally with the name "cl-cli"
sudo install cl-cli /usr/local/bin/cl-cli
# this will install the command globally with the name "cl" [Go to the most recent release](https://gitea.champs-libres.be/julienfastre/cl-cli/releases/latest)
sudo install cl-cli /usr/local/bin/cl
### Install cl-cli binaries
#### Using package manager
A debian package is published and stored on gitea.
```bash
# download the gpg key
sudo curl https://gitea.champs-libres.be/api/packages/julienfastre/debian/repository.key -o /etc/apt/keyrings/gitea-julienfastre.asc
# add the package repository. You must adapt the $distribution (see below)
echo "deb [signed-by=/etc/apt/keyrings/gitea-julienfastre.asc] https://gitea.champs-libres.be/api/packages/julienfastre/debian $distribution main" | sudo tee -a /etc/apt/sources.list.d/gitea.list
# update
sudo apt update
# install
sudo apt install cl-cli
``` ```
### Configure Available distributions:
- Ubuntu 24..04: noble
- Ubuntu 25.04: plucky
- Ubuntu: 25.10: questing
**Note:** The package is very simple, there are very few dependencies, and there isn't any difference between them for now.
## Configure
Copy the file [config.toml.dist](./config.toml.dist) as a template, and Copy the file [config.toml.dist](./config.toml.dist) as a template, and
save it at the path `$HOME/.config/cl-cli/config.toml`: save it at the path `$HOME/.config/cl-cli/config.toml`:
@@ -33,14 +61,19 @@ cp config.toml $HOME/.config/cl-cli/config.toml
editor $HOME/.config/cl-cli/config.toml editor $HOME/.config/cl-cli/config.toml
``` ```
Then, fill it with the required configuration options (gitlab and openproject token). Then, fill it with the required configuration options:
- gitea personal access token ([Access the page on gitea.champs-libres.be](https://gitea.champs-libres.be/user/settings/applications)):
![configuration screenshort](./docs/configuration/gitea_screenshot_configure_pat.png)
- Gitlab: [Create an access token](https://gitlab.com/-/user_settings/personal_access_tokens?name=cl-cli&scopes=api,read_user) With the "api" and "read_user" scopes
- [Openproject](https://champs-libres.openproject.com/my/access_tokens)
## Usage ## Usage as CLI
### Convert a gitlab issue into a work package ### Convert a gitlab issue into a work package
```bash ```bash
cl-cli planning i2work https://gitlab.com/Chill-Projet/chill-bundles/-/issues/240 chill cl-cli planning i2work https://gitlab.com/Chill-Projet/chill-bundles/-/issues/240 chill
``` ```

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -6,9 +6,12 @@ use crate::gitlab::issue::{Issue, IssueBundle, Project};
use crate::openproject::client::Client; use crate::openproject::client::Client;
use crate::openproject::user::{GetMe, User}; use crate::openproject::user::{GetMe, User};
use crate::openproject::work::WorkPackageWriterAssignee; use crate::openproject::work::WorkPackageWriterAssignee;
use crate::planning::utils::{append_related_issues, IssueRelated};
use crate::planning::Issue2WorkActionTrait; use crate::planning::Issue2WorkActionTrait;
use crate::planning::Issue2WorkResult; use crate::planning::Issue2WorkResult;
use gitlab::api::{issues, projects, AsyncQuery}; use gitlab::api::projects::issues::EditIssue;
use gitlab::api::{issues, projects, ApiError, AsyncQuery};
use gitlab::RestError;
use url::Url; use url::Url;
#[derive(Debug)] #[derive(Debug)]
@@ -71,6 +74,27 @@ impl Issue2WorkActionTrait for GitlabAction {
.create_work_package(&(&dto).into(), &args.project_id) .create_work_package(&(&dto).into(), &args.project_id)
.await?; .await?;
let url_wp = format!(
"{}/projects/{}/work_packages/{}",
config.openproject.base_url, args.project_id, work_package.id
);
let content =
append_related_issues(&IssueRelated::OpenProjectIssue(url_wp), &issue.description);
let endpoint_put_result = EditIssue::builder()
.project(issue.project_id)
.issue(issue.iid)
.description(content)
.build();
if let Ok(endpoint_put) = endpoint_put_result {
let r: Result<Issue, ApiError<RestError>> = endpoint_put.query_async(&client).await;
if r.is_err() {
eprintln!("Error while updating the issue description: {:?}", r);
}
}
let result = Issue2WorkResult { let result = Issue2WorkResult {
work_package_url: format!( work_package_url: format!(
"{}/projects/{}/work_packages/{}", "{}/projects/{}/work_packages/{}",

View File

@@ -65,10 +65,30 @@ fn add_related_issues_section(issue: &IssueRelated) -> Vec<String> {
fn convert_issue_link_items(issue: &IssueRelated) -> String { fn convert_issue_link_items(issue: &IssueRelated) -> String {
match issue { match issue {
IssueRelated::OpenProjectIssue(issue_url) => format!("- [{}]({})", issue_url, issue_url), IssueRelated::OpenProjectIssue(issue_url) => {
let m = format!("- [{}]({})", issue_url, issue_url);
match extract_id(issue_url) {
Some(n) => format!("{}\n- OP#{}", m, n),
None => m,
}
}
} }
} }
use regex::Regex;
fn extract_id(url: &str) -> Option<u32> {
// Cette regex capture le dernier segment numérique si lURL se termine par :
// - /work_packages/<id>
// - /wp/<id>
// Et accepte n'importe quel segment avant.
let re = Regex::new(r"/(?:work_packages|wp)/(\d+)$").unwrap();
re.captures(url)
.and_then(|caps| caps.get(1))
.and_then(|m| m.as_str().parse::<u32>().ok())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::planning::utils::{append_related_issues, IssueRelated}; use crate::planning::utils::{append_related_issues, IssueRelated};
@@ -80,7 +100,8 @@ mod tests {
r#" r#"
## Related issues ## Related issues
- [https://example/wp/1](https://example/wp/1)"#, - [https://example/wp/1](https://example/wp/1)
- OP#1"#,
append_related_issues(&issue, &("".to_string())) append_related_issues(&issue, &("".to_string()))
); );
} }
@@ -97,7 +118,8 @@ Some content
## Related issues ## Related issues
- [https://example/wp/1](https://example/wp/1)"#, - [https://example/wp/1](https://example/wp/1)
- OP#1"#,
append_related_issues( append_related_issues(
&issue, &issue,
&"Something happens.\n\ &"Something happens.\n\
@@ -123,7 +145,8 @@ Some content
## Related issues ## Related issues
- [https://example/wp/2](https://example/wp/2) - [https://example/wp/2](https://example/wp/2)
- [https://example/wp/1](https://example/wp/1)"#, - [https://example/wp/1](https://example/wp/1)
- OP#1"#,
append_related_issues( append_related_issues(
&issue, &issue,
&r#"Something happens. &r#"Something happens.
@@ -154,6 +177,7 @@ Some content
- [https://example/wp/2](https://example/wp/2) - [https://example/wp/2](https://example/wp/2)
- [https://example/wp/1](https://example/wp/1) - [https://example/wp/1](https://example/wp/1)
- OP#1
## Other content ## Other content

View File

@@ -172,10 +172,10 @@ mod tests {
fn deserializes_issue2work_variant() { fn deserializes_issue2work_variant() {
// Given: a JSON with the external tag "type" matching the enum variant name // Given: a JSON with the external tag "type" matching the enum variant name
let payload = json!({ let payload = json!({
"type": "Issue2Work", "type": "Issue2Work",
"url": "https://example.com/issues/123", "url": "https://example.com/issues/123",
"project": "test" "project": "test"
}); });
// When: deserializing to InputMessage // When: deserializing to InputMessage
let msg: InputMessage = serde_json::from_value(payload).expect("valid enum JSON"); let msg: InputMessage = serde_json::from_value(payload).expect("valid enum JSON");
@@ -192,10 +192,10 @@ mod tests {
#[test] #[test]
fn fails_on_unknown_type_tag() { fn fails_on_unknown_type_tag() {
let payload = json!({ let payload = json!({
"type": "Unknown", "type": "Unknown",
"url": "https://example.com/issues/123", "url": "https://example.com/issues/123",
"project": "test" "project": "test"
}); });
let err = serde_json::from_value::<InputMessage>(payload).unwrap_err(); let err = serde_json::from_value::<InputMessage>(payload).unwrap_err();
// Basic sanity check that it's a data error mentioning the unrecognized variant // Basic sanity check that it's a data error mentioning the unrecognized variant
@@ -210,8 +210,8 @@ mod tests {
fn fails_when_missing_required_fields() { fn fails_when_missing_required_fields() {
// Missing "url" and "project" // Missing "url" and "project"
let payload = json!({ let payload = json!({
"type": "Issue2Work" "type": "Issue2Work"
}); });
let err = serde_json::from_value::<InputMessage>(payload).unwrap_err(); let err = serde_json::from_value::<InputMessage>(payload).unwrap_err();
let msg = err.to_string(); let msg = err.to_string();

View File

@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Champs-Libres Helper", "name": "Champs-Libres Helper",
"version": "0.4", "version": "0.6.0",
"browser_specific_settings": { "browser_specific_settings": {
"gecko": { "gecko": {
"id": "helper@champs-libres-coop", "id": "helper@champs-libres-coop",
@@ -26,4 +26,4 @@
"scripts": ["dist/background/background.js"], "scripts": ["dist/background/background.js"],
"type": "module" "type": "module"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "web-extension", "name": "web-extension",
"version": "0.4.0", "version": "0.6.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {