bulk operations fixes

- Frontend: implémente bulkDelete, bulkArchive, bulkRestore
- Backend: adapts ticket DTOs pour bulk operations
- Infrastructure: MCP server pour gestion bulk tickets
- Tests: coverage frontend + backend
This commit is contained in:
2026-07-28 20:38:07 +02:00
parent 73c6081ab8
commit bd4f76ae74
19 changed files with 597 additions and 35 deletions

View File

@ -14,26 +14,26 @@ use std::sync::{Arc, Mutex};
use application::{
AgentResumer, AgentWakeService, AppError, AssignIssueAgent, AssignSkillToAgent,
AssignTicketToSprint, AttachLiveAgent, AuthenticateSession, BackgroundCommandArchive,
BulkUpdateIssuePriority, BulkUpdateIssueStatus, CancelBackgroundTask, ChangeAgentProfile,
CheckEmbedderSuggestion, CloneOpenCodeProfileFromSeed, CloneProfileFromSeed, CloseProject,
CloseTab, CloseTerminal, CloseTicketAssistant, ConfigureProfiles, ContextGuardUseCases,
CreateAgentFromScratch, CreateAgentFromTemplate, CreateIssue, CreateLayout, CreateMemory,
CreateProject, CreateSkill, CreateSprint, CreateTemplate, DeleteAgent, DeleteEmbedderProfile,
DeleteIssue, DeleteLayout, DeleteMemory, DeleteModelArtifact, DeleteModelServer, DeleteProfile,
DeleteSkill, DeleteSprint, DeleteTemplate, DescribeEmbedderEngines, DetectAgentDrift,
DetectProfiles, DismissEmbedderSuggestion, EnsureLocalModelServer, FirstRunState,
GetAppExitWorkGuardState, GetLiveStateLean, GetMemory, GetProjectPermissions,
GetProjectSystemPermissions, GetProjectWorkState, GitBranches, GitCheckout, GitCommit,
GitGraph, GitInit, GitLog, GitStage, GitStatus, GitUnstage, HarvestMemoryFromTurn,
HealthUseCase, InspectConversation, InstallPluginFromArchive, InstallPluginFromDirectory,
JsonPluginManifestValidator, LaunchAgent, LaunchAgentInput, LinkIssues, ListAgents,
ListAgentsInput, ListClaudeModels, ListCodexModels, ListDevices, ListEmbedderProfiles,
ListIssues, ListLayouts, ListMemories, ListModelServers, ListOpenCodeProviders,
ListPluginRuntimeContributions, ListPlugins, ListProfiles, ListProjects, ListResumableAgents,
ListSkills, ListSprints, ListTemplates, LiveAgentRegistry, LiveSessions, LiveStateLeanProvider,
LiveStateProvider, LiveStateReadProvider, LoadLayout, McpRuntime, McpToolPermissionCatalogue,
MoveTabToNewWindow, MutateLayout, OnnxModelView, OpenProject, OpenTerminal,
OpenTicketAssistant, OrchestratorService, PairAttemptLimiter, PairDevice,
BulkDeleteIssues, BulkUpdateIssuePriority, BulkUpdateIssueStatus, CancelBackgroundTask,
ChangeAgentProfile, CheckEmbedderSuggestion, CloneOpenCodeProfileFromSeed,
CloneProfileFromSeed, CloseProject, CloseTab, CloseTerminal, CloseTicketAssistant,
ConfigureProfiles, ContextGuardUseCases, CreateAgentFromScratch, CreateAgentFromTemplate,
CreateIssue, CreateLayout, CreateMemory, CreateProject, CreateSkill, CreateSprint,
CreateTemplate, DeleteAgent, DeleteEmbedderProfile, DeleteIssue, DeleteLayout, DeleteMemory,
DeleteModelArtifact, DeleteModelServer, DeleteProfile, DeleteSkill, DeleteSprint,
DeleteTemplate, DescribeEmbedderEngines, DetectAgentDrift, DetectProfiles,
DismissEmbedderSuggestion, EnsureLocalModelServer, FirstRunState, GetAppExitWorkGuardState,
GetLiveStateLean, GetMemory, GetProjectPermissions, GetProjectSystemPermissions,
GetProjectWorkState, GitBranches, GitCheckout, GitCommit, GitGraph, GitInit, GitLog, GitStage,
GitStatus, GitUnstage, HarvestMemoryFromTurn, HealthUseCase, InspectConversation,
InstallPluginFromArchive, InstallPluginFromDirectory, JsonPluginManifestValidator, LaunchAgent,
LaunchAgentInput, LinkIssues, ListAgents, ListAgentsInput, ListClaudeModels, ListCodexModels,
ListDevices, ListEmbedderProfiles, ListIssues, ListLayouts, ListMemories, ListModelServers,
ListOpenCodeProviders, ListPluginRuntimeContributions, ListPlugins, ListProfiles, ListProjects,
ListResumableAgents, ListSkills, ListSprints, ListTemplates, LiveAgentRegistry, LiveSessions,
LiveStateLeanProvider, LiveStateProvider, LiveStateReadProvider, LoadLayout, McpRuntime,
McpToolPermissionCatalogue, MoveTabToNewWindow, MutateLayout, OnnxModelView, OpenProject,
OpenTerminal, OpenTicketAssistant, OrchestratorService, PairAttemptLimiter, PairDevice,
PermissionProjectorRegistry, ProposeContext, ReadAgentContext, ReadContext,
ReadConversationPage, ReadIssue, ReadIssueCarnet, ReadMcpToolPermissions, ReadMemory,
ReadMemoryIndex, ReadProjectContext, ReadSkill, ReadTemplate, RecallMemory, ReconcileLayouts,
@ -991,6 +991,8 @@ pub struct BackendCore {
pub bulk_update_issue_status: Arc<BulkUpdateIssueStatus>,
/// Update public ticket priorities in batch.
pub bulk_update_issue_priority: Arc<BulkUpdateIssuePriority>,
/// Delete public tickets in batch.
pub bulk_delete_issues: Arc<BulkDeleteIssues>,
/// Read a ticket carnet.
pub read_issue_carnet: Arc<ReadIssueCarnet>,
/// Update a ticket carnet.
@ -1684,6 +1686,10 @@ impl BackendCore {
Arc::clone(&clock) as Arc<dyn Clock>,
Arc::clone(&events_port),
));
let bulk_delete_issues = Arc::new(BulkDeleteIssues::new(
Arc::clone(&issue_store_port),
Arc::clone(&events_port),
));
let read_issue_carnet = Arc::new(ReadIssueCarnet::new(Arc::clone(&issue_store_port)));
let update_issue_carnet = Arc::new(UpdateIssueCarnet::new(
Arc::clone(&issue_store_port),
@ -2834,6 +2840,7 @@ impl BackendCore {
update_issue,
bulk_update_issue_status,
bulk_update_issue_priority,
bulk_delete_issues,
read_issue_carnet,
update_issue_carnet,
link_issues,
@ -4967,6 +4974,7 @@ mod mcp_serve_peer_tests {
"idea_ticket_update_priority",
"idea_ticket_bulk_update_status",
"idea_ticket_bulk_update_priority",
"idea_ticket_bulk_delete",
"idea_ticket_read_carnet",
"idea_ticket_update_carnet",
"idea_ticket_link",
@ -4987,8 +4995,8 @@ mod mcp_serve_peer_tests {
assert!(!names.contains(&"idea_reply"));
assert_eq!(
tools.len(),
33,
"exactly the thirty-three exposed idea_* tools; got {names:?}"
34,
"exactly the thirty-four exposed idea_* tools; got {names:?}"
);
drop(client); // EOF ⇒ serve loop ends

View File

@ -357,7 +357,9 @@ fn is_ticket_policy_mutation_tool(name: &str) -> bool {
fn is_ticket_policy_bulk_mutation_tool(name: &str) -> bool {
matches!(
name,
"idea_ticket_bulk_update_status" | "idea_ticket_bulk_update_priority"
"idea_ticket_bulk_update_status"
| "idea_ticket_bulk_update_priority"
| "idea_ticket_bulk_delete"
)
}

View File

@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize};
use uuid::Uuid;
use application::{
BulkIssueMutationOutput, BulkUpdateIssuePriorityInput, BulkUpdateIssueStatusInput,
CreateIssueInput, UpdateIssueInput,
BulkDeleteIssuesInput, BulkIssueMutationOutput, BulkUpdateIssuePriorityInput,
BulkUpdateIssueStatusInput, CreateIssueInput, UpdateIssueInput,
};
use domain::{
AgentId, AgentIssueRole, Issue, IssueActor, IssueCarnet, IssueIndexEntry, IssueLink,
@ -225,6 +225,15 @@ pub struct TicketBulkUpdatePriorityRequestDto {
pub priority: String,
}
/// Bulk delete request.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TicketBulkDeleteRequestDto {
#[serde(default)]
pub project_id: String,
pub refs: Vec<String>,
}
/// Bulk operation response.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -757,6 +766,16 @@ pub fn bulk_update_priority_input(
})
}
pub fn bulk_delete_input(
project: Project,
request: TicketBulkDeleteRequestDto,
) -> Result<BulkDeleteIssuesInput, ErrorDto> {
Ok(BulkDeleteIssuesInput {
project,
issue_refs: parse_bulk_refs(request.refs)?,
})
}
impl From<BulkIssueMutationOutput> for TicketBulkResultDto {
fn from(output: BulkIssueMutationOutput) -> Self {
Self {
@ -1272,6 +1291,27 @@ mod tests {
assert!(err.message.contains("duplicate ticket ref: #1"));
}
#[test]
fn bulk_delete_input_maps_refs_in_order() {
let input = bulk_delete_input(
project_for_tests(),
TicketBulkDeleteRequestDto {
project_id: String::new(),
refs: vec!["#2".into(), "#1".into()],
},
)
.unwrap();
assert_eq!(
input
.issue_refs
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>(),
vec!["#2", "#1"]
);
}
#[test]
fn actor_from_requester_maps_agent_uuid_else_user() {
let agent_id = AgentId::new_random();