1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-04 21:35:23 +02:00

fix(agent-updates): fix remote agent updates cannot be scheduled properly for large edge groups [BE-11691] (#528)

This commit is contained in:
Viktor Pettersson 2025-03-20 10:05:15 +01:00 committed by GitHub
parent 5d1b42b314
commit a61c1004d3
8 changed files with 46 additions and 74 deletions

View file

@ -7,6 +7,7 @@ import {
EnvironmentStatus, EnvironmentStatus,
EnvironmentGroupId, EnvironmentGroupId,
PlatformType, PlatformType,
EdgeGroupId,
} from '@/react/portainer/environments/types'; } from '@/react/portainer/environments/types';
import { type TagId } from '@/portainer/tags/types'; import { type TagId } from '@/portainer/tags/types';
import { UserId } from '@/portainer/users/types'; import { UserId } from '@/portainer/users/types';
@ -47,6 +48,7 @@ export interface BaseEnvironmentsQueryParams {
updateInformation?: boolean; updateInformation?: boolean;
edgeCheckInPassedSeconds?: number; edgeCheckInPassedSeconds?: number;
platformTypes?: PlatformType[]; platformTypes?: PlatformType[];
edgeGroupIds?: EdgeGroupId[];
} }
export type EnvironmentsQueryParams = BaseEnvironmentsQueryParams & export type EnvironmentsQueryParams = BaseEnvironmentsQueryParams &

View file

