okr

package
v1.0.18 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 23, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
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},
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		cycleID := runtime.Str("cycle-id")
		if cycleID == "" {
			return common.FlagErrorf("--cycle-id is required")
		}
		if id, err := strconv.ParseInt(cycleID, 10, 64); err != nil || id <= 0 {
			return common.FlagErrorf("--cycle-id must be a positive int64")
		}
		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")

		queryParams := make(larkcore.QueryParams)
		queryParams.Set("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.DoAPIJSON("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.Set("page_token", pageToken)
		}

		respObjectives := make([]*RespObjective, 0, len(objectives))
		for i := range objectives {
			if err := ctx.Err(); err != nil {
				return err
			}
			obj := &objectives[i]

			krQuery := make(larkcore.QueryParams)
			krQuery.Set("page_size", "100")

			var keyResults []KeyResult
			krPage := 0
			for {
				if err := ctx.Err(); err != nil {
					return err
				}
				if krPage > 0 {
					select {
					case <-ctx.Done():
						return ctx.Err()
					case <-time.After(500 * time.Millisecond):
					}
				}
				krPage++

				path := fmt.Sprintf("/open-apis/okr/v2/objectives/%s/key_results", obj.ID)
				data, err := runtime.DoAPIJSON("GET", path, krQuery, nil)
				if err != nil {
					return err
				}

				itemsRaw, _ := data["items"].([]interface{})
				for _, item := range itemsRaw {
					raw, err := json.Marshal(item)
					if err != nil {
						continue
					}
					var kr KeyResult
					if err := json.Unmarshal(raw, &kr); err != nil {
						continue
					}
					keyResults = append(keyResults, kr)
				}

				hasMore, pageToken := common.PaginationMeta(data)
				if !hasMore || pageToken == "" {
					break
				}
				krQuery.Set("page_token", pageToken)
			}

			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),
		}

		runtime.OutFormat(result, nil, func(w io.Writer) {
			fmt.Fprintf(w, "Cycle %s: %d objective(s)\n", cycleID, len(respObjectives))
			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.

View Source
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 common.FlagErrorf("--user-id-type must be one of: open_id | union_id | user_id")
		}
		userID := runtime.Str("user-id")
		if err := validate.RejectControlChars(userID, "user-id"); err != nil {
			return err
		}

		tr := runtime.Str("time-range")
		if tr != "" {
			if err := validate.RejectControlChars(tr, "time-range"); err != nil {
				return err
			}
			if _, _, err := parseTimeRange(tr); err != nil {
				return common.FlagErrorf("--time-range: %s", 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 common.FlagErrorf("--time-range: %s", err)
			}
			hasRange = true
		}

		queryParams := make(larkcore.QueryParams)
		queryParams.Set("user_id", userID)
		queryParams.Set("user_id_type", userIDType)
		queryParams.Set("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.DoAPIJSON("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.Set("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())
		}

		runtime.OutFormat(map[string]interface{}{
			"cycles": respCycles,
			"total":  len(respCycles),
		}, 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))
			}
		})
		return nil
	},
}

Functions

func Shortcuts

func Shortcuts() []common.Shortcut

Shortcuts returns all okr shortcuts.

Types

type BlockElementType

type BlockElementType string

BlockElementType 块元素类型

const (
	BlockElementTypeGallery   BlockElementType = "gallery"
	BlockElementTypeParagraph BlockElementType = "paragraph"
)

func (BlockElementType) Ptr

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 内容块

type ContentBlockElement

type ContentBlockElement struct {
	BlockElementType *BlockElementType `json:"block_element_type,omitempty"`
	Paragraph        *ContentParagraph `json:"paragraph,omitempty"`
	Gallery          *ContentGallery   `json:"gallery,omitempty"`
}

ContentBlockElement 内容块元素

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 struct {
	URL   *string `json:"url,omitempty"`
	Title *string `json:"title,omitempty"`
}

ContentDocsLink 文档链接

type ContentGallery

type ContentGallery struct {
	Images []ContentImageItem `json:"images,omitempty"`
}

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 图片项

type ContentLink struct {
	URL *string `json:"url,omitempty"`
}

ContentLink 链接

type ContentList

type ContentList struct {
	ListType    *ListType `json:"list_type,omitempty"`
	IndentLevel *int32    `json:"indent_level,omitempty"`
	Number      *int32    `json:"number,omitempty"`
}

ContentList 列表

type ContentMention

type ContentMention struct {
	UserID *string `json:"user_id,omitempty"`
}

ContentMention 提及

type ContentParagraph

type ContentParagraph struct {
	Style    *ContentParagraphStyle    `json:"style,omitempty"`
	Elements []ContentParagraphElement `json:"elements,omitempty"`
}

ContentParagraph 段落

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 段落元素

type ContentParagraphStyle

type ContentParagraphStyle struct {
	List *ContentList `json:"list,omitempty"`
}

ContentParagraphStyle 段落样式

type ContentTextRun

type ContentTextRun struct {
	Text  *string           `json:"text,omitempty"`
	Style *ContentTextStyle `json:"style,omitempty"`
}

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 文本样式

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 周期

func (*Cycle) ToResp

func (c *Cycle) ToResp() *RespCycle

ToResp converts Cycle to RespCycle

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

func (CycleStatus) ToString

func (t CycleStatus) ToString() string

ToString CycleStatus to string

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

type ListType

type ListType string

ListType 列表类型

const (
	ListTypeBullet     ListType = "bullet"
	ListTypeCheckBox   ListType = "checkBox"
	ListTypeCheckedBox ListType = "checkedBox"
	ListTypeIndent     ListType = "indent"
	ListTypeNumber     ListType = "number"
)

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

type Owner

type Owner struct {
	OwnerType OwnerType `json:"owner_type"`
	UserID    *string   `json:"user_id,omitempty"`
}

Owner OKR 所有者

func (*Owner) ToResp

func (o *Owner) ToResp() *RespOwner

ToResp converts Owner to RespOwner

type OwnerType

type OwnerType string

OwnerType 所有者类型

const (
	OwnerTypeDepartment OwnerType = "department"
	OwnerTypeUser       OwnerType = "user"
)

type ParagraphElementType

type ParagraphElementType string

ParagraphElementType 段落元素类型

const (
	ParagraphElementTypeDocsLink ParagraphElementType = "docsLink"
	ParagraphElementTypeMention  ParagraphElementType = "mention"
	ParagraphElementTypeTextRun  ParagraphElementType = "textRun"
)

func (ParagraphElementType) Ptr

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"`
	CreateTime    string    `json:"create_time"`
	UpdateTime    string    `json:"update_time"`
	TenantCycleID string    `json:"tenant_cycle_id"`
	Owner         RespOwner `json:"owner"`
	StartTime     string    `json:"start_time"`
	EndTime       string    `json:"end_time"`
	CycleStatus   *string   `json:"cycle_status,omitempty"`
	Score         *float64  `json:"score,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 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 RespOwner

type RespOwner struct {
	OwnerType string  `json:"owner_type"`
	UserID    *string `json:"user_id,omitempty"`
}

RespOwner OKR 所有者

type StatusCalculateType

type StatusCalculateType int32

StatusCalculateType 状态计算类型

const (
	StatusCalculateTypeManualUpdate                                      StatusCalculateType = 0
	StatusCalculateTypeAutomaticallyUpdatesBasedOnProgressAndCurrentTime StatusCalculateType = 1
	StatusCalculateTypeStatusUpdatesBasedOnTheHighestRiskKeyResults      StatusCalculateType = 2
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL