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: implement AppTemplate Workflow component #2454

Merged
merged 8 commits into from
Feb 10, 2025
13 changes: 11 additions & 2 deletions src/Pages/App/CreateAppModal/CreateAppModal.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,11 @@ const CreateAppModal = ({ isJobView, handleClose }: CreateAppModalProps) => {

return {
isFormValid: Object.keys(updatedFormErrorState)
.filter((key) => key !== 'tags')
.filter((key: keyof typeof updatedFormErrorState) => key !== 'tags' && key !== 'workflowConfig')
.every((key) => !updatedFormErrorState[key]),
invalidLabels,
labelTags,
invalidWorkFlow: updatedFormErrorState.workflowConfig,
}
}

Expand Down Expand Up @@ -210,7 +211,7 @@ const CreateAppModal = ({ isJobView, handleClose }: CreateAppModalProps) => {
}

const handleCreateApp = async () => {
const { isFormValid, invalidLabels, labelTags } = validateForm()
const { isFormValid, invalidLabels, labelTags, invalidWorkFlow } = validateForm()

if (!isFormValid || invalidLabels) {
ToastManager.showToast({
Expand All @@ -222,6 +223,14 @@ const CreateAppModal = ({ isJobView, handleClose }: CreateAppModalProps) => {
return
}

if (invalidWorkFlow) {
ToastManager.showToast({
variant: ToastVariantType.error,
description: 'Invalid Workflow!',
})
return
}

const request = {
appName: formState.name,
teamId: +formState.projectId,
Expand Down
25 changes: 16 additions & 9 deletions src/Pages/App/CreateAppModal/UpdateTemplateConfig.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { noop } from '@devtron-labs/devtron-fe-common-lib'

import MaterialList from '@Components/material/MaterialList'
import CIConfig from '@Components/ciConfig/CIConfig'
import { CIConfigProps } from '@Components/ciConfig/types'
import { DockerConfigOverrideKeys } from '@Components/ciPipeline/types'
import { MaterialListProps } from '@Components/material/material.types'

import { Workflow, WorkflowProps } from './Workflow'
import { CreateAppFormStateActionType, UpdateTemplateConfigProps } from './types'

const parentState: CIConfigProps['parentState'] = {
Expand Down Expand Up @@ -72,15 +75,15 @@ const UpdateTemplateConfig = ({ formState, isJobView, handleFormStateChange }: U
})
}

// const handleWorkflowConfigChange = (workflowConfig, isError) => {
// handleFormStateChange({
// action: CreateAppFormStateActionType.updateWorkflowConfig,
// value: {
// data: workflowConfig,
// isError,
// },
// })
// }
const handleWorkflowConfigChange: WorkflowProps['onChange'] = (workflowConfig, isError) => {
handleFormStateChange({
action: CreateAppFormStateActionType.updateWorkflowConfig,
value: {
data: workflowConfig,
isError,
},
})
}

return (
<>
Expand Down Expand Up @@ -111,6 +114,10 @@ const UpdateTemplateConfig = ({ formState, isJobView, handleFormStateChange }: U
updateDockerConfigOverride={handleBuildConfigurationChange}
/>
</div>
<div className="br-8 border__secondary bg__primary p-20 flexbox-col dc__gap-16">
<h4 className="fs-14 fw-6 lh-20 cn-9 m-0">Workflows</h4>
<Workflow templateId={stringTemplateId} onChange={handleWorkflowConfigChange} />
</div>
</>
)
}
Expand Down
155 changes: 155 additions & 0 deletions src/Pages/App/CreateAppModal/Workflow/Workflow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { useEffect, useState } from 'react'
import {
APIResponseHandler,
GraphVisualizer,

Check failure on line 4 in src/Pages/App/CreateAppModal/Workflow/Workflow.tsx

View workflow job for this annotation

GitHub Actions / ci

Module '"@devtron-labs/devtron-fe-common-lib"' has no exported member 'GraphVisualizer'.

Check failure on line 4 in src/Pages/App/CreateAppModal/Workflow/Workflow.tsx

View workflow job for this annotation

GitHub Actions / ci

Module '"@devtron-labs/devtron-fe-common-lib"' has no exported member 'GraphVisualizer'.
GraphVisualizerEdge,

Check failure on line 5 in src/Pages/App/CreateAppModal/Workflow/Workflow.tsx

View workflow job for this annotation

GitHub Actions / ci

Module '"@devtron-labs/devtron-fe-common-lib"' has no exported member 'GraphVisualizerEdge'.

Check failure on line 5 in src/Pages/App/CreateAppModal/Workflow/Workflow.tsx

View workflow job for this annotation

GitHub Actions / ci

Module '"@devtron-labs/devtron-fe-common-lib"' has no exported member 'GraphVisualizerEdge'.
GraphVisualizerNode,

Check failure on line 6 in src/Pages/App/CreateAppModal/Workflow/Workflow.tsx

View workflow job for this annotation

GitHub Actions / ci

Module '"@devtron-labs/devtron-fe-common-lib"' has no exported member 'GraphVisualizerNode'.

Check failure on line 6 in src/Pages/App/CreateAppModal/Workflow/Workflow.tsx

View workflow job for this annotation

GitHub Actions / ci

Module '"@devtron-labs/devtron-fe-common-lib"' has no exported member 'GraphVisualizerNode'.
GraphVisualizerProps,

Check failure on line 7 in src/Pages/App/CreateAppModal/Workflow/Workflow.tsx

View workflow job for this annotation

GitHub Actions / ci

Module '"@devtron-labs/devtron-fe-common-lib"' has no exported member 'GraphVisualizerProps'.

Check failure on line 7 in src/Pages/App/CreateAppModal/Workflow/Workflow.tsx

View workflow job for this annotation

GitHub Actions / ci

Module '"@devtron-labs/devtron-fe-common-lib"' has no exported member 'GraphVisualizerProps'.
useAsync,
} from '@devtron-labs/devtron-fe-common-lib'

import { getCreateWorkflows } from '@Components/app/details/triggerView/workflow.service'
import { getEnvironmentListMin } from '@Services/service'

import {
getCDNodeIcon,
getValidatedNodes,
getWorkflowGraphVisualizerEdges,
getWorkflowGraphVisualizerNodes,
getWorkflowLinkedCDNodes,
} from './utils'
import { HandleNodeUpdateActionProps, NodeUpdateActionType, WorkflowProps } from './types'

export const Workflow = ({ templateId, onChange }: WorkflowProps) => {
// STATES
const [nodes, setNodes] = useState<Record<string, GraphVisualizerNode[]>>({})
const [edges, setEdges] = useState<Record<string, GraphVisualizerEdge[]>>({})

// ASYNC CALL - FETCH WORKFLOWS
const [isWorkflowDataLoading, workflowData, workflowDataErr, reloadWorkflowData] = useAsync(
() => Promise.all([getCreateWorkflows(templateId, false, ''), getEnvironmentListMin()]),
[templateId],
)

// METHODS
const setNodesHandler =
(wfId: string): GraphVisualizerProps['setNodes'] =>
(newState) =>
setNodes((prev) => ({
...prev,
[wfId]: typeof newState === 'function' ? newState(prev[wfId] || []) : newState,
}))

const setEdgesHandler =
(wfId: string): GraphVisualizerProps['setEdges'] =>
(newState) =>
setEdges((prev) => ({
...prev,
[wfId]: typeof newState === 'function' ? newState(prev[wfId] || []) : newState,
}))

// CENTRAL NODE UPDATE HANDLER
const handleNodeUpdateAction = (nodeAction: HandleNodeUpdateActionProps) => {
const { actionType } = nodeAction

switch (actionType) {
case NodeUpdateActionType.UPDATE_CD_PIPELINE:
{
const { id, value, wfId } = nodeAction
setNodes((prev) => {
const changedNodes: Parameters<WorkflowProps['onChange']>[0]['cd'] = []

const updatedNodes = {
...prev,
[wfId]: prev[wfId].map((node) => {
if (node.id === id && node.type === 'dropdownNode') {
changedNodes.push({
environmentId: Number(value.value),
pipelineId: Number(node.id),
})

return {
...node,
data: {
...node.data,
value,
icon: getCDNodeIcon({
isVirtualEnvironment: value.isVirtualEnvironment,
showPluginWarning: false,
}),
},
}
}

return node
}),
}

const linkedCDNodesMap = getWorkflowLinkedCDNodes(workflowData[0].workflows, id)
if (linkedCDNodesMap.size) {
Array.from(linkedCDNodesMap.entries()).forEach(([parentWfId, linkedCDNode]) => {
updatedNodes[parentWfId] = updatedNodes[parentWfId].map((node) =>
node.id === linkedCDNode.id && node.type === 'textNode'
? {
...node,
data: { ...node.data, text: value.label as string },
}
: node,
)
})
}

const { isValid, validatedNodes } = getValidatedNodes(updatedNodes)
onChange?.({ cd: changedNodes }, !isValid)

return validatedNodes
})
}
break

default:
break
}
}

// UPDATE NODES & EDGES AFTER API RESPONSE
useEffect(() => {
if (!isWorkflowDataLoading && workflowData) {
setNodes(
getWorkflowGraphVisualizerNodes({
workflows: workflowData[0].workflows,
environmentList: workflowData[1].result,
handleNodeUpdateAction,
}),
)
setEdges(getWorkflowGraphVisualizerEdges(workflowData[0].workflows))
}
}, [isWorkflowDataLoading, workflowData])

return (
<APIResponseHandler
progressingProps={{ pageLoader: true }}
isLoading={
isWorkflowDataLoading || !workflowData || !Object.keys(nodes).length || !Object.keys(edges).length
RohitRaj011 marked this conversation as resolved.
Show resolved Hide resolved
}
error={workflowDataErr}
reloadProps={{
reload: reloadWorkflowData,
}}
>
<>
{(workflowData?.[0]?.workflows ?? []).map(({ id, name }) => (
<div key={id} className="flexbox-col dc__gap-6">
<p className="m-0 fs-13 lh-20 fw-6">{name}</p>
<GraphVisualizer
nodes={nodes[id]}
setNodes={setNodesHandler(id)}
edges={edges[id]}
setEdges={setEdgesHandler(id)}
/>
</div>
))}
</>
</APIResponseHandler>
)
}
2 changes: 2 additions & 0 deletions src/Pages/App/CreateAppModal/Workflow/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './Workflow'
export type { WorkflowProps } from './types'
43 changes: 43 additions & 0 deletions src/Pages/App/CreateAppModal/Workflow/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { CommonNodeAttr, SelectPickerOptionType, WorkflowType } from '@devtron-labs/devtron-fe-common-lib'

import { EnvironmentListMinType } from '@Components/app/types'
import { createClusterEnvGroup } from '@Components/common'

import { CreateAppFormStateType } from '../types'

export interface WorkflowProps {
templateId: string
onChange?: (workflowConfig: CreateAppFormStateType['workflowConfig'], isError: boolean) => void
}

export type CDNodeEnvironmentSelectPickerOptionType = SelectPickerOptionType & {
isVirtualEnvironment: boolean
}

export enum NodeUpdateActionType {
UPDATE_CD_PIPELINE = 'UPDATE_CD_PIPELINE',
}

type NodeUpdateActionPropsMap = {
[NodeUpdateActionType.UPDATE_CD_PIPELINE]: {
id: string
wfId: string
value: CDNodeEnvironmentSelectPickerOptionType
}
}

export type HandleNodeUpdateActionProps<T extends keyof NodeUpdateActionPropsMap = keyof NodeUpdateActionPropsMap> =
T extends keyof NodeUpdateActionPropsMap ? { actionType: T } & NodeUpdateActionPropsMap[T] : never

export interface GetWorkflowGraphVisualizerNodesProps {
workflows: WorkflowType[]
environmentList: EnvironmentListMinType[]
handleNodeUpdateAction: (props: HandleNodeUpdateActionProps) => void
}

export interface ConvertWorkflowNodesToGraphVisualizerNodesProps
extends Pick<GetWorkflowGraphVisualizerNodesProps, 'handleNodeUpdateAction'> {
workflowNodes: CommonNodeAttr[]
workflowId: string
environmentListOptions: ReturnType<typeof createClusterEnvGroup<EnvironmentListMinType>>
}
Loading
Loading