Documentation
¶
Index ¶
- Variables
- func Shortcuts() []common.Shortcut
- type BlockElementType
- type CategoryName
- type ContentBlock
- type ContentBlockElement
- type ContentBlockElementV1
- type ContentBlockV1
- type ContentColor
- type ContentDocsLink
- type ContentGallery
- type ContentGalleryV1
- type ContentImageItem
- type ContentImageItemV1
- type ContentLink
- type ContentList
- type ContentListV1
- type ContentMention
- type ContentParagraph
- type ContentParagraphElement
- type ContentParagraphElementV1
- type ContentParagraphStyle
- type ContentParagraphStyleV1
- type ContentParagraphV1
- type ContentPersonV1
- type ContentTextRun
- type ContentTextRunV1
- type ContentTextStyle
- type ContentTextStyleV1
- type Cycle
- type CycleStatus
- type KeyResult
- type ListType
- type Objective
- type Owner
- type OwnerType
- type ParagraphElementType
- type ParagraphElementTypeV1
- type Progress
- type ProgressRate
- type ProgressRateV1
- type ProgressStatus
- type ProgressV1
- type RespAlignment
- type RespCategory
- type RespCycle
- type RespIndicator
- type RespIndicatorUnit
- type RespKeyResult
- type RespKeyResultSimple
- type RespObjective
- type RespObjectiveSimple
- type RespOwner
- type RespProgress
- type RespProgressRate
- type RespProgressSimple
- type SemiPlainContent
- type SemiPlainDoc
- type StatusCalculateType
Constants ¶
This section is empty.
Variables ¶
var OKRBatchCreate = common.Shortcut{ Service: "okr", Command: "+batch-create", Description: "Batch create OKR objectives and key results with rollback on failure", Risk: "write", Scopes: []string{"okr:okr.content:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "cycle-id", Desc: "OKR cycle ID (int64)", Required: true}, {Name: "input", Desc: "JSON array of objectives: [{\"text\":\"...\",\"mention\":[\"...\"],\"krs\":[{\"text\":\"...\",\"mention\":[\"...\"]}]}]", Input: []string{common.File, common.Stdin}, Required: true}, {Name: "user-id-type", Default: "open_id", Desc: "user ID type: open_id | union_id | user_id"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { cycleID := runtime.Str("cycle-id") if id, err := strconv.ParseInt(cycleID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--cycle-id must be a positive int64").WithParam("--cycle-id") } input := runtime.Str("input") if err := common.RejectDangerousCharsTyped("--input", input); err != nil { return err } if _, err := parseBatchCreateInput(input); err != nil { return err } idType := runtime.Str("user-id-type") if idType != "open_id" && idType != "union_id" && idType != "user_id" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--user-id-type must be one of: open_id | union_id | user_id").WithParam("--user-id-type") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { cycleID := runtime.Str("cycle-id") userIDType := runtime.Str("user-id-type") objectives, _ := parseBatchCreateInput(runtime.Str("input")) apis := common.NewDryRunAPI() for i, obj := range objectives { objContent := BuildContentBlock(obj.Text, obj.Mention) objBody := map[string]interface{}{ "content": objContent, } objParams := map[string]interface{}{ "cycle_id": cycleID, "user_id_type": userIDType, } objPath := fmt.Sprintf("/open-apis/okr/v2/cycles/%s/objectives", cycleID) apis = apis. POST(objPath). Params(objParams). Body(objBody). Desc(fmt.Sprintf("Create objective[%d]: %s", i, obj.Text)) for j, kr := range obj.KRs { krContent := BuildContentBlock(kr.Text, kr.Mention) krBody := map[string]interface{}{ "content": krContent, } krParams := map[string]interface{}{ "objective_id": "<objective_id_from_previous_call>", "user_id_type": userIDType, } krPath := "/open-apis/okr/v2/objectives/<objective_id>/key_results" apis = apis. POST(krPath). Params(krParams). Body(krBody). Desc(fmt.Sprintf("Create objective[%d].krs[%d]: %s", i, j, kr.Text)) } } return apis }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { cycleID := runtime.Str("cycle-id") userIDType := runtime.Str("user-id-type") objectives, err := parseBatchCreateInput(runtime.Str("input")) if err != nil { return err } var created []createdObjective for i, obj := range objectives { if i > 0 { time.Sleep(500 * time.Millisecond) } objectiveID, err := createObjective(ctx, runtime, cycleID, userIDType, obj) if err != nil { if len(created) == 0 { return err } rollbackErrs := rollback(ctx, runtime, created) return buildRollbackError(err, rollbackErrs, created) } createdObj := createdObjective{ ObjectiveID: objectiveID, } for j, kr := range obj.KRs { if j > 0 { time.Sleep(500 * time.Millisecond) } krID, err := createKR(ctx, runtime, objectiveID, userIDType, kr) if err != nil { created = append(created, createdObj) rollbackErrs := rollback(ctx, runtime, created) return buildRollbackError(err, rollbackErrs, created) } createdObj.KRIDs = append(createdObj.KRIDs, krID) } created = append(created, createdObj) } respCreated := make([]map[string]interface{}, 0, len(created)) for _, obj := range created { respCreated = append(respCreated, map[string]interface{}{ "objective_id": obj.ObjectiveID, "krs": obj.KRIDs, }) } result := map[string]interface{}{ "ok": true, "data": map[string]interface{}{"created": respCreated}, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Successfully created %d objective(s)\n", len(created)) for i, obj := range created { fmt.Fprintf(w, "Objective[%d] ID: %s (%d KR(s))\n", i, obj.ObjectiveID, len(obj.KRIDs)) for j, krID := range obj.KRIDs { fmt.Fprintf(w, " KR[%d] ID: %s\n", j, krID) } } }) return nil }, }
OKRBatchCreate batch creates objectives and their key results.
var OKRCreateProgressRecord = common.Shortcut{ Service: "okr", Command: "+progress-create", Description: "Create an OKR progress", Risk: "write", Scopes: []string{"okr:okr.progress:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "content", Desc: "progress content: semi-plain JSON {\"text\":\"...\",\"mention\":[\"...\"]} (simple style) or ContentBlock JSON (richtext style)", Required: true, Input: []string{common.File, common.Stdin}}, {Name: "target-id", Desc: "target ID (objective or key result ID)", Required: true}, {Name: "target-type", Desc: "target type: objective | key_result", Required: true, Enum: []string{"objective", "key_result"}}, {Name: "progress-percent", Desc: "progress percentage"}, {Name: "progress-status", Desc: "progress status: normal | overdue | done. must provided with --progress-percent", Enum: []string{"normal", "overdue", "done"}}, {Name: "source-title", Default: "created by lark-cli", Desc: "source title for display"}, {Name: "source-url", Desc: "source URL for display (defaults to open platform URL based on brand)"}, {Name: "user-id-type", Default: "open_id", Desc: "user ID type: open_id | union_id | user_id"}, {Name: "style", Default: "simple", Desc: "input style: simple (semi-plain text JSON) | richtext (ContentBlock JSON)", Enum: []string{"simple", "richtext"}}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { content := runtime.Str("content") if content == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content is required").WithParam("--content") } if err := common.RejectDangerousCharsTyped("--content", content); err != nil { return err } style := runtime.Str("style") if style != "simple" && style != "richtext" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--style must be one of: simple | richtext").WithParam("--style") } if style == "simple" { var sp SemiPlainContent if err := json.Unmarshal([]byte(content), &sp); err != nil { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content must be valid semi-plain JSON: {\"text\":\"...\",\"mention\":[\"...\"]}: %s", err).WithParam("--content").WithCause(err) } if strings.TrimSpace(sp.Text) == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content text is required and cannot be empty").WithParam("--content") } for i, m := range sp.Mention { if strings.TrimSpace(m) == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content mention[%d] cannot be empty", i).WithParam("--content") } } if len(sp.Docs) > 0 || len(sp.Images) > 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content docs and images are not supported in simple style input; use richtext style or remove these fields").WithParam("--content") } } else { // richtext mode var cb ContentBlock if err := json.Unmarshal([]byte(content), &cb); err != nil { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content must be valid ContentBlock JSON: %s", err).WithParam("--content").WithCause(err) } } targetID := runtime.Str("target-id") if targetID == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--target-id is required").WithParam("--target-id") } if err := common.RejectDangerousCharsTyped("--target-id", targetID); err != nil { return err } if id, err := strconv.ParseInt(targetID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--target-id must be a positive int64").WithParam("--target-id") } targetType := runtime.Str("target-type") if _, ok := targetTypeAllowed[targetType]; !ok { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--target-type must be one of: objective | key_result").WithParam("--target-type") } if v := runtime.Str("source-title"); v != "" { if err := common.RejectDangerousCharsTyped("--source-title", v); err != nil { return err } } if v := runtime.Str("source-url"); v != "" { if err := common.RejectDangerousCharsTyped("--source-url", v); err != nil { return err } } if v := runtime.Str("progress-percent"); v != "" { percent, err := strconv.ParseFloat(v, 64) if err != nil || math.IsNaN(percent) || math.IsInf(percent, 0) || percent < -99999999999 || percent > 99999999999 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--progress-percent must be a number between -99999999999 and 99999999999").WithParam("--progress-percent") } } if v := runtime.Str("progress-status"); v != "" { if _, ok := ParseProgressStatus(v); !ok { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--progress-status must be one of: normal | overdue | done").WithParam("--progress-status") } if v := runtime.Str("progress-percent"); v == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--progress-percent must provided with --progress-status").WithParam("--progress-percent") } } idType := runtime.Str("user-id-type") if idType != "open_id" && idType != "union_id" && idType != "user_id" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--user-id-type must be one of: open_id | union_id | user_id").WithParam("--user-id-type") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { p, _ := parseCreateProgressRecordParams(runtime) params := map[string]interface{}{ "user_id_type": p.UserIDType, } body := map[string]interface{}{ "content": p.ContentV1, "target_id": p.TargetID, "target_type": p.TargetType, "source_title": p.SourceTitle, "source_url": p.SourceURL, } if p.ProgressRate != nil { body["progress_rate"] = p.ProgressRate } return common.NewDryRunAPI(). POST("/open-apis/okr/v1/progress_records/"). Params(params). Body(body). Desc(fmt.Sprintf("Create OKR progress for %s", runtime.Str("target-type"))) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { p, err := parseCreateProgressRecordParams(runtime) if err != nil { return err } body := map[string]interface{}{ "content": p.ContentV1, "target_id": p.TargetID, "target_type": p.TargetType, "source_title": p.SourceTitle, "source_url": p.SourceURL, } if p.ProgressRate != nil { body["progress_rate"] = p.ProgressRate } queryParams := map[string]interface{}{"user_id_type": p.UserIDType} data, err := runtime.CallAPITyped("POST", "/open-apis/okr/v1/progress_records/", queryParams, body) if err != nil { return err } record, err := parseProgressRecord(data) if err != nil { return err } style := runtime.Str("style") var result map[string]interface{} if style == "simple" { resp := record.ToSimple() result = map[string]interface{}{ "progress": resp, "style": style, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Created Progress [%s] (style: %s)\n", resp.ID, style) fmt.Fprintf(w, " ModifyTime: %s\n", resp.ModifyTime) if resp.ProgressRate != nil && resp.ProgressRate.Percent != nil { fmt.Fprintf(w, " ProgressRate: %.1f%%\n", *resp.ProgressRate.Percent) } if resp.Content != nil { fmt.Fprintf(w, " Content: %s\n", resp.Content.Text) } }) } else { resp := record.ToResp() result = map[string]interface{}{ "progress": resp, "style": style, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Created Progress [%s] (style: %s)\n", resp.ID, style) fmt.Fprintf(w, " ModifyTime: %s\n", resp.ModifyTime) if resp.ProgressRate != nil && resp.ProgressRate.Percent != nil { fmt.Fprintf(w, " ProgressRate: %.1f%%\n", *resp.ProgressRate.Percent) } if resp.Content != nil { fmt.Fprintf(w, " Content: %s\n", *resp.Content) } }) } return nil }, }
OKRCreateProgressRecord creates a progress.
var OKRCycleDetail = common.Shortcut{ Service: "okr", Command: "+cycle-detail", Description: "List objectives and key results under an OKR cycle", Risk: "read", Scopes: []string{"okr:okr.content:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "cycle-id", Desc: "OKR cycle id (int64)", Required: true}, {Name: "style", Default: "simple", Desc: "output style: simple (semi-plain text JSON) | richtext (ContentBlock JSON)", Enum: []string{"simple", "richtext"}}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { cycleID := runtime.Str("cycle-id") if cycleID == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--cycle-id is required").WithParam("--cycle-id") } if id, err := strconv.ParseInt(cycleID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--cycle-id must be a positive int64").WithParam("--cycle-id") } style := runtime.Str("style") if style != "simple" && style != "richtext" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--style must be one of: simple | richtext").WithParam("--style") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { cycleID := runtime.Str("cycle-id") params := map[string]interface{}{ "page_size": 100, } return common.NewDryRunAPI(). GET("/open-apis/okr/v2/cycles/:cycle_id/objectives"). Params(params). Set("cycle_id", cycleID). Desc("Auto-paginates objectives in the cycle, then calls GET /open-apis/okr/v2/objectives/:objective_id/key_results for each objective to fetch key results") }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { cycleID := runtime.Str("cycle-id") style := runtime.Str("style") queryParams := map[string]interface{}{"page_size": "100"} var objectives []Objective page := 0 for { if err := ctx.Err(); err != nil { return err } if page > 0 { select { case <-ctx.Done(): return ctx.Err() case <-time.After(500 * time.Millisecond): } } page++ path := fmt.Sprintf("/open-apis/okr/v2/cycles/%s/objectives", cycleID) data, err := runtime.CallAPITyped("GET", path, queryParams, nil) if err != nil { return err } itemsRaw, _ := data["items"].([]interface{}) for _, item := range itemsRaw { raw, err := json.Marshal(item) if err != nil { continue } var obj Objective if err := json.Unmarshal(raw, &obj); err != nil { continue } objectives = append(objectives, obj) } hasMore, pageToken := common.PaginationMeta(data) if !hasMore || pageToken == "" { break } queryParams["page_token"] = pageToken } if style == "simple" { respObjectives := make([]*RespObjectiveSimple, 0, len(objectives)) for i := range objectives { if err := ctx.Err(); err != nil { return err } obj := &objectives[i] keyResults, err := fetchKeyResults(ctx, runtime, obj.ID) if err != nil { return err } respObj := obj.ToSimple() if respObj == nil { continue } respKRs := make([]RespKeyResultSimple, 0, len(keyResults)) for j := range keyResults { if r := keyResults[j].ToSimple(); r != nil { respKRs = append(respKRs, *r) } } respObj.KeyResults = respKRs respObjectives = append(respObjectives, respObj) } result := map[string]interface{}{ "cycle_id": cycleID, "objectives": respObjectives, "total": len(respObjectives), "style": style, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Cycle %s: %d objective(s) (style: %s)\n", cycleID, len(respObjectives), style) for _, o := range respObjectives { contentText := "" if o.Content != nil { contentText = o.Content.Text } notesText := "" if o.Notes != nil { notesText = o.Notes.Text } fmt.Fprintf(w, "Objective [%s]: %s \n Notes: %s \n score=%.2f weight=%.2f\n", o.ID, contentText, notesText, ptrFloat64(o.Score), ptrFloat64(o.Weight)) for _, kr := range o.KeyResults { krText := "" if kr.Content != nil { krText = kr.Content.Text } fmt.Fprintf(w, " - KR [%s]: %s \n score=%.2f weight=%.2f\n", kr.ID, krText, ptrFloat64(kr.Score), ptrFloat64(kr.Weight)) } } }) } else { respObjectives := make([]*RespObjective, 0, len(objectives)) for i := range objectives { if err := ctx.Err(); err != nil { return err } obj := &objectives[i] keyResults, err := fetchKeyResults(ctx, runtime, obj.ID) if err != nil { return err } respObj := obj.ToResp() if respObj == nil { continue } respKRs := make([]RespKeyResult, 0, len(keyResults)) for j := range keyResults { if r := keyResults[j].ToResp(); r != nil { respKRs = append(respKRs, *r) } } respObj.KeyResults = respKRs respObjectives = append(respObjectives, respObj) } result := map[string]interface{}{ "cycle_id": cycleID, "objectives": respObjectives, "total": len(respObjectives), "style": style, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Cycle %s: %d objective(s) (style: %s)\n", cycleID, len(respObjectives), style) for _, o := range respObjectives { fmt.Fprintf(w, "Objective [%s]: %s \n Notes: %s \n score=%.2f weight=%.2f\n", o.ID, ptrStr(o.Content), ptrStr(o.Notes), ptrFloat64(o.Score), ptrFloat64(o.Weight)) for _, kr := range o.KeyResults { fmt.Fprintf(w, " - KR [%s]: %s \n score=%.2f weight=%.2f\n", kr.ID, ptrStr(kr.Content), ptrFloat64(kr.Score), ptrFloat64(kr.Weight)) } } }) } return nil }, }
OKRCycleDetail lists all objectives and their key results under a given OKR cycle.
var OKRDeleteProgressRecord = common.Shortcut{ Service: "okr", Command: "+progress-delete", Description: "Delete an OKR progress by ID", Risk: "high-risk-write", Scopes: []string{"okr:okr.progress:delete"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "progress-id", Desc: "progress ID (int64)", Required: true}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { progressID := runtime.Str("progress-id") if progressID == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--progress-id is required").WithParam("--progress-id") } if id, err := strconv.ParseInt(progressID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--progress-id must be a positive int64").WithParam("--progress-id") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { progressID := runtime.Str("progress-id") return common.NewDryRunAPI(). DELETE("/open-apis/okr/v1/progress_records/:progress_id"). Set("progress_id", progressID). Desc("Delete OKR progress") }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { progressID := runtime.Str("progress-id") path := fmt.Sprintf("/open-apis/okr/v1/progress_records/%s", progressID) _, err := runtime.CallAPITyped("DELETE", path, nil, nil) if err != nil { return err } result := map[string]interface{}{ "deleted": true, "progress_id": progressID, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Deleted progress record %s\n", progressID) }) return nil }, }
OKRDeleteProgressRecord deletes a progress by ID.
var OKRGetProgressRecord = common.Shortcut{ Service: "okr", Command: "+progress-get", Description: "Get an OKR progress by ID", Risk: "read", Scopes: []string{"okr:okr.progress:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "progress-id", Desc: "progress ID (int64)", Required: true}, {Name: "user-id-type", Default: "open_id", Desc: "user ID type: open_id | union_id | user_id"}, {Name: "style", Default: "simple", Desc: "output style: simple (semi-plain text JSON) | richtext (ContentBlock JSON)", Enum: []string{"simple", "richtext"}}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { progressID := runtime.Str("progress-id") if progressID == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--progress-id is required").WithParam("--progress-id") } if id, err := strconv.ParseInt(progressID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--progress-id must be a positive int64").WithParam("--progress-id") } idType := runtime.Str("user-id-type") if idType != "open_id" && idType != "union_id" && idType != "user_id" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--user-id-type must be one of: open_id | union_id | user_id").WithParam("--user-id-type") } style := runtime.Str("style") if style != "simple" && style != "richtext" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--style must be one of: simple | richtext").WithParam("--style") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { progressID := runtime.Str("progress-id") params := map[string]interface{}{ "user_id_type": runtime.Str("user-id-type"), } return common.NewDryRunAPI(). GET("/open-apis/okr/v1/progress_records/:progress_id"). Params(params). Set("progress_id", progressID). Desc("Get OKR progress") }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { progressID := runtime.Str("progress-id") userIDType := runtime.Str("user-id-type") style := runtime.Str("style") queryParams := map[string]interface{}{"user_id_type": userIDType} path := fmt.Sprintf("/open-apis/okr/v1/progress_records/%s", progressID) data, err := runtime.CallAPITyped("GET", path, queryParams, nil) if err != nil { return err } record, err := parseProgressRecord(data) if err != nil { return err } var result map[string]interface{} if style == "simple" { resp := record.ToSimple() result = map[string]interface{}{ "progress": resp, "style": style, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Progress [%s] (style: %s)\n", resp.ID, style) fmt.Fprintf(w, " ModifyTime: %s\n", resp.ModifyTime) if resp.ProgressRate != nil && resp.ProgressRate.Percent != nil { fmt.Fprintf(w, " ProgressRate: %.1f%%\n", *resp.ProgressRate.Percent) } if resp.Content != nil { fmt.Fprintf(w, " Content: %s\n", resp.Content.Text) if len(resp.Content.Mention) > 0 { fmt.Fprintf(w, " Mentions: %v\n", resp.Content.Mention) } } }) } else { resp := record.ToResp() result = map[string]interface{}{ "progress": resp, "style": style, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Progress [%s] (style: %s)\n", resp.ID, style) fmt.Fprintf(w, " ModifyTime: %s\n", resp.ModifyTime) if resp.ProgressRate != nil && resp.ProgressRate.Percent != nil { fmt.Fprintf(w, " ProgressRate: %.1f%%\n", *resp.ProgressRate.Percent) } if resp.Content != nil { fmt.Fprintf(w, " Content: %s\n", *resp.Content) } }) } return nil }, }
OKRGetProgressRecord gets a progress by ID.
var OKRIndicatorUpdate = common.Shortcut{ Service: "okr", Command: "+indicator-update", Description: "Update the indicator current value for an objective or key result", Risk: "write", Scopes: []string{"okr:okr.content:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "level", Desc: "level to update: objective | key-result, Required.", Enum: []string{"objective", "key-result"}}, {Name: "id", Desc: "objective or key result ID (int64), Required."}, {Name: "value", Desc: "new current value for the indicator (number), Required."}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { level := runtime.Str("level") if level == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--level is required").WithParam("--level") } if level != "objective" && level != "key-result" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--level must be one of: objective | key-result").WithParam("--level") } id := runtime.Str("id") if id == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--id is required").WithParam("--id") } if _, err := strconv.ParseInt(id, 10, 64); err != nil { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--id must be a valid int64").WithParam("--id") } if runtime.Str("value") == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--value is required").WithParam("--value") } if _, err := parseIndicatorValue(runtime.Str("value")); err != nil { return err } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { level := runtime.Str("level") id := runtime.Str("id") value, _ := parseIndicatorValue(runtime.Str("value")) apis := common.NewDryRunAPI() var listPath string if level == "objective" { listPath = fmt.Sprintf("/open-apis/okr/v2/objectives/%s/indicators", id) } else { listPath = fmt.Sprintf("/open-apis/okr/v2/key_results/%s/indicators", id) } apis = apis. GET(listPath). Params(map[string]interface{}{"page_size": 100}). Desc(fmt.Sprintf("Fetch indicators for the %s to get indicator ID", level)) patchPath := "/open-apis/okr/v2/indicators/:indicator_id" patchBody := map[string]interface{}{ "current_value": value, } apis = apis. PATCH(patchPath). Body(patchBody). Set("indicator_id", "<indicator_id_from_list>"). Desc("Update indicator current value") return apis }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { level := runtime.Str("level") id := runtime.Str("id") value, err := parseIndicatorValue(runtime.Str("value")) if err != nil { return err } indicatorID, err := fetchIndicatorID(ctx, runtime, level, id) if err != nil { return err } patchPath := fmt.Sprintf("/open-apis/okr/v2/indicators/%s", indicatorID) patchBody := map[string]interface{}{ "current_value": value, } _, err = runtime.CallAPITyped("PATCH", patchPath, nil, patchBody) if err != nil { return wrapOkrNetworkErr(err, "failed to update indicator value") } result := map[string]interface{}{ "indicator_id": indicatorID, "current_value": value, "level": level, "target_id": id, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Updated Indicator [%s]\n", indicatorID) fmt.Fprintf(w, " Level: %s\n", level) fmt.Fprintf(w, " Target ID: %s\n", id) fmt.Fprintf(w, " Current Value: %v\n", value) }) return nil }, }
OKRIndicatorUpdate updates the current value of an indicator for an objective or key result.
var OKRListCycles = common.Shortcut{ Service: "okr", Command: "+cycle-list", Description: "List okr cycles of a certain user", Risk: "read", Scopes: []string{"okr:okr.period:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "user-id", Desc: "user ID", Required: true}, {Name: "user-id-type", Default: "open_id", Desc: "user ID type: open_id | union_id | user_id"}, {Name: "time-range", Desc: "specify time range. Use Format as YYYY-MM--YYYY-MM. leave empty to fetch all user cycles."}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { idType := runtime.Str("user-id-type") if idType != "open_id" && idType != "union_id" && idType != "user_id" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--user-id-type must be one of: open_id | union_id | user_id").WithParam("--user-id-type") } userID := runtime.Str("user-id") if err := common.RejectDangerousCharsTyped("--user-id", userID); err != nil { return err } tr := runtime.Str("time-range") if tr != "" { if err := common.RejectDangerousCharsTyped("--time-range", tr); err != nil { return err } if _, _, err := parseTimeRange(tr); err != nil { return err } } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { params := map[string]interface{}{ "user_id": runtime.Str("user-id"), "user_id_type": runtime.Str("user-id-type"), "page_size": 100, } return common.NewDryRunAPI(). GET("/open-apis/okr/v2/cycles"). Params(params). Desc("List OKR cycles for user, paginated at 100 per page, filtered by time-range") }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { userID := runtime.Str("user-id") userIDType := runtime.Str("user-id-type") timeRange := runtime.Str("time-range") // Parse time range for filtering var rangeStart, rangeEnd time.Time var hasRange bool if timeRange != "" { var err error rangeStart, rangeEnd, err = parseTimeRange(timeRange) if err != nil { return err } hasRange = true } queryParams := map[string]interface{}{ "user_id": userID, "user_id_type": userIDType, "page_size": "100", } var allCycles []Cycle page := 0 for { if err := ctx.Err(); err != nil { return err } if page > 0 { select { case <-ctx.Done(): return ctx.Err() case <-time.After(500 * time.Millisecond): } } page++ data, err := runtime.CallAPITyped("GET", "/open-apis/okr/v2/cycles", queryParams, nil) if err != nil { return err } itemsRaw, _ := data["items"].([]interface{}) for _, item := range itemsRaw { raw, err := json.Marshal(item) if err != nil { continue } var cycle Cycle if err := json.Unmarshal(raw, &cycle); err != nil { continue } allCycles = append(allCycles, cycle) } hasMore, pageToken := common.PaginationMeta(data) if !hasMore || pageToken == "" { break } queryParams["page_token"] = pageToken } // Filter by time-range overlap var filtered []Cycle for i := range allCycles { if !hasRange || cycleOverlaps(&allCycles[i], rangeStart, rangeEnd) { filtered = append(filtered, allCycles[i]) } } respCycles := make([]*RespCycle, 0, len(filtered)) for i := range filtered { respCycles = append(respCycles, filtered[i].ToResp()) } now := time.Now() currentActiveCycles := make([]*RespCycle, 0) for i := range filtered { if isCurrentActiveCycle(&filtered[i], now) { currentActiveCycles = append(currentActiveCycles, filtered[i].ToResp()) } } runtime.OutFormat(map[string]interface{}{ "cycles": respCycles, "total": len(respCycles), "current_active_cycles": currentActiveCycles, }, nil, func(w io.Writer) { fmt.Fprintf(w, "Found %d cycle(s)\n", len(respCycles)) for _, c := range respCycles { fmt.Fprintf(w, " [%s] %s ~ %s (status: %s)\n", c.ID, c.StartTime, c.EndTime, ptrStr(c.CycleStatus)) } if len(currentActiveCycles) > 0 { fmt.Fprintf(w, "\nCurrent active cycle(s):\n") for _, c := range currentActiveCycles { fmt.Fprintf(w, " [%s] %s ~ %s\n", c.ID, c.StartTime, c.EndTime) } } }) return nil }, }
var OKRListProgress = common.Shortcut{ Service: "okr", Command: "+progress-list", Description: "List progress for an objective or key result", Risk: "read", Scopes: []string{"okr:okr.progress:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "target-id", Desc: "target ID (objective or key result ID)", Required: true}, {Name: "target-type", Desc: "target type: objective | key_result", Required: true, Enum: []string{"objective", "key_result"}}, {Name: "user-id-type", Default: "open_id", Desc: "user ID type: open_id | union_id | user_id"}, {Name: "department-id-type", Default: "open_department_id", Desc: "department ID type: department_id | open_department_id"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { targetID := runtime.Str("target-id") if targetID == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--target-id is required").WithParam("--target-id") } if err := common.RejectDangerousCharsTyped("--target-id", targetID); err != nil { return err } if id, err := strconv.ParseInt(targetID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--target-id must be a positive int64").WithParam("--target-id") } targetType := runtime.Str("target-type") if _, ok := targetTypeAllowed[targetType]; !ok { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--target-type must be one of: objective | key_result").WithParam("--target-type") } idType := runtime.Str("user-id-type") if idType != "open_id" && idType != "union_id" && idType != "user_id" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--user-id-type must be one of: open_id | union_id | user_id").WithParam("--user-id-type") } deptIDType := runtime.Str("department-id-type") if deptIDType != "department_id" && deptIDType != "open_department_id" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--department-id-type must be one of: department_id | open_department_id").WithParam("--department-id-type") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { targetID := runtime.Str("target-id") targetType := runtime.Str("target-type") params := map[string]interface{}{ "user_id_type": runtime.Str("user-id-type"), "department_id_type": runtime.Str("department-id-type"), "page_size": 100, } switch targetType { case "objective": return common.NewDryRunAPI(). GET("/open-apis/okr/v2/objectives/:objective_id/progresses"). Params(params). Set("objective_id", targetID). Desc("List progresses for objective") case "key_result": return common.NewDryRunAPI(). GET("/open-apis/okr/v2/key_results/:key_result_id/progresses"). Params(params). Set("key_result_id", targetID). Desc("List progresses for key result") } return nil }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { targetID := runtime.Str("target-id") targetType := runtime.Str("target-type") userIDType := runtime.Str("user-id-type") deptIDType := runtime.Str("department-id-type") queryParams := map[string]interface{}{ "user_id_type": userIDType, "department_id_type": deptIDType, "page_size": "100", } var apiPath string switch targetType { case "objective": apiPath = fmt.Sprintf("/open-apis/okr/v2/objectives/%s/progresses", targetID) case "key_result": apiPath = fmt.Sprintf("/open-apis/okr/v2/key_results/%s/progresses", targetID) } var allProgress []*Progress for { if err := ctx.Err(); err != nil { return err } data, err := runtime.CallAPITyped("GET", apiPath, queryParams, nil) if err != nil { return err } itemsRaw, _ := data["items"].([]interface{}) for _, item := range itemsRaw { raw, err := json.Marshal(item) if err != nil { continue } var progress Progress if err := json.Unmarshal(raw, &progress); err != nil { continue } allProgress = append(allProgress, &progress) } hasMore, pageToken := common.PaginationMeta(data) if !hasMore || pageToken == "" { break } queryParams["page_token"] = pageToken } respProgress := make([]*RespProgress, 0, len(allProgress)) for _, p := range allProgress { respProgress = append(respProgress, p.ToResp()) } runtime.OutFormat(map[string]interface{}{ "progress_list": respProgress, "total": len(respProgress), }, nil, func(w io.Writer) { fmt.Fprintf(w, "Found %d progress(es)\n", len(respProgress)) for _, p := range respProgress { fmt.Fprintf(w, " [%s] , %s", p.ID, p.ModifyTime) if p.ProgressRate != nil && p.ProgressRate.Percent != nil { fmt.Fprintf(w, " (%.2f%%", *p.ProgressRate.Percent) if p.ProgressRate.Status != nil { fmt.Fprintf(w, ", %s", *p.ProgressRate.Status) } fmt.Fprintf(w, ")\n") if p.Content != nil { fmt.Fprintf(w, " Content: %s\n", *p.Content) } } fmt.Fprintln(w) } }) return nil }, }
OKRListProgress lists progress for an objective or key result.
var OKRPatch = common.Shortcut{ Service: "okr", Command: "+patch", Description: "Patch an OKR objective or key result (content, notes, score, deadline)", Risk: "write", Scopes: []string{"okr:okr.content:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "level", Desc: "patch level: objective | key-result", Required: true, Enum: []string{"objective", "key-result"}}, {Name: "target-id", Desc: "target ID (objective or key result ID)", Required: true}, {Name: "style", Default: "simple", Desc: "input style for content/notes: simple (semi-plain text JSON) | richtext (ContentBlock JSON)", Enum: []string{"simple", "richtext"}}, {Name: "content", Desc: "content: semi-plain JSON {\"text\":\"...\",\"mention\":[\"...\"]} (simple) or ContentBlock JSON (richtext)", Input: []string{common.File, common.Stdin}}, {Name: "notes", Desc: "notes (objective only): semi-plain JSON {\"text\":\"...\",\"mention\":[\"...\"]} (simple) or ContentBlock JSON (richtext)", Input: []string{common.File, common.Stdin}}, {Name: "score", Desc: "score value between 0 and 1, with at most one decimal place (e.g., 0.5)"}, {Name: "deadline", Desc: "deadline as millisecond timestamp"}, {Name: "user-id-type", Default: "open_id", Desc: "user ID type: open_id | union_id | user_id"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { level := runtime.Str("level") if level != "objective" && level != "key-result" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--level must be one of: objective | key-result").WithParam("--level") } targetID := runtime.Str("target-id") if targetID == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--target-id is required").WithParam("--target-id") } if err := common.RejectDangerousCharsTyped("--target-id", targetID); err != nil { return err } if id, err := strconv.ParseInt(targetID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--target-id must be a positive int64").WithParam("--target-id") } style := runtime.Str("style") if style != "simple" && style != "richtext" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--style must be one of: simple | richtext").WithParam("--style") } idType := runtime.Str("user-id-type") if idType != "open_id" && idType != "union_id" && idType != "user_id" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--user-id-type must be one of: open_id | union_id | user_id").WithParam("--user-id-type") } if _, err := parsePatchParams(runtime); err != nil { return err } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { p, err := parsePatchParams(runtime) if err != nil { return common.NewDryRunAPI(). PATCH(""). Desc(fmt.Sprintf("Dry-run skipped: %s", err.Error())) } body := make(map[string]interface{}) if p.Content != nil { body["content"] = p.Content } if p.Notes != nil { body["notes"] = p.Notes } if p.Score != nil { body["score"] = *p.Score } if p.Deadline != nil { body["deadline"] = *p.Deadline } params := map[string]interface{}{ "user_id_type": p.UserIDType, } api := common.NewDryRunAPI() if p.Level == "objective" { api = api.PATCH("/open-apis/okr/v2/objectives/:objective_id"). Set("objective_id", p.TargetID) } else { api = api.PATCH("/open-apis/okr/v2/key_results/:key_result_id"). Set("key_result_id", p.TargetID) } return api.Params(params).Body(body). Desc(fmt.Sprintf("Patch OKR %s: content=%v, notes=%v, score=%v, deadline=%v", p.Level, p.Content != nil, p.Notes != nil, p.Score != nil, p.Deadline != nil)) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { p, err := parsePatchParams(runtime) if err != nil { return err } body := make(map[string]interface{}) if p.Content != nil { body["content"] = p.Content } if p.Notes != nil { body["notes"] = p.Notes } if p.Score != nil { body["score"] = *p.Score } if p.Deadline != nil { body["deadline"] = *p.Deadline } queryParams := map[string]interface{}{ "user_id_type": p.UserIDType, } var path string if p.Level == "objective" { path = fmt.Sprintf("/open-apis/okr/v2/objectives/%s", p.TargetID) } else { path = fmt.Sprintf("/open-apis/okr/v2/key_results/%s", p.TargetID) } _, err = runtime.CallAPITyped("PATCH", path, queryParams, body) if err != nil { return wrapOkrNetworkErr(err, "failed to patch OKR %s", p.Level) } result := map[string]interface{}{ "level": p.Level, "target_id": p.TargetID, "patched": map[string]bool{ "content": p.Content != nil, "notes": p.Notes != nil, "score": p.Score != nil, "deadline": p.Deadline != nil, }, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Patched OKR %s [%s]\n", p.Level, p.TargetID) if p.Content != nil { fmt.Fprintf(w, " - content: updated\n") } if p.Notes != nil { fmt.Fprintf(w, " - notes: updated\n") } if p.Score != nil { fmt.Fprintf(w, " - score: %.1f\n", *p.Score) } if p.Deadline != nil { fmt.Fprintf(w, " - deadline: %s\n", formatTimestamp(*p.Deadline)) } }) return nil }, }
OKRPatch patches an objective or key result.
var OKRReorder = common.Shortcut{ Service: "okr", Command: "+reorder", Description: "Adjust the position (order) of OKR objectives or key results", Risk: "write", Scopes: []string{"okr:okr.content:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "level", Desc: "level to reorder: objective | key-result, Required.", Enum: []string{"objective", "key-result"}}, {Name: "cycle-id", Desc: "OKR cycle ID (int64), Required."}, {Name: "objective-id", Desc: "objective ID (required when --level=key-result)"}, {Name: "ops", Desc: "JSON array of reorder operations: [{\"id\":\"...\",\"position\":1}], Required.", Input: []string{common.File, common.Stdin}}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { level := runtime.Str("level") if strings.TrimSpace(level) == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--level is required").WithParam("--level") } if level != "objective" && level != "key-result" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--level must be one of: objective | key-result").WithParam("--level") } cycleID := runtime.Str("cycle-id") if strings.TrimSpace(cycleID) == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--cycle-id is required").WithParam("--cycle-id") } if id, err := strconv.ParseInt(cycleID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--cycle-id must be a positive int64").WithParam("--cycle-id") } if level == "key-result" { objID := runtime.Str("objective-id") if objID == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--objective-id is required when --level=key-result").WithParam("--objective-id") } if id, err := strconv.ParseInt(objID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--objective-id must be a positive int64").WithParam("--objective-id") } } opsStr := runtime.Str("ops") if strings.TrimSpace(opsStr) == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--ops is required").WithParam("--ops") } if err := common.RejectDangerousCharsTyped("--ops", opsStr); err != nil { return err } if _, err := parseReorderOps(opsStr); err != nil { return err } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { level := runtime.Str("level") cycleID := runtime.Str("cycle-id") objectiveID := runtime.Str("objective-id") ops, _ := parseReorderOps(runtime.Str("ops")) apis := common.NewDryRunAPI() if level == "objective" { listParams := map[string]interface{}{ "page_size": 100, } listPath := fmt.Sprintf("/open-apis/okr/v2/cycles/%s/objectives", cycleID) apis = apis. GET(listPath). Params(listParams). Desc("Fetch all objectives in the cycle to determine current order") reorderParams := map[string]interface{}{ "cycle_id": cycleID, } objectiveIDs := make([]string, 0, len(ops)) for _, op := range ops { objectiveIDs = append(objectiveIDs, op.ID) } reorderBody := map[string]interface{}{ "objective_ids": objectiveIDs, } reorderPath := fmt.Sprintf("/open-apis/okr/v2/cycles/%s/objectives_position", cycleID) apis = apis. PUT(reorderPath). Params(reorderParams). Body(reorderBody). Desc("Update objective positions (full list sent, not just changes)") } else { listParams := map[string]interface{}{ "page_size": 100, } listPath := fmt.Sprintf("/open-apis/okr/v2/objectives/%s/key_results", objectiveID) apis = apis. GET(listPath). Params(listParams). Desc("Fetch all key results for the objective to determine current order") reorderParams := map[string]interface{}{ "objective_id": objectiveID, } keyResultIDs := make([]string, 0, len(ops)) for _, op := range ops { keyResultIDs = append(keyResultIDs, op.ID) } reorderBody := map[string]interface{}{ "key_result_ids": keyResultIDs, } reorderPath := fmt.Sprintf("/open-apis/okr/v2/objectives/%s/key_results_position", objectiveID) apis = apis. PUT(reorderPath). Params(reorderParams). Body(reorderBody). Desc("Update key result positions (full list sent, not just changes)") } return apis }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { level := runtime.Str("level") cycleID := runtime.Str("cycle-id") objectiveID := runtime.Str("objective-id") ops, err := parseReorderOps(runtime.Str("ops")) if err != nil { return err } var reorderedIDs []string var total int if level == "objective" { objectives, err := fetchObjectives(ctx, runtime, cycleID) if err != nil { return err } total = len(objectives) reorderedIDs, err = buildReorderedIDs(objectives, ops, total) if err != nil { return err } params := map[string]interface{}{ "cycle_id": cycleID, } body := map[string]interface{}{ "objective_ids": reorderedIDs, } path := fmt.Sprintf("/open-apis/okr/v2/cycles/%s/objectives_position", cycleID) _, err = runtime.CallAPITyped("PUT", path, params, body) if err != nil { return wrapOkrNetworkErr(err, "failed to update objective positions") } } else { keyResults, err := fetchKeyResults(ctx, runtime, objectiveID) if err != nil { return err } total = len(keyResults) reorderedIDs, err = buildReorderedIDs(keyResults, ops, total) if err != nil { return err } params := map[string]interface{}{ "objective_id": objectiveID, } body := map[string]interface{}{ "key_result_ids": reorderedIDs, } path := fmt.Sprintf("/open-apis/okr/v2/objectives/%s/key_results_position", objectiveID) _, err = runtime.CallAPITyped("PUT", path, params, body) if err != nil { return wrapOkrNetworkErr(err, "failed to update key result positions") } } result := map[string]interface{}{ "level": level, "cycle_id": cycleID, "total": total, "ordered": reorderedIDs, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Successfully reordered %d %s(s)\n", total, level) fmt.Fprintln(w, "New order:") for i, id := range reorderedIDs { fmt.Fprintf(w, " Position %d: %s\n", i+1, id) } }) return nil }, }
OKRReorder adjusts the position of objectives or key results.
var OKRUpdateProgressRecord = common.Shortcut{ Service: "okr", Command: "+progress-update", Description: "Update an OKR progress", Risk: "write", Scopes: []string{"okr:okr.progress:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "progress-id", Desc: "progress ID (int64)", Required: true}, {Name: "content", Desc: "progress content: semi-plain JSON {\"text\":\"...\",\"mention\":[\"...\"]} (simple style) or ContentBlock JSON (richtext style)", Required: true, Input: []string{common.File, common.Stdin}}, {Name: "progress-percent", Desc: "progress percentage"}, {Name: "progress-status", Desc: "progress status: normal | overdue | done", Enum: []string{"normal", "overdue", "done"}}, {Name: "user-id-type", Default: "open_id", Desc: "user ID type: open_id | union_id | user_id"}, {Name: "style", Default: "simple", Desc: "input style: simple (semi-plain text JSON) | richtext (ContentBlock JSON)", Enum: []string{"simple", "richtext"}}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { progressID := runtime.Str("progress-id") if progressID == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--progress-id is required").WithParam("--progress-id") } if id, err := strconv.ParseInt(progressID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--progress-id must be a positive int64").WithParam("--progress-id") } content := runtime.Str("content") if content == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content is required").WithParam("--content") } if err := common.RejectDangerousCharsTyped("--content", content); err != nil { return err } style := runtime.Str("style") if style != "simple" && style != "richtext" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--style must be one of: simple | richtext").WithParam("--style") } if style == "simple" { var sp SemiPlainContent if err := json.Unmarshal([]byte(content), &sp); err != nil { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content must be valid semi-plain JSON: {\"text\":\"...\",\"mention\":[\"...\"]}: %s", err).WithParam("--content").WithCause(err) } if strings.TrimSpace(sp.Text) == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content text is required and cannot be empty").WithParam("--content") } for i, m := range sp.Mention { if strings.TrimSpace(m) == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content mention[%d] cannot be empty", i).WithParam("--content") } } if len(sp.Docs) > 0 || len(sp.Images) > 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content docs and images are not supported in simple style input; use richtext style or remove these fields").WithParam("--content") } } else { // richtext mode var cb ContentBlock if err := json.Unmarshal([]byte(content), &cb); err != nil { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content must be valid ContentBlock JSON: %s", err).WithParam("--content").WithCause(err) } } if v := runtime.Str("progress-percent"); v != "" { percent, err := strconv.ParseFloat(v, 64) if err != nil || math.IsNaN(percent) || math.IsInf(percent, 0) || percent < -99999999999 || percent > 99999999999 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--progress-percent must be a number between -99999999999 and 99999999999").WithParam("--progress-percent") } } if v := runtime.Str("progress-status"); v != "" { if _, ok := ParseProgressStatus(v); !ok { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--progress-status must be one of: normal | overdue | done").WithParam("--progress-status") } if v := runtime.Str("progress-percent"); v == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--progress-percent must provided with --progress-status").WithParam("--progress-percent") } } idType := runtime.Str("user-id-type") if idType != "open_id" && idType != "union_id" && idType != "user_id" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--user-id-type must be one of: open_id | union_id | user_id").WithParam("--user-id-type") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { p, _ := parseUpdateProgressRecordParams(runtime) params := map[string]interface{}{ "user_id_type": p.UserIDType, } body := map[string]interface{}{ "content": p.ContentV1, } if p.ProgressRate != nil { body["progress_rate"] = p.ProgressRate } return common.NewDryRunAPI(). PUT("/open-apis/okr/v1/progress_records/:progress_id"). Params(params). Body(body). Set("progress_id", p.ProgressID). Desc("Update OKR progress") }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { p, err := parseUpdateProgressRecordParams(runtime) if err != nil { return err } body := map[string]interface{}{ "content": p.ContentV1, } if p.ProgressRate != nil { body["progress_rate"] = p.ProgressRate } queryParams := map[string]interface{}{"user_id_type": p.UserIDType} path := fmt.Sprintf("/open-apis/okr/v1/progress_records/%s", p.ProgressID) data, err := runtime.CallAPITyped("PUT", path, queryParams, body) if err != nil { return err } record, err := parseProgressRecord(data) if err != nil { return err } style := runtime.Str("style") var result map[string]interface{} if style == "simple" { resp := record.ToSimple() result = map[string]interface{}{ "progress": resp, "style": style, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Updated Progress [%s] (style: %s)\n", resp.ID, style) fmt.Fprintf(w, " ModifyTime: %s\n", resp.ModifyTime) if resp.ProgressRate != nil && resp.ProgressRate.Percent != nil { fmt.Fprintf(w, " Progress: %.1f%%\n", *resp.ProgressRate.Percent) } if resp.Content != nil { fmt.Fprintf(w, " Content: %s\n", resp.Content.Text) } }) } else { resp := record.ToResp() result = map[string]interface{}{ "progress": resp, "style": style, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Updated Progress [%s] (style: %s)\n", resp.ID, style) fmt.Fprintf(w, " ModifyTime: %s\n", resp.ModifyTime) if resp.ProgressRate != nil && resp.ProgressRate.Percent != nil { fmt.Fprintf(w, " Progress: %.1f%%\n", *resp.ProgressRate.Percent) } if resp.Content != nil { fmt.Fprintf(w, " Content: %s\n", *resp.Content) } }) } return nil }, }
OKRUpdateProgressRecord updates a progress.
var OKRUploadImage = common.Shortcut{ Service: "okr", Command: "+upload-image", Description: "Upload an image for use in OKR progress rich text", Risk: "write", Scopes: []string{"okr:okr.progress.file:upload"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ {Name: "file", Desc: "local image path (supports JPG, JPEG, PNG, GIF, BMP)", Required: true}, {Name: "target-id", Desc: "target ID (objective or key result ID) for the progress", Required: true}, {Name: "target-type", Desc: "target type: objective | key_result", Required: true, Enum: []string{"objective", "key_result"}}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { filePath := runtime.Str("file") if filePath == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--file is required").WithParam("--file") } ext := strings.ToLower(filepath.Ext(filePath)) if !allowedImageExts[ext] { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--file must be an image (supported: JPG, JPEG, PNG, GIF, BMP), got %q", ext).WithParam("--file") } targetID := runtime.Str("target-id") if targetID == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--target-id is required").WithParam("--target-id") } if id, err := strconv.ParseInt(targetID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--target-id must be a positive int64").WithParam("--target-id") } targetType := runtime.Str("target-type") if _, ok := targetTypeAllowed[targetType]; !ok { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--target-type must be one of: objective | key_result").WithParam("--target-type") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { filePath := runtime.Str("file") targetID := runtime.Str("target-id") targetType := runtime.Str("target-type") targetTypeVal := targetTypeAllowed[targetType] return common.NewDryRunAPI(). POST("/open-apis/okr/v1/images/upload"). Body(map[string]interface{}{ "file": "@" + filePath, "target_id": targetID, "target_type": targetTypeVal, }). Desc(fmt.Sprintf("Upload image for OKR %s %s", targetType, targetID)) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { filePath := runtime.Str("file") targetID := runtime.Str("target-id") targetType := runtime.Str("target-type") targetTypeVal := targetTypeAllowed[targetType] info, err := runtime.FileIO().Stat(filePath) if err != nil { return okrInputStatError(err) } f, err := runtime.FileIO().Open(filePath) if err != nil { return okrInputStatError(err) } defer f.Close() fileName := filepath.Base(filePath) fmt.Fprintf(runtime.IO().ErrOut, "Uploading: %s (%s)\n", fileName, common.FormatSize(info.Size())) fd := larkcore.NewFormdata() fd.AddField("target_id", targetID) fd.AddField("target_type", fmt.Sprintf("%d", targetTypeVal)) fd.AddFile("data", f) apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: "POST", ApiPath: "/open-apis/okr/v1/images/upload", Body: fd, }, larkcore.WithFileUpload()) if err != nil { return wrapOkrNetworkErr(err, "upload failed: %v", err) } data, err := runtime.ClassifyAPIResponse(apiResp) if err != nil { return err } fileToken := common.GetString(data, "file_token") if fileToken == "" { return errs.NewInternalError(errs.SubtypeInvalidResponse, "upload failed: no file_token returned") } url := common.GetString(data, "url") runtime.Out(map[string]interface{}{ "file_token": fileToken, "url": url, "file_name": fileName, "size": info.Size(), }, nil) return nil }, }
OKRUploadImage uploads an image for use in OKR progress rich text.
var OKRWeight = common.Shortcut{ Service: "okr", Command: "+weight", Description: "Adjust the weight of OKR objectives or key results", Risk: "write", Scopes: []string{"okr:okr.content:writeonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "level", Desc: "level to adjust: objective | key-result", Enum: []string{"objective", "key-result"}, Required: true}, {Name: "cycle-id", Desc: "OKR cycle ID (int64)", Required: true}, {Name: "objective-id", Desc: "objective ID (required when --level=key-result)"}, {Name: "weights", Desc: "JSON array of weight assignments: [{\"id\":\"...\",\"weight\":0.5}]", Input: []string{common.File, common.Stdin}, Required: true}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { level := runtime.Str("level") if level != "objective" && level != "key-result" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--level must be one of: objective | key-result").WithParam("--level") } cycleID := runtime.Str("cycle-id") if id, err := strconv.ParseInt(cycleID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--cycle-id must be a positive int64").WithParam("--cycle-id") } if level == "key-result" { objID := runtime.Str("objective-id") if objID == "" { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--objective-id is required when --level=key-result").WithParam("--objective-id") } if id, err := strconv.ParseInt(objID, 10, 64); err != nil || id <= 0 { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--objective-id must be a positive int64").WithParam("--objective-id") } } weightsStr := runtime.Str("weights") if err := common.RejectDangerousCharsTyped("--weights", weightsStr); err != nil { return err } if _, err := parseWeightOps(weightsStr); err != nil { return err } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { level := runtime.Str("level") cycleID := runtime.Str("cycle-id") objectiveID := runtime.Str("objective-id") ops, _ := parseWeightOps(runtime.Str("weights")) apis := common.NewDryRunAPI() if level == "objective" { listParams := map[string]interface{}{ "page_size": 100, } listPath := fmt.Sprintf("/open-apis/okr/v2/cycles/%s/objectives", cycleID) apis = apis. GET(listPath). Params(listParams). Desc("Fetch all objectives in the cycle to get current weights for normalization") weightParams := map[string]interface{}{ "cycle_id": cycleID, } objectiveWeights := make([]map[string]interface{}, 0, len(ops)) for _, op := range ops { objectiveWeights = append(objectiveWeights, map[string]interface{}{ "objective_id": op.ID, "weight": op.Weight, }) } weightBody := map[string]interface{}{ "objective_weights": objectiveWeights, } weightPath := fmt.Sprintf("/open-apis/okr/v2/cycles/%s/objectives_weight", cycleID) apis = apis. PUT(weightPath). Params(weightParams). Body(weightBody). Desc("Update objective weights (full list sent after normalization)") } else { listParams := map[string]interface{}{ "page_size": 100, } listPath := fmt.Sprintf("/open-apis/okr/v2/objectives/%s/key_results", objectiveID) apis = apis. GET(listPath). Params(listParams). Desc("Fetch all key results for the objective to get current weights for normalization") weightParams := map[string]interface{}{ "objective_id": objectiveID, } keyResultWeights := make([]map[string]interface{}, 0, len(ops)) for _, op := range ops { keyResultWeights = append(keyResultWeights, map[string]interface{}{ "key_result_id": op.ID, "weight": op.Weight, }) } weightBody := map[string]interface{}{ "key_result_weights": keyResultWeights, } weightPath := fmt.Sprintf("/open-apis/okr/v2/objectives/%s/key_results_weight", objectiveID) apis = apis. PUT(weightPath). Params(weightParams). Body(weightBody). Desc("Update key result weights (full list sent after normalization)") } return apis }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { level := runtime.Str("level") cycleID := runtime.Str("cycle-id") objectiveID := runtime.Str("objective-id") ops, err := parseWeightOps(runtime.Str("weights")) if err != nil { return err } var normalizedWeights []map[string]interface{} var total int if level == "objective" { objectives, err := fetchObjectives(ctx, runtime, cycleID) if err != nil { return err } total = len(objectives) objIDs := make(map[string]bool) for _, obj := range objectives { objIDs[obj.ID] = true } for _, op := range ops { if !objIDs[op.ID] { return errs.NewValidationError(errs.SubtypeInvalidArgument, "objective id %q not found in cycle", op.ID).WithParam("--weights") } } normalizedWeights, err = normalizeWeights(objectives, ops) if err != nil { return err } posMap := make(map[string]int32) for _, obj := range objectives { if obj.Position != nil { posMap[obj.ID] = *obj.Position } } params := map[string]interface{}{ "cycle_id": cycleID, } objectiveWeights := make([]map[string]interface{}, 0, len(normalizedWeights)) for _, w := range normalizedWeights { objectiveWeights = append(objectiveWeights, map[string]interface{}{ "objective_id": w["id"], "weight": w["weight"], }) } sort.Slice(objectiveWeights, func(i, j int) bool { idI := objectiveWeights[i]["objective_id"].(string) idJ := objectiveWeights[j]["objective_id"].(string) return posMap[idI] < posMap[idJ] }) body := map[string]interface{}{ "objective_weights": objectiveWeights, } path := fmt.Sprintf("/open-apis/okr/v2/cycles/%s/objectives_weight", cycleID) _, err = runtime.CallAPITyped("PUT", path, params, body) if err != nil { return wrapOkrNetworkErr(err, "failed to update objective weights") } } else { keyResults, err := fetchKeyResults(ctx, runtime, objectiveID) if err != nil { return err } total = len(keyResults) krIDs := make(map[string]bool) for _, kr := range keyResults { krIDs[kr.ID] = true } for _, op := range ops { if !krIDs[op.ID] { return errs.NewValidationError(errs.SubtypeInvalidArgument, "key_result id %q not found in objective", op.ID).WithParam("--weights") } } normalizedWeights, err = normalizeWeights(keyResults, ops) if err != nil { return err } posMap := make(map[string]int32) for _, kr := range keyResults { if kr.Position != nil { posMap[kr.ID] = *kr.Position } } params := map[string]interface{}{ "objective_id": objectiveID, } keyResultWeights := make([]map[string]interface{}, 0, len(normalizedWeights)) for _, w := range normalizedWeights { keyResultWeights = append(keyResultWeights, map[string]interface{}{ "key_result_id": w["id"], "weight": w["weight"], }) } sort.Slice(keyResultWeights, func(i, j int) bool { idI := keyResultWeights[i]["key_result_id"].(string) idJ := keyResultWeights[j]["key_result_id"].(string) return posMap[idI] < posMap[idJ] }) body := map[string]interface{}{ "key_result_weights": keyResultWeights, } path := fmt.Sprintf("/open-apis/okr/v2/objectives/%s/key_results_weight", objectiveID) _, err = runtime.CallAPITyped("PUT", path, params, body) if err != nil { return wrapOkrNetworkErr(err, "failed to update key result weights") } } result := map[string]interface{}{ "level": level, "cycle_id": cycleID, "total": total, "weights": normalizedWeights, } runtime.OutFormat(result, nil, func(w io.Writer) { fmt.Fprintf(w, "Successfully updated weights for %d %s(s)\n", total, level) fmt.Fprintln(w, "Weights:") for _, weightEntry := range normalizedWeights { fmt.Fprintf(w, " %s: %v\n", weightEntry["id"], weightEntry["weight"]) } }) return nil }, }
OKRWeight adjusts the weight of objectives or key results.
Functions ¶
Types ¶
type BlockElementType ¶
type BlockElementType string
BlockElementType 块元素类型
const ( BlockElementTypeGallery BlockElementType = "gallery" BlockElementTypeParagraph BlockElementType = "paragraph" )
func (BlockElementType) Ptr ¶
func (t BlockElementType) Ptr() *BlockElementType
type CategoryName ¶
type CategoryName struct {
Zh *string `json:"zh,omitempty"`
En *string `json:"en,omitempty"`
Ja *string `json:"ja,omitempty"`
}
CategoryName 分类名称
type ContentBlock ¶
type ContentBlock struct {
Blocks []ContentBlockElement `json:"blocks,omitempty"`
}
ContentBlock 内容块
func BuildContentBlock ¶ added in v1.0.64
func BuildContentBlock(text string, mentions []string) *ContentBlock
BuildContentBlock converts text and mentions to a ContentBlock. This is a convenience wrapper around SemiPlainContent.ToContentBlock().
func (*ContentBlock) ToSemiPlain ¶ added in v1.0.64
func (c *ContentBlock) ToSemiPlain() *SemiPlainContent
ToSemiPlain converts ContentBlock to SemiPlainContent (lossy conversion). Position information and formatting are discarded; only text, mentions, docs, and images are extracted.
func (*ContentBlock) ToV1 ¶ added in v1.0.21
func (c *ContentBlock) ToV1() *ContentBlockV1
ToV1 将 ContentBlock 转换为 ContentBlockV1
type ContentBlockElement ¶
type ContentBlockElement struct {
BlockElementType *BlockElementType `json:"block_element_type,omitempty"`
Paragraph *ContentParagraph `json:"paragraph,omitempty"`
Gallery *ContentGallery `json:"gallery,omitempty"`
}
ContentBlockElement 内容块元素
func (*ContentBlockElement) ToV1 ¶ added in v1.0.21
func (e *ContentBlockElement) ToV1() ContentBlockElementV1
ToV1 将 ContentBlockElement 转换为 ContentBlockElementV1
type ContentBlockElementV1 ¶ added in v1.0.21
type ContentBlockElementV1 struct {
Type *BlockElementType `json:"type,omitempty"`
Paragraph *ContentParagraphV1 `json:"paragraph,omitempty"`
Gallery *ContentGalleryV1 `json:"gallery,omitempty"`
}
ContentBlockElementV1 内容块元素
func (*ContentBlockElementV1) ToV2 ¶ added in v1.0.21
func (e *ContentBlockElementV1) ToV2() ContentBlockElement
ToV2 将 ContentBlockElementV1 转换为 ContentBlockElement
type ContentBlockV1 ¶ added in v1.0.21
type ContentBlockV1 struct {
Blocks []ContentBlockElementV1 `json:"blocks,omitempty"`
}
ContentBlockV1 是 OKR v1 API 使用的内容块
func (*ContentBlockV1) ToV2 ¶ added in v1.0.21
func (c *ContentBlockV1) ToV2() *ContentBlock
ToV2 将 ContentBlockV1 转换为 ContentBlock
type ContentColor ¶
type ContentColor struct {
Red *int32 `json:"red,omitempty"`
Green *int32 `json:"green,omitempty"`
Blue *int32 `json:"blue,omitempty"`
Alpha *float64 `json:"alpha,omitempty"`
}
ContentColor 颜色
type ContentDocsLink ¶
type ContentDocsLink struct {
URL *string `json:"url,omitempty"`
Title *string `json:"title,omitempty"`
}
ContentDocsLink 文档链接
type ContentGallery ¶
type ContentGallery struct {
Images []ContentImageItem `json:"images,omitempty"`
}
ContentGallery 图库
func (*ContentGallery) ToV1 ¶ added in v1.0.21
func (g *ContentGallery) ToV1() *ContentGalleryV1
ToV1 将 ContentGallery 转换为 ContentGalleryV1
type ContentGalleryV1 ¶ added in v1.0.21
type ContentGalleryV1 struct {
ImageList []ContentImageItemV1 `json:"imageList,omitempty"`
}
ContentGalleryV1 图库
func (*ContentGalleryV1) ToV2 ¶ added in v1.0.21
func (g *ContentGalleryV1) ToV2() *ContentGallery
ToV2 将 ContentGalleryV1 转换为 ContentGallery
type ContentImageItem ¶
type ContentImageItem struct {
FileToken *string `json:"file_token,omitempty"`
Src *string `json:"src,omitempty"`
Width *float64 `json:"width,omitempty"`
Height *float64 `json:"height,omitempty"`
}
ContentImageItem 图片项
func (*ContentImageItem) ToV1 ¶ added in v1.0.21
func (i *ContentImageItem) ToV1() ContentImageItemV1
ToV1 将 ContentImageItem 转换为 ContentImageItemV1
type ContentImageItemV1 ¶ added in v1.0.21
type ContentImageItemV1 struct {
FileToken *string `json:"fileToken,omitempty"`
Src *string `json:"src,omitempty"`
Width *float64 `json:"width,omitempty"`
Height *float64 `json:"height,omitempty"`
}
ContentImageItemV1 图片项
func (*ContentImageItemV1) ToV2 ¶ added in v1.0.21
func (i *ContentImageItemV1) ToV2() ContentImageItem
ToV2 将 ContentImageItemV1 转换为 ContentImageItem
type ContentList ¶
type ContentList struct {
ListType *ListType `json:"list_type,omitempty"`
IndentLevel *int32 `json:"indent_level,omitempty"`
Number *int32 `json:"number,omitempty"`
}
ContentList 列表
func (*ContentList) ToV1 ¶ added in v1.0.21
func (l *ContentList) ToV1() *ContentListV1
ToV1 将 ContentList 转换为 ContentListV1
type ContentListV1 ¶ added in v1.0.21
type ContentListV1 struct {
Type *ListType `json:"type,omitempty"`
IndentLevel *int32 `json:"indentLevel,omitempty"`
Number *int32 `json:"number,omitempty"`
}
ContentListV1 列表
func (*ContentListV1) ToV2 ¶ added in v1.0.21
func (l *ContentListV1) ToV2() *ContentList
ToV2 将 ContentListV1 转换为 ContentList
type ContentMention ¶
type ContentMention struct {
UserID *string `json:"user_id,omitempty"`
}
ContentMention 提及
func (*ContentMention) ToV1 ¶ added in v1.0.21
func (m *ContentMention) ToV1() *ContentPersonV1
ToV1 将 ContentMention 转换为 ContentPersonV1
type ContentParagraph ¶
type ContentParagraph struct {
Style *ContentParagraphStyle `json:"style,omitempty"`
Elements []ContentParagraphElement `json:"elements,omitempty"`
}
ContentParagraph 段落
func (*ContentParagraph) ToV1 ¶ added in v1.0.21
func (p *ContentParagraph) ToV1() *ContentParagraphV1
ToV1 将 ContentParagraph 转换为 ContentParagraphV1
type ContentParagraphElement ¶
type ContentParagraphElement struct {
ParagraphElementType *ParagraphElementType `json:"paragraph_element_type,omitempty"`
TextRun *ContentTextRun `json:"text_run,omitempty"`
DocsLink *ContentDocsLink `json:"docs_link,omitempty"`
Mention *ContentMention `json:"mention,omitempty"`
}
ContentParagraphElement 段落元素
func (*ContentParagraphElement) ToV1 ¶ added in v1.0.21
func (e *ContentParagraphElement) ToV1() ContentParagraphElementV1
ToV1 将 ContentParagraphElement 转换为 ContentParagraphElementV1
type ContentParagraphElementV1 ¶ added in v1.0.21
type ContentParagraphElementV1 struct {
Type *ParagraphElementTypeV1 `json:"type,omitempty"`
TextRun *ContentTextRunV1 `json:"textRun,omitempty"`
DocsLink *ContentDocsLink `json:"docsLink,omitempty"`
Person *ContentPersonV1 `json:"person,omitempty"`
}
ContentParagraphElementV1 段落元素
func (*ContentParagraphElementV1) ToV2 ¶ added in v1.0.21
func (e *ContentParagraphElementV1) ToV2() ContentParagraphElement
ToV2 将 ContentParagraphElementV1 转换为 ContentParagraphElement
type ContentParagraphStyle ¶
type ContentParagraphStyle struct {
List *ContentList `json:"list,omitempty"`
}
ContentParagraphStyle 段落样式
func (*ContentParagraphStyle) ToV1 ¶ added in v1.0.21
func (s *ContentParagraphStyle) ToV1() *ContentParagraphStyleV1
ToV1 将 ContentParagraphStyle 转换为 ContentParagraphStyleV1
type ContentParagraphStyleV1 ¶ added in v1.0.21
type ContentParagraphStyleV1 struct {
List *ContentListV1 `json:"list,omitempty"`
}
ContentParagraphStyleV1 段落样式
func (*ContentParagraphStyleV1) ToV2 ¶ added in v1.0.21
func (s *ContentParagraphStyleV1) ToV2() *ContentParagraphStyle
ToV2 将 ContentParagraphStyleV1 转换为 ContentParagraphStyle
type ContentParagraphV1 ¶ added in v1.0.21
type ContentParagraphV1 struct {
Style *ContentParagraphStyleV1 `json:"style,omitempty"`
Elements []ContentParagraphElementV1 `json:"elements,omitempty"`
}
ContentParagraphV1 段落
func (*ContentParagraphV1) ToV2 ¶ added in v1.0.21
func (p *ContentParagraphV1) ToV2() *ContentParagraph
ToV2 将 ContentParagraphV1 转换为 ContentParagraph
type ContentPersonV1 ¶ added in v1.0.21
type ContentPersonV1 struct {
OpenID *string `json:"openId,omitempty"`
}
ContentPersonV1 提及的人
func (*ContentPersonV1) ToV2 ¶ added in v1.0.21
func (p *ContentPersonV1) ToV2() *ContentMention
ToV2 将 ContentPersonV1 转换为 ContentMention
type ContentTextRun ¶
type ContentTextRun struct {
Text *string `json:"text,omitempty"`
Style *ContentTextStyle `json:"style,omitempty"`
}
ContentTextRun 文本块
func (*ContentTextRun) ToV1 ¶ added in v1.0.21
func (t *ContentTextRun) ToV1() *ContentTextRunV1
ToV1 将 ContentTextRun 转换为 ContentTextRunV1
type ContentTextRunV1 ¶ added in v1.0.21
type ContentTextRunV1 struct {
Text *string `json:"text,omitempty"`
Style *ContentTextStyleV1 `json:"style,omitempty"`
}
ContentTextRunV1 文本块
func (*ContentTextRunV1) ToV2 ¶ added in v1.0.21
func (t *ContentTextRunV1) ToV2() *ContentTextRun
ToV2 将 ContentTextRunV1 转换为 ContentTextRun
type ContentTextStyle ¶
type ContentTextStyle struct {
Bold *bool `json:"bold,omitempty"`
StrikeThrough *bool `json:"strike_through,omitempty"`
BackColor *ContentColor `json:"back_color,omitempty"`
TextColor *ContentColor `json:"text_color,omitempty"`
Link *ContentLink `json:"link,omitempty"`
}
ContentTextStyle 文本样式
func (*ContentTextStyle) ToV1 ¶ added in v1.0.21
func (s *ContentTextStyle) ToV1() *ContentTextStyleV1
ToV1 将 ContentTextStyle 转换为 ContentTextStyleV1
type ContentTextStyleV1 ¶ added in v1.0.21
type ContentTextStyleV1 struct {
Bold *bool `json:"bold,omitempty"`
StrikeThrough *bool `json:"strikeThrough,omitempty"`
BackColor *ContentColor `json:"backColor,omitempty"`
TextColor *ContentColor `json:"textColor,omitempty"`
Link *ContentLink `json:"link,omitempty"`
}
ContentTextStyleV1 文本样式
func (*ContentTextStyleV1) ToV2 ¶ added in v1.0.21
func (s *ContentTextStyleV1) ToV2() *ContentTextStyle
ToV2 将 ContentTextStyleV1 转换为 ContentTextStyle
type Cycle ¶
type Cycle struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
TenantCycleID string `json:"tenant_cycle_id"`
Owner Owner `json:"owner"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
CycleStatus *CycleStatus `json:"cycle_status,omitempty"`
Score *float64 `json:"score,omitempty"`
}
Cycle 周期
type CycleStatus ¶
type CycleStatus int32
CycleStatus 周期状态
const ( CycleStatusDefault CycleStatus = 0 CycleStatusNormal CycleStatus = 1 CycleStatusInvalid CycleStatus = 2 CycleStatusHidden CycleStatus = 3 )
func (CycleStatus) Ptr ¶
func (t CycleStatus) Ptr() *CycleStatus
type KeyResult ¶
type KeyResult struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
Owner Owner `json:"owner"`
ObjectiveID string `json:"objective_id"`
Position *int32 `json:"position,omitempty"`
Content *ContentBlock `json:"content,omitempty"`
Score *float64 `json:"score,omitempty"`
Weight *float64 `json:"weight,omitempty"`
Deadline *string `json:"deadline,omitempty"`
}
KeyResult 关键结果
func (*KeyResult) ToResp ¶
func (k *KeyResult) ToResp() *RespKeyResult
ToResp converts KeyResult to RespKeyResult
func (*KeyResult) ToSimple ¶ added in v1.0.64
func (k *KeyResult) ToSimple() *RespKeyResultSimple
ToSimple converts KeyResult to RespKeyResultSimple.
type Objective ¶
type Objective struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
Owner Owner `json:"owner"`
CycleID string `json:"cycle_id"`
Position *int32 `json:"position,omitempty"`
Content *ContentBlock `json:"content,omitempty"`
Score *float64 `json:"score,omitempty"`
Notes *ContentBlock `json:"notes,omitempty"`
Weight *float64 `json:"weight,omitempty"`
Deadline *string `json:"deadline,omitempty"`
CategoryID *string `json:"category_id,omitempty"`
}
Objective 目标
func (*Objective) ToResp ¶
func (o *Objective) ToResp() *RespObjective
ToResp converts Objective to RespObjective
func (*Objective) ToSimple ¶ added in v1.0.64
func (o *Objective) ToSimple() *RespObjectiveSimple
ToSimple converts Objective to RespObjectiveSimple.
type Owner ¶
type Owner struct {
OwnerType OwnerType `json:"owner_type"`
UserID *string `json:"user_id,omitempty"`
}
Owner OKR 所有者
type ParagraphElementType ¶
type ParagraphElementType string
ParagraphElementType 段落元素类型
const ( ParagraphElementTypeDocsLink ParagraphElementType = "docsLink" ParagraphElementTypeMention ParagraphElementType = "mention" ParagraphElementTypeTextRun ParagraphElementType = "textRun" )
func (ParagraphElementType) Ptr ¶
func (t ParagraphElementType) Ptr() *ParagraphElementType
func (ParagraphElementType) ToV1 ¶ added in v1.0.21
func (t ParagraphElementType) ToV1() ParagraphElementTypeV1
ToV1 将 ParagraphElementType 转换为 ParagraphElementTypeV1
type ParagraphElementTypeV1 ¶ added in v1.0.21
type ParagraphElementTypeV1 string
const ( ParagraphElementTypeV1DocsLink ParagraphElementTypeV1 = "docsLink" ParagraphElementTypeV1Mention ParagraphElementTypeV1 = "person" ParagraphElementTypeV1TextRun ParagraphElementTypeV1 = "textRun" )
func (ParagraphElementTypeV1) Ptr ¶ added in v1.0.21
func (t ParagraphElementTypeV1) Ptr() *ParagraphElementTypeV1
func (ParagraphElementTypeV1) ToV2 ¶ added in v1.0.21
func (t ParagraphElementTypeV1) ToV2() ParagraphElementType
ToV2 将 ParagraphElementTypeV1 转换为 ParagraphElementType
type Progress ¶ added in v1.0.21
type Progress struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
Owner Owner `json:"owner"`
EntityType *int32 `json:"entity_type,omitempty"`
EntityID string `json:"entity_id"`
Content *ContentBlock `json:"content,omitempty"`
ProgressRate *ProgressRate `json:"progress_rate,omitempty"`
}
Progress 进展记录(v2 API)
func (*Progress) ToResp ¶ added in v1.0.21
func (p *Progress) ToResp() *RespProgress
ToResp converts Progress to RespProgress
func (*Progress) ToSimple ¶ added in v1.0.64
func (p *Progress) ToSimple() *RespProgressSimple
ToSimple converts Progress to RespProgressSimple.
type ProgressRate ¶ added in v1.0.21
type ProgressRate struct {
ProgressPercent *float64 `json:"progress_percent,omitempty"`
ProgressStatus *int32 `json:"progress_status,omitempty"`
}
ProgressRate 进度率(v2 API)
type ProgressRateV1 ¶ added in v1.0.21
type ProgressRateV1 struct {
Percent *float64 `json:"percent,omitempty"`
Status *int32 `json:"status,omitempty"`
}
ProgressRateV1 进度率
type ProgressStatus ¶ added in v1.0.21
type ProgressStatus int32
ProgressStatus 进展状态
const ( ProgressStatusNormal ProgressStatus = 0 // 正常 ProgressStatusOverdue ProgressStatus = 1 // 逾期 ProgressStatusDone ProgressStatus = 2 // 已完成 )
func ParseProgressStatus ¶ added in v1.0.21
func ParseProgressStatus(s string) (ProgressStatus, bool)
ParseProgressStatus parses a progress status string into ProgressStatus. Accepts "normal", "overdue", "done" or their numeric values "0", "1", "2".
func (ProgressStatus) String ¶ added in v1.0.21
func (s ProgressStatus) String() string
String returns a human-readable name for ProgressStatus.
type ProgressV1 ¶ added in v1.0.21
type ProgressV1 struct {
ID string `json:"progress_id"`
ModifyTime string `json:"modify_time"`
Content *ContentBlockV1 `json:"content,omitempty"`
ProgressRate *ProgressRateV1 `json:"progress_rate,omitempty"`
}
ProgressV1 进展记录
func (*ProgressV1) ToResp ¶ added in v1.0.21
func (p *ProgressV1) ToResp() *RespProgress
ToResp converts ProgressV1 to RespProgress
func (*ProgressV1) ToSimple ¶ added in v1.0.64
func (p *ProgressV1) ToSimple() *RespProgressSimple
ToSimple converts ProgressV1 to RespProgressSimple.
type RespAlignment ¶
type RespAlignment struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
FromOwner RespOwner `json:"from_owner"`
ToOwner RespOwner `json:"to_owner"`
FromEntityType string `json:"from_entity_type"`
FromEntityID string `json:"from_entity_id"`
ToEntityType string `json:"to_entity_type"`
ToEntityID string `json:"to_entity_id"`
}
RespAlignment 对齐关系
type RespCategory ¶
type RespCategory struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
CategoryType string `json:"category_type"`
Enabled *bool `json:"enabled,omitempty"`
Color *string `json:"color,omitempty"`
Name CategoryName `json:"name"`
}
RespCategory 分类
type RespCycle ¶
type RespCycle struct {
ID string `json:"id"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
CycleStatus *string `json:"cycle_status,omitempty"`
}
RespCycle 周期
type RespIndicator ¶
type RespIndicator struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
Owner RespOwner `json:"owner"`
EntityType *string `json:"entity_type,omitempty"`
EntityID *string `json:"entity_id,omitempty"`
IndicatorStatus *string `json:"indicator_status,omitempty"`
StatusCalculateType *string `json:"status_calculate_type,omitempty"`
StartValue *float64 `json:"start_value,omitempty"`
TargetValue *float64 `json:"target_value,omitempty"`
CurrentValue *float64 `json:"current_value,omitempty"`
CurrentValueCalculateType *string `json:"current_value_calculate_type,omitempty"`
Unit *RespIndicatorUnit `json:"unit,omitempty"`
}
RespIndicator 指标
type RespIndicatorUnit ¶
type RespIndicatorUnit struct {
UnitType *string `json:"unit_type,omitempty"`
UnitValue *string `json:"unit_value,omitempty"`
}
RespIndicatorUnit 指标单位
type RespKeyResult ¶
type RespKeyResult struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
Owner RespOwner `json:"owner"`
ObjectiveID string `json:"objective_id"`
Position *int32 `json:"position,omitempty"`
Content *string `json:"content,omitempty"`
Score *float64 `json:"score,omitempty"`
Weight *float64 `json:"weight,omitempty"`
Deadline *string `json:"deadline,omitempty"`
}
RespKeyResult 关键结果
type RespKeyResultSimple ¶ added in v1.0.64
type RespKeyResultSimple struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
Owner RespOwner `json:"owner"`
ObjectiveID string `json:"objective_id"`
Position *int32 `json:"position,omitempty"`
Content *SemiPlainContent `json:"content,omitempty"`
Score *float64 `json:"score,omitempty"`
Weight *float64 `json:"weight,omitempty"`
Deadline *string `json:"deadline,omitempty"`
}
RespKeyResultSimple is KeyResult response with SemiPlainContent instead of ContentBlock JSON string.
type RespObjective ¶
type RespObjective struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
Owner RespOwner `json:"owner"`
CycleID string `json:"cycle_id"`
Position *int32 `json:"position,omitempty"`
Content *string `json:"content,omitempty"`
Score *float64 `json:"score,omitempty"`
Notes *string `json:"notes,omitempty"`
Weight *float64 `json:"weight,omitempty"`
Deadline *string `json:"deadline,omitempty"`
CategoryID *string `json:"category_id,omitempty"`
KeyResults []RespKeyResult `json:"key_results,omitempty"`
}
RespObjective 目标
type RespObjectiveSimple ¶ added in v1.0.64
type RespObjectiveSimple struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
Owner RespOwner `json:"owner"`
CycleID string `json:"cycle_id"`
Position *int32 `json:"position,omitempty"`
Content *SemiPlainContent `json:"content,omitempty"`
Score *float64 `json:"score,omitempty"`
Notes *SemiPlainContent `json:"notes,omitempty"`
Weight *float64 `json:"weight,omitempty"`
Deadline *string `json:"deadline,omitempty"`
CategoryID *string `json:"category_id,omitempty"`
KeyResults []RespKeyResultSimple `json:"key_results,omitempty"`
}
RespObjectiveSimple is Objective response with SemiPlainContent instead of ContentBlock JSON string.
type RespOwner ¶
type RespOwner struct {
OwnerType string `json:"owner_type"`
UserID *string `json:"user_id,omitempty"`
}
RespOwner OKR 所有者
type RespProgress ¶ added in v1.0.21
type RespProgress struct {
ID string `json:"progress_id"`
ModifyTime string `json:"modify_time"`
CreateTime *string `json:"create_time,omitempty"`
Content *string `json:"content,omitempty"`
ProgressRate *RespProgressRate `json:"progress_rate,omitempty"`
}
RespProgress 进展记录
type RespProgressRate ¶ added in v1.0.21
type RespProgressRate struct {
Percent *float64 `json:"percent,omitempty"`
Status *string `json:"status,omitempty"`
}
RespProgressRate 进度率(面向用户的响应格式,Status 为可读字符串)
type RespProgressSimple ¶ added in v1.0.64
type RespProgressSimple struct {
ID string `json:"progress_id"`
ModifyTime string `json:"modify_time"`
CreateTime *string `json:"create_time,omitempty"`
Content *SemiPlainContent `json:"content,omitempty"`
ProgressRate *RespProgressRate `json:"progress_rate,omitempty"`
}
RespProgressSimple is Progress response with SemiPlainContent instead of ContentBlock JSON string.
type SemiPlainContent ¶ added in v1.0.64
type SemiPlainContent struct {
Text string `json:"text"`
Mention []string `json:"mention,omitempty"`
Docs []SemiPlainDoc `json:"docs,omitempty"`
Images []string `json:"images,omitempty"`
}
SemiPlainContent is a simplified, lossy representation of ContentBlock. It contains plain text, mentions, docs, and images without rich formatting or position info.
func (*SemiPlainContent) ToContentBlock ¶ added in v1.0.64
func (s *SemiPlainContent) ToContentBlock() *ContentBlock
ToContentBlock converts SemiPlainContent to ContentBlock. Text and mentions are placed in a single paragraph (text first, then mentions). Docs and images are NOT converted (input semi-plain format only supports text+mention).
type SemiPlainDoc ¶ added in v1.0.64
SemiPlainDoc represents a document link in semi-plain content.
type StatusCalculateType ¶
type StatusCalculateType int32
StatusCalculateType 状态计算类型
const ( StatusCalculateTypeManualUpdate StatusCalculateType = 0 StatusCalculateTypeAutomaticallyUpdatesBasedOnProgressAndCurrentTime StatusCalculateType = 1 StatusCalculateTypeStatusUpdatesBasedOnTheHighestRiskKeyResults StatusCalculateType = 2 )