Add YouTrack pull/push sync with project settings and time tracking.
Wire manual Y menu and CLI sync for issues and work items, persist per-project credentials, and keep local edits until an explicit push. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+35
-24
@@ -11,6 +11,11 @@ const (
|
||||
SourceManual = "manual"
|
||||
)
|
||||
|
||||
const (
|
||||
// SourceYouTrackTime marks time entries imported from YouTrack work items.
|
||||
SourceYouTrackTime = "youtrack"
|
||||
)
|
||||
|
||||
// TimeEntry is a recorded block of work on a ticket.
|
||||
type TimeEntry struct {
|
||||
ID string `json:"id"`
|
||||
@@ -21,43 +26,48 @@ type TimeEntry struct {
|
||||
Duration time.Duration `json:"duration"`
|
||||
Source string `json:"source"`
|
||||
Note string `json:"note,omitempty"`
|
||||
// ExternalID is the YouTrack work-item id when synced.
|
||||
ExternalID string `json:"externalId,omitempty"`
|
||||
}
|
||||
|
||||
// MarshalJSON encodes Duration as nanoseconds for stable JSON.
|
||||
func (e TimeEntry) MarshalJSON() ([]byte, error) {
|
||||
type alias struct {
|
||||
ID string `json:"id"`
|
||||
TicketID string `json:"ticketId"`
|
||||
ProjectID string `json:"projectId"`
|
||||
Start time.Time `json:"start"`
|
||||
End time.Time `json:"end"`
|
||||
Duration int64 `json:"duration"`
|
||||
Source string `json:"source"`
|
||||
Note string `json:"note,omitempty"`
|
||||
ID string `json:"id"`
|
||||
TicketID string `json:"ticketId"`
|
||||
ProjectID string `json:"projectId"`
|
||||
Start time.Time `json:"start"`
|
||||
End time.Time `json:"end"`
|
||||
Duration int64 `json:"duration"`
|
||||
Source string `json:"source"`
|
||||
Note string `json:"note,omitempty"`
|
||||
ExternalID string `json:"externalId,omitempty"`
|
||||
}
|
||||
return json.Marshal(alias{
|
||||
ID: e.ID,
|
||||
TicketID: e.TicketID,
|
||||
ProjectID: e.ProjectID,
|
||||
Start: e.Start,
|
||||
End: e.End,
|
||||
Duration: int64(e.Duration),
|
||||
Source: e.Source,
|
||||
Note: e.Note,
|
||||
ID: e.ID,
|
||||
TicketID: e.TicketID,
|
||||
ProjectID: e.ProjectID,
|
||||
Start: e.Start,
|
||||
End: e.End,
|
||||
Duration: int64(e.Duration),
|
||||
Source: e.Source,
|
||||
Note: e.Note,
|
||||
ExternalID: e.ExternalID,
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes Duration from nanoseconds.
|
||||
func (e *TimeEntry) UnmarshalJSON(data []byte) error {
|
||||
type alias struct {
|
||||
ID string `json:"id"`
|
||||
TicketID string `json:"ticketId"`
|
||||
ProjectID string `json:"projectId"`
|
||||
Start time.Time `json:"start"`
|
||||
End time.Time `json:"end"`
|
||||
Duration int64 `json:"duration"`
|
||||
Source string `json:"source"`
|
||||
Note string `json:"note,omitempty"`
|
||||
ID string `json:"id"`
|
||||
TicketID string `json:"ticketId"`
|
||||
ProjectID string `json:"projectId"`
|
||||
Start time.Time `json:"start"`
|
||||
End time.Time `json:"end"`
|
||||
Duration int64 `json:"duration"`
|
||||
Source string `json:"source"`
|
||||
Note string `json:"note,omitempty"`
|
||||
ExternalID string `json:"externalId,omitempty"`
|
||||
}
|
||||
var a alias
|
||||
if err := json.Unmarshal(data, &a); err != nil {
|
||||
@@ -71,6 +81,7 @@ func (e *TimeEntry) UnmarshalJSON(data []byte) error {
|
||||
e.Duration = time.Duration(a.Duration)
|
||||
e.Source = a.Source
|
||||
e.Note = a.Note
|
||||
e.ExternalID = a.ExternalID
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user