Simplify provisioning queue and add shared filters
This commit is contained in:
@@ -60,9 +60,11 @@ function warningMessages(raw: string): string[] {
|
||||
function sourceDetails(source: Omit<ProvisioningSource, 'label' | 'href'>): ProvisioningSource {
|
||||
const id = source.source_id;
|
||||
if (source.kind === 'campaign') {
|
||||
const row = db().prepare('SELECT action FROM deployments WHERE id = ?').get(id) as { action: string } | undefined;
|
||||
const action = row?.action === 'promote' ? 'Promotion' : row?.action === 'rollback' ? 'Rollback' : row?.action === 'drift' ? 'Reconciliation' : 'Campaign';
|
||||
return { ...source, label: `${action} ${id.slice(0, 8)}`, href: row ? `/campaigns/${encodeURIComponent(id)}` : null };
|
||||
const row = db().prepare(`SELECT d.action, d.beta_id, s.name FROM deployments d
|
||||
LEFT JOIN scripts s ON s.id=COALESCE(d.script_id, (SELECT script_id FROM deployment_scripts WHERE deployment_id=d.id ORDER BY script_id LIMIT 1))
|
||||
WHERE d.id=?`).get(id) as { action: string; beta_id: number | null; name: string | null } | undefined;
|
||||
const action = row?.action === 'promote' ? 'Promotion' : row?.action === 'rollback' ? 'Rollback' : row?.action === 'drift' ? 'Maintenance repair' : 'Campaign';
|
||||
return { ...source, label: row ? `${action}${row.beta_id ? ` · Beta #${row.beta_id}` : row.name ? ` · ${row.name}` : action === 'Campaign' ? '' : ' campaign'}` : 'Campaign unavailable', href: row ? `/campaigns/${encodeURIComponent(id)}` : null };
|
||||
}
|
||||
if (source.kind === 'beta') {
|
||||
const row = db().prepare('SELECT notes FROM betas WHERE id = ?').get(id) as { notes: string | null } | undefined;
|
||||
@@ -251,9 +253,13 @@ function preserveOutcome(id: string): void {
|
||||
}
|
||||
|
||||
const activeDeployment = "d.server_deleted_at IS NULL AND d.state IN ('queued', 'awaiting-health', 'running', 'waiting')";
|
||||
const reviewDeployment = "d.state IN ('failed', 'interrupted', 'warning')";
|
||||
const reviewDeployment = `d.state IN ('failed', 'interrupted', 'warning') AND NOT EXISTS (
|
||||
SELECT 1 FROM retention_controls c WHERE c.category='provisioning' AND c.record_id=d.id
|
||||
AND c.resolved_at >= COALESCE(d.finished_at,d.started_at))`;
|
||||
const deploymentListColumns = `d.id, d.server, d.server_deleted_at, d.action, d.state, d.stage,
|
||||
d.started_at, d.finished_at, d.triggered_by, d.error, d.warnings,
|
||||
d.phase_key AS phase, d.phase_started_at AS phaseStartedAt,
|
||||
CASE WHEN json_valid(d.observation_json) THEN json_extract(d.observation_json,'$.blocker.code') END AS waitCode,
|
||||
(d.server_deleted_at IS NULL AND EXISTS(SELECT 1 FROM servers s WHERE s.name = d.server AND s.destroyed_at IS NULL)) AS server_exists,
|
||||
(d.server_deleted_at IS NULL AND d.action = 'destroy' AND d.state = 'waiting' AND d.stage != ?) AS canCancelRemoval,
|
||||
(d.server_deleted_at IS NULL AND d.state IN ('queued','waiting','running','awaiting-health','interrupted')) AS canCancel
|
||||
@@ -265,6 +271,16 @@ function deploymentListQuery(options: ProvisioningListOptions) {
|
||||
const parameters: string[] = [];
|
||||
if (options.action) { conditions.push('d.action = ?'); parameters.push(options.action); }
|
||||
if (options.status === 'attention') conditions.push(reviewDeployment);
|
||||
else if (options.status === 'pending') conditions.push("d.state IN ('queued','waiting')");
|
||||
else if (options.status) { conditions.push('d.state=?'); parameters.push(options.status); }
|
||||
const linked = (kinds: string[]) => `(EXISTS (SELECT 1 FROM provisioning_sources src WHERE src.operation_id=d.id AND src.kind IN (${kinds.map(kind => `'${kind}'`).join(',')}))
|
||||
OR ${kinds.map(kind => `d.triggered_by LIKE '${kind}:%'`).join(' OR ')})`;
|
||||
if (options.source === 'campaign' || options.source === 'beta') conditions.push(linked([options.source]));
|
||||
else if (options.source === 'maintenance') conditions.push(linked(['maintenance', 'watchdog']));
|
||||
else if (options.source === 'manual' || options.source === 'system') {
|
||||
conditions.push(`NOT ${linked(['campaign', 'beta', 'maintenance', 'watchdog'])}`);
|
||||
conditions.push(options.source === 'manual' ? "d.triggered_by='ui'" : "d.triggered_by!='ui'");
|
||||
}
|
||||
const search = (options.server ?? '').slice(0, 100).toLowerCase();
|
||||
if (search) {
|
||||
// Read only distinct names to preserve friendly map/lobby searches, including deleted servers.
|
||||
@@ -278,9 +294,27 @@ function deploymentListQuery(options: ProvisioningListOptions) {
|
||||
}
|
||||
|
||||
function deploymentListRows(query: ReturnType<typeof deploymentListQuery>, active: boolean): ProvisioningListRow[] {
|
||||
return db().prepare(`SELECT ${deploymentListColumns} FROM server_deployments d WHERE ${query.where}
|
||||
const rows = db().prepare(`SELECT ${deploymentListColumns} FROM server_deployments d WHERE ${query.where}
|
||||
ORDER BY d.started_at DESC, d.rowid DESC ${active ? '' : 'LIMIT ? OFFSET ?'}`)
|
||||
.all(REMOVING_SERVER, ...query.parameters, ...(active ? [] : [query.limit, query.offset])) as ProvisioningListRow[];
|
||||
if (!rows.length) return rows;
|
||||
const links = db().prepare(`SELECT operation_id,kind,source_id FROM provisioning_sources
|
||||
WHERE operation_id IN (SELECT value FROM json_each(?)) ORDER BY kind,source_id`).all(JSON.stringify(rows.map(row => row.id))) as
|
||||
(Omit<ProvisioningSource, 'label' | 'href'> & { operation_id: string })[];
|
||||
const byOperation = new Map<string, Omit<ProvisioningSource, 'label' | 'href'>[]>();
|
||||
for (const { operation_id, ...link } of links) byOperation.set(operation_id, [...(byOperation.get(operation_id) ?? []), link]);
|
||||
const cache = new Map<string, ProvisioningSource>();
|
||||
for (const row of rows) {
|
||||
const sources = byOperation.get(row.id) ?? [];
|
||||
const fallback = row.triggered_by.match(/^(campaign|beta|maintenance|watchdog):(.+)$/);
|
||||
if (fallback && !sources.some(source => source.kind === fallback[1] && source.source_id === fallback[2])) sources.unshift({ kind: fallback[1] as ProvisioningSource['kind'], source_id: fallback[2] });
|
||||
row.sources = sources.map(source => {
|
||||
const key = `${source.kind}:${source.source_id}`;
|
||||
if (!cache.has(key)) cache.set(key, sourceDetails(source));
|
||||
return cache.get(key)!;
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/** All list reads omit observation snapshots; retained history is always bounded. */
|
||||
|
||||
Reference in New Issue
Block a user