Ctrl K
ComunidadGuías…

internal/task/schedule_list.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package task

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"sort"
	"strconv"
	"strings"
	"time"
)

const (
	scheduleListReadPageSize = 200
	scheduleListDefaultLimit = 50
	scheduleListMaximumLimit = 100
)

var ErrScheduleListCursor = errors.New("cursor is not one a previous schedule_list result gave")

type ScheduleListInput struct {
	Status string `json:"status"`
	Limit  int    `json:"limit"`
	Cursor string `json:"cursor"`
}

type ScheduleListOutput struct {
	Schedules  []ScheduleListItem `json:"schedules"`
	TotalCount int                `json:"totalCount"`
	NextCursor string             `json:"nextCursor,omitempty"`
}

type ScheduleListItem struct {
	ScheduleID      string     `json:"scheduleID"`
	TaskInstruction string     `json:"taskInstruction"`
	Description     string     `json:"description,omitempty"`
	Cadence         string     `json:"cadence"`
	CronExpression  string     `json:"cronExpression,omitempty"`
	RunAt           *time.Time `json:"runAt,omitempty"`
	Status          string     `json:"status"`
	NextRunAt       *time.Time `json:"nextRunAt,omitempty"`
	LastRunAt       *time.Time `json:"lastRunAt,omitempty"`
}

type scheduleListPosition struct {
	CreatedAt  time.Time `json:"createdAt"`
	ScheduleID string    `json:"scheduleID"`
}

func ScheduleListQuery(creatorPersonID string, referenceTime time.Time, page int) ScheduleListRequest {
	return ScheduleListRequest{
		CreatorPersonID: strings.TrimSpace(creatorPersonID),
		IncludeExpired:  true,
		Page:            page,
		PageSize:        scheduleListReadPageSize,
		ReferenceTime:   referenceTime,
	}
}

func ProjectScheduleList(schedules []Schedule, input ScheduleListInput, referenceTime time.Time) (ScheduleListOutput, error) {
	after, errorValue := decodedScheduleListPosition(input.Cursor)
	if errorValue != nil {
		return ScheduleListOutput{}, errorValue
	}
	matching := schedulesWithStatus(schedules, strings.TrimSpace(input.Status), referenceTime)
	sortSchedulesNewestFirst(matching)
	remaining := schedulesAfter(matching, after)
	limit := normalizedScheduleListLimit(input.Limit)
	page := remaining[:min(limit, len(remaining))]
	output := ScheduleListOutput{Schedules: []ScheduleListItem{}, TotalCount: len(matching)}
	for _, schedule := range page {
		output.Schedules = append(output.Schedules, scheduleListItemFromSchedule(schedule, referenceTime))
	}
	if len(remaining) > len(page) {
		output.NextCursor = encodedScheduleListPosition(page[len(page)-1])
	}
	return output, nil
}

func schedulesWithStatus(schedules []Schedule, status string, referenceTime time.Time) []Schedule {
	matching := []Schedule{}
	for _, schedule := range schedules {
		if status != "" && scheduleStatus(schedule, referenceTime) != status {
			continue
		}
		matching = append(matching, schedule)
	}
	return matching
}

func sortSchedulesNewestFirst(schedules []Schedule) {
	sort.SliceStable(schedules, func(left, right int) bool {
		return isScheduleListedBefore(schedules[left], scheduleListPosition{CreatedAt: schedules[right].CreatedAt, ScheduleID: schedules[right].ScheduleID})
	})
}

func isScheduleListedBefore(schedule Schedule, position scheduleListPosition) bool {
	if !schedule.CreatedAt.Equal(position.CreatedAt) {
		return schedule.CreatedAt.After(position.CreatedAt)
	}
	return schedule.ScheduleID < position.ScheduleID
}

func schedulesAfter(schedules []Schedule, after *scheduleListPosition) []Schedule {
	if after == nil {
		return schedules
	}
	for index, schedule := range schedules {
		if !isScheduleListedBefore(schedule, *after) && schedule.ScheduleID != after.ScheduleID {
			return schedules[index:]
		}
	}
	return []Schedule{}
}

func encodedScheduleListPosition(schedule Schedule) string {
	encoded, _ := json.Marshal(scheduleListPosition{CreatedAt: schedule.CreatedAt.UTC(), ScheduleID: schedule.ScheduleID})
	return base64.RawURLEncoding.EncodeToString(encoded)
}

func decodedScheduleListPosition(cursor string) (*scheduleListPosition, error) {
	trimmedCursor := strings.TrimSpace(cursor)
	if trimmedCursor == "" {
		return nil, nil
	}
	decoded, errorValue := base64.RawURLEncoding.DecodeString(trimmedCursor)
	if errorValue != nil {
		return nil, ErrScheduleListCursor
	}
	var position scheduleListPosition
	if errorValue := json.Unmarshal(decoded, &position); errorValue != nil || position.ScheduleID == "" || position.CreatedAt.IsZero() {
		return nil, ErrScheduleListCursor
	}
	return &position, nil
}

func normalizedScheduleListLimit(limit int) int {
	if limit <= 0 {
		return scheduleListDefaultLimit
	}
	return min(limit, scheduleListMaximumLimit)
}

func scheduleListItemFromSchedule(schedule Schedule, referenceTime time.Time) ScheduleListItem {
	return ScheduleListItem{
		ScheduleID:      schedule.ScheduleID,
		TaskInstruction: schedule.Prompt,
		Description:     schedule.Name,
		Cadence:         scheduleCadence(schedule),
		CronExpression:  schedule.CronExpression,
		RunAt:           schedule.RunAt,
		Status:          scheduleStatus(schedule, referenceTime),
		NextRunAt:       schedule.NextRunAt,
		LastRunAt:       schedule.LastRunAt,
	}
}

func scheduleCadence(schedule Schedule) string {
	switch schedule.Kind {
	case ScheduleKindInterval:
		return "every " + strconv.Itoa(schedule.IntervalSecond) + " seconds"
	case ScheduleKindCron:
		return "cron"
	default:
		return "once"
	}
}

func scheduleStatus(schedule Schedule, referenceTime time.Time) string {
	if schedule.NextRunAt == nil || schedule.ExpiresAt != nil && !schedule.ExpiresAt.After(referenceTime) {
		return "expired"
	}
	if strings.TrimSpace(schedule.LastError) != "" {
		return "failed"
	}
	return "active"
}