app

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package app resolves the selected project and names into ids and computes minimal writes for the curated commands. It talks to Taiga only through the narrow API interface.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FieldPath

func FieldPath(kind string) (string, error)

FieldPath is the definitions endpoint of a kind of resource.

func ID

func ID(v any) int64

ID returns v as a positive id, or 0 when it is not an integer.

func MergeIDs

func MergeIDs(current, add, remove []int64) []int64

MergeIDs returns current ∪ add − remove, keeping order and dropping duplicates. It never returns nil, so removing the last id sends [].

func MergeNames

func MergeNames(current, add, remove []string) []string

MergeNames returns current ∪ add − remove, keeping order and dropping duplicates.

func Names

func Names(v any) ([]string, error)

Names normalizes Taiga tags ([name, color] pairs or plain names) to names.

func OrderStatuses

func OrderStatuses(current []string, specs []StatusSpec) ([]string, error)

OrderStatuses returns the desired order of every status: current ones (in their order) plus the declared ones that do not exist yet, appended in file order, each declaration with After moved right behind its predecessor. Statuses after the same predecessor keep the file order. An unknown predecessor or a cycle is an error.

func ParseFieldValue

func ParseFieldValue(typ, raw string) (any, error)

ParseFieldValue converts raw to the JSON value of a field of type typ. Taiga does not validate values (docs/api-notes.md), so the CLI does. An assignment never unsets a field: "null" is text, and an empty date is refused; unsetting takes UnsetFieldValue.

func PreviewProject

func PreviewProject(plan ProjectPlan, statuses []Object, projectID any) ([]WritePlan, []Action, error)

PreviewProject materializes the requests of plan without sending anything. statuses is the read that produced plan. New statuses get an order after the current ones, in file order.

func Snapshot

func Snapshot(o Object) (map[string]json.RawMessage, error)

Snapshot converts an object read from Taiga back to raw JSON, for WriteVersionedFrom.

func SortStatuses

func SortStatuses(statuses []Object)

SortStatuses orders statuses as Taiga does (order, then name), numerically.

func SystemComment

func SystemComment(o Object) bool

SystemComment reports a history entry written by Taiga's own GitLab integration for a push hook: the inactive gitlab-<hash> user and one of the hook's comment templates (taiga/hooks/event_hooks.py). Both must match. A service account, a human quoting the template, a diff without text or an unknown author are never system comments: agents publish human comments with integration accounts (docs/api-notes.md).

func TagName

func TagName(s string) string

TagName is how Taiga stores a tag: lower case (observed in the local probe).

func UnsetFieldValue

func UnsetFieldValue(typ, name string) error

UnsetFieldValue checks that a field of type typ can be cleared.

func Unsupported

func Unsupported(cause, recovery string) error

Unsupported reports a flag whose Taiga contract the CLI cannot honour safely.

func Usage

func Usage(cause string) error

func ValidateField

func ValidateField(name, typ string) error

ValidateField checks a definition before any request. Taiga limits names to 64 characters; "=" is refused because Name=value assignments end the name at the first "=".

func ValuePath

func ValuePath(kind string, id int64) (string, error)

ValuePath is the custom-attributes-values resource of a story or task. It has its own version, independent of the story's or task's.

func WriteApplied

func WriteApplied(method, path string, status int, readErr error) error

WriteApplied reports a write confirmed by its HTTP status whose result could not be read. It exits 1, not 7: scripts that retry network errors must not repeat an applied write.

Types

type API

type API interface {
	Do(context.Context, taiga.Request) (*taiga.Response, error)
	GetAll(context.Context, string, url.Values) ([]json.RawMessage, error)
	WriteVersioned(context.Context, string, string, map[string]any, bool) (*taiga.Response, error)
	WriteVersionedFrom(context.Context, string, string, map[string]any, map[string]json.RawMessage, bool) (*taiga.Response, error)
	BaseURL() string
}

API is the part of *taiga.Client the services use.

type Action

type Action struct {
	Kind string `json:"kind"`
	Name string `json:"name"`
	Body Object `json:"body"`
}

Action is one semantic step of a project plan; new statuses have no id or version yet.

type ApplyResult

type ApplyResult struct {
	Plan      ProjectPlan `json:"plan"`
	Applied   []Action    `json:"applied"`
	Remaining []Action    `json:"remaining"`
	Complete  bool        `json:"complete"`
	Requests  []WritePlan `json:"requests"`
	Deferred  []Action    `json:"deferred"`
}

ApplyResult is what project apply did. Applied and Remaining describe a partial run; Complete is set only after a new read finds nothing left to do. Requests is the dry-run preview.

func Apply

func Apply(ctx context.Context, writer StatusWriter, spec ProjectSpec, dry bool) (ApplyResult, error)

