Skip to content

Commit 44bb080

Browse files
authored
Merge pull request #61 from FlowTestAI/group-nodes-bytag
Group nodes by tag in AddNodes for better discoverability
2 parents cd168d2 + 743b373 commit 44bb080

File tree

10 files changed

+107
-50
lines changed

10 files changed

+107
-50
lines changed

packages/flowtest-electron/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"chokidar": "^3.6.0",
2929
"dotenv": "^16.4.5",
3030
"electron-store": "^8.1.0",
31+
"flatted": "^3.3.1",
3132
"fs": "^0.0.1-security",
3233
"json-refs": "^3.0.15",
3334
"langchain": "^0.1.28",

packages/flowtest-electron/src/ipc/collection.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const deleteFile = require('../utils/filemanager/deletefile');
1616
const { flowDataToReadableData, readableDataToFlowData } = require('../utils/parser');
1717
const readFile = require('../utils/filemanager/readfile');
1818
const FlowtestAI = require('../utils/flowtestai');
19+
const { stringify, parse } = require('flatted');
1920

2021
const collectionStore = new Collections();
2122
const flowTestAI = new FlowtestAI();
@@ -87,7 +88,7 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
8788
id: id,
8889
name: collectionName,
8990
pathname: pathname,
90-
openapi_spec: resolvedSpec.resolved,
91+
openapi_spec: stringify(resolvedSpec.resolved),
9192
nodes: parsedNodes,
9293
};
9394

@@ -122,7 +123,7 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
122123
id: id,
123124
name: collectionName,
124125
pathname: collectionFolderPath,
125-
openapi_spec: resolvedSpec.resolved,
126+
openapi_spec: stringify(resolvedSpec.resolved),
126127
nodes: parsedNodes,
127128
};
128129

