mirror of https://github.com/portainer/portainer
fix(edge/groups): filter selected environments [EE-5891] (#10050)
parent
a27cc6c0e5
commit
a1e610a39a
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -92,7 +92,6 @@ export const componentsModule = angular
|
|||
'query',
|
||||
'title',
|
||||
'data-cy',
|
||||
'hideEnvironmentIds',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
|
|
|
@ -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<DecoratedEnvironment> = 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 (
|
||||
<Datatable<DecoratedEnvironment>
|
||||
|
|
|
@ -32,6 +32,7 @@ export interface BaseEnvironmentsQueryParams {
|
|||
types?: EnvironmentType[] | readonly EnvironmentType[];
|
||||
tagIds?: TagId[];
|
||||
endpointIds?: EnvironmentId[];
|
||||
excludeIds?: EnvironmentId[];
|
||||
tagsPartialMatch?: boolean;
|
||||
groupIds?: EnvironmentGroupId[];
|
||||
status?: EnvironmentStatus[];
|
||||
|
|
Loading…
Reference in New Issue