feat(graph): display expanded task inputs (#19597)
This commit is contained in:
parent
92e05003cc
commit
c727a22530
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,6 +18,7 @@ jest.debug.config.js
|
||||
/graph/client/src/assets/dev/environment.js
|
||||
/graph/client/src/assets/generated-project-graphs
|
||||
/graph/client/src/assets/generated-task-graphs
|
||||
/graph/client/src/assets/generated-task-inputs
|
||||
/nx-dev/nx-dev/public/documentation
|
||||
/nx-dev/nx-dev/public/images/open-graph
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { parseJson } from '@nx/devkit';
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
@ -8,6 +9,7 @@ import {
|
||||
setMaxWorkers,
|
||||
uniq,
|
||||
updateFile,
|
||||
readFile,
|
||||
updateJson,
|
||||
} from '@nx/e2e/utils';
|
||||
import { join } from 'path';
|
||||
@ -321,4 +323,108 @@ describe('Extra Nx Misc Tests', () => {
|
||||
expect(unitTestsOutput).toContain('Successfully ran target test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('task graph inputs', () => {
|
||||
const readExpandedTaskInputResponse = (): Record<
|
||||
string,
|
||||
Record<string, string[]>
|
||||
> =>
|
||||
parseJson(
|
||||
readFile('static/environment.js').match(
|
||||
/window\.expandedTaskInputsResponse\s*=\s*(.*?);/
|
||||
)[1]
|
||||
);
|
||||
|
||||
const baseLib = 'lib-base-123';
|
||||
beforeAll(() => {
|
||||
runCLI(`generate @nx/js:lib ${baseLib}`);
|
||||
});
|
||||
|
||||
it('should correctly expand default task inputs', () => {
|
||||
runCLI('graph --file=graph.html');
|
||||
|
||||
expect(readExpandedTaskInputResponse()[`${baseLib}:build`])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"external": [
|
||||
"npm:@nx/js",
|
||||
"npm:tslib",
|
||||
],
|
||||
"general": [
|
||||
".gitignore",
|
||||
"nx.json",
|
||||
],
|
||||
"lib-base-123": [
|
||||
"libs/lib-base-123/README.md",
|
||||
"libs/lib-base-123/package.json",
|
||||
"libs/lib-base-123/project.json",
|
||||
"libs/lib-base-123/src/index.ts",
|
||||
"libs/lib-base-123/src/lib/lib-base-123.ts",
|
||||
"libs/lib-base-123/tsconfig.json",
|
||||
"libs/lib-base-123/tsconfig.lib.json",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should correctly expand dependent task inputs', () => {
|
||||
const dependentLib = 'lib-dependent-123';
|
||||
runCLI(`generate @nx/js:lib ${dependentLib}`);
|
||||
|
||||
updateJson(join('libs', baseLib, 'project.json'), (config) => {
|
||||
config.targets['build'].inputs = ['default', '^default'];
|
||||
config.implicitDependencies = [dependentLib];
|
||||
return config;
|
||||
});
|
||||
|
||||
updateJson('nx.json', (json) => {
|
||||
json.namedInputs = {
|
||||
...json.namedInputs,
|
||||
default: ['{projectRoot}/**/*'],
|
||||
};
|
||||
return json;
|
||||
});
|
||||
runCLI('graph --file=graph.html');
|
||||
|
||||
expect(readExpandedTaskInputResponse()[`${baseLib}:build`])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"external": [
|
||||
"npm:@nx/js",
|
||||
"npm:tslib",
|
||||
],
|
||||
"general": [
|
||||
".gitignore",
|
||||
"nx.json",
|
||||
],
|
||||
"lib-base-123": [
|
||||
"libs/lib-base-123/.eslintrc.json",
|
||||
"libs/lib-base-123/README.md",
|
||||
"libs/lib-base-123/jest.config.ts",
|
||||
"libs/lib-base-123/package.json",
|
||||
"libs/lib-base-123/project.json",
|
||||
"libs/lib-base-123/src/index.ts",
|
||||
"libs/lib-base-123/src/lib/lib-base-123.spec.ts",
|
||||
"libs/lib-base-123/src/lib/lib-base-123.ts",
|
||||
"libs/lib-base-123/tsconfig.json",
|
||||
"libs/lib-base-123/tsconfig.lib.json",
|
||||
"libs/lib-base-123/tsconfig.spec.json",
|
||||
],
|
||||
"lib-dependent-123": [
|
||||
"libs/lib-dependent-123/.eslintrc.json",
|
||||
"libs/lib-dependent-123/README.md",
|
||||
"libs/lib-dependent-123/jest.config.ts",
|
||||
"libs/lib-dependent-123/package.json",
|
||||
"libs/lib-dependent-123/project.json",
|
||||
"libs/lib-dependent-123/src/index.ts",
|
||||
"libs/lib-dependent-123/src/lib/lib-dependent-123.spec.ts",
|
||||
"libs/lib-dependent-123/src/lib/lib-dependent-123.ts",
|
||||
"libs/lib-dependent-123/tsconfig.json",
|
||||
"libs/lib-dependent-123/tsconfig.lib.json",
|
||||
"libs/lib-dependent-123/tsconfig.spec.json",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
1
graph/client-e2e/cypress/downloads/downloads.html
Normal file
1
graph/client-e2e/cypress/downloads/downloads.html
Normal file
@ -0,0 +1 @@
|
||||
Cr24
|
||||
@ -18,11 +18,12 @@ import {
|
||||
getToggleAllButtonForFolder,
|
||||
getUncheckedProjectItems,
|
||||
getUnfocusProjectButton,
|
||||
openTooltipForNode,
|
||||
} from '../support/app.po';
|
||||
|
||||
import * as affectedJson from '../fixtures/affected.json';
|
||||
import { testProjectsRoutes, testTaskRoutes } from '../support/routing-tests';
|
||||
import * as nxExamplesJson from '../fixtures/nx-examples-project-graph.json';
|
||||
import * as nxExamplesTaskInputs from '../fixtures/nx-examples-task-inputs.json';
|
||||
|
||||
describe('dev mode - task graph', () => {
|
||||
before(() => {
|
||||
@ -183,4 +184,54 @@ describe('dev mode - task graph', () => {
|
||||
// and also new /projects route
|
||||
testTaskRoutes('browser', ['/e2e/tasks']);
|
||||
});
|
||||
|
||||
describe('file inputs', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/task-inputs.json*',
|
||||
},
|
||||
async (req) => {
|
||||
// Extract the desired query parameter
|
||||
const taskId = req.url.split('taskId=')[1];
|
||||
// Load the fixture data and find the property based on the query parameter
|
||||
|
||||
const expandedInputs = nxExamplesTaskInputs[taskId];
|
||||
|
||||
// Reply with the selected property
|
||||
req.reply({
|
||||
body: expandedInputs,
|
||||
});
|
||||
}
|
||||
).as('getTaskInputs');
|
||||
});
|
||||
it('should display input files', () => {
|
||||
getSelectTargetDropdown().select('build', { force: true });
|
||||
cy.get('[data-project="cart"]').click({
|
||||
force: true,
|
||||
});
|
||||
openTooltipForNode('cart:build');
|
||||
cy.get('[data-cy="inputs-accordion"]').click();
|
||||
|
||||
cy.get('[data-cy="input-list-entry"]').should('have.length', 18);
|
||||
const expectedSections = [
|
||||
'cart-cart-page',
|
||||
'shared-assets',
|
||||
'shared-header',
|
||||
'shared-styles',
|
||||
'External Inputs',
|
||||
];
|
||||
cy.get('[data-cy="input-section-entry"]').each((el, idx) => {
|
||||
expect(el.text()).to.equal(expectedSections[idx]);
|
||||
});
|
||||
|
||||
const sharedHeaderSelector =
|
||||
'[data-cy="input-section-entry"]:contains(shared-header)';
|
||||
cy.get(sharedHeaderSelector).click();
|
||||
cy.get(sharedHeaderSelector)
|
||||
.nextAll('[data-cy="input-list-entry"]')
|
||||
.should('have.length', 9);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
4369
graph/client-e2e/src/fixtures/nx-examples-task-inputs.json
Normal file
4369
graph/client-e2e/src/fixtures/nx-examples-task-inputs.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -45,3 +45,12 @@ export const getToggleAllButtonForFolder = (folderName: string) =>
|
||||
|
||||
export const getSelectTargetDropdown = () =>
|
||||
cy.get('[data-cy=selected-target-dropdown]');
|
||||
|
||||
export const openTooltipForNode = (nodeId: string) =>
|
||||
cy.window().then((window) => {
|
||||
// @ts-ignore - we will access private methods only in this e2e test
|
||||
const pos = window.externalApi.graphService.renderGraph.cy
|
||||
.$(nodeId)
|
||||
.renderedPosition();
|
||||
cy.get('#cytoscape-graph').click(pos.x, pos.y);
|
||||
});
|
||||
|
||||
@ -67,6 +67,7 @@
|
||||
"graph/client/src/assets/task-graphs/",
|
||||
"graph/client/src/assets/generated-project-graphs/",
|
||||
"graph/client/src/assets/generated-task-graphs/",
|
||||
"graph/client/src/assets/generated-task-inputs/",
|
||||
{
|
||||
"input": "graph/client/src/assets/dev",
|
||||
"output": "/",
|
||||
@ -79,6 +80,7 @@
|
||||
"graph/client/src/favicon.ico",
|
||||
"graph/client/src/assets/project-graphs/",
|
||||
"graph/client/src/assets/task-graphs/",
|
||||
"graph/client/src/assets/task-inputs/",
|
||||
{
|
||||
"input": "graph/client/src/assets/dev-e2e",
|
||||
"output": "/",
|
||||
|
||||
@ -250,7 +250,6 @@ export const projectGraphMachine = createMachine<
|
||||
setGraph: assign((ctx, event) => {
|
||||
if (event.type !== 'setProjects' && event.type !== 'updateGraph')
|
||||
return;
|
||||
|
||||
ctx.projects = event.projects;
|
||||
ctx.dependencies = event.dependencies;
|
||||
ctx.fileMap = event.fileMap;
|
||||
|
||||
@ -114,6 +114,7 @@ const mockAppConfig: AppConfig = {
|
||||
describe('dep-graph machine', () => {
|
||||
beforeEach(() => {
|
||||
window.appConfig = mockAppConfig;
|
||||
window.environment = 'release';
|
||||
});
|
||||
describe('initGraph', () => {
|
||||
it('should set projects, dependencies, and workspaceLayout', () => {
|
||||
|
||||
@ -305,7 +305,6 @@ export function ProjectsSidebar(): JSX.Element {
|
||||
await projectGraphDataService.getProjectGraph(
|
||||
projectInfo.projectGraphUrl
|
||||
);
|
||||
|
||||
projectGraphService.send({
|
||||
type: 'updateGraph',
|
||||
projects: response.projects,
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { TaskList } from './task-list';
|
||||
import {
|
||||
useNavigate,
|
||||
useParams,
|
||||
useRouteLoaderData,
|
||||
useSearchParams,
|
||||
} from 'react-router-dom';
|
||||
import { TaskList } from './task-list';
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import type {
|
||||
@ -12,14 +12,16 @@ import type {
|
||||
TaskGraphClientResponse,
|
||||
} from 'nx/src/command-line/graph/graph';
|
||||
/* eslint-enable @nx/enforce-module-boundaries */
|
||||
import { getGraphService } from '../machines/graph.service';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { getGraphService } from '../machines/graph.service';
|
||||
import { CheckboxPanel } from '../ui-components/checkbox-panel';
|
||||
|
||||
import { Dropdown } from '@nx/graph/ui-components';
|
||||
import { ShowHideAll } from '../ui-components/show-hide-all';
|
||||
import { useCurrentPath } from '../hooks/use-current-path';
|
||||
import { ShowHideAll } from '../ui-components/show-hide-all';
|
||||
import { createTaskName, useRouteConstructor } from '../util';
|
||||
import { GraphInteractionEvents } from '@nx/graph/ui-graph';
|
||||
import { getProjectGraphDataService } from '../hooks/get-project-graph-data-service';
|
||||
|
||||
export function TasksSidebar() {
|
||||
const graphService = getGraphService();
|
||||
|
||||
@ -8,6 +8,8 @@ import type {
|
||||
import { ProjectGraphService } from './interfaces';
|
||||
|
||||
export class FetchProjectGraphService implements ProjectGraphService {
|
||||
private taskInputsUrl: string;
|
||||
|
||||
async getHash(): Promise<string> {
|
||||
const request = new Request('currentHash', { mode: 'no-cors' });
|
||||
|
||||
@ -31,4 +33,22 @@ export class FetchProjectGraphService implements ProjectGraphService {
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
setTaskInputsUrl(url: string) {
|
||||
this.taskInputsUrl = url;
|
||||
}
|
||||
|
||||
async getExpandedTaskInputs(
|
||||
taskId: string
|
||||
): Promise<Record<string, string[]>> {
|
||||
if (!this.taskInputsUrl) {
|
||||
return {};
|
||||
}
|
||||
const request = new Request(`${this.taskInputsUrl}?taskId=${taskId}`, {
|
||||
mode: 'no-cors',
|
||||
});
|
||||
|
||||
const response = await fetch(request);
|
||||
return (await response.json())[taskId];
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ export interface WorkspaceData {
|
||||
label: string;
|
||||
projectGraphUrl: string;
|
||||
taskGraphUrl: string;
|
||||
taskInputsUrl: string;
|
||||
}
|
||||
|
||||
export interface WorkspaceLayout {
|
||||
@ -22,6 +23,8 @@ export interface ProjectGraphService {
|
||||
getHash: () => Promise<string>;
|
||||
getProjectGraph: (url: string) => Promise<ProjectGraphClientResponse>;
|
||||
getTaskGraph: (url: string) => Promise<TaskGraphClientResponse>;
|
||||
setTaskInputsUrl?: (url: string) => void;
|
||||
getExpandedTaskInputs?: (taskId: string) => Promise<Record<string, string[]>>;
|
||||
}
|
||||
|
||||
export interface Environment {
|
||||
|
||||
@ -19,4 +19,12 @@ export class LocalProjectGraphService implements ProjectGraphService {
|
||||
async getTaskGraph(url: string): Promise<TaskGraphClientResponse> {
|
||||
return new Promise((resolve) => resolve(window.taskGraphResponse));
|
||||
}
|
||||
|
||||
async getExpandedTaskInputs(
|
||||
taskId: string
|
||||
): Promise<Record<string, string[]>> {
|
||||
return new Promise((resolve) =>
|
||||
resolve(window.expandedTaskInputsResponse[taskId])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,21 @@
|
||||
import { GraphService } from '@nx/graph/ui-graph';
|
||||
import { selectValueByThemeStatic } from '../theme-resolver';
|
||||
import { getEnvironmentConfig } from '../hooks/use-environment-config';
|
||||
import { getProjectGraphDataService } from '../hooks/get-project-graph-data-service';
|
||||
|
||||
let graphService: GraphService;
|
||||
|
||||
export function getGraphService(): GraphService {
|
||||
const environment = getEnvironmentConfig();
|
||||
|
||||
if (!graphService) {
|
||||
const projectDataService = getProjectGraphDataService();
|
||||
graphService = new GraphService(
|
||||
'cytoscape-graph',
|
||||
selectValueByThemeStatic('dark', 'light'),
|
||||
environment.environment === 'nx-console' ? 'nx-console' : undefined
|
||||
environment.environment === 'nx-console' ? 'nx-console' : undefined,
|
||||
'TB',
|
||||
(taskId: string) => projectDataService.getExpandedTaskInputs(taskId)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -26,6 +26,8 @@ const workspaceDataLoader = async (selectedWorkspaceId: string) => {
|
||||
(graph) => graph.id === selectedWorkspaceId
|
||||
);
|
||||
|
||||
projectGraphDataService.setTaskInputsUrl?.(workspaceInfo.taskInputsUrl);
|
||||
|
||||
const projectGraph: ProjectGraphClientResponse =
|
||||
await projectGraphDataService.getProjectGraph(
|
||||
workspaceInfo.projectGraphUrl
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
Tooltip,
|
||||
} from '@nx/graph/ui-tooltips';
|
||||
import { ProjectNodeActions } from './project-node-actions';
|
||||
import { TaskNodeActions } from './task-node-actions';
|
||||
|
||||
const tooltipService = getTooltipService();
|
||||
|
||||
@ -29,7 +30,11 @@ export function TooltipDisplay() {
|
||||
tooltipToRender = <ProjectEdgeNodeTooltip {...currentTooltip.props} />;
|
||||
break;
|
||||
case 'taskNode':
|
||||
tooltipToRender = <TaskNodeTooltip {...currentTooltip.props} />;
|
||||
tooltipToRender = (
|
||||
<TaskNodeTooltip {...currentTooltip.props}>
|
||||
<TaskNodeActions {...currentTooltip.props} />
|
||||
</TaskNodeTooltip>
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
126
graph/client/src/app/ui-tooltips/task-node-actions.tsx
Normal file
126
graph/client/src/app/ui-tooltips/task-node-actions.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline';
|
||||
import { TaskNodeTooltipProps } from '@nx/graph/ui-tooltips';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function TaskNodeActions(props: TaskNodeTooltipProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
setIsOpen(false);
|
||||
}, [props.id]);
|
||||
const project = props.id.split(':')[0];
|
||||
return (
|
||||
<div className="overflow-auto w-full min-w-[350px] max-w-full rounded-md border border-slate-200 dark:border-slate-800 w-full">
|
||||
<div
|
||||
className="flex justify-between items-center w-full bg-slate-50 px-4 py-2 text-xs font-medium uppercase text-slate-500 dark:bg-slate-800 dark:text-slate-400"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
data-cy="inputs-accordion"
|
||||
>
|
||||
<span>Inputs</span>
|
||||
<span>
|
||||
{isOpen ? (
|
||||
<ChevronUpIcon className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<ul
|
||||
className={`max-h-[300px] divide-y divide-slate-200 overflow-auto dark:divide-slate-800 ${
|
||||
!isOpen && 'hidden'
|
||||
}`}
|
||||
>
|
||||
{Object.entries(props.inputs ?? {})
|
||||
.sort(compareInputSectionKeys(project))
|
||||
.map(([key, inputs]) => {
|
||||
if (!inputs.length) return undefined;
|
||||
if (key === 'general' || key === project) {
|
||||
return renderInputs(inputs);
|
||||
}
|
||||
if (key === 'external') {
|
||||
return InputAccordion({ section: 'External Inputs', inputs });
|
||||
}
|
||||
|
||||
return InputAccordion({ section: key, inputs });
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function InputAccordion({ section, inputs }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return [
|
||||
<li
|
||||
key={section}
|
||||
className="flex justify-between items-center whitespace-nowrap px-4 py-2 text-sm font-medium text-slate-800 dark:text-slate-300"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
data-cy="input-section-entry"
|
||||
>
|
||||
<span className="block truncate font-normal font-bold">{section}</span>
|
||||
<span>
|
||||
{isOpen ? (
|
||||
<ChevronUpIcon className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
)}
|
||||
</span>
|
||||
</li>,
|
||||
isOpen ? renderInputs(inputs) : undefined,
|
||||
];
|
||||
}
|
||||
|
||||
function renderInputs(inputs: string[]) {
|
||||
return inputs.map((input) => (
|
||||
<li
|
||||
key={input}
|
||||
className="whitespace-nowrap px-4 py-2 text-sm font-medium text-slate-800 dark:text-slate-300"
|
||||
title={input}
|
||||
data-cy="input-list-entry"
|
||||
>
|
||||
<span className="block truncate font-normal">{input}</span>
|
||||
</li>
|
||||
));
|
||||
}
|
||||
|
||||
function compareInputSectionKeys(project: string) {
|
||||
return ([keya]: [string, string[]], [keyb]: [string, string[]]) => {
|
||||
const first = 'general';
|
||||
const second = project;
|
||||
const last = 'external';
|
||||
|
||||
// Check if 'keya' and/or 'keyb' are one of the special strings
|
||||
if (
|
||||
keya === first ||
|
||||
keya === second ||
|
||||
keya === last ||
|
||||
keyb === first ||
|
||||
keyb === second ||
|
||||
keyb === last
|
||||
) {
|
||||
// If 'keya' is 'general', 'keya' should always be first
|
||||
if (keya === first) return -1;
|
||||
// If 'keyb' is 'general', 'keyb' should always be first
|
||||
if (keyb === first) return 1;
|
||||
// At this point, we know neither 'keya' nor 'keyb' are 'general'
|
||||
// If 'keya' is project, 'keya' should be second (i.e., before 'keyb' unless 'keyb' is 'general')
|
||||
if (keya === second) return -1;
|
||||
// If 'keyb' is project, 'keyb' should be second (i.e., before 'keya')
|
||||
if (keyb === second) return 1;
|
||||
// At this point, we know neither 'keya' nor 'keyb' are 'general' or project
|
||||
// If 'keya' is 'external', 'keya' should be last (i.e., after 'keyb')
|
||||
if (keya === last) return 1;
|
||||
// If 'keyb' is 'external', 'keyb' should be last (i.e., after 'keya')
|
||||
if (keyb === last) return -1;
|
||||
}
|
||||
|
||||
// If neither 'keya' nor 'b' are one of the special strings, sort alphabetically
|
||||
if (keya < keyb) {
|
||||
return -1;
|
||||
}
|
||||
if (keya > keyb) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
@ -12,12 +12,14 @@ window.appConfig = {
|
||||
label: 'e2e',
|
||||
projectGraphUrl: 'assets/project-graphs/e2e.json',
|
||||
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
||||
taskInputsUrl: 'assets/task-inputs/e2e.json',
|
||||
},
|
||||
{
|
||||
id: 'affected',
|
||||
label: 'affected',
|
||||
projectGraphUrl: 'assets/project-graphs/affected.json',
|
||||
taskGraphUrl: 'assets/task-graphs/affected.json',
|
||||
taskInputsUrl: 'assets/task-inputs/affected.json',
|
||||
},
|
||||
],
|
||||
defaultWorkspaceId: 'e2e',
|
||||
|
||||
@ -12,6 +12,7 @@ window.appConfig = {
|
||||
label: 'local',
|
||||
projectGraphUrl: 'assets/project-graphs/e2e.json',
|
||||
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
||||
taskInputsUrl: 'assets/task-inputs/e2e.json',
|
||||
},
|
||||
],
|
||||
defaultWorkspaceId: 'local',
|
||||
|
||||
9786
graph/client/src/assets/task-inputs/e2e-affected.json
Normal file
9786
graph/client/src/assets/task-inputs/e2e-affected.json
Normal file
File diff suppressed because it is too large
Load Diff
9786
graph/client/src/assets/task-inputs/e2e.json
Normal file
9786
graph/client/src/assets/task-inputs/e2e.json
Normal file
File diff suppressed because it is too large
Load Diff
2
graph/client/src/globals.d.ts
vendored
2
graph/client/src/globals.d.ts
vendored
@ -1,6 +1,7 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import type {
|
||||
ExpandedTaskInputsReponse,
|
||||
ProjectGraphClientResponse,
|
||||
TaskGraphClientResponse,
|
||||
} from 'nx/src/command-line/graph/graph';
|
||||
@ -15,6 +16,7 @@ export declare global {
|
||||
localMode: 'serve' | 'build';
|
||||
projectGraphResponse?: ProjectGraphClientResponse;
|
||||
taskGraphResponse?: TaskGraphClientResponse;
|
||||
expandedTaskInputsResponse?: ExpandedTaskInputsReponse;
|
||||
environment: 'dev' | 'watch' | 'release' | 'nx-console';
|
||||
appConfig: AppConfig;
|
||||
useXstateInspect: boolean;
|
||||
|
||||
@ -32,7 +32,10 @@ export class GraphService {
|
||||
container: string | HTMLElement,
|
||||
theme: 'light' | 'dark',
|
||||
public renderMode?: 'nx-console' | 'nx-docs',
|
||||
rankDir: 'TB' | 'LR' = 'TB'
|
||||
rankDir: 'TB' | 'LR' = 'TB',
|
||||
public getTaskInputs: (
|
||||
taskId: string
|
||||
) => Promise<Record<string, string[]>> = undefined
|
||||
) {
|
||||
use(cytoscapeDagre);
|
||||
use(popper);
|
||||
|
||||
@ -6,12 +6,13 @@ import {
|
||||
ProjectEdgeNodeTooltipProps,
|
||||
} from '@nx/graph/ui-tooltips';
|
||||
import { TooltipEvent } from './interfaces';
|
||||
import { GraphInteractionEvents } from './graph-interaction-events';
|
||||
|
||||
export class GraphTooltipService {
|
||||
private subscribers: Set<Function> = new Set();
|
||||
|
||||
constructor(graph: GraphService) {
|
||||
graph.listen((event) => {
|
||||
graph.listen((event: GraphInteractionEvents) => {
|
||||
switch (event.type) {
|
||||
case 'GraphRegenerated':
|
||||
this.hideAll();
|
||||
@ -49,6 +50,20 @@ export class GraphTooltipService {
|
||||
...event.data,
|
||||
runTaskCallback,
|
||||
});
|
||||
if (graph.getTaskInputs) {
|
||||
graph.getTaskInputs(event.data.id).then((inputs) => {
|
||||
if (
|
||||
this.currentTooltip.type === 'taskNode' &&
|
||||
this.currentTooltip.props.id === event.data.id
|
||||
) {
|
||||
this.openTaskNodeTooltip(event.ref, {
|
||||
...event.data,
|
||||
runTaskCallback,
|
||||
inputs,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'EdgeClick':
|
||||
const callback =
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
import { PlayIcon } from '@heroicons/react/24/outline';
|
||||
import { Tag } from '@nx/graph/ui-components';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface TaskNodeTooltipProps {
|
||||
id: string;
|
||||
executor: string;
|
||||
runTaskCallback?: () => void;
|
||||
description?: string;
|
||||
inputs?: Record<string, string[]>;
|
||||
|
||||
children?: ReactNode | ReactNode[];
|
||||
}
|
||||
|
||||
export function TaskNodeTooltip({
|
||||
@ -13,10 +17,11 @@ export function TaskNodeTooltip({
|
||||
executor,
|
||||
description,
|
||||
runTaskCallback: runTargetCallback,
|
||||
children,
|
||||
}: TaskNodeTooltipProps) {
|
||||
return (
|
||||
<div className="text-sm text-slate-700 dark:text-slate-400">
|
||||
<h4 className="flex justify-between items-center gap-4">
|
||||
<h4 className="flex justify-between items-center gap-4 mb-3">
|
||||
<div className="flex items-center">
|
||||
<Tag className="mr-3">{executor}</Tag>
|
||||
<span className="font-mono">{id}</span>
|
||||
@ -31,8 +36,8 @@ export function TaskNodeTooltip({
|
||||
</button>
|
||||
) : undefined}
|
||||
</h4>
|
||||
<h4></h4>
|
||||
{description ? <p className="mt-4">{description}</p> : null}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -87,21 +87,15 @@ export function PackageSchemaSubList({
|
||||
</p>
|
||||
|
||||
{vm.type === 'document' ? (
|
||||
<>
|
||||
<DocumentList documents={vm.package.documents} />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{vm.type === 'executor' ? (
|
||||
<>
|
||||
<SchemaList files={vm.package.executors} type={'executor'} />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{vm.type === 'generator' ? (
|
||||
<>
|
||||
<SchemaList files={vm.package.generators} type={'generator'} />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -16,7 +16,6 @@ export function DocumentList({
|
||||
documents: DocumentMetadata[];
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<ul className="divide-y divide-slate-100 dark:divide-slate-800">
|
||||
{!!documents.length ? (
|
||||
documents.map((guide) => (
|
||||
@ -26,7 +25,6 @@ export function DocumentList({
|
||||
<EmptyList type="document" />
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -63,7 +61,6 @@ export function SchemaList({
|
||||
type: 'executor' | 'generator';
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<ul className="divide-y divide-slate-100 dark:divide-slate-800">
|
||||
{!!files.length ? (
|
||||
files.map((schema) => (
|
||||
@ -73,7 +70,6 @@ export function SchemaList({
|
||||
<EmptyList type={type} />
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,6 @@ import { useLayoutEffect as ReactUseLayoutEffect } from 'react';
|
||||
*
|
||||
* See: https://reactjs.org/docs/hooks-reference.html#uselayouteffect
|
||||
*/
|
||||
export const useLayoutEffect = (<any>globalThis)?.document
|
||||
export const useLayoutEffect = (globalThis as any)?.document
|
||||
? ReactUseLayoutEffect
|
||||
: () => void 0;
|
||||
|
||||
@ -1,36 +1,51 @@
|
||||
import { workspaceRoot } from '../../utils/workspace-root';
|
||||
import { createHash } from 'crypto';
|
||||
import { existsSync, readFileSync, statSync, writeFileSync } from 'fs';
|
||||
import { copySync, ensureDirSync } from 'fs-extra';
|
||||
import * as http from 'http';
|
||||
import * as open from 'open';
|
||||
import { basename, dirname, extname, isAbsolute, join, parse } from 'path';
|
||||
import { performance } from 'perf_hooks';
|
||||
import * as minimatch from 'minimatch';
|
||||
import { URL } from 'node:url';
|
||||
import * as open from 'open';
|
||||
import {
|
||||
basename,
|
||||
dirname,
|
||||
extname,
|
||||
isAbsolute,
|
||||
join,
|
||||
parse,
|
||||
relative,
|
||||
} from 'path';
|
||||
import { performance } from 'perf_hooks';
|
||||
import { readNxJson, workspaceLayout } from '../../config/configuration';
|
||||
import { output } from '../../utils/output';
|
||||
import { writeJsonFile } from '../../utils/fileutils';
|
||||
import {
|
||||
ProjectFileMap,
|
||||
ProjectGraph,
|
||||
ProjectGraphDependency,
|
||||
ProjectGraphProjectNode,
|
||||
} from '../../config/project-graph';
|
||||
import { writeJsonFile } from '../../utils/fileutils';
|
||||
import { output } from '../../utils/output';
|
||||
import { workspaceRoot } from '../../utils/workspace-root';
|
||||
|
||||
import { Server } from 'net';
|
||||
|
||||
import { FileData } from '../../config/project-graph';
|
||||
import { TaskGraph } from '../../config/task-graph';
|
||||
import { daemonClient } from '../../daemon/client/client';
|
||||
import { filterUsingGlobPatterns } from '../../hasher/task-hasher';
|
||||
import { getRootTsConfigPath } from '../../plugins/js/utils/typescript';
|
||||
import { pruneExternalNodes } from '../../project-graph/operators';
|
||||
import { createProjectGraphAsync } from '../../project-graph/project-graph';
|
||||
import {
|
||||
createTaskGraph,
|
||||
mapTargetDefaultsToDependencies,
|
||||
} from '../../tasks-runner/create-task-graph';
|
||||
import { TaskGraph } from '../../config/task-graph';
|
||||
import { daemonClient } from '../../daemon/client/client';
|
||||
import { Server } from 'net';
|
||||
import { readFileMapCache } from '../../project-graph/nx-deps-cache';
|
||||
import { getAffectedGraphNodes } from '../affected/affected';
|
||||
import { allFileData } from '../../utils/all-file-data';
|
||||
import { splitArgsIntoNxArgsAndOverrides } from '../../utils/command-line-utils';
|
||||
import { NxJsonConfiguration } from '../../config/nx-json';
|
||||
import { HashPlanner } from '../../native';
|
||||
import { transformProjectGraphForRust } from '../../native/transform-objects';
|
||||
import { getAffectedGraphNodes } from '../affected/affected';
|
||||
import { readFileMapCache } from '../../project-graph/nx-deps-cache';
|
||||
|
||||
export interface ProjectGraphClientResponse {
|
||||
hash: string;
|
||||
@ -50,6 +65,10 @@ export interface TaskGraphClientResponse {
|
||||
errors: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ExpandedTaskInputsReponse {
|
||||
[taskId: string]: Record<string, string[]>;
|
||||
}
|
||||
|
||||
// maps file extention to MIME types
|
||||
const mimeType = {
|
||||
'.ico': 'image/x-icon',
|
||||
@ -73,7 +92,8 @@ function buildEnvironmentJs(
|
||||
watchMode: boolean,
|
||||
localMode: 'build' | 'serve',
|
||||
depGraphClientResponse?: ProjectGraphClientResponse,
|
||||
taskGraphClientResponse?: TaskGraphClientResponse
|
||||
taskGraphClientResponse?: TaskGraphClientResponse,
|
||||
expandedTaskInputsReponse?: ExpandedTaskInputsReponse
|
||||
) {
|
||||
let environmentJs = `window.exclude = ${JSON.stringify(exclude)};
|
||||
window.watch = ${!!watchMode};
|
||||
@ -88,7 +108,8 @@ function buildEnvironmentJs(
|
||||
id: 'local',
|
||||
label: 'local',
|
||||
projectGraphUrl: 'project-graph.json',
|
||||
taskGraphUrl: 'task-graph.json'
|
||||
taskGraphUrl: 'task-graph.json',
|
||||
taskInputsUrl: 'task-inputs.json',
|
||||
}
|
||||
],
|
||||
defaultWorkspaceId: 'local',
|
||||
@ -105,9 +126,13 @@ function buildEnvironmentJs(
|
||||
taskGraphClientResponse
|
||||
)};
|
||||
`;
|
||||
environmentJs += `window.expandedTaskInputsResponse = ${JSON.stringify(
|
||||
expandedTaskInputsReponse
|
||||
)};`;
|
||||
} else {
|
||||
environmentJs += `window.projectGraphResponse = null;`;
|
||||
environmentJs += `window.taskGraphResponse = null;`;
|
||||
environmentJs += `window.expandedTaskInputsResponse = null;`;
|
||||
}
|
||||
|
||||
return environmentJs;
|
||||
@ -318,13 +343,18 @@ export async function generateGraph(
|
||||
);
|
||||
|
||||
const taskGraphClientResponse = await createTaskGraphClientResponse();
|
||||
const taskInputsReponse = await createExpandedTaskInputResponse(
|
||||
taskGraphClientResponse,
|
||||
depGraphClientResponse
|
||||
);
|
||||
|
||||
const environmentJs = buildEnvironmentJs(
|
||||
args.exclude || [],
|
||||
args.watch,
|
||||
!!args.file && args.file.endsWith('html') ? 'build' : 'serve',
|
||||
depGraphClientResponse,
|
||||
taskGraphClientResponse
|
||||
taskGraphClientResponse,
|
||||
taskInputsReponse
|
||||
);
|
||||
html = html.replace(/src="/g, 'src="static/');
|
||||
html = html.replace(/href="styles/g, 'href="static/styles');
|
||||
@ -455,7 +485,6 @@ async function startServer(
|
||||
// by limiting the path to current directory only
|
||||
|
||||
const sanitizePath = basename(parsedUrl.pathname);
|
||||
|
||||
if (sanitizePath === 'project-graph.json') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(currentDepGraphClientResponse));
|
||||
@ -468,6 +497,23 @@ async function startServer(
|
||||
return;
|
||||
}
|
||||
|
||||
if (sanitizePath === 'task-inputs.json') {
|
||||
performance.mark('task input generation:start');
|
||||
|
||||
const taskId = parsedUrl.searchParams.get('taskId');
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
const inputs = await getExpandedTaskInputs(taskId);
|
||||
performance.mark('task input generation:end');
|
||||
|
||||
res.end(JSON.stringify({ [taskId]: inputs }));
|
||||
performance.measure(
|
||||
'task input generation',
|
||||
'task input generation:start',
|
||||
'task input generation:end'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sanitizePath === 'currentHash') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ hash: currentDepGraphClientResponse.hash }));
|
||||
@ -481,7 +527,6 @@ async function startServer(
|
||||
}
|
||||
|
||||
let pathname = join(__dirname, '../../core/graph/', sanitizePath);
|
||||
|
||||
// if the file is not found or is a directory, return index.html
|
||||
if (!existsSync(pathname) || statSync(pathname).isDirectory()) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
@ -618,10 +663,17 @@ async function createDepGraphClientResponse(
|
||||
};
|
||||
}
|
||||
|
||||
async function createTaskGraphClientResponse(): Promise<TaskGraphClientResponse> {
|
||||
let graph = pruneExternalNodes(
|
||||
async function createTaskGraphClientResponse(
|
||||
pruneExternal: boolean = false
|
||||
): Promise<TaskGraphClientResponse> {
|
||||
let graph: ProjectGraph;
|
||||
if (pruneExternal) {
|
||||
graph = pruneExternalNodes(
|
||||
await createProjectGraphAsync({ exitOnError: true })
|
||||
);
|
||||
} else {
|
||||
graph = await createProjectGraphAsync({ exitOnError: true });
|
||||
}
|
||||
|
||||
const nxJson = readNxJson();
|
||||
|
||||
@ -667,6 +719,36 @@ async function createTaskGraphClientResponse(): Promise<TaskGraphClientResponse>
|
||||
};
|
||||
}
|
||||
|
||||
async function createExpandedTaskInputResponse(
|
||||
taskGraphClientResponse: TaskGraphClientResponse,
|
||||
depGraphClientResponse: ProjectGraphClientResponse
|
||||
): Promise<ExpandedTaskInputsReponse> {
|
||||
performance.mark('task input static generation:start');
|
||||
|
||||
const allWorkspaceFiles = await allFileData();
|
||||
const response: Record<string, Record<string, string[]>> = {};
|
||||
|
||||
Object.entries(taskGraphClientResponse.plans).forEach(([key, inputs]) => {
|
||||
const [project] = key.split(':');
|
||||
|
||||
const expandedInputs = expandInputs(
|
||||
inputs,
|
||||
depGraphClientResponse.projects.find((p) => p.name === project),
|
||||
allWorkspaceFiles,
|
||||
depGraphClientResponse
|
||||
);
|
||||
|
||||
response[key] = expandedInputs;
|
||||
});
|
||||
performance.mark('task input static generation:end');
|
||||
performance.measure(
|
||||
'task input static generation',
|
||||
'task input static generation:start',
|
||||
'task input static generation:end'
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
function getAllTaskGraphsForWorkspace(
|
||||
nxJson: NxJsonConfiguration,
|
||||
projectGraph: ProjectGraph
|
||||
@ -684,7 +766,7 @@ function getAllTaskGraphsForWorkspace(
|
||||
// TODO(cammisuli): improve performance here. Cache results or something.
|
||||
for (const projectName in projectGraph.nodes) {
|
||||
const project = projectGraph.nodes[projectName];
|
||||
const targets = Object.keys(project.data.targets);
|
||||
const targets = Object.keys(project.data.targets ?? {});
|
||||
|
||||
targets.forEach((target) => {
|
||||
const taskId = createTaskId(projectName, target);
|
||||
@ -752,6 +834,130 @@ function createTaskId(
|
||||
}
|
||||
}
|
||||
|
||||
async function getExpandedTaskInputs(
|
||||
taskId: string
|
||||
): Promise<Record<string, string[]>> {
|
||||
const [project] = taskId.split(':');
|
||||
const taskGraphResponse = await createTaskGraphClientResponse(false);
|
||||
|
||||
const allWorkspaceFiles = await allFileData();
|
||||
|
||||
const inputs = taskGraphResponse.plans[taskId];
|
||||
if (inputs) {
|
||||
return expandInputs(
|
||||
inputs,
|
||||
currentDepGraphClientResponse.projects.find((p) => p.name === project),
|
||||
allWorkspaceFiles,
|
||||
currentDepGraphClientResponse
|
||||
);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function expandInputs(
|
||||
inputs: string[],
|
||||
project: ProjectGraphProjectNode,
|
||||
allWorkspaceFiles: FileData[],
|
||||
depGraphClientResponse: ProjectGraphClientResponse
|
||||
): Record<string, string[]> {
|
||||
const projectNames = depGraphClientResponse.projects.map((p) => p.name);
|
||||
|
||||
const workspaceRootInputs: string[] = [];
|
||||
const projectRootInputs: string[] = [];
|
||||
const externalInputs: string[] = [];
|
||||
const otherInputs: string[] = [];
|
||||
inputs.forEach((input) => {
|
||||
if (input.startsWith('{workspaceRoot}')) {
|
||||
workspaceRootInputs.push(input);
|
||||
return;
|
||||
}
|
||||
const maybeProjectName = input.split(':')[0];
|
||||
if (projectNames.includes(maybeProjectName)) {
|
||||
projectRootInputs.push(input);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
input === 'ProjectConfiguration' ||
|
||||
input === 'TsConfig' ||
|
||||
input === 'AllExternalDependencies'
|
||||
) {
|
||||
otherInputs.push(input);
|
||||
return;
|
||||
}
|
||||
// there shouldn't be any other imports in here, but external ones are always going to have a modifier in front
|
||||
if (input.includes(':')) {
|
||||
externalInputs.push(input);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
const workspaceRootsExpanded: string[] = workspaceRootInputs.flatMap(
|
||||
(input) => {
|
||||
const matches = [];
|
||||
const withoutWorkspaceRoot = input.substring(16);
|
||||
const matchingFile = allWorkspaceFiles.find(
|
||||
(t) => t.file === withoutWorkspaceRoot
|
||||
);
|
||||
if (matchingFile) {
|
||||
matches.push(matchingFile.file);
|
||||
} else {
|
||||
allWorkspaceFiles
|
||||
.filter((f) => minimatch(f.file, withoutWorkspaceRoot))
|
||||
.forEach((f) => {
|
||||
matches.push(f.file);
|
||||
});
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
);
|
||||
|
||||
const otherInputsExpanded = otherInputs.map((input) => {
|
||||
if (input === 'TsConfig') {
|
||||
return relative(workspaceRoot, getRootTsConfigPath());
|
||||
}
|
||||
if (input === 'ProjectConfiguration') {
|
||||
return depGraphClientResponse.fileMap[project.name].find(
|
||||
(file) =>
|
||||
file.file === `${project.data.root}/project.json` ||
|
||||
file.file === `${project.data.root}/package.json`
|
||||
).file;
|
||||
}
|
||||
|
||||
return input;
|
||||
});
|
||||
|
||||
const projectRootsExpanded = projectRootInputs
|
||||
.map((input) => {
|
||||
const fileSetProjectName = input.split(':')[0];
|
||||
const fileSetProject = depGraphClientResponse.projects.find(
|
||||
(p) => p.name === fileSetProjectName
|
||||
);
|
||||
const fileSets = input.replace(`${fileSetProjectName}:`, '').split(',');
|
||||
|
||||
const projectInputExpanded = {
|
||||
[fileSetProject.name]: filterUsingGlobPatterns(
|
||||
fileSetProject.data.root,
|
||||
depGraphClientResponse.fileMap[fileSetProject.name],
|
||||
fileSets
|
||||
).map((f) => f.file),
|
||||
};
|
||||
|
||||
return projectInputExpanded;
|
||||
})
|
||||
.reduce((curr, acc) => {
|
||||
for (let key in curr) {
|
||||
acc[key] = curr[key];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
general: [...workspaceRootsExpanded, ...otherInputsExpanded],
|
||||
...projectRootsExpanded,
|
||||
external: externalInputs,
|
||||
};
|
||||
}
|
||||
|
||||
interface GraphJsonResponse {
|
||||
tasks?: TaskGraph;
|
||||
graph: ProjectGraph;
|
||||
|
||||
@ -15,7 +15,7 @@ export function transformProjectGraphForRust(
|
||||
for (const [projectName, projectNode] of Object.entries(graph.nodes)) {
|
||||
const targets: Record<string, Target> = {};
|
||||
for (const [targetName, targetConfig] of Object.entries(
|
||||
projectNode.data.targets
|
||||
projectNode.data.targets ?? {}
|
||||
)) {
|
||||
targets[targetName] = {
|
||||
executor: targetConfig.executor,
|
||||
|
||||
@ -33,6 +33,7 @@ function writeFile() {
|
||||
label: id,
|
||||
projectGraphUrl: join('assets/generated-project-graphs/', filename),
|
||||
taskGraphUrl: join('assets/generated-task-graphs/', filename),
|
||||
taskInputsUrl: join('assets/generated-task-inputs/', filename),
|
||||
};
|
||||
});
|
||||
} catch {
|
||||
@ -50,6 +51,7 @@ function writeFile() {
|
||||
label: id,
|
||||
projectGraphUrl: join('assets/project-graphs/', filename),
|
||||
taskGraphUrl: join('assets/task-graphs/', filename),
|
||||
taskInputsUrl: join('assets/task-inputs/', filename),
|
||||
};
|
||||
});
|
||||
} catch {
|
||||
|
||||
@ -32,12 +32,19 @@ async function generateGraph(directory: string, name: string) {
|
||||
/window.taskGraphResponse = (.*?);/
|
||||
);
|
||||
|
||||
const expandedTaskInputsReponse = environmentJs.match(
|
||||
/window.expandedTaskInputsResponse = (.*?);/
|
||||
);
|
||||
|
||||
ensureDirSync(
|
||||
join(__dirname, '../graph/client/src/assets/generated-project-graphs/')
|
||||
);
|
||||
ensureDirSync(
|
||||
join(__dirname, '../graph/client/src/assets/generated-task-graphs/')
|
||||
);
|
||||
ensureDirSync(
|
||||
join(__dirname, '../graph/client/src/assets/generated-task-inputs/')
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
join(
|
||||
@ -56,6 +63,15 @@ async function generateGraph(directory: string, name: string) {
|
||||
),
|
||||
taskGraphResponse[1]
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
join(
|
||||
__dirname,
|
||||
'../graph/client/src/assets/generated-task-inputs/',
|
||||
`${name}.json`
|
||||
),
|
||||
expandedTaskInputsReponse[1]
|
||||
);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user