feat(graph): add atomizer label to target groups (#26622)

## Current Behavior
Atomized Groups are treated just like any other groups in the PDV

## Expected Behavior
We want to let people know that something was created by the Atomizer
and also surface more information to users.
This commit is contained in:
MaxKless 2024-06-26 16:17:59 +02:00 committed by GitHub
parent ce3f7f4ed8
commit 6528da3bd8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 439 additions and 120 deletions

View File

@ -25,40 +25,10 @@ export class ExternalApiImpl extends ExternalApi {
console.log('graphInteractionEventListener not registered.');
return;
}
if (type === 'file-click') {
const url = `${payload.sourceRoot}/${payload.file}`;
this.graphInteractionEventListener({
type: 'file-click',
payload: { url },
});
} else if (type === 'open-project-config') {
this.graphInteractionEventListener({
type: 'open-project-config',
payload,
});
} else if (type === 'run-task') {
this.graphInteractionEventListener({
type: 'run-task',
payload,
});
} else if (type === 'open-project-graph') {
this.graphInteractionEventListener({
type: 'open-project-graph',
payload,
});
} else if (type === 'open-task-graph') {
this.graphInteractionEventListener({
type: 'open-task-graph',
payload,
});
} else if (type === 'override-target') {
this.graphInteractionEventListener({
type: 'override-target',
payload,
});
} else {
console.log('unhandled event', type, payload);
}
this.graphInteractionEventListener({
type,
payload,
});
}
);

View File

@ -83,6 +83,7 @@ const projectDetailsLoader = async (
project: ProjectGraphProjectNode;
sourceMap: Record<string, string[]>;
errors?: GraphError[];
connectedToCloud?: boolean;
}> => {
const workspaceData = await workspaceDataLoader(selectedWorkspaceId);
const sourceMaps = await sourceMapsLoader(selectedWorkspaceId);
@ -102,6 +103,7 @@ const projectDetailsLoader = async (
project,
sourceMap: sourceMaps[project.data.root],
errors: workspaceData.errors,
connectedToCloud: workspaceData.connectedToCloud,
};
};

View File

