feat: make best-effort to maintain order of stacks and cards
Some checks failed
Release Please / Release-plz (push) Failing after 42s
Test / build (map[name:stable]) (push) Has been cancelled
Test / build (map[name:nightly]) (push) Has been cancelled

This commit is contained in:
Paul Campbell 2024-12-22 22:47:29 +00:00
parent f9762512f4
commit 4f69fc0a4b
8 changed files with 90 additions and 24 deletions

View file

@ -1,6 +1,12 @@
//
use crate::nextcloud::model::{NextcloudCardDescription, NextcloudCardTitle};
use crate::trello::model::card::{TrelloCardDescription, TrelloCardName};
use crate::nextcloud::model::NextcloudStackTitle;
use crate::{
nextcloud::model::{NextcloudCardDescription, NextcloudCardTitle, NextcloudOrder},
trello::model::{
card::{TrelloCardDescription, TrelloCardName, TrelloCardPosition},
list::{TrelloListName, TrelloListPosition},
},
};
impl From<&TrelloCardName> for NextcloudCardTitle {
fn from(value: &TrelloCardName) -> Self {
@ -13,3 +19,21 @@ impl From<&TrelloCardDescription> for NextcloudCardDescription {
Self::new(value.to_string())
}
}
impl From<&TrelloListName> for NextcloudStackTitle {
fn from(value: &TrelloListName) -> Self {
Self::new(value.to_string())
}
}
impl From<&TrelloListPosition> for NextcloudOrder {
fn from(value: &TrelloListPosition) -> Self {
Self::new(value.0)
}
}
impl From<&TrelloCardPosition> for NextcloudOrder {
fn from(value: &TrelloCardPosition) -> Self {
Self::new(value.0)
}
}

View file

@ -40,7 +40,9 @@ pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> {
.map(TrelloBoardId::new)
.expect("find selected board");
// get list of trello stacks for the selected board
let trello_stacks = trello_client.board(&trello_board_id).await.result?.lists;
let mut trello_stacks = trello_client.board(&trello_board_id).await.result?.lists;
// sort stacks by position
trello_stacks.sort_by_key(|s| s.pos);
// prompt user to select some stacks
let trello_stack_names = trello_stacks
.iter()

View file

@ -56,10 +56,12 @@ impl Actor for ImportStackActor {
crate::p!(this.ctx.prt, "Importing stack: {}", this.trello_stack.name);
// - get the list of trello cards in the stack
let trello_cards = trello_client
let mut trello_cards = trello_client
.list_cards(&TrelloListId::new(this.trello_stack.id.as_ref()))
.await
.result?;
// sort cards by their position
trello_cards.sort_by_key(|card| card.pos);
// - for each card in the trello stack
for trello_card in trello_cards.into_iter() {

View file

@ -21,26 +21,41 @@ use crate::{
async fn create_any_missing_stacks(
ctx: &FullCtx,
selected_trello_stack_names: &[String],
selected_trello_stacks: &[TrelloList],
nextcloud_board_id: NextcloudBoardId,
nextcloud_stacks: Vec<Stack>,
) -> color_eyre::Result<()> {
let deck_client: DeckClient = ctx.deck_client();
// identify any stacks by name from those selected in trello that are missing in nextcloud
let missing_stack_names = selected_trello_stack_names
let missing_stacks = selected_trello_stacks
.iter()
.filter(|s| !nextcloud_stacks.iter().any(|ns| ns.title.deref() == *s))
.filter(|trello_stack| {
!nextcloud_stacks
.iter()
.any(|nextcloud_stack| nextcloud_stack.title.deref() == trello_stack.name.deref())
})
.cloned()
.collect::<Vec<_>>();
if !missing_stack_names.is_empty() {
crate::p!(ctx.prt, "Missing stacks: {:?}", missing_stack_names);
if !missing_stacks.is_empty() {
crate::p!(
ctx.prt,
"Missing stacks: {:?}",
missing_stacks
.iter()
.map(|s| s.name.as_ref())
.collect::<Vec<_>>()
);
// create any missing stacks in nextcloud
// for each missing stack
for missing_stack_name in missing_stack_names.into_iter() {
for missing_stack in missing_stacks.into_iter() {
// - create the stack
let stack = deck_client
.create_stack(nextcloud_board_id, &missing_stack_name.clone().into())
.create_stack(
nextcloud_board_id,
(&missing_stack.name).into(),
(&missing_stack.pos).into(),
)
.await
.result?;
p!(ctx.prt, "Created stack: {}", stack.title);
@ -83,7 +98,12 @@ impl Actor for ImportStacksActor {
on_actor_start!(this, actor_ref, {
// spawn a new ImportStack actor for each trello_stack named in selected_trello_stack_names
let mut trello_stacks = vec![];
std::mem::swap(&mut this.trello_stacks, &mut trello_stacks);
let selected_trello_stacks = trello_stacks
.into_iter()
.filter(|s| this.selected_trello_stack_names.contains(s.name.as_ref()))
.collect::<Vec<_>>();
// get list of nextcloud stacks in the selected board
let nextcloud_stacks = this
.ctx
@ -93,7 +113,7 @@ impl Actor for ImportStacksActor {
.result?;
create_any_missing_stacks(
&this.ctx,
&this.selected_trello_stack_names,
&selected_trello_stacks,
this.nextcloud_board_id,
nextcloud_stacks,
)
@ -107,11 +127,7 @@ impl Actor for ImportStacksActor {
.result?;
// for each selected trello stack
for selected_trello_stack in this
.trello_stacks
.iter()
.filter(|s| this.selected_trello_stack_names.contains(s.name.as_ref()))
{
for selected_trello_stack in selected_trello_stacks {
let nextcloud_stack_id = //this.
nextcloud_stacks
.iter()

View file

@ -7,6 +7,7 @@ use kxio::{
use reqwest::multipart;
use serde_json::json;
use crate::nextcloud::model::NextcloudOrder;
use crate::{
api_result::APIResult,
f,
@ -118,13 +119,14 @@ impl<'ctx> DeckClient<'ctx> {
pub(crate) async fn create_stack(
&self,
board_id: NextcloudBoardId,
stack_title: &NextcloudStackTitle,
stack_title: NextcloudStackTitle,
stack_order: NextcloudOrder,
) -> APIResult<Stack> {
self.request_with_body(
f!("boards/{board_id}/stacks"),
json!({
"title": stack_title,
"order": 999,
"order": stack_order,
})
.to_string(),
|net, url| net.post(url),

View file

@ -2,6 +2,7 @@
use clap::Parser;
use crate::execute::Execute;
use crate::nextcloud::model::NextcloudOrder;
use crate::{p, FullCtx};
#[derive(Parser, Debug)]
@ -50,7 +51,11 @@ impl Execute for NextcloudStackCommand {
} => {
let api_result = ctx
.deck_client()
.create_stack((*board_id).into(), &stack_title.clone().into())
.create_stack(
(*board_id).into(),
stack_title.clone().into(),
NextcloudOrder::new(999),
)
.await;
if *dump {
p!(ctx.prt, "{}", api_result.text);

View file

@ -11,8 +11,8 @@ newtype!(TrelloCardName, String, Display, "Card Name");
newtype!(TrelloCardDescription, String, Display, "Card Description");
newtype!(TrelloCardDue, String, Display, "Card Due");
#[derive(Debug, PartialEq, Eq, Display)]
pub struct TrelloCardPosition(i64);
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Display)]
pub(crate) struct TrelloCardPosition(pub i64);
impl<'de> Deserialize<'de> for TrelloCardPosition {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>

View file

@ -1,6 +1,6 @@
//
use derive_more::derive::Display;
use serde::Deserialize;
use serde::{Deserialize, Deserializer};
use crate::newtype;
@ -14,8 +14,23 @@ newtype!(
"List Name"
);
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Display)]
pub(crate) struct TrelloListPosition(pub i64);
impl<'de> Deserialize<'de> for TrelloListPosition {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// First deserialize as f64
let value = f64::deserialize(deserializer)?;
// Convert to i64 by rounding
Ok(TrelloListPosition(value.round() as i64))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub(crate) struct TrelloList {
pub(crate) id: TrelloListId,
pub(crate) name: TrelloListName,
pub(crate) pos: TrelloListPosition,
}