diff --git a/.changeset/honest-pandas-appear.md b/.changeset/honest-pandas-appear.md new file mode 100644 index 0000000..f04e2b2 --- /dev/null +++ b/.changeset/honest-pandas-appear.md @@ -0,0 +1,5 @@ +--- +'flowtestai': minor +--- + +beautify logs sidesheet and ability to upload flow scans diff --git a/package.json b/package.json index 6e84dbe..8e20df3 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@emotion/styled": "^11.11.0", "@headlessui/react": "^1.7.18", "@heroicons/react": "^2.1.1", + "@hookform/resolvers": "^3.7.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -53,6 +54,7 @@ "autoprefixer": "^10.4.18", "axios": "^1.5.1", "codemirror": "^6.0.1", + "date-fns": "^3.6.0", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-import": "^2.29.1", "immer": "^10.0.4", @@ -65,6 +67,7 @@ "react-custom-scrollbars": "^4.2.1", "react-dom": "^18.2.0", "react-edit-text": "^5.1.1", + "react-hook-form": "^7.52.0", "react-icons": "^5.0.1", "react-json-view-lite": "^1.4.0", "react-perfect-scrollbar": "^1.5.8", @@ -79,10 +82,12 @@ "tailwindcss": "^3.4.1", "typescript": "4", "web-vitals": "^2.1.4", + "zod": "^3.23.8", "zustand": "^4.5.2" }, "devDependencies": { "@changesets/cli": "^2.27.1", + "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.10", "@types/node": "20.11.5", "daisyui": "^4.7.2", diff --git a/packages/flowtest-cli/graph/compute/requestnode.js b/packages/flowtest-cli/graph/compute/requestnode.js index 1d5a461..9c4e50c 100644 --- a/packages/flowtest-cli/graph/compute/requestnode.js +++ b/packages/flowtest-cli/graph/compute/requestnode.js @@ -48,6 +48,11 @@ class requestNode extends Node { const res = await this.runHttpRequest(options); + if (this.nodeData.requestBody.type === 'form-data') { + // we don't want to send full file value + options.data.value = ''; + } + if (res.error) { console.log(chalk.red(` ✕ `) + chalk.dim(`Request failed: ${JSON.stringify(res.error)}`)); this.logger.add(LogLevel.ERROR, 'HTTP request failed', { diff --git a/packages/flowtest-electron/src/ipc/collection.js b/packages/flowtest-electron/src/ipc/collection.js index bebd5b7..8e68af5 100644 --- a/packages/flowtest-electron/src/ipc/collection.js +++ b/packages/flowtest-electron/src/ipc/collection.js @@ -340,7 +340,7 @@ const registerRendererEventHandlers = (mainWindow, watcher) => { } }); - ipcMain.handle('renderer:upload-logs', async (event, name, config, logs) => { + ipcMain.handle('renderer:upload-logs', async (event, name, config, status, time, logs) => { function bytesToBase64(bytes) { const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join(''); return btoa(binString); @@ -348,15 +348,16 @@ const registerRendererEventHandlers = (mainWindow, watcher) => { try { const data = { - version: 1, - name, - scan: logs, + scan_metadata: { + version: 1, + name, + status, + time, + }, + scan: bytesToBase64(new TextEncoder().encode(JSON.stringify(logs))), }; try { - const response = await axiosClient(config.hostUrl, config.accessId, config.accessKey).post( - '/upload', - bytesToBase64(new TextEncoder().encode(JSON.stringify(data))), - ); + const response = await axiosClient(config.hostUrl, config.accessId, config.accessKey).post('/upload', data); return { upload: 'success', url: `${config.hostUrl}/scan/${response.data.data[0].id}`, diff --git a/packages/flowtest-electron/src/ipc/settings.js b/packages/flowtest-electron/src/ipc/settings.js index 8721071..23e652e 100644 --- a/packages/flowtest-electron/src/ipc/settings.js +++ b/packages/flowtest-electron/src/ipc/settings.js @@ -12,7 +12,7 @@ const registerSettingsEventHandlers = (mainWindow) => { ipcMain.handle('renderer:add-logsyncconfig', async (event, config) => { try { - settingsStore.addLogSyncConfig(...config); + settingsStore.addLogSyncConfig(config.enabled, config.hostUrl, config.accessId, config.accessKey); const savedSettings = settingsStore.getAll(); mainWindow.webContents.send('main:saved-settings', savedSettings); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca812fc..e04f2ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: '@heroicons/react': specifier: ^2.1.1 version: 2.1.3(react@18.3.1) + '@hookform/resolvers': + specifier: ^3.7.0 + version: 3.7.0(react-hook-form@7.52.0(react@18.3.1)) '@testing-library/jest-dom': specifier: ^5.17.0 version: 5.17.0 @@ -59,6 +62,9 @@ importers: codemirror: specifier: ^6.0.1 version: 6.0.1(@lezer/common@1.2.1) + date-fns: + specifier: ^3.6.0 + version: 3.6.0 eslint-import-resolver-alias: specifier: ^1.1.2 version: 1.1.2(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)) @@ -95,6 +101,9 @@ importers: react-edit-text: specifier: ^5.1.1 version: 5.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-hook-form: + specifier: ^7.52.0 + version: 7.52.0(react@18.3.1) react-icons: specifier: ^5.0.1 version: 5.2.1(react@18.3.1) @@ -137,6 +146,9 @@ importers: web-vitals: specifier: ^2.1.4 version: 2.1.4 + zod: + specifier: ^3.23.8 + version: 3.23.8 zustand: specifier: ^4.5.2 version: 4.5.2(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1) @@ -144,6 +156,9 @@ importers: '@changesets/cli': specifier: ^2.27.1 version: 2.27.5 + '@tailwindcss/forms': + specifier: ^0.5.7 + version: 0.5.7(tailwindcss@3.4.3) '@tailwindcss/typography': specifier: ^0.5.10 version: 0.5.13(tailwindcss@3.4.3) @@ -1493,6 +1508,11 @@ packages: peerDependencies: react: '>= 16' + '@hookform/resolvers@3.7.0': + resolution: {integrity: sha512-42p5X18noBV3xqOpTlf2V5qJZwzNgO4eLzHzmKGh/w7z4+4XqRw5AsESVkqE+qwAuRRlg2QG12EVEjPkrRIbeg==} + peerDependencies: + react-hook-form: ^7.0.0 + '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -2401,6 +2421,11 @@ packages: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} + '@tailwindcss/forms@0.5.7': + resolution: {integrity: sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==} + peerDependencies: + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' + '@tailwindcss/typography@0.5.13': resolution: {integrity: sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==} peerDependencies: @@ -3900,6 +3925,9 @@ packages: resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} engines: {node: '>= 0.4'} + date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + debounce-fn@4.0.0: resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} engines: {node: '>=10'} @@ -6155,6 +6183,10 @@ packages: peerDependencies: webpack: ^5.0.0 + mini-svg-data-uri@1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true + minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -7164,6 +7196,10 @@ packages: q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qs@6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} @@ -7241,6 +7277,12 @@ packages: react-error-overlay@6.0.11: resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==} + react-hook-form@7.52.0: + resolution: {integrity: sha512-mJX506Xc6mirzLsmXUJyqlAI3Kj9Ph2RhplYhUVffeOQSnubK2uVqBFOBJmvKikvbFV91pxVXmDiR+QMF19x6A==} + engines: {node: '>=12.22.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + react-icons@5.2.1: resolution: {integrity: sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==} peerDependencies: @@ -10636,6 +10678,10 @@ snapshots: dependencies: react: 18.3.1 + '@hookform/resolvers@3.7.0(react-hook-form@7.52.0(react@18.3.1))': + dependencies: + react-hook-form: 7.52.0(react@18.3.1) + '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -11690,6 +11736,11 @@ snapshots: dependencies: defer-to-connect: 2.0.1 + '@tailwindcss/forms@0.5.7(tailwindcss@3.4.3)': + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 3.4.3 + '@tailwindcss/typography@0.5.13(tailwindcss@3.4.3)': dependencies: lodash.castarray: 4.4.0 @@ -13521,6 +13572,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.1 + date-fns@3.6.0: {} + debounce-fn@4.0.0: dependencies: mimic-fn: 3.1.0 @@ -16356,6 +16409,8 @@ snapshots: tapable: 2.2.1 webpack: 5.91.0 + mini-svg-data-uri@1.4.4: {} + minimalistic-assert@1.0.1: {} minimatch@3.1.2: @@ -17402,6 +17457,10 @@ snapshots: react-error-overlay@6.0.11: {} + react-hook-form@7.52.0(react@18.3.1): + dependencies: + react: 18.3.1 + react-icons@5.2.1(react@18.3.1): dependencies: react: 18.3.1 diff --git a/src/components/molecules/flow/graph/compute/requestnode.js b/src/components/molecules/flow/graph/compute/requestnode.js index 528b7c0..e0c0828 100644 --- a/src/components/molecules/flow/graph/compute/requestnode.js +++ b/src/components/molecules/flow/graph/compute/requestnode.js @@ -32,6 +32,10 @@ class requestNode extends Node { const res = await this.runHttpRequest(options); + if (this.nodeData.requestBody.type === 'form-data') { + options.data.value = ''; + } + if (res.error) { this.logger.add(LogLevel.ERROR, 'HTTP request failed', { type: 'requestNode', diff --git a/src/components/molecules/flow/index.js b/src/components/molecules/flow/index.js index 8ed8fee..a9c081a 100644 --- a/src/components/molecules/flow/index.js +++ b/src/components/molecules/flow/index.js @@ -194,8 +194,8 @@ const Flow = ({ tab, collectionId }) => { return true; }; - const onGraphComplete = async (status, logs) => { - const response = await uploadGraphRunLogs(tab.name, logs); + const onGraphComplete = async (status, time, logs) => { + const response = await uploadGraphRunLogs(tab.name, status, time, logs); console.log(response); setLogs(tab.id, logs, response); if (status == 'Success') { @@ -243,7 +243,7 @@ const Flow = ({ tab, collectionId }) => { > setViewport(reactFlowInstance.getViewport())} > + +
@@ -62,6 +76,7 @@ const MainFooter = () => {
+ setOpenSettingsModal(false)} open={openSettingsModal} /> ); }; diff --git a/src/components/molecules/headers/TabPanelHeader.js b/src/components/molecules/headers/TabPanelHeader.js index 128e494..5864f87 100644 --- a/src/components/molecules/headers/TabPanelHeader.js +++ b/src/components/molecules/headers/TabPanelHeader.js @@ -13,12 +13,13 @@ import 'react-sliding-pane/dist/react-sliding-pane.css'; import TimeoutSelector from 'components/atoms/common/TimeoutSelector'; import { timeoutForGraphRun } from 'components/molecules/flow/utils'; import HorizontalDivider from 'components/atoms/common/HorizontalDivider'; -import { JsonView, allExpanded, collapseAllNested, darkStyles, defaultStyles } from 'react-json-view-lite'; +// import { JsonView, allExpanded, collapseAllNested, darkStyles, defaultStyles } from 'react-json-view-lite'; import 'react-json-view-lite/dist/index.css'; -import { LogLevel } from '../flow/graph/GraphLogger'; -import { ShieldCheckIcon, BarsArrowUpIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline'; +// import { LogLevel } from '../flow/graph/GraphLogger'; + import GenAIUsageDisclaimer from '../modals/GenAIUsageDisclaimer'; import useSettingsStore from 'stores/SettingsStore'; +import FlowLogs from '../sideSheets/FlowLogs'; const TabPanelHeader = () => { const focusTabId = useTabStore((state) => state.focusTabId); @@ -37,114 +38,6 @@ const TabPanelHeader = () => { const [genAiUsageDisclaimerModalOpen, setGenAiUsageDisclaimerModalOpen] = useState(false); const [generateFlowTestModalOpen, setGenerateFlowTestModalOpen] = useState(false); - const renderFlowScan = (flowScan) => { - if (flowScan.upload === 'disabled') { - return ( -
- - - - {'Activate Flow Scan'} -
- ); - } else if (flowScan.upload === 'success') { - return ( -
- - {flowScan.url} -
- ); - } else if (flowScan.upload === 'fail') { - return ( -
- - {flowScan.message} - {flowScan?.reason} -
- ); - } - }; - - const renderLog = (log) => { - if (log.logLevel === LogLevel.INFO) { - let message = ''; - let json = undefined; - if (log.message.trim() != '') { - message = log.message; - } - - if (log.node != undefined) { - const type = log.node.type; - const data = log.node.data; - if (type === 'outputNode') { - json = { - output: data.output, - }; - } - - if (type === 'authNode') { - message = `${data.authType}`; - } - - if (type === 'assertNode') { - message = `Assert : ${data.var1} of type ${typeof data.var1} ${data.operator} ${data.var2} of type ${typeof data.var2} = ${data.result}`; - } - - if (type === 'delayNode') { - message = `Waiting for ${data.delay} ms`; - } - - if (type === 'setVarNode') { - message = `Setting Variable: ${data.name} = ${data.value}`; - } - - if (type === 'requestNode') { - message = `${data.request.type.toUpperCase()} ${data.request.url}`; - json = data; - } - } - - return ( -
-
-
-

{log.timestamp}

-
-
-

: {message}

-
-
-
- {json != undefined ? ( - - - - ) : ( - <> - )} -
-
- ); - } else { - return ( -
-

- {log.timestamp} : {log.message} -

-
- {log.node != undefined ? ( - - - - ) : ( - <> - )} -
-
- ); - } - }; - return ( <> {focusTab ? ( @@ -152,7 +45,7 @@ const TabPanelHeader = () => {
{focusTab.name}
-
+
{focusTab.type === OBJ_TYPES.flowtest && ( // ToDo: Check this
@@ -165,7 +58,7 @@ const TabPanelHeader = () => {
)} -
+
{focusTab.type === OBJ_TYPES.flowtest && focusTab.run.logs && focusTab.run.logs.length != 0 ? ( @@ -186,7 +79,7 @@ const TabPanelHeader = () => { > @@ -206,17 +99,7 @@ const TabPanelHeader = () => { }); }} > - -
    -
  • {renderFlowScan(focusTab.run.scan)}
  • - {focusTab.run.logs.map((item, index) => ( -
  • {renderLog(item)}
  • - ))} -
+
) : ( @@ -237,7 +120,7 @@ const TabPanelHeader = () => { fullWidth={true} className='flex items-center justify-between gap-x-4' > - + Generate null, open = false }) => { const [selectedFilePath, setSelectedFilePath] = useState(''); diff --git a/src/components/molecules/modals/SettingsModal.js b/src/components/molecules/modals/SettingsModal.js new file mode 100644 index 0000000..f153732 --- /dev/null +++ b/src/components/molecules/modals/SettingsModal.js @@ -0,0 +1,214 @@ +import React, { Fragment, useState, useEffect } from 'react'; +import { Dialog, Transition, Tab } from '@headlessui/react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { XCircleIcon } from '@heroicons/react/20/solid'; +import Button from 'components/atoms/common/Button'; +import { BUTTON_INTENT_TYPES, BUTTON_TYPES } from 'constants/Common'; +import { addLogSyncConfig } from 'service/settings'; +import useSettingsStore from 'stores/SettingsStore'; + +const schema = z.object({ + enabled: z.boolean(), + accessId: z.string(), + accessKey: z.string(), +}); + +const SettingsModal = ({ closeFn = () => null, open = false, initialTab = 0 }) => { + const [successFullSubmissionMessage, showSuccessFullSubmissionMessage] = useState(false); + const config = useSettingsStore((state) => state.logSyncConfig); + + const { + register, + handleSubmit, + setValue, + setError, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + enabled: false, + accessId: '', + accessKey: '', + }, + resolver: zodResolver(schema), + }); + + useEffect(() => { + setValue('enabled', config?.enabled || false); + setValue('accessId', config?.accessId || ''); + setValue('accessKey', config?.accessKey || ''); + }, [config]); + + const onFormSubmit = async (data) => { + try { + await addLogSyncConfig(data.enabled, 'http://localhost:3000', data.accessId, data.accessKey); + // send the form data as a request + showSuccessFullSubmissionMessage(true); + closeFn(); + } catch (error) { + // To show error message from the request handler + setError('root', { message: error }); + } + }; + + return ( + <> + + { + closeFn(); + }} + > + +
+ + +
+
+ + + +

Settings

+ +
+ + + + `w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-blue-700 + ${selected ? 'bg-white shadow' : 'text-blue-100 hover:bg-white/[0.12] hover:text-white'}` + } + > + Scans + + + `w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-blue-700 + ${selected ? 'bg-white shadow' : 'text-blue-100 hover:bg-white/[0.12] hover:text-white'}` + } + > + Theme + + + + + {/* Scans Content */} +
+
+
+

+ Scans aim to provide anayltics and observability for your flows.
+ + Get Access Keys + +

+
+
+ + + {errors.enabled &&
{errors.enabled.message}
} +
+
+ + + {errors.accessId &&
{errors.accessId.message}
} +
+
+ + + {errors.accessKey &&
{errors.accessKey.message}
} +
+
+ {errors.root &&
{errors.root.message}
}{' '} + {successFullSubmissionMessage && ( +
Successfully saved settings
+ )} +
+
+
+ +
+
+
+ + {/* Theme Content */} +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+ + ); +}; + +export default SettingsModal; diff --git a/src/components/molecules/modals/sidebar/NewEnvironmentFileModal.js b/src/components/molecules/modals/sidebar/NewEnvironmentFileModal.js index 671306a..5866efe 100644 --- a/src/components/molecules/modals/sidebar/NewEnvironmentFileModal.js +++ b/src/components/molecules/modals/sidebar/NewEnvironmentFileModal.js @@ -1,18 +1,15 @@ import React, { Fragment, useState } from 'react'; -import { PropTypes } from 'prop-types'; import { Dialog, Transition, Listbox } from '@headlessui/react'; -import { createFolder, createFlowTest, createEnvironmentFile } from 'service/collection'; -import { DirectoryOptionsActions } from 'constants/WorkspaceDirectory'; +import { createEnvironmentFile } from 'service/collection'; import { toast } from 'react-toastify'; import Button from 'components/atoms/common/Button'; import { BUTTON_INTENT_TYPES, BUTTON_TYPES, OBJ_TYPES } from 'constants/Common'; import TextInput from 'components/atoms/common/TextInput'; -import Collection from '../../sidebar/content/Collection'; import useCollectionStore from 'stores/CollectionStore'; import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid'; import { isEmpty } from 'lodash'; -import TimeoutSelector from 'components/atoms/common/TimeoutSelector'; -import { timeoutForGraphRun } from 'components/molecules/flow/utils'; +import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'; +import { ChevronDownIcon } from '@heroicons/react/20/solid'; const NewEnvironmentFileModal = ({ closeFn = () => null, open = false }) => { const collections = useCollectionStore.getState().collections; @@ -84,7 +81,7 @@ const NewEnvironmentFileModal = ({ closeFn = () => null, open = false }) => { )}
-
+
{ diff --git a/src/components/molecules/sideSheets/FlowLogs.js b/src/components/molecules/sideSheets/FlowLogs.js new file mode 100644 index 0000000..f2a0dae --- /dev/null +++ b/src/components/molecules/sideSheets/FlowLogs.js @@ -0,0 +1,198 @@ +import React, { useState } from 'react'; +import { ShieldCheckIcon, BarsArrowUpIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline'; +import { JsonView, collapseAllNested, defaultStyles } from 'react-json-view-lite'; +import { LogLevel } from '../flow/graph/GraphLogger'; +import { ClockIcon } from '@heroicons/react/20/solid'; +import Button from 'components/atoms/common/Button'; +import { BUTTON_INTENT_TYPES, BUTTON_TYPES } from 'constants/Common'; +import HorizontalDivider from 'components/atoms/common/HorizontalDivider'; +import SettingsModal from '../modals/SettingsModal'; +import { formatTimeStamp } from 'utils/common'; + +const FlowLogs = ({ logsData }) => { + const [openSettingsModal, setOpenSettingsModal] = useState(false); + + const renderFlowScan = (flowScan) => { + if (flowScan.upload === 'disabled') { + return ( +
+
+
+ +
+
+

{flowScan?.message}

+
+
+
+ + setOpenSettingsModal(false)} open={openSettingsModal} initialTab={0} /> +
+
+ ); + } else if (flowScan.upload === 'success') { + return ( +
+
+ +
+
+

Successfully published the scan

+
+ +

+ URL: + + {flowScan.url} + +

+
+
+
+ ); + } else if (flowScan.upload === 'fail') { + return ( +
+
+ +
+
+
+

{flowScan.message}

+
+ {flowScan?.reason ? ( + <> + +

{flowScan?.reason}

+ + ) : ( + <> + )} +
+
+ ); + } + }; + + return ( +
+
{renderFlowScan(logsData.run.scan)}
+
+

Logs

+
+ {logsData.run.logs.map((log, index) => { + if (log.logLevel === LogLevel.INFO) { + let message = ''; + let json = undefined; + if (log.message.trim() != '') { + message = log.message; + } + + if (log.node != undefined) { + const type = log.node.type; + const data = log.node.data; + if (type === 'outputNode') { + json = { + output: data.output, + }; + } + + if (type === 'authNode') { + message = `${data.authType}`; + } + + if (type === 'assertNode') { + message = `Assert : ${data.var1} of type ${typeof data.var1} ${data.operator} ${data.var2} of type ${typeof data.var2} = ${data.result}`; + } + + if (type === 'delayNode') { + message = `Waiting for ${data.delay} ms`; + } + + if (type === 'setVarNode') { + message = `Setting Variable: ${data.name} = ${data.value}`; + } + + if (type === 'requestNode') { + message = `${data.request.type.toUpperCase()} ${data.request.url}`; + json = data; + } + } + + return ( + <> +
    +
  • +
    +
    + + {`${log.timestamp} : ${message}`} +
    +
    +
      +
    • + {json != undefined ? ( +
      + + + +
      + ) : ( + <> + )} +
    • +
    +
  • +
+ + ); + } else { + return ( + <> +
    +
  • +
    +
    + + {`${log.timestamp} : ${log.message}`} +
    +
    +
      +
    • + {log.node != undefined ? ( +
      + + + +
      + ) : ( + <> + )} +
    • +
    +
  • +
+ + ); + } + })} +
+
+
+ ); +}; + +export default FlowLogs; diff --git a/src/index.css b/src/index.css index 204a590..be6f4c8 100644 --- a/src/index.css +++ b/src/index.css @@ -67,7 +67,7 @@ details summary::-webkit-details-marker { text-overflow: ellipsis; } .side-sheet-overlay .slide-pane__content { - padding: 2.5rem 2rem; + padding: 1rem 2rem; } /* React flow related CSS */ @@ -137,3 +137,12 @@ details summary::-webkit-details-marker { .cm-scroller { overflow: auto; } */ + +/* Overriding style for react-json-view-lite */ +.json-view-container > div { + background: transparent !important; +} + +.flow-logs-menu { + margin-inline-start: 1.25rem; +} diff --git a/src/ipc/collection.js b/src/ipc/collection.js index 43a13fe..c998df0 100644 --- a/src/ipc/collection.js +++ b/src/ipc/collection.js @@ -18,6 +18,7 @@ const registerMainEventHandlers = () => { const { ipcRenderer } = window; ipcRenderer.on('main:collection-created', (id, name, pathname, nodes) => { + console.log(`\n \n main:collection-created :: _createCollection \n \n `); _createCollection(id, name, pathname, nodes); }); diff --git a/src/service/collection.js b/src/service/collection.js index 1262ee9..ed93651 100644 --- a/src/service/collection.js +++ b/src/service/collection.js @@ -403,16 +403,17 @@ export const deleteFlowTest = (pathname, collectionId) => { } }; -export const uploadGraphRunLogs = async (name, logs) => { +export const uploadGraphRunLogs = async (name, status, time, logs) => { try { const { ipcRenderer } = window; const logSyncConfig = useSettingsStore.getState().logSyncConfig; + if (logSyncConfig.enabled) { - return await ipcRenderer.invoke('renderer:upload-logs', name, logSyncConfig, logs); + return await ipcRenderer.invoke('renderer:upload-logs', name, logSyncConfig, status, time, logs); } else { return { upload: 'disabled', - message: 'Enable flow scans today to get more value out of your APIs', + message: 'Enable flow scans to get more value out of your APIs', }; } } catch (error) { diff --git a/src/stores/CanvasStore.js b/src/stores/CanvasStore.js index f6ab81b..1fac759 100644 --- a/src/stores/CanvasStore.js +++ b/src/stores/CanvasStore.js @@ -58,10 +58,14 @@ const useCanvasStore = create((set, get) => ({ nodes: get().nodes.map((node) => { if (node.id === nodeId) { // it's important to create a new object here, to inform React Flow about the cahnges - node.data = { - ...node.data, - type: authType, - }; + if (authType == 'no-auth') { + node.data = {}; + } else { + node.data = { + ...node.data, + type: authType, + }; + } } return node; diff --git a/src/utils/common.js b/src/utils/common.js index de17f0c..4e91650 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -45,3 +45,8 @@ export const isEmptyObj = (obj) => { } return JSON.stringify(obj) === JSON.stringify({}); }; + +export const formatTimeStamp = (timeStamp) => { + let formattedDate = new Date(timeStamp); + return formattedDate.toGMTString(); +}; diff --git a/tailwind.config.js b/tailwind.config.js index 0b5c8fa..eaa690a 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -70,7 +70,7 @@ module.exports = { montserrat: ['Montserrat', 'sans-serif'], }, }, - plugins: [require('daisyui')], + plugins: [require('daisyui'), require('@tailwindcss/forms')], daisyui: { // themes: false, themes: [],