Skip to content

[dashboard] Add storageClass dropdown for stateful apps#2131

Merged
sircthulhu merged 1 commit intomainfrom
feat/storageclass-dropdown
Mar 2, 2026
Merged

[dashboard] Add storageClass dropdown for stateful apps#2131
sircthulhu merged 1 commit intomainfrom
feat/storageclass-dropdown

Conversation

@sircthulhu
Copy link
Contributor

@sircthulhu sircthulhu commented Mar 2, 2026

What this PR does

Replaces the plain text input for storageClass fields with an
API-backed dropdown listing available StorageClasses from the cluster.
Follows the same pattern as the instanceType dropdown for VMInstance.

Affected applications:

  • Top-level spec.storageClass: ClickHouse, Harbor, HTTPCache,
    Kubernetes, MariaDB, MongoDB, NATS, OpenBAO, Postgres, Qdrant,
    RabbitMQ, Redis, VMDisk
  • Nested spec.storage.storageClass: FoundationDB
  • Nested spec.kafka.storageClass / spec.zookeeper.storageClass: Kafka

Release note

[dashboard] storageClass fields in stateful app forms now render as a
dropdown populated with available StorageClasses from the cluster,
instead of a free-text input.

Summary by CodeRabbit

Release Notes

  • New Features

    • Storage class selection dropdowns now available in configuration forms for multiple database, messaging, and storage services.
  • Tests

    • Added comprehensive test coverage for storage class configuration handling.

Replace plain text input with an API-backed listInput dropdown for
storageClass fields across all applications that expose them.

The dropdown fetches available StorageClasses from the cluster via
/api/clusters/{cluster}/k8s/apis/storage.k8s.io/v1/storageclasses,
following the same pattern as the instanceType dropdown for VMInstance.

Top-level spec.storageClass: ClickHouse, Harbor, HTTPCache, Kubernetes,
MariaDB, MongoDB, NATS, OpenBAO, Postgres, Qdrant, RabbitMQ, Redis, VMDisk.

Nested paths: FoundationDB (spec.storage.storageClass),
Kafka (spec.kafka.storageClass and spec.zookeeper.storageClass).

Co-Authored-By: Claude <[email protected]>
Signed-off-by: Kirill Ilin <[email protected]>
@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. enhancement New feature or request labels Mar 2, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the user experience for configuring stateful applications within the dashboard. By transitioning storageClass input fields from free-text to dynamic, API-backed dropdowns, users can now easily select from available StorageClasses directly from the cluster. This change streamlines the deployment process for a wide range of applications, reducing potential configuration errors and enhancing usability.

Highlights

  • Enhanced User Experience: Replaced plain text input fields for storageClass with an API-backed dropdown, improving usability and reducing configuration errors.
  • Broad Application Support: The storageClass dropdown has been implemented for numerous stateful applications, including ClickHouse, Harbor, HTTPCache, Kubernetes, MariaDB, MongoDB, NATS, OpenBAO, Postgres, Qdrant, RabbitMQ, Redis, VMDisk, FoundationDB, and Kafka.
  • Dynamic StorageClass Selection: The dropdown dynamically populates with available StorageClasses directly from the Kubernetes cluster, ensuring accurate and up-to-date options.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • internal/controller/dashboard/customformsoverride.go
    • Implemented logic to override storageClass fields with a listInput type for various application kinds.
    • Introduced a new helper function storageClassListInput to define the configuration for the API-backed dropdown.
  • internal/controller/dashboard/customformsoverride_test.go
    • Added new test functions TestApplyListInputOverrides_StorageClassSimple, TestApplyListInputOverrides_StorageClassFoundationDB, and TestApplyListInputOverrides_StorageClassKafka to verify the correct application of storageClass dropdowns.
    • Included a helper assertion function assertStorageClassListInput to validate the structure of the generated listInput fields.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

📝 Walkthrough

Walkthrough

The changes add support for API-backed storageClass dropdowns across multiple Kubernetes resource kinds in the dashboard custom forms, enabling dynamic selection of available StorageClasses via the cluster's StorageClasses API.

Changes

