Skip to content

Commit

Permalink
feat: add sorting support for topology query
Browse files Browse the repository at this point in the history
  • Loading branch information
yashmehrotra authored and moshloop committed Jan 24, 2024
1 parent 20ec594 commit cfbee68
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 0 deletions.
55 changes: 55 additions & 0 deletions query/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@ import (
"github.com/flanksource/duty/context"
"github.com/flanksource/duty/models"
"github.com/jackc/pgx/v5"
"github.com/samber/lo"
)

const DefaultDepth = 5

type TopologyQuerySortBy string

const (
TopologyQuerySortByName TopologyQuerySortBy = "name"
TopologyQuerySortByField TopologyQuerySortBy = "field:"
)

type TopologyOptions struct {
ID string
Owner string
Expand All @@ -26,6 +34,9 @@ type TopologyOptions struct {
Types []string
Status []string

SortBy TopologyQuerySortBy
SortOrder string

// when set to true, only the children (except the direct children) are returned.
// when set to false, the direct children & the parent itself is fetched.
nonDirectChildrenOnly bool
Expand Down Expand Up @@ -253,18 +264,62 @@ func Topology(ctx context.Context, params TopologyOptions) (*TopologyResponse, e

// Remove fields from children that aren't required by the UI
root := response.Components

if len(root) == 1 {
for j := range root[0].Components {
removeComponentFields(root[0].Components[j].Components)
}

if params.SortBy != "" {
sortComponents(root[0].Components, params.SortBy, params.SortOrder != "desc")
}
} else {
for i := range root {
removeComponentFields(root[i].Components)
}

if params.SortBy != "" {
sortComponents(root, params.SortBy, params.SortOrder != "desc")
}
}
return &response, nil
}

func sortComponents(c models.Components, sortBy TopologyQuerySortBy, asc bool) {
switch {
case sortBy == TopologyQuerySortByName:
sort.Slice(c, func(i, j int) bool {
if !asc {
i, j = j, i
}
return c[i].Name < c[j].Name
})

case strings.HasPrefix(string(sortBy), string(TopologyQuerySortByField)):
field := strings.TrimPrefix(string(sortBy), string(TopologyQuerySortByField))
isTextProperty := lo.Reduce(c, func(val bool, comp *models.Component, _ int) bool {
return val && comp.Properties.Find(field).Text != ""
}, true)

sort.Slice(c, func(i, j int) bool {
if !asc {
i, j = j, i
}
propI := c[i].Properties.Find(field)
propJ := c[j].Properties.Find(field)
if propI == nil || propJ == nil {
return false
}

if isTextProperty {
return propI.Text < propJ.Text
} else {
return propI.Value < propJ.Value
}
})
}
}

// applyDepthFilter limits the tree size to the given depth and also
// dereferences pointer cycles by creating new copies of components
// to prevent cyclic errors during json.Marshal
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/dummy/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ var LogisticsAPIPod = models.Component{
ParentId: &PodsComponent.ID,
CreatedAt: DummyCreatedAt,
Path: fmt.Sprintf("%s.%s", ClusterComponent.ID.String(), PodsComponent.ID.String()),
Properties: []*models.Property{{Name: "memory", Unit: "bytes", Value: 100}},
}

var LogisticsUIPod = models.Component{
Expand All @@ -154,6 +155,7 @@ var LogisticsUIPod = models.Component{
ParentId: &PodsComponent.ID,
CreatedAt: DummyCreatedAt,
Path: fmt.Sprintf("%s.%s", ClusterComponent.ID.String(), PodsComponent.ID.String()),
Properties: []*models.Property{{Name: "memory", Unit: "bytes", Value: 200}},
}

var LogisticsWorkerPod = models.Component{
Expand All @@ -167,6 +169,7 @@ var LogisticsWorkerPod = models.Component{
ParentId: &PodsComponent.ID,
CreatedAt: DummyCreatedAt,
Path: fmt.Sprintf("%s.%s", ClusterComponent.ID.String(), PodsComponent.ID.String()),
Properties: []*models.Property{{Name: "memory", Unit: "bytes", Value: 300}},
}

var PaymentsAPI = models.Component{
Expand Down
14 changes: 14 additions & 0 deletions tests/fixtures/expectations/topology_child_tree.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
"tooltip": "Logistic API Pod",
"icon": "icon-kubernetes-pod",
"type": "KubernetesPod",
"properties": [
{
"name": "memory",
"value": 100,
"unit": "bytes"
}
],
"path": "018681fe-8156-4b91-d178-caf8b3c2818c.018681ff-559f-7183-19d1-7d898b4e1413",
"summary": {
"healthy": 1
Expand All @@ -49,6 +56,13 @@
"tooltip": "Logistic UI Pod",
"icon": "icon-kubernetes-pod",
"type": "KubernetesPod",
"properties": [
{
"name": "memory",
"value": 200,
"unit": "bytes"
}
],
"path": "018681fe-8156-4b91-d178-caf8b3c2818c.018681ff-559f-7183-19d1-7d898b4e1413",
"summary": {
"healthy": 1
Expand Down
117 changes: 117 additions & 0 deletions tests/fixtures/expectations/topology_tree_with_desc_sort.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{
"components": [
{
"id": "018681ff-559f-7183-19d1-7d898b4e1413",
"agent_id": "00000000-0000-0000-0000-000000000000",
"external_id": "dummy/pods",
"parent_id": "018681fe-8156-4b91-d178-caf8b3c2818c",
"name": "Pods",
"status": "healthy",
"tooltip": "Kubernetes Pods",
"icon": "icon-kubernetes-pod",
"type": "KubernetesPods",
"path": "018681fe-8156-4b91-d178-caf8b3c2818c",
"summary": {
"healthy": 3
},
"is_leaf": false,
"created_at": "2023-01-01T05:29:00+05:30",
"updated_at": "2023-01-01T05:29:00+05:30",
"components": [
{
"id": "018681ff-e578-a926-e366-d2dc0646eafa",
"agent_id": "00000000-0000-0000-0000-000000000000",
"external_id": "dummy/logistics-worker-79cb67d8f5-lr66n",
"parent_id": "018681ff-559f-7183-19d1-7d898b4e1413",
"name": "logistics-worker-79cb67d8f5-lr66n",
"status": "healthy",
"tooltip": "Logistic Worker Pod",
"icon": "icon-kubernetes-pod",
"type": "KubernetesPod",
"properties": [
{
"name": "memory",
"value": 300,
"unit": "bytes"
}
],
"path": "018681fe-8156-4b91-d178-caf8b3c2818c.018681ff-559f-7183-19d1-7d898b4e1413",
"summary": {
"healthy": 1
},
"is_leaf": false,
"created_at": "2023-01-01T05:29:00+05:30",
"updated_at": "2023-01-01T05:29:00+05:30",
"parents": [
"018681ff-227e-4d71-b38e-0693cc862213"
]
},
{
"id": "018681ff-b6c1-a14d-2fd4-8c7dac94cddd",
"agent_id": "00000000-0000-0000-0000-000000000000",
"external_id": "dummy/logistics-ui-676b85b87c-tjjcp",
"parent_id": "018681ff-559f-7183-19d1-7d898b4e1413",
"name": "logistics-ui-676b85b87c-tjjcp",
"status": "healthy",
"tooltip": "Logistic UI Pod",
"icon": "icon-kubernetes-pod",
"type": "KubernetesPod",
"properties": [
{
"name": "memory",
"value": 200,
"unit": "bytes"
}
],
"path": "018681fe-8156-4b91-d178-caf8b3c2818c.018681ff-559f-7183-19d1-7d898b4e1413",
"summary": {
"healthy": 1
},
"is_leaf": false,
"created_at": "2023-01-01T05:29:00+05:30",
"updated_at": "2023-01-01T05:29:00+05:30",
"parents": [
"018681fe-f5aa-37e9-83f7-47b5b0232d5e"
]
},
{
"id": "018681ff-80ed-d10d-21ef-c74f152b085b",
"agent_id": "00000000-0000-0000-0000-000000000000",
"external_id": "dummy/logistics-api-574dc95b5d-mp64w",
"parent_id": "018681ff-559f-7183-19d1-7d898b4e1413",
"name": "logistics-api-574dc95b5d-mp64w",
"status": "healthy",
"tooltip": "Logistic API Pod",
"icon": "icon-kubernetes-pod",
"type": "KubernetesPod",
"properties": [
{
"name": "memory",
"value": 100,
"unit": "bytes"
}
],
"path": "018681fe-8156-4b91-d178-caf8b3c2818c.018681ff-559f-7183-19d1-7d898b4e1413",
"summary": {
"healthy": 1
},
"is_leaf": false,
"created_at": "2023-01-01T05:29:00+05:30",
"updated_at": "2023-01-01T05:29:00+05:30",
"parents": [
"018681fe-f5aa-37e9-83f7-47b5b0232d5e"
]
}
]
}
],
"healthStatuses": [
"healthy"
],
"teams": [],
"tags": null,
"types": [
"KubernetesPod",
"KubernetesPods"
]
}
117 changes: 117 additions & 0 deletions tests/fixtures/expectations/topology_tree_with_sort.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{
"components": [
{
"id": "018681ff-559f-7183-19d1-7d898b4e1413",
"agent_id": "00000000-0000-0000-0000-000000000000",
"external_id": "dummy/pods",
"parent_id": "018681fe-8156-4b91-d178-caf8b3c2818c",
"name": "Pods",
"status": "healthy",
"tooltip": "Kubernetes Pods",
"icon": "icon-kubernetes-pod",
"type": "KubernetesPods",
"path": "018681fe-8156-4b91-d178-caf8b3c2818c",
"summary": {
"healthy": 3
},
"is_leaf": false,
"created_at": "2023-01-01T05:29:00+05:30",
"updated_at": "2023-01-01T05:29:00+05:30",
"components": [
{
"id": "018681ff-80ed-d10d-21ef-c74f152b085b",
"agent_id": "00000000-0000-0000-0000-000000000000",
"external_id": "dummy/logistics-api-574dc95b5d-mp64w",
"parent_id": "018681ff-559f-7183-19d1-7d898b4e1413",
"name": "logistics-api-574dc95b5d-mp64w",
"status": "healthy",
"tooltip": "Logistic API Pod",
"icon": "icon-kubernetes-pod",
"type": "KubernetesPod",
"properties": [
{
"name": "memory",
"value": 100,
"unit": "bytes"
}
],
"path": "018681fe-8156-4b91-d178-caf8b3c2818c.018681ff-559f-7183-19d1-7d898b4e1413",
"summary": {
"healthy": 1
},
"is_leaf": false,
"created_at": "2023-01-01T05:29:00+05:30",
"updated_at": "2023-01-01T05:29:00+05:30",
"parents": [
"018681fe-f5aa-37e9-83f7-47b5b0232d5e"
]
},
{
"id": "018681ff-b6c1-a14d-2fd4-8c7dac94cddd",
"agent_id": "00000000-0000-0000-0000-000000000000",
"external_id": "dummy/logistics-ui-676b85b87c-tjjcp",
"parent_id": "018681ff-559f-7183-19d1-7d898b4e1413",
"name": "logistics-ui-676b85b87c-tjjcp",
"status": "healthy",
"tooltip": "Logistic UI Pod",
"icon": "icon-kubernetes-pod",
"type": "KubernetesPod",
"properties": [
{
"name": "memory",
"value": 200,
"unit": "bytes"
}
],
"path": "018681fe-8156-4b91-d178-caf8b3c2818c.018681ff-559f-7183-19d1-7d898b4e1413",
"summary": {
"healthy": 1
},
"is_leaf": false,
"created_at": "2023-01-01T05:29:00+05:30",
"updated_at": "2023-01-01T05:29:00+05:30",
"parents": [
"018681fe-f5aa-37e9-83f7-47b5b0232d5e"
]
},
{
"id": "018681ff-e578-a926-e366-d2dc0646eafa",
"agent_id": "00000000-0000-0000-0000-000000000000",
"external_id": "dummy/logistics-worker-79cb67d8f5-lr66n",
"parent_id": "018681ff-559f-7183-19d1-7d898b4e1413",
"name": "logistics-worker-79cb67d8f5-lr66n",
"status": "healthy",
"tooltip": "Logistic Worker Pod",
"icon": "icon-kubernetes-pod",
"type": "KubernetesPod",
"properties": [
{
"name": "memory",
"value": 300,
"unit": "bytes"
}
],
"path": "018681fe-8156-4b91-d178-caf8b3c2818c.018681ff-559f-7183-19d1-7d898b4e1413",
"summary": {
"healthy": 1
},
"is_leaf": false,
"created_at": "2023-01-01T05:29:00+05:30",
"updated_at": "2023-01-01T05:29:00+05:30",
"parents": [
"018681ff-227e-4d71-b38e-0693cc862213"
]
}
]
}
],
"healthStatuses": [
"healthy"
],
"teams": [],
"tags": null,
"types": [
"KubernetesPod",
"KubernetesPods"
]
}
6 changes: 6 additions & 0 deletions tests/topology_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,10 @@ var _ = ginkgo.Describe("Topology behavior", func() {
ginkgo.It("Should test tree with agent ID filter", func() {
testTopologyJSON(query.TopologyOptions{AgentID: dummy.GCPAgent.ID.String()}, "fixtures/expectations/topology_tree_with_agent_id.json")
})

ginkgo.It("Should test tree with sort options", func() {
testTopologyJSON(query.TopologyOptions{ID: dummy.PodsComponent.ID.String(), SortBy: "field:memory"}, "fixtures/expectations/topology_tree_with_sort.json")

testTopologyJSON(query.TopologyOptions{ID: dummy.PodsComponent.ID.String(), SortBy: "field:memory", SortOrder: "desc"}, "fixtures/expectations/topology_tree_with_desc_sort.json")
})
})

0 comments on commit cfbee68

Please sign in to comment.