Skip to content

feat(ws): add ws counts to backend wsk model #368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions workspaces/backend/api/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,5 +455,34 @@ func NewExampleWorkspaceKind(name string) *kubefloworgv1beta1.WorkspaceKind {
},
},
},
Status: kubefloworgv1beta1.WorkspaceKindStatus{
Workspaces: 1,
PodTemplateOptions: kubefloworgv1beta1.PodTemplateOptionsMetrics{
ImageConfig: []kubefloworgv1beta1.OptionMetric{
{
Id: "jupyterlab_scipy_180",
Workspaces: 1,
},
{
Id: "jupyterlab_scipy_190",
Workspaces: 0,
},
},
PodConfig: []kubefloworgv1beta1.OptionMetric{
{
Id: "tiny_cpu",
Workspaces: 1,
},
{
Id: "small_cpu",
Workspaces: 0,
},
{
Id: "big_gpu",
Workspaces: 0,
},
},
},
},
}
}
30 changes: 26 additions & 4 deletions workspaces/backend/internal/models/workspacekinds/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ func NewWorkspaceKindModelFromWorkspaceKind(wsk *kubefloworgv1beta1.WorkspaceKin
podAnnotations[k] = v
}
}
statusImageConfigMap := buildOptionMetricsMap(wsk.Status.PodTemplateOptions.ImageConfig)
statusPodConfigMap := buildOptionMetricsMap(wsk.Status.PodTemplateOptions.PodConfig)

