-
Notifications
You must be signed in to change notification settings - Fork 789
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into GlebBerjoskin/push_notification_update
- Loading branch information
Showing
28 changed files
with
2,706 additions
and
329 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
workflow: | ||
id: update-jira-ticket | ||
triggers: | ||
- type: manual | ||
actions: | ||
- name: jira-action | ||
provider: | ||
config: '{{ providers.Jira }}' | ||
type: jira | ||
with: | ||
board_name: '' | ||
description: Update description of an issue | ||
issue_id: 10023 | ||
project_key: '' | ||
summary: Update summary of an issue |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import React from 'react' | ||
import useStore from './builder-store'; | ||
import { Button } from '@tremor/react'; | ||
import { reConstructWorklowToDefinition } from 'utils/reactFlow'; | ||
|
||
export default function BuilderChanagesTracker({onDefinitionChange}:{onDefinitionChange:(def: Record<string,any>) => void}) { | ||
const { | ||
nodes, | ||
edges, | ||
setEdges, | ||
setNodes, | ||
isLayouted, | ||
setIsLayouted, | ||
v2Properties, | ||
changes, | ||
setChanges, | ||
lastSavedChanges, | ||
setLastSavedChanges | ||
} = useStore(); | ||
const handleDiscardChanges = (e: React.MouseEvent<HTMLButtonElement>) => { | ||
if(!isLayouted) return; | ||
setEdges(lastSavedChanges.edges || []); | ||
setNodes(lastSavedChanges.nodes || []); | ||
setChanges(0); | ||
setIsLayouted(false); | ||
} | ||
|
||
const handleSaveChanges = (e: React.MouseEvent<HTMLButtonElement>) =>{ | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
setLastSavedChanges({nodes: nodes, edges: edges}); | ||
const value = reConstructWorklowToDefinition({nodes: nodes, edges: edges, properties: v2Properties}); | ||
onDefinitionChange(value); | ||
setChanges(0); | ||
} | ||
|
||
return ( | ||
<div className='flex gap-2.5'> | ||
<Button | ||
onClick={handleDiscardChanges} | ||
disabled={changes === 0} | ||
>Discard{changes ? `(${changes})`: ""}</Button> | ||
<Button | ||
onClick={handleSaveChanges} | ||
disabled={changes === 0} | ||
>Save</Button> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import React from "react"; | ||
import { BaseEdge, EdgeLabelRenderer, getSmoothStepPath } from "@xyflow/react"; | ||
import type { Edge, EdgeProps } from "@xyflow/react"; | ||
import useStore from "./builder-store"; | ||
import { PlusIcon } from "@radix-ui/react-icons"; | ||
import { Button } from "@tremor/react"; | ||
import '@xyflow/react/dist/style.css'; | ||
|
||
interface CustomEdgeProps extends EdgeProps { | ||
label?: string; | ||
type?: string; | ||
data?: any; | ||
} | ||
|
||
const CustomEdge: React.FC<CustomEdgeProps> = ({ | ||
id, | ||
sourceX, | ||
sourceY, | ||
targetX, | ||
targetY, | ||
label, | ||
source, | ||
target, | ||
data, | ||
style, | ||
}: CustomEdgeProps) => { | ||
const { deleteEdges, edges, setSelectedEdge, selectedEdge } = useStore(); | ||
|
||
// Calculate the path and midpoint | ||
const [edgePath, labelX, labelY] = getSmoothStepPath({ | ||
sourceX, | ||
sourceY, | ||
targetX, | ||
targetY, | ||
borderRadius: 10, | ||
}); | ||
|
||
const midpointX = (sourceX + targetX) / 2; | ||
const midpointY = (sourceY + targetY) / 2; | ||
|
||
let dynamicLabel = label; | ||
const isLayouted = !!data?.isLayouted; | ||
let showAddButton = !source?.includes('empty') && !target?.includes('trigger_end') && source !== 'start'; | ||
|
||
if (!showAddButton) { | ||
showAddButton = target?.includes('trigger_end') && source?.includes("trigger_start"); | ||
} | ||
|
||
const color = dynamicLabel === "True" ? "left-0 bg-green-500" : dynamicLabel === "False" ? "bg-red-500" : "bg-orange-500"; | ||
return ( | ||
<> | ||
<BaseEdge | ||
id={id} | ||
path={edgePath} | ||
style={{ | ||
opacity: isLayouted ? 1 : 0, | ||
...style, | ||
strokeWidth: 2, | ||
}} | ||
|
||
/> | ||
<defs> | ||
<marker | ||
id={`arrow-${id}`} | ||
markerWidth="15" | ||
markerHeight="15" | ||
refX="10" | ||
refY="5" | ||
orient="auto" | ||
markerUnits="strokeWidth" | ||
> | ||
<path | ||
d="M 0,0 L 10,5 L 0,10 L 3,5 Z" | ||
fill="currentColor" | ||
className="text-gray-500 font-extrabold" // Tailwind class for arrow color | ||
style={{ opacity: isLayouted ? 1 : 0 }} | ||
/> | ||
</marker> | ||
</defs> | ||
<BaseEdge | ||
id={id} | ||
path={edgePath} | ||
className="stroke-gray-700 stroke-2" | ||
style={{ | ||
markerEnd: `url(#arrow-${id})`, | ||
opacity: isLayouted ? 1 : 0 | ||
}} // Add arrowhead | ||
/> | ||
<EdgeLabelRenderer> | ||
{!!dynamicLabel && ( | ||
<div | ||
className={`absolute ${color} text-white rounded px-3 py-1 border border-gray-700`} | ||
style={{ | ||
transform: `translate(-50%, -50%) translate(${dynamicLabel === "True" ? labelX - 45 : labelX + 48}px, ${labelY}px)`, | ||
pointerEvents: "none", | ||
opacity: isLayouted ? 1 : 0 | ||
}} | ||
> | ||
{dynamicLabel} | ||
</div> | ||
)} | ||
{showAddButton && <Button | ||
style={{ | ||
position: "absolute", | ||
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`, | ||
pointerEvents: "all", | ||
opacity: isLayouted ? 1 : 0 | ||
}} | ||
className={`p-0 m-0 bg-transparent text-transparent border-none`} | ||
// tooltip="Add node" | ||
onClick={(e) => { | ||
setSelectedEdge(id); | ||
}} | ||
> | ||
<PlusIcon | ||
className={`size-7 hover:text-black rounded text-sm bg-white border text-gray-700 ${selectedEdge === id ? "border-2 border-orange-500" : "border-gray-700" | ||
}`} | ||
/> </Button>} | ||
</EdgeLabelRenderer> | ||
</> | ||
); | ||
}; | ||
|
||
export default CustomEdge; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import React, { memo } from "react"; | ||
import { Handle, Position } from "@xyflow/react"; | ||
import NodeMenu from "./NodeMenu"; | ||
import useStore, { FlowNode, V2Step } from "./builder-store"; | ||
import Image from "next/image"; | ||
import { GoPlus } from "react-icons/go"; | ||
import { MdNotStarted } from "react-icons/md"; | ||
import { GoSquareFill } from "react-icons/go"; | ||
import { PiDiamondsFourFill, PiSquareLogoFill } from "react-icons/pi"; | ||
import { BiSolidError } from "react-icons/bi"; | ||
import { FaHandPointer } from "react-icons/fa"; | ||
import { toast } from "react-toastify"; | ||
|
||
|
||
|
||
function IconUrlProvider(data: FlowNode["data"]) { | ||
const { componentType, type } = data || {}; | ||
if (type === "alert" || type === "workflow" || type === "trigger" || !type) return "/keep.png"; | ||
return `/icons/${type | ||
?.replace("step-", "") | ||
?.replace("action-", "") | ||
?.replace("__end", "") | ||
?.replace("condition-", "")}-icon.png`; | ||
} | ||
|
||
function CustomNode({ id, data }: FlowNode) { | ||
const { selectedNode, setSelectedNode, setOpneGlobalEditor, errorNode, synced } = useStore(); | ||
const type = data?.type | ||
?.replace("step-", "") | ||
?.replace("action-", "") | ||
?.replace("condition-", "") | ||
?.replace("__end", "") | ||
?.replace("trigger_", ""); | ||
|
||
const isEmptyNode = !!data?.type?.includes("empty"); | ||
const specialNodeCheck = ['start', 'end', 'trigger_end', 'trigger_start'].includes(type) | ||
|
||
function getTriggerIcon(step: any) { | ||
const { type } = step; | ||
switch (type) { | ||
case "manual": | ||
return <FaHandPointer size={32} /> | ||
case "interval": | ||
return <PiDiamondsFourFill size={32} /> | ||
} | ||
} | ||
|
||
return ( | ||
<> | ||
{!specialNodeCheck && <div | ||
className={`p-2 flex shadow-md rounded-md bg-white border-2 w-full h-full ${id === selectedNode | ||
? "border-orange-500" | ||
: "border-stone-400" | ||
}`} | ||
onClick={(e) => { | ||
e.stopPropagation(); | ||
if(!synced){ | ||
toast('Please save the previous step or wait while properties sync with the workflow.'); | ||
return; | ||
} | ||
if (type === 'start' || type === 'end' || id?.includes('end') || id?.includes('empty')) { | ||
if (id?.includes('empty')) { | ||
setSelectedNode(id); | ||
} | ||
setOpneGlobalEditor(true); | ||
return; | ||
} | ||
setSelectedNode(id); | ||
}} | ||
style={{ | ||
opacity: data.isLayouted ? 1 : 0, | ||
borderStyle: isEmptyNode ? 'dashed' : "", | ||
borderColor: errorNode == id ? 'red' : '' | ||
}} | ||
> | ||
{isEmptyNode && ( | ||
<div className="flex-1 flex flex-col items-center justify-center"> | ||
<GoPlus className="w-8 h-8 text-gray-600 font-bold p-0" /> | ||
{selectedNode === id && ( | ||
<div className="text-gray-600 font-bold text-center">Go to Toolbox</div> | ||
)} | ||
</div> | ||
)} | ||
{errorNode === id && <BiSolidError className="size-16 text-red-500 absolute right-[-40px] top-[-40px]" />} | ||
{!isEmptyNode && ( | ||
<div className="container flex-1 flex flex-row items-center justify-between gap-2 flex-wrap"> | ||
{getTriggerIcon(data)} | ||
{!!data && !['interval', 'manual'].includes(data.type) && <Image | ||
src={IconUrlProvider(data) || "/keep.png"} | ||
alt={data?.type} | ||
className="object-cover w-8 h-8 rounded-full bg-gray-100" | ||
width={32} | ||
height={32} | ||
/>} | ||
<div className="flex-1 flex-col gap-2 flex-wrap truncate"> | ||
<div className="text-lg font-bold truncate">{data?.name}</div> | ||
<div className="text-gray-500 truncate"> | ||
{type} | ||
</div> | ||
</div> | ||
<div> | ||
<NodeMenu data={data} id={id} /> | ||
</div> | ||
</div> | ||
)} | ||
|
||
<Handle | ||
type="target" | ||
position={Position.Top} | ||
className="w-32" | ||
/> | ||
<Handle | ||
type="source" | ||
position={Position.Bottom} | ||
className="w-32" | ||
/> | ||
</div>} | ||
|
||
{specialNodeCheck && <div | ||
style={{ | ||
opacity: data.isLayouted ? 1 : 0 | ||
}} | ||
onClick={(e) => { | ||
e.stopPropagation(); | ||
if(!synced){ | ||
toast('Please save the previous step or wait while properties sync with the workflow.'); | ||
return; | ||
} | ||
if (type === 'start' || type === 'end' || id?.includes('end')) { | ||
setOpneGlobalEditor(true); | ||
return; | ||
} | ||
setSelectedNode(id); | ||
}} | ||
> | ||
<div className={`flex flex-col items-center justify-center`}> | ||
{type === 'start' && <MdNotStarted className="size-20 bg-orange-500 text-white rounded-full font-bold mb-2" />} | ||
{type === 'end' && <GoSquareFill className="size-20 bg-orange-500 text-white rounded-full font-bold mb-2" />} | ||
{['threshold', 'assert', 'foreach'].includes(type) && | ||
<div className={`border-2 ${id === selectedNode | ||
? "border-orange-500" | ||
: "border-stone-400"}`}> | ||
{id.includes('end') ? <PiSquareLogoFill className="size-20 rounded bg-white-400 p-2" /> : | ||
<Image | ||
src={IconUrlProvider(data) || "/keep.png"} | ||
alt={data?.type} | ||
className="object-contain size-20 rounded bg-white-400 p-2" | ||
width={32} | ||
height={32} | ||
/>} | ||
</div> | ||
} | ||
{'start' === type && <Handle | ||
type="source" | ||
position={Position.Bottom} | ||
className="w-32" | ||
/>} | ||
|
||
{'end' === type && <Handle | ||
type="target" | ||
position={Position.Top} | ||
className="w-32" | ||
/>} | ||
</div> | ||
</div>} | ||
</> | ||
); | ||
} | ||
|
||
export default memo(CustomNode); |
Oops, something went wrong.