Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useExecutionDataOptional } from "@/providers/ExecutionDataProvider";
import { APP_ROUTES } from "@/routes/router";
import type { PipelineRun } from "@/types/pipelineRun";
import { extractCanonicalName } from "@/utils/canonicalPipelineName";
import type { ComponentSpec } from "@/utils/componentSpec";
import type { ArgumentType, ComponentSpec } from "@/utils/componentSpec";
import { submitPipelineRun } from "@/utils/submitPipeline";

type RerunPipelineButtonProps = {
Expand Down Expand Up @@ -73,7 +73,9 @@ export const RerunPipelineButton = ({
componentSpec,
),
),
taskArguments: executionData?.rootDetails?.task_spec.arguments,
// The generated API types don't include SecretArgument but the backend supports it
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we re-generate the types? npm run generate-api I believe

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that's done in a later PRs - this was created before backend was ready.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in general it is a broader problem that we have two sets of types to describe the same thing - ComponentSpec one from generated api, one manually written

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with this ^ Wish we had a singular automatic solution for a type interface when dealing with the api/backend

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can set this as agenda item for Frontend Sync

taskArguments: (executionData?.rootDetails?.task_spec.arguments ??
undefined) as Record<string, ArgumentType> | undefined,
authorizationToken,
runNameOverride,
onSuccess: resolve,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,14 @@ import type { ArgumentInput } from "@/types/arguments";
import { isGraphImplementation, isSecretArgument } from "@/utils/componentSpec";

import { ArgumentInputDialog } from "./ArgumentInputDialog";
import { SecretArgumentInput } from "./SecretArgumentInput";
import {
getDefaultValue,
getInputValue,
getInputValueAsString,
getPlaceholder,
typeSpecToString,
} from "./utils";

interface SecretArgumentInputProps {
secretName: string | null;
isRemoved: boolean;
disabled: boolean;
onClear: () => void;
}

interface PlainArgumentInputProps {
argument: ArgumentInput;
inputValue: string;
Expand All @@ -72,40 +65,6 @@ interface PlainArgumentInputProps {
const ACTIONS_BASE_CLASS =
"hover:bg-transparent hover:text-blue-500 hidden group-hover:flex";

const SecretArgumentInput = ({
secretName,
isRemoved,
disabled,
onClear,
}: SecretArgumentInputProps) => {
return (
<InlineStack
gap="2"
blockAlign="center"
className={cn(
"w-full px-3 py-1 rounded-md border ",
isRemoved && "opacity-50",
)}
>
<Icon name="Lock" size="sm" className="text-amber-600 shrink-0" />
<Paragraph size="sm" className="truncate flex-1 text-amber-600">
{secretName}
</Paragraph>
{!disabled && (
<TooltipButton
onClick={onClear}
variant="ghost"
size="xs"
tooltip="Clear Secret"
className="shrink-0"
>
<Icon name="X" size="sm" />
</TooltipButton>
)}
</InlineStack>
);
};

const PlainArgumentInput = ({
argument,
inputValue,
Expand Down
Comment thread
maxy-shpfy marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import TooltipButton from "@/components/shared/Buttons/TooltipButton";
import { Icon } from "@/components/ui/icon";
import { InlineStack } from "@/components/ui/layout";
import { Paragraph } from "@/components/ui/typography";
import { cn } from "@/lib/utils";

interface SecretArgumentInputProps {
secretName: string | null;
isRemoved?: boolean;
disabled?: boolean;
onClear: () => void;
}

export const SecretArgumentInput = ({
secretName,
onClear,
isRemoved = false,
disabled = false,
}: SecretArgumentInputProps) => {
return (
<InlineStack
gap="2"
blockAlign="center"
className={cn(
"w-full px-3 py-1 rounded-md border ",
isRemoved && "opacity-50",
)}
>
<Icon name="Lock" size="sm" className="text-amber-600 shrink-0" />
<Paragraph size="sm" className="truncate flex-1 text-amber-600">
{secretName}
</Paragraph>
{!disabled && (
<TooltipButton
onClick={onClear}
variant="ghost"
size="xs"
tooltip="Clear Secret"
className="shrink-0"
>
<Icon name="X" size="sm" />
</TooltipButton>
)}
</InlineStack>
);
};
10 changes: 5 additions & 5 deletions src/components/shared/Submitters/Oasis/OasisSubmitter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useNavigate } from "@tanstack/react-router";
import { AlertCircle, CheckCircle, Loader2, SendHorizonal } from "lucide-react";
import { type MouseEvent, useRef, useState } from "react";

import type { TaskSpecOutput } from "@/api/types.gen";
import { useAwaitAuthorization } from "@/components/shared/Authentication/useAwaitAuthorization";
import { useFlagValue } from "@/components/shared/Settings/useFlags";
import { Button } from "@/components/ui/button";
Expand All @@ -17,6 +16,7 @@ import { APP_ROUTES } from "@/routes/router";
import { updateRunNotes } from "@/services/pipelineRunService";
import type { PipelineRun } from "@/types/pipelineRun";
import {
type ArgumentType,
type ComponentSpec,
isGraphImplementation,
} from "@/utils/componentSpec";
Expand Down Expand Up @@ -53,7 +53,7 @@ function useSubmitPipeline() {
onError,
}: {
componentSpec: ComponentSpec;
taskArguments?: TaskSpecOutput["arguments"];
taskArguments?: Record<string, ArgumentType>;
onSuccess: (data: PipelineRun) => void;
onError: (error: Error | string) => void;
}) => {
Expand Down Expand Up @@ -168,7 +168,7 @@ const OasisSubmitter = ({
setCooldownTime(3);
};

const handleSubmit = async (taskArguments?: Record<string, string>) => {
const handleSubmit = async (taskArguments?: Record<string, ArgumentType>) => {
if (!componentSpec) {
handleError("No pipeline to submit");
return;
Expand All @@ -183,7 +183,7 @@ const OasisSubmitter = ({

if (
onlyFixableIssues &&
!validateArguments(componentSpec?.inputs ?? [], taskArguments ?? {})
!validateArguments(componentSpec.inputs ?? [], taskArguments ?? {})
) {
setIsArgumentsDialogOpen(true);
return;
Expand All @@ -199,7 +199,7 @@ const OasisSubmitter = ({
};

const handleSubmitWithArguments = (
args: Record<string, string>,
args: Record<string, ArgumentType>,
notes: string,
) => {
runNotes.current = notes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@ import {
} from "react";

import type { TaskSpecOutput } from "@/api/types.gen";
import TooltipButton from "@/components/shared/Buttons/TooltipButton";
import { PipelineRunsList } from "@/components/shared/PipelineRunDisplay/PipelineRunsList";
import { SecretArgumentInput } from "@/components/shared/ReactFlow/FlowCanvas/TaskNode/ArgumentsEditor/SecretArgumentInput";
import { typeSpecToString } from "@/components/shared/ReactFlow/FlowCanvas/TaskNode/ArgumentsEditor/utils";
import { getArgumentsFromInputs } from "@/components/shared/ReactFlow/FlowCanvas/utils/getArgumentsFromInputs";
import { SelectSecretDialog } from "@/components/shared/SecretsManagement/SelectSecretDialog";
import {
createSecretArgument,
extractSecretName,
} from "@/components/shared/SecretsManagement/types";
import { useFlagValue } from "@/components/shared/Settings/useFlags";
import { Button } from "@/components/ui/button";
import {
Dialog,
Expand Down Expand Up @@ -39,7 +47,12 @@ import {
fetchPipelineRun,
} from "@/services/executionService";
import type { PipelineRun } from "@/types/pipelineRun";
import type { ComponentSpec, InputSpec } from "@/utils/componentSpec";
import {
type ArgumentType,
type ComponentSpec,
type InputSpec,
isSecretArgument,
} from "@/utils/componentSpec";
import { extractTaskArguments } from "@/utils/nodes/taskArguments";
import { validateArguments } from "@/utils/validations";

Expand All @@ -48,7 +61,7 @@ type TaskArguments = TaskSpecOutput["arguments"];
interface SubmitTaskArgumentsDialogProps {
open: boolean;
onCancel: () => void;
onConfirm: (args: Record<string, string>, notes: string) => void;
onConfirm: (args: Record<string, ArgumentType>, notes: string) => void;
componentSpec: ComponentSpec;
}

Expand All @@ -63,7 +76,7 @@ export const SubmitTaskArgumentsDialog = ({

const [runNotes, setRunNotes] = useState<string>("");
const [taskArguments, setTaskArguments] =
useState<Record<string, string>>(initialArgs);
useState<Record<string, ArgumentType>>(initialArgs);

// Track highlighted args with a version key to re-trigger CSS animation
const [highlightedArgs, setHighlightedArgs] = useState<Map<string, number>>(
Expand Down Expand Up @@ -92,7 +105,7 @@ export const SubmitTaskArgumentsDialog = ({
notify(`Copied ${diff.length} arguments`, "success");
};

const handleValueChange = (name: string, value: string) => {
const handleValueChange = (name: string, value: ArgumentType) => {
setTaskArguments((prev) => ({
...prev,
[name]: value,
Expand Down Expand Up @@ -157,7 +170,7 @@ export const SubmitTaskArgumentsDialog = ({
<ArgumentField
key={`${input.name}-${highlightVersion ?? "static"}`}
input={input}
value={taskArguments[input.name] ?? ""}
value={taskArguments[input.name]}
onChange={handleValueChange}
isHighlighted={highlightVersion !== undefined}
/>
Expand Down Expand Up @@ -315,8 +328,8 @@ const CopyFromRunPopover = ({

interface ArgumentFieldProps {
input: InputSpec;
value: string;
onChange: (name: string, value: string) => void;
value: ArgumentType | undefined;
onChange: (name: string, value: ArgumentType) => void;
isHighlighted?: boolean;
}

Expand All @@ -326,49 +339,101 @@ const ArgumentField = ({
onChange,
isHighlighted,
}: ArgumentFieldProps) => {
const isSecretUIEnabled = useFlagValue("secrets");
const [isSelectSecretDialogOpen, setIsSelectSecretDialogOpen] =
useState(false);

const isValueSecret = isSecretArgument(value);
const secretName = isValueSecret ? extractSecretName(value) : null;
// For the submit dialog, we only expect string values or SecretArguments
const stringValue = typeof value === "string" ? value : "";

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
onChange(input.name, e.target.value);
};

const handleSecretSelect = (selectedSecretName: string) => {
onChange(input.name, createSecretArgument(selectedSecretName));
setIsSelectSecretDialogOpen(false);
};

const handleClearSecret = () => {
onChange(input.name, "");
};

const typeLabel = typeSpecToString(input.type);
const isRequired = !input.optional;
const placeholder = input.default ?? "";
const hasValidValue =
isValueSecret || Boolean(stringValue) || Boolean(placeholder);

return (
<BlockStack
gap="1"
className={cn(
"rounded-md px-1 py-1",
isHighlighted && "animate-highlight-fade",
)}
>
<InlineStack gap="2" align="start">
<Paragraph size="sm" className="wrap-break-word">
{input.name}
</Paragraph>
<Paragraph size="xs" tone="subdued" className="truncate">
({typeLabel}
{isRequired ? "*" : ""})
</Paragraph>
</InlineStack>

{input.description && (
<Paragraph size="xs" tone="subdued" className="italic">
{input.description}
</Paragraph>
)}

<Input
id={input.name}
value={value}
onChange={handleChange}
placeholder={placeholder}
<>
<BlockStack
gap="1"
className={cn(
isRequired && !value && !placeholder && "border-red-300",
// todo: remove this once we have a proper style in Input component
"bg-white!",
"rounded-md px-1 py-1",
isHighlighted && "animate-highlight-fade",
)}
>
<InlineStack gap="2" align="start">
<Paragraph size="sm" className="wrap-break-word">
{input.name}
</Paragraph>
<Paragraph size="xs" tone="subdued" className="truncate">
({typeLabel}
{isRequired ? "*" : ""})
</Paragraph>
</InlineStack>

{input.description && (
<Paragraph size="xs" tone="subdued" className="italic">
{input.description}
</Paragraph>
)}

<div className="relative group w-full">
{isValueSecret && secretName ? (
<SecretArgumentInput
secretName={secretName}
onClear={handleClearSecret}
/>
) : (
<>
<Input
id={input.name}
value={stringValue}
onChange={handleChange}
placeholder={placeholder}
className={cn(
isRequired && !hasValidValue && "border-red-300",
isSecretUIEnabled && "group-hover:pr-8",
"bg-white!",
)}
/>
{isSecretUIEnabled && (
<InlineStack className="absolute right-0 top-1/2 -translate-y-1/2 mr-1 px-1 bg-white">
<TooltipButton
onClick={() => setIsSelectSecretDialogOpen(true)}
className="hover:bg-transparent hover:text-blue-500 hidden group-hover:flex"
variant="ghost"
size="xs"
tooltip="Use Secret"
>
<Icon name="Lock" />
</TooltipButton>
</InlineStack>
)}
</>
)}
</div>
</BlockStack>

<SelectSecretDialog
open={isSelectSecretDialogOpen}
onOpenChange={setIsSelectSecretDialogOpen}
onSelect={handleSecretSelect}
/>
</BlockStack>
</>
);
};
Loading
Loading