// TODO: icons can either be a remote URL or read from a ConfigMap.
// in BOTH cases, we should cache and serve the image under a path on the backend API:
Expand All @@ -60,6 +62,10 @@ func NewWorkspaceKindModelFromWorkspaceKind(wsk *kubefloworgv1beta1.WorkspaceKin
Hidden: ptr.Deref(wsk.Spec.Spawner.Hidden, false),
Icon: iconRef,
Logo: logoRef,
// TODO: in the future will need to support including exactly one of clusterMetrics or namespaceMetrics based on request context
ClusterMetrics: clusterMetrics{
Workspaces: wsk.Status.Workspaces,
},
PodTemplate: PodTemplate{
PodMetadata: PodMetadata{
Labels: podLabels,
Expand All @@ -71,18 +77,26 @@ func NewWorkspaceKindModelFromWorkspaceKind(wsk *kubefloworgv1beta1.WorkspaceKin
Options: PodTemplateOptions{
ImageConfig: ImageConfig{
Default: wsk.Spec.PodTemplate.Options.ImageConfig.Spawner.Default,
Values: buildImageConfigValues(wsk.Spec.PodTemplate.Options.ImageConfig),
Values: buildImageConfigValues(wsk.Spec.PodTemplate.Options.ImageConfig, statusImageConfigMap),
},
PodConfig: PodConfig{
Default: wsk.Spec.PodTemplate.Options.PodConfig.Spawner.Default,
Values: buildPodConfigValues(wsk.Spec.PodTemplate.Options.PodConfig),
Values: buildPodConfigValues(wsk.Spec.PodTemplate.Options.PodConfig, statusPodConfigMap),
},
},
},
}
}

func buildImageConfigValues(imageConfig kubefloworgv1beta1.ImageConfig) []ImageConfigValue {
func buildOptionMetricsMap(metrics []kubefloworgv1beta1.OptionMetric) map[string]int32 {
resultMap := make(map[string]int32)
for _, metric := range metrics {
resultMap[metric.Id] = metric.Workspaces
}
return resultMap
}
Comment on lines +91 to +97
Copy link
Contributor

@andyatmiami andyatmiami Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am legitimately torn here on a subtle and potentially in-this-context-unimportant implementation decision 😇

for _, metric := range metrics will perform a copy on the elements defined in metrics

To avoid the copy - you can declare the for loop as: for i := range metrics

However, I am then unsure how the body of the loop should be constructed

avoid copy (readable): metric := &metrics[i]

  • as this is a pointer - if in the future someone modified this implementation for some reason to change this value - it would also change the actual element in the slice

avoid copy (less readable): resultMap[metrics[i].Id] = metrics[i].Workspaces

  • less likely for someone in future to "accidentally" re-assign a value - but also not as visually pleasing to read

explicit copy: metric := metrics[i]

  • intent more clear as compared to for _, metric := range metrics
  • as these structs are small - copy overhead not a concern

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given these objects we are handling are inherently small (and bounded to be reasonably sure they will always be small) - the code here as written is sufficient.


func buildImageConfigValues(imageConfig kubefloworgv1beta1.ImageConfig, statusImageConfigMap map[string]int32) []ImageConfigValue {
imageConfigValues := make([]ImageConfigValue, len(imageConfig.Values))
for i := range imageConfig.Values {
option := imageConfig.Values[i]
Expand All @@ -93,12 +107,16 @@ func buildImageConfigValues(imageConfig kubefloworgv1beta1.ImageConfig) []ImageC
Labels: buildOptionLabels(option.Spawner.Labels),
Hidden: ptr.Deref(option.Spawner.Hidden, false),
Redirect: buildOptionRedirect(option.Redirect),
// TODO: in the future will need to support including exactly one of clusterMetrics or namespaceMetrics based on request context
ClusterMetrics: clusterMetrics{
Workspaces: statusImageConfigMap[option.Id],
},
}
}
return imageConfigValues
}

func buildPodConfigValues(podConfig kubefloworgv1beta1.PodConfig) []PodConfigValue {
func buildPodConfigValues(podConfig kubefloworgv1beta1.PodConfig, statusPodConfigMap map[string]int32) []PodConfigValue {
podConfigValues := make([]PodConfigValue, len(podConfig.Values))
for i := range podConfig.Values {
option := podConfig.Values[i]
Expand All @@ -109,6 +127,10 @@ func buildPodConfigValues(podConfig kubefloworgv1beta1.PodConfig) []PodConfigVal
Labels: buildOptionLabels(option.Spawner.Labels),
Hidden: ptr.Deref(option.Spawner.Hidden, false),
Redirect: buildOptionRedirect(option.Redirect),
// TODO: in the future will need to support including exactly one of clusterMetrics or namespaceMetrics based on request context
ClusterMetrics: clusterMetrics{
Workspaces: statusPodConfigMap[option.Id],
},
}
}
return podConfigValues
Expand Down
49 changes: 28 additions & 21 deletions workspaces/backend/internal/models/workspacekinds/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,20 @@ limitations under the License.
package workspacekinds

type WorkspaceKind struct {
Name string `json:"name"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
Deprecated bool `json:"deprecated"`
DeprecationMessage string `json:"deprecationMessage"`
Hidden bool `json:"hidden"`
Icon ImageRef `json:"icon"`
Logo ImageRef `json:"logo"`
PodTemplate PodTemplate `json:"podTemplate"`
Name string `json:"name"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
Deprecated bool `json:"deprecated"`
DeprecationMessage string `json:"deprecationMessage"`
Hidden bool `json:"hidden"`
Icon ImageRef `json:"icon"`
Logo ImageRef `json:"logo"`
ClusterMetrics clusterMetrics `json:"clusterMetrics,omitempty"`
PodTemplate PodTemplate `json:"podTemplate"`
}

type clusterMetrics struct {
Workspaces int32 `json:"workspacesCount"`
}

type ImageRef struct {
Expand Down Expand Up @@ -58,12 +63,13 @@ type ImageConfig struct {
}

type ImageConfigValue struct {
Id string `json:"id"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
Labels []OptionLabel `json:"labels"`
Hidden bool `json:"hidden"`
Redirect *OptionRedirect `json:"redirect,omitempty"`
Id string `json:"id"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
Labels []OptionLabel `json:"labels"`
Hidden bool `json:"hidden"`
Redirect *OptionRedirect `json:"redirect,omitempty"`
ClusterMetrics clusterMetrics `json:"clusterMetrics,omitempty"`
}

type PodConfig struct {
Expand All @@ -72,12 +78,13 @@ type PodConfig struct {
}

type PodConfigValue struct {
Id string `json:"id"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
Labels []OptionLabel `json:"labels"`
Hidden bool `json:"hidden"`
Redirect *OptionRedirect `json:"redirect,omitempty"`
Id string `json:"id"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
Labels []OptionLabel `json:"labels"`
Hidden bool `json:"hidden"`
Redirect *OptionRedirect `json:"redirect,omitempty"`
ClusterMetrics clusterMetrics `json:"clusterMetrics,omitempty"`
}

type OptionLabel struct {
Expand Down
17 changes: 17 additions & 0 deletions workspaces/backend/openapi/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,9 @@ const docTemplate = `{
"workspacekinds.ImageConfigValue": {
"type": "object",
"properties": {
"clusterMetrics": {
"$ref": "#/definitions/workspacekinds.clusterMetrics"
},
"description": {
"type": "string"
},
Expand Down Expand Up @@ -848,6 +851,9 @@ const docTemplate = `{
"workspacekinds.PodConfigValue": {
"type": "object",
"properties": {
"clusterMetrics": {
"$ref": "#/definitions/workspacekinds.clusterMetrics"
},
"description": {
"type": "string"
},
Expand Down Expand Up @@ -948,6 +954,9 @@ const docTemplate = `{
"workspacekinds.WorkspaceKind": {
"type": "object",
"properties": {
"clusterMetrics": {
"$ref": "#/definitions/workspacekinds.clusterMetrics"
},
"deprecated": {
"type": "boolean"
},
Expand Down Expand Up @@ -977,6 +986,14 @@ const docTemplate = `{
}
}
},
"workspacekinds.clusterMetrics": {
"type": "object",
"properties": {
"workspacesCount": {
"type": "integer"
}
}
},
"workspaces.Activity": {
"type": "object",
"properties": {
Expand Down
17 changes: 17 additions & 0 deletions workspaces/backend/openapi/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,9 @@
"workspacekinds.ImageConfigValue": {
"type": "object",
"properties": {
"clusterMetrics": {
"$ref": "#/definitions/workspacekinds.clusterMetrics"
},
"description": {
"type": "string"
},
Expand Down Expand Up @@ -846,6 +849,9 @@
"workspacekinds.PodConfigValue": {
"type": "object",
"properties": {
"clusterMetrics": {
"$ref": "#/definitions/workspacekinds.clusterMetrics"
},
"description": {
"type": "string"
},
Expand Down Expand Up @@ -946,6 +952,9 @@
"workspacekinds.WorkspaceKind": {
"type": "object",
"properties": {
"clusterMetrics": {
"$ref": "#/definitions/workspacekinds.clusterMetrics"
},
"deprecated": {
"type": "boolean"
},
Expand Down Expand Up @@ -975,6 +984,14 @@
}
}
},
"workspacekinds.clusterMetrics": {
"type": "object",
"properties": {
"workspacesCount": {
"type": "integer"
}
}
},
"workspaces.Activity": {
"type": "object",
"properties": {
Expand Down