package plane_v2 import ( "fmt" "go.uber.org/zap" ) // ==================== Cycle API ==================== // ListCycles 列出迭代 func ListCycles(slug, projectID string) ([]byte, error) { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/", slug, projectID) resp, err := planeWebGet(url) if err != nil { zap.L().Error("[PlaneWeb] ListCycles 请求失败", zap.Error(err)) return nil, err } if !isWebSuccess(resp.StatusCode) { zap.L().Error("[PlaneWeb] ListCycles 返回错误", zap.String("status", resp.Status)) return nil, fmt.Errorf("ListCycles 失败: %s", resp.Status) } return resp.Body, nil } // CreateCycle 创建迭代 func CreateCycle(slug, projectID string, data map[string]interface{}) ([]byte, error) { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/", slug, projectID) resp, err := planeWebPost(url, data) if err != nil { zap.L().Error("[PlaneWeb] CreateCycle 请求失败", zap.Error(err)) return nil, err } if resp.StatusCode != 201 { zap.L().Error("[PlaneWeb] CreateCycle 返回错误", zap.String("status", resp.Status), zap.String("body", string(resp.Body)), ) return nil, fmt.Errorf("CreateCycle 失败: %s", resp.Status) } return resp.Body, nil } // GetCycle 获取迭代详情 func GetCycle(slug, projectID, cycleID string) ([]byte, error) { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/", slug, projectID, cycleID) resp, err := planeWebGet(url) if err != nil { zap.L().Error("[PlaneWeb] GetCycle 请求失败", zap.Error(err)) return nil, err } if !isWebSuccess(resp.StatusCode) { zap.L().Error("[PlaneWeb] GetCycle 返回错误", zap.String("status", resp.Status)) return nil, fmt.Errorf("GetCycle 失败: %s", resp.Status) } return resp.Body, nil } // UpdateCycle 更新迭代 func UpdateCycle(slug, projectID, cycleID string, data map[string]interface{}) ([]byte, error) { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/", slug, projectID, cycleID) resp, err := planeWebPatch(url, data) if err != nil { zap.L().Error("[PlaneWeb] UpdateCycle 请求失败", zap.Error(err)) return nil, err } if !isWebSuccess(resp.StatusCode) { zap.L().Error("[PlaneWeb] UpdateCycle 返回错误", zap.String("status", resp.Status), zap.String("body", string(resp.Body)), ) return nil, fmt.Errorf("UpdateCycle 失败: %s", resp.Status) } return resp.Body, nil } // DeleteCycle 删除迭代 func DeleteCycle(slug, projectID, cycleID string) error { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/", slug, projectID, cycleID) resp, err := planeWebDelete(url) if err != nil { zap.L().Error("[PlaneWeb] DeleteCycle 请求失败", zap.Error(err)) return err } if resp.StatusCode != 204 { zap.L().Error("[PlaneWeb] DeleteCycle 返回错误", zap.String("status", resp.Status)) return fmt.Errorf("DeleteCycle 失败: %s", resp.Status) } return nil } // ListCycleIssues 列出迭代中的工作项 func ListCycleIssues(slug, projectID, cycleID string) ([]byte, error) { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/cycle-issues/", slug, projectID, cycleID) resp, err := planeWebGet(url) if err != nil { zap.L().Error("[PlaneWeb] ListCycleIssues 请求失败", zap.Error(err)) return nil, err } if !isWebSuccess(resp.StatusCode) { zap.L().Error("[PlaneWeb] ListCycleIssues 返回错误", zap.String("status", resp.Status)) return nil, fmt.Errorf("ListCycleIssues 失败: %s", resp.Status) } return resp.Body, nil } // AddIssuesToCycle 添加工作项到迭代 func AddIssuesToCycle(slug, projectID, cycleID string, issueIDs []string) ([]byte, error) { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/cycle-issues/", slug, projectID, cycleID) payload := map[string]interface{}{ "issues": issueIDs, } resp, err := planeWebPost(url, payload) if err != nil { zap.L().Error("[PlaneWeb] AddIssuesToCycle 请求失败", zap.Error(err)) return nil, err } if !isWebSuccess(resp.StatusCode) { zap.L().Error("[PlaneWeb] AddIssuesToCycle 返回错误", zap.String("status", resp.Status), zap.String("body", string(resp.Body)), ) return nil, fmt.Errorf("AddIssuesToCycle 失败: %s", resp.Status) } return resp.Body, nil } // RemoveIssueFromCycle 从迭代移除工作项 func RemoveIssueFromCycle(slug, projectID, cycleID, issueID string) error { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/cycle-issues/%s/", slug, projectID, cycleID, issueID) resp, err := planeWebDelete(url) if err != nil { zap.L().Error("[PlaneWeb] RemoveIssueFromCycle 请求失败", zap.Error(err)) return err } if resp.StatusCode != 204 { zap.L().Error("[PlaneWeb] RemoveIssueFromCycle 返回错误", zap.String("status", resp.Status)) return fmt.Errorf("RemoveIssueFromCycle 失败: %s", resp.Status) } return nil } // TransferCycleIssues 转移未完成工作项到另一迭代 func TransferCycleIssues(slug, projectID, cycleID, newCycleID string) ([]byte, error) { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/transfer-issues/", slug, projectID, cycleID) payload := map[string]interface{}{ "new_cycle_id": newCycleID, } resp, err := planeWebPost(url, payload) if err != nil { zap.L().Error("[PlaneWeb] TransferCycleIssues 请求失败", zap.Error(err)) return nil, err } if !isWebSuccess(resp.StatusCode) { zap.L().Error("[PlaneWeb] TransferCycleIssues 返回错误", zap.String("status", resp.Status), zap.String("body", string(resp.Body)), ) return nil, fmt.Errorf("TransferCycleIssues 失败: %s", resp.Status) } return resp.Body, nil } // ArchiveCycle 归档迭代 func ArchiveCycle(slug, projectID, cycleID string) error { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/archive/", slug, projectID, cycleID) resp, err := planeWebPost(url, map[string]interface{}{}) if err != nil { zap.L().Error("[PlaneWeb] ArchiveCycle 请求失败", zap.Error(err)) return err } if !isWebSuccess(resp.StatusCode) { zap.L().Error("[PlaneWeb] ArchiveCycle 返回错误", zap.String("status", resp.Status)) return fmt.Errorf("ArchiveCycle 失败: %s", resp.Status) } return nil } // ListArchivedCycles 列出已归档迭代 func ListArchivedCycles(slug, projectID string) ([]byte, error) { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/archived-cycles/", slug, projectID) resp, err := planeWebGet(url) if err != nil { zap.L().Error("[PlaneWeb] ListArchivedCycles 请求失败", zap.Error(err)) return nil, err } if !isWebSuccess(resp.StatusCode) { zap.L().Error("[PlaneWeb] ListArchivedCycles 返回错误", zap.String("status", resp.Status)) return nil, fmt.Errorf("ListArchivedCycles 失败: %s", resp.Status) } return resp.Body, nil } // UnarchiveCycle 取消归档迭代 func UnarchiveCycle(slug, projectID, cycleID string) error { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/archived-cycles/%s/", slug, projectID, cycleID) resp, err := planeWebDelete(url) if err != nil { zap.L().Error("[PlaneWeb] UnarchiveCycle 请求失败", zap.Error(err)) return err } if !isWebSuccess(resp.StatusCode) { zap.L().Error("[PlaneWeb] UnarchiveCycle 返回错误", zap.String("status", resp.Status)) return fmt.Errorf("UnarchiveCycle 失败: %s", resp.Status) } return nil } // GetCycleProgress 获取迭代进度 func GetCycleProgress(slug, projectID, cycleID string) ([]byte, error) { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/progress/", slug, projectID, cycleID) resp, err := planeWebGet(url) if err != nil { zap.L().Error("[PlaneWeb] GetCycleProgress 请求失败", zap.Error(err)) return nil, err } if !isWebSuccess(resp.StatusCode) { zap.L().Error("[PlaneWeb] GetCycleProgress 返回错误", zap.String("status", resp.Status)) return nil, fmt.Errorf("GetCycleProgress 失败: %s", resp.Status) } return resp.Body, nil } // GetCycleAnalytics 获取迭代分析 func GetCycleAnalytics(slug, projectID, cycleID string) ([]byte, error) { url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/analytics/", slug, projectID, cycleID) resp, err := planeWebGet(url) if err != nil { zap.L().Error("[PlaneWeb] GetCycleAnalytics 请求失败", zap.Error(err)) return nil, err } if !isWebSuccess(resp.StatusCode) { zap.L().Error("[PlaneWeb] GetCycleAnalytics 返回错误", zap.String("status", resp.Status)) return nil, fmt.Errorf("GetCycleAnalytics 失败: %s", resp.Status) } return resp.Body, nil }