okr

package
v1.0.22 Latest Latest
Warning

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

Go to latest
Published: Apr 29, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
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 in ContentBlock JSON format", 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"},
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		content := runtime.Str("content")
		if content == "" {
			return common.FlagErrorf("--content is required")
		}
		if err := validate.RejectControlChars(content, "content"); err != nil {
			return err
		}
		// Validate content is valid JSON and can be parsed as ContentBlock
		var cb ContentBlock
		if err := json.Unmarshal([]byte(content), &cb); err != nil {
			return common.FlagErrorf("--content must be valid ContentBlock JSON: %s", err)
		}

		targetID := runtime.Str("target-id")
		if targetID == "" {
			return common.FlagErrorf("--target-id is required")
		}
		if err := validate.RejectControlChars(targetID, "target-id"); err != nil {
			return err
		}
		if id, err := strconv.ParseInt(targetID, 10, 64); err != nil || id <= 0 {
			return common.FlagErrorf("--target-id must be a positive int64")
		}

		targetType := runtime.Str("target-type")
		if _, ok := targetTypeAllowed[targetType]; !ok {
			return common.FlagErrorf("--target-type must be one of: objective | key_result")
		}

		if v := runtime.Str("source-title"); v != "" {
			if err := validate.RejectControlChars(v, "source-title"); err != nil {
				return err
			}
		}
		if v := runtime.Str("source-url"); v != "" {
			if err := validate.RejectControlChars(v, "source-url"); 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 common.FlagErrorf("--progress-percent must be a number between -99999999999 and 99999999999")
			}
		}
		if v := runtime.Str("progress-status"); v != "" {
			if _, ok := ParseProgressStatus(v); !ok {
				return common.FlagErrorf("--progress-status must be one of: normal | overdue | done")
			}
			if v := runtime.Str("progress-percent"); v == "" {
				return common.FlagErrorf("--progress-percent must provided with --progress-status")
			}
		}

		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")
		}
		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 := make(larkcore.QueryParams)
		queryParams.Set("user_id_type", p.UserIDType)

		data, err := runtime.DoAPIJSON("POST", "/open-apis/okr/v1/progress_records/", queryParams, body)
		if err != nil {
			return err
		}

		record, err := parseProgressRecord(data)
		if err != nil {
			return err
		}

		resp := record.ToResp()
		result := map[string]interface{}{
			"progress": resp,
		}

		runtime.OutFormat(result, nil, func(w io.Writer) {
			fmt.Fprintf(w, "Created Progress [%s]\n", resp.ID)
			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.

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 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 common.FlagErrorf("--progress-id is required")
		}
		if id, err := strconv.ParseInt(progressID, 10, 64); err != nil || id <= 0 {
			return common.FlagErrorf("--progress-id must be a positive int64")
		}
		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.DoAPIJSON("DELETE", path, larkcore.QueryParams{}, 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.

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

		queryParams := make(larkcore.QueryParams)
		queryParams.Set("user_id_type", userIDType)

		path := fmt.Sprintf("/open-apis/okr/v1/progress_records/%s", progressID)
		data, err := runtime.DoAPIJSON("GET", path, queryParams, nil)
		if err != nil {
			return err
		}

		record, err := parseProgressRecord(data)
		if err != nil {
			return err
		}

		resp := record.ToResp()
		result := map[string]interface{}{
			"progress": resp,
		}

		runtime.OutFormat(result, nil, func(w io.Writer) {
			fmt.Fprintf(w, "Progress [%s]\n", resp.ID)
			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.

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
	},
}
View Source
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 common.FlagErrorf("--target-id is required")
		}
		if err := validate.RejectControlChars(targetID, "target-id"); err != nil {
			return err
		}
		if id, err := strconv.ParseInt(targetID, 10, 64); err != nil || id <= 0 {
			return common.FlagErrorf("--target-id must be a positive int64")
		}

		targetType := runtime.Str("target-type")
		if _, ok := targetTypeAllowed[targetType]; !ok {
			return common.FlagErrorf("--target-type must be one of: objective | key_result")
		}

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

		deptIDType := runtime.Str("department-id-type")
		if deptIDType != "department_id" && deptIDType != "open_department_id" {
			return common.FlagErrorf("--department-id-type must be one of: department_id | open_department_id")
		}
		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 := make(larkcore.QueryParams)
		queryParams.Set("user_id_type", userIDType)
		queryParams.Set("department_id_type", deptIDType)
		queryParams.Set("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.DoAPIJSON("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.Set("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.

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

		content := runtime.Str("content")
		if content == "" {
			return common.FlagErrorf("--content is required")
		}
		if err := validate.RejectControlChars(content, "content"); err != nil {
			return err
		}
		var cb ContentBlock
		if err := json.Unmarshal([]byte(content), &cb); err != nil {
			return common.FlagErrorf("--content must be valid ContentBlock JSON: %s", 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 common.FlagErrorf("--progress-percent must be a number between -99999999999 and 99999999999")
			}
		}
		if v := runtime.Str("progress-status"); v != "" {
			if _, ok := ParseProgressStatus(v); !ok {
				return common.FlagErrorf("--progress-status must be one of: normal | overdue | done")
			}
			if v := runtime.Str("progress-percent"); v == "" {
				return common.FlagErrorf("--progress-percent must provided with --progress-status")
			}
		}

		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")
		}
		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 := make(larkcore.QueryParams)
		queryParams.Set("user_id_type", p.UserIDType)

		path := fmt.Sprintf("/open-apis/okr/v1/progress_records/%s", p.ProgressID)
		data, err := runtime.DoAPIJSON("PUT", path, queryParams, body)
		if err != nil {
			return err
		}

		record, err := parseProgressRecord(data)
		if err != nil {
			return err
		}

		resp := record.ToResp()
		result := map[string]interface{}{
			"progress": resp,
		}

		runtime.OutFormat(result, nil, func(w io.Writer) {
			fmt.Fprintf(w, "Updated Progress [%s]\n", resp.ID)
			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.

View Source
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 common.FlagErrorf("--file is required")
		}
		ext := strings.ToLower(filepath.Ext(filePath))
		if !allowedImageExts[ext] {
			return common.FlagErrorf("--file must be an image (supported: JPG, JPEG, PNG, GIF, BMP), got %q", ext)
		}

		targetID := runtime.Str("target-id")
		if targetID == "" {
			return common.FlagErrorf("--target-id is required")
		}
		if id, err := strconv.ParseInt(targetID, 10, 64); err != nil || id <= 0 {
			return common.FlagErrorf("--target-id must be a positive int64")
		}

		targetType := runtime.Str("target-type")
		if _, ok := targetTypeAllowed[targetType]; !ok {
			return common.FlagErrorf("--target-type must be one of: objective | key_result")
		}
		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 common.WrapInputStatError(err)
		}

		f, err := runtime.FileIO().Open(filePath)
		if err != nil {
			return common.WrapInputStatError(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 {
			var exitErr *output.ExitError
			if errors.As(err, &exitErr) {
				return err
			}
			return output.ErrNetwork("upload failed: %v", err)
		}

		var result map[string]interface{}
		if err := json.Unmarshal(apiResp.RawBody, &result); err != nil {
			return output.Errorf(output.ExitAPI, "api_error", "upload failed: invalid response JSON: %v", err)
		}

		if larkCode := int(common.GetFloat(result, "code")); larkCode != 0 {
			msg, _ := result["msg"].(string)
			return output.ErrAPI(larkCode, fmt.Sprintf("upload failed: [%d] %s", larkCode, msg), result["error"])
		}

		data, _ := result["data"].(map[string]interface{})
		fileToken, _ := data["file_token"].(string)
		url, _ := data["url"].(string)

		if fileToken == "" {
			return output.Errorf(output.ExitAPI, "api_error", "upload failed: no file_token returned")
		}

		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.

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

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

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

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

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

ToV2 将 ContentImageItemV1 转换为 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 列表

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

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

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

ToV2 将 ContentParagraphElementV1 转换为 ContentParagraphElement

type ContentParagraphStyle

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

ContentParagraphStyle 段落样式

func (*ContentParagraphStyle) ToV1 added in v1.0.21

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

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

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

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

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

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

func (ParagraphElementType) ToV1 added in v1.0.21

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 (ParagraphElementTypeV1) ToV2 added in v1.0.21

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

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

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