Cohort / File(s) Summary
StorageClass ListInput Implementation
internal/controller/dashboard/customformsoverride.go
Introduces storageClassListInput() helper function and applies storageClass field overrides via applyListInputOverrides() for 13 resource kinds (ClickHouse, Harbor, HTTPCache, Kubernetes, MariaDB, MongoDB, NATS, OpenBAO, Postgres, Qdrant, RabbitMQ, Redis, VMDisk), with nested path support for FoundationDB (spec.storage.storageClass) and Kafka (spec.kafka and spec.zookeeper).
StorageClass Test Coverage
internal/controller/dashboard/customformsoverride_test.go
Adds three new test functions validating storageClass listInput configuration across simple kinds, FoundationDB, and Kafka. Introduces assertStorageClassListInput() helper to verify listInput type and storageClasses API URI consistency.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A rabbit hops through form fields bright,
StorageClass dropdowns, what a sight!
From ClickHouse to Redis, they shine,
API-backed lists, so divine!
No more typing paths by hand—
The dashboard now understands!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a storageClass dropdown for stateful applications, which is the primary objective of this PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/storageclass-dropdown

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a useful feature by replacing the free-text input for storageClass with a dropdown populated from the cluster's available StorageClasses. The implementation correctly handles different application kinds with varying schema structures. The accompanying tests provide good coverage for the new logic. I've included a couple of suggestions to improve the maintainability of the code, primarily around reducing code duplication between the implementation and tests, and making the test code more robust.

Comment on lines +237 to +238
"ClickHouse", "Harbor", "HTTPCache", "Kubernetes", "MariaDB", "MongoDB",
"NATS", "OpenBAO", "Postgres", "Qdrant", "RabbitMQ", "Redis", "VMDisk",
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This list of kinds is a duplicate of the list in the case statement in applyListInputOverrides in customformsoverride.go. This duplication can lead to maintenance issues where one list is updated but not the other. To improve this, consider defining this list in a single place (e.g., a package-level variable) and reusing it in both the implementation and the tests. This would make the code more robust to future changes.

schema := map[string]any{}
applyListInputOverrides(schema, kind, map[string]any{})

specProps := schema["properties"].(map[string]any)["spec"].(map[string]any)["properties"].(map[string]any)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This long chain of type assertions and map lookups is fragile and repeated across the new tests. For example, this line is already quite long, and the one in TestApplyListInputOverrides_StorageClassFoundationDB (line 258) is even longer. A small change in the schema structure could break these tests in hard-to-debug ways. Consider creating a helper function within the test file to safely navigate the schema and retrieve nested properties. This would make the tests cleaner, more readable, and more robust.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/controller/dashboard/customformsoverride_test.go (1)

286-299: Harden assertStorageClassListInput to validate key mappings too.

Right now it checks only type and valueUri; a regression in keysToValue/keysToLabel would still pass.

