nx/graph/ui-project-details/src/lib/target-configuration-details/target-configuration-details.tsx
Emily Xiong 08ef0e4bde
feat(graph): show script content in header (#23257)
<!-- 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
-->
![Screenshot 2024-05-15 at 10 32
54 AM](https://github.com/nrwl/nx/assets/16211801/1ef4d67f-94e5-46bd-bcee-b966984e86cd)

<img width="1023" alt="Screenshot 2024-05-08 at 5 53 06 PM"
src="https://github.com/nrwl/nx/assets/16211801/cb9161bd-fc4a-4965-89f7-09ce8d72226e">

<img width="585" alt="Screenshot 2024-05-08 at 5 52 51 PM"
src="https://github.com/nrwl/nx/assets/16211801/760ebe19-1c29-4fad-ba9d-174b8303a362">


## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2024-05-21 18:11:04 -04:00

371 lines
14 KiB
TypeScript

/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
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';
import { Pill } from '../pill';
import { TargetConfigurationDetailsHeader } from '../target-configuration-details-header/target-configuration-details-header';
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';
interface TargetConfigurationDetailsProps {
projectName: string;
targetName: string;
targetConfiguration: TargetConfiguration;
sourceMap: Record<string, string[]>;
variant?: 'default' | 'compact';
onCollapse?: (targetName: string) => void;
onExpand?: (targetName: string) => void;
onRunTarget?: (data: { projectName: string; targetName: string }) => void;
onViewInTaskGraph?: (data: {
projectName: string;
targetName: string;
}) => void;
collapsable: boolean;
}
export default function TargetConfigurationDetails({
variant,
projectName,
targetName,
targetConfiguration,
sourceMap,
onViewInTaskGraph,
onRunTarget,
collapsable,
}: TargetConfigurationDetailsProps) {
const isCompact = variant === 'compact';
const [collapsed, setCollapsed] = useState(true);
const { expandedTargets, toggleTarget } = useContext(ExpandedTargetsContext);
const handleCopyClick = async (copyText: string) => {
await window.navigator.clipboard.writeText(copyText);
};
const handleCollapseToggle = useCallback(() => {
if (toggleTarget) {
toggleTarget(targetName);
}
}, [toggleTarget, targetName]);
useEffect(() => {
if (!collapsable) {
setCollapsed(false);
return;
}
if (expandedTargets?.includes(targetName)) {
setCollapsed(false);
} else {
setCollapsed(true);
}
}, [expandedTargets, targetName, collapsable]);
const { link, options, script, ...displayHeader } =
getDisplayHeaderFromTargetConfiguration(targetConfiguration);
const configurations = targetConfiguration.configurations;
const shouldRenderOptions =
options &&
(typeof options === 'object' ? Object.keys(options).length : true);
const shouldRenderConfigurations =
configurations &&
(typeof configurations === 'object'
? Object.keys(configurations).length
: true);
return (
<div className="relative overflow-hidden rounded-md border border-slate-200 dark:border-slate-700/60">
<TargetConfigurationDetailsHeader
isCollasped={collapsed}
toggleCollapse={handleCollapseToggle}
collapsable={collapsable}
isCompact={isCompact}
targetConfiguration={targetConfiguration}
projectName={projectName}
targetName={targetName}
sourceMap={sourceMap}
onRunTarget={onRunTarget}
onViewInTaskGraph={onViewInTaskGraph}
/>
{/* body */}
{!collapsed && (
<div className="p-4 text-base">
<div className="group mb-4">
<h4 className="mb-4">
<TargetExecutorTitle
{...displayHeader}
handleCopyClick={handleCopyClick}
/>
</h4>
<p className="pl-5 font-mono">
<TargetExecutor {...displayHeader} link={link} />
</p>
</div>
{script && (
<div className="group mb-4">
<h4 className="mb-4">
<TargetExecutorTitle
script={script}
handleCopyClick={handleCopyClick}
/>
</h4>
<p className="pl-5 font-mono">
<TargetExecutor script={script} link={link} />
</p>
</div>
)}
{targetConfiguration.inputs && (
<div className="group">
<h4 className="mb-4">
<Tooltip
openAction="hover"
content={(<PropertyInfoTooltip type="inputs" />) as any}
>
<span className="font-medium">
<TooltipTriggerText>Inputs</TooltipTriggerText>
</span>
</Tooltip>
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard
onCopy={() =>
handleCopyClick(
`"inputs": ${JSON.stringify(
targetConfiguration.inputs
)}`
)
}
/>
</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>
);
})}
</ul>
</div>
)}
{targetConfiguration.outputs && (
<div className="group">
<h4 className="mb-4">
<Tooltip
openAction="hover"
content={(<PropertyInfoTooltip type="outputs" />) as any}
>
<span className="font-medium">
<TooltipTriggerText>Outputs</TooltipTriggerText>
</span>
</Tooltip>
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard
onCopy={() =>
handleCopyClick(
`"outputs": ${JSON.stringify(
targetConfiguration.outputs
)}`
)
}
/>
</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>}
</ul>
</div>
)}
{targetConfiguration.dependsOn && (
<div className="group">
<h4 className="mb-4">
<Tooltip
openAction="hover"
content={(<PropertyInfoTooltip type="dependsOn" />) as any}
>
<span className="font-medium">
<TooltipTriggerText>Depends On</TooltipTriggerText>
</span>
</Tooltip>
<span className="inline pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
<CopyToClipboard
onCopy={() =>
handleCopyClick(
`"dependsOn": ${JSON.stringify(
targetConfiguration.dependsOn
)}`
)
}
/>
</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>
);
})}
</ul>
</div>
)}
{shouldRenderOptions ? (
<>
<h4 className="mb-4">
<Tooltip
openAction="hover"
content={(<PropertyInfoTooltip type="options" />) as any}
>
<span className="font-medium">
<TooltipTriggerText>Options</TooltipTriggerText>
</span>
</Tooltip>
</h4>
<div className="mb-4">
<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;
}}
/>
</FadingCollapsible>
</div>
</>
) : (
''
)}
{shouldRenderConfigurations ? (
<>
<h4 className="mb-4 py-2">
<Tooltip
openAction="hover"
content={
(<PropertyInfoTooltip type="configurations" />) as any
}
>
<span className="font-medium">
<TooltipTriggerText>Configurations</TooltipTriggerText>
</span>
</Tooltip>{' '}
{targetConfiguration.defaultConfiguration && (
<span className="ml-3 cursor-help">
<Pill
tooltip="Default Configuration"
text={targetConfiguration.defaultConfiguration}
color="yellow"
/>
</span>
)}
</h4>
<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;
}}
/>
</FadingCollapsible>
</>
) : (
''
)}
</div>
)}
</div>
);
}