Skip to content

Commit b609071

Browse files
Streamline fleet inventory refresh
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 5122d80 commit b609071

5 files changed

Lines changed: 97 additions & 92 deletions

File tree

.github/extensions/process-workflow-fleet/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ automatically.
5050

5151
## Use and test
5252

53-
The dashboard refresh button runs
53+
The dashboard automatically refreshes missing inventory and evidence older than
54+
15 minutes. Its refresh button provides an explicit retry. Both paths run
5455
`.github/scripts/Get-ProcessPSModuleWorkflowInventory.ps1` in authenticated
5556
GitHub mode with target `v8`. A failed refresh clears prior success state and
5657
shows the command context and sanitized diagnostic.

.github/extensions/process-workflow-fleet/extension.mjs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,12 @@ const canvas = createCanvas({
157157
},
158158
],
159159
open: async (ctx) => {
160-
const state = await fleet.ensureState({
160+
const entry = await fleet.openPanel(ctx.instanceId, {
161161
organization: ctx.input?.organization,
162162
});
163-
const entry = await fleet.openPanel(ctx.instanceId);
164163
return {
165164
title: "Process workflow fleet",
166-
status:
167-
state.inventoryStatus === "ready"
168-
? `${state.records.length} workflows`
169-
: "Refresh required",
165+
status: "Inventory updates automatically",
170166
url: entry.url,
171167
};
172168
},

.github/extensions/process-workflow-fleet/fleet-service.mjs

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -546,14 +546,14 @@ export function createFleetService({ getSession, repositoryRoot }) {
546546
};
547547
}
548548

549-
async function handleHttpRequest(request, response, token) {
549+
async function handleHttpRequest(request, response, token, organization) {
550550
const url = new URL(request.url ?? "/", "http://127.0.0.1");
551551
if (request.method === "GET" && url.pathname === "/") {
552552
responseHtml(response, renderDashboard(token));
553553
return;
554554
}
555555
if (request.method === "GET" && url.pathname === "/api/state") {
556-
responseJson(response, 200, await ensureState());
556+
responseJson(response, 200, await ensureState({ organization }));
557557
return;
558558
}
559559
if (request.method !== "POST") {
@@ -592,29 +592,35 @@ export function createFleetService({ getSession, repositoryRoot }) {
592592
});
593593
}
594594

595-
async function openPanel(instanceId) {
595+
async function openPanel(instanceId, { organization } = {}) {
596596
const existing = servers.get(instanceId);
597597
if (existing) {
598598
return existing;
599599
}
600600

601601
const token = randomBytes(24).toString("base64url");
602602
const server = createServer((request, response) => {
603-
handleHttpRequest(request, response, token).catch((error) => {
604-
const code = error.code || "canvas_http_failed";
605-
const message = sanitizeDiagnostic(error.message);
606-
getSession()?.log(
607-
`Process workflow fleet request failed: ${message}`,
608-
{ level: "error", ephemeral: true },
609-
);
610-
if (!response.headersSent) {
611-
responseJson(response, code === "route_not_found" ? 404 : 500, {
612-
error: { code, message },
613-
});
614-
} else {
615-
response.end();
616-
}
617-
});
603+
handleHttpRequest(request, response, token, organization).catch(
604+
(error) => {
605+
const code = error.code || "canvas_http_failed";
606+
const message = sanitizeDiagnostic(error.message);
607+
getSession()?.log(
608+
`Process workflow fleet request failed: ${message}`,
609+
{ level: "error", ephemeral: true },
610+
);
611+
if (!response.headersSent) {
612+
responseJson(
613+
response,
614+
code === "route_not_found" ? 404 : 500,
615+
{
616+
error: { code, message },
617+
},
618+
);
619+
} else {
620+
response.end();
621+
}
622+
},
623+
);
618624
});
619625
server.on("clientError", (error, socket) => {
620626
getSession()?.log(

.github/extensions/process-workflow-fleet/renderer.mjs

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,7 @@ export function renderDashboard(token) {
280280
<table aria-label="Process-PSModule caller workflow inventory">
281281
<thead>
282282
<tr>
283-
<th scope="col">Select</th>
284283
<th scope="col">Repository</th>
285-
<th scope="col">Workflow</th>
286284
<th scope="col">Reference / compliance</th>
287285
<th scope="col">Triggers</th>
288286
<th scope="col">Concurrency</th>
@@ -410,22 +408,6 @@ export function renderDashboard(token) {
410408
return cell;
411409
}
412410
413-
function workflowCell(record) {
414-
const cell = document.createElement("td");
415-
const path = document.createElement(record.workflowUrl ? "a" : "span");
416-
path.textContent = text(record.workflowPath);
417-
if (record.workflowUrl) {
418-
path.href = record.workflowUrl;
419-
path.target = "_blank";
420-
path.rel = "noreferrer";
421-
}
422-
const name = document.createElement("div");
423-
name.className = "muted";
424-
name.textContent = text(record.workflowName);
425-
cell.append(path, name);
426-
return cell;
427-
}
428-
429411
function complianceCell(record) {
430412
const cell = document.createElement("td");
431413
const references = record.processJobs.map((job) => job.reference);
@@ -470,7 +452,6 @@ export function renderDashboard(token) {
470452
renderRows();
471453
renderInspector(record);
472454
});
473-
const selectCell = document.createElement("td");
474455
const checkbox = document.createElement("input");
475456
checkbox.type = "checkbox";
476457
checkbox.checked = ui.state.selection.includes(record.repository);
@@ -480,11 +461,10 @@ export function renderDashboard(token) {
480461
checkbox.checked ? next.add(record.repository) : next.delete(record.repository);
481462
await updateSelection([...next]);
482463
});
483-
selectCell.append(checkbox);
464+
const identityCell = repositoryCell(record);
465+
identityCell.prepend(checkbox, document.createTextNode(" "));
484466
row.append(
485-
selectCell,
486-
repositoryCell(record),
487-
workflowCell(record),
467+
identityCell,
488468
complianceCell(record),
489469
codeCell(triggerText(record)),
490470
codeCell("group=" + text(record.concurrencyGroup) + "\\ncancel=" + text(record.cancelInProgress)),
@@ -558,7 +538,16 @@ export function renderDashboard(token) {
558538
statusBadge.textContent = record.analysis.status;
559539
const path = document.createElement("p");
560540
path.className = "muted";
561-
path.textContent = text(record.workflowPath);
541+
if (record.workflowUrl) {
542+
const link = document.createElement("a");
543+
link.href = record.workflowUrl;
544+
link.target = "_blank";
545+
link.rel = "noreferrer";
546+
link.textContent = text(record.workflowPath);
547+
path.append(link, document.createTextNode(" · " + text(record.workflowName)));
548+
} else {
549+
path.textContent = text(record.workflowPath) + " · " + text(record.workflowName);
550+
}
562551
const deltaSection = detailSection(
563552
"Required changes",
564553
record.analysis.deltas,
@@ -604,9 +593,36 @@ export function renderDashboard(token) {
604593
}
605594
}
606595
596+
function shouldAutoRefresh() {
597+
if (ui.state.inventoryStatus === "not-refreshed") return true;
598+
if (ui.state.inventoryStatus !== "ready" || !ui.state.generatedAt) return false;
599+
const generatedAt = Date.parse(ui.state.generatedAt);
600+
return Number.isFinite(generatedAt) && Date.now() - generatedAt > 15 * 60 * 1000;
601+
}
602+
603+
async function refreshInventory() {
604+
byId("refresh").disabled = true;
605+
setStatus("Refreshing authenticated GitHub inventory…");
606+
try {
607+
await api("/api/refresh", {
608+
method: "POST",
609+
body: JSON.stringify({ organization: ui.state.organization || "PSModule" }),
610+
});
611+
} catch (error) {
612+
setStatus(error.message, "error");
613+
} finally {
614+
ui.state = await api("/api/state");
615+
renderState();
616+
byId("refresh").disabled = false;
617+
}
618+
}
619+
607620
async function loadState() {
608621
ui.state = await api("/api/state");
609622
renderState();
623+
if (shouldAutoRefresh()) {
624+
await refreshInventory();
625+
}
610626
}
611627
612628
async function updateSelection(repositories) {
@@ -634,21 +650,7 @@ export function renderDashboard(token) {
634650
});
635651
}
636652
637-
byId("refresh").addEventListener("click", async () => {
638-
byId("refresh").disabled = true;
639-
setStatus("Refreshing authenticated GitHub inventory…");
640-
try {
641-
await api("/api/refresh", {
642-
method: "POST",
643-
body: JSON.stringify({ organization: ui.state.organization || "PSModule" }),
644-
});
645-
} catch (error) {
646-
setStatus(error.message, "error");
647-
} finally {
648-
await loadState();
649-
byId("refresh").disabled = false;
650-
}
651-
});
653+
byId("refresh").addEventListener("click", refreshInventory);
652654
byId("search").addEventListener("input", renderRows);
653655
byId("filter").addEventListener("change", renderRows);
654656
byId("select-visible").addEventListener("click", () => updateSelection(ui.visible.map((record) => record.repository)));

.github/scripts/Get-ProcessPSModuleWorkflowInventory.ps1

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -659,35 +659,35 @@ function Get-WorkflowInventoryItem {
659659
)
660660

661661
[pscustomobject]@{
662-
Repository = $WorkflowFile.Repository
663-
DefaultBranch = $WorkflowFile.DefaultBranch
664-
Archived = $WorkflowFile.Archived
665-
RepositoryUrl = $WorkflowFile.RepositoryUrl
666-
WorkflowPath = $WorkflowFile.WorkflowPath
667-
WorkflowUrl = $WorkflowFile.WorkflowUrl
668-
SearchQuery = $WorkflowFile.SearchQuery
669-
Status = 'Parsed'
670-
Error = $null
671-
WorkflowName = Get-MapValue -Map $workflow -Name 'name'
672-
RunName = Get-MapValue -Map $workflow -Name 'run-name'
673-
Events = @(Get-MapKey -Map $trigger | Sort-Object)
674-
Schedules = @($schedule | ForEach-Object { Get-MapValue -Map $_ -Name 'cron' })
675-
PushBranches = ConvertTo-StringArray -Value (Get-MapValue -Map $push -Name 'branches')
676-
PushBranchesIgnore = ConvertTo-StringArray -Value (Get-MapValue -Map $push -Name 'branches-ignore')
677-
PushPaths = ConvertTo-StringArray -Value (Get-MapValue -Map $push -Name 'paths')
678-
PushPathsIgnore = ConvertTo-StringArray -Value (Get-MapValue -Map $push -Name 'paths-ignore')
679-
PullRequestBranches = ConvertTo-StringArray -Value (Get-MapValue -Map $pullRequest -Name 'branches')
680-
PullRequestTypes = ConvertTo-StringArray -Value (Get-MapValue -Map $pullRequest -Name 'types')
681-
PullRequestPaths = ConvertTo-StringArray -Value (Get-MapValue -Map $pullRequest -Name 'paths')
662+
Repository = $WorkflowFile.Repository
663+
DefaultBranch = $WorkflowFile.DefaultBranch
664+
Archived = $WorkflowFile.Archived
665+
RepositoryUrl = $WorkflowFile.RepositoryUrl
666+
WorkflowPath = $WorkflowFile.WorkflowPath
667+
WorkflowUrl = $WorkflowFile.WorkflowUrl
668+
SearchQuery = $WorkflowFile.SearchQuery
669+
Status = 'Parsed'
670+
Error = $null
671+
WorkflowName = Get-MapValue -Map $workflow -Name 'name'
672+
RunName = Get-MapValue -Map $workflow -Name 'run-name'
673+
Events = @(Get-MapKey -Map $trigger | Sort-Object)
674+
Schedules = @($schedule | ForEach-Object { Get-MapValue -Map $_ -Name 'cron' })
675+
PushBranches = ConvertTo-StringArray -Value (Get-MapValue -Map $push -Name 'branches')
676+
PushBranchesIgnore = ConvertTo-StringArray -Value (Get-MapValue -Map $push -Name 'branches-ignore')
677+
PushPaths = ConvertTo-StringArray -Value (Get-MapValue -Map $push -Name 'paths')
678+
PushPathsIgnore = ConvertTo-StringArray -Value (Get-MapValue -Map $push -Name 'paths-ignore')
679+
PullRequestBranches = ConvertTo-StringArray -Value (Get-MapValue -Map $pullRequest -Name 'branches')
680+
PullRequestTypes = ConvertTo-StringArray -Value (Get-MapValue -Map $pullRequest -Name 'types')
681+
PullRequestPaths = ConvertTo-StringArray -Value (Get-MapValue -Map $pullRequest -Name 'paths')
682682
PullRequestPathsIgnore = ConvertTo-StringArray -Value (Get-MapValue -Map $pullRequest -Name 'paths-ignore')
683-
ConcurrencyGroup = $concurrencyGroup
684-
CancelInProgress = $cancelInProgress
685-
Permissions = $permissions
686-
ProcessJobs = @($processJobs)
687-
AdditionalJobs = @($allJobNames | Where-Object { $_ -notin $processJobNames })
688-
VersionComments = $versionComments
689-
TargetReference = $ExpectedTargetReference
690-
MatchesTarget = if ($ExpectedTargetReference) {
683+
ConcurrencyGroup = $concurrencyGroup
684+
CancelInProgress = $cancelInProgress
685+
Permissions = $permissions
686+
ProcessJobs = @($processJobs)
687+
AdditionalJobs = @($allJobNames | Where-Object { $_ -notin $processJobNames })
688+
VersionComments = $versionComments
689+
TargetReference = $ExpectedTargetReference
690+
MatchesTarget = if ($ExpectedTargetReference) {
691691
@($processJobs | Where-Object { -not $_.MatchesTarget }).Count -eq 0
692692
} else {
693693
$null

0 commit comments

Comments
 (0)