@@ -309,7 +310,7 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
309310
try {
310311
const collection = collectionStore.getAll().find((c) => c.id === collectionId);
311312
if (collection) {
312-
return await flowTestAI.generate(collection.openapi_spec, instruction, model);
313+
return await flowTestAI.generate(parse(collection.openapi_spec), instruction, model);
313314
} else {
314315
return Promise.reject(new Error('Collection not found'));
315316
}

packages/flowtest-electron/src/utils/collection.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const parseOpenAPISpec = (collection) => {
2525
Object.entries(operation).map(([requestType, request], _) => {
2626
const summary = request['summary'];
2727
const operationId = request['operationId'];
28+
const tags = request['tags'];
2829
var url = replaceSingleToDoubleCurlyBraces(computeUrl(baseUrl, path));
2930
var variables = {};
3031

@@ -59,6 +60,7 @@ const parseOpenAPISpec = (collection) => {
5960
description: summary,
6061
operationId: operationId,
6162
requestType: requestType.toUpperCase(),
63+
tags: tags,
6264
};
6365
// console.log(finalNode);
6466
parsedNodes.push(finalNode);

src/components/molecules/flow/AddNodes.js

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Fragment } from 'react';
66
import { Disclosure } from '@headlessui/react';
77
import { ChevronUpIcon } from '@heroicons/react/20/solid';
88
import useCollectionStore from 'stores/CollectionStore';
9+
import { orderNodesByTags } from './utils';
910

1011
// ToDo: Move these constants to constants file/folder
1112
const requestNodes = [
@@ -63,6 +64,7 @@ const AddNodes = ({ collectionId }) => {
6364

6465
// Get all requests of this collections
6566
const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);
67+
const nodesByTags = orderNodesByTags(collection.nodes);
6668

6769
return (
6870
<>
@@ -129,23 +131,41 @@ const AddNodes = ({ collectionId }) => {
129131
</Disclosure.Button>
130132
<Disclosure.Panel className='px-4 pt-4 pb-2 text-sm border-l border-r'>
131133
<div>
132-
{collection.nodes.map((node) => (
133-
<div
134-
key={`${node.requestType} - ${node.operationId}`}
135-
onDragStart={(event) => {
136-
const newNode = {
137-
...node,
138-
type: 'requestNode',
139-
};
140-
onDragStart(event, newNode);
141-
}}
142-
draggable
143-
cursor='move'
144-
className='py-2 border-b'
145-
>
146-
<div className='text-base font-semibold primary-text'>{`${node.requestType} - ${node.operationId}`}</div>
147-
<div className='text-xs secondary-text'>{node.description}</div>
148-
</div>
134+
{Object.entries(nodesByTags).map(([tag, nodes], index) => (
135+
<Disclosure as='div' key={index}>
136+
{({ open }) => (
137+
<>
138+
<Disclosure.Button className='flex justify-between w-full px-4 py-2 text-lg font-medium text-left border-t border-b bg-gray-50 hover:bg-gray-100 focus:outline-none focus-visible:ring'>
139+
<span>{tag}</span>
140+
<ChevronUpIcon
141+
className={`${open ? 'rotate-180 transform' : ''} h-5 w-5 `}
142+
/>
143+
</Disclosure.Button>
144+
<Disclosure.Panel className='px-4 pt-4 pb-2 text-sm border-l border-r'>
145+
<div>
146+
{nodes.map((node) => (
147+
<div
148+
key={`${node.requestType} - ${node.operationId}`}
149+
onDragStart={(event) => {
150+
const newNode = {
151+
...node,
152+
type: 'requestNode',
153+
};
154+
onDragStart(event, newNode);
155+
}}
156+
draggable
157+
cursor='move'
158+
className='py-2 border-b'
159+
>
160+
<div className='text-base font-semibold primary-text'>{`${node.requestType} - ${node.operationId}`}</div>
161+
<div className='text-xs secondary-text'>{node.description}</div>
162+
</div>
163+
))}
164+
</div>
165+
</Disclosure.Panel>
166+
</>
167+
)}
168+
</Disclosure>
149169
))}
150170
</div>
151171
</Disclosure.Panel>

src/components/molecules/flow/flowtestai.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const translateGeneratedNodesToOpenApiNodes = (generatedNodes, openApiNodes) =>
2727
type: 'requestNode',
2828
});
2929
} else {
30-
throw Error(`Cannot find node: ${node.name} in openApi spec`);
30+
console.log(`Cannot find node: ${gnode.name} in openApi spec`);
3131
}
3232
});
3333

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ const RequestBody = ({ nodeId, nodeData }) => {
4848
};
4949

5050
const handleClose = (option) => {
51-
if (option == 'raw-json') {
51+
if (option == 'None') {
52+
setRequestNodeBody(nodeId, option, undefined);
53+
} else if (option == 'raw-json') {
5254
setRequestNodeBody(nodeId, option, '');
5355
} else if (option == 'form-data') {
5456
setRequestNodeBody(nodeId, option, {
@@ -61,12 +63,12 @@ const RequestBody = ({ nodeId, nodeData }) => {
6163

6264
return (
6365
<>
64-
<div className='border-t border-neutral-300 bg-slate-100 px-2 py-4'>
66+
<div className='px-2 py-4 border-t border-neutral-300 bg-slate-100'>
6567
<div className='flex items-center justify-between font-medium'>
6668
<h3>Body</h3>
6769
<Menu as='div' className='relative inline-block text-left'>
6870
<Menu.Button data-click-from='body-type-menu' className='p-2'>
69-
<EllipsisVerticalIcon className='h-4 w-4' aria-hidden='true' data-click-from='body-type-menu' />
71+
<EllipsisVerticalIcon className='w-4 h-4' aria-hidden='true' data-click-from='body-type-menu' />
7072
</Menu.Button>
7173
<Transition
7274
as={Fragment}
@@ -78,13 +80,13 @@ const RequestBody = ({ nodeId, nodeData }) => {
7880
leaveTo='transform opacity-0 scale-95'
7981
>
8082
<Menu.Items
81-
className='absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white px-1 py-1 shadow-lg ring-1 ring-black/5 focus:outline-none'
83+
className='absolute right-0 z-10 w-56 px-1 py-1 mt-2 origin-top-right bg-white divide-y divide-gray-100 rounded-md shadow-lg ring-1 ring-black/5 focus:outline-none'
8284
data-click-from='body-type-menu'
8385
>
8486
{requestBodyTypeOptions.map((bodyTypeOption, index) => (
8587
<Menu.Item key={index} data-click-from='body-type-menu' onClick={() => handleClose(bodyTypeOption)}>
8688
<button
87-
className='group flex w-full items-center rounded-md px-2 py-2 text-sm text-gray-900 hover:bg-slate-100'
89+
className='flex items-center w-full px-2 py-2 text-sm text-gray-900 rounded-md group hover:bg-slate-100'
8890
data-click-from='body-type-menu'
8991
>
9092
{bodyTypeOption}
@@ -97,10 +99,10 @@ const RequestBody = ({ nodeId, nodeData }) => {
9799
</div>
98100
</div>
99101
{nodeData.requestBody && nodeData.requestBody.type === 'raw-json' && (
100-
<div className='border border-t border-neutral-300 bg-slate-50 p-2'>
102+
<div className='p-2 border border-t border-neutral-300 bg-slate-50'>
101103
<textarea
102104
placeholder='Enter json'
103-
className='nodrag nowheel w-full p-2'
105+
className='w-full p-2 nodrag nowheel'
104106
name='username'
105107
onChange={(e) => handleRawJson(e)}
106108
rows={4}
@@ -109,25 +111,25 @@ const RequestBody = ({ nodeId, nodeData }) => {
109111
</div>
110112
)}
111113
{nodeData.requestBody && nodeData.requestBody.type === 'form-data' && (
112-
<div className='border-t border-neutral-300 bg-slate-50 p-2'>
113-
<div className='flex items-center justify-between rounded-md border border-neutral-500 text-sm text-neutral-500 outline-0 focus:ring-0'>
114+
<div className='p-2 border-t border-neutral-300 bg-slate-50'>
115+
<div className='flex items-center justify-between text-sm border rounded-md border-neutral-500 text-neutral-500 outline-0 focus:ring-0'>
114116
<input
115117
placeholder='key'
116-
className='nodrag nowheel bg-slate-50 pl-4'
118+
className='pl-4 nodrag nowheel bg-slate-50'
117119
name='variable-value'
118120
onChange={(e) => handleFormDataKey(e)}
119121
value={nodeData.requestBody.body.key}
120122
/>
121-
<div className='rounded-br-md rounded-tr-md border-l border-l-neutral-500 px-4 py-2'>File</div>
123+
<div className='px-4 py-2 border-l rounded-br-md rounded-tr-md border-l-neutral-500'>File</div>
122124
</div>
123125
<div className='py-2'>
124126
<button
125-
className='flex w-full cursor-pointer items-center justify-center gap-2 rounded-md border border-neutral-500 bg-slate-100 p-2 hover:bg-slate-200'
127+
className='flex items-center justify-center w-full gap-2 p-2 border rounded-md cursor-pointer border-neutral-500 bg-slate-100 hover:bg-slate-200'
126128
onClick={() => {
127129
uploadFileForRequestNode.current.click();
128130
}}
129131
>
130-
<DocumentArrowUpIcon className='h-4 w-4 text-center' />
132+
<DocumentArrowUpIcon className='w-4 h-4 text-center' />
131133
Upload File
132134
{/* Ref: https://stackoverflow.com/questions/37457128/react-open-file-browser-on-click-a-div */}
133135
<div className='hidden'>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export const orderNodesByTags = (nodes) => {
2+
const result = {};
3+
if (nodes) {
4+
nodes.map((node) => {
5+
node.tags.map((tag) => {
6+
if (!result[tag]) {
7+
result[tag] = [];
8+
}
9+
result[tag].push(node);
10+
});
11+
});
12+
}
13+
return result;
14+
};

src/components/molecules/headers/TabPanelHeader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const TabPanelHeader = () => {
9494
</div>
9595
</>
9696
) : (
97-
<div className='text-base tracking-[0.15em]'> Please select a flow test from the sidebar </div>
97+
<div className='text-base tracking-[0.15em]'> Please select a flow from the sidebar </div>
9898
)}
9999
</div>
100100
);

src/components/molecules/modals/GenerateFlowTestModal.js

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { generateFlowData } from '../flow/flowtestai';
99
import { init } from '../flow';
1010
import useCanvasStore from 'stores/CanvasStore';
1111
import { toast } from 'react-toastify';
12+
import { isEqual } from 'lodash';
1213

1314
const GenerateFlowTestModal = ({ closeFn = () => null, open = false, collectionId }) => {
1415
const setNodes = useCanvasStore((state) => state.setNodes);
@@ -110,19 +111,28 @@ const GenerateFlowTestModal = ({ closeFn = () => null, open = false, collectionI
110111
isDisabled={false}
111112
fullWidth={true}
112113
onClickHandle={() => {
113-
generateFlowData(textareaValue, selectedModel, collectionId)
114-
.then((flowData) => {
115-
const result = init(flowData);
116-
console.log(result);
117-
setNodes(result.nodes);
118-
setEdges(result.edges);
119-
closeFn();
120-
})
121-
.catch((error) => {
122-
console.log(error);
123-
toast.error(`Error while generating flow data`);
124-
closeFn();
125-
});
114+
if (textareaValue.trim() === '') {
115+
toast.info('Please describe your flow');
116+
} else if (selectedModel === null) {
117+
toast.info('Please select a model');
118+
} else {
119+
generateFlowData(textareaValue, selectedModel, collectionId)
120+
.then((flowData) => {
121+
if (isEqual(flowData.nodes, [])) {
122+
toast.info(`${selectedModel} was not able to evaluate the instructions properly`);
123+
} else {
124+
const result = init(flowData);
125+
setNodes(result.nodes);
126+
setEdges(result.edges);
127+
}
128+
closeFn();
129+
})
130+
.catch((error) => {
131+
console.log(error);
132+
toast.error(`Error while generating flow data`);
133+
closeFn();
134+
});
135+
}
126136
}}
127137
>
128138
Generate

src/stores/CanvasStore.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,22 @@ const useCanvasStore = create((set, get) => ({
163163
nodes: get().nodes.map((node) => {
164164
if (node.id === nodeId) {
165165
// it's important to create a new object here, to inform React Flow about the cahnges
166-
if (type == 'raw-json') {
166+
if (type === 'None') {
167+
node.data = {
168+
...node.data,
169+
requestBody: {
170+
type,
171+
},
172+
};
173+
} else if (type === 'raw-json') {
167174
node.data = {
168175
...node.data,
169176
requestBody: {
170177
type,
171178
body: data,
172179
},
173180
};
174-
} else if (type == 'form-data') {
181+
} else if (type === 'form-data') {
175182
node.data = {
176183
...node.data,
177184
requestBody: {

0 commit comments

Comments
 (0)