chore(graph): remove route listener and setter machines (#13524)

This commit is contained in:
Philip Fulcher 2022-11-30 17:18:31 -07:00 committed by GitHub
parent 84cbcb7e10
commit f1f7ccc25e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 415 additions and 877 deletions

View File

@ -1,26 +1,3 @@
import {
getCheckedProjectItems,
getDeselectAllButton,
getFocusButtonForProject,
getGroupByFolderCheckbox,
getImageDownloadButton,
getIncludeProjectsInPathButton,
getSearchDepthCheckbox,
getSearchDepthIncrementButton,
getSelectAffectedButton,
getSelectAllButton,
getSelectProjectsMessage,
getTextFilterInput,
getTextFilterReset,
getToggleAllButtonForFolder,
getUncheckedProjectItems,
getUnfocusProjectButton,
} from '../support/app.po';
import * as affectedJson from '../fixtures/affected.json';
import { testProjectsRoutes } from '../support/routing-tests';
import * as nxExamplesJson from '../fixtures/nx-examples-project-graph.json';
describe('dev mode - app', () => { describe('dev mode - app', () => {
before(() => { before(() => {
cy.intercept('/assets/project-graphs/e2e.json', { cy.intercept('/assets/project-graphs/e2e.json', {

View File

@ -215,6 +215,7 @@ describe('dev mode - project graph', () => {
.map((dependencies) => dependencies.target) .map((dependencies) => dependencies.target)
.includes('cart') .includes('cart')
); );
getUnfocusProjectButton().should('exist');
getCheckedProjectItems().should( getCheckedProjectItems().should(
'have.length', 'have.length',
['cart', ...dependencies, ...dependents].length ['cart', ...dependencies, ...dependents].length
@ -226,6 +227,7 @@ describe('dev mode - project graph', () => {
it('should uncheck all project items', () => { it('should uncheck all project items', () => {
getFocusButtonForProject('cart').click({ force: true }); getFocusButtonForProject('cart').click({ force: true });
getUnfocusProjectButton().click(); getUnfocusProjectButton().click();
getUnfocusProjectButton().should('not.exist');
getCheckedProjectItems().should('have.length', 0); getCheckedProjectItems().should('have.length', 0);
}); });
@ -274,34 +276,52 @@ describe('dev mode - project graph', () => {
it('should set focused project', () => { it('should set focused project', () => {
cy.contains('cart').scrollIntoView().should('be.visible'); cy.contains('cart').scrollIntoView().should('be.visible');
getFocusButtonForProject('cart').click({ force: true }); getFocusButtonForProject('cart').click({ force: true });
getUnfocusProjectButton().should('exist');
cy.url().should('contain', 'focus=cart'); cy.url().should('contain', '/projects/cart');
cy.reload();
getUnfocusProjectButton().should('exist');
}); });
it('should set group by folder', () => { it('should set group by folder', () => {
getGroupByFolderCheckbox().click(); getGroupByFolderCheckbox().click();
getGroupByFolderCheckbox().should('be.checked');
cy.url().should('contain', 'groupByFolder=true'); cy.url().should('contain', 'groupByFolder=true');
cy.reload();
getGroupByFolderCheckbox().should('be.checked');
}); });
it('should set search depth disabled', () => { it('should set search depth disabled', () => {
// it's on by default, clicking should disable it // it's on by default, clicking should disable it
getSearchDepthCheckbox().click(); getSearchDepthCheckbox().click();
getSearchDepthCheckbox().should('not.be.checked');
cy.url().should('contain', 'searchDepth=0'); cy.url().should('contain', 'searchDepth=0');
cy.reload();
getSearchDepthCheckbox().should('not.be.checked');
// re-enable to clean-up for following tests
getSearchDepthCheckbox().click();
}); });
it('should set search depth if greater than 1', () => { it('should set search depth if greater than 1', () => {
// it's on by default and set to 1, clicking should change it to 2 // it's on by default and set to 1, clicking should change it to 2
getSearchDepthIncrementButton().click(); getSearchDepthIncrementButton().click();
cy.get('[data-cy="depth-value"]').should('contain', '2');
cy.url().should('contain', 'searchDepth=2'); cy.url().should('contain', 'searchDepth=2');
cy.reload();
cy.get('[data-cy="depth-value"]').should('contain', '2');
}); });
it('should set select to all', () => { it('should set select to all', () => {
getSelectAllButton().click(); getSelectAllButton().click();
getCheckedProjectItems().should(
cy.url().should('contain', 'select=all'); 'have.length',
nxExamplesJson.projects.length
);
cy.url().should('contain', '/projects/all');
cy.reload();
getCheckedProjectItems().should(
'have.length',
nxExamplesJson.projects.length
);
}); });
}); });
}); });
@ -313,7 +333,5 @@ describe('loading graph client with url params', () => {
}).as('getGraph'); }).as('getGraph');
}); });
// check that params work from old base url of / testProjectsRoutes('browser', ['/e2e/projects']);
// and also new /projects route
testProjectsRoutes('browser', ['/', '/e2e/projects']);
}); });

View File

@ -1,23 +1,25 @@
import { testProjectsRoutes, testTaskRoutes } from '../support/routing-tests'; import { testProjectsRoutes, testTaskRoutes } from '../support/routing-tests';
describe('release static-mode app', () => { describe('release static-mode app', () => {
beforeEach(() => { describe('smoke tests', () => {
cy.visit('/'); beforeEach(() => {
}); cy.visit('/');
});
it('should not display experimental features', () => { it('should not display experimental features', () => {
cy.get('experimental-features').should('not.exist'); cy.get('experimental-features').should('not.exist');
}); });
it('should not display the debugger', () => { it('should not display the debugger', () => {
cy.get('debugger-panel').should('not.exist'); cy.get('debugger-panel').should('not.exist');
}); });
describe('routing', () => {
it('should use hash router', () => { it('should use hash router', () => {
cy.url().should('contain', '/#/projects'); cy.url().should('contain', '/#/projects');
}); });
});
testProjectsRoutes('hash', ['/', '/projects']); describe('routing', () => {
testProjectsRoutes('hash', ['/projects']);
}); });
}); });

View File

@ -25,7 +25,7 @@ function resolveProjectsRoute(
paramString: string paramString: string
) { ) {
if (router === 'hash') { if (router === 'hash') {
return `/?${paramString}#${route}`; return `/#${route}?${paramString}`;
} else { } else {
return `${route}?${paramString}`; return `${route}?${paramString}`;
} }
@ -50,7 +50,7 @@ export function testProjectsRoutes(
routes.forEach((route) => { routes.forEach((route) => {
describe(`for route ${route}`, () => { describe(`for route ${route}`, () => {
it('should focus projects', () => { it('should focus projects', () => {
cy.visit(resolveProjectsRoute(router, route, 'focus=cart')); cy.visit(resolveProjectsRoute(router, `${route}/cart`, ''));
// wait for first graph to finish loading // wait for first graph to finish loading
waitForProjectGraph(router); waitForProjectGraph(router);
@ -70,7 +70,7 @@ export function testProjectsRoutes(
it('should focus projects with search depth', () => { it('should focus projects with search depth', () => {
cy.visit( cy.visit(
resolveProjectsRoute(router, route, `focus=cart&searchDepth=2`) resolveProjectsRoute(router, `${route}/cart`, `searchDepth=2`)
); );
// wait for first graph to finish loading // wait for first graph to finish loading
@ -82,7 +82,7 @@ export function testProjectsRoutes(
it('should focus projects with search depth disabled', () => { it('should focus projects with search depth disabled', () => {
cy.visit( cy.visit(
resolveProjectsRoute(router, route, `focus=cart&searchDepth=0`) resolveProjectsRoute(router, `${route}/cart`, `searchDepth=0`)
); );
// wait for first graph to finish loading // wait for first graph to finish loading
@ -94,11 +94,7 @@ export function testProjectsRoutes(
it('should set group by folder', () => { it('should set group by folder', () => {
cy.visit( cy.visit(
resolveProjectsRoute( resolveProjectsRoute(router, `${route}/cart`, `groupByFolder=true`)
router,
route,
`focus=nx-dev&searchDepth=1&groupByFolder=true`
)
); );
// wait for first graph to finish loading // wait for first graph to finish loading
@ -108,7 +104,7 @@ export function testProjectsRoutes(
}); });
it('should select all projects', () => { it('should select all projects', () => {
cy.visit(resolveProjectsRoute(router, route, `select=all`)); cy.visit(resolveProjectsRoute(router, `${route}/all`, ``));
// wait for first graph to finish loading // wait for first graph to finish loading
waitForProjectGraph(router); waitForProjectGraph(router);

View File

@ -3,12 +3,6 @@ import { actions, send } from 'xstate';
import { ProjectGraphStateNodeConfig } from './interfaces'; import { ProjectGraphStateNodeConfig } from './interfaces';
export const customSelectedStateConfig: ProjectGraphStateNodeConfig = { export const customSelectedStateConfig: ProjectGraphStateNodeConfig = {
entry: actions.choose([
{
cond: 'selectActionCannotBePersistedToRoute',
actions: ['notifyRouteClearSelect'],
},
]),
on: { on: {
updateGraph: { updateGraph: {
target: 'customSelected', target: 'customSelected',

View File

@ -9,59 +9,28 @@ export const focusedStateConfig: ProjectGraphStateNodeConfig = {
ctx.focusedProject = event.projectName; ctx.focusedProject = event.projectName;
}), }),
send(
(ctx, event) => {
if (event.type !== 'focusProject') return;
return {
type: 'notifyRouteFocusProject',
focusedProject: event.projectName,
};
},
{
to: (context) => context.routeSetterActor,
}
),
'notifyGraphFocusProject', 'notifyGraphFocusProject',
], ],
exit: [ exit: [
assign((ctx) => { assign((ctx) => {
ctx.focusedProject = null; ctx.focusedProject = null;
}), }),
'notifyRouteUnfocusProject',
], ],
on: { on: {
incrementSearchDepth: { incrementSearchDepth: {
actions: [ actions: ['incrementSearchDepth', 'notifyGraphFocusProject'],
'incrementSearchDepth',
'notifyGraphFocusProject',
'notifyRouteSearchDepth',
],
}, },
decrementSearchDepth: { decrementSearchDepth: {
actions: [ actions: ['decrementSearchDepth', 'notifyGraphFocusProject'],
'decrementSearchDepth',
'notifyGraphFocusProject',
'notifyRouteSearchDepth',
],
}, },
setSearchDepthEnabled: { setSearchDepthEnabled: {
actions: [ actions: ['setSearchDepthEnabled', 'notifyGraphFocusProject'],
'setSearchDepthEnabled',
'notifyGraphFocusProject',
'notifyRouteSearchDepth',
],
}, },
setSearchDepth: { setSearchDepth: {
actions: [ actions: ['setSearchDepth', 'notifyGraphFocusProject'],
'setSearchDepth',
'notifyGraphFocusProject',
'notifyRouteSearchDepth',
],
}, },
unfocusProject: { unfocusProject: {
target: 'unselected', target: 'unselected',
actions: ['notifyRouteUnfocusProject'],
}, },
updateGraph: { updateGraph: {
actions: [ actions: [

View File

@ -5,7 +5,7 @@ import {
ProjectGraphProjectNode, ProjectGraphProjectNode,
} from 'nx/src/config/project-graph'; } from 'nx/src/config/project-graph';
import { ActionObject, ActorRef, State, StateNodeConfig } from 'xstate'; import { ActionObject, ActorRef, State, StateNodeConfig } from 'xstate';
import { GraphRenderEvents, RouteEvents } from '../../machines/interfaces'; import { GraphRenderEvents } from '../../machines/interfaces';
// The hierarchical schema for the states // The hierarchical schema for the states
export interface ProjectGraphSchema { export interface ProjectGraphSchema {
@ -85,8 +85,6 @@ export interface ProjectGraphContext {
appsDir: string; appsDir: string;
}; };
graphActor: ActorRef<GraphRenderEvents>; graphActor: ActorRef<GraphRenderEvents>;
routeSetterActor: ActorRef<RouteEvents>;
routeListenerActor: ActorRef<ProjectGraphMachineEvents>;
lastPerfReport: GraphPerfReport; lastPerfReport: GraphPerfReport;
tracing: { tracing: {
start: string; start: string;

View File

@ -3,7 +3,6 @@ import { createMachine, send, spawn } from 'xstate';
import { customSelectedStateConfig } from './custom-selected.state'; import { customSelectedStateConfig } from './custom-selected.state';
import { focusedStateConfig } from './focused.state'; import { focusedStateConfig } from './focused.state';
import { graphActor } from './graph.actor'; import { graphActor } from './graph.actor';
import { createRouteMachine } from '../../machines/route-setter.machine';
import { textFilteredStateConfig } from './text-filtered.state'; import { textFilteredStateConfig } from './text-filtered.state';
import { tracingStateConfig } from './tracing.state'; import { tracingStateConfig } from './tracing.state';
import { unselectedStateConfig } from './unselected.state'; import { unselectedStateConfig } from './unselected.state';
@ -26,8 +25,6 @@ export const initialContext: ProjectGraphContext = {
appsDir: '', appsDir: '',
}, },
graphActor: null, graphActor: null,
routeSetterActor: null,
routeListenerActor: null,
lastPerfReport: { lastPerfReport: {
numEdges: 0, numEdges: 0,
numNodes: 0, numNodes: 0,
@ -96,14 +93,11 @@ export const projectGraphMachine = createMachine<
}, },
selectAll: { selectAll: {
target: 'customSelected', target: 'customSelected',
actions: ['notifyGraphShowAllProjects', 'notifyRouteSelectAll'], actions: ['notifyGraphShowAllProjects'],
}, },
selectAffected: { selectAffected: {
target: 'customSelected', target: 'customSelected',
actions: [ actions: ['notifyGraphShowAffectedProjects'],
'notifyGraphShowAffectedProjects',
'notifyRouteSelectAffected',
],
}, },
deselectProject: [ deselectProject: [
{ {
@ -155,19 +149,6 @@ export const projectGraphMachine = createMachine<
to: (context) => context.graphActor, to: (context) => context.graphActor,
} }
), ),
send(
(ctx, event) => {
if (event.type !== 'setCollapseEdges') return;
return {
type: 'notifyRouteCollapseEdges',
collapseEdges: event.collapseEdges,
};
},
{
to: (context) => context.routeSetterActor,
}
),
], ],
}, },
setGroupByFolder: { setGroupByFolder: {
@ -188,19 +169,6 @@ export const projectGraphMachine = createMachine<
to: (context) => context.graphActor, to: (context) => context.graphActor,
} }
), ),
send(
(ctx, event) => {
if (event.type !== 'setGroupByFolder') return;
return {
type: 'notifyRouteGroupByFolder',
groupByFolder: event.groupByFolder,
};
},
{
to: (context) => context.routeSetterActor,
}
),
], ],
}, },
setIncludeProjectsByPath: { setIncludeProjectsByPath: {
@ -211,23 +179,22 @@ export const projectGraphMachine = createMachine<
], ],
}, },
incrementSearchDepth: { incrementSearchDepth: {
actions: ['incrementSearchDepth', 'notifyRouteSearchDepth'], actions: ['incrementSearchDepth'],
}, },
decrementSearchDepth: { decrementSearchDepth: {
actions: ['decrementSearchDepth', 'notifyRouteSearchDepth'], actions: ['decrementSearchDepth'],
}, },
setSearchDepthEnabled: { setSearchDepthEnabled: {
actions: ['setSearchDepthEnabled', 'notifyRouteSearchDepth'], actions: ['setSearchDepthEnabled'],
}, },
setSearchDepth: { setSearchDepth: {
actions: ['setSearchDepth', 'notifyRouteSearchDepth'], actions: ['setSearchDepth'],
}, },
setTracingAlgorithm: { setTracingAlgorithm: {
actions: [ actions: [
assign((ctx, event) => { assign((ctx, event) => {
ctx.tracing.algorithm = event.algorithm; ctx.tracing.algorithm = event.algorithm;
}), }),
'notifyRouteTracing',
'notifyGraphTracing', 'notifyGraphTracing',
], ],
}, },
@ -241,9 +208,6 @@ export const projectGraphMachine = createMachine<
deselectLastProject: (ctx) => { deselectLastProject: (ctx) => {
return ctx.selectedProjects.length <= 1; return ctx.selectedProjects.length <= 1;
}, },
selectActionCannotBePersistedToRoute: (ctx, event) => {
return event.type !== 'selectAffected' && event.type !== 'selectAll';
},
}, },
actions: { actions: {
setGroupByFolder: assign((ctx, event) => { setGroupByFolder: assign((ctx, event) => {
@ -286,9 +250,9 @@ export const projectGraphMachine = createMachine<
ctx.projects = event.projects; ctx.projects = event.projects;
ctx.dependencies = event.dependencies; ctx.dependencies = event.dependencies;
ctx.graphActor = spawn(graphActor, 'graphActor'); ctx.graphActor = spawn(graphActor, 'graphActor');
ctx.routeSetterActor = spawn(createRouteMachine(), { // ctx.routeSetterActor = spawn(createRouteMachine(), {
name: 'route', // name: 'route',
}); // });
if (event.type === 'setProjects') { if (event.type === 'setProjects') {
ctx.workspaceLayout = event.workspaceLayout; ctx.workspaceLayout = event.workspaceLayout;
@ -388,62 +352,7 @@ export const projectGraphMachine = createMachine<
to: (context) => context.graphActor, to: (context) => context.graphActor,
} }
), ),
notifyRouteUnfocusProject: send(
() => ({
type: 'notifyRouteUnfocusProject',
}),
{
to: (ctx) => ctx.routeSetterActor,
}
),
notifyRouteSelectAll: send(
() => ({
type: 'notifyRouteSelectAll',
}),
{
to: (ctx) => ctx.routeSetterActor,
}
),
notifyRouteSelectAffected: send(
() => ({
type: 'notifyRouteSelectAffected',
}),
{
to: (ctx) => ctx.routeSetterActor,
}
),
notifyRouteClearSelect: send(
() => ({
type: 'notifyRouteClearSelect',
}),
{
to: (ctx) => ctx.routeSetterActor,
}
),
notifyRouteTracing: send(
(ctx) => {
return {
type: 'notifyRouteTracing',
start: ctx.tracing.start,
end: ctx.tracing.end,
algorithm: ctx.tracing.algorithm,
};
},
{
to: (ctx) => ctx.routeSetterActor,
}
),
notifyRouteSearchDepth: send(
(ctx, event) => ({
type: 'notifyRouteSearchDepth',
searchDepth: ctx.searchDepth,
searchDepthEnabled: ctx.searchDepthEnabled,
}),
{
to: (ctx) => ctx.routeSetterActor,
}
),
notifyGraphFilterProjectsByText: send( notifyGraphFilterProjectsByText: send(
(context, event) => ({ (context, event) => ({
type: 'notifyGraphFilterProjectsByText', type: 'notifyGraphFilterProjectsByText',

View File

@ -10,7 +10,6 @@ export const tracingStateConfig: ProjectGraphStateNodeConfig = {
ctx.tracing.end = event.projectName; ctx.tracing.end = event.projectName;
} }
}), }),
'notifyRouteTracing',
'notifyGraphTracing', 'notifyGraphTracing',
], ],
exit: [ exit: [
@ -20,7 +19,6 @@ export const tracingStateConfig: ProjectGraphStateNodeConfig = {
ctx.tracing.end = null; ctx.tracing.end = null;
} }
}), }),
'notifyRouteTracing',
], ],
on: { on: {
clearTraceStart: { clearTraceStart: {
@ -28,7 +26,6 @@ export const tracingStateConfig: ProjectGraphStateNodeConfig = {
assign((ctx) => { assign((ctx) => {
ctx.tracing.start = null; ctx.tracing.start = null;
}), }),
'notifyRouteTracing',
'notifyGraphTracing', 'notifyGraphTracing',
], ],
}, },
@ -37,7 +34,6 @@ export const tracingStateConfig: ProjectGraphStateNodeConfig = {
assign((ctx) => { assign((ctx) => {
ctx.tracing.end = null; ctx.tracing.end = null;
}), }),
'notifyRouteTracing',
'notifyGraphTracing', 'notifyGraphTracing',
], ],
}, },

View File

@ -1,18 +1,9 @@
import { assign } from '@xstate/immer'; import { assign } from '@xstate/immer';
import { send, spawn } from 'xstate'; import { send } from 'xstate';
import { routeListener } from '../../machines/route-listener.actor';
import { ProjectGraphStateNodeConfig } from './interfaces'; import { ProjectGraphStateNodeConfig } from './interfaces';
export const unselectedStateConfig: ProjectGraphStateNodeConfig = { export const unselectedStateConfig: ProjectGraphStateNodeConfig = {
entry: [ entry: ['notifyGraphHideAllProjects'],
'notifyGraphHideAllProjects',
assign((ctx, event) => {
if (ctx.routeListenerActor === null) {
ctx.routeListenerActor = spawn(routeListener, 'routeListener');
}
}),
'notifyRouteClearSelect',
],
on: { on: {
updateGraph: { updateGraph: {
target: 'customSelected', target: 'customSelected',

View File

@ -57,6 +57,7 @@ export const SearchDepth = memo(
</button> </button>
<span <span
id="depthFilterValue" id="depthFilterValue"
data-cy="depth-value"
className="block w-full flex-1 rounded-none border-t border-b border-slate-300 bg-white p-1.5 text-center font-mono dark:border-slate-600 dark:bg-slate-800 dark:text-slate-300 hover:dark:bg-slate-700" className="block w-full flex-1 rounded-none border-t border-b border-slate-300 bg-white p-1.5 text-center font-mono dark:border-slate-600 dark:bg-slate-800 dark:text-slate-300 hover:dark:bg-slate-700"
> >
{searchDepth} {searchDepth}

View File

@ -13,10 +13,15 @@ import {
selectedProjectNamesSelector, selectedProjectNamesSelector,
workspaceLayoutSelector, workspaceLayoutSelector,
} from './machines/selectors'; } from './machines/selectors';
import { getProjectsByType, parseParentDirectoriesFromFilePath } from '../util'; import {
getProjectsByType,
parseParentDirectoriesFromFilePath,
useRouteConstructor,
} from '../util';
import ExperimentalFeature from '../ui-components/experimental-feature'; import ExperimentalFeature from '../ui-components/experimental-feature';
import { TracingAlgorithmType } from './machines/interfaces'; import { TracingAlgorithmType } from './machines/interfaces';
import { getProjectGraphService } from '../machines/get-services'; import { getProjectGraphService } from '../machines/get-services';
import { Link, useNavigate } from 'react-router-dom';
interface SidebarProject { interface SidebarProject {
projectGraphNode: ProjectGraphNode; projectGraphNode: ProjectGraphNode;
@ -70,6 +75,8 @@ function ProjectListItem({
tracingInfo: TracingInfo; tracingInfo: TracingInfo;
}) { }) {
const projectGraphService = getProjectGraphService(); const projectGraphService = getProjectGraphService();
const navigate = useNavigate();
const routeConstructor = useRouteConstructor();
function startTrace(projectName: string) { function startTrace(projectName: string) {
projectGraphService.send({ type: 'setTracingStart', projectName }); projectGraphService.send({ type: 'setTracingStart', projectName });
@ -85,24 +92,23 @@ function ProjectListItem({
} else { } else {
projectGraphService.send({ type: 'selectProject', projectName }); projectGraphService.send({ type: 'selectProject', projectName });
} }
} navigate(routeConstructor('/projects', true));
function focusProject(projectName: string) {
projectGraphService.send({ type: 'focusProject', projectName });
} }
return ( return (
<li className="relative block cursor-default select-none py-1 pl-2 pr-6 text-xs text-slate-600 dark:text-slate-400"> <li className="relative block cursor-default select-none py-1 pl-2 pr-6 text-xs text-slate-600 dark:text-slate-400">
<div className="flex items-center"> <div className="flex items-center">
<button <Link
data-cy={`focus-button-${project.projectGraphNode.name}`} data-cy={`focus-button-${project.projectGraphNode.name}`}
type="button"
className="mr-1 flex items-center rounded-md border-slate-300 bg-white p-1 font-medium text-slate-500 shadow-sm ring-1 ring-slate-200 transition hover:bg-slate-50 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-400 dark:ring-slate-600 hover:dark:bg-slate-700" className="mr-1 flex items-center rounded-md border-slate-300 bg-white p-1 font-medium text-slate-500 shadow-sm ring-1 ring-slate-200 transition hover:bg-slate-50 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-400 dark:ring-slate-600 hover:dark:bg-slate-700"
title="Focus on this library" title="Focus on this library"
onClick={() => focusProject(project.projectGraphNode.name)} to={routeConstructor(
`/projects/${project.projectGraphNode.name}`,
true
)}
> >
<DocumentMagnifyingGlassIcon className="h-5 w-5" /> <DocumentMagnifyingGlassIcon className="h-5 w-5" />
</button> </Link>
<ExperimentalFeature> <ExperimentalFeature>
<span className="relative z-0 inline-flex rounded-md shadow-sm"> <span className="relative z-0 inline-flex rounded-md shadow-sm">

View File

@ -25,8 +25,15 @@ import { getProjectGraphService } from '../machines/get-services';
import { useIntervalWhen } from '../hooks/use-interval-when'; import { useIntervalWhen } from '../hooks/use-interval-when';
// nx-ignore-next-line // nx-ignore-next-line
import { ProjectGraphClientResponse } from 'nx/src/command-line/dep-graph'; import { ProjectGraphClientResponse } from 'nx/src/command-line/dep-graph';
import { useParams, useRouteLoaderData } from 'react-router-dom'; import {
useNavigate,
useParams,
useRouteLoaderData,
useSearchParams,
} from 'react-router-dom';
import { getProjectGraphDataService } from '../hooks/get-project-graph-data-service'; import { getProjectGraphDataService } from '../hooks/get-project-graph-data-service';
import { useCurrentPath } from '../hooks/use-current-path';
import { useRouteConstructor } from '../util';
export function ProjectsSidebar(): JSX.Element { export function ProjectsSidebar(): JSX.Element {
const environmentConfig = useEnvironmentConfig(); const environmentConfig = useEnvironmentConfig();
@ -41,58 +48,106 @@ export function ProjectsSidebar(): JSX.Element {
const groupByFolder = useProjectGraphSelector(groupByFolderSelector); const groupByFolder = useProjectGraphSelector(groupByFolderSelector);
const collapseEdges = useProjectGraphSelector(collapseEdgesSelector); const collapseEdges = useProjectGraphSelector(collapseEdgesSelector);
const isTracing = projectGraphService.state.matches('tracing'); const isTracing = projectGraphService.getSnapshot().matches('tracing');
const tracingInfo = useProjectGraphSelector(getTracingInfo); const tracingInfo = useProjectGraphSelector(getTracingInfo);
const projectGraphDataService = getProjectGraphDataService(); const projectGraphDataService = getProjectGraphDataService();
const routeParams = useParams();
const currentRoute = useCurrentPath();
const [searchParams, setSearchParams] = useSearchParams();
const selectedProjectRouteData = useRouteLoaderData( const selectedProjectRouteData = useRouteLoaderData(
'selectedWorkspace' 'selectedWorkspace'
) as ProjectGraphClientResponse; ) as ProjectGraphClientResponse;
const params = useParams(); const params = useParams();
const navigate = useNavigate();
const routeContructor = useRouteConstructor();
function resetFocus() { function resetFocus() {
projectGraphService.send({ type: 'unfocusProject' }); projectGraphService.send({ type: 'unfocusProject' });
navigate(routeContructor('/projects', true));
} }
function showAllProjects() { function showAllProjects() {
projectGraphService.send({ type: 'selectAll' }); navigate(routeContructor('/projects/all', true));
} }
function hideAllProjects() { function hideAllProjects() {
projectGraphService.send({ type: 'deselectAll' }); projectGraphService.send({ type: 'deselectAll' });
navigate(routeContructor('/projects', true));
} }
function showAffectedProjects() { function showAffectedProjects() {
projectGraphService.send({ type: 'selectAffected' }); navigate(routeContructor('/projects/affected', true));
} }
function searchDepthFilterEnabledChange(checked: boolean) { function searchDepthFilterEnabledChange(checked: boolean) {
projectGraphService.send({ setSearchParams((currentSearchParams) => {
type: 'setSearchDepthEnabled', if (checked && searchDepthInfo.searchDepth > 1) {
searchDepthEnabled: checked, currentSearchParams.set(
'searchDepth',
searchDepthInfo.searchDepth.toString()
);
} else if (checked && searchDepthInfo.searchDepth === 1) {
currentSearchParams.delete('searchDepth');
} else {
currentSearchParams.set('searchDepth', '0');
}
return currentSearchParams;
}); });
} }
function groupByFolderChanged(checked: boolean) { function groupByFolderChanged(checked: boolean) {
projectGraphService.send({ setSearchParams((currentSearchParams) => {
type: 'setGroupByFolder', if (checked) {
groupByFolder: checked, currentSearchParams.set('groupByFolder', 'true');
} else {
currentSearchParams.delete('groupByFolder');
}
return currentSearchParams;
}); });
} }
function collapseEdgesChanged(checked: boolean) { function collapseEdgesChanged(checked: boolean) {
projectGraphService.send({ setSearchParams((currentSearchParams) => {
type: 'setCollapseEdges', if (checked) {
collapseEdges: checked, currentSearchParams.set('collapseEdges', 'true');
} else {
currentSearchParams.delete('collapseEdges');
}
return currentSearchParams;
}); });
} }
function incrementDepthFilter() { function incrementDepthFilter() {
projectGraphService.send({ type: 'incrementSearchDepth' }); if (searchDepthInfo.searchDepthEnabled) {
const newSearchDepth = searchDepthInfo.searchDepth + 1;
setSearchParams((currentSearchParams) => {
if (newSearchDepth === 1) {
currentSearchParams.delete('searchDepth');
} else {
currentSearchParams.set('searchDepth', newSearchDepth.toString());
}
return currentSearchParams;
});
}
} }
function decrementDepthFilter() { function decrementDepthFilter() {
projectGraphService.send({ type: 'decrementSearchDepth' }); if (searchDepthInfo.searchDepthEnabled) {
const newSearchDepth =
searchDepthInfo.searchDepth === 0 ? 0 : searchDepthInfo.searchDepth - 1;
setSearchParams((currentSearchParams) => {
if (newSearchDepth === 1) {
currentSearchParams.delete('searchDepth');
} else {
currentSearchParams.set('searchDepth', newSearchDepth.toString());
}
return currentSearchParams;
});
}
} }
function resetTextFilter() { function resetTextFilter() {
@ -108,16 +163,19 @@ export function ProjectsSidebar(): JSX.Element {
function resetTraceStart() { function resetTraceStart() {
projectGraphService.send({ type: 'clearTraceStart' }); projectGraphService.send({ type: 'clearTraceStart' });
navigate(routeContructor('/projects', true));
} }
function resetTraceEnd() { function resetTraceEnd() {
projectGraphService.send({ type: 'clearTraceEnd' }); projectGraphService.send({ type: 'clearTraceEnd' });
navigate(routeContructor('/projects', true));
} }
function setAlgorithm(algorithm: TracingAlgorithmType) { function setAlgorithm(algorithm: TracingAlgorithmType) {
projectGraphService.send({ setSearchParams((searchParams) => {
type: 'setTracingAlgorithm', searchParams.set('traceAlgorithm', algorithm);
algorithm: algorithm,
return searchParams;
}); });
} }
@ -131,6 +189,108 @@ export function ProjectsSidebar(): JSX.Element {
}); });
}, [selectedProjectRouteData]); }, [selectedProjectRouteData]);
useEffect(() => {
switch (currentRoute.currentPath) {
case '/projects/all':
projectGraphService.send({ type: 'selectAll' });
break;
case '/projects/affected':
projectGraphService.send({ type: 'selectAffected' });
break;
}
}, [currentRoute]);
useEffect(() => {
if (routeParams.focusedProject) {
projectGraphService.send({
type: 'focusProject',
projectName: routeParams.focusedProject,
});
}
if (routeParams.startTrace) {
projectGraphService.send({
type: 'setTracingStart',
projectName: routeParams.startTrace,
});
}
if (routeParams.endTrace) {
projectGraphService.send({
type: 'setTracingEnd',
projectName: routeParams.endTrace,
});
}
}, [routeParams]);
useEffect(() => {
if (searchParams.has('groupByFolder') && groupByFolder === false) {
projectGraphService.send({
type: 'setGroupByFolder',
groupByFolder: true,
});
} else if (!searchParams.has('groupByFolder') && groupByFolder === true) {
projectGraphService.send({
type: 'setGroupByFolder',
groupByFolder: false,
});
}
if (searchParams.has('collapseEdges') && collapseEdges === false) {
projectGraphService.send({
type: 'setCollapseEdges',
collapseEdges: true,
});
} else if (!searchParams.has('collapseEdges') && collapseEdges === true) {
projectGraphService.send({
type: 'setCollapseEdges',
collapseEdges: false,
});
}
if (searchParams.has('searchDepth')) {
const parsedValue = parseInt(searchParams.get('searchDepth'), 10);
if (parsedValue === 0) {
projectGraphService.send({
type: 'setSearchDepthEnabled',
searchDepthEnabled: false,
});
} else {
projectGraphService.send({
type: 'setSearchDepth',
searchDepth: parsedValue,
});
}
} else if (
searchDepthInfo.searchDepthEnabled === false ||
searchDepthInfo.searchDepth !== 1
) {
projectGraphService.send({
type: 'setSearchDepthEnabled',
searchDepthEnabled: true,
});
projectGraphService.send({
type: 'setSearchDepth',
searchDepth: 1,
});
}
if (searchParams.has('traceAlgorithm')) {
const tracingAlgorithm = searchParams.get('traceAlgorithm');
if (tracingAlgorithm === 'shortest' || tracingAlgorithm === 'all') {
projectGraphService.send({
type: 'setTracingAlgorithm',
algorithm: tracingAlgorithm,
});
}
} else if (tracingInfo.algorithm !== 'shortest') {
projectGraphService.send({
type: 'setTracingAlgorithm',
algorithm: 'shortest',
});
}
}, [searchParams]);
useIntervalWhen( useIntervalWhen(
() => { () => {
const selectedWorkspaceId = const selectedWorkspaceId =
@ -163,6 +323,7 @@ export function ProjectsSidebar(): JSX.Element {
const updateTextFilter = useCallback( const updateTextFilter = useCallback(
(textFilter: string) => { (textFilter: string) => {
projectGraphService.send({ type: 'filterByText', search: textFilter }); projectGraphService.send({ type: 'filterByText', search: textFilter });
navigate(routeContructor('/projects', true));
}, },
[projectGraphService] [projectGraphService]
); );

View File

@ -38,16 +38,16 @@ export function TasksSidebar() {
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const groupByProject = searchParams.get('groupByProject') === 'true'; const groupByProject = searchParams.get('groupByProject') === 'true';
const selectedProjectRouteData = useRouteLoaderData( const selectedWorkspaceRouteData = useRouteLoaderData(
'selectedWorkspace' 'selectedWorkspace'
) as ProjectGraphClientResponse & { targets: string[] }; ) as ProjectGraphClientResponse & { targets: string[] };
const workspaceLayout = selectedProjectRouteData.layout; const workspaceLayout = selectedWorkspaceRouteData.layout;
const routeData = useRouteLoaderData( const routeData = useRouteLoaderData(
'selectedTarget' 'selectedTarget'
) as TaskGraphClientResponse; ) as TaskGraphClientResponse;
const { taskGraphs } = routeData; const { taskGraphs } = routeData;
const { projects, targets } = selectedProjectRouteData; const { projects, targets } = selectedWorkspaceRouteData;
const selectedTarget = params['selectedTarget'] ?? targets[0]; const selectedTarget = params['selectedTarget'] ?? targets[0];
const [selectedProjects, setSelectedProjects] = useState<string[]>([]); const [selectedProjects, setSelectedProjects] = useState<string[]>([]);
@ -131,10 +131,10 @@ export function TasksSidebar() {
setSelectedProjects([]); setSelectedProjects([]);
graphService.handleTaskEvent({ graphService.handleTaskEvent({
type: 'notifyTaskGraphSetProjects', type: 'notifyTaskGraphSetProjects',
projects: selectedProjectRouteData.projects, projects: selectedWorkspaceRouteData.projects,
taskGraphs, taskGraphs,
}); });
}, [selectedProjectRouteData]); }, [selectedWorkspaceRouteData]);
useEffect(() => { useEffect(() => {
if (groupByProject) { if (groupByProject) {

View File

@ -1,20 +1,34 @@
import { matchRoutes, useLocation } from 'react-router-dom'; import { matchRoutes, useLocation } from 'react-router-dom';
import { getRoutesForEnvironment } from '../routes'; import { getRoutesForEnvironment } from '../routes';
import { getEnvironmentConfig } from './use-environment-config'; import { getEnvironmentConfig } from './use-environment-config';
import { useState } from 'react';
export const useCurrentPath = () => { export const useCurrentPath = () => {
const [lastLocation, setLastLocation] = useState<string>();
const [lastPath, setLastPath] = useState();
const location = useLocation(); const location = useLocation();
if (location.pathname === lastLocation) {
return lastPath;
}
setLastLocation(location.pathname);
const route = matchRoutes(getRoutesForEnvironment(), location).at(-1); const route = matchRoutes(getRoutesForEnvironment(), location).at(-1);
const { environment } = getEnvironmentConfig(); const { environment } = getEnvironmentConfig();
let currentPath;
// if using dev routes, remove first segment for workspace // if using dev routes, remove first segment for workspace
if (environment === 'dev') { if (environment === 'dev') {
return { currentPath = {
workspace: route.pathname.split('/')[1], workspace: route.pathname.split('/')[1],
currentPath: `/${route.pathname.split('/').slice(2).join('/')}`, currentPath: `/${route.pathname.split('/').slice(2).join('/')}`,
}; };
} else { } else {
return { workspace: 'local', currentPath: route.pathname }; currentPath = { workspace: 'local', currentPath: route.pathname };
} }
setLastPath(currentPath);
return currentPath;
}; };

View File

@ -66,38 +66,3 @@ export type GraphRenderEvents =
end: string; end: string;
algorithm: TracingAlgorithmType; algorithm: TracingAlgorithmType;
}; };
export type RouteEvents =
| {
type: 'notifyRouteFocusProject';
focusedProject: string;
}
| {
type: 'notifyRouteGroupByFolder';
groupByFolder: boolean;
}
| {
type: 'notifyRouteCollapseEdges';
collapseEdges: boolean;
}
| {
type: 'notifyRouteSearchDepth';
searchDepthEnabled: boolean;
searchDepth: number;
}
| {
type: 'notifyRouteUnfocusProject';
}
| {
type: 'notifyRouteSelectAll';
}
| {
type: 'notifyRouteSelectAffected';
}
| { type: 'notifyRouteClearSelect' }
| {
type: 'notifyRouteTracing';
start: string;
end: string;
algorithm: TracingAlgorithmType;
};

View File

@ -1,73 +0,0 @@
import { createBrowserHistory } from 'history';
import { InvokeCallback } from 'xstate';
import { ProjectGraphMachineEvents } from '../feature-projects/machines/interfaces';
function parseSearchParamsToEvents(
searchParams: string
): ProjectGraphMachineEvents[] {
const events: ProjectGraphMachineEvents[] = [];
const params = new URLSearchParams(searchParams);
params.forEach((value, key) => {
switch (key) {
case 'select':
if (value === 'all') {
events.push({ type: 'selectAll' });
} else if (value === 'affected') {
events.push({ type: 'selectAffected' });
}
break;
case 'focus':
events.push({ type: 'focusProject', projectName: value });
break;
case 'groupByFolder':
events.push({ type: 'setGroupByFolder', groupByFolder: true });
break;
case 'collapseEdges':
events.push({ type: 'setCollapseEdges', collapseEdges: true });
break;
case 'searchDepth':
const parsedValue = parseInt(value, 10);
if (parsedValue === 0) {
events.push({
type: 'setSearchDepthEnabled',
searchDepthEnabled: false,
});
} else if (parsedValue > 1) {
events.push({
type: 'setSearchDepth',
searchDepth: parseInt(value),
});
}
break;
case 'traceAlgorithm':
if (value === 'shortest' || value === 'all') {
// this needs to go before other tracing options or else the default of 'shortest' gets used
events.unshift({ type: 'setTracingAlgorithm', algorithm: value });
}
break;
case 'traceStart':
events.push({
type: 'setTracingStart',
projectName: value,
});
break;
case 'traceEnd':
events.push({ type: 'setTracingEnd', projectName: value });
break;
}
});
return events;
}
export const routeListener: InvokeCallback<
ProjectGraphMachineEvents,
ProjectGraphMachineEvents
> = (callback) => {
const history = createBrowserHistory();
parseSearchParamsToEvents(history.location.search).forEach((event) =>
callback(event)
);
};

View File

@ -1,166 +0,0 @@
import { assign } from '@xstate/immer';
import { createBrowserHistory } from 'history';
import { createMachine } from 'xstate';
import { RouteEvents } from './interfaces';
type ParamKeys =
| 'focus'
| 'groupByFolder'
| 'searchDepth'
| 'select'
| 'collapseEdges'
| 'traceStart'
| 'traceEnd'
| 'traceAlgorithm';
type ParamRecord = Record<ParamKeys, string | null>;
function reduceParamRecordToQueryString(params: ParamRecord): string {
const newParams = Object.entries(params).reduce((acc, [key, value]) => {
if (value !== null) {
acc[key] = value;
}
return acc;
}, {});
return new URLSearchParams(newParams).toString();
}
export interface RouteSetterContext {
currentParamString: string;
params: Record<ParamKeys, string | null>;
}
export const createRouteMachine = () => {
const history = createBrowserHistory();
const params = new URLSearchParams(history.location.search);
const paramRecord: ParamRecord = {
focus: params.get('focus'),
groupByFolder: params.get('groupByFolder'),
collapseEdges: params.get('collapseEdges'),
searchDepth: params.get('searchDepth'),
select: params.get('select'),
traceStart: params.get('traceStart'),
traceEnd: params.get('traceEnd'),
traceAlgorithm: params.get('traceAlgorithm'),
};
const initialContext: RouteSetterContext = {
currentParamString: reduceParamRecordToQueryString(paramRecord),
params: paramRecord,
};
return createMachine<RouteSetterContext, RouteEvents>(
{
predictableActionArguments: true,
id: 'route',
context: {
currentParamString: '',
params: {
focus: null,
groupByFolder: null,
searchDepth: null,
select: null,
collapseEdges: null,
traceStart: null,
traceEnd: null,
traceAlgorithm: null,
},
},
always: {
actions: assign((ctx) => {
const history = createBrowserHistory();
const newParamString = reduceParamRecordToQueryString(ctx.params);
history.push({
hash: history.location.hash,
search: newParamString,
});
ctx.currentParamString = newParamString;
}),
cond: 'didParamsChange',
},
on: {
notifyRouteSelectAll: {
actions: assign((ctx) => {
ctx.params.select = 'all';
ctx.params.focus = null;
}),
},
notifyRouteSelectAffected: {
actions: assign((ctx) => {
ctx.params.select = 'affected';
ctx.params.focus = null;
}),
},
notifyRouteClearSelect: {
actions: assign((ctx) => {
ctx.params.select = null;
}),
},
notifyRouteFocusProject: {
actions: assign((ctx, event) => {
ctx.params.focus = event.focusedProject;
ctx.params.select = null;
}),
},
notifyRouteUnfocusProject: {
actions: assign((ctx, event) => {
ctx.params.focus = null;
}),
},
notifyRouteGroupByFolder: {
actions: assign((ctx, event) => {
ctx.params.groupByFolder = event.groupByFolder ? 'true' : null;
}),
},
notifyRouteCollapseEdges: {
actions: assign((ctx, event) => {
ctx.params.collapseEdges = event.collapseEdges ? 'true' : null;
}),
},
notifyRouteSearchDepth: {
actions: assign((ctx, event) => {
if (event.searchDepthEnabled === false) {
ctx.params.searchDepth = '0';
} else if (event.searchDepthEnabled && event.searchDepth !== 1) {
ctx.params.searchDepth = event.searchDepth.toString();
} else {
ctx.params.searchDepth = null;
}
}),
},
notifyRouteTracing: {
actions: assign((ctx, event) => {
if (event.start !== null && event.end !== null && event.algorithm) {
ctx.params.traceStart = event.start;
ctx.params.traceEnd = event.end;
ctx.params.traceAlgorithm = event.algorithm;
ctx.params.focus = null;
ctx.params.select = null;
} else {
ctx.params.traceStart = null;
ctx.params.traceEnd = null;
ctx.params.traceAlgorithm = null;
}
}),
},
},
},
{
guards: {
didParamsChange: (ctx) => {
const cond =
ctx.currentParamString !==
reduceParamRecordToQueryString(ctx.params);
return cond;
},
},
}
).withContext(initialContext);
};

View File

@ -6,6 +6,7 @@ import { getEnvironmentConfig } from './hooks/use-environment-config';
// nx-ignore-next-line // nx-ignore-next-line
import { ProjectGraphClientResponse } from 'nx/src/command-line/dep-graph'; import { ProjectGraphClientResponse } from 'nx/src/command-line/dep-graph';
import { getProjectGraphDataService } from './hooks/get-project-graph-data-service'; import { getProjectGraphDataService } from './hooks/get-project-graph-data-service';
import { getProjectGraphService } from './machines/get-services';
const { appConfig } = getEnvironmentConfig(); const { appConfig } = getEnvironmentConfig();
const projectGraphDataService = getProjectGraphDataService(); const projectGraphDataService = getProjectGraphDataService();
@ -52,7 +53,32 @@ const taskDataLoader = async (selectedWorkspaceId: string) => {
const childRoutes: RouteObject[] = [ const childRoutes: RouteObject[] = [
{ {
path: 'projects', path: 'projects',
element: <ProjectsSidebar />, children: [
{
index: true,
element: <ProjectsSidebar />,
},
{
path: 'all',
element: <ProjectsSidebar />,
},
{
path: 'affected',
element: <ProjectsSidebar />,
},
{
path: ':focusedProject',
element: <ProjectsSidebar />,
},
{
path: 'trace/:startTrace',
element: <ProjectsSidebar />,
},
{
path: 'trace/:startTrace/:endTrace',
element: <ProjectsSidebar />,
},
],
}, },
{ {
loader: async ({ request, params }) => { loader: async ({ request, params }) => {

View File

@ -47,7 +47,9 @@ export function DebouncedTextInput({
} }
useEffect(() => { useEffect(() => {
updateTextFilter(debouncedValue); if (debouncedValue !== '') {
updateTextFilter(debouncedValue);
}
}, [debouncedValue, updateTextFilter]); }, [debouncedValue, updateTextFilter]);
return ( return (

View File

@ -1,19 +0,0 @@
import { Link, LinkProps, useSearchParams, To } from 'react-router-dom';
import React from 'react';
type LinkWithSearchParamsProps = LinkProps &
React.RefAttributes<HTMLAnchorElement>;
function LinkWithSearchParams(props: LinkWithSearchParamsProps) {
const [searchParams] = useSearchParams();
let to: To;
if (typeof props.to === 'object') {
to = { ...props.to, search: searchParams.toString() };
} else if (typeof props.to === 'string') {
to = { pathname: props.to, search: searchParams.toString() };
}
return <Link {...props} to={to} />;
}
export default LinkWithSearchParams;

View File

@ -1,7 +1,9 @@
import { getProjectGraphService } from '../machines/get-services'; import { getProjectGraphService } from '../machines/get-services';
import { FlagIcon, MapPinIcon } from '@heroicons/react/24/solid'; import { FlagIcon, MapPinIcon } from '@heroicons/react/24/solid';
import Tag from '../ui-components/tag'; import Tag from '../ui-components/tag';
import { TooltipButton } from './tooltip-button'; import { TooltipButton, TooltipLinkButton } from './tooltip-button';
import { useRouteConstructor } from '../util';
import { useNavigate } from 'react-router-dom';
export interface ProjectNodeToolTipProps { export interface ProjectNodeToolTipProps {
type: 'app' | 'lib' | 'e2e'; type: 'app' | 'lib' | 'e2e';
@ -15,33 +17,25 @@ export function ProjectNodeToolTip({
tags, tags,
}: ProjectNodeToolTipProps) { }: ProjectNodeToolTipProps) {
const projectGraphService = getProjectGraphService(); const projectGraphService = getProjectGraphService();
const { start, end, algorithm } =
function onFocus() { projectGraphService.getSnapshot().context.tracing;
projectGraphService.send({ const routeConstructor = useRouteConstructor();
type: 'focusProject', const navigate = useNavigate();
projectName: id,
});
}
function onExclude() { function onExclude() {
projectGraphService.send({ projectGraphService.send({
type: 'deselectProject', type: 'deselectProject',
projectName: id, projectName: id,
}); });
navigate(routeConstructor('/projects', true));
} }
function onStartTrace() { function onStartTrace() {
projectGraphService.send({ navigate(routeConstructor(`/projects/trace/${id}`, true));
type: 'setTracingStart',
projectName: id,
});
} }
function onEndTrace() { function onEndTrace() {
projectGraphService.send({ navigate(routeConstructor(`/projects/trace/${start}/${id}`, true));
type: 'setTracingEnd',
projectName: id,
});
} }
return ( return (
@ -58,22 +52,27 @@ export function ProjectNodeToolTip({
</p> </p>
) : null} ) : null}
<div className="flex"> <div className="flex">
<TooltipButton onClick={onFocus}>Focus</TooltipButton> <TooltipLinkButton to={routeConstructor(`/projects/${id}`, true)}>
Focus
</TooltipLinkButton>
<TooltipButton onClick={onExclude}>Exclude</TooltipButton> <TooltipButton onClick={onExclude}>Exclude</TooltipButton>
<TooltipButton {!start ? (
className="flex flex-row items-center" <TooltipButton
onClick={onStartTrace} className="flex flex-row items-center"
> onClick={onStartTrace}
<MapPinIcon className="mr-2 h-5 w-5 text-slate-500"></MapPinIcon> >
Start <MapPinIcon className="mr-2 h-5 w-5 text-slate-500"></MapPinIcon>
</TooltipButton> Start
<TooltipButton </TooltipButton>
className="flex flex-row items-center" ) : (
onClick={onEndTrace} <TooltipButton
> className="flex flex-row items-center"
<FlagIcon className="mr-2 h-5 w-5 text-slate-500"></FlagIcon> onClick={onEndTrace}
End >
</TooltipButton> <FlagIcon className="mr-2 h-5 w-5 text-slate-500"></FlagIcon>
End
</TooltipButton>
)}
</div> </div>
</div> </div>
); );

View File

@ -1,7 +1,6 @@
/* eslint-disable-next-line */ /* eslint-disable-next-line */
import { Link, LinkProps } from 'react-router-dom'; import { Link, LinkProps } from 'react-router-dom';
import { HTMLAttributes } from 'react'; import { HTMLAttributes } from 'react';
import LinkWithSearchParams from '../ui-components/link-with-current-search-params';
const sharedClasses = const sharedClasses =
'inline-flex items-center rounded-md border border-slate-300 bg-slate-50 py-2 px-4 mt-2 mr-2 text-slate-500 hover:bg-slate-100 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-300 hover:dark:bg-slate-700'; 'inline-flex items-center rounded-md border border-slate-300 bg-slate-50 py-2 px-4 mt-2 mr-2 text-slate-500 hover:bg-slate-100 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-300 hover:dark:bg-slate-700';
@ -25,12 +24,8 @@ export function TooltipLinkButton({
...rest ...rest
}: LinkProps) { }: LinkProps) {
return ( return (
<LinkWithSearchParams <Link className={`${sharedClasses} ${className}`} to={to} {...rest}>
className={`${sharedClasses} ${className}`}
to={to}
{...rest}
>
{children} {children}
</LinkWithSearchParams> </Link>
); );
} }

View File

@ -1,9 +1,43 @@
// nx-ignore-next-line // nx-ignore-next-line
import { ProjectGraphDependency, ProjectGraphNode } from '@nrwl/devkit'; import { ProjectGraphDependency, ProjectGraphNode } from '@nrwl/devkit';
import { getEnvironmentConfig } from './hooks/use-environment-config';
import { To, useParams, useSearchParams } from 'react-router-dom';
export function trimBackSlash(value: string): string { export const useRouteConstructor = (): ((
return value.replace(/\/$/, ''); to: To,
} retainSearchParams: true
) => To) => {
const { environment } = getEnvironmentConfig();
const { selectedWorkspaceId } = useParams();
const [searchParams] = useSearchParams();
return (to: To, retainSearchParams: true) => {
let pathname = '';
if (typeof to === 'object') {
if (environment === 'dev') {
pathname = `/${selectedWorkspaceId}${to.pathname}`;
} else {
pathname = to.pathname;
}
return {
...to,
pathname,
search: retainSearchParams ? searchParams.toString() : '',
};
} else if (typeof to === 'string') {
if (environment === 'dev') {
pathname = `/${selectedWorkspaceId}${to}`;
} else {
pathname = to;
}
return {
pathname,
search: retainSearchParams ? searchParams.toString() : '',
};
}
};
};
export function parseParentDirectoriesFromFilePath( export function parseParentDirectoriesFromFilePath(
path: string, path: string,

View File

@ -1423,83 +1423,6 @@
} }
} }
}, },
{
"name": "new-lib",
"type": "lib",
"data": {
"tags": [],
"root": "libs/new-lib",
"files": [
{
"file": "libs/new-lib/.eslintrc.json",
"hash": "69f0f5128f733f3ef756c81439f0548eb3f314ff"
},
{
"file": "libs/new-lib/jest.config.ts",
"hash": "4920380a6eaa2ffaa6ac16eafcccb60622ab0dc0"
},
{
"file": "libs/new-lib/project.json",
"hash": "12563cc2fb5447cd596a4a46766ac0e089c35e36"
},
{
"file": "libs/new-lib/README.md",
"hash": "ae28060a863433273a37b6a6818126817b405add"
},
{
"file": "libs/new-lib/src/index.ts",
"hash": "001f7c36454094a6f9a387e47b14445eab276fce"
},
{
"file": "libs/new-lib/src/lib/new-lib.module.ts",
"hash": "4936692048161ebc3cfbf0d146327a7b3c9de46e",
"deps": ["npm:@angular/core", "npm:@angular/common"]
},
{
"file": "libs/new-lib/src/test-setup.ts",
"hash": "1100b3e8a6ed08f4b5c27a96471846d57023c320",
"deps": ["npm:jest-preset-angular"]
},
{
"file": "libs/new-lib/tsconfig.json",
"hash": "1c995b83bf3715a370457c4296b1a11f40572cff"
},
{
"file": "libs/new-lib/tsconfig.lib.json",
"hash": "8e00439a4ac91e9d14eae4b313f59c1d435003ee"
},
{
"file": "libs/new-lib/tsconfig.spec.json",
"hash": "c5db02778f96a2a200d787c0a7b376fe0d6c36f7"
}
],
"targets": {
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/new-lib/jest.config.ts",
"passWithNoTests": true
},
"inputs": [
"default",
"^production",
"{workspaceRoot}/jest.preset.js"
]
},
"lint": {
"executor": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": [
"libs/new-lib/**/*.ts",
"libs/new-lib/**/*.html"
]
},
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"]
}
}
}
},
{ {
"name": "cart", "name": "cart",
"type": "app", "type": "app",
@ -1539,12 +1462,7 @@
{ {
"file": "apps/cart/src/app/app.tsx", "file": "apps/cart/src/app/app.tsx",
"hash": "e971864bdab1bea4c48550c2ab1d9e0e8489e753", "hash": "e971864bdab1bea4c48550c2ab1d9e0e8489e753",
"deps": [ "deps": ["npm:react-router-dom", "shared-header", "cart-cart-page"]
"npm:react-router-dom",
"new-lib",
"shared-header",
"cart-cart-page"
]
}, },
{ {
"file": "apps/cart/src/assets/.gitkeep", "file": "apps/cart/src/assets/.gitkeep",
@ -1858,7 +1776,6 @@
"type": "static" "type": "static"
} }
], ],
"new-lib": [],
"cart": [ "cart": [
{ {
"source": "cart", "source": "cart",
@ -1872,7 +1789,6 @@
}, },
{ {
"source": "cart", "source": "cart",
"target": "new-lib",
"type": "static" "type": "static"
}, },
{ {

View File

@ -1423,83 +1423,6 @@
} }
} }
}, },
{
"name": "new-lib",
"type": "lib",
"data": {
"tags": [],
"root": "libs/new-lib",
"files": [
{
"file": "libs/new-lib/.eslintrc.json",
"hash": "69f0f5128f733f3ef756c81439f0548eb3f314ff"
},
{
"file": "libs/new-lib/jest.config.ts",
"hash": "4920380a6eaa2ffaa6ac16eafcccb60622ab0dc0"
},
{
"file": "libs/new-lib/project.json",
"hash": "12563cc2fb5447cd596a4a46766ac0e089c35e36"
},
{
"file": "libs/new-lib/README.md",
"hash": "ae28060a863433273a37b6a6818126817b405add"
},
{
"file": "libs/new-lib/src/index.ts",
"hash": "001f7c36454094a6f9a387e47b14445eab276fce"
},
{
"file": "libs/new-lib/src/lib/new-lib.module.ts",
"hash": "4936692048161ebc3cfbf0d146327a7b3c9de46e",
"deps": ["npm:@angular/core", "npm:@angular/common"]
},
{
"file": "libs/new-lib/src/test-setup.ts",
"hash": "1100b3e8a6ed08f4b5c27a96471846d57023c320",
"deps": ["npm:jest-preset-angular"]
},
{
"file": "libs/new-lib/tsconfig.json",
"hash": "1c995b83bf3715a370457c4296b1a11f40572cff"
},
{
"file": "libs/new-lib/tsconfig.lib.json",
"hash": "8e00439a4ac91e9d14eae4b313f59c1d435003ee"
},
{
"file": "libs/new-lib/tsconfig.spec.json",
"hash": "c5db02778f96a2a200d787c0a7b376fe0d6c36f7"
}
],
"targets": {
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/new-lib/jest.config.ts",
"passWithNoTests": true
},
"inputs": [
"default",
"^production",
"{workspaceRoot}/jest.preset.js"
]
},
"lint": {
"executor": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": [
"libs/new-lib/**/*.ts",
"libs/new-lib/**/*.html"
]
},
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"]
}
}
}
},
{ {
"name": "cart", "name": "cart",
"type": "app", "type": "app",
@ -1539,12 +1462,7 @@
{ {
"file": "apps/cart/src/app/app.tsx", "file": "apps/cart/src/app/app.tsx",
"hash": "e971864bdab1bea4c48550c2ab1d9e0e8489e753", "hash": "e971864bdab1bea4c48550c2ab1d9e0e8489e753",
"deps": [ "deps": ["npm:react-router-dom", "shared-header", "cart-cart-page"]
"npm:react-router-dom",
"new-lib",
"shared-header",
"cart-cart-page"
]
}, },
{ {
"file": "apps/cart/src/assets/.gitkeep", "file": "apps/cart/src/assets/.gitkeep",
@ -1858,7 +1776,6 @@
"type": "static" "type": "static"
} }
], ],
"new-lib": [],
"cart": [ "cart": [
{ {
"source": "cart", "source": "cart",
@ -1872,7 +1789,6 @@
}, },
{ {
"source": "cart", "source": "cart",
"target": "new-lib",
"type": "static" "type": "static"
}, },
{ {

View File

@ -548,40 +548,6 @@
"products:deploy": [] "products:deploy": []
} }
}, },
"new-lib:test": {
"roots": ["new-lib:test"],
"tasks": {
"new-lib:test": {
"id": "new-lib:test",
"target": {
"project": "new-lib",
"target": "test"
},
"projectRoot": "libs/new-lib",
"overrides": {}
}
},
"dependencies": {
"new-lib:test": []
}
},
"new-lib:lint": {
"roots": ["new-lib:lint"],
"tasks": {
"new-lib:lint": {
"id": "new-lib:lint",
"target": {
"project": "new-lib",
"target": "lint"
},
"projectRoot": "libs/new-lib",
"overrides": {}
}
},
"dependencies": {
"new-lib:lint": []
}
},
"cart:build": { "cart:build": {
"roots": ["cart:build:production"], "roots": ["cart:build:production"],
"tasks": { "tasks": {

View File

@ -548,40 +548,6 @@
"products:deploy": [] "products:deploy": []
} }
}, },
"new-lib:test": {
"roots": ["new-lib:test"],
"tasks": {
"new-lib:test": {
"id": "new-lib:test",
"target": {
"project": "new-lib",
"target": "test"
},
"projectRoot": "libs/new-lib",
"overrides": {}
}
},
"dependencies": {
"new-lib:test": []
}
},
"new-lib:lint": {
"roots": ["new-lib:lint"],
"tasks": {
"new-lib:lint": {
"id": "new-lib:lint",
"target": {
"project": "new-lib",
"target": "lint"
},
"projectRoot": "libs/new-lib",
"overrides": {}
}
},
"dependencies": {
"new-lib:lint": []
}
},
"cart:build": { "cart:build": {
"roots": ["cart:build:production"], "roots": ["cart:build:production"],
"tasks": { "tasks": {

View File

@ -178,26 +178,40 @@ export class GraphService {
renderTime: 0, renderTime: 0,
}; };
if (this.renderGraph && elementsToSendToRender) { if (this.renderGraph) {
this.renderGraph.setElements(elementsToSendToRender); if (elementsToSendToRender) {
this.renderGraph.setElements(elementsToSendToRender);
if (event.type === 'notifyGraphFocusProject') { if (event.type === 'notifyGraphFocusProject') {
this.renderGraph.setFocussedElement(event.projectName); this.renderGraph.setFocussedElement(event.projectName);
}
const { numEdges, numNodes } = this.renderGraph.render();
selectedProjectNames = (
elementsToSendToRender.nodes('[type!="dir"]') ?? []
).map((node) => node.id());
const renderTime = Date.now() - time;
perfReport = {
renderTime,
numNodes,
numEdges,
};
} else {
const { numEdges, numNodes } = this.renderGraph.render();
this.renderGraph.getCurrentlyShownProjectIds();
const renderTime = Date.now() - time;
perfReport = {
renderTime,
numNodes,
numEdges,
};
} }
const { numEdges, numNodes } = this.renderGraph.render();
selectedProjectNames = (
elementsToSendToRender.nodes('[type!="dir"]') ?? []
).map((node) => node.id());
const renderTime = Date.now() - time;
perfReport = {
renderTime,
numNodes,
numEdges,
};
} }
this.lastPerformanceReport = perfReport; this.lastPerformanceReport = perfReport;

View File

@ -76,41 +76,6 @@ export type ProjectGraphRenderEvents =
algorithm: TracingAlgorithmType; algorithm: TracingAlgorithmType;
}; };
export type RouteEvents =
| {
type: 'notifyRouteFocusProject';
focusedProject: string;
}
| {
type: 'notifyRouteGroupByFolder';
groupByFolder: boolean;
}
| {
type: 'notifyRouteCollapseEdges';
collapseEdges: boolean;
}
| {
type: 'notifyRouteSearchDepth';
searchDepthEnabled: boolean;
searchDepth: number;
}
| {
type: 'notifyRouteUnfocusProject';
}
| {
type: 'notifyRouteSelectAll';
}
| {
type: 'notifyRouteSelectAffected';
}
| { type: 'notifyRouteClearSelect' }
| {
type: 'notifyRouteTracing';
start: string;
end: string;
algorithm: TracingAlgorithmType;
};
export type TaskGraphRecord = Record<string, TaskGraph>; export type TaskGraphRecord = Record<string, TaskGraph>;
export type TaskGraphRenderEvents = export type TaskGraphRenderEvents =
| { | {

View File

@ -397,18 +397,18 @@ async function startServer(
}); });
if (openBrowser) { if (openBrowser) {
let url = `http://${host}:${port}`; let url = `http://${host}:${port}/projects`;
let params = new URLSearchParams(); let params = new URLSearchParams();
if (focus) { if (focus) {
params.append('focus', focus); url += `/${focus}`;
} }
if (groupByFolder) { if (groupByFolder) {
params.append('groupByFolder', 'true'); params.append('groupByFolder', 'true');
} }
open(`${url}/projects?${params.toString()}`); open(`${url}?${params.toString()}`);
} }
} }