chore(graph): add group by project option to task graph (#13239)
This commit is contained in:
parent
558b99c3c6
commit
e42da40438
@ -14,7 +14,7 @@ export const getCheckedProjectItems = () => cy.get('[data-active="true"]');
|
|||||||
export const getUncheckedProjectItems = () => cy.get('[data-active="false"]');
|
export const getUncheckedProjectItems = () => cy.get('[data-active="false"]');
|
||||||
|
|
||||||
export const getGroupByFolderCheckbox = () =>
|
export const getGroupByFolderCheckbox = () =>
|
||||||
cy.get('input[name=displayOptions][value=groupByFolder]');
|
cy.get('input[name=groupByFolder]');
|
||||||
|
|
||||||
export const getSearchDepthCheckbox = () =>
|
export const getSearchDepthCheckbox = () =>
|
||||||
cy.get('input[name=depthFilter][value=depthFilterActivated]');
|
cy.get('input[name=depthFilter][value=depthFilterActivated]');
|
||||||
|
|||||||
@ -8,6 +8,12 @@ export class ExternalApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enableExperimentalFeatures() {
|
enableExperimentalFeatures() {
|
||||||
|
localStorage.setItem('showExperimentalFeatures', 'true');
|
||||||
window.appConfig.showExperimentalFeatures = true;
|
window.appConfig.showExperimentalFeatures = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disableExperimentalFeatures() {
|
||||||
|
localStorage.setItem('showExperimentalFeatures', 'false');
|
||||||
|
window.appConfig.showExperimentalFeatures = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
|
||||||
import { GroupByFolderPanel } from './group-by-folder-panel';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
component: GroupByFolderPanel,
|
|
||||||
title: 'Project Graph/GroupByFolderPanel',
|
|
||||||
argTypes: { groupByFolderChanged: { action: 'groupByFolderChanged' } },
|
|
||||||
} as ComponentMeta<typeof GroupByFolderPanel>;
|
|
||||||
|
|
||||||
const Template: ComponentStory<typeof GroupByFolderPanel> = (args) => (
|
|
||||||
<GroupByFolderPanel {...args} />
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Primary = Template.bind({});
|
|
||||||
Primary.args = {
|
|
||||||
groupByFolder: false,
|
|
||||||
};
|
|
||||||
@ -1,41 +1,24 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import CheckboxPanel from '../../ui-components/checkbox-panel';
|
||||||
|
|
||||||
export interface DisplayOptionsPanelProps {
|
export interface DisplayOptionsPanelProps {
|
||||||
groupByFolder: boolean;
|
groupByFolder: boolean;
|
||||||
groupByFolderChanged: (checked: boolean) => void;
|
groupByFolderChanged: (checked: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GroupByFolderPanel = memo(
|
export const GroupByFolderPanel = ({
|
||||||
({ groupByFolder, groupByFolderChanged }: DisplayOptionsPanelProps) => {
|
groupByFolder,
|
||||||
return (
|
groupByFolderChanged,
|
||||||
<div className="mt-8 px-4">
|
}: DisplayOptionsPanelProps) => {
|
||||||
<div className="flex items-start">
|
return (
|
||||||
<div className="flex h-5 items-center">
|
<CheckboxPanel
|
||||||
<input
|
checked={groupByFolder}
|
||||||
id="displayOptions"
|
checkChanged={groupByFolderChanged}
|
||||||
name="displayOptions"
|
name={'groupByFolder'}
|
||||||
value="groupByFolder"
|
label={'Group by folder'}
|
||||||
type="checkbox"
|
description={'Visually arrange libraries by folders.'}
|
||||||
className="h-4 w-4 accent-blue-500 dark:accent-sky-500"
|
/>
|
||||||
onChange={(event) => groupByFolderChanged(event.target.checked)}
|
);
|
||||||
checked={groupByFolder}
|
};
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="ml-3 text-sm">
|
|
||||||
<label
|
|
||||||
htmlFor="displayOptions"
|
|
||||||
className="cursor-pointer font-medium text-slate-600 dark:text-slate-400"
|
|
||||||
>
|
|
||||||
Group by folder
|
|
||||||
</label>
|
|
||||||
<p className="text-slate-400 dark:text-slate-500">
|
|
||||||
Visually arrange libraries by folders with different colors.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default GroupByFolderPanel;
|
export default GroupByFolderPanel;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import ExperimentalFeature from '../ui-components/experimental-feature';
|
import ExperimentalFeature from '../ui-components/experimental-feature';
|
||||||
import { useProjectGraphSelector } from './hooks/use-project-graph-selector';
|
import { useProjectGraphSelector } from './hooks/use-project-graph-selector';
|
||||||
import {
|
import {
|
||||||
@ -22,6 +22,11 @@ import TracingPanel from './panels/tracing-panel';
|
|||||||
import { useEnvironmentConfig } from '../hooks/use-environment-config';
|
import { useEnvironmentConfig } from '../hooks/use-environment-config';
|
||||||
import { TracingAlgorithmType } from './machines/interfaces';
|
import { TracingAlgorithmType } from './machines/interfaces';
|
||||||
import { getProjectGraphService } from '../machines/get-services';
|
import { getProjectGraphService } from '../machines/get-services';
|
||||||
|
import { useIntervalWhen } from '../hooks/use-interval-when';
|
||||||
|
// nx-ignore-next-line
|
||||||
|
import { ProjectGraphClientResponse } from 'nx/src/command-line/dep-graph';
|
||||||
|
import { useParams, useRouteLoaderData } from 'react-router-dom';
|
||||||
|
import { getProjectGraphDataService } from '../hooks/get-project-graph-data-service';
|
||||||
|
|
||||||
export function ProjectsSidebar(): JSX.Element {
|
export function ProjectsSidebar(): JSX.Element {
|
||||||
const environmentConfig = useEnvironmentConfig();
|
const environmentConfig = useEnvironmentConfig();
|
||||||
@ -38,6 +43,12 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
|
|
||||||
const isTracing = projectGraphService.state.matches('tracing');
|
const isTracing = projectGraphService.state.matches('tracing');
|
||||||
const tracingInfo = useProjectGraphSelector(getTracingInfo);
|
const tracingInfo = useProjectGraphSelector(getTracingInfo);
|
||||||
|
const projectGraphDataService = getProjectGraphDataService();
|
||||||
|
|
||||||
|
const selectedProjectRouteData = useRouteLoaderData(
|
||||||
|
'selectedWorkspace'
|
||||||
|
) as ProjectGraphClientResponse;
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
function resetFocus() {
|
function resetFocus() {
|
||||||
projectGraphService.send({ type: 'unfocusProject' });
|
projectGraphService.send({ type: 'unfocusProject' });
|
||||||
@ -110,6 +121,45 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
projectGraphService.send({
|
||||||
|
type: 'setProjects',
|
||||||
|
projects: selectedProjectRouteData.projects,
|
||||||
|
dependencies: selectedProjectRouteData.dependencies,
|
||||||
|
affectedProjects: selectedProjectRouteData.affected,
|
||||||
|
workspaceLayout: selectedProjectRouteData.layout,
|
||||||
|
});
|
||||||
|
}, [selectedProjectRouteData]);
|
||||||
|
|
||||||
|
useIntervalWhen(
|
||||||
|
() => {
|
||||||
|
const selectedWorkspaceId =
|
||||||
|
params.selectedProjectId ??
|
||||||
|
environmentConfig.appConfig.defaultWorkspaceId;
|
||||||
|
|
||||||
|
const projectInfo = environmentConfig.appConfig.workspaces.find(
|
||||||
|
(graph) => graph.id === selectedWorkspaceId
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchProjectGraph = async () => {
|
||||||
|
const project: ProjectGraphClientResponse =
|
||||||
|
await projectGraphDataService.getProjectGraph(
|
||||||
|
projectInfo.projectGraphUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
projectGraphService.send({
|
||||||
|
type: 'updateGraph',
|
||||||
|
projects: project.projects,
|
||||||
|
dependencies: project.dependencies,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchProjectGraph();
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
environmentConfig.watch
|
||||||
|
);
|
||||||
|
|
||||||
const updateTextFilter = useCallback(
|
const updateTextFilter = useCallback(
|
||||||
(textFilter: string) => {
|
(textFilter: string) => {
|
||||||
projectGraphService.send({ type: 'filterByText', search: textFilter });
|
projectGraphService.send({ type: 'filterByText', search: textFilter });
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import TaskList from './task-list';
|
import TaskList from './task-list';
|
||||||
import { useNavigate, useParams, useRouteLoaderData } from 'react-router-dom';
|
import {
|
||||||
|
useNavigate,
|
||||||
|
useParams,
|
||||||
|
useRouteLoaderData,
|
||||||
|
useSearchParams,
|
||||||
|
} from 'react-router-dom';
|
||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import type {
|
import type {
|
||||||
ProjectGraphClientResponse,
|
ProjectGraphClientResponse,
|
||||||
@ -8,24 +13,35 @@ import type {
|
|||||||
import { getGraphService } from '../machines/graph.service';
|
import { getGraphService } from '../machines/graph.service';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import FocusedPanel from '../ui-components/focused-panel';
|
import FocusedPanel from '../ui-components/focused-panel';
|
||||||
|
import CheckboxPanel from '../ui-components/checkbox-panel';
|
||||||
|
|
||||||
export function TasksSidebar() {
|
export function TasksSidebar() {
|
||||||
const graphService = getGraphService();
|
const graphService = getGraphService();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
const selectedProjectRouteData = useRouteLoaderData(
|
const selectedProjectRouteData = useRouteLoaderData(
|
||||||
'SelectedProject'
|
'selectedWorkspace'
|
||||||
) as ProjectGraphClientResponse;
|
) as ProjectGraphClientResponse;
|
||||||
const projects = selectedProjectRouteData.projects;
|
|
||||||
const workspaceLayout = selectedProjectRouteData.layout;
|
const workspaceLayout = selectedProjectRouteData.layout;
|
||||||
|
|
||||||
const routeData = useRouteLoaderData(
|
const routeData = useRouteLoaderData(
|
||||||
'selectedTask'
|
'selectedTask'
|
||||||
) as TaskGraphClientResponse;
|
) as TaskGraphClientResponse;
|
||||||
const { taskGraphs } = routeData;
|
const { taskGraphs } = routeData;
|
||||||
|
const projects = selectedProjectRouteData.projects;
|
||||||
const selectedTask = params['selectedTask'];
|
// const projects = selectedProjectRouteData.projects.filter((project) => {
|
||||||
|
// return (
|
||||||
|
// Object.keys(project.data.targets).filter((target) => {
|
||||||
|
// const taskName = `${project.name}:${target}`;
|
||||||
|
// return (
|
||||||
|
// taskGraphs[taskName]?.dependencies[taskName]?.length > 0
|
||||||
|
// );
|
||||||
|
// }).length > 0
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
const selectedTask = params['selectedTaskId'];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
graphService.handleTaskEvent({
|
graphService.handleTaskEvent({
|
||||||
@ -48,6 +64,22 @@ export function TasksSidebar() {
|
|||||||
}
|
}
|
||||||
}, [params]);
|
}, [params]);
|
||||||
|
|
||||||
|
const groupByProject = searchParams.get('groupByProject') === 'true';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (groupByProject) {
|
||||||
|
graphService.handleTaskEvent({
|
||||||
|
type: 'setGroupByProject',
|
||||||
|
groupByProject: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
graphService.handleTaskEvent({
|
||||||
|
type: 'setGroupByProject',
|
||||||
|
groupByProject: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
function selectTask(taskId: string) {
|
function selectTask(taskId: string) {
|
||||||
if (selectedTask) {
|
if (selectedTask) {
|
||||||
navigate(`../${taskId}`);
|
navigate(`../${taskId}`);
|
||||||
@ -60,6 +92,18 @@ export function TasksSidebar() {
|
|||||||
navigate('..');
|
navigate('..');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function groupByProjectChanged(checked) {
|
||||||
|
setSearchParams((currentSearchParams) => {
|
||||||
|
if (checked) {
|
||||||
|
currentSearchParams.set('groupByProject', 'true');
|
||||||
|
} else {
|
||||||
|
currentSearchParams.delete('groupByProject');
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentSearchParams;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{selectedTask ? (
|
{selectedTask ? (
|
||||||
@ -68,6 +112,15 @@ export function TasksSidebar() {
|
|||||||
resetFocus={resetFocus}
|
resetFocus={resetFocus}
|
||||||
></FocusedPanel>
|
></FocusedPanel>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<CheckboxPanel
|
||||||
|
checked={groupByProject}
|
||||||
|
checkChanged={groupByProjectChanged}
|
||||||
|
name={'groupByProject'}
|
||||||
|
label={'Group by project'}
|
||||||
|
description={'Visually arrange tasks by project.'}
|
||||||
|
/>
|
||||||
|
|
||||||
<TaskList
|
<TaskList
|
||||||
projects={projects}
|
projects={projects}
|
||||||
workspaceLayout={workspaceLayout}
|
workspaceLayout={workspaceLayout}
|
||||||
|
|||||||
@ -24,7 +24,13 @@ export function getEnvironmentConfig() {
|
|||||||
localMode: window.localMode,
|
localMode: window.localMode,
|
||||||
projectGraphResponse: window.projectGraphResponse,
|
projectGraphResponse: window.projectGraphResponse,
|
||||||
environment: window.environment,
|
environment: window.environment,
|
||||||
appConfig: window.appConfig,
|
appConfig: {
|
||||||
|
...window.appConfig,
|
||||||
|
showExperimentalFeatures:
|
||||||
|
localStorage.getItem('showExperimentalFeatures') === 'true'
|
||||||
|
? true
|
||||||
|
: window.appConfig.showExperimentalFeatures,
|
||||||
|
},
|
||||||
useXstateInspect: window.useXstateInspect,
|
useXstateInspect: window.useXstateInspect,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type {
|
|||||||
TaskGraphClientResponse,
|
TaskGraphClientResponse,
|
||||||
} from 'nx/src/command-line/dep-graph';
|
} from 'nx/src/command-line/dep-graph';
|
||||||
|
|
||||||
export interface GraphListItem {
|
export interface WorkspaceData {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
projectGraphUrl: string;
|
projectGraphUrl: string;
|
||||||
@ -29,8 +29,8 @@ export interface Environment {
|
|||||||
export interface AppConfig {
|
export interface AppConfig {
|
||||||
showDebugger: boolean;
|
showDebugger: boolean;
|
||||||
showExperimentalFeatures: boolean;
|
showExperimentalFeatures: boolean;
|
||||||
projects: GraphListItem[];
|
workspaces: WorkspaceData[];
|
||||||
defaultProject: string;
|
defaultWorkspaceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GraphPerfReport {
|
export interface GraphPerfReport {
|
||||||
|
|||||||
@ -6,7 +6,8 @@ let projectGraphService = interpret(projectGraphMachine, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export function getProjectGraphService() {
|
export function getProjectGraphService() {
|
||||||
if (projectGraphService.status === InterpreterStatus.Stopped) {
|
if (projectGraphService.status === InterpreterStatus.NotStarted) {
|
||||||
|
projectGraphService.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
return projectGraphService;
|
return projectGraphService;
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { getEnvironmentConfig } from './hooks/use-environment-config';
|
|||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import { ProjectGraphClientResponse } from 'nx/src/command-line/dep-graph';
|
import { ProjectGraphClientResponse } from 'nx/src/command-line/dep-graph';
|
||||||
import { getProjectGraphDataService } from './hooks/get-project-graph-data-service';
|
import { getProjectGraphDataService } from './hooks/get-project-graph-data-service';
|
||||||
import { getProjectGraphService } from './machines/get-services';
|
|
||||||
|
|
||||||
const { appConfig } = getEnvironmentConfig();
|
const { appConfig } = getEnvironmentConfig();
|
||||||
const projectGraphDataService = getProjectGraphDataService();
|
const projectGraphDataService = getProjectGraphDataService();
|
||||||
@ -19,19 +18,21 @@ export function getRoutesForEnvironment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDataLoader = async (selectedProjectId: string) => {
|
const workspaceDataLoader = async (selectedWorkspaceId: string) => {
|
||||||
const projectInfo = appConfig.projects.find(
|
const workspaceInfo = appConfig.workspaces.find(
|
||||||
(graph) => graph.id === selectedProjectId
|
(graph) => graph.id === selectedWorkspaceId
|
||||||
);
|
);
|
||||||
|
|
||||||
const projectGraph: ProjectGraphClientResponse =
|
const projectGraph: ProjectGraphClientResponse =
|
||||||
await projectGraphDataService.getProjectGraph(projectInfo.projectGraphUrl);
|
await projectGraphDataService.getProjectGraph(
|
||||||
|
workspaceInfo.projectGraphUrl
|
||||||
|
);
|
||||||
|
|
||||||
return projectGraph;
|
return projectGraph;
|
||||||
};
|
};
|
||||||
|
|
||||||
const taskDataLoader = async (selectedProjectId: string) => {
|
const taskDataLoader = async (selectedProjectId: string) => {
|
||||||
const projectInfo = appConfig.projects.find(
|
const projectInfo = appConfig.workspaces.find(
|
||||||
(graph) => graph.id === selectedProjectId
|
(graph) => graph.id === selectedProjectId
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -41,9 +42,7 @@ const taskDataLoader = async (selectedProjectId: string) => {
|
|||||||
const childRoutes: RouteObject[] = [
|
const childRoutes: RouteObject[] = [
|
||||||
{
|
{
|
||||||
path: 'projects',
|
path: 'projects',
|
||||||
loader: () => {
|
loader: () => {},
|
||||||
getProjectGraphService().start();
|
|
||||||
},
|
|
||||||
element: <ProjectsSidebar />,
|
element: <ProjectsSidebar />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -54,10 +53,8 @@ const childRoutes: RouteObject[] = [
|
|||||||
return redirect(`/projects`);
|
return redirect(`/projects`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getProjectGraphService().stop();
|
|
||||||
|
|
||||||
const selectedProjectId =
|
const selectedProjectId =
|
||||||
params.selectedProjectId ?? appConfig.defaultProject;
|
params.selectedProjectId ?? appConfig.defaultWorkspaceId;
|
||||||
return taskDataLoader(selectedProjectId);
|
return taskDataLoader(selectedProjectId);
|
||||||
},
|
},
|
||||||
path: 'tasks',
|
path: 'tasks',
|
||||||
@ -68,7 +65,7 @@ const childRoutes: RouteObject[] = [
|
|||||||
element: <TasksSidebar />,
|
element: <TasksSidebar />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':selectedTask',
|
path: ':selectedTaskId',
|
||||||
element: <TasksSidebar />,
|
element: <TasksSidebar />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -84,17 +81,17 @@ export const devRoutes: RouteObject[] = [
|
|||||||
loader: async ({ request, params }) => {
|
loader: async ({ request, params }) => {
|
||||||
const { search } = new URL(request.url);
|
const { search } = new URL(request.url);
|
||||||
|
|
||||||
return redirect(`/${appConfig.defaultProject}/projects${search}`);
|
return redirect(`/${appConfig.defaultWorkspaceId}/projects${search}`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':selectedProjectId',
|
path: ':selectedProjectId',
|
||||||
id: 'SelectedProject',
|
id: 'selectedWorkspace',
|
||||||
element: <Shell />,
|
element: <Shell />,
|
||||||
loader: async ({ request, params }) => {
|
loader: async ({ request, params }) => {
|
||||||
const selectedProjectId =
|
const selectedProjectId =
|
||||||
params.selectedProjectId ?? appConfig.defaultProject;
|
params.selectedProjectId ?? appConfig.defaultWorkspaceId;
|
||||||
return projectDataLoader(selectedProjectId);
|
return workspaceDataLoader(selectedProjectId);
|
||||||
},
|
},
|
||||||
children: childRoutes,
|
children: childRoutes,
|
||||||
},
|
},
|
||||||
@ -105,18 +102,19 @@ export const devRoutes: RouteObject[] = [
|
|||||||
export const releaseRoutes: RouteObject[] = [
|
export const releaseRoutes: RouteObject[] = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
id: 'SelectedProject',
|
id: 'selectedWorkspace',
|
||||||
loader: async ({ request, params }) => {
|
loader: async ({ request, params }) => {
|
||||||
const selectedProjectId =
|
const selectedWorkspaceId = appConfig.defaultWorkspaceId;
|
||||||
params.selectedProjectId ?? appConfig.defaultProject;
|
return workspaceDataLoader(selectedWorkspaceId);
|
||||||
return projectDataLoader(selectedProjectId);
|
|
||||||
},
|
},
|
||||||
element: <Shell />,
|
element: <Shell />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
index: true,
|
index: true,
|
||||||
loader: () => {
|
loader: ({ request }) => {
|
||||||
return redirect(`/projects/`);
|
const { search } = new URL(request.url);
|
||||||
|
|
||||||
|
return redirect(`/projects${search}`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...childRoutes,
|
...childRoutes,
|
||||||
|
|||||||
@ -20,7 +20,12 @@ import {
|
|||||||
projectIsSelectedSelector,
|
projectIsSelectedSelector,
|
||||||
} from './feature-projects/machines/selectors';
|
} from './feature-projects/machines/selectors';
|
||||||
import { selectValueByThemeStatic } from './theme-resolver';
|
import { selectValueByThemeStatic } from './theme-resolver';
|
||||||
import { Outlet, useLoaderData, useNavigate } from 'react-router-dom';
|
import {
|
||||||
|
Outlet,
|
||||||
|
useLoaderData,
|
||||||
|
useNavigate,
|
||||||
|
useParams,
|
||||||
|
} from 'react-router-dom';
|
||||||
import ThemePanel from './feature-projects/panels/theme-panel';
|
import ThemePanel from './feature-projects/panels/theme-panel';
|
||||||
import Dropdown from './ui-components/dropdown';
|
import Dropdown from './ui-components/dropdown';
|
||||||
import { useCurrentPath } from './hooks/use-current-path';
|
import { useCurrentPath } from './hooks/use-current-path';
|
||||||
@ -32,20 +37,15 @@ import TooltipDisplay from './ui-tooltips/graph-tooltip-display';
|
|||||||
export function Shell(): JSX.Element {
|
export function Shell(): JSX.Element {
|
||||||
const projectGraphService = getProjectGraphService();
|
const projectGraphService = getProjectGraphService();
|
||||||
|
|
||||||
const projectGraphDataService = getProjectGraphDataService();
|
|
||||||
const environment = useEnvironmentConfig();
|
const environment = useEnvironmentConfig();
|
||||||
const lastPerfReport = useProjectGraphSelector(lastPerfReportSelector);
|
const lastPerfReport = useProjectGraphSelector(lastPerfReportSelector);
|
||||||
const projectIsSelected = useProjectGraphSelector(projectIsSelectedSelector);
|
const projectIsSelected = useProjectGraphSelector(projectIsSelectedSelector);
|
||||||
const taskIsSelected = true;
|
|
||||||
const environmentConfig = useEnvironmentConfig();
|
const environmentConfig = useEnvironmentConfig();
|
||||||
|
|
||||||
const [selectedProjectId, setSelectedProjectId] = useState<string>(
|
|
||||||
environment.appConfig.defaultProject
|
|
||||||
);
|
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const currentPath = useCurrentPath();
|
const currentPath = useCurrentPath();
|
||||||
|
const { selectedProjectId, selectedTaskId } = useParams();
|
||||||
|
const taskIsSelected = !!selectedTaskId;
|
||||||
const currentRoute = currentPath.currentPath;
|
const currentRoute = currentPath.currentPath;
|
||||||
|
|
||||||
const topLevelRoute = currentRoute.startsWith('/tasks')
|
const topLevelRoute = currentRoute.startsWith('/tasks')
|
||||||
@ -67,40 +67,6 @@ export function Shell(): JSX.Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const routeData = useLoaderData() as ProjectGraphClientResponse;
|
const routeData = useLoaderData() as ProjectGraphClientResponse;
|
||||||
useEffect(() => {
|
|
||||||
projectGraphService.send({
|
|
||||||
type: 'setProjects',
|
|
||||||
projects: routeData.projects,
|
|
||||||
dependencies: routeData.dependencies,
|
|
||||||
affectedProjects: routeData.affected,
|
|
||||||
workspaceLayout: routeData.layout,
|
|
||||||
});
|
|
||||||
}, [routeData]);
|
|
||||||
|
|
||||||
useIntervalWhen(
|
|
||||||
() => {
|
|
||||||
const projectInfo = environment.appConfig.projects.find(
|
|
||||||
(graph) => graph.id === selectedProjectId
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetchProjectGraph = async () => {
|
|
||||||
const project: ProjectGraphClientResponse =
|
|
||||||
await projectGraphDataService.getProjectGraph(
|
|
||||||
projectInfo.projectGraphUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
projectGraphService.send({
|
|
||||||
type: 'updateGraph',
|
|
||||||
projects: project.projects,
|
|
||||||
dependencies: project.dependencies,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchProjectGraph();
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
environment.watch
|
|
||||||
);
|
|
||||||
|
|
||||||
function downloadImage() {
|
function downloadImage() {
|
||||||
const graph = getGraphService();
|
const graph = getGraphService();
|
||||||
@ -202,7 +168,7 @@ export function Shell(): JSX.Element {
|
|||||||
>
|
>
|
||||||
{environment.appConfig.showDebugger ? (
|
{environment.appConfig.showDebugger ? (
|
||||||
<DebuggerPanel
|
<DebuggerPanel
|
||||||
projects={environment.appConfig.projects}
|
projects={environment.appConfig.workspaces}
|
||||||
selectedProject={selectedProjectId}
|
selectedProject={selectedProjectId}
|
||||||
lastPerfReport={lastPerfReport}
|
lastPerfReport={lastPerfReport}
|
||||||
selectedProjectChange={projectChange}
|
selectedProjectChange={projectChange}
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||||
|
import CheckboxPanel from './checkbox-panel';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: CheckboxPanel,
|
||||||
|
title: 'Shared/CheckboxPanel',
|
||||||
|
argTypes: { checkChanged: { action: 'checkChanged' } },
|
||||||
|
} as ComponentMeta<typeof CheckboxPanel>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof CheckboxPanel> = (args) => (
|
||||||
|
<CheckboxPanel {...args} />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Primary = Template.bind({});
|
||||||
|
Primary.args = {
|
||||||
|
checked: false,
|
||||||
|
name: 'option-to-check',
|
||||||
|
label: 'Option to check',
|
||||||
|
description: 'You can check this option.',
|
||||||
|
};
|
||||||
42
graph/client/src/app/ui-components/checkbox-panel.tsx
Normal file
42
graph/client/src/app/ui-components/checkbox-panel.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
export interface CheckboxPanelProps {
|
||||||
|
checked: boolean;
|
||||||
|
checkChanged: (checked: boolean) => void;
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CheckboxPanel = memo(
|
||||||
|
({ checked, checkChanged, label, description, name }: CheckboxPanelProps) => {
|
||||||
|
return (
|
||||||
|
<div className="mt-8 px-4">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex h-5 items-center">
|
||||||
|
<input
|
||||||
|
id={name}
|
||||||
|
name={name}
|
||||||
|
value={name}
|
||||||
|
type="checkbox"
|
||||||
|
className="h-4 w-4 accent-blue-500 dark:accent-sky-500"
|
||||||
|
onChange={(event) => checkChanged(event.target.checked)}
|
||||||
|
checked={checked}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 text-sm">
|
||||||
|
<label
|
||||||
|
htmlFor={name}
|
||||||
|
className="cursor-pointer font-medium text-slate-600 dark:text-slate-400"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
<p className="text-slate-400 dark:text-slate-500">{description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CheckboxPanel;
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { GraphListItem, GraphPerfReport } from '../interfaces';
|
import { WorkspaceData, GraphPerfReport } from '../interfaces';
|
||||||
import Dropdown from './dropdown';
|
import Dropdown from './dropdown';
|
||||||
|
|
||||||
export interface DebuggerPanelProps {
|
export interface DebuggerPanelProps {
|
||||||
projects: GraphListItem[];
|
projects: WorkspaceData[];
|
||||||
selectedProject: string;
|
selectedProject: string;
|
||||||
selectedProjectChange: (projectName: string) => void;
|
selectedProjectChange: (projectName: string) => void;
|
||||||
lastPerfReport: GraphPerfReport;
|
lastPerfReport: GraphPerfReport;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ window.useXstateInspect = false;
|
|||||||
window.appConfig = {
|
window.appConfig = {
|
||||||
showDebugger: true,
|
showDebugger: true,
|
||||||
showExperimentalFeatures: true,
|
showExperimentalFeatures: true,
|
||||||
projects: [
|
workspaces: [
|
||||||
{
|
{
|
||||||
id: 'e2e',
|
id: 'e2e',
|
||||||
label: 'e2e',
|
label: 'e2e',
|
||||||
@ -20,5 +20,5 @@ window.appConfig = {
|
|||||||
taskGraphUrl: 'assets/task-graphs/affected.json',
|
taskGraphUrl: 'assets/task-graphs/affected.json',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
defaultProject: 'e2e',
|
defaultWorkspaceId: 'e2e',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,7 @@ window.useXstateInspect = false;
|
|||||||
window.appConfig = {
|
window.appConfig = {
|
||||||
showDebugger: false,
|
showDebugger: false,
|
||||||
showExperimentalFeatures: false,
|
showExperimentalFeatures: false,
|
||||||
projects: [
|
workspaces: [
|
||||||
{
|
{
|
||||||
id: 'local',
|
id: 'local',
|
||||||
label: 'local',
|
label: 'local',
|
||||||
@ -14,5 +14,5 @@ window.appConfig = {
|
|||||||
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
defaultProject: 'local',
|
defaultWorkspaceId: 'local',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,7 +7,7 @@ window.localMode = 'build';
|
|||||||
window.appConfig = {
|
window.appConfig = {
|
||||||
showDebugger: false,
|
showDebugger: false,
|
||||||
showExperimentalFeatures: false,
|
showExperimentalFeatures: false,
|
||||||
projects: [
|
workspaces: [
|
||||||
{
|
{
|
||||||
id: 'local',
|
id: 'local',
|
||||||
label: 'local',
|
label: 'local',
|
||||||
@ -15,7 +15,7 @@ window.appConfig = {
|
|||||||
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
defaultProject: 'local',
|
defaultWorkspaceId: 'local',
|
||||||
};
|
};
|
||||||
|
|
||||||
window.projectGraphResponse = {
|
window.projectGraphResponse = {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ window.useXstateInspect = false;
|
|||||||
window.appConfig = {
|
window.appConfig = {
|
||||||
showDebugger: false,
|
showDebugger: false,
|
||||||
showExperimentalFeatures: false,
|
showExperimentalFeatures: false,
|
||||||
projects: [
|
workspaces: [
|
||||||
{
|
{
|
||||||
id: 'local',
|
id: 'local',
|
||||||
label: 'local',
|
label: 'local',
|
||||||
@ -14,5 +14,5 @@ window.appConfig = {
|
|||||||
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
defaultProject: 'local',
|
defaultWorkspaceId: 'local',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,7 @@ window.useXstateInspect = false;
|
|||||||
window.appConfig = {
|
window.appConfig = {
|
||||||
showDebugger: false,
|
showDebugger: false,
|
||||||
showExperimentalFeatures: true,
|
showExperimentalFeatures: true,
|
||||||
projects: [
|
workspaces: [
|
||||||
{
|
{
|
||||||
id: 'local',
|
id: 'local',
|
||||||
label: 'local',
|
label: 'local',
|
||||||
@ -14,5 +14,5 @@ window.appConfig = {
|
|||||||
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
defaultProject: 'local',
|
defaultWorkspaceId: 'local',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -216,6 +216,12 @@ export class GraphService {
|
|||||||
break;
|
break;
|
||||||
case 'notifyTaskGraphDeselectTask':
|
case 'notifyTaskGraphDeselectTask':
|
||||||
elementsToSendToRender = this.taskTraversalGraph.deselectTask();
|
elementsToSendToRender = this.taskTraversalGraph.deselectTask();
|
||||||
|
break;
|
||||||
|
case 'setGroupByProject':
|
||||||
|
elementsToSendToRender = this.taskTraversalGraph.setGroupByProject(
|
||||||
|
event.groupByProject
|
||||||
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -124,4 +124,8 @@ export type TaskGraphRenderEvents =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'notifyTaskGraphDeselectTask';
|
type: 'notifyTaskGraphDeselectTask';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'setGroupByProject';
|
||||||
|
groupByProject: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -91,6 +91,13 @@ const highlightedNodes: Stylesheet = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const taskNodes: Stylesheet = {
|
||||||
|
selector: 'node.task',
|
||||||
|
style: {
|
||||||
|
label: 'data(label)',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const transparentProjectNodes: Stylesheet = {
|
const transparentProjectNodes: Stylesheet = {
|
||||||
selector: 'node.transparent:childless',
|
selector: 'node.transparent:childless',
|
||||||
style: { opacity: 0.5 },
|
style: { opacity: 0.5 },
|
||||||
@ -113,4 +120,5 @@ export const nodeStyles = [
|
|||||||
highlightedNodes,
|
highlightedNodes,
|
||||||
transparentProjectNodes,
|
transparentProjectNodes,
|
||||||
transparentParentNodes,
|
transparentParentNodes,
|
||||||
|
taskNodes,
|
||||||
];
|
];
|
||||||
|
|||||||
@ -46,29 +46,12 @@ export class RenderGraph {
|
|||||||
|
|
||||||
set theme(theme: 'light' | 'dark') {
|
set theme(theme: 'light' | 'dark') {
|
||||||
this._theme = theme;
|
this._theme = theme;
|
||||||
|
this.render();
|
||||||
if (this.cy) {
|
|
||||||
this.cy.unmount();
|
|
||||||
const useDarkMode = theme === 'dark';
|
|
||||||
|
|
||||||
this.cy.scratch(darkModeScratchKey, useDarkMode);
|
|
||||||
this.cy.elements().scratch(darkModeScratchKey, useDarkMode);
|
|
||||||
|
|
||||||
this.cy.mount(this.activeContainer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set rankDir(rankDir: 'LR' | 'TB') {
|
set rankDir(rankDir: 'LR' | 'TB') {
|
||||||
this._rankDir = rankDir;
|
this._rankDir = rankDir;
|
||||||
if (this.cy) {
|
this.render();
|
||||||
const elements = this.cy.elements();
|
|
||||||
elements
|
|
||||||
.layout({
|
|
||||||
...cytoscapeDagreConfig,
|
|
||||||
...{ rankDir: rankDir },
|
|
||||||
} as CytoscapeDagreConfig)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeContainer() {
|
get activeContainer() {
|
||||||
|
|||||||
@ -4,26 +4,33 @@ import * as cy from 'cytoscape';
|
|||||||
|
|
||||||
export interface TaskNodeDataDefinition extends cy.NodeDataDefinition {
|
export interface TaskNodeDataDefinition extends cy.NodeDataDefinition {
|
||||||
id: string;
|
id: string;
|
||||||
|
label: string;
|
||||||
executor: string;
|
executor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TaskNode {
|
export class TaskNode {
|
||||||
constructor(private task: Task, private project: ProjectGraphProjectNode) {}
|
constructor(private task: Task, private project: ProjectGraphProjectNode) {}
|
||||||
|
|
||||||
getCytoscapeNodeDef(): cy.NodeDefinition {
|
getCytoscapeNodeDef(groupByProject: boolean): cy.NodeDefinition {
|
||||||
return {
|
return {
|
||||||
group: 'nodes',
|
group: 'nodes',
|
||||||
data: this.getData(),
|
classes: 'task',
|
||||||
|
data: this.getData(groupByProject),
|
||||||
selectable: false,
|
selectable: false,
|
||||||
grabbable: false,
|
grabbable: false,
|
||||||
pannable: true,
|
pannable: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getData(): TaskNodeDataDefinition {
|
private getData(groupByProject: boolean): TaskNodeDataDefinition {
|
||||||
|
const label = groupByProject
|
||||||
|
? this.task.id.split(':').slice(1).join(':')
|
||||||
|
: this.task.id;
|
||||||
return {
|
return {
|
||||||
id: this.task.id,
|
id: this.task.id,
|
||||||
executor: 'placeholder',
|
label,
|
||||||
|
executor: this.project.data.targets[this.task.target.target].executor,
|
||||||
|
parent: groupByProject ? this.task.target.project : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,22 +4,44 @@ import { TaskGraphRecord } from '../interfaces';
|
|||||||
import { TaskNode } from './task-node';
|
import { TaskNode } from './task-node';
|
||||||
import { TaskEdge } from './task-edge';
|
import { TaskEdge } from './task-edge';
|
||||||
import cytoscape, { Core } from 'cytoscape';
|
import cytoscape, { Core } from 'cytoscape';
|
||||||
|
import { ParentNode } from './parent-node';
|
||||||
|
|
||||||
export class TaskTraversalGraph {
|
export class TaskTraversalGraph {
|
||||||
private projects: ProjectGraphProjectNode[] = [];
|
private projects: ProjectGraphProjectNode[] = [];
|
||||||
private taskGraphs: TaskGraphRecord = {};
|
private taskGraphs: TaskGraphRecord = {};
|
||||||
private cy: Core;
|
private cy: Core;
|
||||||
|
private selectedTask: string;
|
||||||
|
private groupByProject: boolean = false;
|
||||||
|
|
||||||
setProjects(
|
setProjects(
|
||||||
projects: ProjectGraphProjectNode[],
|
projects: ProjectGraphProjectNode[],
|
||||||
taskGraphs: TaskGraphRecord
|
taskGraphs: TaskGraphRecord
|
||||||
) {
|
) {
|
||||||
|
this.selectedTask = null;
|
||||||
this.projects = projects;
|
this.projects = projects;
|
||||||
this.taskGraphs = taskGraphs;
|
this.taskGraphs = taskGraphs;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectTask(taskId: string) {
|
selectTask(taskId: string) {
|
||||||
this.createElements(taskId);
|
this.selectedTask = taskId;
|
||||||
|
this.createElements(taskId, this.groupByProject);
|
||||||
|
|
||||||
|
return this.cy.elements();
|
||||||
|
}
|
||||||
|
|
||||||
|
setGroupByProject(groupByProject: boolean) {
|
||||||
|
this.groupByProject = groupByProject;
|
||||||
|
|
||||||
|
if (this.selectedTask) {
|
||||||
|
this.createElements(this.selectedTask, groupByProject);
|
||||||
|
} else {
|
||||||
|
this.cy = cytoscape({
|
||||||
|
headless: true,
|
||||||
|
elements: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.cy.elements();
|
||||||
|
}
|
||||||
|
|
||||||
return this.cy.elements();
|
return this.cy.elements();
|
||||||
}
|
}
|
||||||
@ -33,31 +55,43 @@ export class TaskTraversalGraph {
|
|||||||
return this.cy.elements();
|
return this.cy.elements();
|
||||||
}
|
}
|
||||||
|
|
||||||
private createElements(taskId: string) {
|
private createElements(taskId: string, groupByFolder: boolean) {
|
||||||
const [projectName, target, configuration] = taskId.split(':');
|
|
||||||
const taskGraph = this.taskGraphs[taskId];
|
const taskGraph = this.taskGraphs[taskId];
|
||||||
|
|
||||||
if (taskGraph === undefined) {
|
if (taskGraph === undefined) {
|
||||||
throw new Error(`Could not find task graph for ${taskId}`);
|
throw new Error(`Could not find task graph for ${taskId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const project = this.projects.find(
|
|
||||||
(project) => project.name === projectName
|
|
||||||
);
|
|
||||||
|
|
||||||
if (project === undefined) {
|
|
||||||
throw new Error(`Could not find project ${projectName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const taskElements = [];
|
const taskElements = [];
|
||||||
|
|
||||||
|
const parents: Record<
|
||||||
|
string,
|
||||||
|
{ id: string; parentId: string; label: string }
|
||||||
|
> = {};
|
||||||
|
|
||||||
for (let taskName in taskGraph.tasks) {
|
for (let taskName in taskGraph.tasks) {
|
||||||
taskElements.push(
|
const task = taskGraph.tasks[taskName];
|
||||||
new TaskNode(
|
const project = this.projects.find(
|
||||||
taskGraph.tasks[taskName],
|
(project) => project.name === task.target.project
|
||||||
this.projects[taskGraph.tasks[taskName].target.project]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (project === undefined) {
|
||||||
|
throw new Error(`Could not find project ${project.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
taskElements.push(new TaskNode(taskGraph.tasks[taskName], project));
|
||||||
|
|
||||||
|
if (groupByFolder) {
|
||||||
|
parents[project.name] = {
|
||||||
|
id: project.name,
|
||||||
|
parentId: null,
|
||||||
|
label: project.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let parent in parents) {
|
||||||
|
taskElements.push(new ParentNode(parents[parent]));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let topDep in taskGraph.dependencies) {
|
for (let topDep in taskGraph.dependencies) {
|
||||||
@ -68,7 +102,9 @@ export class TaskTraversalGraph {
|
|||||||
|
|
||||||
this.cy = cytoscape({
|
this.cy = cytoscape({
|
||||||
headless: true,
|
headless: true,
|
||||||
elements: taskElements.map((element) => element.getCytoscapeNodeDef()),
|
elements: taskElements.map((element) =>
|
||||||
|
element.getCytoscapeNodeDef(groupByFolder)
|
||||||
|
),
|
||||||
boxSelectionEnabled: false,
|
boxSelectionEnabled: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,7 +73,7 @@ function buildEnvironmentJs(
|
|||||||
window.appConfig = {
|
window.appConfig = {
|
||||||
showDebugger: false,
|
showDebugger: false,
|
||||||
showExperimentalFeatures: false,
|
showExperimentalFeatures: false,
|
||||||
projects: [
|
workspaces: [
|
||||||
{
|
{
|
||||||
id: 'local',
|
id: 'local',
|
||||||
label: 'local',
|
label: 'local',
|
||||||
@ -81,7 +81,7 @@ function buildEnvironmentJs(
|
|||||||
taskGraphUrl: 'task-graph.json'
|
taskGraphUrl: 'task-graph.json'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
defaultProject: 'local',
|
defaultWorkspaceId: 'local',
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -408,7 +408,7 @@ async function startServer(
|
|||||||
params.append('groupByFolder', 'true');
|
params.append('groupByFolder', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
open(`${url}?${params.toString()}`);
|
open(`${url}/projects?${params.toString()}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { readdirSync, writeFileSync } from 'fs';
|
|||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
|
|
||||||
function generateFileContent(
|
function generateFileContent(
|
||||||
projects: { id: string; label: string; url: string }[]
|
workspaces: { id: string; label: string; url: string }[]
|
||||||
) {
|
) {
|
||||||
return `
|
return `
|
||||||
window.exclude = [];
|
window.exclude = [];
|
||||||
@ -15,8 +15,8 @@ function generateFileContent(
|
|||||||
window.appConfig = {
|
window.appConfig = {
|
||||||
showDebugger: true,
|
showDebugger: true,
|
||||||
showExperimentalFeatures: true,
|
showExperimentalFeatures: true,
|
||||||
projects: ${JSON.stringify(projects)},
|
workspaces: ${JSON.stringify(workspaces)},
|
||||||
defaultProject: '${projects[0].id}',
|
defaultWorkspaceId: '${workspaces[0].id}',
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user