@ -69,8 +69,7 @@ export function TooltipDisplay() {
externalApiService.postEvent({
type: 'file-click',
payload: {
sourceRoot: currentTooltip.props.sourceRoot,
file: url,
url: `${currentTooltip.props.sourceRoot}/${url}`,
},
})
: undefined;

View File

@ -21,14 +21,14 @@ import {
import { ProjectDetailsHeader } from './project-details-header';
export function ProjectDetailsPage() {
const { project, sourceMap, hash, errors } = useRouteLoaderData(
'selectedProjectDetails'
) as {
hash: string;
project: ProjectGraphProjectNode;
sourceMap: Record<string, string[]>;
errors?: GraphError[];
};
const { project, sourceMap, hash, errors, connectedToCloud } =
useRouteLoaderData('selectedProjectDetails') as {
hash: string;
project: ProjectGraphProjectNode;
sourceMap: Record<string, string[]>;
errors?: GraphError[];
connectedToCloud?: boolean;
};
const { environment, watch, appConfig } = useEnvironmentConfig();
@ -64,6 +64,7 @@ export function ProjectDetailsPage() {
project={project}
sourceMap={sourceMap}
errors={errors}
connectedToCloud={connectedToCloud}
></ProjectDetailsWrapper>
</div>
</div>

View File

@ -21,12 +21,14 @@ interface ProjectDetailsProps {
project: ProjectGraphProjectNode;
sourceMap: Record<string, string[]>;
errors?: GraphError[];
connectedToCloud?: boolean;
}
export function ProjectDetailsWrapper({
project,
sourceMap,
errors,
connectedToCloud,
}: ProjectDetailsProps) {
const environment = useEnvironmentConfig()?.environment;
const externalApiService = getExternalApiService();
@ -95,6 +97,14 @@ export function ProjectDetailsWrapper({
[externalApiService]
);
const handleNxConnect = useCallback(
() =>
externalApiService.postEvent({
type: 'nx-connect',
}),
[externalApiService]
);
const updateSearchParams = (
params: URLSearchParams,
targetNames?: string[]
@ -162,6 +172,10 @@ export function ProjectDetailsWrapper({
viewInProjectGraphPosition={
environment === 'nx-console' ? 'bottom' : 'top'
}
connectedToCloud={connectedToCloud}
nxConnectCallback={
environment === 'nx-console' ? handleNxConnect : undefined
}
/>
<ErrorToast errors={errors} />
</>

View File

@ -9,10 +9,10 @@ export function getExternalApiService() {
}
export class ExternalApiService {
private subscribers: Set<(event: { type: string; payload: any }) => void> =
private subscribers: Set<(event: { type: string; payload?: any }) => void> =
new Set();
postEvent(event: { type: string; payload: any }) {
postEvent(event: { type: string; payload?: any }) {
this.subscribers.forEach((subscriber) => {
subscriber(event);
});

View File

@ -1,2 +1,3 @@
export * from './lib/technology-icon';
export * from './lib/framework-icons';
export * from './lib/ nx-cloud-icon';

View File

@ -0,0 +1,17 @@
import { FC, SVGProps } from 'react';
export const NxCloudIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
role="img"
xmlns="http://www.w3.org/2000/svg"
stroke="currentColor"
fill="transparent"
viewBox="0 0 24 24"
{...props}
>
<path
d="M22.167 7.167v-2.5a2.5 2.5 0 0 0-2.5-2.5h-15a2.5 2.5 0 0 0-2.5 2.5v15a2.5 2.5 0 0 0 2.5 2.5h2.5m15-15c-2.76 0-5 2.24-5 5s-2.24 5-5 5-5 2.24-5 5m15-15V19.59a2.577 2.577 0 0 1-2.576 2.576H7.167"
strokeWidth="2"
/>
</svg>
);

View File

@ -19,12 +19,14 @@ export interface ProjectDetailsProps {
sourceMap: Record<string, string[]>;
errors?: GraphError[];
variant?: 'default' | 'compact';
connectedToCloud?: boolean;
onViewInProjectGraph?: (data: { projectName: string }) => void;
onViewInTaskGraph?: (data: {
projectName: string;
targetName: string;
}) => void;
onRunTarget?: (data: { projectName: string; targetName: string }) => void;
nxConnectCallback?: () => void;
viewInProjectGraphPosition?: 'top' | 'bottom';
}
@ -41,7 +43,9 @@ export const ProjectDetails = ({
onViewInProjectGraph,
onViewInTaskGraph,
onRunTarget,
nxConnectCallback,
viewInProjectGraphPosition = 'top',
connectedToCloud,
}: ProjectDetailsProps) => {
const projectData = project.data;
const isCompact = variant === 'compact';
@ -161,6 +165,8 @@ export const ProjectDetails = ({
variant={variant}
onRunTarget={onRunTarget}
onViewInTaskGraph={onViewInTaskGraph}
connectedToCloud={connectedToCloud}
nxConnectCallback={nxConnectCallback}
/>
</div>
</>

View File

@ -3,12 +3,18 @@ import { TargetConfigurationGroupHeader } from '../target-configuration-details-
export interface TargetConfigurationGroupContainerProps {
targetGroupName: string;
targetsNumber: number;
nonAtomizedTarget?: string;
connectedToCloud?: boolean;
nxConnectCallback?: () => void;
children: React.ReactNode;
}
export function TargetConfigurationGroupContainer({
targetGroupName,
targetsNumber,
nonAtomizedTarget,
connectedToCloud,
nxConnectCallback,
children,
}: TargetConfigurationGroupContainerProps) {
return (
@ -16,6 +22,9 @@ export function TargetConfigurationGroupContainer({
<TargetConfigurationGroupHeader
targetGroupName={targetGroupName}
targetsNumber={targetsNumber}
nonAtomizedTarget={nonAtomizedTarget}
connectedToCloud={connectedToCloud}
nxConnectCallback={nxConnectCallback}
className="sticky top-0 z-10 bg-white dark:bg-slate-900"
/>
<div className="rounded-md border border-slate-200 p-2 dark:border-slate-700">

View File

@ -15,3 +15,21 @@ export const Simple: Story = {
targetsNumber: 5,
},
};
export const AtomizerCloud: Story = {
args: {
targetGroupName: 'Target Group Name',
targetsNumber: 5,
nonAtomizedTarget: 'e2e',
connectedToCloud: true,
},
};
export const AtomizerNoCloud: Story = {
args: {
targetGroupName: 'Target Group Name',
targetsNumber: 5,
nonAtomizedTarget: 'e2e',
connectedToCloud: false,
},
};

View File

@ -1,25 +1,60 @@
import { AtomizerTooltip, Tooltip } from '@nx/graph/ui-tooltips';
import { Pill } from '../pill';
import { Square3Stack3DIcon } from '@heroicons/react/24/outline';
export interface TargetConfigurationGroupHeaderProps {
targetGroupName: string;
targetsNumber: number;
className?: string;
nonAtomizedTarget?: string;
connectedToCloud?: boolean;
nxConnectCallback?: () => void;
showIcon?: boolean;
}
export const TargetConfigurationGroupHeader = ({
targetGroupName,
targetsNumber,
nonAtomizedTarget,
connectedToCloud = true,
nxConnectCallback,
className = '',
}: TargetConfigurationGroupHeaderProps) => {
return (
<header className={`px-4 py-2 text-lg capitalize ${className}`}>
<header
className={`flex items-center gap-2 px-4 py-2 text-lg capitalize ${className}`}
>
{targetGroupName}{' '}
{nonAtomizedTarget && <Square3Stack3DIcon className="h-5 w-5" />}
<Pill
text={
targetsNumber.toString() +
(targetsNumber === 1 ? ' target' : ' targets')
}
/>
{nonAtomizedTarget && (
<Tooltip
openAction="hover"
strategy="fixed"
usePortal={true}
content={
(
<AtomizerTooltip
connectedToCloud={connectedToCloud}
nonAtomizedTarget={nonAtomizedTarget}
nxConnectCallback={nxConnectCallback}
/>
) as any
}
>
<span className="inline-flex">
<Pill
color={connectedToCloud ? 'grey' : 'yellow'}
text={'Atomizer'}
/>
</span>
</Tooltip>
)}
</header>
);
};

View File

@ -4,7 +4,10 @@ import type { ProjectGraphProjectNode } from '@nx/devkit';
import { TargetConfigurationDetailsListItem } from '../target-configuration-details-list-item/target-configuration-details-list-item';
import { TargetConfigurationGroupContainer } from '../target-configuration-details-group-container/target-configuration-details-group-container';
import { groupTargets } from '../utils/group-targets';
import {
getNonAtomizedTargetForGroup,
groupTargets,
} from '../utils/group-targets';
import { useMemo } from 'react';
export interface TargetConfigurationGroupListProps {
@ -16,6 +19,8 @@ export interface TargetConfigurationGroupListProps {
projectName: string;
targetName: string;
}) => void;
nxConnectCallback?: () => void;
connectedToCloud?: boolean;
className?: string;
}
@ -25,7 +30,9 @@ export function TargetConfigurationGroupList({
sourceMap,
onRunTarget,
onViewInTaskGraph,
nxConnectCallback,
className = '',
connectedToCloud,
}: TargetConfigurationGroupListProps) {
const targetsGroup = useMemo(() => groupTargets(project), [project]);
const hasGroups = useMemo(() => {
@ -47,6 +54,12 @@ export function TargetConfigurationGroupList({
<TargetConfigurationGroupContainer
targetGroupName={targetGroupName}
targetsNumber={targets.length}
nonAtomizedTarget={getNonAtomizedTargetForGroup(
project,
targetGroupName
)}
connectedToCloud={connectedToCloud}
nxConnectCallback={nxConnectCallback}
key={targetGroupName}
>
<ul className={className}>

View File

@ -33,3 +33,18 @@ function sortNxReleasePublishLast(a: string, b: string) {
if (b === 'nx-release-publish') return -1;
return a.localeCompare(b);
}
export function getNonAtomizedTargetForGroup(
project: ProjectGraphProjectNode,
targetGroupName: string
): string | undefined {
const targetWithNonAtomizedEquivalent = project.data.metadata?.targetGroups?.[
targetGroupName
]?.find(
(target) => project.data.targets?.[target]?.metadata?.nonAtomizedTarget
);
return targetWithNonAtomizedEquivalent
? project.data.targets?.[targetWithNonAtomizedEquivalent]?.metadata
?.nonAtomizedTarget
: undefined;
}

View File

@ -6,3 +6,4 @@ export * from './lib/tooltip-button';
export * from './lib/property-info-tooltip';
export * from './lib/sourcemap-info-tooltip';
export * from './lib/external-link';
export * from './lib/atomizer-tooltip';

View File

@ -0,0 +1,72 @@
import type { Meta, StoryObj } from '@storybook/react';
import { AtomizerTooltip, AtomizerTooltipProps } from './atomizer-tooltip';
import { Tooltip } from './tooltip';
const meta: Meta<typeof AtomizerTooltip> = {
component: AtomizerTooltip,
title: 'Tooltips/AtomizerTooltip',
};
export default meta;
type Story = StoryObj<typeof AtomizerTooltip>;
export const Cloud: Story = {
args: {
connectedToCloud: true,
nonAtomizedTarget: 'e2e',
} as AtomizerTooltipProps,
render: (args) => {
return (
<div className="flex w-full justify-center">
<Tooltip
open={true}
openAction="manual"
content={(<AtomizerTooltip {...args} />) as any}
>
<p>Internal Reference</p>
</Tooltip>
</div>
);
},
};
export const NoCloud: Story = {
args: {
connectedToCloud: false,
nonAtomizedTarget: 'e2e',
} as AtomizerTooltipProps,
render: (args) => {
return (
<div className="flex w-full justify-center">
<Tooltip
open={true}
openAction="manual"
content={(<AtomizerTooltip {...args} />) as any}
>
<p>Internal Reference</p>
</Tooltip>
</div>
);
},
};
export const NoCloudConsole: Story = {
args: {
connectedToCloud: false,
nonAtomizedTarget: 'e2e',
nxConnectCallback: () => console.log('nxConnectCallback'),
} as AtomizerTooltipProps,
render: (args) => {
return (
<div className="flex w-full justify-center">
<Tooltip
open={true}
openAction="manual"
content={(<AtomizerTooltip {...args} />) as any}
>
<p>Internal Reference</p>
</Tooltip>
</div>
);
},
};

View File

@ -0,0 +1,106 @@
import { NxCloudIcon } from '@nx/graph/ui-icons';
import { twMerge } from 'tailwind-merge';
export interface AtomizerTooltipProps {
connectedToCloud: boolean;
nonAtomizedTarget: string;
nxConnectCallback?: () => void;
}
export function AtomizerTooltip(props: AtomizerTooltipProps) {
return (
<div className="z-20 max-w-lg text-sm text-slate-700 dark:text-slate-400">
<h4 className="flex items-center justify-between border-b border-slate-200 text-base dark:border-slate-700/60">
<span className="font-mono">Atomizer</span>
</h4>
<div
className={twMerge(
'flex flex-col py-2 font-mono',
!props.connectedToCloud
? 'border-b border-slate-200 dark:border-slate-700/60'
: ''
)}
>
<p className="whitespace-pre-wrap normal-case">
{'Nx '}
<Link
href="https://nx.dev/ci/features/split-e2e-tasks"
text="automatically split"
/>
{
' this potentially slow task into separate tasks for each file. We recommend enabling '
}
{!props.connectedToCloud && (
<>
<Link href="https://nx.app/" text="Nx Cloud" />
{' and '}
</>
)}
<Link
href="https://nx.dev/ci/features/distribute-task-execution"
text="Nx Agents"
/>
{' to benefit from '}
<Link
href="https://nx.dev/ci/features/distribute-task-execution"
text="task distribution"
/>
{!props.connectedToCloud && (
<>
{', '}
<Link
href="https://nx.dev/ci/features/remote-cache"
text="remote caching"
/>
</>
)}
{' and '}
<Link
href="https://nx.dev/ci/features/flaky-tasks"
text="flaky task re-runs"
/>
. Use
<code className="mx-2 inline rounded bg-gray-100 px-1 font-mono text-gray-800 dark:bg-gray-700 dark:text-gray-300">
{props.nonAtomizedTarget}
</code>
when running without Nx Agents.
</p>
</div>
{!props.connectedToCloud && (
<div className="flex py-2">
<p className="pr-4 normal-case">
{props.nxConnectCallback ? (
<button
className="inline-flex cursor-pointer items-center gap-2 rounded-md px-2 py-1 text-base text-slate-600 ring-2 ring-inset ring-slate-400/40 hover:bg-slate-50 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-800/60"
onClick={() => props.nxConnectCallback!()}
>
<NxCloudIcon className="h-5 w-5 "></NxCloudIcon>
<span>Connect to Nx Cloud</span>
</button>
) : (
<span className="font-mono">
{'Run'}
<code className="mx-2 inline rounded bg-gray-100 px-1 font-mono text-gray-800 dark:bg-gray-700 dark:text-gray-300">
nx connect
</code>
{'to connect to Nx Cloud'}
</span>
)}
</p>
</div>
)}
</div>
);
}
function Link({ href, text }: { href: string; text: string }) {
return (
<a
href={href}
className="inline text-slate-500 underline decoration-slate-700/50 decoration-dotted decoration-2 dark:text-slate-400 dark:decoration-slate-400/50"
target="_blank"
rel="noreferrer"
>
{text}
</a>
);
}

View File

@ -99,7 +99,7 @@ export function PropertyInfoTooltip({ type }: PropertyInfoTooltipProps) {
: ''
)}
>
<p className="flex grow items-center gap-2 whitespace-pre-wrap">
<p className="flex grow items-center gap-2 whitespace-pre-wrap normal-case">
{propertyInfo.description}
</p>
</div>

View File

@ -25,6 +25,7 @@ import {
useRole,
safePolygon,
useTransitionStyles,
FloatingPortal,
} from '@floating-ui/react';
export type TooltipProps = HTMLAttributes<HTMLDivElement> & {
@ -37,6 +38,7 @@ export type TooltipProps = HTMLAttributes<HTMLDivElement> & {
buffer?: number;
showTooltipArrow?: boolean;
strategy?: 'absolute' | 'fixed';
usePortal?: boolean;
};
export function Tooltip({
@ -49,6 +51,7 @@ export function Tooltip({
strategy = 'absolute',
buffer = 0,
showTooltipArrow = true,
usePortal = false,
}: TooltipProps) {
const [isOpen, setIsOpen] = useState(open);
const arrowRef = useRef(null);
@ -123,41 +126,49 @@ export function Tooltip({
...getReferenceProps(),
};
const renderTooltip = () => (
<div
ref={refs.setFloating}
style={{
position: appliedStrategy,
top: showTooltipArrow ? y : y + 8 ?? 0,
left: x ?? 0,
width: 'max-content',
...animationStyles,
}}
className="z-20 min-w-[250px] max-w-prose rounded-md border border-slate-500"
{...getFloatingProps()}
>
{showTooltipArrow && (
<div
style={{
left: arrowX != null ? `${arrowX}px` : '',
top: arrowY != null ? `${arrowY}px` : '',
right: '',
bottom: '',
[staticSide]: '-4px',
}}
className="absolute -z-10 h-4 w-4 rotate-45 bg-slate-500"
ref={arrowRef}
></div>
)}
<div className="select-text rounded-md bg-white p-3 dark:bg-slate-900 dark:text-slate-400">
{content}
</div>
</div>
);
return (
<>
{!externalReference && !!children
? cloneElement(children, cloneProps)
: children}
{isOpen && isMounted ? (
<div
ref={refs.setFloating}
style={{
position: appliedStrategy,
top: showTooltipArrow ? y : y + 8 ?? 0,
left: x ?? 0,
width: 'max-content',
...animationStyles,
}}
className="z-10 min-w-[250px] max-w-prose rounded-md border border-slate-500"
{...getFloatingProps()}
>
{showTooltipArrow && (
<div
style={{
left: arrowX != null ? `${arrowX}px` : '',
top: arrowY != null ? `${arrowY}px` : '',
right: '',
bottom: '',
[staticSide]: '-4px',
}}
className="absolute -z-10 h-4 w-4 rotate-45 bg-slate-500"
ref={arrowRef}
></div>
)}
<div className="select-text rounded-md bg-white p-3 dark:bg-slate-900 dark:text-slate-400">
{content}
</div>
</div>
usePortal ? (
<FloatingPortal>{renderTooltip()}</FloatingPortal>
) : (
renderTooltip()
)
) : null}
</>
);

View File

@ -306,6 +306,7 @@ describe('@nx/cypress/plugin', () => {
],
"metadata": {
"description": "Runs Cypress Tests in CI",
"nonAtomizedTarget": "e2e",
"technologies": [
"cypress",
],
@ -329,6 +330,7 @@ describe('@nx/cypress/plugin', () => {
],
"metadata": {
"description": "Runs Cypress Tests in src/test.cy.ts in CI",
"nonAtomizedTarget": "e2e",
"technologies": [
"cypress",
],

View File

@ -270,6 +270,7 @@ async function buildCypressTargets(
metadata: {
technologies: ['cypress'],
description: `Runs Cypress Tests in ${relativeSpecFilePath} in CI`,
nonAtomizedTarget: options.targetName,
},
};
dependsOn.push({
@ -288,6 +289,7 @@ async function buildCypressTargets(
metadata: {
technologies: ['cypress'],
description: 'Runs Cypress Tests in CI',
nonAtomizedTarget: options.targetName,
},
};
ciTargetGroup.push(options.ciTargetName);

View File

@ -179,6 +179,7 @@ describe('@nx/jest/plugin', () => {
],
"metadata": {
"description": "Run Jest Tests in CI",
"nonAtomizedTarget": "test",
"technologies": [
"jest",
],
@ -201,6 +202,7 @@ describe('@nx/jest/plugin', () => {
],
"metadata": {
"description": "Run Jest Tests in src/unit.spec.ts",
"nonAtomizedTarget": "test",
"technologies": [
"jest",
],
@ -304,48 +306,48 @@ describe('@nx/jest/plugin', () => {
);
expect(results).toMatchInlineSnapshot(`
[
[
"proj/jest.config.js",
{
"projects": {
"proj": {
"metadata": undefined,
"root": "proj",
"targets": {
"test": {
"cache": true,
"command": "jest",
"inputs": [
"default",
"^production",
{
"externalDependencies": [
"jest",
"some-package",
],
[
[
"proj/jest.config.js",
{
"projects": {
"proj": {
"metadata": undefined,
"root": "proj",
"targets": {
"test": {
"cache": true,
"command": "jest",
"inputs": [
"default",
"^production",
{
"externalDependencies": [
"jest",
"some-package",
],
},
],
"metadata": {
"description": "Run Jest Tests",
"technologies": [
"jest",
],
},
"options": {
"cwd": "proj",
},
"outputs": [
"{workspaceRoot}/coverage",
],
},
},
},
],
"metadata": {
"description": "Run Jest Tests",
"technologies": [
"jest",
],
},
"options": {
"cwd": "proj",
},
"outputs": [
"{workspaceRoot}/coverage",
],
},
},
},
},
},
],
]
`);
],
]
`);
}
);
});

View File

@ -237,6 +237,7 @@ async function buildJestTargets(
metadata: {
technologies: ['jest'],
description: 'Run Jest Tests in CI',
nonAtomizedTarget: options.targetName,
},
};
targetGroup.push(options.ciTargetName);
@ -258,6 +259,7 @@ async function buildJestTargets(
metadata: {
technologies: ['jest'],
description: `Run Jest Tests in ${relativePath}`,
nonAtomizedTarget: options.targetName,
},
};
targetGroup.push(targetName);

View File

@ -56,6 +56,7 @@ import { createTaskHasher } from '../../hasher/create-task-hasher';
import { filterUsingGlobPatterns } from '../../hasher/task-hasher';
import { ProjectGraphError } from '../../project-graph/error-types';
import { isNxCloudUsed } from '../../utils/nx-cloud-utils';
export interface GraphError {
message: string;
@ -78,6 +79,7 @@ export interface ProjectGraphClientResponse {
exclude: string[];
isPartial: boolean;
errors?: GraphError[];
connectedToCloud?: boolean;
}
export interface TaskGraphClientResponse {
@ -748,11 +750,14 @@ async function createProjectGraphAndSourceMapClientResponse(
let sourceMaps: ConfigurationSourceMaps;
let isPartial = false;
let errors: GraphError[] | undefined;
let connectedToCloud: boolean | undefined;
try {
const projectGraphAndSourceMaps =
await createProjectGraphAndSourceMapsAsync({ exitOnError: false });
projectGraph = projectGraphAndSourceMaps.projectGraph;
sourceMaps = projectGraphAndSourceMaps.sourceMaps;
connectedToCloud = isNxCloudUsed(readNxJson());
} catch (e) {
if (e instanceof ProjectGraphError) {
projectGraph = e.getPartialProjectGraph();
@ -786,7 +791,14 @@ async function createProjectGraphAndSourceMapClientResponse(
const hasher = createHash('sha256');
hasher.update(
JSON.stringify({ layout, projects, dependencies, sourceMaps, errors })
JSON.stringify({
layout,
projects,
dependencies,
sourceMaps,
errors,
connectedToCloud,
})
);
const hash = hasher.digest('hex');
@ -816,6 +828,7 @@ async function createProjectGraphAndSourceMapClientResponse(
fileMap,
isPartial,
errors,
connectedToCloud,
},
sourceMapResponse: sourceMaps,
};

View File

@ -128,6 +128,7 @@ export interface TargetMetadata {
[k: string]: any;
description?: string;
technologies?: string[];
nonAtomizedTarget?: string;
}
export interface TargetDependencyConfig {

View File

@ -1,8 +1,8 @@
import { NxJsonConfiguration, readNxJson } from '../config/nx-json';
export function isNxCloudUsed(nxJson: NxJsonConfiguration) {
export function isNxCloudUsed(nxJson: NxJsonConfiguration): boolean {
return (
process.env.NX_CLOUD_ACCESS_TOKEN ||
!!process.env.NX_CLOUD_ACCESS_TOKEN ||
!!nxJson.nxCloudAccessToken ||
!!Object.values(nxJson.tasksRunnerOptions ?? {}).find(
(r) => r.runner == '@nrwl/nx-cloud' || r.runner == 'nx-cloud'

View File

@ -99,6 +99,7 @@ describe('@nx/playwright/plugin', () => {
],
"metadata": {
"description": "Runs Playwright Tests in CI",
"nonAtomizedTarget": "e2e",
"technologies": [
"playwright",
],
@ -191,6 +192,7 @@ describe('@nx/playwright/plugin', () => {
],
"metadata": {
"description": "Runs Playwright Tests in CI",
"nonAtomizedTarget": "e2e",
"technologies": [
"playwright",
],
@ -273,6 +275,7 @@ describe('@nx/playwright/plugin', () => {
],
"metadata": {
"description": "Runs Playwright Tests in CI",
"nonAtomizedTarget": "e2e",
"technologies": [
"playwright",
],
@ -297,6 +300,7 @@ describe('@nx/playwright/plugin', () => {
],
"metadata": {
"description": "Runs Playwright Tests in tests/run-me.spec.ts in CI",
"nonAtomizedTarget": "e2e",
"technologies": [
"playwright",
],
@ -324,6 +328,7 @@ describe('@nx/playwright/plugin', () => {
],
"metadata": {
"description": "Runs Playwright Tests in tests/run-me-2.spec.ts in CI",
"nonAtomizedTarget": "e2e",
"technologies": [
"playwright",
],

View File

@ -215,6 +215,7 @@ async function buildPlaywrightTargets(
metadata: {
technologies: ['playwright'],
description: `Runs Playwright Tests in ${relativeSpecFilePath} in CI`,
nonAtomizedTarget: options.targetName,
},
};
dependsOn.push({
@ -241,6 +242,7 @@ async function buildPlaywrightTargets(
metadata: {
technologies: ['playwright'],
description: 'Runs Playwright Tests in CI',
nonAtomizedTarget: options.targetName,
},
};
ciTargetGroup.push(options.ciTargetName);