Skip to content

Commit

Permalink
improve webui and experimental extension (#500)
Browse files Browse the repository at this point in the history
* extension cancel workflow from treeview
* hide rerun/cancel/job and workflow list from extension webview
* webui show inprogress for jobs only when running otherwise queued
  * waiting for concurrency not implemented
  • Loading branch information
ChristopherHX authored Jan 6, 2025
1 parent 63975f0 commit 4b87ad9
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 26 deletions.
19 changes: 14 additions & 5 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,23 @@
"requireExactSource": false
},
{
"name": "Run azure-pipelines-vscode-ext Extension",
"name": "Run azure-pipelines-vscode-ext Extension",
"type": "extensionHost",
"debugWebWorkerHost": true,
"request": "launch",
"runtimeExecutable": "${execPath}",
"request": "launch",
"runtimeExecutable": "${execPath}",
"cwd": "${workspaceFolder}/src/azure-pipelines-vscode-ext",
"args": ["--extensionDevelopmentPath=${workspaceFolder}/src/azure-pipelines-vscode-ext"]
}
"args": ["--extensionDevelopmentPath=${workspaceFolder}/src/azure-pipelines-vscode-ext"]
},
{
"name": "Run runner-server-vscode Extension",
"type": "extensionHost",
"debugWebWorkerHost": true,
"request": "launch",
"runtimeExecutable": "${execPath}",
"cwd": "${workspaceFolder}/src/runner-server-vscode",
"args": ["--extensionDevelopmentPath=${workspaceFolder}/src/runner-server-vscode"]
}
],
}

34 changes: 25 additions & 9 deletions src/Runner.Server/actions-service-webapp/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import remarkGfm from 'remark-gfm'
import remarkBreaks from 'remark-breaks'
import rehypeRaw from 'rehype-raw';
import rehypeHighlight from 'rehype-highlight';
import { CircleIcon, SkipIcon, StopIcon, XCircleFillIcon, CheckCircleFillIcon, ChevronDownIcon, ChevronRightIcon, GitCommitIcon, RepoIcon, PersonIcon, MeterIcon } from '@primer/octicons-react'
import { CircleIcon, SkipIcon, StopIcon, XCircleFillIcon, CheckCircleFillIcon, ChevronDownIcon, ChevronRightIcon, GitCommitIcon, RepoIcon, PersonIcon, MeterIcon, ClockIcon, ClockFillIcon } from '@primer/octicons-react'
import { ghHostApiUrl } from './config';

