feat(graph): add source info for command and script (#26162)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> <img width="993" alt="Screenshot 2024-05-28 at 12 19 15 PM" src="https://github.com/nrwl/nx/assets/16211801/35b95537-72ff-474f-b03a-68e20a7dfe55"> <img width="942" alt="Screenshot 2024-05-28 at 12 19 05 PM" src="https://github.com/nrwl/nx/assets/16211801/b67d920b-2689-452c-9214-d96ce12331dc"> <img width="728" alt="Screenshot 2024-05-28 at 12 09 15 PM" src="https://github.com/nrwl/nx/assets/16211801/c6e74976-83b5-44bf-b0b7-c99e22cd6e03"> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
7e984e11a6
commit
20529d4dc9
@ -4,10 +4,8 @@ import type { TargetConfiguration } from '@nx/devkit';
|
||||
|
||||
import { JsonCodeBlock } from '@nx/graph/ui-code-block';
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { SourceInfo } from '../source-info/source-info';
|
||||
import { FadingCollapsible } from './fading-collapsible';
|
||||
import { TargetConfigurationProperty } from './target-configuration-property';
|
||||
import { selectSourceInfo } from './target-configuration-details.util';
|
||||
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
|
||||
import { PropertyInfoTooltip, Tooltip } from '@nx/graph/ui-tooltips';
|
||||
import { TooltipTriggerText } from './tooltip-trigger-text';
|
||||
@ -17,6 +15,8 @@ import { ExpandedTargetsContext } from '@nx/graph/shared';
|
||||
import { getDisplayHeaderFromTargetConfiguration } from '../utils/get-display-header-from-target-configuration';
|
||||
import { TargetExecutor } from '../target-executor/target-executor';
|
||||
import { TargetExecutorTitle } from '../target-executor/target-executor-title';
|
||||
import { TargetSourceInfo } from '../target-source-info/target-source-info';
|
||||
import { getTargetExecutorSourceMapKey } from '../target-source-info/get-target-executor-source-map-key';
|
||||
|
||||
interface TargetConfigurationDetailsProps {
|
||||
projectName: string;
|
||||
@ -109,7 +109,15 @@ export default function TargetConfigurationDetails({
|
||||
/>
|
||||
</h4>
|
||||
<p className="pl-5 font-mono">
|
||||
<TargetExecutor {...displayHeader} link={link} />
|
||||
<TargetExecutor {...displayHeader} link={link}>
|
||||
<TargetSourceInfo
|
||||
className="pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100"
|
||||
propertyKey={`targets.${targetName}.${getTargetExecutorSourceMapKey(
|
||||
targetConfiguration
|
||||
)}`}
|
||||
sourceMap={sourceMap}
|
||||
/>
|
||||
</TargetExecutor>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -122,7 +130,13 @@ export default function TargetConfigurationDetails({
|
||||
/>
|
||||
</h4>
|
||||
<p className="pl-5 font-mono">
|
||||
<TargetExecutor script={script} link={link} />
|
||||
<TargetExecutor script={script} link={link}>
|
||||
<TargetSourceInfo
|
||||
className="pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100"
|
||||
propertyKey={`targets.${targetName}.options.script`}
|
||||
sourceMap={sourceMap}
|
||||
/>
|
||||
</TargetExecutor>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@ -151,29 +165,20 @@ export default function TargetConfigurationDetails({
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="mb-4 list-disc pl-5">
|
||||
{targetConfiguration.inputs.map((input, idx) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.inputs`
|
||||
);
|
||||
return (
|
||||
<li
|
||||
className="group/line overflow-hidden whitespace-nowrap"
|
||||
key={`input-${idx}`}
|
||||
>
|
||||
<TargetConfigurationProperty data={input}>
|
||||
{sourceInfo && (
|
||||
<span className="inline flex min-w-0 pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.inputs`}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</TargetConfigurationProperty>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{targetConfiguration.inputs.map((input, idx) => (
|
||||
<li
|
||||
className="group/line overflow-hidden whitespace-nowrap"
|
||||
key={`input-${idx}`}
|
||||
>
|
||||
<TargetConfigurationProperty data={input}>
|
||||
<TargetSourceInfo
|
||||
className="min-w-0 flex-1 pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100"
|
||||
propertyKey={`targets.${targetName}.inputs`}
|
||||
sourceMap={sourceMap}
|
||||
/>
|
||||
</TargetConfigurationProperty>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
@ -201,29 +206,20 @@ export default function TargetConfigurationDetails({
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="mb-4 list-disc pl-5">
|
||||
{targetConfiguration.outputs?.map((output, idx) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.outputs`
|
||||
);
|
||||
return (
|
||||
<li
|
||||
className="group/line overflow-hidden whitespace-nowrap"
|
||||
key={`output-${idx}`}
|
||||
>
|
||||
<TargetConfigurationProperty data={output}>
|
||||
{sourceInfo && (
|
||||
<span className="inline flex min-w-0 pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.outputs`}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</TargetConfigurationProperty>
|
||||
</li>
|
||||
);
|
||||
}) ?? <span>no outputs</span>}
|
||||
{targetConfiguration.outputs?.map((output, idx) => (
|
||||
<li
|
||||
className="group/line overflow-hidden whitespace-nowrap"
|
||||
key={`output-${idx}`}
|
||||
>
|
||||
<TargetConfigurationProperty data={output}>
|
||||
<TargetSourceInfo
|
||||
className="min-w-0 flex-1 pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100"
|
||||
propertyKey={`targets.${targetName}.outputs`}
|
||||
sourceMap={sourceMap}
|
||||
/>
|
||||
</TargetConfigurationProperty>
|
||||
</li>
|
||||
)) ?? <span>no outputs</span>}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
@ -251,30 +247,20 @@ export default function TargetConfigurationDetails({
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="mb-4 list-disc pl-5">
|
||||
{targetConfiguration.dependsOn.map((dep, idx) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.dependsOn`
|
||||
);
|
||||
|
||||
return (
|
||||
<li
|
||||
className="group/line overflow-hidden whitespace-nowrap"
|
||||
key={`dependsOn-${idx}`}
|
||||
>
|
||||
<TargetConfigurationProperty data={dep}>
|
||||
<span className="inline flex min-w-0 pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
{sourceInfo && (
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.dependsOn`}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</TargetConfigurationProperty>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{targetConfiguration.dependsOn.map((dep, idx) => (
|
||||
<li
|
||||
className="group/line overflow-hidden whitespace-nowrap"
|
||||
key={`dependsOn-${idx}`}
|
||||
>
|
||||
<TargetConfigurationProperty data={dep}>
|
||||
<TargetSourceInfo
|
||||
className="min-w-0 flex-1 pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100"
|
||||
propertyKey={`targets.${targetName}.dependsOn`}
|
||||
sourceMap={sourceMap}
|
||||
/>
|
||||
</TargetConfigurationProperty>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
@ -295,20 +281,13 @@ export default function TargetConfigurationDetails({
|
||||
<FadingCollapsible>
|
||||
<JsonCodeBlock
|
||||
data={options}
|
||||
renderSource={(propertyName: string) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.options.${propertyName}`
|
||||
);
|
||||
return sourceInfo ? (
|
||||
<span className="flex min-w-0 pl-4">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.options.${propertyName}`}
|
||||
/>
|
||||
</span>
|
||||
) : null;
|
||||
}}
|
||||
renderSource={(propertyName: string) => (
|
||||
<TargetSourceInfo
|
||||
className="flex min-w-0 pl-4"
|
||||
propertyKey={`targets.${targetName}.options.${propertyName}`}
|
||||
sourceMap={sourceMap}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FadingCollapsible>
|
||||
</div>
|
||||
@ -343,20 +322,13 @@ export default function TargetConfigurationDetails({
|
||||
<FadingCollapsible>
|
||||
<JsonCodeBlock
|
||||
data={targetConfiguration.configurations}
|
||||
renderSource={(propertyName: string) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.configurations.${propertyName}`
|
||||
);
|
||||
return sourceInfo ? (
|
||||
<span className="flex min-w-0 pl-4">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.configurations.${propertyName}`}
|
||||
/>{' '}
|
||||
</span>
|
||||
) : null;
|
||||
}}
|
||||
renderSource={(propertyName: string) => (
|
||||
<TargetSourceInfo
|
||||
className="flex min-w-0 pl-4"
|
||||
propertyKey={`targets.${targetName}.configurations.${propertyName}`}
|
||||
sourceMap={sourceMap}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FadingCollapsible>
|
||||
</>
|
||||
|
||||
@ -7,6 +7,7 @@ export interface TargetExecutorProps {
|
||||
executor?: string;
|
||||
isCompact?: boolean;
|
||||
link?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function TargetExecutor({
|
||||
@ -16,9 +17,16 @@ export function TargetExecutor({
|
||||
executor,
|
||||
isCompact,
|
||||
link,
|
||||
children,
|
||||
}: TargetExecutorProps) {
|
||||
if (script) {
|
||||
return link ? <ExternalLink href={link}>{script}</ExternalLink> : script;
|
||||
return link ? (
|
||||
<div className="group/line">
|
||||
<ExternalLink href={link}>{script}</ExternalLink> {children}
|
||||
</div>
|
||||
) : (
|
||||
script
|
||||
);
|
||||
}
|
||||
|
||||
if (commands) {
|
||||
@ -34,19 +42,22 @@ export function TargetExecutor({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ul>
|
||||
{commands?.map((c) =>
|
||||
c ? (
|
||||
<div className="group/line">
|
||||
<ul>
|
||||
{commands?.filter(Boolean).map((c) => (
|
||||
<li>{link ? <ExternalLink href={link}>{c}</ExternalLink> : c}</li>
|
||||
) : null
|
||||
)}
|
||||
</ul>
|
||||
))}
|
||||
</ul>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const displayText = command ?? executor ?? '';
|
||||
return link ? (
|
||||
<ExternalLink href={link}>{displayText}</ExternalLink>
|
||||
<div className="group/line">
|
||||
<ExternalLink href={link}>{displayText}</ExternalLink> {children}
|
||||
</div>
|
||||
) : (
|
||||
displayText
|
||||
);
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import type { TargetConfiguration } from '@nx/devkit';
|
||||
|
||||
export function getTargetExecutorSourceMapKey(
|
||||
targetConfiguration: TargetConfiguration
|
||||
): string {
|
||||
if (targetConfiguration.options?.command) {
|
||||
return 'options.command';
|
||||
} else if (targetConfiguration.options?.commands) {
|
||||
return 'options.commands';
|
||||
} else if (targetConfiguration.options?.script) {
|
||||
return 'options.script';
|
||||
} else {
|
||||
return 'executor';
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { selectSourceInfo } from './target-configuration-details.util';
|
||||
import { selectSourceInfo } from './select-source-info';
|
||||
|
||||
test('selectSourceInfo', () => {
|
||||
const map = {
|
||||
@ -0,0 +1,26 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import { SourceInfo } from '../source-info/source-info';
|
||||
import { selectSourceInfo } from './select-source-info';
|
||||
|
||||
export interface TargetSourceInfoProps {
|
||||
propertyKey: string;
|
||||
sourceMap: Record<string, string[]>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function TargetSourceInfo({
|
||||
propertyKey,
|
||||
sourceMap,
|
||||
className,
|
||||
}: TargetSourceInfoProps) {
|
||||
const sourceInfo = selectSourceInfo(sourceMap, propertyKey);
|
||||
if (!sourceInfo) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<span className={className}>
|
||||
<SourceInfo data={sourceInfo} propertyKey={propertyKey} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -1,19 +1,25 @@
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function ExternalLink({
|
||||
children,
|
||||
href,
|
||||
title,
|
||||
className,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
href: string;
|
||||
className?: string;
|
||||
title?: string;
|
||||
}) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
title={title}
|
||||
className="gap-2 text-slate-500 hover:underline dark:text-slate-400"
|
||||
className={twMerge(
|
||||
'gap-2 text-slate-500 hover:underline dark:text-slate-400',
|
||||
className
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user