Files
portainer/api/http/handler/endpoints/sort.go
T
bernard-portainer 76f525fd38 refactor(home): refactor Environment List to use SortableList component [C9S-131] (#2522)
- Migrate `EnvironmentList` from `GroupSortTable` to `SortableList`, removing ~1,700 lines of duplicated component code
- Move health sort ranking to the backend (`sort.go`), adding `Health` and `Id` sort keys
- Delete `GroupSortTable`, `GroupSortTableGroupRow`, `useGroupSortTableState`, and `store` — functionality absorbed by `SortableList`
- Add `useHomeViewState` hook to centralise home view URL state (`groupBy`, `groupFilter`, `order`, `page`, `search`)
- Update `useTableStateFromUrl` to support `groupBy` and `groupFilter` URL params with a `buildExtra` callback
- Rename URL param `filter` → `groupFilter` for clarity; add `search` and `order` to `/home` route definition
- Simplify `EnvironmentList` props — remove `headerFilter` / `onHeaderFilterChange`, leaving only `onClickBrowse`
- Add `computeSortDesc` pure utility to `SortableList` and cover all toggle/reset cases with unit tests
- Update `SortableListHeader` to use `activeKey` prop (renamed from `sortBy`); fix all callsites and stories
- Fix `SortableList` sort-key normalisation to be case-insensitive; update tests to reflect no-match behaviour
2026-05-08 16:55:40 +12:00

125 lines
3.0 KiB
Go

package endpoints
import (
"slices"
"github.com/fvbommel/sortorder"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/endpointutils"
)
type comp[T any] func(a, b T) int
func stringComp(a, b string) int {
if sortorder.NaturalLess(a, b) {
return -1
} else if sortorder.NaturalLess(b, a) {
return 1
} else {
return 0
}
}
func healthRank(endpoint *portainer.Endpoint, settings *portainer.Settings) int {
status := resolveEndpointStatus(endpoint, settings)
if status == statusDown {
return 0
}
if isOutdated(endpoint) {
return 1
}
if status == statusHeartbeat {
return 2
}
return 3
}
func sortEnvironmentsByField(environments []portainer.Endpoint, environmentGroups []portainer.EndpointGroup, sortField sortKey, isSortDesc bool, settings *portainer.Settings) {
if sortField == "" {
return
}
var less comp[portainer.Endpoint]
switch sortField {
case sortKeyName:
less = func(a, b portainer.Endpoint) int {
return stringComp(a.Name, b.Name)
}
case sortKeyGroup:
environmentGroupNames := make(map[portainer.EndpointGroupID]string, 0)
for _, group := range environmentGroups {
environmentGroupNames[group.ID] = group.Name
}
// set the "unassigned" group name to be empty string
environmentGroupNames[1] = ""
less = func(a, b portainer.Endpoint) int {
aGroup := environmentGroupNames[a.GroupID]
bGroup := environmentGroupNames[b.GroupID]
return stringComp(aGroup, bGroup)
}
case sortKeyStatus:
less = func(a, b portainer.Endpoint) int {
return int(a.Status - b.Status)
}
case sortKeyLastCheckInDate:
less = func(a, b portainer.Endpoint) int {
return int(a.LastCheckInDate - b.LastCheckInDate)
}
case sortKeyEdgeID:
less = func(a, b portainer.Endpoint) int {
return stringComp(a.EdgeID, b.EdgeID)
}
case sortKeyPlatformType:
less = func(a, b portainer.Endpoint) int {
return int(endpointutils.EndpointPlatformType(&a) - endpointutils.EndpointPlatformType(&b))
}
case sortKeyHealth:
less = func(a, b portainer.Endpoint) int {
return healthRank(&a, settings) - healthRank(&b, settings)
}
case sortKeyId:
less = func(a, b portainer.Endpoint) int {
return int(a.ID - b.ID)
}
}
slices.SortStableFunc(environments, func(a, b portainer.Endpoint) int {
mul := 1
if isSortDesc {
mul = -1
}
return less(a, b) * mul
})
}
type sortKey string
const (
sortKeyName sortKey = "Name"
sortKeyGroup sortKey = "Group"
sortKeyStatus sortKey = "Status"
sortKeyLastCheckInDate sortKey = "LastCheckIn"
sortKeyEdgeID sortKey = "EdgeID"
sortKeyPlatformType sortKey = "PlatformType"
sortKeyHealth sortKey = "Health"
sortKeyId sortKey = "Id"
)
func getSortKey(sortField string) sortKey {
fieldAsSortKey := sortKey(sortField)
if slices.Contains([]sortKey{sortKeyName, sortKeyGroup, sortKeyStatus, sortKeyLastCheckInDate, sortKeyEdgeID, sortKeyPlatformType, sortKeyHealth, sortKeyId}, fieldAsSortKey) {
return fieldAsSortKey
}
return ""
}