CloudMigrations: Add sorting and error filtering to Snapshot Results backend (#102753)

* implement sorting

* swagger gen

* minor fixes

* clean up param reading

* add todo

* add errors only prop

* codegen stuff

* fix copy paste error

* forgot the api gen

* cleanup

* remove tests that are obe

* fix test
This commit is contained in:
Michael Mandrus
2025-03-30 10:42:45 -04:00
committed by GitHub
parent f84654d162
commit 88e51d549c
13 changed files with 438 additions and 40 deletions
+58 -18
View File
@@ -3,6 +3,7 @@ package api
import (
"errors"
"net/http"
"strings"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
@@ -364,19 +365,25 @@ func (cma *CloudMigrationAPI) GetSnapshot(c *contextmodel.ReqContext) response.R
return response.ErrOrFallback(http.StatusBadRequest, "invalid snapshot uid", err)
}
page := getQueryPageParams(c.QueryInt("resultPage"), cloudmigration.ResultPage(1))
lim := getQueryPageParams(c.QueryInt("resultLimit"), cloudmigration.ResultLimit(100))
col := getQueryCol(c.Query("resultSortColumn"), cloudmigration.SortColumnID)
order := getQueryOrder(c.Query("resultSortOrder"), cloudmigration.SortOrderAsc)
errorsOnly := c.QueryBool("errorsOnly")
q := cloudmigration.GetSnapshotsQuery{
SnapshotUID: snapshotUid,
SessionUID: sessUid,
ResultPage: c.QueryInt("resultPage"),
ResultLimit: c.QueryInt("resultLimit"),
OrgID: c.SignedInUser.OrgID,
SnapshotResultQueryParams: cloudmigration.SnapshotResultQueryParams{
ResultPage: page,
ResultLimit: lim,
SortColumn: col,
SortOrder: order,
ErrorsOnly: errorsOnly,
},
}
if q.ResultLimit == 0 {
q.ResultLimit = 100
}
if q.ResultPage < 1 {
q.ResultPage = 1
}
snapshot, err := cma.cloudMigrationService.GetSnapshot(ctx, q)
if err != nil {
span.SetStatus(codes.Error, "error retrieving snapshot")
@@ -387,6 +394,7 @@ func (cma *CloudMigrationAPI) GetSnapshot(c *contextmodel.ReqContext) response.R
results := snapshot.Resources
// convert the results to DTOs
dtoResults := make([]MigrateDataResponseItemDTO, len(results))
for i := 0; i < len(results); i++ {
dtoResults[i] = MigrateDataResponseItemDTO{
@@ -427,6 +435,43 @@ func (cma *CloudMigrationAPI) GetSnapshot(c *contextmodel.ReqContext) response.R
return response.JSON(http.StatusOK, respDto)
}
type PageParam interface {
~int // any int or underlying int type
}
func getQueryPageParams[D PageParam](page int, def D) D {
if page < 1 || page > 10000 {
return def
}
return D(page)
}
func getQueryCol(col string, defaultCol cloudmigration.ResultSortColumn) cloudmigration.ResultSortColumn {
switch strings.ToLower(col) {
case string(cloudmigration.SortColumnID):
return cloudmigration.SortColumnID
case string(cloudmigration.SortColumnName):
return cloudmigration.SortColumnName
case string(cloudmigration.SortColumnType):
return cloudmigration.SortColumnType
case string(cloudmigration.SortColumnStatus):
return cloudmigration.SortColumnStatus
default:
return defaultCol
}
}
func getQueryOrder(order string, defaultOrder cloudmigration.SortOrder) cloudmigration.SortOrder {
switch strings.ToUpper(order) {
case string(cloudmigration.SortOrderAsc):
return cloudmigration.SortOrderAsc
case string(cloudmigration.SortOrderDesc):
return cloudmigration.SortOrderDesc
default:
return defaultOrder
}
}
// swagger:route GET /cloudmigration/migration/{uid}/snapshots migrations getShapshotList
//
// Get a list of snapshots for a session.
@@ -450,16 +495,11 @@ func (cma *CloudMigrationAPI) GetSnapshotList(c *contextmodel.ReqContext) respon
}
q := cloudmigration.ListSnapshotsQuery{
SessionUID: uid,
Limit: c.QueryInt("limit"),
Page: c.QueryInt("page"),
Sort: c.Query("sort"),
OrgID: c.SignedInUser.OrgID,
}
if q.Limit == 0 {
q.Limit = 100
}
if q.Page < 1 {
q.Page = 1
Limit: getQueryPageParams(c.QueryInt("limit"), 100),
Page: getQueryPageParams(c.QueryInt("page"), 1),
// TODO: change to pattern used by GetSnapshot results
Sort: c.Query("sort"),
OrgID: c.SignedInUser.OrgID,
}
snapshotList, err := cma.cloudMigrationService.GetSnapshotList(ctx, q)
+135
View File
@@ -601,3 +601,138 @@ func runSimpleApiTest(tt TestCase) func(t *testing.T) {
}
}
}
func TestGetQueryPageParams(t *testing.T) {
tests := []struct {
name string
page int
def int
expected int
}{
{
name: "returns default when page is 0",
page: 0,
def: 1,
expected: 1,
},
{
name: "returns default when page is negative",
page: -1,
def: 1,
expected: 1,
},
{
name: "returns default when page exceeds max",
page: 10001,
def: 1,
expected: 1,
},
{
name: "returns page when within valid range",
page: 100,
def: 1,
expected: 100,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getQueryPageParams(tt.page, tt.def)
require.Equal(t, tt.expected, result)
})
}
}
func TestGetQueryCol(t *testing.T) {
tests := []struct {
name string
col string
defaultCol cloudmigration.ResultSortColumn
expected cloudmigration.ResultSortColumn
}{
{
name: "returns id column",
col: "id",
defaultCol: cloudmigration.SortColumnName,
expected: cloudmigration.SortColumnID,
},
{
name: "returns name column",
col: "name",
defaultCol: cloudmigration.SortColumnID,
expected: cloudmigration.SortColumnName,
},
{
name: "returns type column",
col: "resource_type",
defaultCol: cloudmigration.SortColumnID,
expected: cloudmigration.SortColumnType,
},
{
name: "returns status column",
col: "status",
defaultCol: cloudmigration.SortColumnID,
expected: cloudmigration.SortColumnStatus,
},
{
name: "returns default for unknown column",
col: "unknown",
defaultCol: cloudmigration.SortColumnID,
expected: cloudmigration.SortColumnID,
},
{
name: "case insensitive column matching",
col: "NaMe",
defaultCol: cloudmigration.SortColumnID,
expected: cloudmigration.SortColumnName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getQueryCol(tt.col, tt.defaultCol)
require.Equal(t, tt.expected, result)
})
}
}
func TestGetQueryOrder(t *testing.T) {
tests := []struct {
name string
order string
defaultOrder cloudmigration.SortOrder
expected cloudmigration.SortOrder
}{
{
name: "returns ASC order",
order: "ASC",
defaultOrder: cloudmigration.SortOrderDesc,
expected: cloudmigration.SortOrderAsc,
},
{
name: "returns DESC order",
order: "DESC",
defaultOrder: cloudmigration.SortOrderAsc,
expected: cloudmigration.SortOrderDesc,
},
{
name: "returns default for unknown order",
order: "unknown",
defaultOrder: cloudmigration.SortOrderAsc,
expected: cloudmigration.SortOrderAsc,
},
{
name: "case insensitive order matching",
order: "aSc",
defaultOrder: cloudmigration.SortOrderDesc,
expected: cloudmigration.SortOrderAsc,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getQueryOrder(tt.order, tt.defaultOrder)
require.Equal(t, tt.expected, result)
})
}
}
+18
View File
@@ -295,6 +295,24 @@ type GetSnapshotParams struct {
// default: 100
ResultLimit int `json:"resultLimit"`
// ResultSortColumn can be used to override the default system sort. Valid values are "name", "resource_type", and "status".
// in:query
// required:false
// default: default
ResultSortColumn string `json:"resultSortColumn"`
// ResultSortOrder is used with ResultSortColumn. Valid values are ASC and DESC.
// in:query
// required:false
// default: ASC
ResultSortOrder string `json:"resultSortOrder"`
// ErrorsOnly is used to only return resources with error statuses
// in:query
// required:false
// default: false
ErrorsOnly bool `json:"errorsOnly"`
// Session UID of a session
// in: path
UID string `json:"uid"`
@@ -566,7 +566,7 @@ func (s *Service) GetSnapshot(ctx context.Context, query cloudmigration.GetSnaps
defer span.End()
orgID, sessionUid, snapshotUid := query.OrgID, query.SessionUID, query.SnapshotUID
snapshot, err := s.store.GetSnapshotByUID(ctx, orgID, sessionUid, snapshotUid, query.ResultPage, query.ResultLimit)
snapshot, err := s.store.GetSnapshotByUID(ctx, orgID, sessionUid, snapshotUid, query.SnapshotResultQueryParams)
if err != nil {
return nil, fmt.Errorf("fetching snapshot for uid %s: %w", snapshotUid, err)
}
@@ -615,7 +615,7 @@ func (s *Service) GetSnapshot(ctx context.Context, query cloudmigration.GetSnaps
}
// Refresh the snapshot after the update
snapshot, err = s.store.GetSnapshotByUID(ctx, orgID, sessionUid, snapshotUid, query.ResultPage, query.ResultLimit)
snapshot, err = s.store.GetSnapshotByUID(ctx, orgID, sessionUid, snapshotUid, query.SnapshotResultQueryParams)
if err != nil {
return nil, fmt.Errorf("fetching snapshot for uid %s: %w", snapshotUid, err)
}
@@ -343,6 +343,12 @@ func Test_GetSnapshotStatusFromGMS(t *testing.T) {
snapshot, err = s.GetSnapshot(context.Background(), cloudmigration.GetSnapshotsQuery{
SnapshotUID: snapshotUID,
SessionUID: sessionUID,
SnapshotResultQueryParams: cloudmigration.SnapshotResultQueryParams{
ResultLimit: 10,
ResultPage: 1,
SortColumn: cloudmigration.SortColumnID,
SortOrder: cloudmigration.SortOrderAsc,
},
})
require.NoError(t, err)
require.NotNil(t, snapshot)
@@ -14,6 +14,6 @@ type store interface {
CreateSnapshot(ctx context.Context, snapshot cloudmigration.CloudMigrationSnapshot) (string, error)
UpdateSnapshot(ctx context.Context, snapshot cloudmigration.UpdateSnapshotCmd) error
GetSnapshotByUID(ctx context.Context, orgID int64, sessUid, id string, resultPage int, resultLimit int) (*cloudmigration.CloudMigrationSnapshot, error)
GetSnapshotByUID(ctx context.Context, orgID int64, sessUid, id string, params cloudmigration.SnapshotResultQueryParams) (*cloudmigration.CloudMigrationSnapshot, error)
GetSnapshotList(ctx context.Context, query cloudmigration.ListSnapshotsQuery) ([]cloudmigration.CloudMigrationSnapshot, error)
}
@@ -241,7 +241,7 @@ func (ss *sqlStore) deleteSnapshot(ctx context.Context, snapshotUid string) erro
})
}
func (ss *sqlStore) GetSnapshotByUID(ctx context.Context, orgID int64, sessionUid, uid string, resultPage int, resultLimit int) (*cloudmigration.CloudMigrationSnapshot, error) {
func (ss *sqlStore) GetSnapshotByUID(ctx context.Context, orgID int64, sessionUid, uid string, params cloudmigration.SnapshotResultQueryParams) (*cloudmigration.CloudMigrationSnapshot, error) {
// first we check if the session exists, using orgId and sessionUid
session, err := ss.GetMigrationSessionByUID(ctx, orgID, sessionUid)
if err != nil || session == nil {
@@ -272,7 +272,7 @@ func (ss *sqlStore) GetSnapshotByUID(ctx context.Context, orgID int64, sessionUi
snapshot.EncryptionKey = []byte(secret)
}
resources, err := ss.getSnapshotResources(ctx, uid, resultPage, resultLimit)
resources, err := ss.getSnapshotResources(ctx, uid, params)
if err == nil {
snapshot.Resources = resources
}
@@ -421,19 +421,17 @@ func (ss *sqlStore) UpdateSnapshotResources(ctx context.Context, snapshotUid str
})
}
func (ss *sqlStore) getSnapshotResources(ctx context.Context, snapshotUid string, page int, limit int) ([]cloudmigration.CloudMigrationResource, error) {
if page < 1 {
page = 1
}
if limit == 0 {
limit = 100
}
func (ss *sqlStore) getSnapshotResources(ctx context.Context, snapshotUid string, params cloudmigration.SnapshotResultQueryParams) ([]cloudmigration.CloudMigrationResource, error) {
page, limit, col, dir, errorsOnly := int(params.ResultPage), int(params.ResultLimit), string(params.SortColumn), string(params.SortOrder), params.ErrorsOnly
var resources []cloudmigration.CloudMigrationResource
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
offset := (page - 1) * limit
sess.Limit(limit, offset)
return sess.OrderBy("id ASC").Find(&resources, &cloudmigration.CloudMigrationResource{
if errorsOnly {
sess.Where("status = ?", cloudmigration.ItemStatusError)
}
return sess.OrderBy(fmt.Sprintf("%s %s", col, dir)).Find(&resources, &cloudmigration.CloudMigrationResource{
SnapshotUID: snapshotUid,
})
})
@@ -6,6 +6,7 @@ import (
"strconv"
"testing"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/cloudmigration"
fakeSecrets "github.com/grafana/grafana/pkg/services/secrets/fakes"
secretskv "github.com/grafana/grafana/pkg/services/secrets/kvstore"
@@ -138,7 +139,12 @@ func Test_SnapshotManagement(t *testing.T) {
require.NotEmpty(t, snapshotUid)
//retrieve it from the db
snapshot, err := s.GetSnapshotByUID(ctx, 1, session.UID, snapshotUid, 0, 0)
snapshot, err := s.GetSnapshotByUID(ctx, 1, session.UID, snapshotUid, cloudmigration.SnapshotResultQueryParams{
ResultPage: 1,
ResultLimit: 100,
SortColumn: cloudmigration.SortColumnID,
SortOrder: cloudmigration.SortOrderAsc,
})
require.NoError(t, err)
require.Equal(t, cloudmigration.SnapshotStatusCreating, snapshot.Status)
@@ -147,7 +153,12 @@ func Test_SnapshotManagement(t *testing.T) {
require.NoError(t, err)
//retrieve it again
snapshot, err = s.GetSnapshotByUID(ctx, 1, session.UID, snapshotUid, 0, 0)
snapshot, err = s.GetSnapshotByUID(ctx, 1, session.UID, snapshotUid, cloudmigration.SnapshotResultQueryParams{
ResultPage: 1,
ResultLimit: 100,
SortColumn: cloudmigration.SortColumnID,
SortOrder: cloudmigration.SortOrderAsc,
})
require.NoError(t, err)
require.Equal(t, cloudmigration.SnapshotStatusCreating, snapshot.Status)
@@ -162,7 +173,12 @@ func Test_SnapshotManagement(t *testing.T) {
require.NoError(t, err)
// now we expect not to find the snapshot
snapshot, err = s.GetSnapshotByUID(ctx, 1, session.UID, snapshotUid, 0, 0)
snapshot, err = s.GetSnapshotByUID(ctx, 1, session.UID, snapshotUid, cloudmigration.SnapshotResultQueryParams{
ResultPage: 1,
ResultLimit: 100,
SortColumn: cloudmigration.SortColumnID,
SortOrder: cloudmigration.SortOrderAsc,
})
require.ErrorIs(t, err, cloudmigration.ErrSnapshotNotFound)
require.Nil(t, snapshot)
})
@@ -174,9 +190,14 @@ func Test_SnapshotResources(t *testing.T) {
_, s := setUpTest(t)
ctx := context.Background()
t.Run("tests CRUD of snapshot resources", func(t *testing.T) {
t.Run("test CRUD of snapshot resources", func(t *testing.T) {
// Get the default rows from the test
resources, err := s.getSnapshotResources(ctx, "poiuy", 0, 100)
resources, err := s.getSnapshotResources(ctx, "poiuy", cloudmigration.SnapshotResultQueryParams{
ResultPage: 1,
ResultLimit: 100,
SortColumn: cloudmigration.SortColumnID,
SortOrder: cloudmigration.SortOrderAsc,
})
assert.NoError(t, err)
assert.Len(t, resources, 3)
for _, r := range resources {
@@ -204,7 +225,12 @@ func Test_SnapshotResources(t *testing.T) {
assert.NoError(t, err)
// Get resources again
resources, err = s.getSnapshotResources(ctx, "poiuy", 0, 100)
resources, err = s.getSnapshotResources(ctx, "poiuy", cloudmigration.SnapshotResultQueryParams{
ResultPage: 1,
ResultLimit: 100,
SortColumn: cloudmigration.SortColumnID,
SortOrder: cloudmigration.SortOrderAsc,
})
assert.NoError(t, err)
assert.Len(t, resources, 4)
// ensure existing resource was updated from ERROR
@@ -240,10 +266,97 @@ func Test_SnapshotResources(t *testing.T) {
err = s.deleteSnapshotResources(ctx, "poiuy")
assert.NoError(t, err)
// make sure they're gone
resources, err = s.getSnapshotResources(ctx, "poiuy", 0, 100)
resources, err = s.getSnapshotResources(ctx, "poiuy", cloudmigration.SnapshotResultQueryParams{
ResultPage: 1,
ResultLimit: 100,
SortColumn: cloudmigration.SortColumnID,
SortOrder: cloudmigration.SortOrderAsc,
})
assert.NoError(t, err)
assert.Len(t, resources, 0)
})
t.Run("test pagination and sorting", func(t *testing.T) {
// Create test data
resources := []cloudmigration.CloudMigrationResource{
{UID: "1", SnapshotUID: "abc123", Name: "Dashboard 1", Type: cloudmigration.DashboardDataType, Status: cloudmigration.ItemStatusOK},
{UID: "2", SnapshotUID: "abc123", Name: "Alert 1", Type: cloudmigration.AlertRuleType, Status: cloudmigration.ItemStatusError},
{UID: "3", SnapshotUID: "abc123", Name: "Dashboard 2", Type: cloudmigration.DashboardDataType, Status: cloudmigration.ItemStatusPending},
{UID: "4", SnapshotUID: "abc123", Name: "Folder 1", Type: cloudmigration.FolderDataType, Status: cloudmigration.ItemStatusOK},
{UID: "5", SnapshotUID: "abc123", Name: "Alert 2", Type: cloudmigration.AlertRuleType, Status: cloudmigration.ItemStatusOK},
}
err := s.db.WithDbSession(ctx, func(sess *db.Session) error {
_, err := sess.Insert(resources)
return err
})
require.NoError(t, err)
t.Run("default sorting and paging and default params", func(t *testing.T) {
results, err := s.getSnapshotResources(ctx, "abc123", cloudmigration.SnapshotResultQueryParams{
ResultPage: 1,
ResultLimit: 100,
SortColumn: cloudmigration.SortColumnID,
SortOrder: cloudmigration.SortOrderAsc,
})
require.NoError(t, err)
assert.Len(t, results, 5)
// Default sort is by ID ascending
assert.Equal(t, "1", results[0].UID)
assert.Equal(t, "5", results[4].UID)
})
t.Run("sort by name descending", func(t *testing.T) {
results, err := s.getSnapshotResources(ctx, "abc123", cloudmigration.SnapshotResultQueryParams{
ResultPage: 1,
ResultLimit: 100,
SortColumn: cloudmigration.SortColumnName,
SortOrder: cloudmigration.SortOrderDesc,
})
require.NoError(t, err)
assert.Equal(t, "Folder 1", results[0].Name)
assert.Equal(t, "Alert 1", results[4].Name)
})
t.Run("sort by type ascending", func(t *testing.T) {
results, err := s.getSnapshotResources(ctx, "abc123", cloudmigration.SnapshotResultQueryParams{
ResultPage: 1,
ResultLimit: 100,
SortColumn: cloudmigration.SortColumnType,
SortOrder: cloudmigration.SortOrderAsc,
})
require.NoError(t, err)
assert.Equal(t, "2", results[0].UID)
assert.Equal(t, "5", results[1].UID)
})
t.Run("sort by status with pagination", func(t *testing.T) {
results, err := s.getSnapshotResources(ctx, "abc123", cloudmigration.SnapshotResultQueryParams{
ResultPage: 2,
ResultLimit: 2,
SortColumn: cloudmigration.SortColumnStatus,
SortOrder: cloudmigration.SortOrderAsc,
})
require.NoError(t, err)
assert.Len(t, results, 2)
// secondary sort is by ID ascending by default
assert.Equal(t, "4", results[0].UID)
assert.Equal(t, "5", results[1].UID)
})
t.Run("only errors filter returns only error status resources", func(t *testing.T) {
results, err := s.getSnapshotResources(ctx, "abc123", cloudmigration.SnapshotResultQueryParams{
ResultPage: 1,
ResultLimit: 100,
SortColumn: cloudmigration.SortColumnID,
SortOrder: cloudmigration.SortOrderAsc,
ErrorsOnly: true,
})
require.NoError(t, err)
assert.Len(t, results, 1)
assert.Equal(t, "2", results[0].UID)
})
})
}
func TestGetSnapshotList(t *testing.T) {
+31 -2
View File
@@ -162,12 +162,41 @@ type CloudMigrationSessionListResponse struct {
Sessions []CloudMigrationSessionResponse
}
type ResultSortColumn string
const (
SortColumnID ResultSortColumn = "id"
SortColumnName ResultSortColumn = "name"
SortColumnType ResultSortColumn = "resource_type"
SortColumnStatus ResultSortColumn = "status"
)
type SortOrder string
const (
SortOrderAsc SortOrder = "ASC"
SortOrderDesc SortOrder = "DESC"
)
// ResultPage should be in the range [1, 10000]
type ResultPage int
// ResultLimit should be in the rage [1, 10000]
type ResultLimit int
type SnapshotResultQueryParams struct {
ResultPage ResultPage
ResultLimit ResultLimit
SortColumn ResultSortColumn
SortOrder SortOrder
ErrorsOnly bool
}
type GetSnapshotsQuery struct {
SnapshotUID string
OrgID int64
SessionUID string
ResultPage int
ResultLimit int
SnapshotResultQueryParams
}
type ListSnapshotsQuery struct {
+21
View File
@@ -2457,6 +2457,27 @@
"name": "resultLimit",
"in": "query"
},
{
"type": "string",
"default": "default",
"description": "ResultSortColumn can be used to override the default system sort. Valid values are \"name\", \"resource_type\", and \"status\".",
"name": "resultSortColumn",
"in": "query"
},
{
"type": "string",
"default": "ASC",
"description": "ResultSortOrder is used with ResultSortColumn. Valid values are ASC and DESC.",
"name": "resultSortOrder",
"in": "query"
},
{
"type": "boolean",
"default": false,
"description": "ErrorsOnly is used to only return resources with error statuses",
"name": "errorsOnly",
"in": "query"
},
{
"type": "string",
"description": "Session UID of a session",
@@ -4,3 +4,5 @@ The [`endpoints.gen.ts`](./endpoints.gen.ts) file is machine generated. In order
- Run: `make swagger-clean && make openapi3-gen`
- Run: `yarn generate-apis`
If you run into issues, try updating your Node.js version.
@@ -26,6 +26,9 @@ const injectedRtkApi = api.injectEndpoints({
params: {
resultPage: queryArg.resultPage,
resultLimit: queryArg.resultLimit,
resultSortColumn: queryArg.resultSortColumn,
resultSortOrder: queryArg.resultSortOrder,
errorsOnly: queryArg.errorsOnly,
},
}),
}),
@@ -97,6 +100,12 @@ export type GetSnapshotApiArg = {
resultPage?: number;
/** Max limit for snapshot results returned. */
resultLimit?: number;
/** ResultSortColumn can be used to override the default system sort. Valid values are "name", "resource_type", and "status". */
resultSortColumn?: string;
/** ResultSortOrder is used with ResultSortColumn. Valid values are ASC and DESC. */
resultSortOrder?: string;
/** ErrorsOnly is used to only return resources with error statuses */
errorsOnly?: boolean;
/** Session UID of a session */
uid: string;
/** UID of a snapshot */
+27
View File
@@ -16009,6 +16009,33 @@
"type": "integer"
}
},
{
"description": "ResultSortColumn can be used to override the default system sort. Valid values are \"name\", \"resource_type\", and \"status\".",
"in": "query",
"name": "resultSortColumn",
"schema": {
"default": "default",
"type": "string"
}
},
{
"description": "ResultSortOrder is used with ResultSortColumn. Valid values are ASC and DESC.",
"in": "query",
"name": "resultSortOrder",
"schema": {
"default": "ASC",
"type": "string"
}
},
{
"description": "ErrorsOnly is used to only return resources with error statuses",
"in": "query",
"name": "errorsOnly",
"schema": {
"default": false,
"type": "boolean"
}
},
{
"description": "Session UID of a session",
"in": "path",