Ajoute created_by sur IssueIndexEntry/TicketSummaryDto + filtre createdBy (#5), et les use cases BulkUpdateIssueStatus/Priority + BulkDeleteIssues avec les DTO/outils MCP idea_ticket_bulk_* associés (#6). NON VERT : BulkDeleteIssues::execute référence BatchIssueResult::deleted(), constructeur pas encore ajouté à BatchIssueResult (seuls ok/err existent) -> `cargo check` échoue (E0599 dans application::issues::mod). Reste à DevBackend avant toute QA/merge. Branché sur feature/sdk-integration (dépend du commit #2 dans orchestrator/mcp/server.rs et tools.rs) ; à rebaser sur develop une fois feature/sdk-integration mergé. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -278,6 +278,8 @@ pub fn run() {
|
||||
tickets::ticket_create,
|
||||
tickets::ticket_read,
|
||||
tickets::ticket_delete,
|
||||
tickets::ticket_bulk_update_status,
|
||||
tickets::ticket_bulk_update_priority,
|
||||
tickets::open_ticket_chat,
|
||||
tickets::close_ticket_chat,
|
||||
tickets::ticket_list,
|
||||
|
||||
@ -284,7 +284,31 @@ fn enforce_policy(
|
||||
"tool `{name}` is not permitted for requester {requester}"
|
||||
)));
|
||||
}
|
||||
if is_ticket_policy_mutation_tool(name) {
|
||||
if is_ticket_policy_bulk_mutation_tool(name) {
|
||||
let refs = arguments
|
||||
.get("refs")
|
||||
.and_then(Value::as_array)
|
||||
.ok_or_else(|| {
|
||||
ToolInvocationError::InvalidArguments(format!(
|
||||
"tool `{name}` requires ticket refs under the active policy"
|
||||
))
|
||||
})?;
|
||||
for raw_ref in refs {
|
||||
let raw_ref = raw_ref.as_str().ok_or_else(|| {
|
||||
ToolInvocationError::InvalidArguments(format!(
|
||||
"tool `{name}` requires string ticket refs under the active policy"
|
||||
))
|
||||
})?;
|
||||
let issue_ref = IssueRef::from_str(raw_ref).map_err(|e| {
|
||||
ToolInvocationError::InvalidArguments(format!("invalid ticket ref: {e}"))
|
||||
})?;
|
||||
if !policy.permits_ticket_mutation(name, issue_ref) {
|
||||
return Err(ToolInvocationError::Rejected(format!(
|
||||
"tool `{name}` is not permitted for ticket {issue_ref}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
} else if is_ticket_policy_mutation_tool(name) {
|
||||
let raw_ref = arguments
|
||||
.get("ref")
|
||||
.and_then(Value::as_str)
|
||||
@ -317,6 +341,13 @@ 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"
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
@ -66,6 +66,8 @@ impl AppState {
|
||||
read: Arc::clone(&core.read_issue),
|
||||
list: Arc::clone(&core.list_issues),
|
||||
update: Arc::clone(&core.update_issue),
|
||||
bulk_update_status: Arc::clone(&core.bulk_update_issue_status),
|
||||
bulk_update_priority: Arc::clone(&core.bulk_update_issue_priority),
|
||||
read_carnet: Arc::clone(&core.read_issue_carnet),
|
||||
update_carnet: Arc::clone(&core.update_issue_carnet),
|
||||
link: Arc::clone(&core.link_issues),
|
||||
|
||||
@ -29,6 +29,8 @@ pub struct AppTicketToolProvider {
|
||||
pub read: Arc<application::ReadIssue>,
|
||||
pub list: Arc<application::ListIssues>,
|
||||
pub update: Arc<application::UpdateIssue>,
|
||||
pub bulk_update_status: Arc<application::BulkUpdateIssueStatus>,
|
||||
pub bulk_update_priority: Arc<application::BulkUpdateIssuePriority>,
|
||||
pub read_carnet: Arc<application::ReadIssueCarnet>,
|
||||
pub update_carnet: Arc<application::UpdateIssueCarnet>,
|
||||
pub link: Arc<application::LinkIssues>,
|
||||
@ -180,6 +182,28 @@ impl TicketToolProvider for AppTicketToolProvider {
|
||||
.issue;
|
||||
json!(TicketDto::from_issue(issue, None))
|
||||
}
|
||||
"idea_ticket_bulk_update_status" => {
|
||||
let req = mcp_bulk_update_status_request(project, arguments)?;
|
||||
let out = self
|
||||
.bulk_update_status
|
||||
.execute(
|
||||
bulk_update_status_input(project.clone(), req, actor)
|
||||
.map_err(dto_tool_error)?,
|
||||
)
|
||||
.await;
|
||||
json!(TicketBulkResultDto::from(out))
|
||||
}
|
||||
"idea_ticket_bulk_update_priority" => {
|
||||
let req = mcp_bulk_update_priority_request(project, arguments)?;
|
||||
let out = self
|
||||
.bulk_update_priority
|
||||
.execute(
|
||||
bulk_update_priority_input(project.clone(), req, actor)
|
||||
.map_err(dto_tool_error)?,
|
||||
)
|
||||
.await;
|
||||
json!(TicketBulkResultDto::from(out))
|
||||
}
|
||||
"idea_ticket_read_carnet" => {
|
||||
let carnet = self
|
||||
.read_carnet
|
||||
@ -332,6 +356,40 @@ pub async fn ticket_delete(
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn ticket_bulk_update_status(
|
||||
request: TicketBulkUpdateStatusRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<TicketBulkResultDto, ErrorDto> {
|
||||
let project = resolve_project(&state, &request.project_id).await?;
|
||||
let out = state
|
||||
.bulk_update_issue_status
|
||||
.execute(bulk_update_status_input(
|
||||
project,
|
||||
request,
|
||||
IssueActor::User,
|
||||
)?)
|
||||
.await;
|
||||
Ok(TicketBulkResultDto::from(out))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn ticket_bulk_update_priority(
|
||||
request: TicketBulkUpdatePriorityRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<TicketBulkResultDto, ErrorDto> {
|
||||
let project = resolve_project(&state, &request.project_id).await?;
|
||||
let out = state
|
||||
.bulk_update_issue_priority
|
||||
.execute(bulk_update_priority_input(
|
||||
project,
|
||||
request,
|
||||
IssueActor::User,
|
||||
)?)
|
||||
.await;
|
||||
Ok(TicketBulkResultDto::from(out))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_ticket_chat(
|
||||
request: OpenTicketChatRequestDto,
|
||||
@ -766,6 +824,26 @@ fn mcp_list_request(
|
||||
TicketListPageInput::from_request(req).map_err(|e| TicketToolError::new("invalid", e.message))
|
||||
}
|
||||
|
||||
fn mcp_bulk_update_status_request(
|
||||
project: &Project,
|
||||
arguments: Value,
|
||||
) -> Result<TicketBulkUpdateStatusRequestDto, TicketToolError> {
|
||||
let mut req: TicketBulkUpdateStatusRequestDto = serde_json::from_value(arguments)
|
||||
.map_err(|e| TicketToolError::new("invalid", e.to_string()))?;
|
||||
req.project_id = project.id.to_string();
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
fn mcp_bulk_update_priority_request(
|
||||
project: &Project,
|
||||
arguments: Value,
|
||||
) -> Result<TicketBulkUpdatePriorityRequestDto, TicketToolError> {
|
||||
let mut req: TicketBulkUpdatePriorityRequestDto = serde_json::from_value(arguments)
|
||||
.map_err(|e| TicketToolError::new("invalid", e.to_string()))?;
|
||||
req.project_id = project.id.to_string();
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
fn parse_json_ref(arguments: &Value, key: &str) -> Result<IssueRef, TicketToolError> {
|
||||
required_str(arguments, key).and_then(|raw| parse_ref_dto(raw).map_err(dto_tool_error))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user