Verified Commit e71cb994 authored by Emmanuel 326's avatar Emmanuel 326 💬 Committed by GitLab
Browse files

feat(work-items): add ListWorkItemTypes to WorkItemsService

Changelog: Improvements
parent 141aecc6
Loading
Loading
Loading
Loading
+45 −0
Original line number Diff line number Diff line
@@ -173,6 +173,51 @@ func (c *MockWorkItemsServiceInterfaceGetWorkItemCall) DoAndReturn(f func(string
	return c
}

// ListWorkItemTypes mocks base method.
func (m *MockWorkItemsServiceInterface) ListWorkItemTypes(namespacePath string, opt *gitlab.ListWorkItemTypesOptions, options ...gitlab.RequestOptionFunc) ([]gitlab.WorkItemType, *gitlab.Response, error) {
	m.ctrl.T.Helper()
	varargs := []any{namespacePath, opt}
	for _, a := range options {
		varargs = append(varargs, a)
	}
	ret := m.ctrl.Call(m, "ListWorkItemTypes", varargs...)
	ret0, _ := ret[0].([]gitlab.WorkItemType)
	ret1, _ := ret[1].(*gitlab.Response)
	ret2, _ := ret[2].(error)
	return ret0, ret1, ret2
}

// ListWorkItemTypes indicates an expected call of ListWorkItemTypes.
func (mr *MockWorkItemsServiceInterfaceMockRecorder) ListWorkItemTypes(namespacePath, opt any, options ...any) *MockWorkItemsServiceInterfaceListWorkItemTypesCall {
	mr.mock.ctrl.T.Helper()
	varargs := append([]any{namespacePath, opt}, options...)
	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListWorkItemTypes", reflect.TypeOf((*MockWorkItemsServiceInterface)(nil).ListWorkItemTypes), varargs...)
	return &MockWorkItemsServiceInterfaceListWorkItemTypesCall{Call: call}
}

// MockWorkItemsServiceInterfaceListWorkItemTypesCall wrap *gomock.Call
type MockWorkItemsServiceInterfaceListWorkItemTypesCall struct {
	*gomock.Call
}

// Return rewrite *gomock.Call.Return
func (c *MockWorkItemsServiceInterfaceListWorkItemTypesCall) Return(arg0 []gitlab.WorkItemType, arg1 *gitlab.Response, arg2 error) *MockWorkItemsServiceInterfaceListWorkItemTypesCall {
	c.Call = c.Call.Return(arg0, arg1, arg2)
	return c
}

// Do rewrite *gomock.Call.Do
func (c *MockWorkItemsServiceInterfaceListWorkItemTypesCall) Do(f func(string, *gitlab.ListWorkItemTypesOptions, ...gitlab.RequestOptionFunc) ([]gitlab.WorkItemType, *gitlab.Response, error)) *MockWorkItemsServiceInterfaceListWorkItemTypesCall {
	c.Call = c.Call.Do(f)
	return c
}

// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockWorkItemsServiceInterfaceListWorkItemTypesCall) DoAndReturn(f func(string, *gitlab.ListWorkItemTypesOptions, ...gitlab.RequestOptionFunc) ([]gitlab.WorkItemType, *gitlab.Response, error)) *MockWorkItemsServiceInterfaceListWorkItemTypesCall {
	c.Call = c.Call.DoAndReturn(f)
	return c
}

