Skip to content

Commit

Permalink
fix: add result name validation
Browse files Browse the repository at this point in the history
  • Loading branch information
danielgrittner committed Nov 13, 2024
1 parent 4e93aa9 commit b655702
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 4 deletions.
5 changes: 4 additions & 1 deletion admyral/editor/graph_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
serialize_json_with_reference,
deserialize_json_with_reference,
)
from admyral.utils.collections import is_not_empty


def workflow_to_editor_workflow_graph(
Expand Down Expand Up @@ -174,7 +175,9 @@ def editor_workflow_graph_to_workflow(
workflow_dag[node.id] = ActionNode(
id=node.id,
type=node.action_type,
result_name=node.result_name,
result_name=node.result_name
if is_not_empty(node.result_name)
else None,
secrets_mapping=node.secrets_mapping,
args={
k: deserialize_json_with_reference(v) for k, v in node.args.items()
Expand Down
11 changes: 11 additions & 0 deletions admyral/server/endpoints/editor_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@


VALID_WORKFLOW_NAME_REGEX = re.compile(r"^[a-zA-Z][a-zA-Z0-9 _]*$")
SNAKE_CASE_REGEX = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]*$")


router = APIRouter()
Expand Down Expand Up @@ -141,6 +142,16 @@ async def create_workflow_from_react_flow_graph(
detail="Invalid workflow name. Workflow names must start with a letter and can only contain alphanumeric characters, underscores, and spaces.",
)

if any(
not SNAKE_CASE_REGEX.match(node.result_name)
for node in editor_workflow_graph.nodes
if node.type == "action" and node.result_name is not None
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid node result name. Result names must start with a letter and can only contain alphanumeric characters and underscores.",
)

workflow = editor_workflow_graph_to_workflow(editor_workflow_graph)
try:
await get_admyral_store().create_workflow(
Expand Down
12 changes: 12 additions & 0 deletions admyral/server/endpoints/workflow_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
WorkflowSchedule,
WorkflowTriggerResponse,
WorkflowMetadata,
ActionNode,
)
from admyral.logger import get_logger
from admyral.typings import JsonValue
Expand All @@ -27,6 +28,7 @@
MANUAL_TRIGGER_SOURCE_NAME = "manual"
SPACE = " "
VALID_WORKFLOW_NAME_REGEX = re.compile(r"^[a-zA-Z][a-zA-Z0-9 _]*$")
SNAKE_CASE_REGEX = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]*$")


class WorkflowBody(BaseModel):
Expand Down Expand Up @@ -75,6 +77,16 @@ async def push_workflow_impl(
detail="Invalid workflow name. Workflow names must start with a letter and can only contain alphanumeric characters, underscores, and spaces.",
)

if any(
not SNAKE_CASE_REGEX.match(node.result_name)
for node in request.workflow_dag.dag.values()
if isinstance(node, ActionNode) and node.result_name is not None
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid node result name. Result names must be snake_case.",
)

admyral_store = get_admyral_store()
workers_client = get_workers_client()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { useSecretsStore } from "@/stores/secrets-store";
import { useEditorActionStore } from "@/stores/editor-action-store";
import { TEditorWorkflowActionNode } from "@/types/react-flow";
import { produce } from "immer";
import { ChangeEvent, useEffect } from "react";
import { ChangeEvent, useEffect, useState } from "react";
import { useImmer } from "use-immer";
import { TActionMetadata } from "@/types/editor-actions";
import CodeEditorWithDialog from "@/components/code-editor-with-dialog/code-editor-with-dialog";
import useSaveWorkflow from "@/providers/save-workflow";
import { useRouter } from "next/navigation";
import { isValidResultName } from "@/lib/workflow-validation";

function buildInitialArgs(
action: TEditorWorkflowActionNode,
Expand All @@ -38,7 +39,7 @@ export default function ActionEditPanel() {
const { actionsIndex } = useEditorActionStore();
const { secrets } = useSecretsStore();
const { saveWorkflow } = useSaveWorkflow();

const [validResultName, setValidResultName] = useState<boolean>(true);
const [args, updateArgs] = useImmer<string[]>([]);

useEffect(() => {
Expand Down Expand Up @@ -141,8 +142,18 @@ export default function ActionEditPanel() {
<TextField.Root
variant="surface"
value={actionData.resultName || ""}
onChange={onChangeResultName}
onChange={(event) => {
onChangeResultName(event);
setValidResultName(
isValidResultName(event.target.value),
);
}}
/>
{!validResultName && (
<Text color="red">
Result names must be snake_case.
</Text>
)}
</Flex>

{actionDefinition.secretsPlaceholders.length > 0 && (
Expand Down
5 changes: 5 additions & 0 deletions web/src/lib/workflow-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ export const WORKFLOW_NAME_VALIDATION_ERROR_MESSAGE =
"The workflow name must start with a letter. After the first letter, the name must only contain letters, numbers, underscores, and spaces.";

const WORKFLOW_NAME_REGEX = new RegExp("^[a-zA-Z][a-zA-Z0-9 _]*$");
const SNAKE_CASE_REGEX = new RegExp("^[a-zA-Z][a-zA-Z0-9_]*$");

export function isValidWorkflowName(name: string): boolean {
return WORKFLOW_NAME_REGEX.test(name);
}

export function isValidResultName(name: string): boolean {
return SNAKE_CASE_REGEX.test(name);
}
17 changes: 17 additions & 0 deletions web/src/providers/save-workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useEditorActionStore } from "@/stores/editor-action-store";
import { EditorWorkflowNodeType } from "@/types/react-flow";
import { useToast } from "@/providers/toast";
import {
isValidResultName,
isValidWorkflowName,
WORKFLOW_NAME_VALIDATION_ERROR_MESSAGE,
} from "@/lib/workflow-validation";
Expand Down Expand Up @@ -55,6 +56,22 @@ export function SaveWorkflowProvider({
);
}

if (
workflow.nodes
.filter(
(node) => node.type === EditorWorkflowNodeType.ACTION,
)
.some(
(node) =>
node.resultName !== null &&
!isValidResultName(node.resultName),
)
) {
throw new WorkflowValidationError(
"Invalid node result name. Result names must be snake_case.",
);
}

// Make sure that we only save args which are present in the current
// action definition. If an action is updated and an argument is
// removed, we want to clean it up here.
Expand Down

0 comments on commit b655702

Please sign in to comment.