Skip to content

Commit e39e8e5

Browse files
authored
fix: rework graph with reloading & errors & update nx (#2123)
1 parent 237d5f9 commit e39e8e5

File tree

6 files changed

+437
-310
lines changed

6 files changed

+437
-310
lines changed

libs/vscode/graph-base/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './lib/load-graph-base-html';
22
export * from './lib/nx-graph-server';
33
export * from './lib/handle-graph-interaction-event';
44
export * from './lib/graph-webview-base';
5+
export * from './lib/load-graph-error-html';
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { NxError } from '@nx-console/shared/types';
2+
3+
export function loadGraphErrorHtml(errors: NxError[]) {
4+
return /*html*/ `
5+
<style>
6+
pre {
7+
white-space: pre-wrap;
8+
border-radius: 5px;
9+
border: 2px solid var(--vscode-editorWidget-border);
10+
padding: 20px;
11+
}
12+
</style>
13+
<p>Unable to load the project graph. The following error occurred:</p>
14+
${errors
15+
.map(
16+
(error) => `<pre>${error.message ?? ''} \n ${error.stack ?? ''}</pre>`
17+
)
18+
.join('')}
19+
`;
20+
}

libs/vscode/project-details/src/lib/project-details-preview.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
NxGraphServer,
55
getNxGraphServer,
66
handleGraphInteractionEventBase,
7+
loadGraphErrorHtml,
78
loadGraphBaseHtml,
89
} from '@nx-console/vscode/graph-base';
910
import { onWorkspaceRefreshed } from '@nx-console/vscode/lsp-client';
@@ -114,7 +115,7 @@ export class ProjectDetailsPreview {
114115
Object.keys(nxWorkspace?.workspace.projects ?? {}).length > 0;
115116
const hasProject =
116117
project?.name &&
117-
nxWorkspace?.workspace.projects[project?.name] === undefined;
118+
nxWorkspace?.workspace.projects[project?.name] !== undefined;
118119
if (
119120
workspaceErrors &&
120121
(!isPartial ||
@@ -178,22 +179,7 @@ export class ProjectDetailsPreview {
178179
}
179180

180181
private loadErrorHtml(errors: NxError[]) {
181-
this.webviewPanel.webview.html = /*html*/ `
182-
<style>
183-
pre {
184-
white-space: pre-wrap;
185-
border-radius: 5px;
186-
border: 2px solid var(--vscode-editorWidget-border);
187-
padding: 20px;
188-
}
189-
</style>
190-
<p>Unable to load the project graph. The following error occurred:</p>
191-
${errors
192-
.map(
193-
(error) => `<pre>${error.message ?? ''} \n ${error.stack ?? ''}</pre>`
194-
)
195-
.join('')}
196-
`;
182+
this.webviewPanel.webview.html = loadGraphErrorHtml(errors);
197183
this.isShowingErrorHtml = true;
198184
}
199185

libs/vscode/project-graph/src/lib/graph-webview-manager.ts

Lines changed: 163 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import {
22
getNxGraphServer,
33
handleGraphInteractionEventBase,
44
loadGraphBaseHtml,
5+
loadGraphErrorHtml,
56
} from '@nx-console/vscode/graph-base';
7+
import { onWorkspaceRefreshed } from '@nx-console/vscode/lsp-client';
8+
import { getNxWorkspace } from '@nx-console/vscode/nx-workspace';
69
import {
710
commands,
811
ExtensionContext,
@@ -16,11 +19,21 @@ export class GraphWebviewManager implements Disposable {
1619
private webviewPanel: WebviewPanel | undefined;
1720
private currentPanelIsAffected = false;
1821

19-
constructor(private context: ExtensionContext) {}
22+
private lastGraphCommand: GraphCommand | undefined;
23+
24+
constructor(private context: ExtensionContext) {
25+
onWorkspaceRefreshed(() => {
26+
if (this.webviewPanel) {
27+
this.loadGraphWebview(this.currentPanelIsAffected);
28+
this.rerunLastGraphCommand();
29+
}
30+
});
31+
}
2032

2133
async showAllProjects() {
2234
await this.openGraphWebview();
2335
this.webviewPanel?.webview.postMessage({ type: 'show-all' });
36+
this.lastGraphCommand = { type: 'show-all' };
2437
}
2538

2639
async focusProject(projectName: string) {
@@ -29,6 +42,7 @@ export class GraphWebviewManager implements Disposable {
2942
type: 'focus-project',
3043
payload: { projectName },
3144
});
45+
this.lastGraphCommand = { type: 'focus-project', projectName };
3246
}
3347

3448
async selectProject(projectName: string) {
@@ -37,6 +51,7 @@ export class GraphWebviewManager implements Disposable {
3751
type: 'select-project',
3852
payload: { projectName },
3953
});
54+
this.lastGraphCommand = { type: 'select-project', projectName };
4055
}
4156

4257
async focusTarget(projectName: string, targetName: string) {
@@ -45,6 +60,7 @@ export class GraphWebviewManager implements Disposable {
4560
type: 'focus-target',
4661
payload: { projectName, targetName },
4762
});
63+
this.lastGraphCommand = { type: 'focus-target', projectName, targetName };
4864
}
4965

5066
async showAllTargetsByName(targetName: string) {
@@ -53,11 +69,13 @@ export class GraphWebviewManager implements Disposable {
5369
type: 'show-all-targets-by-name',
5470
payload: { targetName },
5571
});
72+
this.lastGraphCommand = { type: 'show-all-targets-by-name', targetName };
5673
}
5774

5875
async showAffectedProjects() {
5976
await this.openGraphWebview(true);
6077
this.webviewPanel?.webview.postMessage({ type: 'show-affected-projects' });
78+
this.lastGraphCommand = { type: 'show-affected-projects' };
6179
}
6280

6381
async openGraphWebview(affected = false) {
@@ -70,59 +88,94 @@ export class GraphWebviewManager implements Disposable {
7088
this.webviewPanel.reveal();
7189
return;
7290
}
91+
92+
this.currentPanelIsAffected = affected;
93+
94+
await this.loadGraphWebview(affected);
95+
96+
this.webviewPanel?.reveal();
97+
}
98+
99+
private async loadGraphWebview(affected: boolean) {
73100
const graphServer = getNxGraphServer(this.context, affected);
74101

102+
const viewColumn = this.webviewPanel?.viewColumn || ViewColumn.Active;
103+
75104
this.webviewPanel?.dispose();
105+
76106
this.webviewPanel = window.createWebviewPanel(
77107
'graph',
78108
`Nx Graph`,
79-
ViewColumn.Active,
109+
viewColumn,
80110
{
81111
enableScripts: true,
82112
retainContextWhenHidden: true,
83113
}
84114
);
85-
this.currentPanelIsAffected = affected;
86115

87-
let html = await loadGraphBaseHtml(this.webviewPanel.webview);
88-
89-
html = html.replace(
90-
'</head>',
91-
/*html*/ `
92-
<script>
93-
window.addEventListener('message', async ({ data }) => {
94-
await window.waitForRouter()
95-
96-
const { type, payload } = data;
97-
if(type === 'show-all') {
98-
window.externalApi.selectAllProjects()
99-
} else if (type === 'focus-project') {
100-
window.externalApi.focusProject(payload.projectName);
101-
} else if(type === 'select-project') {
102-
window.externalApi.toggleSelectProject(payload.projectName);
103-
} else if(type === 'focus-target') {
104-
window.externalApi.focusTarget(payload.projectName, payload.targetName);
105-
} else if(type === 'show-all-targets-by-name') {
106-
window.externalApi.selectAllTargetsByName(payload.targetName)
107-
} else if(type === 'show-affected-projects') {
108-
window.externalApi.showAffectedProjects()
109-
}
110-
});
111-
</script>
112-
</head>
113-
`
114-
);
116+
const nxWorkspace = await getNxWorkspace();
117+
const workspaceErrors = nxWorkspace?.errors;
118+
const isPartial = nxWorkspace?.isPartial;
119+
const hasProjects =
120+
Object.keys(nxWorkspace?.workspace.projects ?? {}).length > 0;
121+
const hasProject =
122+
this.lastGraphCommand &&
123+
isCommandWithProjectName(this.lastGraphCommand) &&
124+
nxWorkspace?.workspace.projects[this.lastGraphCommand.projectName] !==
125+
undefined;
126+
127+
let html: string;
128+
if (
129+
workspaceErrors &&
130+
(!isPartial ||
131+
!hasProjects ||
132+
!hasProject ||
133+
nxWorkspace.nxVersion.major < 19)
134+
) {
135+
html = loadGraphErrorHtml(workspaceErrors);
136+
} else {
137+
html = await loadGraphBaseHtml(this.webviewPanel.webview);
138+
139+
html = html.replace(
140+
'</head>',
141+
/*html*/ `
142+
<script>
143+
window.addEventListener('message', async ({ data }) => {
144+
await window.waitForRouter()
145+
146+
const { type, payload } = data;
147+
if(type === 'show-all') {
148+
window.externalApi.selectAllProjects()
149+
} else if (type === 'focus-project') {
150+
window.externalApi.focusProject(payload.projectName);
151+
} else if(type === 'select-project') {
152+
window.externalApi.toggleSelectProject(payload.projectName);
153+
} else if(type === 'focus-target') {
154+
window.externalApi.focusTarget(payload.projectName, payload.targetName);
155+
} else if(type === 'show-all-targets-by-name') {
156+
window.externalApi.selectAllTargetsByName(payload.targetName)
157+
} else if(type === 'show-affected-projects') {
158+
window.externalApi.showAffectedProjects()
159+
}
160+
});
161+
</script>
162+
</head>
163+
`
164+
);
165+
}
115166

116167
this.webviewPanel.webview.html = html;
117-
this.webviewPanel.webview.onDidReceiveMessage(async (event) => {
118-
const handled = await handleGraphInteractionEventBase(event);
119-
if (handled) return;
120-
if (event.type.startsWith('request')) {
121-
const response = await graphServer.handleWebviewRequest(event);
122-
this.webviewPanel?.webview.postMessage(response);
123-
return;
168+
const messageListener = this.webviewPanel.webview.onDidReceiveMessage(
169+
async (event) => {
170+
const handled = await handleGraphInteractionEventBase(event);
171+
if (handled) return;
172+
if (event.type.startsWith('request')) {
173+
const response = await graphServer.handleWebviewRequest(event);
174+
this.webviewPanel?.webview.postMessage(response);
175+
return;
176+
}
124177
}
125-
});
178+
);
126179

127180
const viewStateListener = this.webviewPanel.onDidChangeViewState(
128181
({ webviewPanel }) => {
@@ -137,15 +190,84 @@ export class GraphWebviewManager implements Disposable {
137190
this.webviewPanel.onDidDispose(() => {
138191
commands.executeCommand('setContext', 'graphWebviewVisible', false);
139192
viewStateListener.dispose();
193+
messageListener.dispose();
140194

141195
this.webviewPanel = undefined;
142-
this.currentPanelIsAffected = false;
143196
});
197+
}
198+
199+
private rerunLastGraphCommand() {
200+
if (!this.lastGraphCommand) return;
144201

145-
this.webviewPanel.reveal();
202+
switch (this.lastGraphCommand.type) {
203+
case 'show-all':
204+
this.showAllProjects();
205+
break;
206+
case 'focus-project':
207+
this.focusProject(this.lastGraphCommand.projectName);
208+
break;
209+
case 'select-project':
210+
this.selectProject(this.lastGraphCommand.projectName);
211+
break;
212+
case 'focus-target':
213+
this.focusTarget(
214+
this.lastGraphCommand.projectName,
215+
this.lastGraphCommand.targetName
216+
);
217+
break;
218+
case 'show-all-targets-by-name':
219+
this.showAllTargetsByName(this.lastGraphCommand.targetName);
220+
break;
221+
case 'show-affected-projects':
222+
this.showAffectedProjects();
223+
break;
224+
}
146225
}
147226

148227
dispose() {
149228
this.webviewPanel?.dispose();
150229
}
151230
}
231+
232+
type GraphCommand =
233+
| ShowAllCommand
234+
| FocusProjectCommand
235+
| SelectProjectCommand
236+
| FocusTargetCommand
237+
| ShowAllTargetsByNameCommand
238+
| ShowAffectedProjectsCommand;
239+
240+
interface ShowAllCommand {
241+
type: 'show-all';
242+
}
243+
244+
interface FocusProjectCommand {
245+
type: 'focus-project';
246+
projectName: string;
247+
}
248+
249+
interface SelectProjectCommand {
250+
type: 'select-project';
251+
projectName: string;
252+
}
253+
254+
interface FocusTargetCommand {
255+
type: 'focus-target';
256+
projectName: string;
257+
targetName: string;
258+
}
259+
260+
interface ShowAllTargetsByNameCommand {
261+
type: 'show-all-targets-by-name';
262+
targetName: string;
263+
}
264+
265+
interface ShowAffectedProjectsCommand {
266+
type: 'show-affected-projects';
267+
}
268+
269+
function isCommandWithProjectName(
270+
command: GraphCommand
271+
): command is FocusProjectCommand | SelectProjectCommand {
272+
return command.type === 'focus-project' || command.type === 'select-project';
273+
}

0 commit comments

Comments
 (0)