Skip to content

Commit

Permalink
feat(misc): add watch mode to dep-graph command (nrwl#6043)
Browse files Browse the repository at this point in the history
  • Loading branch information
philipjfulcher authored Jun 22, 2021
1 parent 377fca4 commit 98ac32e
Show file tree
Hide file tree
Showing 42 changed files with 4,742 additions and 77,967 deletions.
10 changes: 8 additions & 2 deletions dep-graph/dep-graph-e2e/src/integration/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@ describe('dep-graph-client', () => {
getSelectProjectsMessage().should('be.visible');
});

it('should hide select projects message when a project is selected', () => {
cy.contains('nx-docs-site').siblings('button').click();
getSelectProjectsMessage().should('not.be.visible');
});

describe('selecting a different project', () => {
it('should change the available projects', () => {
getProjectCheckboxes().should('have.length', 133);
getProjectCheckboxes().should('have.length', 135);
cy.get('[data-cy=project-select]').select('Nx');
getProjectCheckboxes().should('have.length', 42);
getProjectCheckboxes().should('have.length', 45);
});

it("should restore sidebar if it's been hidden", () => {
Expand All @@ -53,6 +58,7 @@ describe('dep-graph-client', () => {
it('should uncheck all project checkboxes', () => {
getDeselectAllButton().click();
getProjectCheckboxes().should('not.be.checked');
getSelectProjectsMessage().should('be.visible');
});
});

Expand Down
101 changes: 65 additions & 36 deletions dep-graph/dep-graph/src/app/app.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,76 @@
import { DepGraphClientResponse } from '@nrwl/workspace';
import { ProjectGraph } from '@nrwl/workspace/src/core/project-graph';
import { combineLatest, fromEvent, Subject } from 'rxjs';
import { startWith, takeUntil } from 'rxjs/operators';
import { projectGraphs } from '../graphs';
import { DebuggerPanel } from './debugger-panel';
import { GraphComponent } from './graph';
import { AppConfig, DEFAULT_CONFIG } from './models';
import { GraphTooltipService } from './tooltip-service';
import { SidebarComponent } from './ui-sidebar/sidebar';

export interface AppConfig {
showDebugger: boolean;
}

const DEFAULT_CONFIG: AppConfig = {
showDebugger: false,
};
export class AppComponent {
private sidebar: SidebarComponent;
private tooltipService = new GraphTooltipService();
private graph = new GraphComponent(this.tooltipService);
private debuggerPanel: DebuggerPanel;

private windowResize$ = fromEvent(window, 'resize').pipe(startWith({}));
private render$ = new Subject<void>();
private destroy$ = new Subject<void>();
private render$ = new Subject<{ newProjects: string[] }>();

constructor(private config: AppConfig = DEFAULT_CONFIG) {
this.render$.subscribe(() => this.render());
this.render$.next();
}
this.render$.subscribe((nextRenderConfig) => this.render(nextRenderConfig));

private onProjectGraphChange(projectGraphId: string) {
// reset previous listeners as we're re-rendering
this.destroy$.next();
this.loadProjectGraph(config.defaultProjectGraph);

const project = projectGraphs.find((graph) => graph.id === projectGraphId);
const projectGraph = project?.graph;
const workspaceLayout = project?.workspaceLayout;
if (window.watch === true) {
setInterval(
() => this.loadProjectGraph(config.defaultProjectGraph),
5000
);
}
}

const nodes = Object.values(projectGraph.nodes).filter(
(node) => node.type !== 'npm'
private async loadProjectGraph(projectGraphId: string) {
const projectInfo = this.config.projectGraphs.find(
(graph) => graph.id === projectGraphId
);

window.projects = nodes;
window.graph = projectGraph;
window.affected = [];
window.exclude = [];
window.focusedProject = null;
window.projectGraphList = projectGraphs;
const project: DepGraphClientResponse =
await this.config.projectGraphService.getProjectGraph(projectInfo.url);

const workspaceLayout = project?.layout;

const nodes = Object.values(project.projects)
.filter((node: any) => node.type !== 'npm')
.reduce((acc, cur: any) => {
acc[cur.name] = cur;
return acc;
}, {});

const newProjects = project.changes.added.filter(
(addedProject) => !window.graph.nodes[addedProject]
);
window.projects = project.projects;
window.graph = <ProjectGraph>{
dependencies: project.dependencies,
nodes: nodes,
};
window.affected = project.affected;
window.exclude = project.exclude;
window.focusedProject = project.focus;
window.groupByFolder = project.groupByFolder;
window.projectGraphList = this.config.projectGraphs;
window.selectedProjectGraph = projectGraphId;
window.workspaceLayout = workspaceLayout;

this.render();
if (this.sidebar) {
this.render$.next({ newProjects });
} else {
this.render$.next();
}
}

private render() {
private render(renderConfig: { newProjects: string[] } | undefined) {
const debuggerPanelContainer = document.getElementById('debugger-panel');

if (this.config.showDebugger) {
Expand All @@ -65,7 +83,8 @@ export class AppComponent {
);

this.debuggerPanel.selectProject$.subscribe((id) => {
this.onProjectGraphChange(id);
this.loadProjectGraph(id);
this.sidebar.resetSidebarVisibility();
});
}

Expand All @@ -75,11 +94,16 @@ export class AppComponent {
);

this.graph.affectedProjects = affectedProjects;
this.sidebar = new SidebarComponent(
this.destroy$,
affectedProjects,
this.config.showDebugger
);

if (!this.sidebar) {
this.sidebar = new SidebarComponent(affectedProjects);
} else {
this.sidebar.projects = window.projects;

if (renderConfig?.newProjects.length > 0) {
this.sidebar.selectProjects(renderConfig.newProjects);
}
}

combineLatest([
this.sidebar.selectedProjectsChanged$,
Expand All @@ -96,7 +120,12 @@ export class AppComponent {
}
});

this.graph.render(selectedProjects, groupByFolder);
if (selectedProjects.length === 0) {
document.getElementById('no-projects-chosen').style.display = 'flex';
} else {
document.getElementById('no-projects-chosen').style.display = 'none';
this.graph.render(selectedProjects, groupByFolder);
}
});

if (this.debuggerPanel) {
Expand Down
20 changes: 20 additions & 0 deletions dep-graph/dep-graph/src/app/fetch-project-graph-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { DepGraphClientResponse } from '@nrwl/workspace';
import { ProjectGraphService } from './models';

export class FetchProjectGraphService implements ProjectGraphService {
async getHash(): Promise<string> {
const request = new Request('currentHash', { mode: 'no-cors' });

const response = await fetch(request);

return response.json();
}

async getProjectGraph(url: string): Promise<DepGraphClientResponse> {
const request = new Request(url, { mode: 'no-cors' });

const response = await fetch(request);

return response.json();
}
}
12 changes: 12 additions & 0 deletions dep-graph/dep-graph/src/app/local-project-graph-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { DepGraphClientResponse } from '@nrwl/workspace';
import { ProjectGraphService } from './models';

export class LocalProjectGraphService implements ProjectGraphService {
async getHash(): Promise<string> {
return new Promise((resolve) => resolve('some-hash'));
}

async getProjectGraph(url: string): Promise<DepGraphClientResponse> {
return new Promise((resolve) => resolve(window.projectGraphResponse));
}
}
94 changes: 94 additions & 0 deletions dep-graph/dep-graph/src/app/mock-project-graph-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { ProjectGraphDependency } from '@nrwl/devkit';
import { DepGraphClientProject, DepGraphClientResponse } from '@nrwl/workspace';
import { ProjectGraphService } from '../app/models';

export class MockProjectGraphService implements ProjectGraphService {
private response: DepGraphClientResponse = {
hash: '79054025255fb1a26e4bc422aef54eb4',
layout: {
appsDir: 'apps',
libsDir: 'libs',
},
projects: [
{
name: 'existing-app-1',
type: 'app',
data: {
root: 'apps/app1',
tags: [],
},
},
{
name: 'existing-lib-1',
type: 'lib',
data: {
root: 'libs/lib1',
tags: [],
},
},
],
dependencies: {
'existing-app-1': [
{
source: 'existing-app-1',
target: 'existing-lib-1',
type: 'statis',
},
],
'existing-lib-1': [],
},
changes: {
added: [],
},
affected: [],
focus: null,
exclude: [],
groupByFolder: false,
};

constructor(updateFrequency: number = 7500) {
setInterval(() => this.updateResponse(), updateFrequency);
}

async getHash(): Promise<string> {
return new Promise((resolve) => resolve(this.response.hash));
}

getProjectGraph(url: string): Promise<DepGraphClientResponse> {
return new Promise((resolve) => resolve(this.response));
}

private createNewProject(): DepGraphClientProject {
const type = Math.random() > 0.25 ? 'lib' : 'app';
const name = `${type}-${this.response.projects.length + 1}`;

return {
name,
type,
data: {
root: type === 'app' ? `apps/${name}` : `libs/${name}`,
tags: [],
},
};
}

private updateResponse() {
const newProject = this.createNewProject();
const libProjects = this.response.projects.filter(
(project) => project.type === 'lib'
);

const targetDependency =
libProjects[Math.floor(Math.random() * libProjects.length)];
const newDependency: ProjectGraphDependency[] = [
{
source: newProject.name,
target: targetDependency.name,
type: 'static',
},
];
this.response.projects.push(newProject);
this.response.dependencies[newProject.name] = newDependency;
this.response.changes.added.push(newProject.name);
}
}
28 changes: 25 additions & 3 deletions dep-graph/dep-graph/src/app/models.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import { ProjectGraphCache } from '@nrwl/workspace';
import { DepGraphClientResponse } from '@nrwl/workspace';

export interface ProjectGraphList {
id: string;
label: string;
graph: ProjectGraphCache;
workspaceLayout: WorkspaceLayout;
url: string;
}

export interface WorkspaceLayout {
libsDir: string;
appsDir: string;
}

export interface ProjectGraphService {
getHash: () => Promise<string>;
getProjectGraph: (url: string) => Promise<DepGraphClientResponse>;
}
export interface Environment {
environment: 'dev' | 'dev-watch' | 'release';
appConfig: AppConfig;
}

export interface AppConfig {
showDebugger: boolean;
projectGraphs: ProjectGraphList[];
defaultProjectGraph: string;
projectGraphService: ProjectGraphService;
}

export const DEFAULT_CONFIG: AppConfig = {
showDebugger: false,
projectGraphs: [],
defaultProjectGraph: null,
projectGraphService: null,
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export class FocusedProjectPanel {
this.render();
}

unfocusProject() {
this.render();
}

private render(projectName?: string) {
removeChildrenFromContainer(this.container);

Expand Down
30 changes: 26 additions & 4 deletions dep-graph/dep-graph/src/app/ui-sidebar/project-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,33 @@ export class ProjectList {
checkedProjectsChange$ = this.checkedProjectsChangeSubject.asObservable();
focusProject$ = this.focusProjectSubject.asObservable();

constructor(
private container: HTMLElement,
private projects: ProjectGraphNode[]
) {
private _projects: ProjectGraphNode[] = [];

set projects(projects: ProjectGraphNode[]) {
this._projects = projects;

const previouslyCheckedProjects = Object.values(this.checkboxes)
.filter((checkbox) => checkbox.checked)
.map((checkbox) => checkbox.value);
this.render();
this.selectProjects(previouslyCheckedProjects);
}

get projects(): ProjectGraphNode[] {
return this._projects;
}

constructor(private container: HTMLElement) {
this.render();
}

selectProjects(projects: string[]) {
projects.forEach((projectName) => {
if (!!this.checkboxes[projectName]) {
this.checkboxes[projectName].checked = true;
}
});
this.emitChanges();
}

setCheckedProjects(selectedProjects: string[]) {
Expand Down
Loading

0 comments on commit 98ac32e

Please sign in to comment.