feat(dep-graph): add experimental support for finding path between projects (#9643)
* feat(dep-graph): add experimental support for tracing paths between projects * feat(graph): add algorithm for finding all paths between projects * cleanup(dep-graph): clean-up edge tooltip * cleanup(dep-graph): fix watch mode dev environment
This commit is contained in:
parent
05a9544806
commit
dbe942c9c0
@ -55,12 +55,12 @@ describe('dep-graph-client', () => {
|
|||||||
|
|
||||||
it('should filter projects', () => {
|
it('should filter projects', () => {
|
||||||
getTextFilterInput().type('nx-dev');
|
getTextFilterInput().type('nx-dev');
|
||||||
getCheckedProjectItems().should('have.length', 9);
|
getCheckedProjectItems().should('have.length', 15);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear selection on reset', () => {
|
it('should clear selection on reset', () => {
|
||||||
getTextFilterInput().type('nx-dev');
|
getTextFilterInput().type('nx-dev');
|
||||||
getCheckedProjectItems().should('have.length', 9);
|
getCheckedProjectItems().should('have.length', 15);
|
||||||
getTextFilterReset().click();
|
getTextFilterReset().click();
|
||||||
getCheckedProjectItems().should('have.length', 0);
|
getCheckedProjectItems().should('have.length', 0);
|
||||||
});
|
});
|
||||||
@ -68,7 +68,7 @@ describe('dep-graph-client', () => {
|
|||||||
|
|
||||||
describe('selecting a different project', () => {
|
describe('selecting a different project', () => {
|
||||||
it('should change the available projects', () => {
|
it('should change the available projects', () => {
|
||||||
getProjectItems().should('have.length', 53);
|
getProjectItems().should('have.length', 62);
|
||||||
cy.get('[data-cy=project-select]').select('Ocean', { force: true });
|
cy.get('[data-cy=project-select]').select('Ocean', { force: true });
|
||||||
getProjectItems().should('have.length', 124);
|
getProjectItems().should('have.length', 124);
|
||||||
});
|
});
|
||||||
@ -77,14 +77,14 @@ describe('dep-graph-client', () => {
|
|||||||
describe('select all button', () => {
|
describe('select all button', () => {
|
||||||
it('should check all project items', () => {
|
it('should check all project items', () => {
|
||||||
getSelectAllButton().scrollIntoView().click({ force: true });
|
getSelectAllButton().scrollIntoView().click({ force: true });
|
||||||
getCheckedProjectItems().should('have.length', 53);
|
getCheckedProjectItems().should('have.length', 62);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deselect all button', () => {
|
describe('deselect all button', () => {
|
||||||
it('should uncheck all project items', () => {
|
it('should uncheck all project items', () => {
|
||||||
getDeselectAllButton().click();
|
getDeselectAllButton().click();
|
||||||
getUncheckedProjectItems().should('have.length', 53);
|
getUncheckedProjectItems().should('have.length', 62);
|
||||||
getSelectProjectsMessage().should('be.visible');
|
getSelectProjectsMessage().should('be.visible');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -147,7 +147,7 @@ describe('dep-graph-client', () => {
|
|||||||
cy.contains('nx-dev').scrollIntoView().should('be.visible');
|
cy.contains('nx-dev').scrollIntoView().should('be.visible');
|
||||||
cy.get('[data-project="nx-dev"]').prev('button').click({ force: true });
|
cy.get('[data-project="nx-dev"]').prev('button').click({ force: true });
|
||||||
|
|
||||||
getCheckedProjectItems().should('have.length', 10);
|
getCheckedProjectItems().should('have.length', 15);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ describe('dep-graph-client', () => {
|
|||||||
cy.get('[data-project="nx-dev"]').prev('button').click({ force: true });
|
cy.get('[data-project="nx-dev"]').prev('button').click({ force: true });
|
||||||
getUnfocusProjectButton().click();
|
getUnfocusProjectButton().click();
|
||||||
|
|
||||||
getUncheckedProjectItems().should('have.length', 53);
|
getUncheckedProjectItems().should('have.length', 62);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -164,14 +164,14 @@ describe('dep-graph-client', () => {
|
|||||||
it('should filter projects by text when pressing enter', () => {
|
it('should filter projects by text when pressing enter', () => {
|
||||||
getTextFilterInput().type('nx-dev{enter}');
|
getTextFilterInput().type('nx-dev{enter}');
|
||||||
|
|
||||||
getCheckedProjectItems().should('have.length', 9);
|
getCheckedProjectItems().should('have.length', 15);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include projects in path when option is checked', () => {
|
it('should include projects in path when option is checked', () => {
|
||||||
getTextFilterInput().type('nx-dev');
|
getTextFilterInput().type('nx-dev');
|
||||||
getIncludeProjectsInPathButton().click();
|
getIncludeProjectsInPathButton().click();
|
||||||
|
|
||||||
getCheckedProjectItems().should('have.length', 17);
|
getCheckedProjectItems().should('have.length', 24);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -229,7 +229,7 @@ describe('loading dep-graph client with url params', () => {
|
|||||||
// wait for first graph to finish loading
|
// wait for first graph to finish loading
|
||||||
cy.wait('@getGraph');
|
cy.wait('@getGraph');
|
||||||
|
|
||||||
getCheckedProjectItems().should('have.length', 10);
|
getCheckedProjectItems().should('have.length', 15);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should focus projects with search depth', () => {
|
it('should focus projects with search depth', () => {
|
||||||
@ -240,7 +240,7 @@ describe('loading dep-graph client with url params', () => {
|
|||||||
// wait for first graph to finish loading
|
// wait for first graph to finish loading
|
||||||
cy.wait('@getGraph');
|
cy.wait('@getGraph');
|
||||||
|
|
||||||
getCheckedProjectItems().should('have.length', 8);
|
getCheckedProjectItems().should('have.length', 11);
|
||||||
getSearchDepthCheckbox().should('exist');
|
getSearchDepthCheckbox().should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -263,7 +263,7 @@ describe('loading dep-graph client with url params', () => {
|
|||||||
// wait for first graph to finish loading
|
// wait for first graph to finish loading
|
||||||
cy.wait('@getGraph');
|
cy.wait('@getGraph');
|
||||||
|
|
||||||
getCheckedProjectItems().should('have.length', 53);
|
getCheckedProjectItems().should('have.length', 62);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
39
dep-graph/client/src/app/edge-tooltip.tsx
Normal file
39
dep-graph/client/src/app/edge-tooltip.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
export interface EdgeNodeTooltipProps {
|
||||||
|
type: 'static' | 'dynamic' | 'implicit';
|
||||||
|
source: string;
|
||||||
|
target: string;
|
||||||
|
fileDependencies: Array<{ fileName: string }>;
|
||||||
|
}
|
||||||
|
function EdgeNodeTooltip({
|
||||||
|
type,
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
fileDependencies,
|
||||||
|
}: EdgeNodeTooltipProps) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h4 className={type !== 'implicit' ? 'mb-3' : ''}>
|
||||||
|
<span className="tag">{type ?? 'unknown'}</span>
|
||||||
|
{source} → {target}
|
||||||
|
</h4>
|
||||||
|
{type !== 'implicit' ? (
|
||||||
|
<div className="rounded-md border border-gray-200">
|
||||||
|
<div className="rounded-t-md bg-gray-50 px-4 py-2 text-xs font-medium uppercase text-gray-500">
|
||||||
|
<span>Files</span>
|
||||||
|
</div>
|
||||||
|
<ul className="max-h-[300px] divide-y divide-gray-200 overflow-auto">
|
||||||
|
{fileDependencies.map((fileDep) => (
|
||||||
|
<li className="dark:text-sidebar-text-dark whitespace-nowrap px-4 py-2 text-sm font-medium text-gray-900">
|
||||||
|
<span className="block truncate font-normal">
|
||||||
|
{fileDep.fileName}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EdgeNodeTooltip;
|
||||||
18
dep-graph/client/src/app/icons/flag.tsx
Normal file
18
dep-graph/client/src/app/icons/flag.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
function Flag(props) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M3 6a3 3 0 013-3h10a1 1 0 01.8 1.6L14.25 8l2.55 3.4A1 1 0 0116 13H6a1 1 0 00-1 1v3a1 1 0 11-2 0V6z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Flag;
|
||||||
14
dep-graph/client/src/app/icons/map-marker.tsx
Normal file
14
dep-graph/client/src/app/icons/map-marker.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
function MapMarker(props) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MapMarker;
|
||||||
20
dep-graph/client/src/app/icons/x-circle-outline.tsx
Normal file
20
dep-graph/client/src/app/icons/x-circle-outline.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
function XCircleOutline(props) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default XCircleOutline;
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
} from './interfaces';
|
} from './interfaces';
|
||||||
import { createRouteMachine } from './route-setter.machine';
|
import { createRouteMachine } from './route-setter.machine';
|
||||||
import { textFilteredStateConfig } from './text-filtered.state';
|
import { textFilteredStateConfig } from './text-filtered.state';
|
||||||
|
import { tracingStateConfig } from './tracing.state';
|
||||||
import { unselectedStateConfig } from './unselected.state';
|
import { unselectedStateConfig } from './unselected.state';
|
||||||
|
|
||||||
export const initialContext: DepGraphContext = {
|
export const initialContext: DepGraphContext = {
|
||||||
@ -36,6 +37,10 @@ export const initialContext: DepGraphContext = {
|
|||||||
numNodes: 0,
|
numNodes: 0,
|
||||||
renderTime: 0,
|
renderTime: 0,
|
||||||
},
|
},
|
||||||
|
tracing: {
|
||||||
|
start: null,
|
||||||
|
end: null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const depGraphMachine = Machine<
|
export const depGraphMachine = Machine<
|
||||||
@ -53,6 +58,7 @@ export const depGraphMachine = Machine<
|
|||||||
customSelected: customSelectedStateConfig,
|
customSelected: customSelectedStateConfig,
|
||||||
focused: focusedStateConfig,
|
focused: focusedStateConfig,
|
||||||
textFiltered: textFilteredStateConfig,
|
textFiltered: textFilteredStateConfig,
|
||||||
|
tracing: tracingStateConfig,
|
||||||
},
|
},
|
||||||
on: {
|
on: {
|
||||||
initGraph: {
|
initGraph: {
|
||||||
@ -114,6 +120,12 @@ export const depGraphMachine = Machine<
|
|||||||
focusProject: {
|
focusProject: {
|
||||||
target: 'focused',
|
target: 'focused',
|
||||||
},
|
},
|
||||||
|
setTracingStart: {
|
||||||
|
target: 'tracing',
|
||||||
|
},
|
||||||
|
setTracingEnd: {
|
||||||
|
target: 'tracing',
|
||||||
|
},
|
||||||
setCollapseEdges: {
|
setCollapseEdges: {
|
||||||
actions: [
|
actions: [
|
||||||
'setCollapseEdges',
|
'setCollapseEdges',
|
||||||
@ -262,6 +274,19 @@ export const depGraphMachine = Machine<
|
|||||||
ctx.affectedProjects = event.affectedProjects;
|
ctx.affectedProjects = event.affectedProjects;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
notifyGraphTracing: send(
|
||||||
|
(ctx, event) => {
|
||||||
|
return {
|
||||||
|
type: 'notifyGraphTracing',
|
||||||
|
start: ctx.tracing.start,
|
||||||
|
end: ctx.tracing.end,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: (context) => context.graphActor,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
notifyGraphShowProject: send(
|
notifyGraphShowProject: send(
|
||||||
(context, event) => {
|
(context, event) => {
|
||||||
if (event.type !== 'selectProject') return;
|
if (event.type !== 'selectProject') return;
|
||||||
@ -354,6 +379,18 @@ export const depGraphMachine = Machine<
|
|||||||
to: (ctx) => ctx.routeSetterActor,
|
to: (ctx) => ctx.routeSetterActor,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
notifyRouteTracing: send(
|
||||||
|
(ctx) => {
|
||||||
|
return {
|
||||||
|
type: 'notifyRouteTracing',
|
||||||
|
start: ctx.tracing.start,
|
||||||
|
end: ctx.tracing.end,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: (ctx) => ctx.routeSetterActor,
|
||||||
|
}
|
||||||
|
),
|
||||||
notifyRouteSearchDepth: send(
|
notifyRouteSearchDepth: send(
|
||||||
(ctx, event) => ({
|
(ctx, event) => ({
|
||||||
type: 'notifyRouteSearchDepth',
|
type: 'notifyRouteSearchDepth',
|
||||||
|
|||||||
@ -7,8 +7,10 @@ import type { VirtualElement } from '@popperjs/core';
|
|||||||
import { default as cy } from 'cytoscape';
|
import { default as cy } from 'cytoscape';
|
||||||
import { default as cytoscapeDagre } from 'cytoscape-dagre';
|
import { default as cytoscapeDagre } from 'cytoscape-dagre';
|
||||||
import { default as popper } from 'cytoscape-popper';
|
import { default as popper } from 'cytoscape-popper';
|
||||||
|
import path from 'path/posix';
|
||||||
import type { Instance } from 'tippy.js';
|
import type { Instance } from 'tippy.js';
|
||||||
import { ProjectNodeToolTip } from '../project-node-tooltip';
|
import EdgeNodeTooltip from '../edge-tooltip';
|
||||||
|
import ProjectNodeToolTip from '../project-node-tooltip';
|
||||||
import { edgeStyles, nodeStyles } from '../styles-graph';
|
import { edgeStyles, nodeStyles } from '../styles-graph';
|
||||||
import { GraphTooltipService } from '../tooltip-service';
|
import { GraphTooltipService } from '../tooltip-service';
|
||||||
import {
|
import {
|
||||||
@ -106,6 +108,13 @@ export class GraphService {
|
|||||||
case 'notifyGraphShowAffectedProjects':
|
case 'notifyGraphShowAffectedProjects':
|
||||||
this.showAffectedProjects();
|
this.showAffectedProjects();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'notifyGraphTracing':
|
||||||
|
if (event.start && event.end) {
|
||||||
|
this.traceProjects(event.start, event.end);
|
||||||
|
// this.traceAllProjects(event.start, event.end);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectedProjectNames: string[] = [];
|
let selectedProjectNames: string[] = [];
|
||||||
@ -340,6 +349,78 @@ export class GraphService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
traceProjects(start: string, end: string) {
|
||||||
|
const dijkstra = this.traversalGraph
|
||||||
|
.elements()
|
||||||
|
.dijkstra({ root: `[id = "${start}"]`, directed: true });
|
||||||
|
|
||||||
|
const path = dijkstra.pathTo(this.traversalGraph.$(`[id = "${end}"]`));
|
||||||
|
|
||||||
|
this.transferToRenderGraph(path.union(path.ancestors()));
|
||||||
|
}
|
||||||
|
|
||||||
|
traceAllProjects(start: string, end: string) {
|
||||||
|
const startNode = this.traversalGraph.$id(start).nodes().first();
|
||||||
|
|
||||||
|
const queue: cy.NodeSingular[][] = [[startNode]];
|
||||||
|
|
||||||
|
const paths: cy.NodeSingular[][] = [];
|
||||||
|
let iterations = 0;
|
||||||
|
|
||||||
|
while (queue.length > 0 && iterations <= 1000) {
|
||||||
|
const currentPath = queue.pop();
|
||||||
|
|
||||||
|
const nodeToTest = currentPath[currentPath.length - 1];
|
||||||
|
|
||||||
|
const outgoers = nodeToTest.outgoers('node');
|
||||||
|
|
||||||
|
if (outgoers.length > 0) {
|
||||||
|
outgoers.forEach((outgoer) => {
|
||||||
|
const newPath = [...currentPath, outgoer];
|
||||||
|
if (outgoer.id() === end) {
|
||||||
|
paths.push(newPath);
|
||||||
|
} else {
|
||||||
|
queue.push(newPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
iterations++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iterations >= 1000) {
|
||||||
|
console.log('failsafe triggered!');
|
||||||
|
}
|
||||||
|
paths.forEach((currentPath) => {
|
||||||
|
console.log(
|
||||||
|
currentPath
|
||||||
|
.map((path) => path.map((element) => element.id()))
|
||||||
|
.join(' => ')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let finalCollection = this.traversalGraph.collection();
|
||||||
|
|
||||||
|
paths.forEach((path) => {
|
||||||
|
for (let i = 0; i < path.length; i++) {
|
||||||
|
finalCollection = finalCollection.union(path[i]);
|
||||||
|
|
||||||
|
const nextIndex = i + 1;
|
||||||
|
if (nextIndex < path.length) {
|
||||||
|
finalCollection = finalCollection.union(
|
||||||
|
path[i].edgesTo(path[nextIndex])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(finalCollection.length);
|
||||||
|
|
||||||
|
finalCollection.union(finalCollection.ancestors());
|
||||||
|
console.log(finalCollection.map((element) => element.id()));
|
||||||
|
this.transferToRenderGraph(finalCollection);
|
||||||
|
}
|
||||||
|
|
||||||
private transferToRenderGraph(elements: cy.Collection) {
|
private transferToRenderGraph(elements: cy.Collection) {
|
||||||
let currentFocusedProjectName;
|
let currentFocusedProjectName;
|
||||||
if (this.renderGraph) {
|
if (this.renderGraph) {
|
||||||
@ -373,6 +454,7 @@ export class GraphService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.listenForProjectNodeClicks();
|
this.listenForProjectNodeClicks();
|
||||||
|
this.listenForEdgeNodeClicks();
|
||||||
this.listenForProjectNodeHovers();
|
this.listenForProjectNodeHovers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -521,12 +603,41 @@ export class GraphService {
|
|||||||
|
|
||||||
let ref: VirtualElement = node.popperRef(); // used only for positioning
|
let ref: VirtualElement = node.popperRef(); // used only for positioning
|
||||||
|
|
||||||
const content = new ProjectNodeToolTip(node).render();
|
const content = ProjectNodeToolTip({
|
||||||
|
id: node.id(),
|
||||||
|
type: node.data('type'),
|
||||||
|
tags: node.data('tags'),
|
||||||
|
});
|
||||||
|
|
||||||
this.openTooltip = this.tooltipService.open(ref, content);
|
this.openTooltip = this.tooltipService.open(ref, content);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listenForEdgeNodeClicks() {
|
||||||
|
this.renderGraph.$('edge').on('click', (event) => {
|
||||||
|
const edge: cy.EdgeSingular = event.target;
|
||||||
|
let ref: VirtualElement = edge.popperRef(); // used only for positioning
|
||||||
|
|
||||||
|
const tooltipContent = EdgeNodeTooltip({
|
||||||
|
type: edge.data('type'),
|
||||||
|
source: edge.source().id(),
|
||||||
|
target: edge.target().id(),
|
||||||
|
fileDependencies: edge
|
||||||
|
.source()
|
||||||
|
.data('files')
|
||||||
|
.filter((file) => file.deps && file.deps.includes(edge.target().id()))
|
||||||
|
.map((file) => {
|
||||||
|
return {
|
||||||
|
fileName: file.file.replace(`${edge.source().data('root')}/`, ''),
|
||||||
|
target: edge.target().id(),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.openTooltip = this.tooltipService.open(ref, tooltipContent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
listenForProjectNodeHovers(): void {
|
listenForProjectNodeHovers(): void {
|
||||||
this.renderGraph.on('mouseover', (event) => {
|
this.renderGraph.on('mouseover', (event) => {
|
||||||
const node = event.target;
|
const node = event.target;
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export interface DepGraphSchema {
|
|||||||
focused: {};
|
focused: {};
|
||||||
textFiltered: {};
|
textFiltered: {};
|
||||||
customSelected: {};
|
customSelected: {};
|
||||||
|
tracing: {};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,6 +36,10 @@ export type DepGraphUIEvents =
|
|||||||
| { type: 'deselectAll' }
|
| { type: 'deselectAll' }
|
||||||
| { type: 'selectAffected' }
|
| { type: 'selectAffected' }
|
||||||
| { type: 'setGroupByFolder'; groupByFolder: boolean }
|
| { type: 'setGroupByFolder'; groupByFolder: boolean }
|
||||||
|
| { type: 'setTracingStart'; projectName: string }
|
||||||
|
| { type: 'setTracingEnd'; projectName: string }
|
||||||
|
| { type: 'clearTraceStart' }
|
||||||
|
| { type: 'clearTraceEnd' }
|
||||||
| { type: 'setCollapseEdges'; collapseEdges: boolean }
|
| { type: 'setCollapseEdges'; collapseEdges: boolean }
|
||||||
| { type: 'setIncludeProjectsByPath'; includeProjectsByPath: boolean }
|
| { type: 'setIncludeProjectsByPath'; includeProjectsByPath: boolean }
|
||||||
| { type: 'incrementSearchDepth' }
|
| { type: 'incrementSearchDepth' }
|
||||||
@ -116,6 +121,11 @@ export type GraphRenderEvents =
|
|||||||
search: string;
|
search: string;
|
||||||
includeProjectsByPath: boolean;
|
includeProjectsByPath: boolean;
|
||||||
searchDepth: number;
|
searchDepth: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'notifyGraphTracing';
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RouteEvents =
|
export type RouteEvents =
|
||||||
@ -145,7 +155,8 @@ export type RouteEvents =
|
|||||||
| {
|
| {
|
||||||
type: 'notifyRouteSelectAffected';
|
type: 'notifyRouteSelectAffected';
|
||||||
}
|
}
|
||||||
| { type: 'notifyRouteClearSelect' };
|
| { type: 'notifyRouteClearSelect' }
|
||||||
|
| { type: 'notifyRouteTracing'; start: string; end: string };
|
||||||
|
|
||||||
export type AllEvents = DepGraphUIEvents | GraphRenderEvents | RouteEvents;
|
export type AllEvents = DepGraphUIEvents | GraphRenderEvents | RouteEvents;
|
||||||
|
|
||||||
@ -170,6 +181,10 @@ export interface DepGraphContext {
|
|||||||
routeSetterActor: ActorRef<RouteEvents>;
|
routeSetterActor: ActorRef<RouteEvents>;
|
||||||
routeListenerActor: ActorRef<DepGraphUIEvents>;
|
routeListenerActor: ActorRef<DepGraphUIEvents>;
|
||||||
lastPerfReport: GraphPerfReport;
|
lastPerfReport: GraphPerfReport;
|
||||||
|
tracing: {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DepGraphStateNodeConfig = StateNodeConfig<
|
export type DepGraphStateNodeConfig = StateNodeConfig<
|
||||||
|
|||||||
@ -30,6 +30,15 @@ function parseSearchParamsToEvents(searchParams: string): DepGraphUIEvents[] {
|
|||||||
searchDepth: parseInt(value),
|
searchDepth: parseInt(value),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'traceStart':
|
||||||
|
events.push({
|
||||||
|
type: 'setTracingStart',
|
||||||
|
projectName: value,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'traceEnd':
|
||||||
|
events.push({ type: 'setTracingEnd', projectName: value });
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return events;
|
return events;
|
||||||
|
|||||||
@ -8,7 +8,9 @@ type ParamKeys =
|
|||||||
| 'groupByFolder'
|
| 'groupByFolder'
|
||||||
| 'searchDepth'
|
| 'searchDepth'
|
||||||
| 'select'
|
| 'select'
|
||||||
| 'collapseEdges';
|
| 'collapseEdges'
|
||||||
|
| 'traceStart'
|
||||||
|
| 'traceEnd';
|
||||||
type ParamRecord = Record<ParamKeys, string | null>;
|
type ParamRecord = Record<ParamKeys, string | null>;
|
||||||
|
|
||||||
function reduceParamRecordToQueryString(params: ParamRecord): string {
|
function reduceParamRecordToQueryString(params: ParamRecord): string {
|
||||||
@ -33,6 +35,8 @@ export const createRouteMachine = () => {
|
|||||||
collapseEdges: params.get('collapseEdges'),
|
collapseEdges: params.get('collapseEdges'),
|
||||||
searchDepth: params.get('searchDepth'),
|
searchDepth: params.get('searchDepth'),
|
||||||
select: params.get('select'),
|
select: params.get('select'),
|
||||||
|
traceStart: params.get('traceStart'),
|
||||||
|
traceEnd: params.get('traceEnd'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialContext = {
|
const initialContext = {
|
||||||
@ -55,6 +59,8 @@ export const createRouteMachine = () => {
|
|||||||
searchDepth: null,
|
searchDepth: null,
|
||||||
select: null,
|
select: null,
|
||||||
collapseEdges: null,
|
collapseEdges: null,
|
||||||
|
traceStart: null,
|
||||||
|
traceEnd: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
always: {
|
always: {
|
||||||
@ -117,6 +123,20 @@ export const createRouteMachine = () => {
|
|||||||
: null;
|
: null;
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
notifyRouteTracing: {
|
||||||
|
actions: assign((ctx, event) => {
|
||||||
|
if (event.start !== null && event.end !== null) {
|
||||||
|
ctx.params.traceStart = event.start;
|
||||||
|
ctx.params.traceEnd = event.end;
|
||||||
|
|
||||||
|
ctx.params.focus = null;
|
||||||
|
ctx.params.select = null;
|
||||||
|
} else {
|
||||||
|
ctx.params.traceStart = null;
|
||||||
|
ctx.params.traceEnd = null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -48,3 +48,8 @@ export const textFilterSelector: DepGraphSelector<string> = (state) =>
|
|||||||
|
|
||||||
export const hasAffectedProjectsSelector: DepGraphSelector<boolean> = (state) =>
|
export const hasAffectedProjectsSelector: DepGraphSelector<boolean> = (state) =>
|
||||||
state.context.affectedProjects.length > 0;
|
state.context.affectedProjects.length > 0;
|
||||||
|
|
||||||
|
export const getTracingInfo: DepGraphSelector<{
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
}> = (state) => state.context.tracing;
|
||||||
|
|||||||
37
dep-graph/client/src/app/machines/tracing.state.ts
Normal file
37
dep-graph/client/src/app/machines/tracing.state.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { assign } from '@xstate/immer';
|
||||||
|
import { send, actions } from 'xstate';
|
||||||
|
import { DepGraphStateNodeConfig } from './interfaces';
|
||||||
|
|
||||||
|
export const tracingStateConfig: DepGraphStateNodeConfig = {
|
||||||
|
entry: [
|
||||||
|
assign((ctx, event) => {
|
||||||
|
if (event.type === 'setTracingStart') {
|
||||||
|
ctx.tracing.start = event.projectName;
|
||||||
|
} else if (event.type === 'setTracingEnd') {
|
||||||
|
ctx.tracing.end = event.projectName;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
'notifyRouteTracing',
|
||||||
|
'notifyGraphTracing',
|
||||||
|
],
|
||||||
|
on: {
|
||||||
|
clearTraceStart: {
|
||||||
|
actions: [
|
||||||
|
assign((ctx) => {
|
||||||
|
ctx.tracing.start = null;
|
||||||
|
}),
|
||||||
|
'notifyRouteTracing',
|
||||||
|
'notifyGraphTracing',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
clearTraceEnd: {
|
||||||
|
actions: [
|
||||||
|
assign((ctx) => {
|
||||||
|
ctx.tracing.end = null;
|
||||||
|
}),
|
||||||
|
'notifyRouteTracing',
|
||||||
|
'notifyGraphTracing',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -21,6 +21,13 @@ export class MockProjectGraphService implements ProjectGraphService {
|
|||||||
data: {
|
data: {
|
||||||
root: 'apps/app1',
|
root: 'apps/app1',
|
||||||
tags: [],
|
tags: [],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
file: 'some/file.ts',
|
||||||
|
hash: 'ecccd8481d2e5eae0e59928be1bc4c2d071729d7',
|
||||||
|
deps: ['existing-lib-1'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -29,6 +36,7 @@ export class MockProjectGraphService implements ProjectGraphService {
|
|||||||
data: {
|
data: {
|
||||||
root: 'libs/lib1',
|
root: 'libs/lib1',
|
||||||
tags: [],
|
tags: [],
|
||||||
|
files: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -37,7 +45,7 @@ export class MockProjectGraphService implements ProjectGraphService {
|
|||||||
{
|
{
|
||||||
source: 'existing-app-1',
|
source: 'existing-app-1',
|
||||||
target: 'existing-lib-1',
|
target: 'existing-lib-1',
|
||||||
type: 'statis',
|
type: 'static',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'existing-lib-1': [],
|
'existing-lib-1': [],
|
||||||
@ -70,6 +78,7 @@ export class MockProjectGraphService implements ProjectGraphService {
|
|||||||
data: {
|
data: {
|
||||||
root: type === 'app' ? `apps/${name}` : `libs/${name}`,
|
root: type === 'app' ? `apps/${name}` : `libs/${name}`,
|
||||||
tags: [],
|
tags: [],
|
||||||
|
files: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,81 +0,0 @@
|
|||||||
import * as cy from 'cytoscape';
|
|
||||||
import { getDepGraphService } from './machines/dep-graph.service';
|
|
||||||
|
|
||||||
export class ProjectNodeToolTip {
|
|
||||||
constructor(private node: cy.NodeSingular) {}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const wrapper = document.createElement('div');
|
|
||||||
const header = this.createHeader();
|
|
||||||
const tags = this.createTags();
|
|
||||||
const buttons = this.createButtons();
|
|
||||||
|
|
||||||
wrapper.appendChild(header);
|
|
||||||
wrapper.appendChild(tags);
|
|
||||||
wrapper.appendChild(buttons);
|
|
||||||
|
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
private createHeader() {
|
|
||||||
const header = document.createElement('h4');
|
|
||||||
const typeLabel = document.createElement('span');
|
|
||||||
const projectName = document.createTextNode(this.node.attr('id'));
|
|
||||||
|
|
||||||
typeLabel.classList.add('tag');
|
|
||||||
typeLabel.innerText = this.node.attr('type');
|
|
||||||
|
|
||||||
header.appendChild(typeLabel);
|
|
||||||
header.appendChild(projectName);
|
|
||||||
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
private createTags() {
|
|
||||||
const wrapper = document.createElement('p');
|
|
||||||
const tagLabel = document.createElement('strong');
|
|
||||||
const tags = document.createTextNode(
|
|
||||||
this.node.attr('tags')?.join(', ') ?? ''
|
|
||||||
);
|
|
||||||
|
|
||||||
tagLabel.innerText = 'tags';
|
|
||||||
|
|
||||||
wrapper.appendChild(tagLabel);
|
|
||||||
wrapper.appendChild(document.createElement('br'));
|
|
||||||
wrapper.appendChild(tags);
|
|
||||||
|
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
private createButtons() {
|
|
||||||
const wrapper = document.createElement('div');
|
|
||||||
const focusButton = document.createElement('button');
|
|
||||||
const excludeButton = document.createElement('button');
|
|
||||||
|
|
||||||
wrapper.classList.add('flex');
|
|
||||||
|
|
||||||
const depGraphService = getDepGraphService();
|
|
||||||
|
|
||||||
focusButton.addEventListener('click', () =>
|
|
||||||
depGraphService.send({
|
|
||||||
type: 'focusProject',
|
|
||||||
projectName: this.node.attr('id'),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
focusButton.innerText = 'Focus';
|
|
||||||
|
|
||||||
excludeButton.addEventListener('click', () => {
|
|
||||||
depGraphService.send({
|
|
||||||
type: 'deselectProject',
|
|
||||||
projectName: this.node.attr('id'),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
excludeButton.innerText = 'Exclude';
|
|
||||||
|
|
||||||
wrapper.appendChild(focusButton);
|
|
||||||
wrapper.appendChild(excludeButton);
|
|
||||||
|
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
93
dep-graph/client/src/app/project-node-tooltip.tsx
Normal file
93
dep-graph/client/src/app/project-node-tooltip.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { getDepGraphService } from './machines/dep-graph.service';
|
||||||
|
import ExperimentalFeature from './experimental-feature';
|
||||||
|
|
||||||
|
export interface ProjectNodeToolTipProps {
|
||||||
|
type: 'app' | 'lib' | 'e2e';
|
||||||
|
id: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
function ProjectNodeToolTip({ type, id, tags }: ProjectNodeToolTipProps) {
|
||||||
|
const depGraphService = getDepGraphService();
|
||||||
|
|
||||||
|
function onFocus() {
|
||||||
|
depGraphService.send({
|
||||||
|
type: 'focusProject',
|
||||||
|
projectName: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onExclude() {
|
||||||
|
depGraphService.send({
|
||||||
|
type: 'deselectProject',
|
||||||
|
projectName: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStartTrace() {
|
||||||
|
depGraphService.send({
|
||||||
|
type: 'setTracingStart',
|
||||||
|
projectName: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEndTrace() {
|
||||||
|
depGraphService.send({
|
||||||
|
type: 'setTracingEnd',
|
||||||
|
projectName: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h4>
|
||||||
|
<span className="tag">{type}</span>
|
||||||
|
{id}
|
||||||
|
</h4>
|
||||||
|
{tags.length > 0 ? (
|
||||||
|
<p>
|
||||||
|
<strong>tags</strong>
|
||||||
|
<br></br>
|
||||||
|
{tags.join(', ')}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
<div className="flex">
|
||||||
|
<button onClick={onFocus}>Focus</button>
|
||||||
|
<button onClick={onExclude}>Exclude</button>
|
||||||
|
<ExperimentalFeature>
|
||||||
|
<button className="flex flex-row items-center" onClick={onStartTrace}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="mr-2 h-5 w-5 text-gray-500"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Start
|
||||||
|
</button>
|
||||||
|
<button className="flex flex-row items-center" onClick={onEndTrace}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="mr-2 h-5 w-5 text-gray-500"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M3 6a3 3 0 013-3h10a1 1 0 01.8 1.6L14.25 8l2.55 3.4A1 1 0 0116 13H6a1 1 0 00-1 1v3a1 1 0 11-2 0V6z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
End
|
||||||
|
</button>
|
||||||
|
</ExperimentalFeature>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectNodeToolTip;
|
||||||
@ -5,12 +5,14 @@ import { useDepGraphSelector } from '../hooks/use-dep-graph-selector';
|
|||||||
import {
|
import {
|
||||||
collapseEdgesSelector,
|
collapseEdgesSelector,
|
||||||
focusedProjectNameSelector,
|
focusedProjectNameSelector,
|
||||||
|
getTracingInfo,
|
||||||
groupByFolderSelector,
|
groupByFolderSelector,
|
||||||
hasAffectedProjectsSelector,
|
hasAffectedProjectsSelector,
|
||||||
includePathSelector,
|
includePathSelector,
|
||||||
searchDepthSelector,
|
searchDepthSelector,
|
||||||
textFilterSelector,
|
textFilterSelector,
|
||||||
} from '../machines/selectors';
|
} from '../machines/selectors';
|
||||||
|
import { tracingStateConfig } from '../machines/tracing.state';
|
||||||
import CollapseEdgesPanel from './collapse-edges-panel';
|
import CollapseEdgesPanel from './collapse-edges-panel';
|
||||||
import FocusedProjectPanel from './focused-project-panel';
|
import FocusedProjectPanel from './focused-project-panel';
|
||||||
import GroupByFolderPanel from './group-by-folder-panel';
|
import GroupByFolderPanel from './group-by-folder-panel';
|
||||||
@ -18,6 +20,7 @@ import ProjectList from './project-list';
|
|||||||
import SearchDepth from './search-depth';
|
import SearchDepth from './search-depth';
|
||||||
import ShowHideProjects from './show-hide-projects';
|
import ShowHideProjects from './show-hide-projects';
|
||||||
import TextFilterPanel from './text-filter-panel';
|
import TextFilterPanel from './text-filter-panel';
|
||||||
|
import TracingPanel from './tracing-panel';
|
||||||
import ThemePanel from './theme-panel';
|
import ThemePanel from './theme-panel';
|
||||||
import { useEnvironmentConfig } from '../hooks/use-environment-config';
|
import { useEnvironmentConfig } from '../hooks/use-environment-config';
|
||||||
|
|
||||||
@ -34,6 +37,9 @@ export function Sidebar() {
|
|||||||
|
|
||||||
const { showExperimentalFeatures } = environment.appConfig;
|
const { showExperimentalFeatures } = environment.appConfig;
|
||||||
|
|
||||||
|
// const isTracing = depGraphService.state.matches('tracing');
|
||||||
|
const tracingInfo = useDepGraphSelector(getTracingInfo);
|
||||||
|
|
||||||
function resetFocus() {
|
function resetFocus() {
|
||||||
depGraphService.send({ type: 'unfocusProject' });
|
depGraphService.send({ type: 'unfocusProject' });
|
||||||
}
|
}
|
||||||
@ -84,6 +90,14 @@ export function Sidebar() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetTraceStart() {
|
||||||
|
depGraphService.send({ type: 'clearTraceStart' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetTraceEnd() {
|
||||||
|
depGraphService.send({ type: 'clearTraceEnd' });
|
||||||
|
}
|
||||||
|
|
||||||
const updateTextFilter = useCallback(
|
const updateTextFilter = useCallback(
|
||||||
(textFilter: string) => {
|
(textFilter: string) => {
|
||||||
depGraphService.send({ type: 'filterByText', search: textFilter });
|
depGraphService.send({ type: 'filterByText', search: textFilter });
|
||||||
@ -170,6 +184,15 @@ export function Sidebar() {
|
|||||||
></FocusedProjectPanel>
|
></FocusedProjectPanel>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<ExperimentalFeature>
|
||||||
|
<TracingPanel
|
||||||
|
start={tracingInfo.start}
|
||||||
|
end={tracingInfo.end}
|
||||||
|
resetStart={resetTraceStart}
|
||||||
|
resetEnd={resetTraceEnd}
|
||||||
|
></TracingPanel>
|
||||||
|
</ExperimentalFeature>
|
||||||
|
|
||||||
<TextFilterPanel
|
<TextFilterPanel
|
||||||
includePath={includePath}
|
includePath={includePath}
|
||||||
resetTextFilter={resetTextFilter}
|
resetTextFilter={resetTextFilter}
|
||||||
|
|||||||
103
dep-graph/client/src/app/sidebar/tracing-panel.tsx
Normal file
103
dep-graph/client/src/app/sidebar/tracing-panel.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { XCircleIcon } from '@heroicons/react/solid';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import Flag from '../icons/flag';
|
||||||
|
import MapMarker from '../icons/map-marker';
|
||||||
|
|
||||||
|
export interface TracingPanelProps {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
resetStart: () => void;
|
||||||
|
resetEnd: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TracingPanel = memo(
|
||||||
|
({ start, end, resetStart, resetEnd }: TracingPanelProps) => {
|
||||||
|
return (
|
||||||
|
<div className="mt-10 px-4">
|
||||||
|
<div className="transition duration-200 ease-in-out group-hover:opacity-60">
|
||||||
|
<h3 className="cursor-text pb-2 text-sm font-semibold uppercase tracking-wide text-gray-900 lg:text-xs ">
|
||||||
|
Tracing Path
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-row items-center truncate ">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="mr-2 h-5 w-5 text-gray-500"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{start ? (
|
||||||
|
<div
|
||||||
|
className=" group relative flex-1 cursor-pointer flex-col items-center overflow-hidden "
|
||||||
|
data-cy="resetTraceButton"
|
||||||
|
onClick={resetStart}
|
||||||
|
>
|
||||||
|
<div className="bg-green-nx-base flex-1 truncate rounded-md border border-gray-200 p-2 text-gray-50 shadow-sm transition duration-200 ease-in-out group-hover:opacity-60">
|
||||||
|
<span>{start}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute top-2 right-2 flex translate-x-32 items-center rounded-md bg-white pl-2 text-sm font-medium text-gray-700 shadow-sm ring-1 ring-gray-500 transition-all transition duration-200 ease-in-out group-hover:translate-x-0">
|
||||||
|
Reset
|
||||||
|
<span className="rounded-md p-1">
|
||||||
|
<XCircleIcon className="h-5 w-5"></XCircleIcon>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-500">Select start project</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<MapMarker className="h-5 w-5 text-gray-500"></MapMarker>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row items-center truncate ">
|
||||||
|
<Flag className="mr-2 h-5 w-5 text-gray-500"></Flag>
|
||||||
|
|
||||||
|
{end ? (
|
||||||
|
<div
|
||||||
|
className=" group relative flex-1 cursor-pointer flex-col items-center overflow-hidden "
|
||||||
|
data-cy="resetTraceButton"
|
||||||
|
onClick={resetEnd}
|
||||||
|
>
|
||||||
|
<div className="bg-green-nx-base flex-1 truncate rounded-md border border-gray-200 p-2 text-gray-50 shadow-sm transition duration-200 ease-in-out group-hover:opacity-60">
|
||||||
|
<span>{end}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute top-2 right-2 flex translate-x-32 items-center rounded-md bg-white pl-2 text-sm font-medium text-gray-700 shadow-sm ring-1 ring-gray-500 transition-all transition duration-200 ease-in-out group-hover:translate-x-0">
|
||||||
|
Reset
|
||||||
|
<span className="rounded-md p-1">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-500">Select end project</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default TracingPanel;
|
||||||
@ -1,16 +1,22 @@
|
|||||||
import { VirtualElement } from '@popperjs/core';
|
import { VirtualElement } from '@popperjs/core';
|
||||||
import tippy, { Instance, hideAll } from 'tippy.js';
|
import tippy, { Instance, hideAll } from 'tippy.js';
|
||||||
|
import * as ReactDOM from 'react-dom';
|
||||||
import { selectValueByThemeStatic } from './theme-resolver';
|
import { selectValueByThemeStatic } from './theme-resolver';
|
||||||
|
|
||||||
export class GraphTooltipService {
|
export class GraphTooltipService {
|
||||||
open(ref: VirtualElement, tooltipContent: HTMLElement): Instance {
|
open(ref: VirtualElement, tooltipContent: JSX.Element): Instance {
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
|
||||||
|
ReactDOM.render(tooltipContent, tempDiv);
|
||||||
|
|
||||||
let instance = tippy(document.createElement('div'), {
|
let instance = tippy(document.createElement('div'), {
|
||||||
trigger: 'manual',
|
trigger: 'manual',
|
||||||
theme: selectValueByThemeStatic('dark-nx', 'nx'),
|
theme: selectValueByThemeStatic('dark-nx', 'nx'),
|
||||||
interactive: true,
|
interactive: true,
|
||||||
appendTo: document.body,
|
appendTo: document.body,
|
||||||
content: tooltipContent,
|
content: tempDiv,
|
||||||
getReferenceClientRect: ref.getBoundingClientRect,
|
getReferenceClientRect: ref.getBoundingClientRect,
|
||||||
|
maxWidth: 'none',
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.show();
|
instance.show();
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export class ProjectEdge {
|
|||||||
id: `${this.dep.source}|${this.dep.target}`,
|
id: `${this.dep.source}|${this.dep.target}`,
|
||||||
source: this.dep.source,
|
source: this.dep.source,
|
||||||
target: this.dep.target,
|
target: this.dep.target,
|
||||||
|
type: this.dep.type,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
edge.classes = this.dep.type ?? '';
|
edge.classes = this.dep.type ?? '';
|
||||||
|
|||||||
@ -43,6 +43,8 @@ export class ProjectNode {
|
|||||||
groupByFolder && this.project.data.hasOwnProperty('root')
|
groupByFolder && this.project.data.hasOwnProperty('root')
|
||||||
? this.getParentId()
|
? this.getParentId()
|
||||||
: null,
|
: null,
|
||||||
|
files: this.project.data.files,
|
||||||
|
root: this.project.data.root,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4
dep-graph/client/src/globals.d.ts
vendored
4
dep-graph/client/src/globals.d.ts
vendored
@ -28,6 +28,10 @@ declare module 'cytoscape' {
|
|||||||
pannable: () => boolean;
|
pannable: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EdgeSingular {
|
||||||
|
popperRef: Function;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Css {
|
namespace Css {
|
||||||
interface EdgeLine {
|
interface EdgeLine {
|
||||||
'edge-text-rotation'?: string;
|
'edge-text-rotation'?: string;
|
||||||
|
|||||||
@ -89,7 +89,6 @@ canvas {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.025em;
|
letter-spacing: 0.025em;
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
margin-right: 0.75rem;
|
margin-right: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -490,7 +490,7 @@ async function createDepGraphClientResponse(
|
|||||||
data: {
|
data: {
|
||||||
tags: project.data.tags,
|
tags: project.data.tags,
|
||||||
root: project.data.root,
|
root: project.data.root,
|
||||||
files: [],
|
files: project.data.files,
|
||||||
},
|
},
|
||||||
} as ProjectGraphProjectNode)
|
} as ProjectGraphProjectNode)
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user