// ListWorkItems mocks base method.
func (m *MockWorkItemsServiceInterface) ListWorkItems(fullPath string, opt *gitlab.ListWorkItemsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.WorkItem, *gitlab.Response, error) {
	m.ctrl.T.Helper()
+124 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ type (
		ListWorkItems(fullPath string, opt *ListWorkItemsOptions, options ...RequestOptionFunc) ([]*WorkItem, *Response, error)
		UpdateWorkItem(fullPath string, iid int64, opt *UpdateWorkItemOptions, options ...RequestOptionFunc) (*WorkItem, *Response, error)
		DeleteWorkItem(fullPath string, iid int64, options ...RequestOptionFunc) (*Response, error)
		ListWorkItemTypes(namespacePath string, opt *ListWorkItemTypesOptions, options ...RequestOptionFunc) ([]WorkItemType, *Response, error)
	}

	// WorkItemsService handles communication with the work item related methods
@@ -532,6 +533,103 @@ func (s *WorkItemsService) ListWorkItems(fullPath string, opt *ListWorkItemsOpti
	return ret, resp, nil
}

var listWorkItemTypesTemplate = template.Must(template.New("listWorkItemTypes").Parse(`
query ListWorkItemTypes(
  $namespacePath: ID!,
  $name: IssueType,
  $onlyAvailable: Boolean,
  $after: String,
  $before: String,
  $first: Int,
  $last: Int
) {
  namespace(fullPath: $namespacePath) {
    workItemTypes(
      name: $name,
      onlyAvailable: $onlyAvailable,
      after: $after,
      before: $before,
      first: $first,
      last: $last
    ) {
      nodes {
        id
        name
        enabled
      }
      pageInfo {
        endCursor
        hasNextPage
        startCursor
        hasPreviousPage
      }
    }
  }
}
`))

// ListWorkItemTypes lists all work item types (system-defined and custom)
// for a given namespace.
//
// GitLab API docs: https://docs.gitlab.com/api/graphql/reference/#workitemtype
//
// Experimental: The Work Items API is a work in progress and may introduce
// breaking changes even between minor versions.
func (s *WorkItemsService) ListWorkItemTypes(
	namespacePath string,
	opt *ListWorkItemTypesOptions,
	options ...RequestOptionFunc,
) ([]WorkItemType, *Response, error) {
	var queryBuilder strings.Builder

	if opt == nil {
		opt = &ListWorkItemTypesOptions{}
	}
	if err := listWorkItemTypesTemplate.Execute(&queryBuilder, nil); err != nil {
		return nil, nil, err
	}

	vars := map[string]any{
		"namespacePath": namespacePath,
		"name":          opt.Name,
		"onlyAvailable": opt.OnlyAvailable,
		"after":         opt.After,
		"before":        opt.Before,
		"first":         opt.First,
		"last":          opt.Last,
	}

	query := GraphQLQuery{
		Query:     queryBuilder.String(),
		Variables: vars,
	}

	var result struct {
		Data struct {
			Namespace struct {
				WorkItemTypes connectionGQL[WorkItemType] `json:"workItemTypes"`
			} `json:"namespace"`
		}
		GenericGraphQLErrors
	}

	resp, err := s.client.GraphQL.Do(query, &result, options...)
	if err != nil {
		return nil, resp, err
	}

	if len(result.Errors) != 0 {
		return nil, resp, &GraphQLResponseError{
			Err:    errors.New("GraphQL query failed"),
			Errors: result.GenericGraphQLErrors,
		}
	}

	resp.PageInfo = &result.Data.Namespace.WorkItemTypes.PageInfo

	return result.Data.Namespace.WorkItemTypes.Nodes, resp, nil
}

// CreateWorkItemOptions represents the available CreateWorkItem() options.
//
// GitLab API docs:
@@ -1538,6 +1636,32 @@ func (w *workItemWidgetWeightGQL) unwrap() *int64 {
	return w.Weight
}

// WorkItemType represents a GitLab work item type.
//
// GitLab API docs: https://docs.gitlab.com/api/graphql/reference/#workitemtype
//
// Experimental: The Work Items API is a work in progress and may introduce
// breaking changes even between minor versions.
type WorkItemType struct {
	ID      WorkItemTypeID `json:"id"`
	Name    string         `json:"name"`
	Enabled bool           `json:"enabled"`
}

// ListWorkItemTypesOptions specifies the optional parameters to the
// WorkItemsService.ListWorkItemTypes method.
//
// Experimental: The Work Items API is a work in progress and may introduce
// breaking changes even between minor versions.
type ListWorkItemTypesOptions struct {
	Name          *string `json:"name,omitempty"`
	OnlyAvailable *bool   `json:"onlyAvailable,omitempty"`
	After         *string `json:"after,omitempty"`
	Before        *string `json:"before,omitempty"`
	First         *int64  `json:"first,omitempty"`
	Last          *int64  `json:"last,omitempty"`
}

// WorkItemTypeID represents the global ID of a work item type.
//
// GitLab API docs: https://docs.gitlab.com/api/graphql/reference/#workitemtype
+148 −0
Original line number Diff line number Diff line
@@ -1424,3 +1424,151 @@ func validateSchema(schema *graphql.Schema, query GraphQLQuery) error {

	return errs
}

func setupWorkItemTypesHandler(t *testing.T, mux *http.ServeMux, schema *graphql.Schema, response io.Reader) {
	t.Helper()
	mux.HandleFunc("/api/graphql", func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()

		testMethod(t, r, http.MethodPost)

		var q GraphQLQuery
		if err := json.NewDecoder(r.Body).Decode(&q); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		if err := validateSchema(schema, q); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		_, _ = io.Copy(w, response)
	})
}

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

	schema := loadSchema(t)
	mux, client := setup(t)

	setupWorkItemTypesHandler(t, mux, schema, strings.NewReader(`{
		"data": {
			"namespace": {
				"workItemTypes": {
					"nodes": [
						{"id": "gid://gitlab/WorkItems::Type/1",  "name": "Issue",      "enabled": true},
						{"id": "gid://gitlab/WorkItems::Type/5",  "name": "Task",       "enabled": true},
						{"id": "gid://gitlab/WorkItems::Type/99", "name": "CustomType", "enabled": true}
					],
					"pageInfo": {
						"endCursor": "cursor123", "hasNextPage": false,
						"startCursor": "cursor000", "hasPreviousPage": false
					}
				}
			}
		}
	}`))

	got, resp, err := client.WorkItems.ListWorkItemTypes("gitlab-org/gitlab", &ListWorkItemTypesOptions{})

	require.NoError(t, err)
	assert.Equal(t, []WorkItemType{
		{ID: WorkItemTypeIssue, Name: "Issue", Enabled: true},
		{ID: WorkItemTypeTask, Name: "Task", Enabled: true},
		{ID: "gid://gitlab/WorkItems::Type/99", Name: "CustomType", Enabled: true},
	}, got)
	assert.Equal(t, &PageInfo{
		EndCursor:       "cursor123",
		HasNextPage:     false,
		StartCursor:     "cursor000",
		HasPreviousPage: false,
	}, resp.PageInfo)
}

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

	schema := loadSchema(t)
	mux, client := setup(t)

	setupWorkItemTypesHandler(t, mux, schema, strings.NewReader(`{
		"data": {
			"namespace": {
				"workItemTypes": {
					"nodes": [
						{"id": "gid://gitlab/WorkItems::Type/1", "name": "Issue", "enabled": true}
					],
					"pageInfo": {
						"endCursor": "", "hasNextPage": false,
						"startCursor": "", "hasPreviousPage": false
					}
				}
			}
		}
	}`))

	got, _, err := client.WorkItems.ListWorkItemTypes(
		"gitlab-org/gitlab",
		&ListWorkItemTypesOptions{Name: Ptr("ISSUE")},
	)

	require.NoError(t, err)
	assert.Equal(t, []WorkItemType{
		{ID: WorkItemTypeIssue, Name: "Issue", Enabled: true},
	}, got)
}

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

	schema := loadSchema(t)
	mux, client := setup(t)

	setupWorkItemTypesHandler(t, mux, schema, strings.NewReader(`{
		"data": {
			"namespace": {
				"workItemTypes": {
					"nodes": [],
					"pageInfo": {
						"endCursor": "", "hasNextPage": false,
						"startCursor": "", "hasPreviousPage": false
					}
				}
			}
		}
	}`))

	got, _, err := client.WorkItems.ListWorkItemTypes("gitlab-org/gitlab", &ListWorkItemTypesOptions{})

	require.NoError(t, err)
	assert.Equal(t, []WorkItemType{}, got)
}

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

	schema := loadSchema(t)
	mux, client := setup(t)

	setupWorkItemTypesHandler(t, mux, schema, strings.NewReader(`{
		"data": {
			"namespace": {
				"workItemTypes": {
					"nodes": [],
					"pageInfo": {
						"endCursor": "", "hasNextPage": false,
						"startCursor": "", "hasPreviousPage": false
					}
				}
			}
		}
	}`))

	got, _, err := client.WorkItems.ListWorkItemTypes("gitlab-org/gitlab", nil)

	require.NoError(t, err)
	assert.Equal(t, []WorkItemType{}, got)
}