Verified Commit 9457ddc6 authored by Dmitry Gruzd's avatar Dmitry Gruzd 2️⃣ Committed by GitLab
Browse files

fix: add FormattedText field to OrbitGraphStatus and OrbitStatus

Changelog: Improvements
parent 1fef93fd
Loading
Loading
Loading
Loading
+28 −6
Changes for orbit.go: 28 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -39,7 +39,7 @@ type (
		//
		// GitLab API docs:
		// https://docs.gitlab.com/api/orbit/#get-status
		GetStatus(options ...RequestOptionFunc) (*OrbitStatus, *Response, error)
		GetStatus(opt *GetOrbitStatusOptions, options ...RequestOptionFunc) (*OrbitStatus, *Response, error)

		// GetSchema returns the Orbit graph ontology (domains, nodes,
		// edges).
@@ -103,11 +103,26 @@ type (

var _ OrbitServiceInterface = (*OrbitService)(nil)

// GetOrbitStatusOptions represents the available GetStatus() options.
//
// GitLab API docs: https://docs.gitlab.com/api/orbit/#get-status
type GetOrbitStatusOptions struct {
	// ResponseFormat selects the response shape: "raw" (structured
	// JSON, default) or "llm" (compact text for LLM consumption).
	ResponseFormat *OrbitResponseFormatValue `url:"response_format,omitempty"`
}

// OrbitStatus represents the Orbit cluster health response returned
// by `GET /api/v4/orbit/status` with `format=raw`.
// by `GET /api/v4/orbit/status`.
//
// When ResponseFormat is "raw" (default), the structured fields
// (Status, Timestamp, Version, Components) are populated and
// FormattedText is empty. When ResponseFormat is "llm", only
// FormattedText is populated and the structured fields are absent.
//
// GitLab API docs: https://docs.gitlab.com/api/orbit/#get-status
type OrbitStatus struct {
	FormattedText string                  `json:"formatted_text,omitempty"`
	Status        string                  `json:"status,omitempty"`
	Timestamp     string                  `json:"timestamp,omitempty"`
	Version       string                  `json:"version,omitempty"`
@@ -146,7 +161,7 @@ type GetOrbitSchemaOptions struct {
	// Format selects the response format: `raw` (structured JSON) or
	// `llm` (TOON text optimized for LLM consumption). Defaults to
	// `raw` server-side when omitted.
	Format *string `url:"format,omitempty" json:"format,omitempty"`
	Format *OrbitResponseFormatValue `url:"format,omitempty" json:"format,omitempty"`
}

// OrbitSchema represents the Orbit graph ontology returned by
@@ -252,7 +267,7 @@ type OrbitTool struct {
// GitLab API docs: https://docs.gitlab.com/api/orbit/#post-query
type OrbitQueryRequest struct {
	Query          json.RawMessage           `json:"query"`
	ResponseFormat *string         `json:"response_format,omitempty"`
	ResponseFormat *OrbitResponseFormatValue `json:"response_format,omitempty"`
}

// OrbitQueryResult represents the response returned by
@@ -279,10 +294,11 @@ type OrbitQueryResult struct {
// future versions.
//
// GitLab API docs: https://docs.gitlab.com/api/orbit/#get-status
func (s *OrbitService) GetStatus(options ...RequestOptionFunc) (*OrbitStatus, *Response, error) {
func (s *OrbitService) GetStatus(opt *GetOrbitStatusOptions, options ...RequestOptionFunc) (*OrbitStatus, *Response, error) {
	return do[*OrbitStatus](s.client,
		withMethod(http.MethodGet),
		withPath("orbit/status"),
		withAPIOpts(opt),
		withRequestOpts(options...),
	)
}
@@ -360,15 +376,21 @@ type GetGraphStatusOptions struct {

	// ResponseFormat selects the response shape: "raw" (structured
	// JSON, default) or "llm" (compact text for LLM consumption).
	ResponseFormat *string `url:"response_format,omitempty"`
	ResponseFormat *OrbitResponseFormatValue `url:"response_format,omitempty"`
}

// OrbitGraphStatus represents the indexing status response returned by
// `GET /api/v4/orbit/graph_status`.
//
// When ResponseFormat is "raw" (default), the structured fields
// (Projects, Domains, Indexing) are populated and FormattedText is
// empty. When ResponseFormat is "llm", only FormattedText is populated
// and the structured fields are absent.
//
// GitLab API docs:
// https://docs.gitlab.com/api/orbit/#get-graph-status
type OrbitGraphStatus struct {
	FormattedText string                    `json:"formatted_text,omitempty"`
	Projects      *OrbitGraphStatusProjects `json:"projects,omitempty"`
	Domains       []*OrbitGraphStatusDomain `json:"domains,omitempty"`
	Indexing      *OrbitGraphStatusIndexing `json:"indexing,omitempty"`
+43 −20
Changes for orbit_test.go: 43 added lines, 20 removed lines.
Original line number Diff line number Diff line
@@ -45,8 +45,8 @@ func TestOrbitService_GetStatus(t *testing.T) {
		}`)
	})

	// WHEN GetStatus is called
	status, resp, err := client.Orbit.GetStatus()
	// WHEN GetStatus is called with no options
	status, resp, err := client.Orbit.GetStatus(nil)

	// THEN the typed response matches the server payload
	require.NoError(t, err)
@@ -91,8 +91,8 @@ func TestOrbitService_GetStatus_FeatureFlagOff(t *testing.T) {
		fmt.Fprint(w, `{"error":"404 Not Found"}`)
	})

	// WHEN GetStatus is called
	status, resp, err := client.Orbit.GetStatus()
	// WHEN GetStatus is called with no options
	status, resp, err := client.Orbit.GetStatus(nil)

	// THEN the caller receives the underlying response so it can map 404 to a structured error
	require.Error(t, err)
@@ -101,6 +101,32 @@ func TestOrbitService_GetStatus_FeatureFlagOff(t *testing.T) {
	assert.Nil(t, status)
}

func TestOrbitService_GetStatus_LLMFormat(t *testing.T) {
	t.Parallel()
	// GIVEN the orbit/status endpoint returns the compact LLM text when
	// response_format=llm is requested
	mux, client := setup(t)

	var gotQuery string
	mux.HandleFunc("/api/v4/orbit/status", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		gotQuery = r.URL.RawQuery
		fmt.Fprint(w, `{"formatted_text":"status: healthy\nversion: \"0.5.0\""}`)
	})

	// WHEN GetStatus is called with response_format=llm
	status, _, err := client.Orbit.GetStatus(&GetOrbitStatusOptions{
		ResponseFormat: Ptr(OrbitResponseFormatLLM),
	})

	// THEN the response_format parameter is forwarded and FormattedText is populated
	require.NoError(t, err)
	assert.Contains(t, gotQuery, "response_format=llm")
	require.NotNil(t, status)
	assert.Equal(t, "status: healthy\nversion: \"0.5.0\"", status.FormattedText)
	assert.Empty(t, status.Status, "structured fields must be absent in llm response")
}

func TestOrbitService_GetSchema(t *testing.T) {
	t.Parallel()
	// GIVEN a schema response with two domains and summary node entries
@@ -163,10 +189,9 @@ func TestOrbitService_GetSchema_WithExpand(t *testing.T) {

	// WHEN GetSchema is called with multiple expand nodes and llm format
	expand := []string{"User", "Project", "MergeRequest"}
	format := "llm"
	_, _, err := client.Orbit.GetSchema(&GetOrbitSchemaOptions{
		Expand: &expand,
		Format: &format,
		Format: Ptr(OrbitResponseFormatLLM),
	})

	// THEN expand is comma-joined per API convention and format is set
@@ -247,7 +272,6 @@ func TestOrbitService_Query(t *testing.T) {
	})

	// WHEN Query is called with a typed request whose query is opaque JSON
	format := "raw"
	queryDSL := json.RawMessage(`{
		"query_type": "traversal",
		"node": {"id": "p", "entity": "Project"},
@@ -255,7 +279,7 @@ func TestOrbitService_Query(t *testing.T) {
	}`)
	result, resp, err := client.Orbit.Query(&OrbitQueryRequest{
		Query:          queryDSL,
		ResponseFormat: &format,
		ResponseFormat: Ptr(OrbitResponseFormatRaw),
	})

	// THEN the result envelope decodes and `result` stays as raw JSON
@@ -285,10 +309,9 @@ func TestOrbitService_Query_LLMFormat(t *testing.T) {
	})

	// WHEN Query is called with response_format=llm
	format := "llm"
	result, _, err := client.Orbit.Query(&OrbitQueryRequest{
		Query:          json.RawMessage(`{"query_type": "traversal"}`),
		ResponseFormat: &format,
		ResponseFormat: Ptr(OrbitResponseFormatLLM),
	})

	// THEN the JSON-encoded string is preserved verbatim in Result
@@ -443,31 +466,31 @@ func TestOrbitService_GetGraphStatus_ByFullPath(t *testing.T) {

func TestOrbitService_GetGraphStatus_LLMFormat(t *testing.T) {
	t.Parallel()
	// GIVEN an orbit graph_status endpoint that records the query parameters
	// GIVEN the orbit/graph_status endpoint returns only formatted_text
	// when response_format=llm is requested (structured fields are absent)
	mux, client := setup(t)

	var gotQuery string
	mux.HandleFunc("/api/v4/orbit/graph_status", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		gotQuery = r.URL.RawQuery
		fmt.Fprint(w, `{
			"projects": {"indexed": 2, "total_known": 2},
			"domains": [],
			"indexing": {"state": "indexed"}
		}`)
		fmt.Fprint(w, `{"formatted_text":"projects:\n  indexed: 2\n  total_known: 2\nindexing:\n  state: indexed"}`)
	})

	// WHEN GetGraphStatus is called with response_format=llm
	namespaceID := int64(7)
	format := "llm"
	_, _, err := client.Orbit.GetGraphStatus(&GetGraphStatusOptions{
	status, _, err := client.Orbit.GetGraphStatus(&GetGraphStatusOptions{
		NamespaceID:    &namespaceID,
		ResponseFormat: &format,
		ResponseFormat: Ptr(OrbitResponseFormatLLM),
	})

	// THEN the response_format parameter is forwarded to the server
	// THEN the response_format parameter is forwarded and FormattedText is populated
	require.NoError(t, err)
	assert.Contains(t, gotQuery, "response_format=llm")
	require.NotNil(t, status)
	assert.Equal(t, "projects:\n  indexed: 2\n  total_known: 2\nindexing:\n  state: indexed", status.FormattedText)
	assert.Nil(t, status.Projects, "structured fields must be absent in llm response")
	assert.Nil(t, status.Indexing, "structured fields must be absent in llm response")
}

func TestOrbitService_GetGraphStatus_Forbidden(t *testing.T) {
+7 −6
Changes for testing/orbit_mock.go: 7 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -130,9 +130,9 @@ func (c *MockOrbitServiceInterfaceGetSchemaCall) DoAndReturn(f func(*gitlab.GetO
}

// GetStatus mocks base method.
func (m *MockOrbitServiceInterface) GetStatus(options ...gitlab.RequestOptionFunc) (*gitlab.OrbitStatus, *gitlab.Response, error) {
func (m *MockOrbitServiceInterface) GetStatus(opt *gitlab.GetOrbitStatusOptions, options ...gitlab.RequestOptionFunc) (*gitlab.OrbitStatus, *gitlab.Response, error) {
	m.ctrl.T.Helper()
	varargs := []any{}
	varargs := []any{opt}
	for _, a := range options {
		varargs = append(varargs, a)
	}
@@ -144,9 +144,10 @@ func (m *MockOrbitServiceInterface) GetStatus(options ...gitlab.RequestOptionFun
}

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

@@ -162,13 +163,13 @@ func (c *MockOrbitServiceInterfaceGetStatusCall) Return(arg0 *gitlab.OrbitStatus
}

// Do rewrite *gomock.Call.Do
func (c *MockOrbitServiceInterfaceGetStatusCall) Do(f func(...gitlab.RequestOptionFunc) (*gitlab.OrbitStatus, *gitlab.Response, error)) *MockOrbitServiceInterfaceGetStatusCall {
func (c *MockOrbitServiceInterfaceGetStatusCall) Do(f func(*gitlab.GetOrbitStatusOptions, ...gitlab.RequestOptionFunc) (*gitlab.OrbitStatus, *gitlab.Response, error)) *MockOrbitServiceInterfaceGetStatusCall {
	c.Call = c.Call.Do(f)
	return c
}

// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockOrbitServiceInterfaceGetStatusCall) DoAndReturn(f func(...gitlab.RequestOptionFunc) (*gitlab.OrbitStatus, *gitlab.Response, error)) *MockOrbitServiceInterfaceGetStatusCall {
func (c *MockOrbitServiceInterfaceGetStatusCall) DoAndReturn(f func(*gitlab.GetOrbitStatusOptions, ...gitlab.RequestOptionFunc) (*gitlab.OrbitStatus, *gitlab.Response, error)) *MockOrbitServiceInterfaceGetStatusCall {
	c.Call = c.Call.DoAndReturn(f)
	return c
}
+14 −0
Changes for types.go: 14 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -980,3 +980,17 @@ const (
	DuoAvailabilityDefaultOff DuoAvailabilityValue = "default_off"
	DuoAvailabilityNeverOn    DuoAvailabilityValue = "never_on" // Displayed as "Always Off" in the UI
)

// OrbitResponseFormatValue represents the response format for Orbit
// API endpoints that support multiple output shapes.
//
// GitLab API docs: https://docs.gitlab.com/api/orbit/
type OrbitResponseFormatValue string

// List of available Orbit response format values.
//
// GitLab API docs: https://docs.gitlab.com/api/orbit/
const (
	OrbitResponseFormatRaw OrbitResponseFormatValue = "raw"
	OrbitResponseFormatLLM OrbitResponseFormatValue = "llm"
)