Apply validates everything (permission, drift, reorder support) before the first write, then runs the plan in order. A failure stops it: nothing is undone and nothing is repeated, and the result says what was applied and what remains. Running it again re-reads and re-plans.

type FieldSpec

type FieldSpec struct {
	Name        string `toml:"name"`
	Type        string `toml:"type"`
	Description string `toml:"description"`
}

FieldSpec declares a story custom field; an absent description means "".

type Object

type Object map[string]any

Object is a decoded Taiga object; numbers stay json.Number and unknown keys are kept.

func AssignmentPatch

func AssignmentPatch(before Object, p Patch) (Object, error)

AssignmentPatch returns the assignee and block fields that differ from before.

Taiga 6.7 answers assigned_users as the stored list plus assigned_to (docs/api-notes.md), so: removing the owner without changing assigned_to is refused, because Taiga would keep showing them; and any change of assigned_to also sends the full list, because the previous owner may not be in the stored list and would silently disappear.

func BuildPatch

func BuildPatch(before Object, p Patch) (Object, error)

BuildPatch returns only the fields that differ from before.

func Decode

func Decode(b []byte) (Object, error)

func FieldsView

func FieldsView(values Object, defs []Object) []Object

FieldsView binds the values to their definitions: each definition gets the stored value under "value" when there is one. Ids without a definition stay in attributes_values.

func MergeValues

func MergeValues(current Object, updates Object) Object

MergeValues returns current with updates applied; keys not in updates are kept as read.

func Read

func Read(ctx context.Context, api API, path string, q url.Values) (Object, error)

func Resolve

func Resolve(items []Object, selector, nameKey string) (Object, error)

Resolve finds exactly one entry whose nameKey or id equals selector.

type Patch

type Patch struct {
	Set                           Object
	AddTags, RemoveTags           []string
	Append                        *string
	AddAssignees, RemoveAssignees []int64
	Owner                         *int64
	ClearOwner                    bool
	Block                         *string
	Unblock                       bool
}

Patch records which changes were asked for; absent fields stay untouched.

type ProjectPlan

type ProjectPlan struct {
	Actions   []Action `json:"actions"`
	Unmanaged []Object `json:"unmanaged"`
	Drift     []Object `json:"drift"`
}

ProjectPlan is what apply would do. Unmanaged lists what exists and the file does not declare (kept as is); Drift lists declared definitions that exist with other values, which apply refuses instead of updating.

func BuildProjectPlan

func BuildProjectPlan(spec ProjectSpec, statuses, fields []Object) (ProjectPlan, error)

BuildProjectPlan compares spec with the statuses (already sorted by SortStatuses) and story field definitions read from Taiga. It never plans an update or a removal.

type ProjectSpec

type ProjectSpec struct {
	StoryStatus []StatusSpec `toml:"story_status"`
	StoryField  []FieldSpec  `toml:"story_field"`
}

ProjectSpec is the project TOML: only what it declares is managed, nothing is removed.

func ParseProjectSpec

func ParseProjectSpec(r io.Reader) (ProjectSpec, error)

ParseProjectSpec decodes the project TOML strictly (unknown keys are errors) and validates every declaration before anything is read from Taiga.

type Service

type Service struct {
	API     API
	Project Object
	// contains filtered or unexported fields
}

Service is created per command run; catalogs are cached only for that run.

func New

func New(ctx context.Context, api API, project string) (*Service, error)

New resolves project (slug, or numeric id) and returns a service bound to it.

func (*Service) ApplyProject

func (s *Service) ApplyProject(ctx context.Context, spec ProjectSpec, dry bool) (ApplyResult, error)

ApplyProject runs Apply against Taiga.

func (*Service) Catalog

func (s *Service) Catalog(ctx context.Context, path string) ([]Object, error)

Catalog lists path for the project once per run. Entries that declare another project are dropped: a query parameter the server ignores must not widen the scope.

func (*Service) CloseStory

func (s *Service) CloseStory(ctx context.Context, ref, selector string, dry, force bool) (any, error)

CloseStory moves the story to a closed status: the given one, or the project's only closed status. A story already closed is left alone unless another closed status is named.

func (*Service) Comment

func (s *Service) Comment(ctx context.Context, ref, body string, dry bool) (any, error)

Comment publishes body on a story with PATCH {comment, version} and returns the story read again. Taiga's OCC never refuses a comment (it is not a field of the story, so an old version is accepted; docs/api-notes.md), so the version protects nothing and the PATCH is sent once, never repeated. When its outcome is unknown (network error after the connection opened, or 5xx), the CLI compares our comments with this exact text in the history before and after: a new one means published; otherwise the outcome stays unknown (comment_unconfirmed, exit 1).

func (*Service) Comments

