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:
parent
ce3f7f4ed8
commit
6528da3bd8
@ -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',
|
||||
type,
|
||||
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);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -21,13 +21,13 @@ import {
|
||||
import { ProjectDetailsHeader } from './project-details-header';
|
||||
|
||||
export function ProjectDetailsPage() {
|
||||
const { project, sourceMap, hash, errors } = useRouteLoaderData(
|
||||
'selectedProjectDetails'
|
||||
) as {
|
||||
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>
|
||||
|
||||
@ -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} />
|
||||
</>
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './lib/technology-icon';
|
||||
export * from './lib/framework-icons';
|
||||
export * from './lib/ nx-cloud-icon';
|
||||
|
||||
17
graph/ui-icons/src/lib/ nx-cloud-icon.tsx
Normal file
17
graph/ui-icons/src/lib/ nx-cloud-icon.tsx
Normal 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>
|
||||
);
|
||||
@ -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>
|
||||
</>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
72
graph/ui-tooltips/src/lib/atomizer-tooltip.stories.tsx
Normal file
72
graph/ui-tooltips/src/lib/atomizer-tooltip.stories.tsx
Normal 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>
|
||||
);
|
||||
},
|
||||
};
|
||||
106
graph/ui-tooltips/src/lib/atomizer-tooltip.tsx
Normal file
106
graph/ui-tooltips/src/lib/atomizer-tooltip.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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,12 +126,7 @@ export function Tooltip({
|
||||
...getReferenceProps(),
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{!externalReference && !!children
|
||||
? cloneElement(children, cloneProps)
|
||||
: children}
|
||||
{isOpen && isMounted ? (
|
||||
const renderTooltip = () => (
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
style={{
|
||||
@ -138,7 +136,7 @@ export function Tooltip({
|
||||
width: 'max-content',
|
||||
...animationStyles,
|
||||
}}
|
||||
className="z-10 min-w-[250px] max-w-prose rounded-md border border-slate-500"
|
||||
className="z-20 min-w-[250px] max-w-prose rounded-md border border-slate-500"
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
{showTooltipArrow && (
|
||||
@ -158,6 +156,19 @@ export function Tooltip({
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!externalReference && !!children
|
||||
? cloneElement(children, cloneProps)
|
||||
: children}
|
||||
{isOpen && isMounted ? (
|
||||
usePortal ? (
|
||||
<FloatingPortal>{renderTooltip()}</FloatingPortal>
|
||||
) : (
|
||||
renderTooltip()
|
||||
)
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -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",
|
||||
],
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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",
|
||||
],
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -128,6 +128,7 @@ export interface TargetMetadata {
|
||||
[k: string]: any;
|
||||
description?: string;
|
||||
technologies?: string[];
|
||||
nonAtomizedTarget?: string;
|
||||
}
|
||||
|
||||
export interface TargetDependencyConfig {
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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",
|
||||
],
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user