fix(policy) fix policy group pagination issues [R8S-855] (#1898)
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
dist
|
||||
api/datastore/test_data
|
||||
api/datastore/test_data
|
||||
coverage
|
||||
@@ -23,7 +23,7 @@ export function EditGroupView() {
|
||||
// Fetch associated environments for this group (not for unassigned group)
|
||||
const isUnassignedGroup = groupId === 1;
|
||||
const environmentsQuery = useEnvironmentList(
|
||||
{ groupIds: [groupId] },
|
||||
{ groupIds: [groupId], pageLimit: 0 },
|
||||
{ enabled: !!groupId && !isUnassignedGroup }
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { useEnvironmentList } from '@/react/portainer/environments/queries';
|
||||
|
||||
@@ -11,6 +11,8 @@ import { AssociatedEnvironmentsTable } from './AssociatedEnvironmentsTable';
|
||||
import { AvailableEnvironmentsTable } from './AvailableEnvironmentsTable';
|
||||
|
||||
interface Props {
|
||||
/** Group ID when editing an existing group */
|
||||
groupId?: number;
|
||||
/** IDs of currently associated environments */
|
||||
associatedEnvironmentIds: Array<EnvironmentId>;
|
||||
/** IDs of initially associated environments for tracking unsaved changes */
|
||||
@@ -20,6 +22,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export function AssociatedEnvironmentsSelector({
|
||||
groupId,
|
||||
associatedEnvironmentIds,
|
||||
initialAssociatedEnvironmentIds,
|
||||
onChange,
|
||||
@@ -31,24 +34,37 @@ export function AssociatedEnvironmentsSelector({
|
||||
|
||||
// Fetch initially associated environments to populate the cache
|
||||
const initialEnvsQuery = useEnvironmentList(
|
||||
groupId
|
||||
? {
|
||||
groupIds: [groupId],
|
||||
pageLimit: 0,
|
||||
}
|
||||
: {
|
||||
endpointIds: initialAssociatedEnvironmentIds,
|
||||
},
|
||||
{
|
||||
endpointIds: initialAssociatedEnvironmentIds,
|
||||
},
|
||||
{
|
||||
enabled: initialAssociatedEnvironmentIds.length > 0,
|
||||
enabled: groupId
|
||||
? groupId !== 1
|
||||
: initialAssociatedEnvironmentIds.length > 0,
|
||||
}
|
||||
);
|
||||
|
||||
const environmentMap = buildEnvironmentMap(
|
||||
environmentCache,
|
||||
initialEnvsQuery.environments
|
||||
);
|
||||
const addedIds = associatedEnvironmentIds.filter(
|
||||
(id) => !initialAssociatedEnvironmentIds.includes(id)
|
||||
const environmentMap = useMemo(
|
||||
() => buildEnvironmentMap(environmentCache, initialEnvsQuery.environments),
|
||||
[environmentCache, initialEnvsQuery.environments]
|
||||
);
|
||||
const associatedSet = new Set(associatedEnvironmentIds);
|
||||
const initialSet = new Set(initialAssociatedEnvironmentIds);
|
||||
|
||||
const addedIds = associatedEnvironmentIds.filter((id) => !initialSet.has(id));
|
||||
const removedIds = initialAssociatedEnvironmentIds.filter(
|
||||
(id) => !associatedEnvironmentIds.includes(id)
|
||||
(id) => !associatedSet.has(id)
|
||||
);
|
||||
|
||||
const excludeIdsForAvailableEnvironments = groupId
|
||||
? addedIds
|
||||
: associatedEnvironmentIds;
|
||||
|
||||
const associatedEnvironments = associatedEnvironmentIds
|
||||
.map((id) => environmentMap.get(id))
|
||||
.filter((env): env is Environment => env !== undefined);
|
||||
@@ -65,8 +81,9 @@ export function AssociatedEnvironmentsSelector({
|
||||
<div className="w-1/2 flex flex-col">
|
||||
<AvailableEnvironmentsTable
|
||||
title="Available environments"
|
||||
excludeIds={associatedEnvironmentIds}
|
||||
excludeIds={excludeIdsForAvailableEnvironments}
|
||||
includeIds={removedIds}
|
||||
highlightIds={removedIds}
|
||||
onClickRow={handleAddEnvironment}
|
||||
data-cy="group-availableEndpoints"
|
||||
/>
|
||||
|
||||
@@ -28,6 +28,8 @@ interface Props extends AutomationTestingProps {
|
||||
excludeIds: Array<EnvironmentId>;
|
||||
/** IDs to include in the query (e.g., recently removed from associated - will be highlighted) */
|
||||
includeIds?: Array<EnvironmentId>;
|
||||
/** IDs to highlight (unsaved badge) */
|
||||
highlightIds?: Array<EnvironmentId>;
|
||||
onClickRow?: (env: EnvironmentTableData) => void;
|
||||
}
|
||||
|
||||
@@ -35,12 +37,16 @@ export function AvailableEnvironmentsTable({
|
||||
title,
|
||||
excludeIds,
|
||||
includeIds = [],
|
||||
highlightIds = [],
|
||||
onClickRow,
|
||||
'data-cy': dataCy,
|
||||
}: Props) {
|
||||
const tableState = useTableState(settingsStore, tableKey);
|
||||
const [page, setPage] = useState(0);
|
||||
const columns = useMemo(() => buildColumns(includeIds), [includeIds]);
|
||||
const columns = useMemo(
|
||||
() => buildColumns(new Set(highlightIds)),
|
||||
[highlightIds]
|
||||
);
|
||||
|
||||
// Query unassigned environments (group 1)
|
||||
const unassignedQuery = useEnvironmentList({
|
||||
@@ -63,14 +69,13 @@ export function AvailableEnvironmentsTable({
|
||||
);
|
||||
|
||||
// Merge results: removed environments + unassigned environments (deduped)
|
||||
// Only use removedQuery data when includeIds is non-empty to avoid stale cache
|
||||
const environments = useMemo(() => {
|
||||
const { environments, uniqueRemovedCount } = useMemo(() => {
|
||||
const unassigned = unassignedQuery.environments || [];
|
||||
const removed =
|
||||
includeIds.length > 0 ? removedQuery.environments || [] : [];
|
||||
|
||||
if (removed.length === 0) {
|
||||
return unassigned;
|
||||
return { environments: unassigned, uniqueRemovedCount: 0 };
|
||||
}
|
||||
|
||||
const unassignedIds = new Set(unassigned.map((e) => e.Id));
|
||||
@@ -82,12 +87,18 @@ export function AvailableEnvironmentsTable({
|
||||
// useTypeGuard on tableState.sortBy.id to use as a key for sorting
|
||||
const sortKey = getSortKey(tableState.sortBy?.id);
|
||||
if (sortKey) {
|
||||
return combined.sort((a, b) => {
|
||||
const cmp = semverCompare(a[sortKey].toString(), b[sortKey].toString());
|
||||
return isDesc ? -cmp : cmp;
|
||||
});
|
||||
return {
|
||||
environments: combined.sort((a, b) => {
|
||||
const cmp = semverCompare(
|
||||
a[sortKey].toString(),
|
||||
b[sortKey].toString()
|
||||
);
|
||||
return isDesc ? -cmp : cmp;
|
||||
}),
|
||||
uniqueRemovedCount: uniqueRemoved.length,
|
||||
};
|
||||
}
|
||||
return combined;
|
||||
return { environments: combined, uniqueRemovedCount: uniqueRemoved.length };
|
||||
}, [
|
||||
unassignedQuery.environments,
|
||||
removedQuery.environments,
|
||||
@@ -96,9 +107,7 @@ export function AvailableEnvironmentsTable({
|
||||
tableState.sortBy?.id,
|
||||
]);
|
||||
|
||||
const totalCount =
|
||||
unassignedQuery.totalCount +
|
||||
(includeIds.length > 0 ? removedQuery.environments?.length || 0 : 0);
|
||||
const totalCount = unassignedQuery.totalCount + uniqueRemovedCount;
|
||||
|
||||
return (
|
||||
<Widget className="flex-1 flex flex-col">
|
||||
@@ -134,7 +143,7 @@ export function AvailableEnvironmentsTable({
|
||||
);
|
||||
}
|
||||
|
||||
function buildColumns(highlightIds: Array<EnvironmentId>) {
|
||||
function buildColumns(highlightIds: Set<EnvironmentId>) {
|
||||
return [
|
||||
columnHelper.accessor('Name', {
|
||||
header: 'Name',
|
||||
@@ -142,7 +151,7 @@ function buildColumns(highlightIds: Array<EnvironmentId>) {
|
||||
cell: ({ getValue, row }) => (
|
||||
<span className="flex items-center gap-2">
|
||||
<span title={getValue()}>{truncate(getValue(), { length: 64 })}</span>
|
||||
{highlightIds.includes(row.original.Id) && (
|
||||
{highlightIds.has(row.original.Id) && (
|
||||
<Badge type="muted" data-cy="unsaved-badge">
|
||||
Unsaved
|
||||
</Badge>
|
||||
|
||||
@@ -150,6 +150,7 @@ function InnerForm({
|
||||
</FormSection>
|
||||
) : (
|
||||
<AssociatedEnvironmentsSelector
|
||||
groupId={groupId}
|
||||
associatedEnvironmentIds={values.associatedEnvironments}
|
||||
initialAssociatedEnvironmentIds={initialValues.associatedEnvironments}
|
||||
onChange={(ids) => setFieldValue('associatedEnvironments', ids)}
|
||||
|
||||
@@ -33,6 +33,7 @@ export function getSortType(value?: string): SortType | undefined {
|
||||
|
||||
export type Query = EnvironmentsQueryParams & {
|
||||
page?: number;
|
||||
/** Use 0 to fetch all environments without pagination (backend supports limit=0). */
|
||||
pageLimit?: number;
|
||||
sort?: SortType;
|
||||
order?: 'asc' | 'desc';
|
||||
@@ -74,16 +75,10 @@ export function useEnvironmentList(
|
||||
const { isLoading, data } = useQuery(
|
||||
[
|
||||
...environmentQueryKeys.base(),
|
||||
{
|
||||
page,
|
||||
pageLimit,
|
||||
sort,
|
||||
order,
|
||||
...query,
|
||||
},
|
||||
{ page, pageLimit, sort, order, ...query },
|
||||
],
|
||||
async () => {
|
||||
const start = (page - 1) * pageLimit + 1;
|
||||
const start = pageLimit === 0 ? 0 : (page - 1) * pageLimit + 1;
|
||||
|
||||
return getEnvironments({
|
||||
start,
|
||||
|
||||
Reference in New Issue
Block a user