Documentation
¶
Overview ¶
Package migrate exports a mirror into an issuetap fixture document — the seed a fresh local-origin workspace loads one-shot (origin/issuetap.yaml, GDK-1264). Reads are mirror-only; the single origin round-trip is the attachment byte download, and the caller owns that client.
The document shapes mirror issuetap's internal/fixtures.Doc YAML contract (that package is internal, so the structs are declared here). Three rules came out of the GDK-1262 spike and must not regress:
- priorities emit in id order — issuetap assigns priority_rank by catalog position, so encounter order flips the ranking.
- links emit each issue's own mirror rows verbatim, both directions — the fixture load is single-sided (runtime AddIssueLink is what materializes both projections), so a pair exists on both ends only if both ends declare it.
- the status catalog includes every status id the changelog references, not just the ones issues currently sit in.
Index ¶
- Constants
- func Build(ctx context.Context, db *sql.DB, opt Options) (*Doc, *Stats, error)
- func InlineAttachments(ctx context.Context, doc *Doc, fetch Fetch, st *Stats)
- type Attachment
- type Comment
- type Doc
- type Fetch
- type History
- type HistoryItem
- type Issue
- type IssueType
- type LinearOptions
- type LinearReport
- type Link
- type Options
- type Page
- type PageComment
- type Priority
- type Project
- type Space
- type Stats
- type Status
- type User
- type VerifyRow
Constants ¶
const MaxAttachmentBytes = 16 << 20
MaxAttachmentBytes caps one inlined file. The fixture is a YAML document read into memory; a file past this stays metadata-only and is reported.
const MigrateLabel = "gadak-migrate"
MigrateLabel marks every issue this path creates — the workspace-side signal that a row came from a migration, independent of the footer.
Variables ¶
This section is empty.
Functions ¶
func Build ¶
Build reads the mirror and assembles the fixture document. db must be a mirror connection (read-only is fine); nothing is written.
func InlineAttachments ¶
InlineAttachments downloads each attachment's bytes and inlines them into the document — printable text/* as Text, anything else as std base64. A missing file (404) or an oversized one keeps its metadata row and is counted, never fatal: a partial archive that says what is missing beats no archive.
Types ¶
type Attachment ¶
type Attachment struct {
Filename string `json:"filename"`
MimeType string `json:"mimeType,omitempty"`
Text string `json:"text,omitempty"`
DataBase64 string `json:"dataBase64,omitempty"`
Author string `json:"author,omitempty"`
Created string `json:"created,omitempty"`
// ContentID is the origin content id for the byte download
// (external_id when set, else the store row id — the same rule as
// store.AttachmentOrigin). Never emitted.
ContentID string `json:"-"`
// Size is the mirror's byte count, used to skip oversized files
// before spending the download. Never emitted.
Size int64 `json:"-"`
// SourceURL is the mirror's stored origin content URL (non-Jira
// sources). Non-empty means the bytes do not live behind Jira's
// /attachment/content/{id} and this pass skips them. Never emitted.
SourceURL string `json:"-"`
}
type Doc ¶
type Doc struct {
Users []User `json:"users,omitempty"`
Projects []Project `json:"projects,omitempty"`
Statuses []Status `json:"statuses,omitempty"`
Priorities []Priority `json:"priorities,omitempty"`
IssueTypes []IssueType `json:"issueTypes,omitempty"`
Issues []Issue `json:"issues,omitempty"`
Spaces []Space `json:"spaces,omitempty"`
Pages []Page `json:"pages,omitempty"`
}
Doc is the fixture document. Key names follow issuetap's internal/fixtures.Doc (its json and yaml tags agree); only the parts a mirror can fill are declared. Emitted as JSON — see cmdMigrate for why the seed is never yaml.Marshal'd.
type Fetch ¶
Fetch downloads one attachment's bytes by content id. Implemented by the caller over the source origin client (Jira's and issuetap's /attachment/content/{id} are the same shape). A 404 returns status 404 with err nil.
type History ¶
type History struct {
At string `json:"at"`
Author string `json:"author,omitempty"`
Items []HistoryItem `json:"items"`
}
type HistoryItem ¶
type Issue ¶
type Issue struct {
Key string `json:"key"`
Summary string `json:"summary"`
Description string `json:"description,omitempty"`
Project string `json:"project,omitempty"`
Type string `json:"type,omitempty"` // id
Status string `json:"status,omitempty"` // id
Priority string `json:"priority,omitempty"`
Assignee string `json:"assignee,omitempty"`
Reporter string `json:"reporter,omitempty"`
Parent string `json:"parent,omitempty"`
Labels []string `json:"labels,omitempty"`
Components []string `json:"components,omitempty"`
FixVersions []string `json:"fixVersions,omitempty"`
Duedate string `json:"duedate,omitempty"`
Resolution string `json:"resolution,omitempty"`
Created string `json:"created,omitempty"`
Updated string `json:"updated,omitempty"`
Comments []Comment `json:"comments,omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
Links []Link `json:"links,omitempty"`
History []History `json:"history,omitempty"`
// StatusCategory (new|inprogress|done) and PriorityRank are the
// mirror's contract axes — what a target with its own catalogs (Linear)
// maps from, since ids mean nothing there. Never emitted: the fixture
// path carries the ids and rebuilds both from its catalogs.
StatusCategory string `json:"-"`
PriorityRank int `json:"-"`
// AssigneeEmail is the row's own email column — the fallback when the
// users catalog has no row for the account (scrubbed fixtures).
AssigneeEmail string `json:"-"`
}
type LinearOptions ¶ added in v0.20.0
type LinearOptions struct {
TeamKey string
// Limit keeps the first N issues by key (0 = all); references to the
// cut-off rest are dropped and counted like out-of-set ones.
Limit int
// DryRun computes the mapping and counts without one network call.
DryRun bool
// Progress receives one line per created issue ("NMS-1 → MID-42").
Progress io.Writer
// AttachmentURL turns a Jira attachment content id into the URL a
// browser session at the source can open; nil or "" skips the link.
AttachmentURL func(contentID string) string
}
LinearOptions scopes ToLinear.
type LinearReport ¶ added in v0.20.0
type LinearReport struct {
Team string `json:"team"`
DryRun bool `json:"dry_run"`
Counts []VerifyRow `json:"counts"`
Mapping []string `json:"mapping"`
NotMigrated []string `json:"not_migrated"`
Warnings []string `json:"warnings,omitempty"`
}
LinearReport is the run's honest half: counts, the mapping applied, and what did not travel. Counts reuse VerifyRow with Skipped = already present at the target before this run.
func ToLinear ¶ added in v0.20.0
func ToLinear(ctx context.Context, client *linear.Client, doc *Doc, st *Stats, opt LinearOptions) (*LinearReport, error)
ToLinear emits doc into the team opt.TeamKey through client. st is the Build stats (for the not-migrated rows). Only doc.Issues travel; pages have no Linear counterpart.
type Page ¶
type Page struct {
ID string `json:"id,omitempty"`
Title string `json:"title"`
Space string `json:"space"`
Version int `json:"version,omitempty"`
When string `json:"when,omitempty"`
Author string `json:"author,omitempty"`
Body string `json:"body,omitempty"`
Labels []string `json:"labels,omitempty"`
Parent string `json:"parent,omitempty"`
Comments []PageComment `json:"comments,omitempty"`
}
type PageComment ¶
type Stats ¶
type Stats struct {
Projects []string
Spaces []string
Issues int
Comments int
Attachments int
Links int
History int
Pages int
PageComments int
Users int
// Derived columns the target must reproduce from the migrated
// changelog (they are never stored in the fixture).
ReopenSum int
EpicKeys int
// Formatting nodes flattened to plain text by the fixture path
// (descriptions, comments and page bodies all load as adf.Doc(text)).
LossCodeBlock int
LossMedia int
LossTable int
// Dropped because the other end is outside the migrated set.
DroppedLinks int
DroppedParents []string
DroppedPageParents int
// Not migrated (the fixture has no slot, or out of scope).
DevLinks int
CustomIssues int
SprintIssues int
// Attachment byte pass.
AttachInlined int
AttachMissing int // origin answered 404 — metadata kept
AttachTooLarge int
AttachSkipURL int // stored origin URL (non-Jira source) — out of scope
AttachErrors []string
MissingUsers []string // referenced account ids absent from the users catalog
UnnamedStatuses []string // history-only status ids with no display name
}
Stats is the honest half of the export: what was counted, what was flattened, what was dropped and why. The verification report is built from it — silent loss and reported loss are different products.
type VerifyRow ¶
type VerifyRow struct {
Metric string `json:"metric"`
Source int `json:"source"`
Migrated int `json:"migrated"`
// Skipped is the part of Migrated that was already at the target
// before this run (the Linear path's idempotency scan); zero on the
// built-in-tracker path, where the target is always new.
Skipped int `json:"skipped,omitempty"`
}
VerifyRow is one line of the migration report: a metric counted on the source mirror at export time against the same metric on the freshly filled target mirror.
func VerifyMirror ¶
VerifyMirror re-counts the exported axes on the target mirror. The derived rows (reopens, epic keys) matter most: they are never stored in the fixture, so equality proves the migrated changelog reproduces them.