Skip to content

Commit cb3b581

Browse files
authored
Merge pull request #57 from FlowTestAI/e2e-integrations
E2e integrations of generate and env tab
2 parents 4b36e8d + 3ae3a83 commit cb3b581

File tree

13 files changed

+330
-245
lines changed

13 files changed

+330
-245
lines changed

src/components/atoms/Tabs.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { XMarkIcon } from '@heroicons/react/24/outline';
33
import { useTabStore } from 'stores/TabStore';
44
import ConfirmActionModal from 'components/molecules/modals/ConfirmActionModal';
55
import { isEqual } from 'lodash';
6+
import { OBJ_TYPES } from 'constants/Common';
67

78
const Tabs = () => {
89
const tabs = useTabStore((state) => state.tabs);
@@ -18,22 +19,27 @@ const Tabs = () => {
1819
'before:absolute before:h-[0.25rem] before:w-full before:bg-slate-300 before:content-[""] before:bottom-0 before:left-0';
1920
const tabCommonStyles =
2021
'tab flex items-center gap-x-2 border-r border-neutral-300 bg-transparent pr-0 tracking-[0.15em] transition duration-500 ease-in text-sm flex-nowrap';
21-
const messageForConfirmActionModal = 'You have unsaved changes in the flowtest, are you sure you want to close it?';
22+
const messageForConfirmActionModal = `You have unsaved changes in the ${focusTab?.type}, are you sure you want to close it?`;
2223

2324
const handleCloseTab = (event, tab) => {
2425
event.stopPropagation();
2526
event.preventDefault();
26-
// const tabId = event.currentTarget.dataset.tabId;
27-
// const { isDirty, collectionId } = tabs.find((tab) => {
28-
// if (tab.id === tabId) return tab;
29-
// });
27+
3028
setClosingTabId(tab.id);
3129
setClosingCollectionId(tab.collectionId);
3230

33-
if (tab.flowDataDraft && !isEqual(tab.flowData, tab.flowDataDraft)) {
34-
console.debug(`Confirm close for tabId: ${tab.id} : collectionId: ${tab.collectionId}`);
35-
setConfirmActionModalOpen(true);
36-
return;
31+
if (tab.type === OBJ_TYPES.flowtest) {
32+
if (tab.flowDataDraft && !isEqual(tab.flowData, tab.flowDataDraft)) {
33+
console.debug(`Confirm close for tabId: ${tab.id} : collectionId: ${tab.collectionId}`);
34+
setConfirmActionModalOpen(true);
35+
return;
36+
}
37+
} else if (tab.type === OBJ_TYPES.environment) {
38+
if (tab.variablesDraft && !isEqual(tab.variables, tab.variablesDraft)) {
39+
console.debug(`Confirm close for tabId: ${tab.id} : collectionId: ${tab.collectionId}`);
40+
setConfirmActionModalOpen(true);
41+
return;
42+
}
3743
}
3844
closeTab(tab.id, tab.collectionId);
3945
};
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React from 'react';
2+
import useEnvStore from 'stores/EnvStore';
3+
import { EditText } from 'react-edit-text';
4+
import 'react-edit-text/dist/index.css';
5+
import { toast } from 'react-toastify';
6+
import { TrashIcon } from '@heroicons/react/24/outline';
7+
8+
const Env = () => {
9+
const variables = useEnvStore((state) => state.variables);
10+
const handleAddRow = useEnvStore((state) => state.handleAddRow);
11+
const handleKeyChange = useEnvStore((state) => state.handleKeyChange);
12+
const handleValueChange = useEnvStore((state) => state.handleValueChange);
13+
14+
const handleSave = ({ name, value, previousValue }) => {
15+
console.log('handle save');
16+
if (name === 'editVariableKey') {
17+
const existingVar = Object.keys(variables).find((key) => key === value);
18+
if (existingVar) {
19+
toast.error('A variable with the same name already exists');
20+
} else {
21+
handleKeyChange(value, previousValue);
22+
}
23+
} else {
24+
handleValueChange(name, value);
25+
}
26+
};
27+
28+
return (
29+
<div className='p-4'>
30+
{/* <div>Test Tab panel for environment</div> */}
31+
<div className='overflow-x-auto'>
32+
<table className='table table-zebra'>
33+
{/* head */}
34+
<thead>
35+
<tr>
36+
<th></th>
37+
<th>Key</th>
38+
<th>Value</th>
39+
{/* <th>New Value</th> */}
40+
</tr>
41+
</thead>
42+
<tbody>
43+
{Object.entries(variables).map(([key, value], index) => (
44+
<tr key={index}>
45+
<th>{index}</th>
46+
<td>
47+
<EditText name='editVariableKey' className='text-xl' value={key} onSave={handleSave} />
48+
</td>
49+
<td>
50+
<EditText name={key} className='text-xl' value={value} onSave={handleSave} />
51+
</td>
52+
<td>
53+
<div
54+
className='relative inline-block p-2 text-left transition duration-200 ease-out rounded rounded-l-none hover:bg-slate-200'
55+
onClick={() => {
56+
console.log(`Delete ${key}`);
57+
}}
58+
>
59+
<TrashIcon className='w-4 h-4' aria-hidden='true' />
60+
</div>
61+
</td>
62+
</tr>
63+
))}
64+
</tbody>
65+
</table>
66+
<button onClick={handleAddRow}>+ Add Variable</button>
67+
</div>
68+
</div>
69+
);
70+
};
71+
72+
export default Env;

src/components/molecules/flow/graph/compute/utils.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@ const computeNodeVariable = (variable, prevNodeOutputData) => {
4646
};
4747

4848
export const computeNodeVariables = (variables, prevNodeOutputData) => {
49-
let evalVariables = {};
50-
Object.entries(variables).map(([vname, variable]) => {
51-
evalVariables[vname] = computeNodeVariable(variable, prevNodeOutputData);
52-
});
49+
const evalVariables = {};
50+
if (variables) {
51+
Object.entries(variables).map(([vname, variable]) => {
52+
evalVariables[vname] = computeNodeVariable(variable, prevNodeOutputData);
53+
});
54+
}
5355
return evalVariables;
5456
};
5557

src/components/molecules/flow/index.js

Lines changed: 8 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useMemo, useState, useEffect } from 'react';
1+
import React, { useCallback, useMemo, useState } from 'react';
22
import { PropTypes } from 'prop-types';
33
import ReactFlow, { useNodesState, useEdgesState, addEdge, Controls, Background, ControlButton } from 'reactflow';
44
import 'reactflow/dist/style.css';
@@ -8,9 +8,6 @@ import { toast } from 'react-toastify';
88
// css
99
import './index.css';
1010

11-
// notification
12-
import { useSnackbar } from 'notistack';
13-
1411
// ReactFlow Canvas
1512
import CustomEdge from './edges/ButtonEdge';
1613

@@ -20,18 +17,10 @@ import RequestNode from './nodes/RequestNode';
2017
import OutputNode from './nodes/OutputNode';
2118
import EvaluateNode from './nodes/EvaluateNode';
2219
import DelayNode from './nodes/DelayNode';
23-
24-
// file system
2520
import AuthNode from './nodes/AuthNode';
26-
import { useTabStore } from 'stores/TabStore';
2721
import FlowNode from 'components/atoms/flow/FlowNode';
28-
import { Popover } from '@headlessui/react';
29-
import { generateFlowData } from './flowtestai';
30-
import { GENAI_MODELS } from 'constants/Common';
3122
import useCanvasStore from 'stores/CanvasStore';
3223

33-
import { shallow } from 'zustand/shallow';
34-
3524
const StartNode = () => (
3625
<FlowNode title='Start' handleLeft={false} handleRight={true} handleRightData={{ type: 'source' }}></FlowNode>
3726
);
@@ -107,16 +96,14 @@ const selector = (state) => ({
10796
onConnect: state.onConnect,
10897
setNodes: state.setNodes,
10998
setEdges: state.setEdges,
99+
setLogs: state.setLogs,
110100
});
111101

112102
const Flow = ({ collectionId }) => {
113-
const { nodes, edges, onNodesChange, onEdgesChange, onConnect, setNodes, setEdges } = useCanvasStore(selector);
103+
const { nodes, edges, onNodesChange, onEdgesChange, onConnect, setNodes, setEdges, setLogs } =
104+
useCanvasStore(selector);
114105
//console.log(nodes);
115106

116-
// notification
117-
// eslint-disable-next-line no-unused-vars
118-
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
119-
120107
const [reactFlowInstance, setReactFlowInstance] = useState(null);
121108

122109
const nodeTypes = useMemo(
@@ -212,20 +199,13 @@ const Flow = ({ collectionId }) => {
212199
return canConnect;
213200
};
214201

215-
// graph
216-
// eslint-disable-next-line no-unused-vars
217-
const [graphRun, setGraphRun] = useState(false);
218-
// eslint-disable-next-line no-unused-vars
219-
const [graphRunLogs, setGraphRunLogs] = useState(undefined);
220-
221202
const onGraphComplete = (result, logs) => {
222203
console.debug('Graph complete callback: ', result);
223-
setGraphRun(true);
224-
setGraphRunLogs(logs);
204+
setLogs(logs);
225205
if (result[0] == 'Success') {
226-
enqueueSnackbar('FlowTest Run Success!', { variant: 'success' });
206+
toast.success('FlowTest Run Success! View Logs');
227207
} else if (result[0] == 'Failed') {
228-
enqueueSnackbar('FlowTest Run Failed!', { variant: 'error' });
208+
toast.error('FlowTest Run Failed! View Logs');
229209
}
230210
runnableEdges(false);
231211
};
@@ -245,7 +225,7 @@ const Flow = ({ collectionId }) => {
245225
onDragOver={onDragOver}
246226
//onNodeDragStop={() => setCanvasDirty()}
247227
isValidConnection={isValidConnection}
248-
//fitView
228+
fitView
249229
>
250230
<Controls>
251231
<ControlButton
@@ -265,26 +245,6 @@ const Flow = ({ collectionId }) => {
265245
</ControlButton>
266246
</Controls>
267247
<Background variant='dots' gap={12} size={1} />
268-
<div className='absolute right-4 z-[2000] max-w-sm px-4 '>
269-
<button
270-
type='button'
271-
onClick={() => {
272-
generateFlowData('Add a pet then get all pets with status available', GENAI_MODELS.openai, collectionId)
273-
.then((flowData) => {
274-
const result = init(flowData);
275-
console.log(result);
276-
setNodes(result.nodes);
277-
setEdges(result.edges);
278-
})
279-
.catch((error) => {
280-
console.log(error);
281-
toast.error(`Error while generating flow data`);
282-
});
283-
}}
284-
>
285-
FlowTestAI
286-
</button>
287-
</div>
288248
<AddNodes collectionId={collectionId} />
289249
</ReactFlow>
290250
</div>

src/components/molecules/flow/nodes/RequestNode.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { PropTypes } from 'prop-types';
33
import RequestBody from './RequestBody';
44
import FlowNode from 'components/atoms/flow/FlowNode';
55
import { getInputType } from 'utils/common';
6-
import { PlusIcon, TrashIcon } from '@heroicons/react/24/solid';
6+
import { PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
77
import { getDefaultValue } from 'utils/common';
88
import AddVariableModal from 'components/molecules/modals/flow/AddVariableModal';
99
import useCanvasStore from 'stores/CanvasStore';
@@ -38,7 +38,7 @@ const RequestNode = ({ id, data }) => {
3838
{data.variables &&
3939
Object.keys(data.variables).map((id) => (
4040
<div className='flex items-center justify-between pb-2' key={id}>
41-
<div className='flex items-center justify-between rounded-md border border-neutral-500 text-sm text-neutral-500 outline-0 focus:ring-0'>
41+
<div className='flex items-center justify-between text-sm border rounded-md border-neutral-500 text-neutral-500 outline-0 focus:ring-0'>
4242
{data.variables[id].type === 'Boolean' ? (
4343
<select
4444
onChange={(e) => handleVariableChange(e, id)}
@@ -60,12 +60,12 @@ const RequestNode = ({ id, data }) => {
6060
/>
6161
)}
6262
<label>{id}</label>
63-
<div className='rounded-br-md rounded-tr-md border-l border-l-neutral-500 px-4 py-2'>
63+
<div className='px-4 py-2 border-l rounded-br-md rounded-tr-md border-l-neutral-500'>
6464
{data.variables[id].type}
6565
</div>
6666
</div>
6767
<div onClick={(e) => handleDeleteVariable(e, id)} className='p-2 text-neutral-500'>
68-
<TrashIcon className='h-4 w-4' />
68+
<TrashIcon className='w-4 h-4' />
6969
</div>
7070
</div>
7171
))}
@@ -89,18 +89,18 @@ const RequestNode = ({ id, data }) => {
8989
className='nodrag nowheel mb-2 block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 outline-blue-300 focus:border-blue-100 focus:ring-blue-100'
9090
name='username'
9191
onChange={handleUrlInputChange}
92-
defaultValue={data.url ? data.url : ''}
92+
value={data.url ? data.url : ''}
9393
/>
9494
</div>
9595
<RequestBody nodeId={id} nodeData={data} />
9696
<div className='border-t border-neutral-300 bg-slate-100'>
9797
<div className='flex items-center justify-between px-2 py-4 font-medium'>
9898
<h3>Variables</h3>
9999
<button className='p-2' onClick={() => setVariableDialogOpen(true)}>
100-
<PlusIcon className='h-4 w-4' />
100+
<PlusIcon className='w-4 h-4' />
101101
</button>
102102
</div>
103-
<div className='border-t border-neutral-300 bg-slate-50 p-2 pt-4'>{renderVariables()}</div>
103+
<div className='p-2 pt-4 border-t border-neutral-300 bg-slate-50'>{renderVariables()}</div>
104104
</div>
105105
</div>
106106
<AddVariableModal

0 commit comments

Comments
 (0)