agents-relay 1.0.14 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dashboard.js +59 -6
- package/dist/store.js +6 -2
- package/package.json +1 -1
package/dist/dashboard.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
2
5
|
import { DeliveryDeduper, parseGitHubWebhook, verifyGitHubWebhook } from './github-webhook.js';
|
|
3
6
|
const GITHUB_REPOSITORY_PATTERN = /^([A-Za-z0-9](?:[A-Za-z0-9-]{0,36}[A-Za-z0-9])?)\/([A-Za-z0-9._-]{1,100})$/;
|
|
4
7
|
const SSE_HEARTBEAT_MS = 15000;
|
|
8
|
+
const EVENTS_HISTORY_FILE = process.env.EVENTS_BUS_DATA_DIR ? join(process.env.EVENTS_BUS_DATA_DIR, 'events.jsonl') : join(homedir(), 'Library', 'Application Support', 'EventsBus', 'events.jsonl');
|
|
5
9
|
function isPositiveInteger(candidate) {
|
|
6
10
|
return typeof candidate === 'number' && Number.isInteger(candidate) && candidate > 0;
|
|
7
11
|
}
|
|
@@ -19,6 +23,32 @@ export function githubPrUrl(repository, prNumber) {
|
|
|
19
23
|
return null;
|
|
20
24
|
return `https://github.com/${owner}/${repo}/pull/${prNumber}`;
|
|
21
25
|
}
|
|
26
|
+
async function eventHistory(jobId, taskId, limit) {
|
|
27
|
+
const boundedLimit = Math.max(1, Math.min(60, limit));
|
|
28
|
+
let content = '';
|
|
29
|
+
try {
|
|
30
|
+
content = await readFile(EVENTS_HISTORY_FILE, 'utf8');
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const output = [];
|
|
36
|
+
const lines = content.split(/\r?\n/);
|
|
37
|
+
for (let index = lines.length - 1; index >= 0 && output.length < boundedLimit; index -= 1) {
|
|
38
|
+
const line = lines[index];
|
|
39
|
+
if (!line)
|
|
40
|
+
continue;
|
|
41
|
+
try {
|
|
42
|
+
const row = JSON.parse(line);
|
|
43
|
+
const event = row.event;
|
|
44
|
+
if (!event || event.job_id !== jobId || (taskId && event.task_id !== taskId))
|
|
45
|
+
continue;
|
|
46
|
+
output.push(event);
|
|
47
|
+
}
|
|
48
|
+
catch { /* ignore malformed historical rows */ }
|
|
49
|
+
}
|
|
50
|
+
return output;
|
|
51
|
+
}
|
|
22
52
|
export const dashboardHtml = `<!doctype html>
|
|
23
53
|
<html lang="en" data-dashboard-contract="safe-dom,named-relay-events,snapshot-events,bounded-event-history">
|
|
24
54
|
<head>
|
|
@@ -128,7 +158,7 @@ export const dashboardHtml = `<!doctype html>
|
|
|
128
158
|
</div>
|
|
129
159
|
</section>
|
|
130
160
|
<section class="panel" id="events-panel" aria-labelledby="events-heading">
|
|
131
|
-
<div class="panel-head"><h2 id="events-heading">Events</h2><span class="subtle">
|
|
161
|
+
<div class="panel-head"><h2 id="events-heading">Events</h2><div class="panel-actions"><button id="events-all-tasks" class="refresh-button" type="button">All tasks</button><span id="events-scope" class="subtle">Job-wide · newest first · 60 max</span></div></div>
|
|
132
162
|
<div class="panel-body"><ol id="event-feed" class="event-list"><li class="empty">Waiting for events.</li></ol></div>
|
|
133
163
|
</section>
|
|
134
164
|
</div>
|
|
@@ -167,6 +197,8 @@ const jobDetail=document.querySelector('#job-detail');
|
|
|
167
197
|
const taskTree=document.querySelector('#task-tree');
|
|
168
198
|
const taskDetailRoot=document.querySelector('#task-detail');
|
|
169
199
|
const eventFeed=document.querySelector('#event-feed');
|
|
200
|
+
const eventsAllTasks=document.querySelector('#events-all-tasks');
|
|
201
|
+
const eventsScope=document.querySelector('#events-scope');
|
|
170
202
|
const connection=document.querySelector('#connection');
|
|
171
203
|
const selectedJobTitle=document.querySelector('#selected-job-title');
|
|
172
204
|
const usageRoot=document.querySelector('#usage-root');
|
|
@@ -176,6 +208,8 @@ let jobs=[];
|
|
|
176
208
|
let selectedJob=sessionStorage.getItem('agents-relay:selected-job');
|
|
177
209
|
let selectedTaskId=sessionStorage.getItem('agents-relay:selected-task');
|
|
178
210
|
let selectedRepo=sessionStorage.getItem('agents-relay:repo-filter')||'';
|
|
211
|
+
let eventTaskId=selectedTaskId;
|
|
212
|
+
const displayedEventIds=new Set();
|
|
179
213
|
|
|
180
214
|
function value(input){return input===null||input===undefined||input===''?'—':String(input);}
|
|
181
215
|
function text(input){return document.createTextNode(value(input));}
|
|
@@ -190,7 +224,9 @@ function relativeTime(timestamp){if(!timestamp)return '—';const parsed=Date.pa
|
|
|
190
224
|
function safePrUrl(repository,prNumber){if(typeof repository!=='string'||!Number.isInteger(prNumber)||prNumber<=0)return null;const parts=repository.split('/');if(parts.length!==2)return null;const [owner,repo]=parts;if(!owner||!repo||owner==='.'||owner==='..'||repo==='.'||repo==='..')return null;if(!/^[A-Za-z0-9-]+$/.test(owner)||!/^[A-Za-z0-9._-]+$/.test(repo))return null;return 'https://github.com/'+owner+'/'+repo+'/pull/'+prNumber;}
|
|
191
225
|
function prField(job){const box=element('div','detail-group');box.append(element('p','detail-label',text('GitHub PR')));const line=element('p','detail-value');const url=safePrUrl(job.repository,job.prNumber);if(url){const link=document.createElement('a');link.href=url;link.target='_blank';link.rel='noopener noreferrer';link.textContent='PR #'+job.prNumber;line.append(link);}else line.append(text('unavailable'));box.append(line);return box;}
|
|
192
226
|
function chatField(task){const box=element('div','detail-group');box.append(element('p','detail-label',text('Chat')));const line=element('p','detail-value');if(task.adapter==='chatgpt'&&typeof task.threadId==='string'&&task.threadId){const link=document.createElement('a');link.href='https://chatgpt.com/c/'+encodeURIComponent(task.threadId);link.target='_blank';link.rel='noopener noreferrer';link.textContent='Open chat';line.append(link);}else line.append(text('—'));box.append(line);return box;}
|
|
193
|
-
function
|
|
227
|
+
function clearEvents(){displayedEventIds.clear();eventFeed.replaceChildren(element('li','empty',text('Waiting for events.')));}
|
|
228
|
+
async function loadEventHistory(){if(!selectedJob){clearEvents();return;}const params=new URLSearchParams({job:selectedJob,limit:String(MAX_EVENTS)});if(eventTaskId)params.set('task',eventTaskId);try{const history=await loadJSON('/api/events?'+params.toString());clearEvents();for(const event of Array.isArray(history)?history.slice().reverse():[])addEvent(event);}catch{clearEvents();}}
|
|
229
|
+
function selectJob(id){selectedJob=id;selectedTaskId=null;eventTaskId=null;rememberSelection();clearEvents();location.hash=id?'#/jobs/'+encodeURIComponent(id):'#/jobs';renderAll();void loadEventHistory();}
|
|
194
230
|
function filteredJobs(){return jobs.filter(job=>!selectedRepo||job.repository===selectedRepo);}
|
|
195
231
|
function syncFilters(){
|
|
196
232
|
const repos=[...new Set(jobs.map(job=>job.repository).filter(Boolean))].sort();
|
|
@@ -200,11 +236,11 @@ function syncFilters(){
|
|
|
200
236
|
repoFilter.value=selectedRepo;
|
|
201
237
|
}
|
|
202
238
|
function renderJobList(){jobList.replaceChildren();const visible=filteredJobs();if(!jobs.length){jobList.append(element('li','empty',text('No durable jobs discovered.')));return;}if(!visible.length){jobList.append(element('li','empty',text('No jobs match the selected repo.')));return;}for(const job of visible){const button=document.createElement('button');button.type='button';button.className='job-button'+(isCompleteUnmerged(job)?' complete-unmerged':'');button.setAttribute('aria-current',String(job.id===selectedJob));button.title=job.title||job.id;button.addEventListener('click',()=>selectJob(job.id));const meta=element('span','job-meta');meta.append(element('span','badge '+value(job.state),text(job.state)),element('span','badge mode-'+value(job.executionMode||'fixed'),text(value(job.executionMode||'fixed').toUpperCase())),element('span',job.draft?'badge DRAFT':'',text(job.draft?'DRAFT':value(job.githubState||'UNKNOWN'))));if(isCompleteUnmerged(job))meta.append(element('span','badge MERGE_PENDING',text('MERGE PENDING')));meta.append(element('span','',text(job.repository||'unknown repo')),element('span','',text(relativeTime(job.updatedAt))));button.append(element('span','job-title',text('PR#'+value(job.prNumber)+': '+value(job.title||job.id))),meta,element('span','subtle',text(compactCounts(counts(job.tasks)))));jobList.append(element('li','',button));}}
|
|
203
|
-
function taskNode(task,children,depth){const li=element('li');const row=document.createElement('button');row.type='button';row.className='task-row';row.setAttribute('aria-current',String(task.id===selectedTaskId));row.title=task.id;row.addEventListener('click',()=>{selectedTaskId=task.id;rememberSelection();renderAll();});const id=element('span','task-id',text(task.id));id.style.paddingLeft=Math.min(depth,4)*6+'px';row.append(id,element('span','badge '+value(task.state),text(task.state)));li.append(row);const nested=children.get(task.id)||[];if(nested.length){const ul=element('ul','tree');for(const child of nested)ul.append(taskNode(child,children,depth+1));li.append(ul);}return li;}
|
|
239
|
+
function taskNode(task,children,depth){const li=element('li');const row=document.createElement('button');row.type='button';row.className='task-row';row.setAttribute('aria-current',String(task.id===selectedTaskId));row.title=task.id;row.addEventListener('click',()=>{selectedTaskId=task.id;eventTaskId=task.id;rememberSelection();clearEvents();renderAll();void loadEventHistory();});const id=element('span','task-id',text(task.id));id.style.paddingLeft=Math.min(depth,4)*6+'px';row.append(id,element('span','badge '+value(task.state),text(task.state)));li.append(row);const nested=children.get(task.id)||[];if(nested.length){const ul=element('ul','tree');for(const child of nested)ul.append(taskNode(child,children,depth+1));li.append(ul);}return li;}
|
|
204
240
|
function buildTree(tasks){taskTree.replaceChildren();if(!Array.isArray(tasks)||!tasks.length){taskTree.append(element('li','empty',text('No tasks in durable state.')));return;}const known=new Set(tasks.map(task=>task.id));const children=new Map();const roots=[];for(const task of tasks){if(!task.parentTaskId||!known.has(task.parentTaskId))roots.push(task);else{const siblings=children.get(task.parentTaskId)||[];siblings.push(task);children.set(task.parentTaskId,siblings);}}for(const task of roots)taskTree.append(taskNode(task,children,0));}
|
|
205
241
|
function attemptBudget(task){const attempt=Number(task?.attempt)||0;const max=Number(task?.maxAttempts)||0;if(max>=3&&max%3===0){const effectiveMax=Math.max(max,Math.ceil(attempt/3)*3);return attempt+' / 3×'+(effectiveMax/3);}return attempt+' / '+max;}
|
|
206
242
|
function renderTaskDetail(task){taskDetailRoot.replaceChildren();if(!task){taskDetailRoot.append(element('p','empty',text('Select a task to inspect execution details.')));return;}const routing=task.routing||{};const groups=element('div','detail-groups');groups.append(field('Task',task.id),field('Kind',task.kind||'work'),field('State',task.state),field('Project / agent',value(task.projectName)+' / '+value(task.agentName)),field('Parent task',task.parentTaskId||'root'),field('Adapter',task.adapter),field('Provider / model',value(routing.provider)+' / '+value(routing.model)),field('Reasoning / profile',value(routing.reasoning||routing.profile)),field('Attempt',attemptBudget(task)),field('Dependencies',Array.isArray(task.dependencies)&&task.dependencies.length?task.dependencies.join(', '):'none'),chatField(task));if(task.plannerResult)groups.append(field('Objective status',task.plannerResult.objective_status),markdownField('Assessment',task.plannerResult.assessment));if(task.error)groups.append(field('Error',task.error));taskDetailRoot.append(groups);}
|
|
207
|
-
function renderSelectedJob(){jobDetail.replaceChildren();const job=selected();if(!job){selectedJobTitle.textContent='';jobDetail.append(element('div','empty',text('No durable jobs discovered.')));buildTree([]);renderTaskDetail(null);return;}selectedJobTitle.textContent='— '+value(job.title||job.id);if(job.id!==selectedJob){selectedJob=job.id;selectedTaskId=null;rememberSelection();}const summary=element('div','job-summary');summary.append(field('GitHub state',job.githubState||'UNKNOWN'),field('Durable state',job.state),field('Execution mode',job.executionMode||'fixed'),field('Objective',job.objective||job.description||job.title),prField(job),field('Repository',job.repository||'—'),field('Last activity',relativeTime(job.updatedAt)),field('Task states',compactCounts(counts(job.tasks))));jobDetail.append(summary);const selectedTask=(job.tasks||[]).find(task=>task.id===selectedTaskId)||null;if(selectedTaskId&&!selectedTask){selectedTaskId=null;rememberSelection();}buildTree(job.tasks||[]);renderTaskDetail(selectedTask);}
|
|
243
|
+
function renderSelectedJob(){jobDetail.replaceChildren();const job=selected();if(!job){selectedJobTitle.textContent='';jobDetail.append(element('div','empty',text('No durable jobs discovered.')));buildTree([]);renderTaskDetail(null);return;}selectedJobTitle.textContent='— '+value(job.title||job.id);if(job.id!==selectedJob){selectedJob=job.id;selectedTaskId=null;eventTaskId=null;clearEvents();rememberSelection();}const summary=element('div','job-summary');summary.append(field('GitHub state',job.githubState||'UNKNOWN'),field('Durable state',job.state),field('Execution mode',job.executionMode||'fixed'),field('Objective',job.objective||job.description||job.title),prField(job),field('Repository',job.repository||'—'),field('Last activity',relativeTime(job.updatedAt)),field('Task states',compactCounts(counts(job.tasks))));jobDetail.append(summary);const selectedTask=(job.tasks||[]).find(task=>task.id===selectedTaskId)||null;if(selectedTaskId&&!selectedTask){selectedTaskId=null;eventTaskId=null;clearEvents();rememberSelection();}buildTree(job.tasks||[]);renderTaskDetail(selectedTask);renderEventScope();}
|
|
208
244
|
function renderAll(){jobs.sort(compareJobs);syncFilters();renderJobList();renderSelectedJob();}
|
|
209
245
|
function isCompleteUnmerged(job){return job.state==='COMPLETED'&&job.githubState==='OPEN';}
|
|
210
246
|
function isTerminalJob(job){return ['COMPLETED','CANCELLED'].includes(job.state)||['MERGED','CLOSED'].includes(job.githubState);}
|
|
@@ -218,8 +254,9 @@ function renderRateLimitStatus(status){
|
|
|
218
254
|
rateLimitWarning.hidden=false;
|
|
219
255
|
}
|
|
220
256
|
async function refreshStatus(){try{renderRateLimitStatus(await loadJSON('/api/status'));}catch{renderRateLimitStatus(null);}}
|
|
221
|
-
async function refresh(force=false){jobsRefresh.disabled=true;jobsRefresh.textContent='Refreshing…';try{const discovered=await loadJSON('/api/jobs'+(force?'?refresh=1':''));jobs=Array.isArray(discovered)?discovered:[];jobs.sort(compareJobs);if(selectedJob&&!jobs.some(job=>job.id===selectedJob)){selectedJob=null;selectedTaskId=null;}if(!selectedJob){const match=location.hash.match(/^#\\/jobs\\/(.+)$/);const fromHash=match?decodeURIComponent(match[1]):null;selectedJob=jobs.some(job=>job.id===fromHash)?fromHash:(jobs[0]?.id||null);}rememberSelection();renderAll();connection.textContent='Live · durable state connected';connection.className='status-pill ok';}catch(error){connection.textContent='Error · durable state unavailable';connection.className='status-pill bad';jobList.replaceChildren(element('li','error-state',text('Could not load jobs: '+error.message)));}finally{jobsRefresh.disabled=false;jobsRefresh.textContent='Refresh';}}
|
|
222
|
-
function
|
|
257
|
+
async function refresh(force=false){jobsRefresh.disabled=true;jobsRefresh.textContent='Refreshing…';try{const discovered=await loadJSON('/api/jobs'+(force?'?refresh=1':''));jobs=Array.isArray(discovered)?discovered:[];jobs.sort(compareJobs);if(selectedJob&&!jobs.some(job=>job.id===selectedJob)){selectedJob=null;selectedTaskId=null;}if(!selectedJob){const match=location.hash.match(/^#\\/jobs\\/(.+)$/);const fromHash=match?decodeURIComponent(match[1]):null;selectedJob=jobs.some(job=>job.id===fromHash)?fromHash:(jobs[0]?.id||null);}rememberSelection();renderAll();void loadEventHistory();connection.textContent='Live · durable state connected';connection.className='status-pill ok';}catch(error){connection.textContent='Error · durable state unavailable';connection.className='status-pill bad';jobList.replaceChildren(element('li','error-state',text('Could not load jobs: '+error.message)));}finally{jobsRefresh.disabled=false;jobsRefresh.textContent='Refresh';}}
|
|
258
|
+
function renderEventScope(){const task=eventTaskId&&selected()?.tasks?.find(candidate=>candidate.id===eventTaskId);eventsAllTasks.disabled=!task;eventsAllTasks.setAttribute('aria-pressed',String(!task));eventsScope.textContent=task?'Task '+task.id+' · newest first · 60 max':'Job-wide · newest first · 60 max';}
|
|
259
|
+
function addEvent(event,level){if(!event||selectedJob&&event.job_id&&event.job_id!==selectedJob||eventTaskId&&event.task_id!==eventTaskId)return;if(event.event_id&&displayedEventIds.has(event.event_id))return;if(event.event_id)displayedEventIds.add(event.event_id);const item=element('li','event-item');const meta=element('div','event-meta');meta.append(element('span','event-kind',text(event.type||'event')),element('span','',text(relativeTime(event.timestamp))),element('span','',text('task: '+value(event.task_id))));item.append(meta);const message=element('p','event-message',text(event.message||'Event received'));if(level==='error'||event.level==='error')message.classList.add('error-text');item.append(message);eventFeed.querySelector('.empty')?.remove();eventFeed.prepend(item);while(eventFeed.children.length>MAX_EVENTS)eventFeed.lastElementChild?.remove();}
|
|
223
260
|
function usageBar(label,windowData){const percent=Math.max(0,Math.min(100,100-windowData.usedPercent));const row=element('div','usage-row');const heading=element('div','usage-label');heading.append(element('span','',text(label)),element('span','',text(Math.round(percent)+'%')));const track=element('div','usage-track');track.setAttribute('role','progressbar');track.setAttribute('aria-label',label+' available');track.setAttribute('aria-valuemin','0');track.setAttribute('aria-valuemax','100');track.setAttribute('aria-valuenow',String(percent));const fill=element('div','usage-fill');fill.style.width=percent+'%';track.append(fill);row.append(heading,track);return row;}
|
|
224
261
|
function usageProvider(provider){const card=element('section','usage-provider');card.append(element('h3','',text(provider.label)));const usage=provider.usage||{};if(!usage.available||!usage.fiveHour||!usage.weekly){card.append(element('p','empty',text(usage.reason||'Account usage telemetry is unavailable.')));return card;}const bars=element('div','usage-bars');bars.append(usageBar('5-hour remaining',usage.fiveHour),usageBar('Weekly remaining',usage.weekly));card.append(bars);return card;}
|
|
225
262
|
function formatCredit(value,currency){if(value===null||value===undefined||!Number.isFinite(Number(value)))return '—';const number=Number(value);if(currency==='USD')return '$'+number.toFixed(2);if(currency==='CNY')return '¥'+number.toFixed(2);return number.toFixed(2)+' '+currency;}
|
|
@@ -228,6 +265,7 @@ async function drawUsage(){try{const overview=await loadJSON('/api/usage');usage
|
|
|
228
265
|
function showView(view){const isJobs=view==='jobs';jobsView.hidden=!isJobs;jobsWorkspace.hidden=!isJobs;usageView.hidden=isJobs;jobsTab.setAttribute('aria-selected',String(isJobs));usageTab.setAttribute('aria-selected',String(!isJobs));if(isJobs)void refresh();else void drawUsage();}
|
|
229
266
|
jobsTab.addEventListener('click',()=>{location.hash=selectedJob?'#/jobs/'+encodeURIComponent(selectedJob):'#/jobs';showView('jobs');});
|
|
230
267
|
jobsRefresh.addEventListener('click',()=>void refresh(true));
|
|
268
|
+
eventsAllTasks.addEventListener('click',()=>{eventTaskId=null;clearEvents();renderEventScope();void loadEventHistory();});
|
|
231
269
|
repoFilter.addEventListener('change',()=>{selectedRepo=repoFilter.value;sessionStorage.setItem('agents-relay:repo-filter',selectedRepo);renderJobList();});
|
|
232
270
|
selectedJobRefresh.addEventListener('click',()=>void refreshSelected());
|
|
233
271
|
usageTab.addEventListener('click',()=>{location.hash='#/usage';showView('usage');});
|
|
@@ -391,6 +429,21 @@ export function serveDashboard(store, port = 8787, bus, refreshMs = 300000, jobI
|
|
|
391
429
|
}
|
|
392
430
|
return;
|
|
393
431
|
}
|
|
432
|
+
if (new URL(req.url ?? '/', 'http://127.0.0.1').pathname === '/api/events') {
|
|
433
|
+
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
434
|
+
const requestedJobId = url.searchParams.get('job') ?? '';
|
|
435
|
+
const requestedTaskId = url.searchParams.get('task');
|
|
436
|
+
const requestedLimit = Number(url.searchParams.get('limit') ?? '60');
|
|
437
|
+
if (!/^[A-Za-z0-9_-]+$/.test(requestedJobId)) {
|
|
438
|
+
res.writeHead(400);
|
|
439
|
+
res.end('Invalid job id');
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const events = await eventHistory(requestedJobId, requestedTaskId, Number.isFinite(requestedLimit) ? requestedLimit : 60);
|
|
443
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
444
|
+
res.end(JSON.stringify(events));
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
394
447
|
if (req.url === '/api/status') {
|
|
395
448
|
const status = statusProvider ? statusProvider() : { limited: false, cooldownUntil: null };
|
|
396
449
|
res.writeHead(200, { 'content-type': 'application/json' });
|
package/dist/store.js
CHANGED
|
@@ -33,7 +33,9 @@ export async function loadManagedGitHubJobGraphql(client, repository, prNumber,
|
|
|
33
33
|
}
|
|
34
34
|
const COMPLETED_JOB_HISTORY_LIMIT = 10;
|
|
35
35
|
function isCompletedManagedJob(item) { return ['COMPLETED', 'CANCELLED'].includes(item.job.state) || item.githubState !== 'OPEN'; }
|
|
36
|
-
function completedAt(item) {
|
|
36
|
+
function completedAt(item) {
|
|
37
|
+
return [item.job.updatedAt, item.mergedAt, item.closedAt].filter((value) => typeof value === 'string' && value.length > 0).sort().at(-1) ?? '';
|
|
38
|
+
}
|
|
37
39
|
export function dashboardManagedJobs(items) {
|
|
38
40
|
const unfinished = items.filter(item => !isCompletedManagedJob(item));
|
|
39
41
|
const completed = items.filter(isCompletedManagedJob).sort((a, b) => completedAt(b).localeCompare(completedAt(a)) || b.job.prNumber - a.job.prNumber).slice(0, COMPLETED_JOB_HISTORY_LIMIT);
|
|
@@ -578,13 +580,15 @@ export async function listManagedGitHubJobs(client, repository, trustedAuthors)
|
|
|
578
580
|
let firstPage = true;
|
|
579
581
|
do {
|
|
580
582
|
const query = firstPage
|
|
581
|
-
? 'query($owner:String!,$name:String!,$cursor:String){repository(owner:$owner,name:$name){open:pullRequests(first:50,after:$cursor,states:OPEN,orderBy:{field:UPDATED_AT,direction:DESC}){nodes{number body state isDraft merged mergedAt closedAt comments(first:100){nodes{author{login} body}}} pageInfo{hasNextPage endCursor}} recentClosed:pullRequests(first:20,states:CLOSED,orderBy:{field:UPDATED_AT,direction:DESC}){nodes{number body state isDraft merged mergedAt closedAt comments(first:100){nodes{author{login} body}}}}}}'
|
|
583
|
+
? 'query($owner:String!,$name:String!,$cursor:String){repository(owner:$owner,name:$name){open:pullRequests(first:50,after:$cursor,states:OPEN,orderBy:{field:UPDATED_AT,direction:DESC}){nodes{number body state isDraft merged mergedAt closedAt comments(first:100){nodes{author{login} body}}} pageInfo{hasNextPage endCursor}} recentMerged:pullRequests(first:20,states:MERGED,orderBy:{field:UPDATED_AT,direction:DESC}){nodes{number body state isDraft merged mergedAt closedAt comments(first:100){nodes{author{login} body}}}} recentClosed:pullRequests(first:20,states:CLOSED,orderBy:{field:UPDATED_AT,direction:DESC}){nodes{number body state isDraft merged mergedAt closedAt comments(first:100){nodes{author{login} body}}}}}}'
|
|
582
584
|
: 'query($owner:String!,$name:String!,$cursor:String){repository(owner:$owner,name:$name){open:pullRequests(first:50,after:$cursor,states:OPEN,orderBy:{field:UPDATED_AT,direction:DESC}){nodes{number body state isDraft merged mergedAt closedAt comments(first:100){nodes{author{login} body}}} pageInfo{hasNextPage endCursor}}}}';
|
|
583
585
|
const data = await client.graphql(query, { owner, name, cursor });
|
|
584
586
|
const repo = data.repository;
|
|
585
587
|
const open = repo?.open;
|
|
586
588
|
collect(Array.isArray(open?.nodes) ? open.nodes : []);
|
|
587
589
|
if (firstPage) {
|
|
590
|
+
const recentMerged = repo?.recentMerged;
|
|
591
|
+
collect(Array.isArray(recentMerged?.nodes) ? recentMerged.nodes : []);
|
|
588
592
|
const recentClosed = repo?.recentClosed;
|
|
589
593
|
collect(Array.isArray(recentClosed?.nodes) ? recentClosed.nodes : []);
|
|
590
594
|
}
|