Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enhance Webhook component #6313

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a3c9a24
📝 (constants.py): Add "copy_field" attribute to FIELD_FORMAT_ATTRIBUT…
Cristhianzl Feb 10, 2025
66b4584
✨ (NodeDescription/index.tsx): refactor renderedDescription useMemo t…
Cristhianzl Feb 11, 2025
5d5155f
📝 (webhook.py): Add cURL field to WebhookComponent for better integra…
Cristhianzl Feb 11, 2025
8091e82
Merge branch 'main' into cz/webhook
Cristhianzl Feb 12, 2025
53dc18c
✨ (index.tsx): Add a button to generate a token in the WebhookFieldCo…
Cristhianzl Feb 12, 2025
be36681
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 12, 2025
8776df0
✨ (generate-token-dialog.tsx): add GenerateTokenDialog component to h…
Cristhianzl Feb 13, 2025
6cc752c
✨ (frontend): introduce new feature to create API keys with customiza…
Cristhianzl Feb 13, 2025
a031def
Merge branch 'cz/webhook' of https://github.com/langflow-ai/langflow …
Cristhianzl Feb 13, 2025
656ccff
add pool interval variable and tests
Cristhianzl Feb 13, 2025
432f9ac
📝 (NodeOutputfield): Remove unused ScanEyeIcon component
Cristhianzl Feb 13, 2025
395be0d
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 13, 2025
68e2c47
🔧 (backend): rename webhook_pooling_interval to webhook_polling_inter…
Cristhianzl Feb 13, 2025
4b6bbaf
📝 (schemas.py): add webhook_polling_interval field to ConfigResponse …
Cristhianzl Feb 13, 2025
a8a22dc
📝 (frontend): Update import paths and remove unused imports for bette…
Cristhianzl Feb 14, 2025
b36087b
📝 (use-get-api-keys.ts): add a TODO comment to request API key from D…
Cristhianzl Feb 14, 2025
8323811
📝 (input_mixin.py): Remove copy_field attribute from BaseInputMixin a…
Cristhianzl Feb 14, 2025
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
5 changes: 5 additions & 0 deletions src/backend/base/langflow/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ def run(
help="Defines the maximum file size for the upload in MB.",
show_default=False,
),
webhook_polling_interval: int | None = typer.Option( # noqa: ARG001
None,
help="Defines the polling interval for the webhook.",
show_default=False,
),
) -> None:
"""Run Langflow."""
# Register SIGTERM handler
Expand Down
1 change: 1 addition & 0 deletions src/backend/base/langflow/api/v1/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,4 @@ class ConfigResponse(BaseModel):
auto_saving_interval: int
health_check_max_retries: int
max_file_size_upload: int
webhook_polling_interval: int
1 change: 1 addition & 0 deletions src/backend/base/langflow/base/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"refresh_button_text",
"options",
"advanced",
"copy_field",
]

ORJSON_OPTIONS = orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS | orjson.OPT_OMIT_MICROSECONDS
19 changes: 17 additions & 2 deletions src/backend/base/langflow/components/data/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

class WebhookComponent(Component):
display_name = "Webhook"
description = "Defines a webhook input for the flow."
name = "Webhook"
icon = "webhook"

Expand All @@ -16,7 +15,23 @@ class WebhookComponent(Component):
name="data",
display_name="Payload",
info="Receives a payload from external systems via HTTP POST.",
)
advanced=True,
),
MultilineInput(
name="curl",
display_name="cURL",
value="CURL_WEBHOOK",
advanced=True,
input_types=[],
),
MultilineInput(
name="endpoint",
display_name="Endpoint",
value="BACKEND_URL",
advanced=False,
copy_field=True,
input_types=[],
),
]
outputs = [
Output(display_name="Data", name="output_data", method="build_data"),
Expand Down
10 changes: 10 additions & 0 deletions src/backend/base/langflow/graph/graph/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
should_continue,
)
from langflow.graph.schema import InterfaceComponentTypes, RunOutputs
from langflow.graph.utils import log_vertex_build
from langflow.graph.vertex.base import Vertex, VertexStates
from langflow.graph.vertex.schema import NodeData, NodeTypeEnum
from langflow.graph.vertex.vertex_types import ComponentVertex, InterfaceVertex, StateVertex
Expand Down Expand Up @@ -1596,6 +1597,15 @@ async def _execute_tasks(self, tasks: list[asyncio.Task], lock: asyncio.Lock) ->
t.cancel()
raise result
if isinstance(result, VertexBuildResult):
await log_vertex_build(
flow_id=self.flow_id,
vertex_id=result.vertex.id,
valid=result.valid,
params=result.params,
data=result.result_dict,
artifacts=result.artifacts,
)

