diff --git a/apps/console/web/src/components/CreateJob.tsx b/apps/console/web/src/components/CreateJob.tsx
index f905064ed..330684cc4 100644
--- a/apps/console/web/src/components/CreateJob.tsx
+++ b/apps/console/web/src/components/CreateJob.tsx
@@ -76,6 +76,13 @@ const STEP_INDEX: Record
+ Initial probing grows by at least 25%; after the first decrease, growth uses the Additive Increase step. +
+ +{MIN_CLIENT_MAX_CONCURRENCY} - {MAX_CLIENT_MAX_CONCURRENCY}
@@ -1101,33 +1131,71 @@ export function CreateJob({ onBack }: CreateJobProps) { handleParamChange('adaptiveMaxFactor', e.target.value, setAdaptiveMaxFactor, MIN_ADAPTIVE_MAX_FACTOR, undefined, false)} placeholder="e.g. 8" - className={`w-full px-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-teal-500/30 focus:border-teal-500 ${ - paramErrors.adaptiveMaxFactor ? 'border-red-300' : 'border-gray-200' + className={`w-full px-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-teal-500/30 focus:border-teal-500 disabled:bg-gray-50 disabled:text-gray-400 disabled:cursor-not-allowed ${ + adaptiveConcurrency && paramErrors.adaptiveMaxFactor ? 'border-red-300' : 'border-gray-200' }`} /> - {paramErrors.adaptiveMaxFactor && ( + {adaptiveConcurrency && paramErrors.adaptiveMaxFactor && ({paramErrors.adaptiveMaxFactor}
)}Healthy completions before each increase, ≥ 1
+ handleParamChange( + 'adaptiveHealthyWindow', + e.target.value, + setAdaptiveHealthyWindow, + MIN_ADAPTIVE_HEALTHY_WINDOW, + undefined, + true, + )} + placeholder="e.g. 8" + className={`w-full px-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-teal-500/30 focus:border-teal-500 disabled:bg-gray-50 disabled:text-gray-400 disabled:cursor-not-allowed ${ + adaptiveConcurrency && paramErrors.adaptiveHealthyWindow ? 'border-red-300' : 'border-gray-200' + }`} + /> + {adaptiveConcurrency && paramErrors.adaptiveHealthyWindow && ( +{paramErrors.adaptiveHealthyWindow}
+ )} +Fixed growth step after the first decrease, ≥ 1
+ handleParamChange( + 'adaptiveAdditiveIncrease', + e.target.value, + setAdaptiveAdditiveIncrease, + MIN_ADAPTIVE_ADDITIVE_INCREASE, + undefined, + true, + )} + placeholder="e.g. 1" + className={`w-full px-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-teal-500/30 focus:border-teal-500 disabled:bg-gray-50 disabled:text-gray-400 disabled:cursor-not-allowed ${ + adaptiveConcurrency && paramErrors.adaptiveAdditiveIncrease ? 'border-red-300' : 'border-gray-200' + }`} + /> + {adaptiveConcurrency && paramErrors.adaptiveAdditiveIncrease && ( +{paramErrors.adaptiveAdditiveIncrease}
+ )} +≥ 0
diff --git a/apps/console/web/src/utils/api.test.ts b/apps/console/web/src/utils/api.test.ts index 41457f2b6..d796fccf5 100644 --- a/apps/console/web/src/utils/api.test.ts +++ b/apps/console/web/src/utils/api.test.ts @@ -132,6 +132,24 @@ describe('api helpers', () => { }); }); + it('serializes adaptive AIMD settings to the job client contract', () => { + expect(camelToSnake({ + client: { + adaptiveConcurrency: true, + adaptiveMaxFactor: 16, + adaptiveHealthyWindow: 8, + adaptiveAdditiveIncrease: 2, + }, + })).toEqual({ + client: { + adaptive_concurrency: true, + adaptive_max_factor: 16, + adaptive_healthy_window: 8, + adaptive_additive_increase: 2, + }, + }); + }); + it('publishes the first jobs page while older pages are still loading', async () => { let resolveSecondPage!: (response: Response) => void; const secondPage = new Promise