diff --git a/graph/client-e2e/src/support/app.po.ts b/graph/client-e2e/src/support/app.po.ts index fd556ec26b..3a497a0ca0 100644 --- a/graph/client-e2e/src/support/app.po.ts +++ b/graph/client-e2e/src/support/app.po.ts @@ -14,7 +14,7 @@ export const getCheckedProjectItems = () => cy.get('[data-active="true"]'); export const getUncheckedProjectItems = () => cy.get('[data-active="false"]'); export const getGroupByFolderCheckbox = () => - cy.get('input[name=displayOptions][value=groupByFolder]'); + cy.get('input[name=groupByFolder]'); export const getSearchDepthCheckbox = () => cy.get('input[name=depthFilter][value=depthFilterActivated]'); diff --git a/graph/client/src/app/external-api.ts b/graph/client/src/app/external-api.ts index 3b15d90a68..e1f629b140 100644 --- a/graph/client/src/app/external-api.ts +++ b/graph/client/src/app/external-api.ts @@ -8,6 +8,12 @@ export class ExternalApi { } enableExperimentalFeatures() { + localStorage.setItem('showExperimentalFeatures', 'true'); window.appConfig.showExperimentalFeatures = true; } + + disableExperimentalFeatures() { + localStorage.setItem('showExperimentalFeatures', 'false'); + window.appConfig.showExperimentalFeatures = false; + } } diff --git a/graph/client/src/app/feature-projects/panels/group-by-folder-panel.stories.tsx b/graph/client/src/app/feature-projects/panels/group-by-folder-panel.stories.tsx deleted file mode 100644 index c50f38549f..0000000000 --- a/graph/client/src/app/feature-projects/panels/group-by-folder-panel.stories.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { GroupByFolderPanel } from './group-by-folder-panel'; - -export default { - component: GroupByFolderPanel, - title: 'Project Graph/GroupByFolderPanel', - argTypes: { groupByFolderChanged: { action: 'groupByFolderChanged' } }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => ( - -); - -export const Primary = Template.bind({}); -Primary.args = { - groupByFolder: false, -}; diff --git a/graph/client/src/app/feature-projects/panels/group-by-folder-panel.tsx b/graph/client/src/app/feature-projects/panels/group-by-folder-panel.tsx index 13034eb46b..13da542ad7 100644 --- a/graph/client/src/app/feature-projects/panels/group-by-folder-panel.tsx +++ b/graph/client/src/app/feature-projects/panels/group-by-folder-panel.tsx @@ -1,41 +1,24 @@ import { memo } from 'react'; +import CheckboxPanel from '../../ui-components/checkbox-panel'; export interface DisplayOptionsPanelProps { groupByFolder: boolean; groupByFolderChanged: (checked: boolean) => void; } -export const GroupByFolderPanel = memo( - ({ groupByFolder, groupByFolderChanged }: DisplayOptionsPanelProps) => { - return ( -
-
-
- groupByFolderChanged(event.target.checked)} - checked={groupByFolder} - /> -
-
- -

- Visually arrange libraries by folders with different colors. -

-
-
-
- ); - } -); +export const GroupByFolderPanel = ({ + groupByFolder, + groupByFolderChanged, +}: DisplayOptionsPanelProps) => { + return ( + + ); +}; export default GroupByFolderPanel; diff --git a/graph/client/src/app/feature-projects/projects-sidebar.tsx b/graph/client/src/app/feature-projects/projects-sidebar.tsx index 1fcef113c7..225b7c75a4 100644 --- a/graph/client/src/app/feature-projects/projects-sidebar.tsx +++ b/graph/client/src/app/feature-projects/projects-sidebar.tsx @@ -1,4 +1,4 @@ -import { useCallback } from 'react'; +import { useCallback, useEffect } from 'react'; import ExperimentalFeature from '../ui-components/experimental-feature'; import { useProjectGraphSelector } from './hooks/use-project-graph-selector'; import { @@ -22,6 +22,11 @@ import TracingPanel from './panels/tracing-panel'; import { useEnvironmentConfig } from '../hooks/use-environment-config'; import { TracingAlgorithmType } from './machines/interfaces'; import { getProjectGraphService } from '../machines/get-services'; +import { useIntervalWhen } from '../hooks/use-interval-when'; +// nx-ignore-next-line +import { ProjectGraphClientResponse } from 'nx/src/command-line/dep-graph'; +import { useParams, useRouteLoaderData } from 'react-router-dom'; +import { getProjectGraphDataService } from '../hooks/get-project-graph-data-service'; export function ProjectsSidebar(): JSX.Element { const environmentConfig = useEnvironmentConfig(); @@ -38,6 +43,12 @@ export function ProjectsSidebar(): JSX.Element { const isTracing = projectGraphService.state.matches('tracing'); const tracingInfo = useProjectGraphSelector(getTracingInfo); + const projectGraphDataService = getProjectGraphDataService(); + + const selectedProjectRouteData = useRouteLoaderData( + 'selectedWorkspace' + ) as ProjectGraphClientResponse; + const params = useParams(); function resetFocus() { projectGraphService.send({ type: 'unfocusProject' }); @@ -110,6 +121,45 @@ export function ProjectsSidebar(): JSX.Element { }); } + useEffect(() => { + projectGraphService.send({ + type: 'setProjects', + projects: selectedProjectRouteData.projects, + dependencies: selectedProjectRouteData.dependencies, + affectedProjects: selectedProjectRouteData.affected, + workspaceLayout: selectedProjectRouteData.layout, + }); + }, [selectedProjectRouteData]); + + useIntervalWhen( + () => { + const selectedWorkspaceId = + params.selectedProjectId ?? + environmentConfig.appConfig.defaultWorkspaceId; + + const projectInfo = environmentConfig.appConfig.workspaces.find( + (graph) => graph.id === selectedWorkspaceId + ); + + const fetchProjectGraph = async () => { + const project: ProjectGraphClientResponse = + await projectGraphDataService.getProjectGraph( + projectInfo.projectGraphUrl + ); + + projectGraphService.send({ + type: 'updateGraph', + projects: project.projects, + dependencies: project.dependencies, + }); + }; + + fetchProjectGraph(); + }, + 5000, + environmentConfig.watch + ); + const updateTextFilter = useCallback( (textFilter: string) => { projectGraphService.send({ type: 'filterByText', search: textFilter }); diff --git a/graph/client/src/app/feature-tasks/tasks-sidebar.tsx b/graph/client/src/app/feature-tasks/tasks-sidebar.tsx index 57bc8eab31..2a9516971c 100644 --- a/graph/client/src/app/feature-tasks/tasks-sidebar.tsx +++ b/graph/client/src/app/feature-tasks/tasks-sidebar.tsx @@ -1,5 +1,10 @@ import TaskList from './task-list'; -import { useNavigate, useParams, useRouteLoaderData } from 'react-router-dom'; +import { + useNavigate, + useParams, + useRouteLoaderData, + useSearchParams, +} from 'react-router-dom'; // nx-ignore-next-line import type { ProjectGraphClientResponse, @@ -8,24 +13,35 @@ import type { import { getGraphService } from '../machines/graph.service'; import { useEffect } from 'react'; import FocusedPanel from '../ui-components/focused-panel'; +import CheckboxPanel from '../ui-components/checkbox-panel'; export function TasksSidebar() { const graphService = getGraphService(); const navigate = useNavigate(); const params = useParams(); + const [searchParams, setSearchParams] = useSearchParams(); const selectedProjectRouteData = useRouteLoaderData( - 'SelectedProject' + 'selectedWorkspace' ) as ProjectGraphClientResponse; - const projects = selectedProjectRouteData.projects; const workspaceLayout = selectedProjectRouteData.layout; const routeData = useRouteLoaderData( 'selectedTask' ) as TaskGraphClientResponse; const { taskGraphs } = routeData; - - const selectedTask = params['selectedTask']; + const projects = selectedProjectRouteData.projects; + // const projects = selectedProjectRouteData.projects.filter((project) => { + // return ( + // Object.keys(project.data.targets).filter((target) => { + // const taskName = `${project.name}:${target}`; + // return ( + // taskGraphs[taskName]?.dependencies[taskName]?.length > 0 + // ); + // }).length > 0 + // ); + // }); + const selectedTask = params['selectedTaskId']; useEffect(() => { graphService.handleTaskEvent({ @@ -48,6 +64,22 @@ export function TasksSidebar() { } }, [params]); + const groupByProject = searchParams.get('groupByProject') === 'true'; + + useEffect(() => { + if (groupByProject) { + graphService.handleTaskEvent({ + type: 'setGroupByProject', + groupByProject: true, + }); + } else { + graphService.handleTaskEvent({ + type: 'setGroupByProject', + groupByProject: false, + }); + } + }, [searchParams]); + function selectTask(taskId: string) { if (selectedTask) { navigate(`../${taskId}`); @@ -60,6 +92,18 @@ export function TasksSidebar() { navigate('..'); } + function groupByProjectChanged(checked) { + setSearchParams((currentSearchParams) => { + if (checked) { + currentSearchParams.set('groupByProject', 'true'); + } else { + currentSearchParams.delete('groupByProject'); + } + + return currentSearchParams; + }); + } + return ( <> {selectedTask ? ( @@ -68,6 +112,15 @@ export function TasksSidebar() { resetFocus={resetFocus} > ) : null} + + + { - const projectInfo = appConfig.projects.find( - (graph) => graph.id === selectedProjectId +const workspaceDataLoader = async (selectedWorkspaceId: string) => { + const workspaceInfo = appConfig.workspaces.find( + (graph) => graph.id === selectedWorkspaceId ); const projectGraph: ProjectGraphClientResponse = - await projectGraphDataService.getProjectGraph(projectInfo.projectGraphUrl); + await projectGraphDataService.getProjectGraph( + workspaceInfo.projectGraphUrl + ); return projectGraph; }; const taskDataLoader = async (selectedProjectId: string) => { - const projectInfo = appConfig.projects.find( + const projectInfo = appConfig.workspaces.find( (graph) => graph.id === selectedProjectId ); @@ -41,9 +42,7 @@ const taskDataLoader = async (selectedProjectId: string) => { const childRoutes: RouteObject[] = [ { path: 'projects', - loader: () => { - getProjectGraphService().start(); - }, + loader: () => {}, element: , }, { @@ -54,10 +53,8 @@ const childRoutes: RouteObject[] = [ return redirect(`/projects`); } - getProjectGraphService().stop(); - const selectedProjectId = - params.selectedProjectId ?? appConfig.defaultProject; + params.selectedProjectId ?? appConfig.defaultWorkspaceId; return taskDataLoader(selectedProjectId); }, path: 'tasks', @@ -68,7 +65,7 @@ const childRoutes: RouteObject[] = [ element: , }, { - path: ':selectedTask', + path: ':selectedTaskId', element: , }, ], @@ -84,17 +81,17 @@ export const devRoutes: RouteObject[] = [ loader: async ({ request, params }) => { const { search } = new URL(request.url); - return redirect(`/${appConfig.defaultProject}/projects${search}`); + return redirect(`/${appConfig.defaultWorkspaceId}/projects${search}`); }, }, { path: ':selectedProjectId', - id: 'SelectedProject', + id: 'selectedWorkspace', element: , loader: async ({ request, params }) => { const selectedProjectId = - params.selectedProjectId ?? appConfig.defaultProject; - return projectDataLoader(selectedProjectId); + params.selectedProjectId ?? appConfig.defaultWorkspaceId; + return workspaceDataLoader(selectedProjectId); }, children: childRoutes, }, @@ -105,18 +102,19 @@ export const devRoutes: RouteObject[] = [ export const releaseRoutes: RouteObject[] = [ { path: '/', - id: 'SelectedProject', + id: 'selectedWorkspace', loader: async ({ request, params }) => { - const selectedProjectId = - params.selectedProjectId ?? appConfig.defaultProject; - return projectDataLoader(selectedProjectId); + const selectedWorkspaceId = appConfig.defaultWorkspaceId; + return workspaceDataLoader(selectedWorkspaceId); }, element: , children: [ { index: true, - loader: () => { - return redirect(`/projects/`); + loader: ({ request }) => { + const { search } = new URL(request.url); + + return redirect(`/projects${search}`); }, }, ...childRoutes, diff --git a/graph/client/src/app/shell.tsx b/graph/client/src/app/shell.tsx index aa47cf8335..5b13b081c8 100644 --- a/graph/client/src/app/shell.tsx +++ b/graph/client/src/app/shell.tsx @@ -20,7 +20,12 @@ import { projectIsSelectedSelector, } from './feature-projects/machines/selectors'; import { selectValueByThemeStatic } from './theme-resolver'; -import { Outlet, useLoaderData, useNavigate } from 'react-router-dom'; +import { + Outlet, + useLoaderData, + useNavigate, + useParams, +} from 'react-router-dom'; import ThemePanel from './feature-projects/panels/theme-panel'; import Dropdown from './ui-components/dropdown'; import { useCurrentPath } from './hooks/use-current-path'; @@ -32,20 +37,15 @@ import TooltipDisplay from './ui-tooltips/graph-tooltip-display'; export function Shell(): JSX.Element { const projectGraphService = getProjectGraphService(); - const projectGraphDataService = getProjectGraphDataService(); const environment = useEnvironmentConfig(); const lastPerfReport = useProjectGraphSelector(lastPerfReportSelector); const projectIsSelected = useProjectGraphSelector(projectIsSelectedSelector); - const taskIsSelected = true; const environmentConfig = useEnvironmentConfig(); - const [selectedProjectId, setSelectedProjectId] = useState( - environment.appConfig.defaultProject - ); - const navigate = useNavigate(); const currentPath = useCurrentPath(); - + const { selectedProjectId, selectedTaskId } = useParams(); + const taskIsSelected = !!selectedTaskId; const currentRoute = currentPath.currentPath; const topLevelRoute = currentRoute.startsWith('/tasks') @@ -67,40 +67,6 @@ export function Shell(): JSX.Element { } const routeData = useLoaderData() as ProjectGraphClientResponse; - useEffect(() => { - projectGraphService.send({ - type: 'setProjects', - projects: routeData.projects, - dependencies: routeData.dependencies, - affectedProjects: routeData.affected, - workspaceLayout: routeData.layout, - }); - }, [routeData]); - - useIntervalWhen( - () => { - const projectInfo = environment.appConfig.projects.find( - (graph) => graph.id === selectedProjectId - ); - - const fetchProjectGraph = async () => { - const project: ProjectGraphClientResponse = - await projectGraphDataService.getProjectGraph( - projectInfo.projectGraphUrl - ); - - projectGraphService.send({ - type: 'updateGraph', - projects: project.projects, - dependencies: project.dependencies, - }); - }; - - fetchProjectGraph(); - }, - 5000, - environment.watch - ); function downloadImage() { const graph = getGraphService(); @@ -202,7 +168,7 @@ export function Shell(): JSX.Element { > {environment.appConfig.showDebugger ? ( ; + +const Template: ComponentStory = (args) => ( + +); + +export const Primary = Template.bind({}); +Primary.args = { + checked: false, + name: 'option-to-check', + label: 'Option to check', + description: 'You can check this option.', +}; diff --git a/graph/client/src/app/ui-components/checkbox-panel.tsx b/graph/client/src/app/ui-components/checkbox-panel.tsx new file mode 100644 index 0000000000..1248c9616c --- /dev/null +++ b/graph/client/src/app/ui-components/checkbox-panel.tsx @@ -0,0 +1,42 @@ +import { memo } from 'react'; + +export interface CheckboxPanelProps { + checked: boolean; + checkChanged: (checked: boolean) => void; + name: string; + label: string; + description: string; +} + +export const CheckboxPanel = memo( + ({ checked, checkChanged, label, description, name }: CheckboxPanelProps) => { + return ( +
+
+
+ checkChanged(event.target.checked)} + checked={checked} + /> +
+
+ +

