1- import { LockClosedIcon , LockOpenIcon , ScaleIcon } from "@heroicons/react/20/solid" ;
21import { Form , useNavigation } from "@remix-run/react" ;
32import { type ReactNode , useEffect , useMemo , useState } from "react" ;
43import { BigNumber } from "~/components/metrics/BigNumber" ;
@@ -28,64 +27,6 @@ import { cn } from "~/utils/cn";
2827
2928type Drafts = Record < string , number > ;
3029
31- /**
32- * Distribute the env budget across unlocked queues, weighted by current load
33- * (running + queued), largest-remainder rounding, min 1 when the budget allows.
34- * Locked queues keep their current value and are subtracted from the budget first.
35- */
36- export function computeAutoBalance (
37- queues : QueueAllocationItem [ ] ,
38- envLimit : number ,
39- locked : Set < string > ,
40- draftLimit : ( queue : QueueAllocationItem ) => number | null
41- ) : Drafts {
42- const unlocked = queues . filter ( ( q ) => ! locked . has ( q . id ) ) ;
43- if ( unlocked . length === 0 ) return { } ;
44-
45- const lockedSum = queues
46- . filter ( ( q ) => locked . has ( q . id ) )
47- . reduce ( ( sum , q ) => sum + ( draftLimit ( q ) ?? 0 ) , 0 ) ;
48- const budget = Math . max ( 0 , envLimit - lockedSum ) ;
49-
50- const weights = unlocked . map ( ( q ) => q . running + q . queued ) ;
51- const totalWeight = weights . reduce ( ( a , b ) => a + b , 0 ) ;
52- const raw = unlocked . map ( ( _ , i ) =>
53- totalWeight > 0 ? ( budget * weights [ i ] ) / totalWeight : budget / unlocked . length
54- ) ;
55-
56- const shares = raw . map ( Math . floor ) ;
57- let remainder = budget - shares . reduce ( ( a , b ) => a + b , 0 ) ;
58- const byFraction = raw
59- . map ( ( value , i ) => ( { i, fraction : value - Math . floor ( value ) } ) )
60- . sort ( ( a , b ) => b . fraction - a . fraction ) ;
61- for ( const { i } of byFraction ) {
62- if ( remainder <= 0 ) break ;
63- shares [ i ] ++ ;
64- remainder -- ;
65- }
66-
67- // Every unlocked queue gets at least 1 when the budget can afford it.
68- if ( budget >= unlocked . length ) {
69- for ( let i = 0 ; i < shares . length ; i ++ ) {
70- while ( shares [ i ] < 1 ) {
71- let donor = - 1 ;
72- for ( let j = 0 ; j < shares . length ; j ++ ) {
73- if ( j !== i && shares [ j ] > 1 && ( donor === - 1 || shares [ j ] > shares [ donor ] ) ) donor = j ;
74- }
75- if ( donor === - 1 ) break ;
76- shares [ donor ] -- ;
77- shares [ i ] ++ ;
78- }
79- }
80- }
81-
82- const result : Drafts = { } ;
83- unlocked . forEach ( ( q , i ) => {
84- result [ q . id ] = Math . min ( Math . max ( shares [ i ] , 0 ) , envLimit ) ;
85- } ) ;
86- return result ;
87- }
88-
8930export function AllocationView ( {
9031 allocation,
9132 environment,
@@ -94,7 +35,6 @@ export function AllocationView({
9435 environment : Environment ;
9536} ) {
9637 const [ drafts , setDrafts ] = useState < Drafts > ( { } ) ;
97- const [ locked , setLocked ] = useState < Set < string > > ( new Set ( ) ) ;
9838 const [ reviewOpen , setReviewOpen ] = useState ( false ) ;
9939 const navigation = useNavigation ( ) ;
10040 const isSubmitting = navigation . state !== "idle" ;
@@ -154,29 +94,6 @@ export function AllocationView({
15494 } ) ;
15595 } ;
15696
157- const toggleLock = ( id : string ) => {
158- setLocked ( ( prev ) => {
159- const next = new Set ( prev ) ;
160- if ( next . has ( id ) ) next . delete ( id ) ;
161- else next . add ( id ) ;
162- return next ;
163- } ) ;
164- } ;
165-
166- const autoBalance = ( ) => {
167- const balanced = computeAutoBalance ( allocation . queues , envLimit , locked , draftLimit ) ;
168- setDrafts ( ( prev ) => {
169- const next = { ...prev } ;
170- for ( const queue of allocation . queues ) {
171- const value = balanced [ queue . id ] ;
172- if ( value === undefined ) continue ;
173- if ( value === queue . limit ) delete next [ queue . id ] ;
174- else next [ queue . id ] = value ;
175- }
176- return next ;
177- } ) ;
178- } ;
179-
18097 const changesPayload = useMemo (
18198 ( ) =>
18299 JSON . stringify ( changes . map ( ( queue ) => ( { friendlyId : queue . id , limit : drafts [ queue . id ] } ) ) ) ,
@@ -237,8 +154,8 @@ export function AllocationView({
237154 { overAllocated && (
238155 < Callout variant = "warning" >
239156 The queue limits add up to more than the environment limit, so queues will compete for
240- concurrency when the environment saturates. Reduce limits (or use Auto-balance) to
241- guarantee each queue its allocation.
157+ concurrency when the environment saturates. Reduce limits to guarantee each queue its
158+ allocation.
242159 </ Callout >
243160 ) }
244161
@@ -250,16 +167,6 @@ export function AllocationView({
250167 ) }
251168
252169 < div className = "flex items-center gap-2" >
253- < Button
254- type = "button"
255- variant = "secondary/small"
256- LeadingIcon = { ScaleIcon }
257- onClick = { autoBalance }
258- disabled = { isSubmitting }
259- tooltip = "Distribute the environment limit across unlocked queues, weighted by current load"
260- >
261- Auto-balance
262- </ Button >
263170 < Button
264171 type = "button"
265172 variant = "minimal/small"
@@ -332,14 +239,10 @@ export function AllocationView({
332239 >
333240 Limit
334241 </ TableHeaderCell >
335- < TableHeaderCell className = "w-[1%]" >
336- < span className = "sr-only" > Lock</ span >
337- </ TableHeaderCell >
338242 </ TableRow >
339243 </ TableHeader >
340244 < TableBody >
341245 { tableQueues . map ( ( queue ) => {
342- const isLocked = locked . has ( queue . id ) ;
343246 const changed = drafts [ queue . id ] !== undefined && drafts [ queue . id ] !== queue . limit ;
344247 return (
345248 < TableRow key = { queue . id } >
@@ -377,31 +280,12 @@ export function AllocationView({
377280 value = { drafts [ queue . id ] ?? queue . limit ?? "" }
378281 placeholder = { String ( envLimit ) }
379282 onChange = { ( e ) => setDraft ( queue , e . target . value ) }
380- disabled = { isLocked || isSubmitting }
283+ disabled = { isSubmitting }
381284 className = "w-24"
382285 variant = "small"
383286 />
384287 </ span >
385288 </ TableCell >
386- < TableCell >
387- < SimpleTooltip
388- button = {
389- < Button
390- type = "button"
391- variant = "minimal/small"
392- LeadingIcon = { isLocked ? LockClosedIcon : LockOpenIcon }
393- leadingIconClassName = { isLocked ? "text-text-bright" : "text-text-dimmed" }
394- onClick = { ( ) => toggleLock ( queue . id ) }
395- aria-label = { isLocked ? "Unlock queue" : "Lock queue" }
396- />
397- }
398- content = {
399- isLocked
400- ? "Locked: auto-balance keeps this queue's limit"
401- : "Unlocked: auto-balance can change this queue's limit"
402- }
403- />
404- </ TableCell >
405289 </ TableRow >
406290 ) ;
407291 } ) }
0 commit comments