Show app name on workflow logic function nodes (#21675)

Workflow logic-function nodes from installed integration apps now show
the owning app's name as the node label (e.g. "People Data Labs")
instead of the generic "Action". The descriptive title below it (e.g.
"Enrich Person") is unchanged.

This only applies to installed integration apps — plain custom-code
functions and standard functions keep showing "Action".

A new `useWorkflowNodeLabel` hook resolves the logic function's
`applicationId` and returns the app name for integration apps, falling
back to the node type otherwise. It's used by the editable, read-only,
and run node renderers.

## Before

<img width="572" height="766" alt="CleanShot 2026-06-16 at 16 26 42@2x"
src="https://github.com/user-attachments/assets/4ca9cf2f-5469-437d-a4dc-084057730589"
/>

## After

<img width="460" height="706" alt="CleanShot 2026-06-16 at 16 25 46@2x"
src="https://github.com/user-attachments/assets/504ed2ea-49a6-49c6-830e-2fdd214b8c61"
/>


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21675?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Raphaël Bosi
2026-06-16 16:30:23 +02:00
committed by GitHub
parent 637ae65e9f
commit 5b1cfa4cc0
4 changed files with 46 additions and 6 deletions
@@ -17,13 +17,14 @@ import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-node
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultSourceHandleId';
import { useConnectionState } from '@/workflow/workflow-diagram/workflow-nodes/hooks/useConnectionState';
import { useWorkflowNodeLabel } from '@/workflow/workflow-diagram/workflow-nodes/hooks/useWorkflowNodeLabel';
import { isNodeTitleHighlighted } from '@/workflow/workflow-diagram/workflow-nodes/utils/isNodeTitleHighlighted';
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { Position } from '@xyflow/react';
import { useState } from 'react';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared/utils';
const StyledAddStepButtonContainer = styled.div<{
shouldDisplay: boolean;
@@ -75,6 +76,8 @@ export const WorkflowDiagramStepNodeEditableContent = ({
const isNodeConnectable = isConnectable({ nodeId: id });
const nodeLabel = useWorkflowNodeLabel(data);
const handleAddStepButtonContainerClick = (
event: React.MouseEvent<HTMLDivElement>,
) => {
@@ -110,7 +113,7 @@ export const WorkflowDiagramStepNodeEditableContent = ({
<WorkflowNodeRightPart>
<WorkflowNodeLabelWithCounterPart>
<WorkflowNodeLabel selected={selected}>
{capitalize(data.nodeType)}
{nodeLabel}
</WorkflowNodeLabel>
</WorkflowNodeLabelWithCounterPart>
@@ -20,10 +20,11 @@ import { WorkflowNodeLabelWithCounterPart } from '@/workflow/workflow-diagram/wo
import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeRightPart';
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultSourceHandleId';
import { useWorkflowNodeLabel } from '@/workflow/workflow-diagram/workflow-nodes/hooks/useWorkflowNodeLabel';
import { isNodeTitleHighlighted } from '@/workflow/workflow-diagram/workflow-nodes/utils/isNodeTitleHighlighted';
import { Position } from '@xyflow/react';
import { useContext } from 'react';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui-deprecated/display';
export const WorkflowDiagramStepNodeReadonly = ({
@@ -85,6 +86,8 @@ export const WorkflowDiagramStepNodeReadonly = ({
actionType: data.nodeType === 'action' ? data.actionType : undefined,
});
const nodeLabel = useWorkflowNodeLabel(data);
return (
<>
<WorkflowNodeContainer
@@ -100,7 +103,7 @@ export const WorkflowDiagramStepNodeReadonly = ({
<WorkflowNodeRightPart>
<WorkflowNodeLabelWithCounterPart>
<WorkflowNodeLabel selected={selected}>
{capitalize(data.nodeType)}
{nodeLabel}
</WorkflowNodeLabel>
</WorkflowNodeLabelWithCounterPart>
@@ -22,11 +22,12 @@ import { WorkflowNodeLabel } from '@/workflow/workflow-diagram/workflow-nodes/co
import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeRightPart';
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultSourceHandleId';
import { useWorkflowNodeLabel } from '@/workflow/workflow-diagram/workflow-nodes/hooks/useWorkflowNodeLabel';
import { getNodeIterationCount } from '@/workflow/workflow-diagram/workflow-nodes/utils/getNodeIterationCount';
import { styled } from '@linaria/react';
import { Position } from '@xyflow/react';
import { useContext } from 'react';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared/utils';
import { StepStatus } from 'twenty-shared/workflow';
import { IconCheck, IconX, useIcons } from 'twenty-ui-deprecated/display';
import { Loader } from 'twenty-ui-deprecated/feedback';
@@ -116,6 +117,8 @@ export const WorkflowRunDiagramStepNode = ({
? getNodeIterationCount({ stepInfo })
: 0;
const nodeLabel = useWorkflowNodeLabel(data);
const handleClick = () => {
if (!isDefined(workflowVisualizerWorkflowId)) {
throw new Error('Workflow ID must be defined');
@@ -153,7 +156,7 @@ export const WorkflowRunDiagramStepNode = ({
<WorkflowNodeRightPart>
<StyledNodeLabelWithCounterPart>
<WorkflowNodeLabel runStatus={data.runStatus} selected={selected}>
{capitalize(data.nodeType)}
{nodeLabel}
</WorkflowNodeLabel>
<StyledRightPartContainer>
@@ -0,0 +1,31 @@
import { useApplicationChipData } from '@/applications/hooks/useApplicationChipData';
import { useIsThirdPartyApplication } from '@/applications/hooks/useIsThirdPartyApplication';
import { logicFunctionsSelector } from '@/logic-functions/states/logicFunctionsSelector';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { type WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { capitalize, isDefined } from 'twenty-shared/utils';
export const useWorkflowNodeLabel = (
data: WorkflowDiagramStepNodeData,
): string => {
const logicFunctions = useAtomStateValue(logicFunctionsSelector);
const logicFunctionId =
data.nodeType === 'action' ? data.logicFunctionId : undefined;
const applicationId = isDefined(logicFunctionId)
? logicFunctions.find(
(logicFunction) => logicFunction.id === logicFunctionId,
)?.applicationId
: undefined;
const isThirdPartyApplication = useIsThirdPartyApplication(applicationId);
const { applicationChipData } = useApplicationChipData({ applicationId });
if (isThirdPartyApplication) {
return applicationChipData.name;
}
return capitalize(data.nodeType);
};