Verified Commit fbcaeb73 authored by Hamza Hassanain's avatar Hamza Hassanain 💬 Committed by GitLab
Browse files

feat(workitems): add json tags to WorkItem to render the JSON shape explicit

Changelog: Improvements
parent 7f1dadc7
Loading
Loading
Loading
Loading
+28 −28
Changes for workitems.go: 28 added lines, 28 removed lines.
Original line number Diff line number Diff line
@@ -44,32 +44,32 @@ var _ WorkItemsServiceInterface = (*WorkItemsService)(nil)
//
// Experimental: The Work Items API is a work in progress and may introduce breaking changes even between minor versions.
type WorkItem struct {
	ID          int64
	IID         int64
	Type        string
	State       string
	Status      *string
	Title       string
	Description string
	CreatedAt   *time.Time
	UpdatedAt   *time.Time
	ClosedAt    *time.Time
	WebURL      string
	Author      *BasicUser
	Assignees   []*BasicUser
	ID          int64        `json:"id"`
	IID         int64        `json:"iid"`
	Type        string       `json:"type"`
	State       string       `json:"state"`
	Status      *string      `json:"status,omitempty"`
	Title       string       `json:"title"`
	Description string       `json:"description"`
	CreatedAt   *time.Time   `json:"created_at"`
	UpdatedAt   *time.Time   `json:"updated_at"`
	ClosedAt    *time.Time   `json:"closed_at"`
	WebURL      string       `json:"web_url"`
	Author      *BasicUser   `json:"author"`
	Assignees   []*BasicUser `json:"assignees"`

	Color        *string
	Confidential bool
	DueDate      *ISOTime
	HealthStatus *string
	IterationID  *int64
	Labels       []LabelDetails
	LinkedItems  []LinkedWorkItem
	MilestoneID  *int64
	Parent       *WorkItemIID
	Children     []WorkItemIID
	StartDate    *ISOTime
	Weight       *int64
	Color        *string          `json:"color,omitempty"`
	Confidential bool             `json:"confidential"`
	DueDate      *ISOTime         `json:"due_date"`
	HealthStatus *string          `json:"health_status,omitempty"`
	IterationID  *int64           `json:"iteration_id,omitempty"`
	Labels       []LabelDetails   `json:"labels"`
	LinkedItems  []LinkedWorkItem `json:"linked_items"`
	MilestoneID  *int64           `json:"milestone_id"`
	Parent       *WorkItemIID     `json:"parent"`
	Children     []WorkItemIID    `json:"children"`
	StartDate    *ISOTime         `json:"start_date"`
	Weight       *int64           `json:"weight,omitempty"`
}

func (wi WorkItem) GID() string {
@@ -83,8 +83,8 @@ func (wi WorkItem) GID() string {
//
// Experimental: The Work Items API is a work in progress and may introduce breaking changes even between minor versions.
type WorkItemIID struct {
	NamespacePath string
	IID           int64
	NamespacePath string `json:"namespace_path"`
	IID           int64  `json:"iid"`
}

type workItemIIDGQL struct {
@@ -118,7 +118,7 @@ type LinkedWorkItem struct {

	// LinkType is the type of relationship between the work items.
	// Possible values: blocks, is_blocked_by, relates_to
	LinkType string
	LinkType string `json:"link_type"`
}

// WorkItemStateEvent represents a state change event for a work item.
+80 −0
Changes for workitems_test.go: 80 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -1896,3 +1896,83 @@ func TestWorkItemCEAndEEFieldsAreDisjoint(t *testing.T) {
		assert.False(t, isEE, "%q appears in both workItemCEFields and workItemEEFields", f.Name)
	}
}

func TestWorkItem_MarshalJSON_SnakeCaseKeys(t *testing.T) {
	t.Parallel()

	createdAt := time.Date(2026, time.January, 1, 10, 0, 0, 0, time.UTC)
	dueDate := ISOTime(time.Date(2026, time.January, 2, 0, 0, 0, 0, time.UTC))

	wi := WorkItem{
		ID:           1,
		IID:          2,
		Type:         "Issue",
		State:        "OPEN",
		Status:       new("To do"),
		Title:        "Title",
		Description:  "Description",
		CreatedAt:    &createdAt,
		UpdatedAt:    &createdAt,
		WebURL:       "https://gitlab.example.com/group/project/-/work_items/2",
		Author:       &BasicUser{ID: 3, Username: "author"},
		Assignees:    []*BasicUser{{ID: 4, Username: "assignee"}},
		Color:        new("#1068bf"),
		Confidential: true,
		DueDate:      &dueDate,
		HealthStatus: new("onTrack"),
		IterationID:  new(int64(5)),
		Labels:       []LabelDetails{{ID: 6, Name: "bug"}},
		LinkedItems: []LinkedWorkItem{{
			WorkItemIID: WorkItemIID{NamespacePath: "group/project", IID: 7},
			LinkType:    "blocks",
		}},
		MilestoneID: new(int64(8)),
		Parent:      &WorkItemIID{NamespacePath: "group", IID: 9},
		Children:    []WorkItemIID{{NamespacePath: "group/project", IID: 10}},
		StartDate:   &dueDate,
		Weight:      new(int64(3)),
	}

	data, err := json.Marshal(wi)
	require.NoError(t, err)

	var raw map[string]any
	require.NoError(t, json.Unmarshal(data, &raw))

	keys := make([]string, 0, len(raw))
	for k := range raw {
		keys = append(keys, k)
	}
	assert.ElementsMatch(t, []string{
		"id", "iid", "type", "state", "status", "title", "description",
		"created_at", "updated_at", "closed_at", "web_url", "author", "assignees",
		"color", "confidential", "due_date", "health_status", "iteration_id",
		"labels", "linked_items", "milestone_id", "parent", "children",
		"start_date", "weight",
	}, keys)

	assert.Equal(t, "2026-01-02", raw["due_date"])
	assert.Equal(t, map[string]any{"namespace_path": "group", "iid": float64(9)}, raw["parent"])
	assert.Equal(t, []any{
		map[string]any{"namespace_path": "group/project", "iid": float64(10)},
	}, raw["children"])
	assert.Equal(t, []any{
		map[string]any{"namespace_path": "group/project", "iid": float64(7), "link_type": "blocks"},
	}, raw["linked_items"])
}

func TestWorkItem_MarshalJSON_OmitsUnsetEEFields(t *testing.T) {
	t.Parallel()

	data, err := json.Marshal(WorkItem{ID: 1, IID: 2, Title: "CE only"})
	require.NoError(t, err)

	var raw map[string]any
	require.NoError(t, json.Unmarshal(data, &raw))

	for _, key := range []string{"color", "health_status", "iteration_id", "status", "weight"} {
		assert.NotContains(t, raw, key)
	}
	assert.Contains(t, raw, "closed_at")
	assert.Nil(t, raw["closed_at"])
}