vertices.append(result.vertex)
else:
msg = f"Invalid result from task {task_name}: {result}"
Expand Down
2 changes: 1 addition & 1 deletion src/backend/base/langflow/inputs/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ class MultilineInput(MessageTextInput, MultilineMixin, InputTraceMixin, ToolMode

field_type: SerializableFieldTypes = FieldTypes.TEXT
multiline: CoalesceBool = True

copy_field: CoalesceBool = False

class MultilineSecretInput(MessageTextInput, MultilineMixin, InputTraceMixin):
"""Represents a multiline input field.
Expand Down
2 changes: 2 additions & 0 deletions src/backend/base/langflow/services/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ class Settings(BaseSettings):
"""The maximum number of vertex builds to keep in the database."""
max_vertex_builds_per_vertex: int = 2
"""The maximum number of builds to keep per vertex. Older builds will be deleted."""
webhook_polling_interval: int = 5000
"""The polling interval for the webhook in ms."""

# MCP Server
mcp_server_enabled: bool = True
Expand Down
2 changes: 2 additions & 0 deletions src/backend/base/langflow/template/field/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class Input(BaseModel):

refresh_button: bool | None = None
"""Specifies if the field should have a refresh button. Defaults to False."""

refresh_button_text: str | None = None
"""Specifies the text for the refresh button. Defaults to None."""

Expand All @@ -97,6 +98,7 @@ class Input(BaseModel):

load_from_db: bool = False
"""Specifies if the field should be loaded from the database. Defaults to False."""

title_case: bool = False
"""Specifies if the field should be displayed in title case. Defaults to True."""

Expand Down
4 changes: 4 additions & 0 deletions src/backend/base/langflow/utils/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ async def update_settings(
auto_saving_interval: int = 1000,
health_check_max_retries: int = 5,
max_file_size_upload: int = 100,
webhook_polling_interval: int = 5000,
) -> None:
"""Update the settings from a config file."""
# Check for database_url in the environment variables
Expand Down Expand Up @@ -448,6 +449,9 @@ async def update_settings(
if max_file_size_upload is not None:
logger.debug(f"Setting max_file_size_upload to {max_file_size_upload}")
settings_service.settings.update_settings(max_file_size_upload=max_file_size_upload)
if webhook_polling_interval is not None:
logger.debug(f"Setting webhook_polling_interval to {webhook_polling_interval}")
settings_service.settings.update_settings(webhook_polling_interval=webhook_polling_interval)


def is_class_method(func, cls):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function NodeDescription({
description,
selected,
nodeId,
emptyPlaceholder = "Double Click to Edit Description",
emptyPlaceholder = "",
placeholderClassName,
charLimit,
inputClassName,
Expand Down Expand Up @@ -69,23 +69,23 @@ export default function NodeDescription({
}, [description]);

const MemoizedMarkdown = memo(Markdown);
const renderedDescription = useMemo(
() =>
description === "" || !description ? (
emptyPlaceholder
) : (
<MemoizedMarkdown
linkTarget="_blank"
className={cn(
"markdown prose flex w-full flex-col text-[13px] leading-5 word-break-break-word [&_pre]:whitespace-break-spaces [&_pre]:!bg-code-description-background [&_pre_code]:!bg-code-description-background",
mdClassName,
)}
>
{String(description)}
</MemoizedMarkdown>
),
[description, emptyPlaceholder, mdClassName],
);

const renderedDescription = useMemo(() => {
if (description === "" || !description) {
return emptyPlaceholder;
}
return (
<MemoizedMarkdown
linkTarget="_blank"
className={cn(
"markdown prose flex w-full flex-col text-[13px] leading-5 word-break-break-word [&_pre]:whitespace-break-spaces [&_pre]:!bg-code-description-background [&_pre_code]:!bg-code-description-background",
mdClassName,
)}
>
{String(description)}
</MemoizedMarkdown>
);
}, [description, emptyPlaceholder, mdClassName]);

const handleBlurFn = () => {
setNodeDescription(nodeDescription);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class";
import { NodeInfoType } from "@/components/core/parameterRenderComponent/types";
import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value";
import {
CustomParameterComponent,
CustomParameterLabel,
getCustomParameterTitle,
} from "@/customization/components/custom-parameter";
import useAuthStore from "@/stores/authStore";
import { cn } from "@/utils/utils";
import { useEffect, useRef } from "react";
import { useEffect, useMemo, useRef } from "react";
import { default as IconComponent } from "../../../../components/common/genericIconComponent";
import ShadTooltip from "../../../../components/common/shadTooltipComponent";
import {
Expand Down Expand Up @@ -44,6 +46,8 @@ export default function NodeInputField({
const ref = useRef<HTMLDivElement>(null);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const isAuth = useAuthStore((state) => state.isAuthenticated);
const currentFlow = useFlowStore((state) => state.currentFlow);
const myData = useTypesStore((state) => state.data);
const postTemplateValue = usePostTemplateValue({
node: data.node!,
Expand All @@ -64,6 +68,17 @@ export default function NodeInputField({
name,
});

const nodeInformationMetadata: NodeInfoType = useMemo(() => {
return {
flowId: currentFlow?.id ?? "",
nodeType: data?.type?.toLowerCase() ?? "",
flowName: currentFlow?.name ?? "",
endpointName: currentFlow?.endpoint_name ?? "",
isAuth,
variableName: name,
};
}, [data?.node?.id, isAuth, name]);

useFetchDataOnMount(data.node!, handleNodeClass, name, postTemplateValue);

useEffect(() => {
Expand Down Expand Up @@ -193,6 +208,7 @@ export default function NodeInputField({
: data.node?.template[name].placeholder
}
isToolMode={isToolMode}
nodeInformationMetadata={nodeInformationMetadata}
/>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,6 @@ const SnowflakeIcon = memo(() => (
<IconComponent className="h-5 w-5 text-ice" name="Snowflake" />
));

const ScanEyeIcon = memo(({ className }: { className: string }) => (
<IconComponent
className={className}
name="ScanEye"
strokeWidth={ICON_STROKE_WIDTH}
/>
));

// Memoize Button components
const HideShowButton = memo(
({
Expand Down
22 changes: 13 additions & 9 deletions src/frontend/src/CustomNodes/GenericNode/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,13 @@ function GenericNode({
const [hasChangedNodeDescription, setHasChangedNodeDescription] =
useState(false);

const editedNameDescription =
editNameDescription && hasChangedNodeDescription;

const hasDescription = useMemo(() => {
return data.node?.description && data.node?.description !== "";
}, [data.node?.description]);

const memoizedNodeToolbarComponent = useMemo(() => {
return selected ? (
<>
Expand Down Expand Up @@ -294,25 +301,21 @@ function GenericNode({
showNode
? "top-2 translate-x-[10.4rem]"
: "top-0 translate-x-[6.4rem]",
editNameDescription && hasChangedNodeDescription
editedNameDescription
? "bg-accent-emerald"
: "bg-zinc-foreground",
)}
data-testid={
editNameDescription && hasChangedNodeDescription
editedNameDescription
? "save-name-description-button"
: "edit-name-description-button"
}
>
<ForwardedIconComponent
name={
editNameDescription && hasChangedNodeDescription
? "Check"
: "PencilLine"
}
name={editedNameDescription ? "Check" : "PencilLine"}
strokeWidth={ICON_STROKE_WIDTH}
className={cn(
editNameDescription && hasChangedNodeDescription
editedNameDescription
? "text-accent-emerald-foreground"
: "text-muted-foreground",
"icon-size",
Expand Down Expand Up @@ -490,8 +493,9 @@ function GenericNode({
<div
data-testid={`${data.id}-main-node`}
className={cn(
"grid gap-3 truncate text-wrap p-4 leading-5",
"grid truncate text-wrap p-4 leading-5",
showNode && "border-b",
hasDescription && "gap-3",
)}
>
<div
Expand Down
Loading
Loading