Make upgrade applications batch size a job parameter defaulting to 5 (#23101)

Makes the batch size used when upgrading applications a parameter
instead of a hardcoded constant, defaulting to 5, and lets admins set it
from the upgrade confirmation modal.

Backend:
- `UpgradeApplicationsJobData` gains an optional `batchSize` field,
passed through by `UpgradeApplicationsJob` to the service.
- `ApplicationUpgradeService.upgradeAllApplications` accepts an optional
`batchSize` parameter, defaulting to
`UPGRADE_APPLICATIONS_DEFAULT_BATCH_SIZE = 5` (previously a fixed batch
size of 20). The value is sanitized to a positive integer to avoid an
infinite batching loop.
- The `upgradeRegistrationApplications` admin mutation accepts an
optional `batchSize: Int` argument and forwards it to the job.

Frontend (admin panel):
- The "Upgrade existing installations" confirmation modal now includes a
"Batch size" number input, defaulting to 5, sent with the mutation.
- Updated the admin GraphQL document and generated types.

## Screenshots

Upgrade section on the admin app registration page:

![Upgrade
section](https://raw.githubusercontent.com/twentyhq/twenty/8f6f7b5b87b8218e10f9f8ed8a8b705cf25f22f6/upgrade-section.png)

Confirmation modal with the new batch size input (defaults to 5):

![Upgrade confirmation modal with batch size
input](https://raw.githubusercontent.com/twentyhq/twenty/8f6f7b5b87b8218e10f9f8ed8a8b705cf25f22f6/upgrade-modal-batch-size.png)

---------

Co-authored-by: Martin <martin@twenty.com>
This commit is contained in:
martmull
2026-07-21 16:22:19 +02:00
committed by GitHub
parent 07be5e0892
commit e14c32015f
7 changed files with 60 additions and 7 deletions
@@ -566,6 +566,8 @@ export class AdminPanelResolver {
@Mutation(() => Boolean)
async upgradeRegistrationApplications(
@Args('applicationRegistrationId') applicationRegistrationId: string,
@Args('batchSize', { type: () => Int, nullable: true })
batchSize?: number,
): Promise<boolean> {
await this.applicationRegistrationService.findOneByIdGlobal(
applicationRegistrationId,
@@ -573,7 +575,11 @@ export class AdminPanelResolver {
await this.workspaceQueueService.add<UpgradeApplicationsJobData>(
UPGRADE_APPLICATIONS_JOB_NAME,
{ applicationRegistrationId, onlyAutoUpgrade: false },
{
applicationRegistrationId,
onlyAutoUpgrade: false,
...(isDefined(batchSize) ? { batchSize } : {}),
},
{
id: `${UPGRADE_APPLICATIONS_JOB_NAME}-${applicationRegistrationId}`,
}, // Avoids triggering multiple pending jobs for the same app
@@ -21,7 +21,7 @@ const npmPackageMetadataSchema = z.object({
version: z.string(),
});
const UPGRADE_APPLICATIONS_BATCH_SIZE = 20;
const UPGRADE_APPLICATIONS_DEFAULT_BATCH_SIZE = 5;
@Injectable()
export class ApplicationUpgradeService {
@@ -114,9 +114,11 @@ export class ApplicationUpgradeService {
async upgradeAllApplications({
applicationRegistrationId,
onlyAutoUpgrade = false,
batchSize = UPGRADE_APPLICATIONS_DEFAULT_BATCH_SIZE,
}: {
applicationRegistrationId: string;
onlyAutoUpgrade?: boolean;
batchSize?: number;
}): Promise<void> {
const appRegistration = await this.appRegistrationRepository.findOneOrFail({
where: { id: applicationRegistrationId },
@@ -139,14 +141,16 @@ export class ApplicationUpgradeService {
(application) => application.version !== targetVersion,
);
const sanitizedBatchSize = Math.max(1, Math.floor(batchSize));
for (
let batchStart = 0;
batchStart < applicationsToUpgrade.length;
batchStart += UPGRADE_APPLICATIONS_BATCH_SIZE
batchStart += sanitizedBatchSize
) {
const batch = applicationsToUpgrade.slice(
batchStart,
batchStart + UPGRADE_APPLICATIONS_BATCH_SIZE,
batchStart + sanitizedBatchSize,
);
await Promise.all(
@@ -3,4 +3,5 @@ export const UPGRADE_APPLICATIONS_JOB_NAME = 'UpgradeApplicationsJob';
export type UpgradeApplicationsJobData = {
applicationRegistrationId: string;
onlyAutoUpgrade: boolean;
batchSize?: number;
};
@@ -18,6 +18,7 @@ export class UpgradeApplicationsJob {
await this.applicationUpgradeService.upgradeAllApplications({
applicationRegistrationId: data.applicationRegistrationId,
onlyAutoUpgrade: data.onlyAutoUpgrade,
batchSize: data.batchSize,
});
}
}