var convert = new Convert({
Expand Down Expand Up @@ -105,7 +105,7 @@ function List({ fullscreen } : { fullscreen?: boolean }) {
<Link className='btn btn-primary w-50' to={"../"+ (page + 1) + (fullscreen ? "" : "/" + params['*'])}>Next</Link>
</div>
{jobs.map(val => (
<NavLink key={val.jobId} to={encodeURIComponent(val.jobId)} className={({isActive})=> isActive ? 'btn btn-outline-secondary w-100 text-start active' : 'btn btn-outline-secondary w-100 text-start'}><span style={{fontSize: 20}}>{val.name}</span><br/><span style={{fontSize: 12}}>{!params.runid ? (<>repo:&nbsp;{val.repo} workflow:&nbsp;{val.workflowname} runid:&nbsp;{val.runid} </>) : (<></>)}attempt:&nbsp;{val.attempt} result:&nbsp;<TimelineStatus status={val.result ?? "inprogress"}/></span></NavLink>
<NavLink key={val.jobId} to={encodeURIComponent(val.jobId)} className={({isActive})=> isActive ? 'btn btn-outline-secondary w-100 text-start active' : 'btn btn-outline-secondary w-100 text-start'}><span style={{fontSize: 20}}>{val.name}</span><br/><span style={{fontSize: 12}}>{!params.runid ? (<>repo:&nbsp;{val.repo} workflow:&nbsp;{val.workflowname} runid:&nbsp;{val.runid} </>) : (<></>)}attempt:&nbsp;{val.attempt} result:&nbsp;<TimelineStatus status={val.result ?? (val.sessionId === '00000000-0000-0000-0000-000000000000' ? "queued" : "inprogress")}/></span></NavLink>
))}
{ loading ?
<div className="spinner-border" role="status">
Expand Down Expand Up @@ -383,7 +383,8 @@ export interface IJob {
runid : number,
errors: string[],
result: string,
attempt: number
attempt: number,
sessionId: string
}

interface ChunkProps {
Expand Down Expand Up @@ -547,9 +548,11 @@ interface IWorkflowRunAttempt {

const TimelineStatus = ({status, size} : { status : string, size?: number }) => {
switch(status?.toLowerCase()) {
case "running":
case "inprogress":
return <MeterIcon className="text-warning progress-ring" size={size}/>
case "waiting":
return <ClockFillIcon className="text-warning" verticalAlign="middle" size={size}/>
case "pending":
return <CircleIcon verticalAlign="middle" size={size}/>
case "succeeded":
Expand All @@ -560,6 +563,8 @@ const TimelineStatus = ({status, size} : { status : string, size?: number }) =>
return <SkipIcon verticalAlign="middle" size={size}/>
case "canceled":
return <StopIcon verticalAlign="middle" size={size}/>
case "queued":
return <CircleIcon className="text-warning" verticalAlign="middle" size={size}/>
default:
return <span>{status}</span>
}
Expand Down Expand Up @@ -598,6 +603,7 @@ interface IWorkflowRun {
ref: string,
sha: string,
result: string
status: string
}
function JobPage() {
var params = useParams();
Expand Down Expand Up @@ -735,9 +741,13 @@ function JobPage() {
})();
return () => signal.abort();
}, [artifacts]);
return ( <span style={{width: '100%', height: '100%', overflowY: 'auto'}}>
<h1>{workflowRun ? workflowRun.fileName : job ? (<><TimelineStatus status={job?.result ?? "inprogress"} size={32}/> {job.name}</>) : ""}</h1>
var searchParams = new URLSearchParams(window.location.search)
return ( <span style={{width: '100%', height: '100%', overflowY: 'auto'}}>
<h1>{workflowRun ? workflowRun.fileName : job ? (<><TimelineStatus status={job?.result ?? (job.sessionId === '00000000-0000-0000-0000-000000000000' ? "queued" : "inprogress")} size={32}/> {job.name}</>) : ""}</h1>
{(() => {
if(searchParams.get("extension") === "1") {
return <></>
}
if(job !== undefined && job != null) {
if(!job.result && (!job.errors || job.errors.length === 0)) {
return <div className="btn-group" role="group">
Expand Down Expand Up @@ -861,6 +871,7 @@ interface IRepository {
interface IWorkflowRun {
id: string,
fileName: string
status: string
}

function RedirectOldUrl() {
Expand Down Expand Up @@ -967,21 +978,26 @@ function App() {
( <AllJobs/> ) :
searchParams.get("view") === "allworkflows" ?
( <Routes>
<Route path=":page" element={<GenericList hasBack={false} id={(o: IWorkflowRun) => o.id} summary={(o: IWorkflowRun) => <span>{o.displayName ?? o.fileName}<br/>{ o.owner && o.repo ? <>Repository: {o.owner}/{o.repo} </> : <></>}RunId: {o.id}, EventName: {o.eventName}<br/>Workflow: {o.fileName}<br/>{o.ref} {o.sha} <TimelineStatus status={o.result ?? "Pending"}/></span>} url={(params) => `${ghHostApiUrl}/_apis/v1/Message/workflow/runs?page=${params.page || "0"}`} eventName="workflowrun" eventUpdateName="workflowrunupdate" eventQuery={ params => `owner=${encodeURIComponent(params.owner || "")}&repo=${encodeURIComponent(params.repo || "")}` }></GenericList>}/>
<Route path=":page" element={<GenericList hasBack={false} id={(o: IWorkflowRun) => o.id} summary={(o: IWorkflowRun) => <span>{o.displayName ?? o.fileName}<br/>{ o.owner && o.repo ? <>Repository: {o.owner}/{o.repo} </> : <></>}RunId: {o.id}, EventName: {o.eventName}<br/>Workflow: {o.fileName}<br/>{o.ref} {o.sha} <TimelineStatus status={o.result ?? o.status ?? "Pending"}/></span>} url={(params) => `${ghHostApiUrl}/_apis/v1/Message/workflow/runs?page=${params.page || "0"}`} eventName="workflowrun" eventUpdateName="workflowrunupdate" eventQuery={ params => `owner=${encodeURIComponent(params.owner || "")}&repo=${encodeURIComponent(params.repo || "")}` }></GenericList>}/>
<Route path="/" element={<Navigate to={"0"}/>}/>
<Route path=":page/:runid/*" element={
<div style={{display: 'flex', flexFlow: 'row', alignItems: 'left', width: '100%', height: '100%'}}>
<Routes>
{searchParams.get("extension") === "1" ? <></> : (<Routes>
<Route path=":page/*" element={<List/>}/>
<Route path="/" element={<Navigate to={"0"}/>}/>
</Routes>
</Routes>)}
<Routes>
<Route path=":page/:id/*" element={<JobPage></JobPage>}/>
<Route path=":page" element={<JobPage></JobPage>}/>
</Routes>
</div>
}/>
</Routes> ) :
searchParams.get("view") === "singlejob" ? (
<Routes>
<Route path=":id/*" element={<JobPage></JobPage>}/>
</Routes>
) :
(
<Routes>
<Route path="/timeline/:timeLineId" element={<TimeLineViewer></TimeLineViewer>}/>
Expand All @@ -995,7 +1011,7 @@ function App() {
<Route path="/" element={<Navigate to={"0"}/>}/>
<Route path=":page/:repo/*" element={
<Routes>
<Route path=":page" element={<GenericList id={(o: IWorkflowRun) => o.id} hasBack={true} summary={(o: IWorkflowRun) => <span>{o.displayName ?? o.fileName}<br/>RunId: {o.id}, EventName: {o.eventName}<br/>Workflow: {o.fileName}<br/>{o.ref} {o.sha} <TimelineStatus status={o.result ?? "Pending"}/></span>} url={(params) => `${ghHostApiUrl}/_apis/v1/Message/workflow/runs?owner=${encodeURIComponent(params.owner || "")}&repo=${encodeURIComponent(params.repo || "")}&page=${params.page || "0"}`} externalBackUrl={params => gitServerUrl && new URL(`${params.owner}/${params.repo}`, gitServerUrl).href} externalBackLabel={() => "Back to git"} actions={ (run, params) => gitServerUrl ? <a className='btn btn-outline-secondary' href={new URL(`${params.owner}/${params.repo}/commit/${run.sha}`, gitServerUrl).href} target="_blank" rel="noreferrer"><GitCommitIcon verticalAlign='middle' size={24}/></a> : <></> } eventName="workflowrun" eventUpdateName="workflowrunupdate" eventQuery={ params => `owner=${encodeURIComponent(params.owner || "")}&repo=${encodeURIComponent(params.repo || "")}` }></GenericList>}/>
<Route path=":page" element={<GenericList id={(o: IWorkflowRun) => o.id} hasBack={true} summary={(o: IWorkflowRun) => <span>{o.displayName ?? o.fileName}<br/>RunId: {o.id}, EventName: {o.eventName}<br/>Workflow: {o.fileName}<br/>{o.ref} {o.sha} <TimelineStatus status={o.result ?? o.status ?? "Pending"}/></span>} url={(params) => `${ghHostApiUrl}/_apis/v1/Message/workflow/runs?owner=${encodeURIComponent(params.owner || "")}&repo=${encodeURIComponent(params.repo || "")}&page=${params.page || "0"}`} externalBackUrl={params => gitServerUrl && new URL(`${params.owner}/${params.repo}`, gitServerUrl).href} externalBackLabel={() => "Back to git"} actions={ (run, params) => gitServerUrl ? <a className='btn btn-outline-secondary' href={new URL(`${params.owner}/${params.repo}/commit/${run.sha}`, gitServerUrl).href} target="_blank" rel="noreferrer"><GitCommitIcon verticalAlign='middle' size={24}/></a> : <></> } eventName="workflowrun" eventUpdateName="workflowrunupdate" eventQuery={ params => `owner=${encodeURIComponent(params.owner || "")}&repo=${encodeURIComponent(params.repo || "")}` }></GenericList>}/>
<Route path="/" element={<Navigate to={"0"}/>}/>
<Route path=":page/:runid/*" element={
<div style={{display: 'flex', flexFlow: 'row', alignItems: 'left', width: '100%', height: '100%'}}>
Expand Down
6 changes: 3 additions & 3 deletions src/Runner.Server/wwwroot/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"files": {
"main.css": "/static/css/main.4758f308.css",
"main.js": "/static/js/main.7457c726.js",
"main.js": "/static/js/main.e27ea4d0.js",
"static/js/787.0500ceb2.chunk.js": "/static/js/787.0500ceb2.chunk.js",
"index.html": "/index.html",
"main.4758f308.css.map": "/static/css/main.4758f308.css.map",
"main.7457c726.js.map": "/static/js/main.7457c726.js.map",
"main.e27ea4d0.js.map": "/static/js/main.e27ea4d0.js.map",
"787.0500ceb2.chunk.js.map": "/static/js/787.0500ceb2.chunk.js.map"
},
"entrypoints": [
"static/css/main.4758f308.css",
"static/js/main.7457c726.js"
"static/js/main.e27ea4d0.js"
]
}
2 changes: 1 addition & 1 deletion src/Runner.Server/wwwroot/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.css"><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="View workflow runs, jobs and download artifacts"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Actions Service</title><script defer="defer" src="/static/js/main.7457c726.js"></script><link href="/static/css/main.4758f308.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.css"><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="View workflow runs, jobs and download artifacts"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Actions Service</title><script defer="defer" src="/static/js/main.e27ea4d0.js"></script><link href="/static/css/main.4758f308.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
1 change: 0 additions & 1 deletion src/Runner.Server/wwwroot/static/js/main.7457c726.js.map

This file was deleted.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/Runner.Server/wwwroot/static/js/main.e27ea4d0.js.map

Large diffs are not rendered by default.

40 changes: 36 additions & 4 deletions src/runner-server-vscode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function activate(context : ExtensionContext) {
// Custom formatting after the editor has been opened
updateDecorations(editor, logInfo);
})
);
);

(async() => {
console.log("Aquire Dotnet!")
Expand Down Expand Up @@ -185,15 +185,15 @@ function activate(context : ExtensionContext) {
<iframe src="${fullWebServerUri}${url}"></iframe>
</body>
</html>`;
panel.reveal(ViewColumn.One, false);
panel.reveal(ViewColumn.One, true);
};

commands.registerCommand("runner.server.openjob", (runId, id, name) => {
openPanel(name, `?view=allworkflows#/0/${runId}/0/${id}`);
openPanel(name, `?view=allworkflows&extension=1#/0/${runId}/0/${id}`);
});

commands.registerCommand("runner.server.openworkflowrun", runId => {
openPanel(`#${runId}`, `?view=allworkflows#/0/${runId}`);
openPanel(`#${runId}`, `?view=allworkflows&extension=1#/0/${runId}/0`);
});
}
});
Expand Down Expand Up @@ -266,6 +266,38 @@ function activate(context : ExtensionContext) {
});
});

context.subscriptions.push(commands.registerCommand("runner.server.workflow.cancel", async (obj : TreeItem) => {
var items : QuickPickItem[] = [];
if(obj.contextValue?.indexOf("job") !== -1) {
items.push({
label: "Cancel",
description: "Cancel only this job"
});
items.push({
label: "Force Cancel",
description: "Force Cancel only this job"
});
} else {
items.push({
label: "Cancel",
description: "Cancel this workflow run"
});
items.push({
label: "Force Cancel",
description: "Force Cancel this workflow run"
});
}
var selection = await window.showQuickPick(items);
if(!selection) {
return;
}
if(obj.contextValue?.indexOf("job") !== -1) {
await fetch(address + "/_apis/v1/Message/cancel/" + obj.command.arguments[1] + "?force=" + (selection.label.indexOf("Force") !== -1), { method: "POST" });
} else {
await fetch(address + "/_apis/v1/Message/" + (selection.label.indexOf("Force") !== -1 ? "forceCancelWorkflow" : "cancelWorkflow") + "/" + obj.command.arguments[0], { method: "POST" });
}
}));

context.subscriptions.push(client);
client.start();
})();
Expand Down
11 changes: 11 additions & 0 deletions src/runner-server-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
"light": "resources/icons/light/logs.svg"
}
},
{
"command": "runner.server.workflow.cancel",
"title": "Cancel",
"shortTitle": "Cancel",
"icon": "$(stop-circle)"
},
{
"command": "runner.server.start-client",
"title": "Start Runner.Client"
Expand Down Expand Up @@ -86,6 +92,11 @@
"command": "runner.server.workflow.logs",
"group": "inline",
"when": "view == workflow-view && viewItem =~ /completed/ && viewItem =~ /job/"
},
{
"command": "runner.server.workflow.cancel",
"group": "inline",
"when": "view == workflow-view && !(viewItem =~ /completed/) && (viewItem =~ /job/ || viewItem =~ /workflow/ )"
}
]
}
Expand Down

0 comments on commit 4b87ad9

Please sign in to comment.