{description}

+
+
+
+ ); + } +); + +export default CheckboxPanel; diff --git a/graph/client/src/app/ui-components/debugger-panel.tsx b/graph/client/src/app/ui-components/debugger-panel.tsx index 45b401b998..1f32fb7276 100644 --- a/graph/client/src/app/ui-components/debugger-panel.tsx +++ b/graph/client/src/app/ui-components/debugger-panel.tsx @@ -1,9 +1,9 @@ import { memo } from 'react'; -import { GraphListItem, GraphPerfReport } from '../interfaces'; +import { WorkspaceData, GraphPerfReport } from '../interfaces'; import Dropdown from './dropdown'; export interface DebuggerPanelProps { - projects: GraphListItem[]; + projects: WorkspaceData[]; selectedProject: string; selectedProjectChange: (projectName: string) => void; lastPerfReport: GraphPerfReport; diff --git a/graph/client/src/assets/dev-e2e/environment.js b/graph/client/src/assets/dev-e2e/environment.js index 27d52ee4f2..9d4a870d4a 100644 --- a/graph/client/src/assets/dev-e2e/environment.js +++ b/graph/client/src/assets/dev-e2e/environment.js @@ -6,7 +6,7 @@ window.useXstateInspect = false; window.appConfig = { showDebugger: true, showExperimentalFeatures: true, - projects: [ + workspaces: [ { id: 'e2e', label: 'e2e', @@ -20,5 +20,5 @@ window.appConfig = { taskGraphUrl: 'assets/task-graphs/affected.json', }, ], - defaultProject: 'e2e', + defaultWorkspaceId: 'e2e', }; diff --git a/graph/client/src/assets/nx-console/environment.js b/graph/client/src/assets/nx-console/environment.js index 21d94b9743..c35bf7cccc 100644 --- a/graph/client/src/assets/nx-console/environment.js +++ b/graph/client/src/assets/nx-console/environment.js @@ -6,7 +6,7 @@ window.useXstateInspect = false; window.appConfig = { showDebugger: false, showExperimentalFeatures: false, - projects: [ + workspaces: [ { id: 'local', label: 'local', @@ -14,5 +14,5 @@ window.appConfig = { taskGraphUrl: 'assets/task-graphs/e2e.json', }, ], - defaultProject: 'local', + defaultWorkspaceId: 'local', }; diff --git a/graph/client/src/assets/release-static/environment.js b/graph/client/src/assets/release-static/environment.js index e17c55a146..faa5bbf1cf 100644 --- a/graph/client/src/assets/release-static/environment.js +++ b/graph/client/src/assets/release-static/environment.js @@ -7,7 +7,7 @@ window.localMode = 'build'; window.appConfig = { showDebugger: false, showExperimentalFeatures: false, - projects: [ + workspaces: [ { id: 'local', label: 'local', @@ -15,7 +15,7 @@ window.appConfig = { taskGraphUrl: 'assets/task-graphs/e2e.json', }, ], - defaultProject: 'local', + defaultWorkspaceId: 'local', }; window.projectGraphResponse = { diff --git a/graph/client/src/assets/release/environment.js b/graph/client/src/assets/release/environment.js index a0600d2aec..a2ec0f60ea 100644 --- a/graph/client/src/assets/release/environment.js +++ b/graph/client/src/assets/release/environment.js @@ -6,7 +6,7 @@ window.useXstateInspect = false; window.appConfig = { showDebugger: false, showExperimentalFeatures: false, - projects: [ + workspaces: [ { id: 'local', label: 'local', @@ -14,5 +14,5 @@ window.appConfig = { taskGraphUrl: 'assets/task-graphs/e2e.json', }, ], - defaultProject: 'local', + defaultWorkspaceId: 'local', }; diff --git a/graph/client/src/assets/watch/environment.js b/graph/client/src/assets/watch/environment.js index ad56d48622..d1e941a1f3 100644 --- a/graph/client/src/assets/watch/environment.js +++ b/graph/client/src/assets/watch/environment.js @@ -6,7 +6,7 @@ window.useXstateInspect = false; window.appConfig = { showDebugger: false, showExperimentalFeatures: true, - projects: [ + workspaces: [ { id: 'local', label: 'local', @@ -14,5 +14,5 @@ window.appConfig = { taskGraphUrl: 'assets/task-graphs/e2e.json', }, ], - defaultProject: 'local', + defaultWorkspaceId: 'local', }; diff --git a/graph/ui-graph/src/lib/graph.ts b/graph/ui-graph/src/lib/graph.ts index 7e3ae126bd..fa9134a868 100644 --- a/graph/ui-graph/src/lib/graph.ts +++ b/graph/ui-graph/src/lib/graph.ts @@ -216,6 +216,12 @@ export class GraphService { break; case 'notifyTaskGraphDeselectTask': elementsToSendToRender = this.taskTraversalGraph.deselectTask(); + break; + case 'setGroupByProject': + elementsToSendToRender = this.taskTraversalGraph.setGroupByProject( + event.groupByProject + ); + break; } diff --git a/graph/ui-graph/src/lib/interfaces.ts b/graph/ui-graph/src/lib/interfaces.ts index 53f805e4fd..5df8b1bfd1 100644 --- a/graph/ui-graph/src/lib/interfaces.ts +++ b/graph/ui-graph/src/lib/interfaces.ts @@ -124,4 +124,8 @@ export type TaskGraphRenderEvents = } | { type: 'notifyTaskGraphDeselectTask'; + } + | { + type: 'setGroupByProject'; + groupByProject: boolean; }; diff --git a/graph/ui-graph/src/lib/styles-graph/nodes.ts b/graph/ui-graph/src/lib/styles-graph/nodes.ts index 5440579f81..1d35a074c6 100644 --- a/graph/ui-graph/src/lib/styles-graph/nodes.ts +++ b/graph/ui-graph/src/lib/styles-graph/nodes.ts @@ -91,6 +91,13 @@ const highlightedNodes: Stylesheet = { }, }; +const taskNodes: Stylesheet = { + selector: 'node.task', + style: { + label: 'data(label)', + }, +}; + const transparentProjectNodes: Stylesheet = { selector: 'node.transparent:childless', style: { opacity: 0.5 }, @@ -113,4 +120,5 @@ export const nodeStyles = [ highlightedNodes, transparentProjectNodes, transparentParentNodes, + taskNodes, ]; diff --git a/graph/ui-graph/src/lib/util-cytoscape/render-graph.ts b/graph/ui-graph/src/lib/util-cytoscape/render-graph.ts index eb3d5e73c5..e9990ae94a 100644 --- a/graph/ui-graph/src/lib/util-cytoscape/render-graph.ts +++ b/graph/ui-graph/src/lib/util-cytoscape/render-graph.ts @@ -46,29 +46,12 @@ export class RenderGraph { set theme(theme: 'light' | 'dark') { this._theme = theme; - - if (this.cy) { - this.cy.unmount(); - const useDarkMode = theme === 'dark'; - - this.cy.scratch(darkModeScratchKey, useDarkMode); - this.cy.elements().scratch(darkModeScratchKey, useDarkMode); - - this.cy.mount(this.activeContainer); - } + this.render(); } set rankDir(rankDir: 'LR' | 'TB') { this._rankDir = rankDir; - if (this.cy) { - const elements = this.cy.elements(); - elements - .layout({ - ...cytoscapeDagreConfig, - ...{ rankDir: rankDir }, - } as CytoscapeDagreConfig) - .run(); - } + this.render(); } get activeContainer() { diff --git a/graph/ui-graph/src/lib/util-cytoscape/task-node.ts b/graph/ui-graph/src/lib/util-cytoscape/task-node.ts index d53a69c9bd..ce0e9e111a 100644 --- a/graph/ui-graph/src/lib/util-cytoscape/task-node.ts +++ b/graph/ui-graph/src/lib/util-cytoscape/task-node.ts @@ -4,26 +4,33 @@ import * as cy from 'cytoscape'; export interface TaskNodeDataDefinition extends cy.NodeDataDefinition { id: string; + label: string; executor: string; } export class TaskNode { constructor(private task: Task, private project: ProjectGraphProjectNode) {} - getCytoscapeNodeDef(): cy.NodeDefinition { + getCytoscapeNodeDef(groupByProject: boolean): cy.NodeDefinition { return { group: 'nodes', - data: this.getData(), + classes: 'task', + data: this.getData(groupByProject), selectable: false, grabbable: false, pannable: true, }; } - private getData(): TaskNodeDataDefinition { + private getData(groupByProject: boolean): TaskNodeDataDefinition { + const label = groupByProject + ? this.task.id.split(':').slice(1).join(':') + : this.task.id; return { id: this.task.id, - executor: 'placeholder', + label, + executor: this.project.data.targets[this.task.target.target].executor, + parent: groupByProject ? this.task.target.project : null, }; } } diff --git a/graph/ui-graph/src/lib/util-cytoscape/task-traversal.graph.ts b/graph/ui-graph/src/lib/util-cytoscape/task-traversal.graph.ts index 4c425c3a58..ed931d017f 100644 --- a/graph/ui-graph/src/lib/util-cytoscape/task-traversal.graph.ts +++ b/graph/ui-graph/src/lib/util-cytoscape/task-traversal.graph.ts @@ -4,22 +4,44 @@ import { TaskGraphRecord } from '../interfaces'; import { TaskNode } from './task-node'; import { TaskEdge } from './task-edge'; import cytoscape, { Core } from 'cytoscape'; +import { ParentNode } from './parent-node'; export class TaskTraversalGraph { private projects: ProjectGraphProjectNode[] = []; private taskGraphs: TaskGraphRecord = {}; private cy: Core; + private selectedTask: string; + private groupByProject: boolean = false; setProjects( projects: ProjectGraphProjectNode[], taskGraphs: TaskGraphRecord ) { + this.selectedTask = null; this.projects = projects; this.taskGraphs = taskGraphs; } selectTask(taskId: string) { - this.createElements(taskId); + this.selectedTask = taskId; + this.createElements(taskId, this.groupByProject); + + return this.cy.elements(); + } + + setGroupByProject(groupByProject: boolean) { + this.groupByProject = groupByProject; + + if (this.selectedTask) { + this.createElements(this.selectedTask, groupByProject); + } else { + this.cy = cytoscape({ + headless: true, + elements: [], + }); + + return this.cy.elements(); + } return this.cy.elements(); } @@ -33,31 +55,43 @@ export class TaskTraversalGraph { return this.cy.elements(); } - private createElements(taskId: string) { - const [projectName, target, configuration] = taskId.split(':'); + private createElements(taskId: string, groupByFolder: boolean) { const taskGraph = this.taskGraphs[taskId]; if (taskGraph === undefined) { throw new Error(`Could not find task graph for ${taskId}`); } - const project = this.projects.find( - (project) => project.name === projectName - ); - - if (project === undefined) { - throw new Error(`Could not find project ${projectName}`); - } - const taskElements = []; + const parents: Record< + string, + { id: string; parentId: string; label: string } + > = {}; + for (let taskName in taskGraph.tasks) { - taskElements.push( - new TaskNode( - taskGraph.tasks[taskName], - this.projects[taskGraph.tasks[taskName].target.project] - ) + const task = taskGraph.tasks[taskName]; + const project = this.projects.find( + (project) => project.name === task.target.project ); + + if (project === undefined) { + throw new Error(`Could not find project ${project.name}`); + } + + taskElements.push(new TaskNode(taskGraph.tasks[taskName], project)); + + if (groupByFolder) { + parents[project.name] = { + id: project.name, + parentId: null, + label: project.name, + }; + } + } + + for (let parent in parents) { + taskElements.push(new ParentNode(parents[parent])); } for (let topDep in taskGraph.dependencies) { @@ -68,7 +102,9 @@ export class TaskTraversalGraph { this.cy = cytoscape({ headless: true, - elements: taskElements.map((element) => element.getCytoscapeNodeDef()), + elements: taskElements.map((element) => + element.getCytoscapeNodeDef(groupByFolder) + ), boxSelectionEnabled: false, }); } diff --git a/packages/nx/src/command-line/dep-graph.ts b/packages/nx/src/command-line/dep-graph.ts index 907081ee90..ad98378501 100644 --- a/packages/nx/src/command-line/dep-graph.ts +++ b/packages/nx/src/command-line/dep-graph.ts @@ -73,7 +73,7 @@ function buildEnvironmentJs( window.appConfig = { showDebugger: false, showExperimentalFeatures: false, - projects: [ + workspaces: [ { id: 'local', label: 'local', @@ -81,7 +81,7 @@ function buildEnvironmentJs( taskGraphUrl: 'task-graph.json' } ], - defaultProject: 'local', + defaultWorkspaceId: 'local', }; `; @@ -408,7 +408,7 @@ async function startServer( params.append('groupByFolder', 'true'); } - open(`${url}?${params.toString()}`); + open(`${url}/projects?${params.toString()}`); } } diff --git a/scripts/generate-graph-environment.ts b/scripts/generate-graph-environment.ts index 447627674b..dedd2c2815 100644 --- a/scripts/generate-graph-environment.ts +++ b/scripts/generate-graph-environment.ts @@ -4,7 +4,7 @@ import { readdirSync, writeFileSync } from 'fs'; import { execSync } from 'child_process'; function generateFileContent( - projects: { id: string; label: string; url: string }[] + workspaces: { id: string; label: string; url: string }[] ) { return ` window.exclude = []; @@ -15,8 +15,8 @@ function generateFileContent( window.appConfig = { showDebugger: true, showExperimentalFeatures: true, - projects: ${JSON.stringify(projects)}, - defaultProject: '${projects[0].id}', + workspaces: ${JSON.stringify(workspaces)}, + defaultWorkspaceId: '${workspaces[0].id}', }; `; }