Skip to content

Commit 639c24e

Browse files
authored
feat(ws): add @id swag annotation to handlers (#488)
- added @id annotations for all API routes to populate operationId Swagger attribute - split GetWorkspacesHandler into 2 separate handlers to account for @id needing to be unique-per-route - GetAllWorkspacesHandler now services GET /workspaces - GetWorkspacesByNamespaceHandler now services GET /workspaces/{namespace} - non-exported getWorkspacesHandler function contains all business logic that existed in GetWorkspacesHandler - Adjusted test cases to align with the new handler names. Signed-off-by: Andy Stoneberg <[email protected]>
1 parent d71a3f5 commit 639c24e

10 files changed

+87
-48
lines changed

workspaces/backend/api/app.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ func (a *App) Routes() http.Handler {
113113
router.GET(AllNamespacesPath, a.GetNamespacesHandler)
114114

115115
// workspaces
116-
router.GET(AllWorkspacesPath, a.GetWorkspacesHandler)
117-
router.GET(WorkspacesByNamespacePath, a.GetWorkspacesHandler)
116+
router.GET(AllWorkspacesPath, a.GetAllWorkspacesHandler)
117+
router.GET(WorkspacesByNamespacePath, a.GetWorkspacesByNamespaceHandler)
118118
router.GET(WorkspacesByNamePath, a.GetWorkspaceHandler)
119119
router.POST(WorkspacesByNamespacePath, a.CreateWorkspaceHandler)
120120
router.DELETE(WorkspacesByNamePath, a.DeleteWorkspaceHandler)

workspaces/backend/api/healthcheck_handler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
// @Summary Returns the health status of the application
3030
// @Description Provides a healthcheck response indicating the status of key services.
3131
// @Tags healthcheck
32+
// @ID getHealthcheck
3233
// @Produce application/json
3334
// @Success 200 {object} health_check.HealthCheck "Successful healthcheck response"
3435
// @Failure 500 {object} ErrorEnvelope "Internal server error"

workspaces/backend/api/namespaces_handler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type NamespaceListEnvelope Envelope[[]models.Namespace]
3333
// @Summary Returns a list of all namespaces
3434
// @Description Provides a list of all namespaces that the user has access to
3535
// @Tags namespaces
36+
// @ID listNamespaces
3637
// @Produce application/json
3738
// @Success 200 {object} NamespaceListEnvelope "Successful namespaces response"
3839
// @Failure 401 {object} ErrorEnvelope "Unauthorized"

workspaces/backend/api/workspace_actions_handler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type WorkspaceActionPauseEnvelope Envelope[*models.WorkspaceActionPause]
3939
// @Summary Pause or unpause a workspace
4040
// @Description Pauses or unpauses a workspace, stopping or resuming all associated pods.
4141
// @Tags workspaces
42+
// @ID updateWorkspacePauseState
4243
// @Accept json
4344
// @Produce json
4445
// @Param namespace path string true "Namespace of the workspace" extensions(x-example=default)

workspaces/backend/api/workspacekinds_handler.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type WorkspaceKindEnvelope Envelope[models.WorkspaceKind]
4747
// @Summary Get workspace kind
4848
// @Description Returns details of a specific workspace kind identified by its name. Workspace kinds define the available types of workspaces that can be created.
4949
// @Tags workspacekinds
50+
// @ID getWorkspaceKind
5051
// @Accept json
5152
// @Produce json
5253
// @Param name path string true "Name of the workspace kind" extensions(x-example=jupyterlab)
@@ -101,6 +102,7 @@ func (a *App) GetWorkspaceKindHandler(w http.ResponseWriter, r *http.Request, ps
101102
// @Summary List workspace kinds
102103
// @Description Returns a list of all available workspace kinds. Workspace kinds define the different types of workspaces that can be created in the system.
103104
// @Tags workspacekinds
105+
// @ID listWorkspaceKinds
104106
// @Accept json
105107
// @Produce json
106108
// @Success 200 {object} WorkspaceKindListEnvelope "Successful operation. Returns a list of all available workspace kinds."
@@ -136,6 +138,7 @@ func (a *App) GetWorkspaceKindsHandler(w http.ResponseWriter, r *http.Request, _
136138
// @Summary Create workspace kind
137139
// @Description Creates a new workspace kind.
138140
// @Tags workspacekinds
141+
// @ID createWorkspaceKind
139142
// @Accept application/yaml
140143
// @Produce json
141144
// @Param body body string true "Kubernetes YAML manifest of a WorkspaceKind"

workspaces/backend/api/workspacekinds_handler_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ var _ = Describe("WorkspaceKinds Handler", func() {
209209
By("setting the auth headers")
210210
req.Header.Set(userIdHeader, adminUser)
211211

212-
By("executing GetWorkspacesHandler")
212+
By("executing GetWorkspaceKindsHandler")
213213
ps := httprouter.Params{}
214214
rr := httptest.NewRecorder()
215215
a.GetWorkspaceKindsHandler(rr, req, ps)

workspaces/backend/api/workspaces_handler.go

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type WorkspaceEnvelope Envelope[models.Workspace]
4444
// @Summary Get workspace
4545
// @Description Returns details of a specific workspace identified by namespace and workspace name.
4646
// @Tags workspaces
47+
// @ID getWorkspace
4748
// @Accept json
4849
// @Produce json
4950
// @Param namespace path string true "Namespace of the workspace" extensions(x-example=kubeflow-user-example-com)
@@ -99,24 +100,44 @@ func (a *App) GetWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps htt
99100
a.dataResponse(w, r, responseEnvelope)
100101
}
101102

102-
// GetWorkspacesHandler returns a list of workspaces.
103+
// GetAllWorkspacesHandler returns a list of all workspaces across all namespaces.
103104
//
104-
// @Summary List workspaces
105-
// @Description Returns a list of workspaces. The endpoint supports two modes:
106-
// @Description 1. List all workspaces across all namespaces (when no namespace is provided)
107-
// @Description 2. List workspaces in a specific namespace (when namespace is provided)
105+
// @Summary List all workspaces
106+
// @Description Returns a list of all workspaces across all namespaces.
108107
// @Tags workspaces
108+
// @ID listAllWorkspaces
109109
// @Accept json
110110
// @Produce json
111-
// @Param namespace path string true "Namespace to filter workspaces. If not provided, returns all workspaces across all namespaces." extensions(x-example=kubeflow-user-example-com)
112-
// @Success 200 {object} WorkspaceListEnvelope "Successful operation. Returns a list of workspaces."
111+
// @Success 200 {object} WorkspaceListEnvelope "Successful operation. Returns a list of all workspaces."
112+
// @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required."
113+
// @Failure 403 {object} ErrorEnvelope "Forbidden. User does not have permission to list workspaces."
114+
// @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server."
115+
// @Router /workspaces [get]
116+
func (a *App) GetAllWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
117+
a.getWorkspacesHandler(w, r, ps)
118+
}
119+
120+
// GetWorkspacesByNamespaceHandler returns a list of workspaces in a specific namespace.
121+
//
122+
// @Summary List workspaces by namespace
123+
// @Description Returns a list of workspaces in a specific namespace.
124+
// @Tags workspaces
125+
// @ID listWorkspacesByNamespace
126+
// @Accept json
127+
// @Produce json
128+
// @Param namespace path string true "Namespace to filter workspaces" extensions(x-example=kubeflow-user-example-com)
129+
// @Success 200 {object} WorkspaceListEnvelope "Successful operation. Returns a list of workspaces in the specified namespace."
113130
// @Failure 400 {object} ErrorEnvelope "Bad Request. Invalid namespace format."
114131
// @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required."
115132
// @Failure 403 {object} ErrorEnvelope "Forbidden. User does not have permission to list workspaces."
116133
// @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server."
117-
// @Router /workspaces [get]
118134
// @Router /workspaces/{namespace} [get]
119-
func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
135+
func (a *App) GetWorkspacesByNamespaceHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
136+
a.getWorkspacesHandler(w, r, ps)
137+
}
138+
139+
// getWorkspacesHandler is the internal implementation for listing workspaces.
140+
func (a *App) getWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
120141
namespace := ps.ByName(NamespacePathParam)
121142

122143
// validate path parameters
@@ -167,6 +188,7 @@ func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps ht
167188
// @Summary Create workspace
168189
// @Description Creates a new workspace in the specified namespace.
169190
// @Tags workspaces
191+
// @ID createWorkspace
170192
// @Accept json
171193
// @Produce json
172194
// @Param namespace path string true "Namespace for the workspace" extensions(x-example=kubeflow-user-example-com)
@@ -267,6 +289,7 @@ func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps
267289
// @Summary Delete workspace
268290
// @Description Deletes a specific workspace identified by namespace and workspace name.
269291
// @Tags workspaces
292+
// @ID deleteWorkspace
270293
// @Accept json
271294
// @Produce json
272295
// @Param namespace path string true "Namespace of the workspace" extensions(x-example=kubeflow-user-example-com)

workspaces/backend/api/workspaces_handler_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,10 @@ var _ = Describe("Workspaces Handler", func() {
164164
By("setting the auth headers")
165165
req.Header.Set(userIdHeader, adminUser)
166166

167-
By("executing GetWorkspacesHandler")
167+
By("executing GetAllWorkspacesHandler")
168168
ps := httprouter.Params{}
169169
rr := httptest.NewRecorder()
170-
a.GetWorkspacesHandler(rr, req, ps)
170+
a.GetAllWorkspacesHandler(rr, req, ps)
171171
rs := rr.Result()
172172
defer rs.Body.Close()
173173

@@ -219,12 +219,12 @@ var _ = Describe("Workspaces Handler", func() {
219219
By("setting the auth headers")
220220
req.Header.Set(userIdHeader, adminUser)
221221

222-
By("executing GetWorkspacesHandler")
222+
By("executing GetWorkspacesByNamespaceHandler")
223223
ps := httprouter.Params{
224224
httprouter.Param{Key: NamespacePathParam, Value: namespaceName1},
225225
}
226226
rr := httptest.NewRecorder()
227-
a.GetWorkspacesHandler(rr, req, ps)
227+
a.GetWorkspacesByNamespaceHandler(rr, req, ps)
228228
rs := rr.Result()
229229
defer rs.Body.Close()
230230

@@ -429,12 +429,12 @@ var _ = Describe("Workspaces Handler", func() {
429429
By("setting the auth headers")
430430
req.Header.Set(userIdHeader, adminUser)
431431

432-
By("executing GetWorkspacesHandler")
432+
By("executing GetWorkspacesByNamespaceHandler")
433433
ps := httprouter.Params{
434434
httprouter.Param{Key: NamespacePathParam, Value: namespaceName1},
435435
}
436436
rr := httptest.NewRecorder()
437-
a.GetWorkspacesHandler(rr, req, ps)
437+
a.GetWorkspacesByNamespaceHandler(rr, req, ps)
438438
rs := rr.Result()
439439
defer rs.Body.Close()
440440

@@ -543,10 +543,10 @@ var _ = Describe("Workspaces Handler", func() {
543543
By("setting the auth headers")
544544
req.Header.Set(userIdHeader, adminUser)
545545

546-
By("executing GetWorkspacesHandler")
546+
By("executing GetAllWorkspacesHandler")
547547
ps := httprouter.Params{}
548548
rr := httptest.NewRecorder()
549-
a.GetWorkspacesHandler(rr, req, ps)
549+
a.GetAllWorkspacesHandler(rr, req, ps)
550550
rs := rr.Result()
551551
defer rs.Body.Close()
552552

@@ -577,12 +577,12 @@ var _ = Describe("Workspaces Handler", func() {
577577
By("setting the auth headers")
578578
req.Header.Set(userIdHeader, adminUser)
579579

580-
By("executing GetWorkspacesHandler")
580+
By("executing GetWorkspacesByNamespaceHandler")
581581
ps := httprouter.Params{
582582
httprouter.Param{Key: NamespacePathParam, Value: missingNamespace},
583583
}
584584
rr := httptest.NewRecorder()
585-
a.GetWorkspacesHandler(rr, req, ps)
585+
a.GetWorkspacesByNamespaceHandler(rr, req, ps)
586586
rs := rr.Result()
587587
defer rs.Body.Close()
588588

workspaces/backend/openapi/docs.go

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const docTemplate = `{
2929
"healthcheck"
3030
],
3131
"summary": "Returns the health status of the application",
32+
"operationId": "getHealthcheck",
3233
"responses": {
3334
"200": {
3435
"description": "Successful healthcheck response",
@@ -55,6 +56,7 @@ const docTemplate = `{
5556
"namespaces"
5657
],
5758
"summary": "Returns a list of all namespaces",
59+
"operationId": "listNamespaces",
5860
"responses": {
5961
"200": {
6062
"description": "Successful namespaces response",
@@ -96,6 +98,7 @@ const docTemplate = `{
9698
"workspacekinds"
9799
],
98100
"summary": "List workspace kinds",
101+
"operationId": "listWorkspaceKinds",
99102
"responses": {
100103
"200": {
101104
"description": "Successful operation. Returns a list of all available workspace kinds.",
@@ -135,6 +138,7 @@ const docTemplate = `{
135138
"workspacekinds"
136139
],
137140
"summary": "Create workspace kind",
141+
"operationId": "createWorkspaceKind",
138142
"parameters": [
139143
{
140144
"description": "Kubernetes YAML manifest of a WorkspaceKind",
@@ -217,6 +221,7 @@ const docTemplate = `{
217221
"workspacekinds"
218222
],
219223
"summary": "Get workspace kind",
224+
"operationId": "getWorkspaceKind",
220225
"parameters": [
221226
{
222227
"type": "string",
@@ -269,7 +274,7 @@ const docTemplate = `{
269274
},
270275
"/workspaces": {
271276
"get": {
272-
"description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)",
277+
"description": "Returns a list of all workspaces across all namespaces.",
273278
"consumes": [
274279
"application/json"
275280
],
@@ -279,20 +284,15 @@ const docTemplate = `{
279284
"tags": [
280285
"workspaces"
281286
],
282-
"summary": "List workspaces",
287+
"summary": "List all workspaces",
288+
"operationId": "listAllWorkspaces",
283289
"responses": {
284290
"200": {
285-
"description": "Successful operation. Returns a list of workspaces.",
291+
"description": "Successful operation. Returns a list of all workspaces.",
286292
"schema": {
287293
"$ref": "#/definitions/api.WorkspaceListEnvelope"
288294
}
289295
},
290-
"400": {
291-
"description": "Bad Request. Invalid namespace format.",
292-
"schema": {
293-
"$ref": "#/definitions/api.ErrorEnvelope"
294-
}
295-
},
296296
"401": {
297297
"description": "Unauthorized. Authentication is required.",
298298
"schema": {
@@ -316,7 +316,7 @@ const docTemplate = `{
316316
},
317317
"/workspaces/{namespace}": {
318318
"get": {
319-
"description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)",
319+
"description": "Returns a list of workspaces in a specific namespace.",
320320
"consumes": [
321321
"application/json"
322322
],
@@ -326,20 +326,21 @@ const docTemplate = `{
326326
"tags": [
327327
"workspaces"
328328
],
329-
"summary": "List workspaces",
329+
"summary": "List workspaces by namespace",
330+
"operationId": "listWorkspacesByNamespace",
330331
"parameters": [
331332
{
332333
"type": "string",
333334
"x-example": "kubeflow-user-example-com",
334-
"description": "Namespace to filter workspaces. If not provided, returns all workspaces across all namespaces.",
335+
"description": "Namespace to filter workspaces",
335336
"name": "namespace",
336337
"in": "path",
337338
"required": true
338339
}
339340
],
340341
"responses": {
341342
"200": {
342-
"description": "Successful operation. Returns a list of workspaces.",
343+
"description": "Successful operation. Returns a list of workspaces in the specified namespace.",
343344
"schema": {
344345
"$ref": "#/definitions/api.WorkspaceListEnvelope"
345346
}
@@ -382,6 +383,7 @@ const docTemplate = `{
382383
"workspaces"
383384
],
384385
"summary": "Create workspace",
386+
"operationId": "createWorkspace",
385387
"parameters": [
386388
{
387389
"type": "string",
@@ -466,6 +468,7 @@ const docTemplate = `{
466468
"workspaces"
467469
],
468470
"summary": "Pause or unpause a workspace",
471+
"operationId": "updateWorkspacePauseState",
469472
"parameters": [
470473
{
471474
"type": "string",
@@ -564,6 +567,7 @@ const docTemplate = `{
564567
"workspaces"
565568
],
566569
"summary": "Get workspace",
570+
"operationId": "getWorkspace",
567571
"parameters": [
568572
{
569573
"type": "string",
@@ -633,6 +637,7 @@ const docTemplate = `{
633637
"workspaces"
634638
],
635639
"summary": "Delete workspace",
640+
"operationId": "deleteWorkspace",
636641
"parameters": [
637642
{
638643
"type": "string",

0 commit comments

Comments
 (0)