Add customizable plugin display names and weights (#8459)
* feat: add customizable plugin display names and weights - Add weight field to JobTypeCapability proto message - Modify ListKnownJobTypes() to return JobTypeInfo with display names and weights - Modify ListPluginJobTypes() to return JobTypeInfo instead of string - Sort plugins by weight (descending) then alphabetically - Update admin API to return enriched job type metadata - Update plugin UI template to display names instead of IDs - Consolidate API by reusing existing function names instead of suffixed variants * perf: optimize plugin job type capability lookup and add null-safe parsing - Pre-calculate job type capabilities in a map to reduce O(n*m) nested loops to O(n+m) lookup time in ListKnownJobTypes() - Add parseJobTypeItem() helper function for null-safe job type item parsing - Refactor plugin.templ to use parseJobTypeItem() in all job type access points (hasJobType, applyInitialNavigation, ensureActiveNavigation, renderTopTabs) - Deterministic capability resolution by using first worker's capability * templ * refactor: use parseJobTypeItem helper consistently in plugin.templ Replace duplicated job type extraction logic at line 1296-1298 with parseJobTypeItem() helper function for consistency and maintainability. * improve: prefer richer capability metadata and add null-safety checks - Improve capability selection in ListKnownJobTypes() to prefer capabilities with non-empty DisplayName and higher Weight across all workers instead of first-wins approach. Handles mixed-version clusters better. - Add defensive null checks in renderJobTypeSummary() to safely access parseJobTypeItem() result before property access - Ensures malformed or missing entries won't break the rendering pipeline * fix: preserve existing DisplayName when merging capabilities Fix capability merge logic to respect existing DisplayName values: - If existing has DisplayName but candidate doesn't, preserve existing - If existing doesn't have DisplayName but candidate does, use candidate - Only use Weight comparison if DisplayName status is equal - Prevents higher-weight capabilities with empty DisplayName from overriding capabilities with non-empty DisplayName
This commit is contained in:
@@ -34,6 +34,13 @@ type Options struct {
|
||||
ClusterContextProvider func(context.Context) (*plugin_pb.ClusterContext, error)
|
||||
}
|
||||
|
||||
// JobTypeInfo contains metadata about a plugin job type.
|
||||
type JobTypeInfo struct {
|
||||
JobType string `json:"job_type"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Weight int32 `json:"weight"`
|
||||
}
|
||||
|
||||
type Plugin struct {
|
||||
plugin_pb.UnimplementedPluginControlServiceServer
|
||||
|
||||
@@ -623,7 +630,7 @@ func (r *Plugin) ListWorkers() []*WorkerSession {
|
||||
return r.registry.List()
|
||||
}
|
||||
|
||||
func (r *Plugin) ListKnownJobTypes() ([]string, error) {
|
||||
func (r *Plugin) ListKnownJobTypes() ([]JobTypeInfo, error) {
|
||||
registryJobTypes := r.registry.JobTypes()
|
||||
storedJobTypes, err := r.store.ListJobTypes()
|
||||
if err != nil {
|
||||
@@ -638,12 +645,72 @@ func (r *Plugin) ListKnownJobTypes() ([]string, error) {
|
||||
jobTypeSet[jobType] = struct{}{}
|
||||
}
|
||||
|
||||
out := make([]string, 0, len(jobTypeSet))
|
||||
jobTypeList := make([]string, 0, len(jobTypeSet))
|
||||
for jobType := range jobTypeSet {
|
||||
out = append(out, jobType)
|
||||
jobTypeList = append(jobTypeList, jobType)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out, nil
|
||||
sort.Strings(jobTypeList)
|
||||
|
||||
result := make([]JobTypeInfo, 0, len(jobTypeList))
|
||||
workers := r.registry.List()
|
||||
|
||||
// Pre-calculate the best capability for each job type from available workers.
|
||||
// Prefer capabilities with non-empty DisplayName, then higher Weight.
|
||||
jobTypeToCap := make(map[string]*plugin_pb.JobTypeCapability)
|
||||
for _, worker := range workers {
|
||||
for jobType, cap := range worker.Capabilities {
|
||||
if cap == nil {
|
||||
continue
|
||||
}
|
||||
existing, exists := jobTypeToCap[jobType]
|
||||
if !exists || existing == nil {
|
||||
jobTypeToCap[jobType] = cap
|
||||
continue
|
||||
}
|
||||
// Preserve existing if it has DisplayName but cap doesn't.
|
||||
if existing.DisplayName != "" && cap.DisplayName == "" {
|
||||
continue
|
||||
}
|
||||
// Prefer capabilities with a non-empty DisplayName.
|
||||
if existing.DisplayName == "" && cap.DisplayName != "" {
|
||||
jobTypeToCap[jobType] = cap
|
||||
continue
|
||||
}
|
||||
// If DisplayName statuses are equal, prefer higher Weight.
|
||||
if cap.Weight > existing.Weight {
|
||||
jobTypeToCap[jobType] = cap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, jobType := range jobTypeList {
|
||||
info := JobTypeInfo{JobType: jobType}
|
||||
|
||||
// Get display name and weight from pre-calculated capabilities
|
||||
if cap, ok := jobTypeToCap[jobType]; ok && cap != nil {
|
||||
if cap.DisplayName != "" {
|
||||
info.DisplayName = cap.DisplayName
|
||||
}
|
||||
info.Weight = cap.Weight
|
||||
}
|
||||
|
||||
// Default display name to job type if not set
|
||||
if info.DisplayName == "" {
|
||||
info.DisplayName = jobType
|
||||
}
|
||||
|
||||
result = append(result, info)
|
||||
}
|
||||
|
||||
// Sort by weight (descending) then by job type (ascending)
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
if result[i].Weight != result[j].Weight {
|
||||
return result[i].Weight > result[j].Weight // higher weight first
|
||||
}
|
||||
return result[i].JobType < result[j].JobType // alphabetical as tiebreaker
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FilterProposalsWithActiveJobs drops proposals that are already assigned/running.
|
||||
|
||||
Reference in New Issue
Block a user