From 77120abf332a448b6a5dd2f10bb9e95f3e4efeb0 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Wed, 16 Aug 2023 12:24:43 +0300 Subject: [PATCH] fix(edge/groups): filter selected environments [EE-5891] (#10016) --- api/http/handler/endpoints/filter.go | 13 ++++++++ api/http/handler/endpoints/filter_test.go | 23 +++++++++++++ api/internal/slices/slices.go | 9 +++++ app/edge/react/components/index.ts | 1 - .../AssociatedEdgeEnvironmentsSelector.tsx | 2 +- .../components/EdgeGroupAssociationTable.tsx | 33 ++++++------------- .../environments/environment.service/index.ts | 1 + 7 files changed, 57 insertions(+), 25 deletions(-) diff --git a/api/http/handler/endpoints/filter.go b/api/http/handler/endpoints/filter.go index a0006a48d..0397ee52d 100644 --- a/api/http/handler/endpoints/filter.go +++ b/api/http/handler/endpoints/filter.go @@ -34,6 +34,7 @@ type EnvironmentsQuery struct { edgeCheckInPassedSeconds int edgeStackId portainer.EdgeStackID edgeStackStatus *portainer.EdgeStackStatusType + excludeIds []portainer.EndpointID } func parseQuery(r *http.Request) (EnvironmentsQuery, error) { @@ -69,6 +70,11 @@ func parseQuery(r *http.Request) (EnvironmentsQuery, error) { return EnvironmentsQuery{}, err } + excludeIDs, err := getNumberArrayQueryParameter[portainer.EndpointID](r, "excludeIds") + if err != nil { + return EnvironmentsQuery{}, err + } + agentVersions := getArrayQueryParameter(r, "agentVersions") name, _ := request.RetrieveQueryParameter(r, "name", true) @@ -97,6 +103,7 @@ func parseQuery(r *http.Request) (EnvironmentsQuery, error) { types: endpointTypes, tagIds: tagIDs, endpointIds: endpointIDs, + excludeIds: excludeIDs, tagsPartialMatch: tagsPartialMatch, groupIds: groupIDs, status: status, @@ -118,6 +125,12 @@ func (handler *Handler) filterEndpointsByQuery(filteredEndpoints []portainer.End filteredEndpoints = filteredEndpointsByIds(filteredEndpoints, query.endpointIds) } + if len(query.excludeIds) > 0 { + filteredEndpoints = filter(filteredEndpoints, func(endpoint portainer.Endpoint) bool { + return !slices.Contains(query.excludeIds, endpoint.ID) + }) + } + if len(query.groupIds) > 0 { filteredEndpoints = filterEndpointsByGroupIDs(filteredEndpoints, query.groupIds) } diff --git a/api/http/handler/endpoints/filter_test.go b/api/http/handler/endpoints/filter_test.go index e54af0aea..f98ddd20f 100644 --- a/api/http/handler/endpoints/filter_test.go +++ b/api/http/handler/endpoints/filter_test.go @@ -5,6 +5,7 @@ import ( portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/datastore" + "github.com/portainer/portainer/api/internal/slices" "github.com/portainer/portainer/api/internal/testhelpers" "github.com/stretchr/testify/assert" ) @@ -124,6 +125,28 @@ func Test_Filter_edgeFilter(t *testing.T) { runTests(tests, t, handler, endpoints) } +func Test_Filter_excludeIDs(t *testing.T) { + ids := []portainer.EndpointID{1, 2, 3, 4, 5, 6, 7, 8, 9} + + environments := slices.Map(ids, func(id portainer.EndpointID) portainer.Endpoint { + return portainer.Endpoint{ID: id, GroupID: 1, Type: portainer.DockerEnvironment} + }) + + handler := setupFilterTest(t, environments) + + tests := []filterTest{ + { + title: "should exclude IDs 2,5,8", + expected: []portainer.EndpointID{1, 3, 4, 6, 7, 9}, + query: EnvironmentsQuery{ + excludeIds: []portainer.EndpointID{2, 5, 8}, + }, + }, + } + + runTests(tests, t, handler, environments) +} + func runTests(tests []filterTest, t *testing.T, handler *Handler, endpoints []portainer.Endpoint) { for _, test := range tests { t.Run(test.title, func(t *testing.T) { diff --git a/api/internal/slices/slices.go b/api/internal/slices/slices.go index 7eb486398..7d0fa21c7 100644 --- a/api/internal/slices/slices.go +++ b/api/internal/slices/slices.go @@ -63,3 +63,12 @@ func RemoveIndex[T any](s []T, index int) []T { s[index] = s[len(s)-1] return s[:len(s)-1] } + +// Map applies the given function to each element of the slice and returns a new slice with the results +func Map[T, U any](s []T, f func(T) U) []U { + result := make([]U, len(s)) + for i, v := range s { + result[i] = f(v) + } + return result +} diff --git a/app/edge/react/components/index.ts b/app/edge/react/components/index.ts index a40b04ea6..608fe3a0b 100644 --- a/app/edge/react/components/index.ts +++ b/app/edge/react/components/index.ts @@ -92,7 +92,6 @@ export const componentsModule = angular 'query', 'title', 'data-cy', - 'hideEnvironmentIds', ]) ) .component( diff --git a/app/react/edge/components/AssociatedEdgeEnvironmentsSelector.tsx b/app/react/edge/components/AssociatedEdgeEnvironmentsSelector.tsx index 7de8938b6..79e795852 100644 --- a/app/react/edge/components/AssociatedEdgeEnvironmentsSelector.tsx +++ b/app/react/edge/components/AssociatedEdgeEnvironmentsSelector.tsx @@ -28,6 +28,7 @@ export function AssociatedEdgeEnvironmentsSelector({ emptyContentLabel="No environment available" query={{ types: EdgeTypes, + excludeIds: value, }} onClickRow={(env) => { if (!value.includes(env.Id)) { @@ -35,7 +36,6 @@ export function AssociatedEdgeEnvironmentsSelector({ } }} data-cy="edgeGroupCreate-availableEndpoints" - hideEnvironmentIds={value} />
diff --git a/app/react/edge/components/EdgeGroupAssociationTable.tsx b/app/react/edge/components/EdgeGroupAssociationTable.tsx index 6b3dc5b49..86e0d6526 100644 --- a/app/react/edge/components/EdgeGroupAssociationTable.tsx +++ b/app/react/edge/components/EdgeGroupAssociationTable.tsx @@ -3,10 +3,7 @@ import { truncate } from 'lodash'; import { useMemo, useState } from 'react'; import { useEnvironmentList } from '@/react/portainer/environments/queries'; -import { - Environment, - EnvironmentId, -} from '@/react/portainer/environments/types'; +import { Environment } from '@/react/portainer/environments/types'; import { useGroups } from '@/react/portainer/environments/environment-groups/queries'; import { useTags } from '@/portainer/tags/queries'; import { EnvironmentsQueryParams } from '@/react/portainer/environments/environment.service'; @@ -47,13 +44,11 @@ export function EdgeGroupAssociationTable({ emptyContentLabel, onClickRow, 'data-cy': dataCy, - hideEnvironmentIds = [], }: { title: string; query: EnvironmentsQueryParams; emptyContentLabel: string; onClickRow: (env: Environment) => void; - hideEnvironmentIds?: EnvironmentId[]; } & AutomationTestingProps) { const tableState = useTableStateWithoutStorage('Name'); const [page, setPage] = useState(1); @@ -74,25 +69,17 @@ export function EdgeGroupAssociationTable({ const environments: Array = useMemo( () => - environmentsQuery.environments - .filter((e) => !hideEnvironmentIds.includes(e.Id)) - .map((env) => ({ - ...env, - Group: - groupsQuery.data?.find((g) => g.Id === env.GroupId)?.Name || '', - Tags: env.TagIds.map( - (tagId) => tagsQuery.data?.find((t) => t.ID === tagId)?.Name || '' - ), - })), - [ - environmentsQuery.environments, - groupsQuery.data, - hideEnvironmentIds, - tagsQuery.data, - ] + environmentsQuery.environments.map((env) => ({ + ...env, + Group: groupsQuery.data?.find((g) => g.Id === env.GroupId)?.Name || '', + Tags: env.TagIds.map( + (tagId) => tagsQuery.data?.find((t) => t.ID === tagId)?.Name || '' + ), + })), + [environmentsQuery.environments, groupsQuery.data, tagsQuery.data] ); - const totalCount = environmentsQuery.totalCount - hideEnvironmentIds.length; + const { totalCount } = environmentsQuery; return ( diff --git a/app/react/portainer/environments/environment.service/index.ts b/app/react/portainer/environments/environment.service/index.ts index 9e41aece3..2fa1bc2bd 100644 --- a/app/react/portainer/environments/environment.service/index.ts +++ b/app/react/portainer/environments/environment.service/index.ts @@ -32,6 +32,7 @@ export interface BaseEnvironmentsQueryParams { types?: EnvironmentType[] | readonly EnvironmentType[]; tagIds?: TagId[]; endpointIds?: EnvironmentId[]; + excludeIds?: EnvironmentId[]; tagsPartialMatch?: boolean; groupIds?: EnvironmentGroupId[]; status?: EnvironmentStatus[];