@ -3,6 +3,8 @@ import { DockerSnapshot } from '@/react/docker/snapshots/types';
export type EnvironmentGroupId = number; export type EnvironmentGroupId = number;
export type EdgeGroupId = number;
type RoleId = number; type RoleId = number;
interface AccessPolicy { interface AccessPolicy {
RoleId: RoleId; RoleId: RoleId;

View file

@ -9,7 +9,6 @@ import { TextTip } from '@@/Tip/TextTip';
import { usePreviousVersions } from '../queries/usePreviousVersions'; import { usePreviousVersions } from '../queries/usePreviousVersions';
import { FormValues } from './types'; import { FormValues } from './types';
import { useEdgeGroupsEnvironmentIds } from './useEdgeGroupsEnvironmentIds';
export function RollbackOptions() { export function RollbackOptions() {
const { isLoading, count, version, versionError } = useSelectVersionOnMount(); const { isLoading, count, version, versionError } = useSelectVersionOnMount();
@ -50,24 +49,19 @@ function useSelectVersionOnMount() {
errors: { version: versionError }, errors: { version: versionError },
} = useFormikContext<FormValues>(); } = useFormikContext<FormValues>();
const environmentIdsQuery = useEdgeGroupsEnvironmentIds(groupIds); const previousVersionsQuery = usePreviousVersions(groupIds ?? []);
const previousVersionsQuery = usePreviousVersions<string[]>({
enabled: !!environmentIdsQuery.data,
});
const previousVersions = useMemo( const previousVersions = useMemo(
() => () =>
previousVersionsQuery.data previousVersionsQuery.data
? _.uniq( ? _.uniq(Object.values(previousVersionsQuery.data))
_.compact(
environmentIdsQuery.data?.map(
(envId) => previousVersionsQuery.data[envId]
)
)
)
: [], : [],
[environmentIdsQuery.data, previousVersionsQuery.data] [previousVersionsQuery.data]
);
const previousVersionCount = useMemo(
() => Object.keys(previousVersions).length,
[previousVersions]
); );
useEffect(() => { useEffect(() => {
@ -91,7 +85,7 @@ function useSelectVersionOnMount() {
isLoading: previousVersionsQuery.isLoading, isLoading: previousVersionsQuery.isLoading,
versionError, versionError,
version, version,
count: environmentIdsQuery.data?.length, count: previousVersionCount,
}; };
} }

View file

@ -7,7 +7,6 @@ import { NavContainer } from '@@/NavTabs/NavContainer';
import { ScheduleType } from '../types'; import { ScheduleType } from '../types';
import { useEdgeGroupsEnvironmentIds } from './useEdgeGroupsEnvironmentIds';
import { useEnvironments } from './useEnvironments'; import { useEnvironments } from './useEnvironments';
import { defaultValue } from './ScheduledTimeField'; import { defaultValue } from './ScheduledTimeField';
import { FormValues } from './types'; import { FormValues } from './types';
@ -17,10 +16,7 @@ import { RollbackScheduleDetailsFieldset } from './RollbackScheduleDetailsFields
export function ScheduleTypeSelector() { export function ScheduleTypeSelector() {
const { values, setFieldValue } = useFormikContext<FormValues>(); const { values, setFieldValue } = useFormikContext<FormValues>();
const environmentIdsQuery = useEdgeGroupsEnvironmentIds(values.groupIds); const environments = useEnvironments(values.groupIds);
const edgeGroupsEnvironmentIds = environmentIdsQuery.data || [];
const environments = useEnvironments(edgeGroupsEnvironmentIds);
// old version is version that doesn't support scheduling of updates // old version is version that doesn't support scheduling of updates
const hasNoTimeZone = environments.some((env) => !env.LocalTimeZone); const hasNoTimeZone = environments.some((env) => !env.LocalTimeZone);

View file

@ -1,34 +0,0 @@
import _ from 'lodash';
import { useMemo } from 'react';
import { useEdgeGroups } from '@/react/edge/edge-groups/queries/useEdgeGroups';
import { EdgeGroup } from '@/react/edge/edge-groups/types';
export function useEdgeGroupsEnvironmentIds(
edgeGroupsIds: Array<EdgeGroup['Id']>
) {
const groupsQuery = useEdgeGroups({
select: (groups) =>
Object.fromEntries(groups.map((g) => [g.Id, g.Endpoints])),
});
const envIds = useMemo(
() =>
_.uniq(
_.compact(
edgeGroupsIds.flatMap((id) =>
groupsQuery.data ? groupsQuery.data[id] : []
)
)
),
[edgeGroupsIds, groupsQuery.data]
);
return useMemo(
() => ({
data: groupsQuery.data ? envIds : null,
isLoading: groupsQuery.isLoading,
}),
[envIds, groupsQuery.data, groupsQuery.isLoading]
);
}

View file

@ -1,11 +1,11 @@
import { useEnvironmentList } from '@/react/portainer/environments/queries/useEnvironmentList'; import { useEnvironmentList } from '@/react/portainer/environments/queries/useEnvironmentList';
import { EdgeTypes, EnvironmentId } from '@/react/portainer/environments/types'; import { EdgeGroupId, EdgeTypes } from '@/react/portainer/environments/types';
export function useEnvironments(environmentsIds: Array<EnvironmentId>) { export function useEnvironments(edgeGroupIds: Array<EdgeGroupId>) {
const environmentsQuery = useEnvironmentList( const environmentsQuery = useEnvironmentList(
{ endpointIds: environmentsIds, types: EdgeTypes, pageLimit: 0 }, { edgeGroupIds, types: EdgeTypes, pageLimit: 0 },
{ {
enabled: environmentsIds.length > 0, enabled: edgeGroupIds.length > 0,
} }
); );

View file

@ -1,4 +1,7 @@
import { EnvironmentId } from '@/react/portainer/environments/types'; import {
EdgeGroupId,
EnvironmentId,
} from '@/react/portainer/environments/types';
import { EdgeUpdateSchedule } from '../types'; import { EdgeUpdateSchedule } from '../types';
@ -11,5 +14,6 @@ export const queryKeys = {
[...queryKeys.base(), 'active', { environmentIds }] as const, [...queryKeys.base(), 'active', { environmentIds }] as const,
supportedAgentVersions: () => supportedAgentVersions: () =>
[...queryKeys.base(), 'agent_versions'] as const, [...queryKeys.base(), 'agent_versions'] as const,
previousVersions: () => [...queryKeys.base(), 'previous_versions'] as const, previousVersions: (edgeGroupIds?: EdgeGroupId[]) =>
[...queryKeys.base(), 'previous_versions', { edgeGroupIds }] as const,
}; };

View file

@ -1,7 +1,10 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import axios, { parseAxiosError } from '@/portainer/services/axios'; import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types'; import {
EdgeGroupId,
EnvironmentId,
} from '@/react/portainer/environments/types';
import { queryKeys } from './query-keys'; import { queryKeys } from './query-keys';
import { buildUrl } from './urls'; import { buildUrl } from './urls';
@ -12,22 +15,27 @@ interface Options<T> {
enabled?: boolean; enabled?: boolean;
} }
export function usePreviousVersions<T = Record<EnvironmentId, string>>({ export function usePreviousVersions<T = Record<EdgeGroupId, string>>(
select, edgeGroupIds: EdgeGroupId[],
onSuccess, { select, enabled }: Options<T> = {}
enabled, ) {
}: Options<T> = {}) { return useQuery(
return useQuery(queryKeys.previousVersions(), getPreviousVersions, { queryKeys.previousVersions(edgeGroupIds),
select, () => getPreviousVersions(edgeGroupIds),
onSuccess, {
enabled, select,
}); enabled: enabled && edgeGroupIds.length > 0,
}
);
} }
async function getPreviousVersions() { async function getPreviousVersions(edgeGroupIds: EdgeGroupId[]) {
try { try {
const { data } = await axios.get<Record<EnvironmentId, string>>( const { data } = await axios.get<Record<EdgeGroupId, string>>(
buildUrl(undefined, 'previous_versions') buildUrl(undefined, 'previous_versions'),
{
params: { edgeGroupIds },
}
); );
return data; return data;
} catch (err) { } catch (err) {