✅ Suggested assertion extension
 func assertStorageClassListInput(t *testing.T, field map[string]any) {
 	t.Helper()
 	if field["type"] != "listInput" {
 		t.Errorf("expected type listInput, got %v", field["type"])
 	}
 	customProps, ok := field["customProps"].(map[string]any)
 	if !ok {
 		t.Fatal("customProps not found")
 	}
 	expectedURI := "/api/clusters/{cluster}/k8s/apis/storage.k8s.io/v1/storageclasses"
 	if customProps["valueUri"] != expectedURI {
 		t.Errorf("expected valueUri %s, got %v", expectedURI, customProps["valueUri"])
 	}
+	expected0, expected1 := "metadata", "name"
+	if got, ok := customProps["keysToValue"].([]any); !ok || len(got) != 2 || got[0] != expected0 || got[1] != expected1 {
+		t.Errorf("expected keysToValue [metadata name], got %v", customProps["keysToValue"])
+	}
+	if got, ok := customProps["keysToLabel"].([]any); !ok || len(got) != 2 || got[0] != expected0 || got[1] != expected1 {
+		t.Errorf("expected keysToLabel [metadata name], got %v", customProps["keysToLabel"])
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/dashboard/customformsoverride_test.go` around lines 286 -
299, The test helper assertStorageClassListInput only checks field["type"] and
customProps["valueUri"], so it won't catch regressions in
keysToValue/keysToLabel mappings; update assertStorageClassListInput to also
assert that customProps contains the expected keysToValue and keysToLabel
entries (e.g., ensure customProps["keysToValue"] and customProps["keysToLabel"]
exist, are the expected strings or arrays used by the UI, and map the correct
fields), and fail the test with descriptive errors if those keys are missing or
incorrect so regressions in keysToValue/keysToLabel are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/controller/dashboard/customformsoverride.go`:
- Around line 218-231: The new overrides replace any existing OpenAPI default
for storageClass when assigning storageClassListInput(), so preserve an existing
default value from the target schema node before overwriting: for each place you
set specProps["storageClass"], storageProps["storageClass"],
kafkaProps["storageClass"], and zkProps["storageClass"], read the current
props["storageClass"] (if present) and copy its "default" into the object
returned by storageClassListInput() (e.g. new := storageClassListInput(); if
old, ok := props["storageClass"].(map[string]interface{}); ok { if d, has :=
old["default"]; has { new["default"] = d } }; props["storageClass"] = new),
using ensureSchemaPath and storageClassListInput as shown.

---

Nitpick comments:
In `@internal/controller/dashboard/customformsoverride_test.go`:
- Around line 286-299: The test helper assertStorageClassListInput only checks
field["type"] and customProps["valueUri"], so it won't catch regressions in
keysToValue/keysToLabel mappings; update assertStorageClassListInput to also
assert that customProps contains the expected keysToValue and keysToLabel
entries (e.g., ensure customProps["keysToValue"] and customProps["keysToLabel"]
exist, are the expected strings or arrays used by the UI, and map the correct
fields), and fail the test with descriptive errors if those keys are missing or
incorrect so regressions in keysToValue/keysToLabel are caught.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b455405 and c2bf8cf.

📒 Files selected for processing (2)
  • internal/controller/dashboard/customformsoverride.go
  • internal/controller/dashboard/customformsoverride_test.go

Comment on lines +218 to +231
case "ClickHouse", "Harbor", "HTTPCache", "Kubernetes", "MariaDB", "MongoDB",
"NATS", "OpenBAO", "Postgres", "Qdrant", "RabbitMQ", "Redis", "VMDisk":
specProps := ensureSchemaPath(schema, "spec")
specProps["storageClass"] = storageClassListInput()

case "FoundationDB":
storageProps := ensureSchemaPath(schema, "spec", "storage")
storageProps["storageClass"] = storageClassListInput()

case "Kafka":
kafkaProps := ensureSchemaPath(schema, "spec", "kafka")
kafkaProps["storageClass"] = storageClassListInput()
zkProps := ensureSchemaPath(schema, "spec", "zookeeper")
zkProps["storageClass"] = storageClassListInput()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential regression: storageClass defaults are dropped by the new overrides.

Line 221/225/229 assign a fresh listInput schema but never carry over an existing OpenAPI default, unlike the VMInstance handling at Line 200-Line 204. If any affected CRD defines a default storageClass, the form will stop pre-populating it.

💡 Proposed fix (preserve default when available)
 case "ClickHouse", "Harbor", "HTTPCache", "Kubernetes", "MariaDB", "MongoDB",
 	"NATS", "OpenBAO", "Postgres", "Qdrant", "RabbitMQ", "Redis", "VMDisk":
 	specProps := ensureSchemaPath(schema, "spec")
-	specProps["storageClass"] = storageClassListInput()
+	field := storageClassListInput()
+	if prop, _ := openAPIProps["storageClass"].(map[string]any); prop != nil {
+		if def := prop["default"]; def != nil {
+			field["default"] = def
+		}
+	}
+	specProps["storageClass"] = field
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/dashboard/customformsoverride.go` around lines 218 - 231,
The new overrides replace any existing OpenAPI default for storageClass when
assigning storageClassListInput(), so preserve an existing default value from
the target schema node before overwriting: for each place you set
specProps["storageClass"], storageProps["storageClass"],
kafkaProps["storageClass"], and zkProps["storageClass"], read the current
props["storageClass"] (if present) and copy its "default" into the object
returned by storageClassListInput() (e.g. new := storageClassListInput(); if
old, ok := props["storageClass"].(map[string]interface{}); ok { if d, has :=
old["default"]; has { new["default"] = d } }; props["storageClass"] = new),
using ensureSchemaPath and storageClassListInput as shown.

@sircthulhu sircthulhu self-assigned this Mar 2, 2026
Copy link
Member

@kvaps kvaps left a comment

Choose a reason for hiding this comment

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

LGTM

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Mar 2, 2026
@sircthulhu sircthulhu enabled auto-merge March 2, 2026 18:19
@sircthulhu sircthulhu merged commit cb919f4 into main Mar 2, 2026
14 of 15 checks passed
@sircthulhu sircthulhu deleted the feat/storageclass-dropdown branch March 2, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request lgtm This PR has been approved by a maintainer size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants