Update scheduling algorithm

This commit is contained in:
2026-07-23 14:02:14 -04:00
parent 5eebee70d8
commit 23ae3a2f3a
12 changed files with 555 additions and 231 deletions
+17 -3
View File
@@ -5,7 +5,17 @@ export default function FailedBanner({ rerunTests, onDecision }) {
const hours = Math.floor(totalMinutes / 60)
const mins = totalMinutes % 60
const timeStr = hours > 0 ? `${hours}h ${mins}m` : `${mins}m`
const testIds = rerunTests.map(t => t.test_id).join(', ')
// Group test IDs by device
const byDevice = rerunTests.reduce((acc, t) => {
const device = t.device ?? 'Unknown'
if (!acc[device]) acc[device] = []
acc[device].push(t.test_id)
return acc
}, {})
const deviceSummaries = Object.entries(byDevice)
.sort(([a], [b]) => a.localeCompare(b))
.map(([device, ids]) => `${device}: ${ids.join(', ')}`)
return (
<div className="flex items-start gap-4 px-6 py-3 bg-red-900/70 border-b border-red-700 text-red-100">
@@ -26,9 +36,13 @@ export default function FailedBanner({ rerunTests, onDecision }) {
<div className="flex-1 min-w-0">
<p className="text-sm font-medium">
<span className="font-semibold">{rerunTests.length} test{rerunTests.length !== 1 ? 's' : ''}</span>
{' '}require a rerun not completed in last overnight window:{' '}
<span className="font-mono text-red-200">{testIds}</span>
{' '}require a rerun not completed in last overnight window:
</p>
{deviceSummaries.map((summary) => (
<p key={summary} className="text-sm font-mono text-red-200 mt-0.5 truncate">
{summary}
</p>
))}
<p className="text-sm text-red-300 mt-0.5">
Estimated rerun time: <span className="font-semibold text-red-100">{timeStr}</span>
{' '} Schedule rerun during daytime today?
+93 -2
View File
@@ -16,12 +16,22 @@ function Field({ label, hint, required = false, children }) {
const INPUT_CLS =
'w-full bg-gray-900 border border-gray-600 rounded-md px-3 py-1.5 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-blue-500 font-mono'
export default function SettingsModal({ isOpen, onClose, settings, onSave }) {
export default function SettingsModal({ isOpen, onClose, settings, onSave, onRestart }) {
const [form, setForm] = useState({ ...settings })
const [confirmOpen, setConfirmOpen] = useState(false)
const [confirmText, setConfirmText] = useState('')
const [restartBusy, setRestartBusy] = useState(false)
const [restartError, setRestartError] = useState(null)
// Sync if parent settings change while modal is closed
useEffect(() => {
if (!isOpen) setForm({ ...settings })
if (!isOpen) {
setForm({ ...settings })
setConfirmOpen(false)
setConfirmText('')
setRestartBusy(false)
setRestartError(null)
}
}, [isOpen, settings])
if (!isOpen) return null
@@ -35,6 +45,30 @@ export default function SettingsModal({ isOpen, onClose, settings, onSave }) {
onClose()
}
async function handleRestartClick() {
if (!confirmOpen) {
setConfirmOpen(true)
setRestartError(null)
return
}
if (confirmText.trim() !== 'RESET') {
setRestartError('Type RESET to confirm database clear.')
return
}
setRestartBusy(true)
setRestartError(null)
try {
await onRestart()
onClose()
} catch (err) {
setRestartError(err?.message || 'Failed to restart and clear data.')
} finally {
setRestartBusy(false)
}
}
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
@@ -263,6 +297,63 @@ export default function SettingsModal({ isOpen, onClose, settings, onSave }) {
/>
</Field>
</div>
<hr className="border-gray-700" />
{/* Section: Danger Zone */}
<div>
<p className="text-[11px] font-bold uppercase tracking-widest text-red-400 mb-3">
Danger Zone
</p>
<div className="rounded-lg border border-red-800 bg-red-950/30 p-3">
<p className="text-sm text-red-200 font-semibold">Reset And Clear Database</p>
<p className="mt-1 text-xs text-red-300/90">
This permanently deletes tests, schedules, settings, and holidays from the database.
</p>
{confirmOpen && (
<div className="mt-3 flex flex-col gap-2">
<p className="text-xs text-red-200">
Type <span className="font-semibold">RESET</span> below, then click the button again to confirm.
</p>
<input
type="text"
className={INPUT_CLS}
placeholder="Type RESET"
value={confirmText}
onChange={(e) => setConfirmText(e.target.value)}
/>
</div>
)}
{restartError && (
<p className="mt-2 text-xs text-red-300">{restartError}</p>
)}
<div className="mt-3 flex gap-2">
<button
onClick={handleRestartClick}
disabled={restartBusy}
className="px-3 py-1.5 text-sm font-semibold text-white bg-red-700 hover:bg-red-600 border border-red-500 rounded-md transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
>
{restartBusy ? 'Clearing...' : confirmOpen ? 'Confirm Clear DB' : 'Reset'}
</button>
{confirmOpen && (
<button
onClick={() => {
setConfirmOpen(false)
setConfirmText('')
setRestartError(null)
}}
disabled={restartBusy}
className="px-3 py-1.5 text-sm text-gray-300 bg-gray-800 border border-gray-600 rounded-md hover:bg-gray-700 transition-colors disabled:opacity-60"
>
Cancel
</button>
)}
</div>
</div>
</div>
</div>
{/* Footer */}