chore(graph): remove route listener and setter machines (#13524)
This commit is contained in:
parent
84cbcb7e10
commit
f1f7ccc25e
@ -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', () => {
|
||||
before(() => {
|
||||
cy.intercept('/assets/project-graphs/e2e.json', {
|
||||
|
||||
@ -215,6 +215,7 @@ describe('dev mode - project graph', () => {
|
||||
.map((dependencies) => dependencies.target)
|
||||
.includes('cart')
|
||||
);
|
||||
getUnfocusProjectButton().should('exist');
|
||||
getCheckedProjectItems().should(
|
||||
'have.length',
|
||||
['cart', ...dependencies, ...dependents].length
|
||||
@ -226,6 +227,7 @@ describe('dev mode - project graph', () => {
|
||||
it('should uncheck all project items', () => {
|
||||
getFocusButtonForProject('cart').click({ force: true });
|
||||
getUnfocusProjectButton().click();
|
||||
getUnfocusProjectButton().should('not.exist');
|
||||
|
||||
getCheckedProjectItems().should('have.length', 0);
|
||||
});
|
||||
@ -274,34 +276,52 @@ describe('dev mode - project graph', () => {
|
||||
it('should set focused project', () => {
|
||||
cy.contains('cart').scrollIntoView().should('be.visible');
|
||||
getFocusButtonForProject('cart').click({ force: true });
|
||||
|
||||
cy.url().should('contain', 'focus=cart');
|
||||
getUnfocusProjectButton().should('exist');
|
||||
cy.url().should('contain', '/projects/cart');
|
||||
cy.reload();
|
||||
getUnfocusProjectButton().should('exist');
|
||||
});
|
||||
|
||||
it('should set group by folder', () => {
|
||||
getGroupByFolderCheckbox().click();
|
||||
|
||||
getGroupByFolderCheckbox().should('be.checked');
|
||||
cy.url().should('contain', 'groupByFolder=true');
|
||||
cy.reload();
|
||||
getGroupByFolderCheckbox().should('be.checked');
|
||||
});
|
||||
|
||||
it('should set search depth disabled', () => {
|
||||
// it's on by default, clicking should disable it
|
||||
getSearchDepthCheckbox().click();
|
||||
|
||||
getSearchDepthCheckbox().should('not.be.checked');
|
||||
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's on by default and set to 1, clicking should change it to 2
|
||||
getSearchDepthIncrementButton().click();
|
||||
|
||||
cy.get('[data-cy="depth-value"]').should('contain', '2');
|
||||
cy.url().should('contain', 'searchDepth=2');
|
||||
cy.reload();
|
||||
cy.get('[data-cy="depth-value"]').should('contain', '2');
|
||||
});
|
||||
|
||||
it('should set select to all', () => {
|
||||
getSelectAllButton().click();
|
||||
|
||||
cy.url().should('contain', 'select=all');
|
||||
getCheckedProjectItems().should(
|
||||
'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');
|
||||
});
|
||||
|
||||
// check that params work from old base url of /
|
||||
// and also new /projects route
|
||||
testProjectsRoutes('browser', ['/', '/e2e/projects']);
|
||||
testProjectsRoutes('browser', ['/e2e/projects']);
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { testProjectsRoutes, testTaskRoutes } from '../support/routing-tests';
|
||||
|
||||
describe('release static-mode app', () => {
|
||||
describe('smoke tests', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/');
|
||||
});
|
||||
@ -13,11 +14,12 @@ describe('release static-mode app', () => {
|
||||
cy.get('debugger-panel').should('not.exist');
|
||||
});
|
||||
|
||||
describe('routing', () => {
|
||||
it('should use hash router', () => {
|
||||
cy.url().should('contain', '/#/projects');
|
||||
});
|
||||
});
|
||||
|
||||
testProjectsRoutes('hash', ['/', '/projects']);
|
||||
describe('routing', () => {
|
||||
testProjectsRoutes('hash', ['/projects']);
|
||||
});
|
||||
});
|
||||
|
||||
@ -25,7 +25,7 @@ function resolveProjectsRoute(
|
||||
paramString: string
|
||||
) {
|
||||
if (router === 'hash') {
|
||||
return `/?${paramString}#${route}`;
|
||||
return `/#${route}?${paramString}`;
|
||||
} else {
|
||||
return `${route}?${paramString}`;
|
||||
}
|
||||
@ -50,7 +50,7 @@ export function testProjectsRoutes(
|
||||
routes.forEach((route) => {
|
||||
describe(`for route ${route}`, () => {
|
||||
it('should focus projects', () => {
|
||||
cy.visit(resolveProjectsRoute(router, route, 'focus=cart'));
|
||||
cy.visit(resolveProjectsRoute(router, `${route}/cart`, ''));
|
||||
|
||||
// wait for first graph to finish loading
|
||||
waitForProjectGraph(router);
|
||||
@ -70,7 +70,7 @@ export function testProjectsRoutes(
|
||||
|
||||
it('should focus projects with search depth', () => {
|
||||
cy.visit(
|
||||
resolveProjectsRoute(router, route, `focus=cart&searchDepth=2`)
|
||||
resolveProjectsRoute(router, `${route}/cart`, `searchDepth=2`)
|
||||
);
|
||||
|
||||
// wait for first graph to finish loading
|
||||
@ -82,7 +82,7 @@ export function testProjectsRoutes(
|
||||
|
||||
it('should focus projects with search depth disabled', () => {
|
||||
cy.visit(
|
||||
resolveProjectsRoute(router, route, `focus=cart&searchDepth=0`)
|
||||
resolveProjectsRoute(router, `${route}/cart`, `searchDepth=0`)
|
||||
);
|
||||
|
||||
// wait for first graph to finish loading
|
||||
@ -94,11 +94,7 @@ export function testProjectsRoutes(
|
||||
|
||||
it('should set group by folder', () => {
|
||||
cy.visit(
|
||||
resolveProjectsRoute(
|
||||
router,
|
||||
route,
|
||||
`focus=nx-dev&searchDepth=1&groupByFolder=true`
|
||||
)
|
||||
resolveProjectsRoute(router, `${route}/cart`, `groupByFolder=true`)
|
||||
);
|
||||
|
||||
// wait for first graph to finish loading
|
||||
@ -108,7 +104,7 @@ export function testProjectsRoutes(
|
||||
});
|
||||
|
||||
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
|
||||
waitForProjectGraph(router);
|
||||
|
||||
@ -3,12 +3,6 @@ import { actions, send } from 'xstate';
|
||||
import { ProjectGraphStateNodeConfig } from './interfaces';
|
||||
|
||||
export const customSelectedStateConfig: ProjectGraphStateNodeConfig = {
|
||||
entry: actions.choose([
|
||||
{
|
||||
cond: 'selectActionCannotBePersistedToRoute',
|
||||
actions: ['notifyRouteClearSelect'],
|
||||
},
|
||||
]),
|
||||
on: {
|
||||
updateGraph: {
|
||||
target: 'customSelected',
|
||||
|
||||
@ -9,59 +9,28 @@ export const focusedStateConfig: ProjectGraphStateNodeConfig = {
|
||||
|
||||
ctx.focusedProject = event.projectName;
|
||||
}),
|
||||
send(
|
||||
(ctx, event) => {
|
||||
if (event.type !== 'focusProject') return;
|
||||
|
||||
return {
|
||||
type: 'notifyRouteFocusProject',
|
||||
focusedProject: event.projectName,
|
||||
};
|
||||
},
|
||||
{
|
||||
to: (context) => context.routeSetterActor,
|
||||
}
|
||||
),
|
||||
'notifyGraphFocusProject',
|
||||
],
|
||||
exit: [
|
||||
assign((ctx) => {
|
||||
ctx.focusedProject = null;
|
||||
}),
|
||||
'notifyRouteUnfocusProject',
|
||||
],
|
||||
on: {
|
||||
incrementSearchDepth: {
|
||||
actions: [
|
||||
'incrementSearchDepth',
|
||||
'notifyGraphFocusProject',
|
||||
'notifyRouteSearchDepth',
|
||||
],
|
||||
actions: ['incrementSearchDepth', 'notifyGraphFocusProject'],
|
||||
},
|
||||
decrementSearchDepth: {
|
||||
actions: [
|
||||
'decrementSearchDepth',
|
||||
'notifyGraphFocusProject',
|
||||
'notifyRouteSearchDepth',
|
||||
],
|
||||
actions: ['decrementSearchDepth', 'notifyGraphFocusProject'],
|
||||
},
|
||||
setSearchDepthEnabled: {
|
||||
actions: [
|
||||
'setSearchDepthEnabled',
|
||||
'notifyGraphFocusProject',
|
||||
'notifyRouteSearchDepth',
|
||||
],
|
||||
actions: ['setSearchDepthEnabled', 'notifyGraphFocusProject'],
|
||||
},
|
||||
setSearchDepth: {
|
||||
actions: [
|
||||
'setSearchDepth',
|
||||
'notifyGraphFocusProject',
|
||||
'notifyRouteSearchDepth',
|
||||
],
|
||||
actions: ['setSearchDepth', 'notifyGraphFocusProject'],
|
||||
},
|
||||
unfocusProject: {
|
||||
target: 'unselected',
|
||||
actions: ['notifyRouteUnfocusProject'],
|
||||
},
|
||||
updateGraph: {
|
||||
actions: [
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
ProjectGraphProjectNode,
|
||||
} from 'nx/src/config/project-graph';
|
||||
import { ActionObject, ActorRef, State, StateNodeConfig } from 'xstate';
|
||||
import { GraphRenderEvents, RouteEvents } from '../../machines/interfaces';
|
||||
import { GraphRenderEvents } from '../../machines/interfaces';
|
||||
|
||||
// The hierarchical schema for the states
|
||||
export interface ProjectGraphSchema {
|
||||
@ -85,8 +85,6 @@ export interface ProjectGraphContext {
|
||||
appsDir: string;
|
||||
};
|
||||
graphActor: ActorRef<GraphRenderEvents>;
|
||||
routeSetterActor: ActorRef<RouteEvents>;
|
||||
routeListenerActor: ActorRef<ProjectGraphMachineEvents>;
|
||||
lastPerfReport: GraphPerfReport;
|
||||
tracing: {
|
||||
start: string;
|
||||
|
||||
@ -3,7 +3,6 @@ import { createMachine, send, spawn } from 'xstate';
|
||||
import { customSelectedStateConfig } from './custom-selected.state';
|
||||
import { focusedStateConfig } from './focused.state';
|
||||
import { graphActor } from './graph.actor';
|
||||
import { createRouteMachine } from '../../machines/route-setter.machine';
|
||||
import { textFilteredStateConfig } from './text-filtered.state';
|
||||
import { tracingStateConfig } from './tracing.state';
|
||||
import { unselectedStateConfig } from './unselected.state';
|
||||
@ -26,8 +25,6 @@ export const initialContext: ProjectGraphContext = {
|
||||
appsDir: '',
|
||||
},
|
||||
graphActor: null,
|
||||
routeSetterActor: null,
|
||||
routeListenerActor: null,
|
||||
lastPerfReport: {
|
||||
numEdges: 0,
|
||||
numNodes: 0,
|
||||
@ -96,14 +93,11 @@ export const projectGraphMachine = createMachine<
|
||||
},
|
||||
selectAll: {
|
||||
target: 'customSelected',
|
||||
actions: ['notifyGraphShowAllProjects', 'notifyRouteSelectAll'],
|
||||
actions: ['notifyGraphShowAllProjects'],
|
||||
},
|
||||
selectAffected: {
|
||||
target: 'customSelected',
|
||||
actions: [
|
||||
'notifyGraphShowAffectedProjects',
|
||||
'notifyRouteSelectAffected',
|
||||
],
|
||||
actions: ['notifyGraphShowAffectedProjects'],
|
||||
},
|
||||
deselectProject: [
|
||||
{
|
||||
@ -155,19 +149,6 @@ export const projectGraphMachine = createMachine<
|
||||
to: (context) => context.graphActor,
|
||||
}
|
||||
),
|
||||
send(
|
||||
(ctx, event) => {
|
||||
if (event.type !== 'setCollapseEdges') return;
|
||||
|
||||
return {
|
||||
type: 'notifyRouteCollapseEdges',
|
||||
collapseEdges: event.collapseEdges,
|
||||
};
|
||||
},
|
||||
{
|
||||
to: (context) => context.routeSetterActor,
|
||||
}
|
||||
),
|
||||
],
|
||||
},
|
||||
setGroupByFolder: {
|
||||
@ -188,19 +169,6 @@ export const projectGraphMachine = createMachine<
|
||||
to: (context) => context.graphActor,
|
||||
}
|
||||
),
|
||||
send(
|
||||
(ctx, event) => {
|
||||
if (event.type !== 'setGroupByFolder') return;
|
||||
|
||||
return {
|
||||
type: 'notifyRouteGroupByFolder',
|
||||
groupByFolder: event.groupByFolder,
|
||||
};
|
||||
},
|
||||
{
|
||||
to: (context) => context.routeSetterActor,
|
||||
}
|
||||
),
|
||||
],
|
||||
},
|
||||
setIncludeProjectsByPath: {
|
||||
@ -211,23 +179,22 @@ export const projectGraphMachine = createMachine<
|
||||
],
|
||||
},
|
||||
incrementSearchDepth: {
|
||||
actions: ['incrementSearchDepth', 'notifyRouteSearchDepth'],
|
||||
actions: ['incrementSearchDepth'],
|
||||
},
|
||||
decrementSearchDepth: {
|
||||
actions: ['decrementSearchDepth', 'notifyRouteSearchDepth'],
|
||||
actions: ['decrementSearchDepth'],
|
||||
},
|
||||
setSearchDepthEnabled: {
|
||||
actions: ['setSearchDepthEnabled', 'notifyRouteSearchDepth'],
|
||||
actions: ['setSearchDepthEnabled'],
|
||||
},
|
||||
setSearchDepth: {
|
||||
actions: ['setSearchDepth', 'notifyRouteSearchDepth'],
|
||||
actions: ['setSearchDepth'],
|
||||
},
|
||||
setTracingAlgorithm: {
|
||||
actions: [
|
||||
assign((ctx, event) => {
|
||||
ctx.tracing.algorithm = event.algorithm;
|
||||
}),
|
||||
'notifyRouteTracing',
|
||||
'notifyGraphTracing',
|
||||
],
|
||||
},
|
||||
@ -241,9 +208,6 @@ export const projectGraphMachine = createMachine<
|
||||
deselectLastProject: (ctx) => {
|
||||
return ctx.selectedProjects.length <= 1;
|
||||
},
|
||||
selectActionCannotBePersistedToRoute: (ctx, event) => {
|
||||
return event.type !== 'selectAffected' && event.type !== 'selectAll';
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setGroupByFolder: assign((ctx, event) => {
|
||||
@ -286,9 +250,9 @@ export const projectGraphMachine = createMachine<
|
||||
ctx.projects = event.projects;
|
||||
ctx.dependencies = event.dependencies;
|
||||
ctx.graphActor = spawn(graphActor, 'graphActor');
|
||||
ctx.routeSetterActor = spawn(createRouteMachine(), {
|
||||
name: 'route',
|
||||
});
|
||||
// ctx.routeSetterActor = spawn(createRouteMachine(), {
|
||||
// name: 'route',
|
||||
// });
|
||||
|
||||
if (event.type === 'setProjects') {
|
||||
ctx.workspaceLayout = event.workspaceLayout;
|
||||
@ -388,62 +352,7 @@ export const projectGraphMachine = createMachine<
|
||||
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(
|
||||
(context, event) => ({
|
||||
type: 'notifyGraphFilterProjectsByText',
|
||||
|
||||
@ -10,7 +10,6 @@ export const tracingStateConfig: ProjectGraphStateNodeConfig = {
|
||||
ctx.tracing.end = event.projectName;
|
||||
}
|
||||
}),
|
||||
'notifyRouteTracing',
|
||||
'notifyGraphTracing',
|
||||
],
|
||||
exit: [
|
||||
@ -20,7 +19,6 @@ export const tracingStateConfig: ProjectGraphStateNodeConfig = {
|
||||
ctx.tracing.end = null;
|
||||
}
|
||||
}),
|
||||
'notifyRouteTracing',
|
||||
],
|
||||
on: {
|
||||
clearTraceStart: {
|
||||
@ -28,7 +26,6 @@ export const tracingStateConfig: ProjectGraphStateNodeConfig = {
|
||||
assign((ctx) => {
|
||||
ctx.tracing.start = null;
|
||||
}),
|
||||
'notifyRouteTracing',
|
||||
'notifyGraphTracing',
|
||||
],
|
||||
},
|
||||
@ -37,7 +34,6 @@ export const tracingStateConfig: ProjectGraphStateNodeConfig = {
|
||||
assign((ctx) => {
|
||||
ctx.tracing.end = null;
|
||||
}),
|
||||
'notifyRouteTracing',
|
||||
'notifyGraphTracing',
|
||||
],
|
||||
},
|
||||
|
||||
@ -1,18 +1,9 @@
|
||||
import { assign } from '@xstate/immer';
|
||||
import { send, spawn } from 'xstate';
|
||||
import { routeListener } from '../../machines/route-listener.actor';
|
||||
import { send } from 'xstate';
|
||||
import { ProjectGraphStateNodeConfig } from './interfaces';
|
||||
|
||||
export const unselectedStateConfig: ProjectGraphStateNodeConfig = {
|
||||
entry: [
|
||||
'notifyGraphHideAllProjects',
|
||||
assign((ctx, event) => {
|
||||
if (ctx.routeListenerActor === null) {
|
||||
ctx.routeListenerActor = spawn(routeListener, 'routeListener');
|
||||
}
|
||||
}),
|
||||
'notifyRouteClearSelect',
|
||||
],
|
||||
entry: ['notifyGraphHideAllProjects'],
|
||||
on: {
|
||||
updateGraph: {
|
||||
target: 'customSelected',
|
||||
|
||||
@ -57,6 +57,7 @@ export const SearchDepth = memo(
|
||||
</button>
|
||||
<span
|
||||
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"
|
||||
>
|
||||
{searchDepth}
|
||||
|
||||
@ -13,10 +13,15 @@ import {
|
||||
selectedProjectNamesSelector,
|
||||
workspaceLayoutSelector,
|
||||
} from './machines/selectors';
|
||||
import { getProjectsByType, parseParentDirectoriesFromFilePath } from '../util';
|
||||
import {
|
||||
getProjectsByType,
|
||||
parseParentDirectoriesFromFilePath,
|
||||
useRouteConstructor,
|
||||
} from '../util';
|
||||
import ExperimentalFeature from '../ui-components/experimental-feature';
|
||||
import { TracingAlgorithmType } from './machines/interfaces';
|
||||
import { getProjectGraphService } from '../machines/get-services';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
interface SidebarProject {
|
||||
projectGraphNode: ProjectGraphNode;
|
||||
@ -70,6 +75,8 @@ function ProjectListItem({
|
||||
tracingInfo: TracingInfo;
|
||||
}) {
|
||||
const projectGraphService = getProjectGraphService();
|
||||
const navigate = useNavigate();
|
||||
const routeConstructor = useRouteConstructor();
|
||||
|
||||
function startTrace(projectName: string) {
|
||||
projectGraphService.send({ type: 'setTracingStart', projectName });
|
||||
@ -85,24 +92,23 @@ function ProjectListItem({
|
||||
} else {
|
||||
projectGraphService.send({ type: 'selectProject', projectName });
|
||||
}
|
||||
}
|
||||
|
||||
function focusProject(projectName: string) {
|
||||
projectGraphService.send({ type: 'focusProject', projectName });
|
||||
navigate(routeConstructor('/projects', true));
|
||||
}
|
||||
|
||||
return (
|
||||
<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">
|
||||
<button
|
||||
<Link
|
||||
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"
|
||||
title="Focus on this library"
|
||||
onClick={() => focusProject(project.projectGraphNode.name)}
|
||||
to={routeConstructor(
|
||||
`/projects/${project.projectGraphNode.name}`,
|
||||
true
|
||||
)}
|
||||
>
|
||||
<DocumentMagnifyingGlassIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
<ExperimentalFeature>
|
||||
<span className="relative z-0 inline-flex rounded-md shadow-sm">
|
||||
|
||||
@ -25,8 +25,15 @@ 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 {
|
||||
useNavigate,
|
||||
useParams,
|
||||
useRouteLoaderData,
|
||||
useSearchParams,
|
||||
} from 'react-router-dom';
|
||||
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 {
|
||||
const environmentConfig = useEnvironmentConfig();
|
||||
@ -41,58 +48,106 @@ export function ProjectsSidebar(): JSX.Element {
|
||||
const groupByFolder = useProjectGraphSelector(groupByFolderSelector);
|
||||
const collapseEdges = useProjectGraphSelector(collapseEdgesSelector);
|
||||
|
||||
const isTracing = projectGraphService.state.matches('tracing');
|
||||
const isTracing = projectGraphService.getSnapshot().matches('tracing');
|
||||
const tracingInfo = useProjectGraphSelector(getTracingInfo);
|
||||
const projectGraphDataService = getProjectGraphDataService();
|
||||
|
||||
const routeParams = useParams();
|
||||
const currentRoute = useCurrentPath();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const selectedProjectRouteData = useRouteLoaderData(
|
||||
'selectedWorkspace'
|
||||
) as ProjectGraphClientResponse;
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const routeContructor = useRouteConstructor();
|
||||
|
||||
function resetFocus() {
|
||||
projectGraphService.send({ type: 'unfocusProject' });
|
||||
navigate(routeContructor('/projects', true));
|
||||
}
|
||||
|
||||
function showAllProjects() {
|
||||
projectGraphService.send({ type: 'selectAll' });
|
||||
navigate(routeContructor('/projects/all', true));
|
||||
}
|
||||
|
||||
function hideAllProjects() {
|
||||
projectGraphService.send({ type: 'deselectAll' });
|
||||
navigate(routeContructor('/projects', true));
|
||||
}
|
||||
|
||||
function showAffectedProjects() {
|
||||
projectGraphService.send({ type: 'selectAffected' });
|
||||
navigate(routeContructor('/projects/affected', true));
|
||||
}
|
||||
|
||||
function searchDepthFilterEnabledChange(checked: boolean) {
|
||||
projectGraphService.send({
|
||||
type: 'setSearchDepthEnabled',
|
||||
searchDepthEnabled: checked,
|
||||
setSearchParams((currentSearchParams) => {
|
||||
if (checked && searchDepthInfo.searchDepth > 1) {
|
||||
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) {
|
||||
projectGraphService.send({
|
||||
type: 'setGroupByFolder',
|
||||
groupByFolder: checked,
|
||||
setSearchParams((currentSearchParams) => {
|
||||
if (checked) {
|
||||
currentSearchParams.set('groupByFolder', 'true');
|
||||
} else {
|
||||
currentSearchParams.delete('groupByFolder');
|
||||
}
|
||||
return currentSearchParams;
|
||||
});
|
||||
}
|
||||
|
||||
function collapseEdgesChanged(checked: boolean) {
|
||||
projectGraphService.send({
|
||||
type: 'setCollapseEdges',
|
||||
collapseEdges: checked,
|
||||
setSearchParams((currentSearchParams) => {
|
||||
if (checked) {
|
||||
currentSearchParams.set('collapseEdges', 'true');
|
||||
} else {
|
||||
currentSearchParams.delete('collapseEdges');
|
||||
}
|
||||
return currentSearchParams;
|
||||
});
|
||||
}
|
||||
|
||||
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() {
|
||||
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() {
|
||||
@ -108,16 +163,19 @@ export function ProjectsSidebar(): JSX.Element {
|
||||
|
||||
function resetTraceStart() {
|
||||
projectGraphService.send({ type: 'clearTraceStart' });
|
||||
navigate(routeContructor('/projects', true));
|
||||
}
|
||||
|
||||
function resetTraceEnd() {
|
||||
projectGraphService.send({ type: 'clearTraceEnd' });
|
||||
navigate(routeContructor('/projects', true));
|
||||
}
|
||||
|
||||
function setAlgorithm(algorithm: TracingAlgorithmType) {
|
||||
projectGraphService.send({
|
||||
type: 'setTracingAlgorithm',
|
||||
algorithm: algorithm,
|
||||
setSearchParams((searchParams) => {
|
||||
searchParams.set('traceAlgorithm', algorithm);
|
||||
|
||||
return searchParams;
|
||||
});
|
||||
}
|
||||
|
||||
@ -131,6 +189,108 @@ export function ProjectsSidebar(): JSX.Element {
|
||||
});
|
||||
}, [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(
|
||||
() => {
|
||||
const selectedWorkspaceId =
|
||||
@ -163,6 +323,7 @@ export function ProjectsSidebar(): JSX.Element {
|
||||
const updateTextFilter = useCallback(
|
||||
(textFilter: string) => {
|
||||
projectGraphService.send({ type: 'filterByText', search: textFilter });
|
||||
navigate(routeContructor('/projects', true));
|
||||
},
|
||||
[projectGraphService]
|
||||
);
|
||||
|
||||
@ -38,16 +38,16 @@ export function TasksSidebar() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const groupByProject = searchParams.get('groupByProject') === 'true';
|
||||
|
||||
const selectedProjectRouteData = useRouteLoaderData(
|
||||
const selectedWorkspaceRouteData = useRouteLoaderData(
|
||||
'selectedWorkspace'
|
||||
) as ProjectGraphClientResponse & { targets: string[] };
|
||||
const workspaceLayout = selectedProjectRouteData.layout;
|
||||
const workspaceLayout = selectedWorkspaceRouteData.layout;
|
||||
|
||||
const routeData = useRouteLoaderData(
|
||||
'selectedTarget'
|
||||
) as TaskGraphClientResponse;
|
||||
const { taskGraphs } = routeData;
|
||||
const { projects, targets } = selectedProjectRouteData;
|
||||
const { projects, targets } = selectedWorkspaceRouteData;
|
||||
const selectedTarget = params['selectedTarget'] ?? targets[0];
|
||||
|
||||
const [selectedProjects, setSelectedProjects] = useState<string[]>([]);
|
||||
@ -131,10 +131,10 @@ export function TasksSidebar() {
|
||||
setSelectedProjects([]);
|
||||
graphService.handleTaskEvent({
|
||||
type: 'notifyTaskGraphSetProjects',
|
||||
projects: selectedProjectRouteData.projects,
|
||||
projects: selectedWorkspaceRouteData.projects,
|
||||
taskGraphs,
|
||||
});
|
||||
}, [selectedProjectRouteData]);
|
||||
}, [selectedWorkspaceRouteData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (groupByProject) {
|
||||
|
||||
@ -1,20 +1,34 @@
|
||||
import { matchRoutes, useLocation } from 'react-router-dom';
|
||||
import { getRoutesForEnvironment } from '../routes';
|
||||
import { getEnvironmentConfig } from './use-environment-config';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const useCurrentPath = () => {
|
||||
const [lastLocation, setLastLocation] = useState<string>();
|
||||
const [lastPath, setLastPath] = useState();
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
if (location.pathname === lastLocation) {
|
||||
return lastPath;
|
||||
}
|
||||
|
||||
setLastLocation(location.pathname);
|
||||
|
||||
const route = matchRoutes(getRoutesForEnvironment(), location).at(-1);
|
||||
|
||||
const { environment } = getEnvironmentConfig();
|
||||
|
||||
let currentPath;
|
||||
// if using dev routes, remove first segment for workspace
|
||||
if (environment === 'dev') {
|
||||
return {
|
||||
currentPath = {
|
||||
workspace: route.pathname.split('/')[1],
|
||||
currentPath: `/${route.pathname.split('/').slice(2).join('/')}`,
|
||||
};
|
||||
} else {
|
||||
return { workspace: 'local', currentPath: route.pathname };
|
||||
currentPath = { workspace: 'local', currentPath: route.pathname };
|
||||
}
|
||||
setLastPath(currentPath);
|
||||
return currentPath;
|
||||
};
|
||||
|
||||
@ -66,38 +66,3 @@ export type GraphRenderEvents =
|
||||
end: string;
|
||||
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;
|
||||
};
|
||||
|
||||
@ -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)
|
||||
);
|
||||
};
|
||||
@ -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);
|
||||
};
|
||||
@ -6,6 +6,7 @@ import { getEnvironmentConfig } from './hooks/use-environment-config';
|
||||
// nx-ignore-next-line
|
||||
import { ProjectGraphClientResponse } from 'nx/src/command-line/dep-graph';
|
||||
import { getProjectGraphDataService } from './hooks/get-project-graph-data-service';
|
||||
import { getProjectGraphService } from './machines/get-services';
|
||||
|
||||
const { appConfig } = getEnvironmentConfig();
|
||||
const projectGraphDataService = getProjectGraphDataService();
|
||||
@ -52,8 +53,33 @@ const taskDataLoader = async (selectedWorkspaceId: string) => {
|
||||
const childRoutes: RouteObject[] = [
|
||||
{
|
||||
path: 'projects',
|
||||
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 }) => {
|
||||
const selectedWorkspaceId =
|
||||
|
||||
@ -47,7 +47,9 @@ export function DebouncedTextInput({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedValue !== '') {
|
||||
updateTextFilter(debouncedValue);
|
||||
}
|
||||
}, [debouncedValue, updateTextFilter]);
|
||||
|
||||
return (
|
||||
|
||||
@ -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;
|
||||
@ -1,7 +1,9 @@
|
||||
import { getProjectGraphService } from '../machines/get-services';
|
||||
import { FlagIcon, MapPinIcon } from '@heroicons/react/24/solid';
|
||||
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 {
|
||||
type: 'app' | 'lib' | 'e2e';
|
||||
@ -15,33 +17,25 @@ export function ProjectNodeToolTip({
|
||||
tags,
|
||||
}: ProjectNodeToolTipProps) {
|
||||
const projectGraphService = getProjectGraphService();
|
||||
|
||||
function onFocus() {
|
||||
projectGraphService.send({
|
||||
type: 'focusProject',
|
||||
projectName: id,
|
||||
});
|
||||
}
|
||||
const { start, end, algorithm } =
|
||||
projectGraphService.getSnapshot().context.tracing;
|
||||
const routeConstructor = useRouteConstructor();
|
||||
const navigate = useNavigate();
|
||||
|
||||
function onExclude() {
|
||||
projectGraphService.send({
|
||||
type: 'deselectProject',
|
||||
projectName: id,
|
||||
});
|
||||
navigate(routeConstructor('/projects', true));
|
||||
}
|
||||
|
||||
function onStartTrace() {
|
||||
projectGraphService.send({
|
||||
type: 'setTracingStart',
|
||||
projectName: id,
|
||||
});
|
||||
navigate(routeConstructor(`/projects/trace/${id}`, true));
|
||||
}
|
||||
|
||||
function onEndTrace() {
|
||||
projectGraphService.send({
|
||||
type: 'setTracingEnd',
|
||||
projectName: id,
|
||||
});
|
||||
navigate(routeConstructor(`/projects/trace/${start}/${id}`, true));
|
||||
}
|
||||
|
||||
return (
|
||||
@ -58,8 +52,11 @@ export function ProjectNodeToolTip({
|
||||
</p>
|
||||
) : null}
|
||||
<div className="flex">
|
||||
<TooltipButton onClick={onFocus}>Focus</TooltipButton>
|
||||
<TooltipLinkButton to={routeConstructor(`/projects/${id}`, true)}>
|
||||
Focus
|
||||
</TooltipLinkButton>
|
||||
<TooltipButton onClick={onExclude}>Exclude</TooltipButton>
|
||||
{!start ? (
|
||||
<TooltipButton
|
||||
className="flex flex-row items-center"
|
||||
onClick={onStartTrace}
|
||||
@ -67,6 +64,7 @@ export function ProjectNodeToolTip({
|
||||
<MapPinIcon className="mr-2 h-5 w-5 text-slate-500"></MapPinIcon>
|
||||
Start
|
||||
</TooltipButton>
|
||||
) : (
|
||||
<TooltipButton
|
||||
className="flex flex-row items-center"
|
||||
onClick={onEndTrace}
|
||||
@ -74,6 +72,7 @@ export function ProjectNodeToolTip({
|
||||
<FlagIcon className="mr-2 h-5 w-5 text-slate-500"></FlagIcon>
|
||||
End
|
||||
</TooltipButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
/* eslint-disable-next-line */
|
||||
import { Link, LinkProps } from 'react-router-dom';
|
||||
import { HTMLAttributes } from 'react';
|
||||
import LinkWithSearchParams from '../ui-components/link-with-current-search-params';
|
||||
|
||||
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';
|
||||
@ -25,12 +24,8 @@ export function TooltipLinkButton({
|
||||
...rest
|
||||
}: LinkProps) {
|
||||
return (
|
||||
<LinkWithSearchParams
|
||||
className={`${sharedClasses} ${className}`}
|
||||
to={to}
|
||||
{...rest}
|
||||
>
|
||||
<Link className={`${sharedClasses} ${className}`} to={to} {...rest}>
|
||||
{children}
|
||||
</LinkWithSearchParams>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,9 +1,43 @@
|
||||
// nx-ignore-next-line
|
||||
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 {
|
||||
return value.replace(/\/$/, '');
|
||||
export const useRouteConstructor = (): ((
|
||||
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(
|
||||
path: string,
|
||||
|
||||
@ -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",
|
||||
"type": "app",
|
||||
@ -1539,12 +1462,7 @@
|
||||
{
|
||||
"file": "apps/cart/src/app/app.tsx",
|
||||
"hash": "e971864bdab1bea4c48550c2ab1d9e0e8489e753",
|
||||
"deps": [
|
||||
"npm:react-router-dom",
|
||||
"new-lib",
|
||||
"shared-header",
|
||||
"cart-cart-page"
|
||||
]
|
||||
"deps": ["npm:react-router-dom", "shared-header", "cart-cart-page"]
|
||||
},
|
||||
{
|
||||
"file": "apps/cart/src/assets/.gitkeep",
|
||||
@ -1858,7 +1776,6 @@
|
||||
"type": "static"
|
||||
}
|
||||
],
|
||||
"new-lib": [],
|
||||
"cart": [
|
||||
{
|
||||
"source": "cart",
|
||||
@ -1872,7 +1789,6 @@
|
||||
},
|
||||
{
|
||||
"source": "cart",
|
||||
"target": "new-lib",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
|
||||
@ -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",
|
||||
"type": "app",
|
||||
@ -1539,12 +1462,7 @@
|
||||
{
|
||||
"file": "apps/cart/src/app/app.tsx",
|
||||
"hash": "e971864bdab1bea4c48550c2ab1d9e0e8489e753",
|
||||
"deps": [
|
||||
"npm:react-router-dom",
|
||||
"new-lib",
|
||||
"shared-header",
|
||||
"cart-cart-page"
|
||||
]
|
||||
"deps": ["npm:react-router-dom", "shared-header", "cart-cart-page"]
|
||||
},
|
||||
{
|
||||
"file": "apps/cart/src/assets/.gitkeep",
|
||||
@ -1858,7 +1776,6 @@
|
||||
"type": "static"
|
||||
}
|
||||
],
|
||||
"new-lib": [],
|
||||
"cart": [
|
||||
{
|
||||
"source": "cart",
|
||||
@ -1872,7 +1789,6 @@
|
||||
},
|
||||
{
|
||||
"source": "cart",
|
||||
"target": "new-lib",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
|
||||
@ -548,40 +548,6 @@
|
||||
"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": {
|
||||
"roots": ["cart:build:production"],
|
||||
"tasks": {
|
||||
|
||||
@ -548,40 +548,6 @@
|
||||
"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": {
|
||||
"roots": ["cart:build:production"],
|
||||
"tasks": {
|
||||
|
||||
@ -178,7 +178,8 @@ export class GraphService {
|
||||
renderTime: 0,
|
||||
};
|
||||
|
||||
if (this.renderGraph && elementsToSendToRender) {
|
||||
if (this.renderGraph) {
|
||||
if (elementsToSendToRender) {
|
||||
this.renderGraph.setElements(elementsToSendToRender);
|
||||
|
||||
if (event.type === 'notifyGraphFocusProject') {
|
||||
@ -198,6 +199,19 @@ export class GraphService {
|
||||
numNodes,
|
||||
numEdges,
|
||||
};
|
||||
} else {
|
||||
const { numEdges, numNodes } = this.renderGraph.render();
|
||||
|
||||
this.renderGraph.getCurrentlyShownProjectIds();
|
||||
|
||||
const renderTime = Date.now() - time;
|
||||
|
||||
perfReport = {
|
||||
renderTime,
|
||||
numNodes,
|
||||
numEdges,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.lastPerformanceReport = perfReport;
|
||||
|
||||
@ -76,41 +76,6 @@ export type ProjectGraphRenderEvents =
|
||||
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 TaskGraphRenderEvents =
|
||||
| {
|
||||
|
||||
@ -397,18 +397,18 @@ async function startServer(
|
||||
});
|
||||
|
||||
if (openBrowser) {
|
||||
let url = `http://${host}:${port}`;
|
||||
let url = `http://${host}:${port}/projects`;
|
||||
let params = new URLSearchParams();
|
||||
|
||||
if (focus) {
|
||||
params.append('focus', focus);
|
||||
url += `/${focus}`;
|
||||
}
|
||||
|
||||
if (groupByFolder) {
|
||||
params.append('groupByFolder', 'true');
|
||||
}
|
||||
|
||||
open(`${url}/projects?${params.toString()}`);
|
||||
open(`${url}?${params.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user