Skip to content

Commit

Permalink
feat(graph): show script content in header
Browse files Browse the repository at this point in the history
  • Loading branch information
xiongemi committed May 10, 2024
1 parent efe4cb1 commit 38f4515
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { Pill } from '../pill';
import { TargetTechnologies } from '../target-technologies/target-technologies';
import { SourceInfo } from '../source-info/source-info';
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
import { getExecutorFromTargetConfiguration } from '../utils/get-executor-from-target-configuration';
import { TargetExecutor } from '../target-executor/target-executor';

export interface TargetConfigurationDetailsHeaderProps {
isCollasped: boolean;
Expand Down Expand Up @@ -52,10 +54,7 @@ export const TargetConfigurationDetailsHeader = ({
isCollasped = false;
}

const singleCommand =
targetConfiguration.executor === 'nx:run-commands'
? targetConfiguration.command ?? targetConfiguration.options?.command
: null;
const { executor } = getExecutorFromTargetConfiguration(targetConfiguration);

return (
<header
Expand Down Expand Up @@ -86,7 +85,7 @@ export const TargetConfigurationDetailsHeader = ({
{isCollasped &&
targetConfiguration?.executor !== '@nx/js:release-publish' && (
<p className="min-w-0 flex-1 truncate text-sm text-slate-400">
{singleCommand ? singleCommand : targetConfiguration.executor}
<TargetExecutor {...executor} isCompact={true} />
</p>
)}
{targetName === 'nx-release-publish' && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@
import type { TargetConfiguration } from '@nx/devkit';

import { JsonCodeBlock } from '@nx/graph/ui-code-block';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
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 {
ExternalLink,
PropertyInfoTooltip,
Tooltip,
} from '@nx/graph/ui-tooltips';
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 { getExecutorFromTargetConfiguration } from '../utils/get-executor-from-target-configuration';
import {
TargetExecutor,
TargetExecutorTitle,
} from '../target-executor/target-executor';

interface TargetConfigurationDetailsProps {
projectName: string;
Expand Down Expand Up @@ -71,36 +72,8 @@ export default function TargetConfigurationDetails({
}
}, [expandedTargets, targetName, collapsable]);

let executorLink: string | null = null;

// TODO: Handle this better because this will not work with labs
if (targetConfiguration.executor?.startsWith('@nx/')) {
const packageName = targetConfiguration.executor
.split('/')[1]
.split(':')[0];
const executorName = targetConfiguration.executor
.split('/')[1]
.split(':')[1];
executorLink = `https://nx.dev/nx-api/${packageName}/executors/${executorName}`;
} else if (targetConfiguration.executor === 'nx:run-commands') {
executorLink = `https://nx.dev/nx-api/nx/executors/run-commands`;
} else if (targetConfiguration.executor === 'nx:run-script') {
executorLink = `https://nx.dev/nx-api/nx/executors/run-script`;
}

const singleCommand =
targetConfiguration.executor === 'nx:run-commands'
? targetConfiguration.command ?? targetConfiguration.options?.command
: null;
const options = useMemo(() => {
if (singleCommand) {
const { command, ...rest } = targetConfiguration.options;
return rest;
} else {
return targetConfiguration.options;
}
}, [targetConfiguration.options, singleCommand]);

const { executorLink, executor, options } =
getExecutorFromTargetConfiguration(targetConfiguration);
const configurations = targetConfiguration.configurations;

const shouldRenderOptions =
Expand Down Expand Up @@ -132,45 +105,13 @@ export default function TargetConfigurationDetails({
<div className="p-4 text-base">
<div className="group mb-4">
<h4 className="mb-4">
{singleCommand ? (
<span className="font-medium">
Command
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard
onCopy={() =>
handleCopyClick(`"command": "${singleCommand}"`)
}
/>
</span>
</span>
) : (
<Tooltip
openAction="hover"
content={(<PropertyInfoTooltip type="executors" />) as any}
>
<span className="font-medium">
<TooltipTriggerText>Executor</TooltipTriggerText>
</span>
</Tooltip>
)}
<TargetExecutorTitle
{...executor}
handleCopyClick={handleCopyClick}
/>
</h4>
<p className="pl-5 font-mono">
{executorLink ? (
<span>
<ExternalLink
href={executorLink ?? 'https://nx.dev/nx-api'}
text={
singleCommand
? singleCommand
: targetConfiguration.executor
}
/>
</span>
) : singleCommand ? (
singleCommand
) : (
targetConfiguration.executor
)}
<TargetExecutor {...executor} link={executorLink} />
</p>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Meta, StoryObj } from '@storybook/react';
import { TargetExecutor } from './target-executor';

const meta: Meta<typeof TargetExecutor> = {
component: TargetExecutor,
title: 'TargetExecutor',
};
export default meta;

type Story = StoryObj<typeof TargetExecutor>;

export const Command: Story = {
args: {
command: 'nx run my-app:build',
},
};

export const Commands: Story = {
args: {
commands: ['nx run my-app:build', 'nx run my-app:test'],
},
};

export const Script: Story = {
args: {
script: 'nx run my-app:build',
},
};

export const Executor: Story = {
args: {
executor: 'nx run my-app:build',
},
};
112 changes: 112 additions & 0 deletions graph/ui-project-details/src/lib/target-executor/target-executor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {
ExternalLink,
PropertyInfoTooltip,
Tooltip,
} from '@nx/graph/ui-tooltips';
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
import { TooltipTriggerText } from '../target-configuration-details/tooltip-trigger-text';

export interface TargetExecutorProps {
command?: string;
commands?: string[];
script?: string;
executor?: string;
isCompact?: boolean;
link?: string;
}

export function TargetExecutor({
command,
commands,
script,
executor,
isCompact,
link,
}: TargetExecutorProps) {
if (commands) {
if (isCompact) {
return link ? (
<ExternalLink href={link}>commands[0]</ExternalLink>
) : (
commands[0]
);
}
return (
<ul>
{commands?.map((c) =>
c ? (
<li>{link ? <ExternalLink href={link}>{c}</ExternalLink> : c}</li>
) : null
)}
</ul>
);
}

const executorText = command ?? script ?? executor ?? '';
return link ? (
<ExternalLink href={link}>{executorText}</ExternalLink>
) : (
executorText
);
}

export function TargetExecutorTitle({
commands,
command,
script,
handleCopyClick,
}: {
handleCopyClick: (copyText: string) => void;
commands?: string[];
command?: string;
script?: string;
}) {
if (commands && commands.length) {
return (
<span className="font-medium">
Commands
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard
onCopy={() =>
handleCopyClick(
`"commands": [${commands.map((c) => `"${c}"`).join(', ')}]`
)
}
/>
</span>
</span>
);
}
if (command) {
return (
<span className="font-medium">
Command
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard
onCopy={() => handleCopyClick(`"command": "${command}"`)}
/>
</span>
</span>
);
}
if (script) {
return (
<span className="font-medium">
Script
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard onCopy={() => handleCopyClick(script)} />
</span>
</span>
);
}
return (
<Tooltip
openAction="hover"
content={(<PropertyInfoTooltip type="executors" />) as any}
>
<span className="font-medium">
<TooltipTriggerText>Executor</TooltipTriggerText>
</span>
</Tooltip>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import type { TargetConfiguration } from '@nx/devkit';

export function getExecutorFromTargetConfiguration(
targetConfiguration: TargetConfiguration
): {
executorLink: string | undefined;
options: Record<string, any>;
executor?: {
command?: string;
commands?: string[];
script?: string;
executor?: string;
};
} {
let executorLink: string | undefined;
let executor: {
command?: string;
commands?: string[];
script?: string;
executor?: string;
} = {
executor: targetConfiguration.executor,
};
let options = targetConfiguration.options;
// TODO: Handle this better because this will not work with labs
if (targetConfiguration.executor?.startsWith('@nx/')) {
const packageName = targetConfiguration.executor
.split('/')[1]
.split(':')[0];
const executorName = targetConfiguration.executor
.split('/')[1]
.split(':')[1];
executorLink = `https://nx.dev/nx-api/${packageName}/executors/${executorName}`;
} else if (targetConfiguration.executor === 'nx:run-commands') {
executorLink = `https://nx.dev/nx-api/nx/executors/run-commands`;
executor.command =
targetConfiguration.command ?? targetConfiguration.options?.command;
executor.commands = targetConfiguration.options?.commands?.map(
(c: { command: string }) => c.command ?? c
);
const { command, commands, ...rest } = targetConfiguration.options;
options = rest;
} else if (targetConfiguration.executor === 'nx:run-script') {
executorLink = `https://nx.dev/nx-api/nx/executors/run-script`;
const scriptText =
targetConfiguration.options?.scriptContent ??
targetConfiguration.options?.script;
if (scriptText) {
executor.script = scriptText;
const { scriptContent, script, ...rest } = targetConfiguration.options;
options = rest;
}
}

return {
executorLink,
executor,
options,
};
}
9 changes: 5 additions & 4 deletions graph/ui-tooltips/src/lib/external-link.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline';

export function ExternalLink({
text,
children,
href,
title,
}: {
text: string;
children?: React.ReactNode;
href: string;
title?: string;
}) {
return (
<a
href={href}
title={title}
className="text-slate-500 dark:text-slate-400 hover:underline inline-flex items-center gap-2"
className="gap-2 text-slate-500 hover:underline dark:text-slate-400"
target="_blank"
rel="noreferrer"
>
{text} <ArrowTopRightOnSquareIcon className="w-4 h-4 inline" />
{children} <ArrowTopRightOnSquareIcon className="inline h-4 w-4" />
</a>
);
}

0 comments on commit 38f4515

Please sign in to comment.