pkg/inspection/metadata/form/form.go (108 lines of code) (raw):
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package form
import (
"fmt"
"slices"
"sync"
"github.com/GoogleCloudPlatform/khi/pkg/common/typedmap"
"github.com/GoogleCloudPlatform/khi/pkg/inspection/metadata"
"github.com/GoogleCloudPlatform/khi/pkg/server/upload"
"github.com/GoogleCloudPlatform/khi/pkg/task"
)
var FormFieldSetMetadataKey = metadata.NewMetadataKey[*FormFieldSet]("form")
// ParameterInputType represents the type of parameter form field.
type ParameterInputType string
const (
// Group is a type of ParameetrInputType. This contains multiple children fields.
Group ParameterInputType = "group"
// Text is a type of ParameterInputType. This represents the text type input field.
Text ParameterInputType = "text"
// File is a type of ParameterInputType. This represents the file type input field.
File ParameterInputType = "file"
)
// ParameterHintType represents the types of hint message shown at the bottom of parameter forms.
type ParameterHintType string
const (
// None is a type of ParameterHintType. Frontend will supress hint when this type is given.
None ParameterHintType = "none"
// Error is a type of ParameterHintType. Frontend will prevent user to click the button to go the next page when there is at least a field with a hint of this type.
Error ParameterHintType = "error"
// Warning is a type of ParameterHintType. User may need be cautious about the field, but user can navigate the form to the next step with a field having this type hint.
Warning ParameterHintType = "warning"
// Info is a type of ParameterHintType. It's a supplemntal hint just for helping user.
Info ParameterHintType = "info"
)
type ParameterFormField interface{}
// ParameterFormFieldBase is the base type of parameter form fields.
type ParameterFormFieldBase struct {
// Priority is a number used in sorting order.
Priority int `json:"-"`
// ID is a unique name of the form field.
ID string `json:"id"`
// Type is the ParameterInputType of this field.
Type ParameterInputType `json:"type"`
// Label is a short human readable title of this field. This is visible on the form.
Label string `json:"label"`
// Description is a human readable explaination of this field.
Description string `json:"description"`
// HintType is the ParameterHintType of the Hint field of this parameter field.
HintType ParameterHintType `json:"hintType"`
// Hint is the message shown under the form field. Assign HintType as well when you assign a value to this field.
Hint string `json:"hint"`
}
// GroupParameterFormField represents Group type parameter specific data.
type GroupParameterFormField struct {
ParameterFormFieldBase
// Children is the children of this field.
Children []ParameterFormField `json:"children"`
// Collapsible is true when user can collapse or expand the grouped fields.
Collapsible bool `json:"collapsible"`
// CollapsedByDefault is true when the form is collapsed at first time when the form shows up.
CollapsedByDefault bool `json:"collapsedByDefault"`
}
// TextParameterFormField represents Text type parameter specific data.
type TextParameterFormField struct {
ParameterFormFieldBase
// Readonly limits users to modify the field.
Readonly bool `json:"readonly"`
// Default is the default value of this field.
Default string `json:"default"`
// Suggestion is the auto complete drop down values.
Suggestions []string `json:"suggestions"`
}
// FileParameterFormField represents File type parameter specific data.
type FileParameterFormField struct {
ParameterFormFieldBase
// Token is the type used for specifying the destination of file. This value is generated from server side and the client will upload the file with the token.
Token upload.UploadToken `json:"token"`
// Status is the current status of the file.
Status upload.UploadStatus `json:"status"`
}
// FormFieldSet is a metadata type used in frontend to generate the form fields.
type FormFieldSet struct {
fieldsLock sync.RWMutex
fields []ParameterFormField
}
var _ metadata.Metadata = (*FormFieldSet)(nil)
// Labels implements Metadata.
func (*FormFieldSet) Labels() *typedmap.ReadonlyTypedMap {
return task.NewLabelSet(metadata.IncludeInDryRunResult())
}
func (f *FormFieldSet) ToSerializable() interface{} {
return f.fields
}
func (f *FormFieldSet) SetField(newField ParameterFormField) error {
f.fieldsLock.Lock()
defer f.fieldsLock.Unlock()
newFieldBase := GetParameterFormFieldBase(newField)
if newFieldBase.ID == "" {
return fmt.Errorf("id must not be empty")
}
for _, field := range f.fields {
fieldBase := GetParameterFormFieldBase(field)
if fieldBase.ID == newFieldBase.ID {
return fmt.Errorf("id %s is already used", newFieldBase.ID)
}
}
f.fields = append(f.fields, newField)
slices.SortFunc(f.fields, func(a, b ParameterFormField) int {
return GetParameterFormFieldBase(b).Priority - GetParameterFormFieldBase(a).Priority
})
return nil
}
// DangerouslyGetField shouldn't be used in non testing code. Because a field shouldn't depend on the other field metadata.
// This is only for testing purpose.
func (f *FormFieldSet) DangerouslyGetField(id string) ParameterFormField {
f.fieldsLock.RLock()
defer f.fieldsLock.RUnlock()
for _, field := range f.fields {
if GetParameterFormFieldBase(field).ID == id {
return field
}
}
return ParameterFormFieldBase{}
}
// GetParameterFormFieldBase returns the ParameterFormFieldBase from the given ParameterFormField.
func GetParameterFormFieldBase(parameter ParameterFormField) ParameterFormFieldBase {
switch v := parameter.(type) {
case GroupParameterFormField:
return v.ParameterFormFieldBase
case TextParameterFormField:
return v.ParameterFormFieldBase
case FileParameterFormField:
return v.ParameterFormFieldBase
default:
return ParameterFormFieldBase{}
}
}
func NewFormFieldSet() *FormFieldSet {
return &FormFieldSet{
fields: make([]ParameterFormField, 0),
}
}