func (s *Service) Comments(ctx context.Context, ref string, includeSystem bool) ([]Object, error)

Comments lists the comments of a story, newest first as Taiga sends them. Comments of the GitLab integration (SystemComment) are left out unless includeSystem; every entry is marked with is_system and its story. Edited and deleted comments stay, with Taiga's edit_comment_date and delete_comment_date.

func (*Service) CreateField

func (s *Service) CreateField(ctx context.Context, kind, name, typ string, description *string, dry bool) (any, error)

CreateField creates a definition unless one with the same name (case-sensitive, as Taiga compares) already exists: a compatible one is returned unchanged, a different one is an error. Existing definitions are never modified. A nil description is not compared.

func (*Service) CreateStory

func (s *Service) CreateStory(ctx context.Context, body Object, dry bool) (any, error)

CreateStory posts body to the project. There is no previous version: none is sent.

func (*Service) Epic

func (s *Service) Epic(ctx context.Context, ref string) (Object, error)

Epic resolves an epic of the project by ref only: a ref is never an id.

func (*Service) FieldValues

func (s *Service) FieldValues(ctx context.Context, kind string, id int64) (Object, error)

FieldValues reads the values of the story or task id, after checking that it belongs to the selected project. A read error is returned as is, never as empty values.

func (*Service) Fields

func (s *Service) Fields(ctx context.Context, kind string) ([]Object, error)

Fields lists the custom field definitions of the project for kind.

func (*Service) Member

func (s *Service) Member(ctx context.Context, selector string) (Object, error)

Member resolves a project member by exact username, id or "me": the id must appear in the project's memberships.

func (*Service) ProjectPlan

func (s *Service) ProjectPlan(ctx context.Context, spec ProjectSpec) (ProjectPlan, error)

ProjectPlan reads the project's statuses and story fields and compares them with spec. It only reads: no permission is needed beyond seeing the project.

func (*Service) SetFieldValues

func (s *Service) SetFieldValues(ctx context.Context, kind string, id int64, entries, unsets []string, dry, force bool) (any, error)

SetFieldValues merges the Name=value entries into the values of the story or task id, clears the fields named in unsets, and writes the whole dictionary with the version of the values resource. A cleared field keeps its key with null: Taiga refuses an empty dictionary, so the key cannot be dropped. Clearing a field without a stored value adds nothing, because an absent key and null both mean no value. Every entry is resolved and parsed before anything is read or written; an unchanged dictionary writes nothing.

func (*Service) Stories

func (s *Service) Stories(ctx context.Context, filters url.Values) ([]Object, error)

Stories lists every story of the project matching filters (keys: ref, status, closed, search, assignee, epic, tag; ids already resolved). Proven filters go to Taiga to shorten the list; every filter is then checked locally, so an ignored parameter never widens it.

func (*Service) Story

func (s *Service) Story(ctx context.Context, ref string, id int64) (Object, error)

Story reads a story of the selected project by ref (userstories/by_ref) or by id. Tags stay as Taiga sent them, because the object is the baseline of a versioned write.

func (*Service) StoryView

func (s *Service) StoryView(o Object) (Object, error)

StoryView is the output form: tag names instead of pairs, plus the web URL.

func (*Service) UpdateStory

func (s *Service) UpdateStory(ctx context.Context, ref string, p Patch, dry, force bool) (any, error)

func (*Service) User

func (s *Service) User(ctx context.Context, selector string) (Object, error)

User resolves a user by exact username, id or "me" in users?project=, which also lists non-members (probe). Use it only where membership does not matter, like removing an assignee.

func (*Service) Write

func (s *Service) Write(ctx context.Context, path string, before, patch Object, dry, force bool) (any, error)

Write sends patch against before (the read used to compute it) and re-reads the resource. An empty patch writes nothing and returns before.

type StatusSpec

type StatusSpec struct {
	Name   string `toml:"name"`
	Color  string `toml:"color"`
	Closed bool   `toml:"closed"`
	After  string `toml:"after"`
}

StatusSpec declares a story status. Closed defaults to false; After names the status it follows (existing or declared in the same file).

type StatusWriter

type StatusWriter interface {
	CheckPermission(context.Context) error
	ProjectID() any
	Load(context.Context) (statuses, fields []Object, err error)
	CheckReorderSupport(context.Context) error
	CreateStatus(context.Context, Object) error
	CreateField(context.Context, Object) error
	Reorder(context.Context, []string) error
}

StatusWriter is the Taiga side of apply; its contracts are in docs/api-notes.md.

type WritePlan

type WritePlan struct {
	DryRun bool   `json:"dry_run"`
	Method string `json:"method"`
	Path   string `json:"path"`
	Body   Object `json:"body"`
}

WritePlan is what --dry-run prints instead of sending.

Jump to

Keyboard shortcuts

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