From d7cd86e31a110ae6ce94ea5a830a9ecff9db2a9f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 20 Nov 2024 17:58:28 +0530 Subject: [PATCH 01/37] feat: add capture list manual and auto options buttons --- src/components/organisms/RightSidePanel.tsx | 45 +++++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index a11989bd8..00682be2c 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -54,6 +54,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const [showCaptureScreenshot, setShowCaptureScreenshot] = useState(true); const [showCaptureText, setShowCaptureText] = useState(true); const [hoverStates, setHoverStates] = useState<{ [id: string]: boolean }>({}); + const [showCaptureListOptions, setShowCaptureListOptions] = useState(false); const { lastAction, notify, currentWorkflowActionsState, setCurrentWorkflowActionsState } = useGlobalInfoStore(); const { getText, startGetText, stopGetText, getScreenshot, startGetScreenshot, stopGetScreenshot, getList, startGetList, stopGetList, startPaginationMode, stopPaginationMode, paginationType, updatePaginationType, limitType, customLimit, updateLimitType, updateCustomLimit, stopLimitMode, startLimitMode, captureStage, setCaptureStage } = useActionContext(); @@ -387,6 +388,22 @@ export const RightSidePanel: React.FC = ({ onFinishCapture return !hasValidListSelector || hasUnconfirmedListTextFields; }, [captureStage, browserSteps, hasUnconfirmedListTextFields]); + const handleCaptureListClick = () => { + setShowCaptureListOptions(true); + setShowCaptureText(false); + setShowCaptureScreenshot(false); + }; + + const handleManualCapture = () => { + startGetList(); + setShowCaptureListOptions(false); + }; + + const handleAutoCapture = () => { + console.log('Auto Capture clicked'); + setShowCaptureListOptions(false); + }; + return ( {/* @@ -394,7 +411,29 @@ export const RightSidePanel: React.FC = ({ onFinishCapture */} - {!getText && !getScreenshot && !getList && showCaptureList && } + {!getText && !getScreenshot && !getList && showCaptureList && !showCaptureListOptions && ( + + )} + {/* Show Manual and Auto Capture options when "Capture List" is clicked */} + {!getText && !getScreenshot && !getList && showCaptureList && showCaptureListOptions && ( + + + + + + )} {getList && ( <> @@ -459,7 +498,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture )} - {!getText && !getScreenshot && !getList && showCaptureText && } + {!getText && !getScreenshot && !getList && showCaptureText && !showCaptureListOptions && } {getText && <> @@ -468,7 +507,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture } - {!getText && !getScreenshot && !getList && showCaptureScreenshot && } + {!getText && !getScreenshot && !getList && showCaptureScreenshot && !showCaptureListOptions && } {getScreenshot && ( From 9cafb6ae43d887e8904a499b8f0e1d5413c9872c Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 20 Nov 2024 18:22:33 +0530 Subject: [PATCH 02/37] feat: getListAuto browser action --- src/context/browserActions.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/context/browserActions.tsx b/src/context/browserActions.tsx index ef303f822..828deb956 100644 --- a/src/context/browserActions.tsx +++ b/src/context/browserActions.tsx @@ -8,6 +8,7 @@ export type CaptureStage = 'initial' | 'pagination' | 'limit' | 'complete' | ''; interface ActionContextProps { getText: boolean; getList: boolean; + getListAuto: boolean; getScreenshot: boolean; paginationMode: boolean; limitMode: boolean; @@ -21,6 +22,8 @@ interface ActionContextProps { stopGetText: () => void; startGetList: () => void; stopGetList: () => void; + startGetListAuto: () => void; + stopGetListAuto: () => void; startGetScreenshot: () => void; stopGetScreenshot: () => void; stopPaginationMode: () => void; @@ -36,6 +39,7 @@ const ActionContext = createContext(undefined); export const ActionProvider = ({ children }: { children: ReactNode }) => { const [getText, setGetText] = useState(false); const [getList, setGetList] = useState(false); + const [getListAuto, setGetListAuto] = useState(false); const [getScreenshot, setGetScreenshot] = useState(false); const [paginationMode, setPaginationMode] = useState(false); const [limitMode, setLimitMode] = useState(false); @@ -82,6 +86,9 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { setCaptureStage('complete'); }; + const startGetListAuto = () => setGetListAuto(true); + const stopGetListAuto = () => setGetListAuto(false); + const startGetScreenshot = () => setGetScreenshot(true); const stopGetScreenshot = () => setGetScreenshot(false); @@ -89,6 +96,7 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { { stopGetText, startGetList, stopGetList, + startGetListAuto, + stopGetListAuto, startGetScreenshot, stopGetScreenshot, startPaginationMode, From 45a25341e114d5e32b288851fe214245e35a9ed6 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 20 Nov 2024 18:24:16 +0530 Subject: [PATCH 03/37] feat: remove handle pair delete --- src/components/organisms/RightSidePanel.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index 00682be2c..81d0e7a34 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -126,8 +126,6 @@ export const RightSidePanel: React.FC = ({ onFinishCapture setHoverStates(prev => ({ ...prev, [id]: false })); }; - const handlePairDelete = () => { } - const handleTextLabelChange = (id: number, label: string, listId?: number, fieldKey?: string) => { if (listId !== undefined && fieldKey !== undefined) { // Prevent editing if the field is confirmed From 8315d854e99fed4d61346339bbaf94fb5a06a708 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 20 Nov 2024 18:26:19 +0530 Subject: [PATCH 04/37] feat: invoke startGetListAuto on handleAutoCapture --- src/components/organisms/RightSidePanel.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index 81d0e7a34..1744fe5f3 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -57,7 +57,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const [showCaptureListOptions, setShowCaptureListOptions] = useState(false); const { lastAction, notify, currentWorkflowActionsState, setCurrentWorkflowActionsState } = useGlobalInfoStore(); - const { getText, startGetText, stopGetText, getScreenshot, startGetScreenshot, stopGetScreenshot, getList, startGetList, stopGetList, startPaginationMode, stopPaginationMode, paginationType, updatePaginationType, limitType, customLimit, updateLimitType, updateCustomLimit, stopLimitMode, startLimitMode, captureStage, setCaptureStage } = useActionContext(); + const { getText, startGetText, stopGetText, getScreenshot, startGetScreenshot, stopGetScreenshot, getList, startGetList, stopGetList, getListAuto, startGetListAuto, stopGetListAuto, startPaginationMode, stopPaginationMode, paginationType, updatePaginationType, limitType, customLimit, updateLimitType, updateCustomLimit, stopLimitMode, startLimitMode, captureStage, setCaptureStage } = useActionContext(); const { browserSteps, updateBrowserTextStepLabel, deleteBrowserStep, addScreenshotStep, updateListTextFieldLabel, removeListTextField } = useBrowserSteps(); const { id, socket } = useSocketStore(); @@ -398,6 +398,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture }; const handleAutoCapture = () => { + startGetListAuto(); console.log('Auto Capture clicked'); setShowCaptureListOptions(false); }; From 17e14d1c7071ea02de858029e4ad973344ecbcfa Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 20 Nov 2024 18:47:43 +0530 Subject: [PATCH 05/37] feat: handle get list auto for highlighter --- src/components/organisms/BrowserWindow.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 697b4adb1..48cfd7dc8 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -66,7 +66,7 @@ export const BrowserWindow = () => { const { socket } = useSocketStore(); const { notify } = useGlobalInfoStore(); - const { getText, getList, paginationMode, paginationType, limitMode } = useActionContext(); + const { getText, getList, getListAuto, paginationMode, paginationType, limitMode } = useActionContext(); const { addTextStep, addListStep } = useBrowserSteps(); const onMouseMove = (e: MouseEvent) => { @@ -115,7 +115,7 @@ export const BrowserWindow = () => { }, [screenShot, canvasRef, socket, screencastHandler]); const highlighterHandler = useCallback((data: { rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[] }) => { - if (getList === true) { + if (getList === true || getListAuto === true) { if (listSelector) { socket?.emit('listSelector', { selector: listSelector }); if (limitMode) { @@ -142,7 +142,7 @@ export const BrowserWindow = () => { // for non-list steps setHighlighterData(data); } - }, [highlighterData, getList, socket, listSelector, paginationMode, paginationType]); + }, [highlighterData, getList, getListAuto, socket, listSelector, paginationMode, paginationType]); useEffect(() => { @@ -196,7 +196,7 @@ export const BrowserWindow = () => { } } - if (paginationMode && getList) { + if (paginationMode && (getList || getListAuto)) { // Only allow selection in pagination mode if type is not empty, 'scrollDown', or 'scrollUp' if (paginationType !== '' && paginationType !== 'scrollDown' && paginationType !== 'scrollUp' && paginationType !== 'none') { setPaginationSelector(highlighterData.selector); @@ -206,12 +206,12 @@ export const BrowserWindow = () => { return; } - if (getList === true && !listSelector) { + if ((getList === true || getListAuto === true) && !listSelector) { setListSelector(highlighterData.selector); notify(`info`, `List selected succesfully. Select the text data for extraction.`) setCurrentListId(Date.now()); setFields({}); - } else if (getList === true && listSelector && currentListId) { + } else if ((getList === true || getListAuto === true) && listSelector && currentListId) { const attribute = options[0].value; const data = attribute === 'href' ? highlighterData.elementInfo?.url || '' : attribute === 'src' ? highlighterData.elementInfo?.imageUrl || '' : @@ -277,7 +277,7 @@ export const BrowserWindow = () => { attribute: attribute }); } - if (getList === true && listSelector && currentListId) { + if ((getList === true || getListAuto === true) && listSelector && currentListId) { const newField: TextStep = { id: Date.now(), type: 'text', @@ -321,7 +321,7 @@ export const BrowserWindow = () => { return (
{ - getText === true || getList === true ? ( + getText === true || getList === true || getListAuto === true ? ( { }} @@ -361,7 +361,7 @@ export const BrowserWindow = () => { ) : null }
- {((getText === true || getList === true) && !showAttributeModal && highlighterData?.rect != null && highlighterData?.rect.top != null) && canvasRef?.current ? + {((getText === true || getList === true || getListAuto === true) && !showAttributeModal && highlighterData?.rect != null && highlighterData?.rect.top != null) && canvasRef?.current ? Date: Wed, 20 Nov 2024 19:21:57 +0530 Subject: [PATCH 06/37] feat: add listAuto functions --- src/components/organisms/RightSidePanel.tsx | 44 +++++++++++++++++---- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index 1744fe5f3..ae8b62acd 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -342,7 +342,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture notify('error', 'Capture Text Discarded'); }, [browserSteps, stopGetText, deleteBrowserStep]); - const discardGetList = useCallback(() => { + const discardGetListManual = useCallback(() => { stopGetList(); browserSteps.forEach(step => { if (step.type === 'list') { @@ -357,6 +357,21 @@ export const RightSidePanel: React.FC = ({ onFinishCapture notify('error', 'Capture List Discarded'); }, [browserSteps, stopGetList, deleteBrowserStep, resetListState]); + const discardGetListAuto = useCallback(() => { + stopGetListAuto(); + browserSteps.forEach(step => { + if (step.type === 'list') { + deleteBrowserStep(step.id); + } + }); + resetListState(); + setShowPaginationOptions(false); + setShowLimitOptions(false); + setCaptureStage('initial'); + setConfirmedListTextFields({}); + notify('error', 'Capture List Discarded'); + }, [browserSteps, stopGetListAuto, deleteBrowserStep, resetListState]); + const captureScreenshot = (fullPage: boolean) => { const screenshotSettings: ScreenshotSettings = { @@ -399,7 +414,6 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const handleAutoCapture = () => { startGetListAuto(); - console.log('Auto Capture clicked'); setShowCaptureListOptions(false); }; @@ -410,13 +424,13 @@ export const RightSidePanel: React.FC = ({ onFinishCapture */} - {!getText && !getScreenshot && !getList && showCaptureList && !showCaptureListOptions && ( + {!getText && !getScreenshot && !getList && !getListAuto && showCaptureList && !showCaptureListOptions && ( )} {/* Show Manual and Auto Capture options when "Capture List" is clicked */} - {!getText && !getScreenshot && !getList && showCaptureList && showCaptureListOptions && ( + {!getText && !getScreenshot && !getList && !getListAuto && showCaptureList && showCaptureListOptions && ( )} + {getListAuto && ( + <> + + + + + + )} {getList && ( <> @@ -445,7 +475,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture captureStage === 'pagination' ? 'Confirm Pagination' : captureStage === 'limit' ? 'Confirm Limit' : 'Finish Capture'} - + )} @@ -497,7 +527,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture )} - {!getText && !getScreenshot && !getList && showCaptureText && !showCaptureListOptions && } + {!getText && !getScreenshot && !getList && !getListAuto && showCaptureText && !showCaptureListOptions && } {getText && <> @@ -506,7 +536,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture } - {!getText && !getScreenshot && !getList && showCaptureScreenshot && !showCaptureListOptions && } + {!getText && !getScreenshot && !getList && !getListAuto && showCaptureScreenshot && !showCaptureListOptions && } {getScreenshot && ( From 6e9d4e58e9e90ef9828b05655957c0a9083872f2 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 20 Nov 2024 19:23:48 +0530 Subject: [PATCH 07/37] feat: add getListAuto context --- .../molecules/ActionDescriptionBox.tsx | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/ActionDescriptionBox.tsx b/src/components/molecules/ActionDescriptionBox.tsx index cad962c76..b85a78618 100644 --- a/src/components/molecules/ActionDescriptionBox.tsx +++ b/src/components/molecules/ActionDescriptionBox.tsx @@ -44,10 +44,11 @@ const Content = styled.div` `; const ActionDescriptionBox = () => { - const { getText, getScreenshot, getList, captureStage } = useActionContext() as { + const { getText, getScreenshot, getList, captureStage, getListAuto } = useActionContext() as { getText: boolean; getScreenshot: boolean; getList: boolean; + getListAuto: boolean; captureStage: 'initial' | 'pagination' | 'limit' | 'complete'; }; @@ -99,7 +100,32 @@ const ActionDescriptionBox = () => { ); - } else { + } else if (getListAuto) { + return ( + <> + Capture List + + Hover over the list you want to extract + + + {messages.map(({ stage, text }, index) => ( + + } + label={{text}} + /> + ))} + + + ); + } + + else { return ( <> What data do you want to extract? From 82ba3fc6e1029907460992f213da8d8cd936f592 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 20 Nov 2024 19:24:50 +0530 Subject: [PATCH 08/37] feat: add getListAuto functionality --- src/context/browserActions.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/context/browserActions.tsx b/src/context/browserActions.tsx index 828deb956..cbed85e5b 100644 --- a/src/context/browserActions.tsx +++ b/src/context/browserActions.tsx @@ -86,8 +86,20 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { setCaptureStage('complete'); }; - const startGetListAuto = () => setGetListAuto(true); - const stopGetListAuto = () => setGetListAuto(false); + const startGetListAuto = () => { + setGetListAuto(true); + socket?.emit('setGetList', { getList: true }); + setCaptureStage('initial'); + }; + + const stopGetListAuto = () => { + setGetListAuto(false); + socket?.emit('setGetList', { getList: false }); + setPaginationType(''); + setLimitType(''); + setCustomLimit(''); + setCaptureStage('complete'); + }; const startGetScreenshot = () => setGetScreenshot(true); const stopGetScreenshot = () => setGetScreenshot(false); From a80760aa2cdcf78a21c4ec5e19051a536fc324da Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 20 Nov 2024 19:50:14 +0530 Subject: [PATCH 09/37] feat: add handle list auto settings object --- src/components/organisms/RightSidePanel.tsx | 42 +++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index ae8b62acd..b928cf637 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -217,6 +217,32 @@ export const RightSidePanel: React.FC = ({ onFinishCapture onFinishCapture(); }, [stopGetText, getTextSettingsObject, socket, browserSteps, confirmedTextSteps]); + const getListAutoSettingsObject = useCallback(() => { + let settings: { + listSelector?: string; + } = {}; + + browserSteps.forEach(step => { + if (step.type === 'list' && step.listSelector && Object.keys(step.fields).length > 0) { + const fields: Record = {}; + + Object.entries(step.fields).forEach(([id, field]) => { + if (field.selectorObj?.selector) { + fields[field.label] = { + selector: field.selectorObj.selector, + }; + } + }); + + settings = { + listSelector: step.listSelector, + }; + } + }); + + return settings; + }, [browserSteps]); + const getListSettingsObject = useCallback(() => { let settings: { listSelector?: string; @@ -264,6 +290,11 @@ export const RightSidePanel: React.FC = ({ onFinishCapture resetListState(); }, [stopGetList, resetListState]); + const handleStopGetListAuto = useCallback(() => { + stopGetListAuto(); + resetListState(); + }, [stopGetListAuto, resetListState]); + const stopCaptureAndEmitGetListSettings = useCallback(() => { const settings = getListSettingsObject(); if (settings) { @@ -275,6 +306,17 @@ export const RightSidePanel: React.FC = ({ onFinishCapture onFinishCapture(); }, [stopGetList, getListSettingsObject, socket, notify, handleStopGetList]); + const captureAndEmitGetListAutoSettings = useCallback(() => { + const settings = getListAutoSettingsObject(); + if (settings) { + socket?.emit('action', { action: 'scrapeListAuto', settings }); + } else { + notify('error', 'Unable to create list settings. Make sure you have defined a field for the list.'); + } + handleStopGetListAuto(); + onFinishCapture(); + }, [getListAutoSettingsObject, socket, notify]); + const hasUnconfirmedListTextFields = browserSteps.some(step => step.type === 'list' && Object.values(step.fields).some(field => !confirmedListTextFields[step.id]?.[field.id])); const handleConfirmListCapture = useCallback(() => { From c51e42d425a0ee13ee2ae836d8508c5ceb0c32bf Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 20 Nov 2024 21:54:45 +0530 Subject: [PATCH 10/37] feat: emit setGetListAuto socket event --- src/context/browserActions.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/context/browserActions.tsx b/src/context/browserActions.tsx index cbed85e5b..f0619ac5b 100644 --- a/src/context/browserActions.tsx +++ b/src/context/browserActions.tsx @@ -88,13 +88,13 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { const startGetListAuto = () => { setGetListAuto(true); - socket?.emit('setGetList', { getList: true }); + socket?.emit('setGetListAuto', { getListAuto: true }); setCaptureStage('initial'); }; const stopGetListAuto = () => { setGetListAuto(false); - socket?.emit('setGetList', { getList: false }); + socket?.emit('setGetListAuto', { getListAuto: false }); setPaginationType(''); setLimitType(''); setCustomLimit(''); From 70de2a7b880cc4eae3deb2fbc2c762aece38f8a0 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 20 Nov 2024 21:55:28 +0530 Subject: [PATCH 11/37] feat: send highlighter data for get list auto --- .../workflow-management/classes/Generator.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 1716531cb..2550b3929 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -10,7 +10,8 @@ import { getChildSelectors, getNonUniqueSelectors, isRuleOvershadowing, - selectorAlreadyInWorkflow + selectorAlreadyInWorkflow, + extractChildData } from "../selector"; import { CustomActions } from "../../../../src/shared/types"; import { workflow } from "../../routes"; @@ -61,6 +62,8 @@ export class WorkflowGenerator { */ private getList: boolean = false; + private getListAuto: boolean = false; + private listSelector: string = ''; /** @@ -116,6 +119,9 @@ export class WorkflowGenerator { this.socket.on('setGetList', (data: { getList: boolean }) => { this.getList = data.getList; }); + this.socket.on('setGetListAuto', (data: { getListAuto: boolean }) => { + this.getListAuto = data.getListAuto; + }); this.socket.on('listSelector', (data: { selector: string }) => { this.listSelector = data.selector; }) @@ -524,7 +530,7 @@ export class WorkflowGenerator { private generateSelector = async (page: Page, coordinates: Coordinates, action: ActionType) => { const elementInfo = await getElementInformation(page, coordinates); - const selectorBasedOnCustomAction = (this.getList === true) + const selectorBasedOnCustomAction = (this.getList === true || this.getListAuto === true) ? await getNonUniqueSelectors(page, coordinates) : await getSelectors(page, coordinates); @@ -562,7 +568,13 @@ export class WorkflowGenerator { } else { this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); } - } else { + } else if ( this.getListAuto === true) { + //const childSelectors = await getChildSelectors(page, this.listSelector || ''); + const childData = await extractChildData(page, this.listSelector || ''); + console.log(`child data is: ${JSON.stringify(childData)}`) + this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo, childData }); + } + else { this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); } } From 517216f03b10a8a4fc10d5505f3bc2016392a461 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 20 Nov 2024 21:55:52 +0530 Subject: [PATCH 12/37] feat: set get list auto true --- src/components/organisms/BrowserWindow.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 48cfd7dc8..0113ae83b 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -54,7 +54,7 @@ const getAttributeOptions = (tagName: string, elementInfo: ElementInfo | null): export const BrowserWindow = () => { const [canvasRef, setCanvasReference] = useState | undefined>(undefined); const [screenShot, setScreenShot] = useState(""); - const [highlighterData, setHighlighterData] = useState<{ rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[] } | null>(null); + const [highlighterData, setHighlighterData] = useState<{ rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[], childData?: string[] } | null>(null); const [showAttributeModal, setShowAttributeModal] = useState(false); const [attributeOptions, setAttributeOptions] = useState([]); const [selectedElement, setSelectedElement] = useState<{ selector: string, info: ElementInfo | null } | null>(null); @@ -114,7 +114,7 @@ export const BrowserWindow = () => { } }, [screenShot, canvasRef, socket, screencastHandler]); - const highlighterHandler = useCallback((data: { rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[] }) => { + const highlighterHandler = useCallback((data: { rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[], childData?: string[] }) => { if (getList === true || getListAuto === true) { if (listSelector) { socket?.emit('listSelector', { selector: listSelector }); From 6db7a196056bc5d2588679a182bcae7c919a94ae Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 20 Nov 2024 21:56:15 +0530 Subject: [PATCH 13/37] feat: extract child data --- server/src/workflow-management/selector.ts | 130 +++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 18b878ff5..ed4b7f9e4 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -790,6 +790,136 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates } }; +export const extractChildData = async ( + page: Page, + parentSelector: string +): Promise<{ data: string; selector: string }[]> => { + try { + const baseURL = new URL(page.url()); + + const childData = await page.evaluate(({ parentSelector, baseHref }: { parentSelector: string, baseHref: string }) => { + interface ElementData { + data: string; + selector: string; + } + + function getNonUniqueSelector(element: HTMLElement): string { + let selector = element.tagName.toLowerCase(); + + const className = typeof element.className === 'string' ? element.className : ''; + if (className) { + const classes = className.split(/\s+/).filter(cls => cls); + if (classes.length > 0) { + selector += '.' + classes.map(cls => CSS.escape(cls)).join('.'); + } + } + + return selector; + } + + function getSelectorPath(element: HTMLElement | null): string { + if (!element || !element.parentElement) return ''; + + const parentSelector = getNonUniqueSelector(element.parentElement); + const elementSelector = getNonUniqueSelector(element); + + return `${parentSelector} > ${elementSelector}`; + } + + function resolveURL(url: string | null): string | null { + if (!url) return null; + try { + return new URL(url, baseHref).href; + } catch { + return url; + } + } + + function cleanText(text: string): string { + return text.replace(/\s+/g, ' ').trim(); + } + + function extractElementData(element: HTMLElement): ElementData[] { + const selector = getSelectorPath(element); + const results: ElementData[] = []; + + // Clean text content (if present) + const textContent = cleanText(element.textContent || ''); + if (textContent) { + results.push({ data: textContent, selector }); + } + + // Links (if ) + if (element.tagName.toLowerCase() === 'a') { + const href = resolveURL(element.getAttribute('href')); + if (href) { + results.push({ data: href, selector }); + } + } + + // Images (if ) + if (element.tagName.toLowerCase() === 'img') { + const src = resolveURL(element.getAttribute('src')); + if (src) { + results.push({ data: src, selector }); + } + const alt = cleanText(element.getAttribute('alt') || ''); + if (alt) { + results.push({ data: alt, selector }); + } + } + + // Additional attributes (e.g., title, data-*) + const title = cleanText(element.getAttribute('title') || ''); + if (title) { + results.push({ data: title, selector }); + } + + const dataAttributes = Array.from(element.attributes) + .filter(attr => attr.name.startsWith('data-')) + .map(attr => ({ + data: cleanText(attr.value), + selector + })); + results.push(...dataAttributes); + + return results; + } + + function getAllDescendantData(element: HTMLElement): ElementData[] { + let data: ElementData[] = []; + const children = Array.from(element.children) as HTMLElement[]; + + for (const child of children) { + data = data.concat(extractElementData(child)); + data = data.concat(getAllDescendantData(child)); + } + + return data; + } + + const parentElement = document.querySelector(parentSelector) as HTMLElement; + if (!parentElement) return []; + + const allData = getAllDescendantData(parentElement); + + // Deduplicate results based on data and selector + const uniqueData = Array.from( + new Map(allData.map(item => [`${item.data}-${item.selector}`, item])).values() + ); + + return uniqueData; + }, { parentSelector, baseHref: baseURL.href }); + + return childData || []; + } catch (error) { + console.error('Error in extractChildData:', error); + return []; + } +}; + + + export const getChildSelectors = async (page: Page, parentSelector: string): Promise => { try { From 4af621f7ced862e13160e5c1ef4a201d4b0db8ad Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 21 Nov 2024 07:20:00 +0530 Subject: [PATCH 14/37] feat: get data as per importance --- server/src/workflow-management/selector.ts | 87 ++++++++++++---------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index ed4b7f9e4..3bdbe8d8a 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -797,33 +797,54 @@ export const extractChildData = async ( try { const baseURL = new URL(page.url()); - const childData = await page.evaluate(({ parentSelector, baseHref }: { parentSelector: string, baseHref: string }) => { + const relevantData = await page.evaluate(({ parentSelector, baseHref }: { parentSelector: string, baseHref: string }) => { interface ElementData { data: string; selector: string; + importance: number; // To assign relevance to the data } + // Utility function to get a more unique selector function getNonUniqueSelector(element: HTMLElement): string { let selector = element.tagName.toLowerCase(); - - const className = typeof element.className === 'string' ? element.className : ''; - if (className) { - const classes = className.split(/\s+/).filter(cls => cls); - if (classes.length > 0) { - selector += '.' + classes.map(cls => CSS.escape(cls)).join('.'); - } + const id = element.id ? `#${element.id}` : ''; + + // Ensure className is always a string before using replace + const className = element.classList instanceof DOMTokenList + ? Array.from(element.classList).join(' ') // Join classes into a string if it's a DOMTokenList + : element.className; // Else, use className directly (it should already be a string) + const classSelector = className ? `.${className.replace(/\s+/g, '.')}` : ''; + + selector += id + classSelector; + + // Handle other attributes (e.g., name, type) + if (element.hasAttribute('name')) { + selector += `[name=${CSS.escape(element.getAttribute('name') || '')}]`; } - + if (element.hasAttribute('type')) { + selector += `[type=${CSS.escape(element.getAttribute('type') || '')}]`; + } + return selector; } + + + // Function to prioritize elements based on relevance + function determineImportance(element: HTMLElement): number { + let importance = 0; + + // Increase importance based on tag type (e.g., headers, main content) + if (['h1', 'h2', 'h3'].includes(element.tagName.toLowerCase())) { + importance += 3; // High relevance for headings + } else if (element.tagName.toLowerCase() === 'p') { + importance += 2; // Moderate relevance for paragraphs + } else if (element.tagName.toLowerCase() === 'a') { + importance += 1; // Low relevance for links (if it's a primary link) + } - function getSelectorPath(element: HTMLElement | null): string { - if (!element || !element.parentElement) return ''; - - const parentSelector = getNonUniqueSelector(element.parentElement); - const elementSelector = getNonUniqueSelector(element); + // Further logic can be added to assign importance based on class or other attributes - return `${parentSelector} > ${elementSelector}`; + return importance; } function resolveURL(url: string | null): string | null { @@ -839,53 +860,44 @@ export const extractChildData = async ( return text.replace(/\s+/g, ' ').trim(); } + // Extract relevant data from elements function extractElementData(element: HTMLElement): ElementData[] { - const selector = getSelectorPath(element); + const selector = getNonUniqueSelector(element); + const importance = determineImportance(element); const results: ElementData[] = []; - // Clean text content (if present) const textContent = cleanText(element.textContent || ''); if (textContent) { - results.push({ data: textContent, selector }); + results.push({ data: textContent, selector, importance }); } - // Links (if ) if (element.tagName.toLowerCase() === 'a') { const href = resolveURL(element.getAttribute('href')); if (href) { - results.push({ data: href, selector }); + results.push({ data: href, selector, importance: importance + 1 }); } } - // Images (if ) if (element.tagName.toLowerCase() === 'img') { const src = resolveURL(element.getAttribute('src')); if (src) { - results.push({ data: src, selector }); + results.push({ data: src, selector, importance }); } const alt = cleanText(element.getAttribute('alt') || ''); if (alt) { - results.push({ data: alt, selector }); + results.push({ data: alt, selector, importance }); } } - // Additional attributes (e.g., title, data-*) const title = cleanText(element.getAttribute('title') || ''); if (title) { - results.push({ data: title, selector }); + results.push({ data: title, selector, importance }); } - const dataAttributes = Array.from(element.attributes) - .filter(attr => attr.name.startsWith('data-')) - .map(attr => ({ - data: cleanText(attr.value), - selector - })); - results.push(...dataAttributes); - return results; } + // Collect descendant elements data with their importance function getAllDescendantData(element: HTMLElement): ElementData[] { let data: ElementData[] = []; const children = Array.from(element.children) as HTMLElement[]; @@ -903,24 +915,23 @@ export const extractChildData = async ( const allData = getAllDescendantData(parentElement); - // Deduplicate results based on data and selector + // Deduplicate and prioritize the most important data const uniqueData = Array.from( new Map(allData.map(item => [`${item.data}-${item.selector}`, item])).values() - ); + ).sort((a, b) => b.importance - a.importance); // Sort by importance return uniqueData; }, { parentSelector, baseHref: baseURL.href }); - return childData || []; + return relevantData || []; } catch (error) { - console.error('Error in extractChildData:', error); + console.error('Error in extractRelevantData:', error); return []; } }; - export const getChildSelectors = async (page: Page, parentSelector: string): Promise => { try { const childSelectors = await page.evaluate((parentSelector: string) => { From 86ba1c8e5b81a2e380a04dadc96e0c78ecd130a9 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 21 Nov 2024 16:17:41 +0530 Subject: [PATCH 15/37] feat: importance by element --- .../workflow-management/classes/Generator.ts | 3 +- server/src/workflow-management/selector.ts | 60 +++++++++---------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 2550b3929..77d730bf7 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -569,8 +569,9 @@ export class WorkflowGenerator { this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); } } else if ( this.getListAuto === true) { + console.log(this.listSelector) //const childSelectors = await getChildSelectors(page, this.listSelector || ''); - const childData = await extractChildData(page, this.listSelector || ''); + const childData = await extractChildData(page, this.listSelector); console.log(`child data is: ${JSON.stringify(childData)}`) this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo, childData }); } diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 3bdbe8d8a..09862105d 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -833,17 +833,13 @@ export const extractChildData = async ( function determineImportance(element: HTMLElement): number { let importance = 0; - // Increase importance based on tag type (e.g., headers, main content) - if (['h1', 'h2', 'h3'].includes(element.tagName.toLowerCase())) { - importance += 3; // High relevance for headings - } else if (element.tagName.toLowerCase() === 'p') { - importance += 2; // Moderate relevance for paragraphs - } else if (element.tagName.toLowerCase() === 'a') { - importance += 1; // Low relevance for links (if it's a primary link) + // Assign importance based on tag type + if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(element.tagName.toLowerCase())) { + importance = 2; // Importance 2 for all headings + } else if (['p', 'span', 'a'].includes(element.tagName.toLowerCase())) { + importance = 1; // Importance 1 for p, span, a } - // Further logic can be added to assign importance based on class or other attributes - return importance; } @@ -866,32 +862,35 @@ export const extractChildData = async ( const importance = determineImportance(element); const results: ElementData[] = []; - const textContent = cleanText(element.textContent || ''); - if (textContent) { - results.push({ data: textContent, selector, importance }); - } - - if (element.tagName.toLowerCase() === 'a') { - const href = resolveURL(element.getAttribute('href')); - if (href) { - results.push({ data: href, selector, importance: importance + 1 }); + // Only process elements with importance >= 1 + if (importance >= 1) { + const textContent = cleanText(element.textContent || ''); + if (textContent) { + results.push({ data: textContent, selector, importance }); } - } - if (element.tagName.toLowerCase() === 'img') { - const src = resolveURL(element.getAttribute('src')); - if (src) { - results.push({ data: src, selector, importance }); + if (element.tagName.toLowerCase() === 'a') { + const href = resolveURL(element.getAttribute('href')); + if (href) { + results.push({ data: href, selector, importance: importance + 1 }); + } } - const alt = cleanText(element.getAttribute('alt') || ''); - if (alt) { - results.push({ data: alt, selector, importance }); + + if (element.tagName.toLowerCase() === 'img') { + const src = resolveURL(element.getAttribute('src')); + if (src) { + results.push({ data: src, selector, importance }); + } + const alt = cleanText(element.getAttribute('alt') || ''); + if (alt) { + results.push({ data: alt, selector, importance }); + } } - } - const title = cleanText(element.getAttribute('title') || ''); - if (title) { - results.push({ data: title, selector, importance }); + const title = cleanText(element.getAttribute('title') || ''); + if (title) { + results.push({ data: title, selector, importance }); + } } return results; @@ -932,6 +931,7 @@ export const extractChildData = async ( + export const getChildSelectors = async (page: Page, parentSelector: string): Promise => { try { const childSelectors = await page.evaluate((parentSelector: string) => { From eb5ba668704e3b6256cc07c3c7de8390dbdbf120 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 21 Nov 2024 18:33:14 +0530 Subject: [PATCH 16/37] feat: handle a and img urls --- server/src/workflow-management/selector.ts | 128 ++++++++++++--------- 1 file changed, 71 insertions(+), 57 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 09862105d..6dcb97c17 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -797,26 +797,25 @@ export const extractChildData = async ( try { const baseURL = new URL(page.url()); - const relevantData = await page.evaluate(({ parentSelector, baseHref }: { parentSelector: string, baseHref: string }) => { + const uniqueData = await page.evaluate(({ parentSelector, baseHref }: { parentSelector: string, baseHref: string }) => { interface ElementData { data: string; selector: string; - importance: number; // To assign relevance to the data + importance: number; } - // Utility function to get a more unique selector + // Utility function to get a unique selector function getNonUniqueSelector(element: HTMLElement): string { let selector = element.tagName.toLowerCase(); const id = element.id ? `#${element.id}` : ''; - - // Ensure className is always a string before using replace + const className = element.classList instanceof DOMTokenList ? Array.from(element.classList).join(' ') // Join classes into a string if it's a DOMTokenList : element.className; // Else, use className directly (it should already be a string) const classSelector = className ? `.${className.replace(/\s+/g, '.')}` : ''; - + selector += id + classSelector; - + // Handle other attributes (e.g., name, type) if (element.hasAttribute('name')) { selector += `[name=${CSS.escape(element.getAttribute('name') || '')}]`; @@ -824,85 +823,86 @@ export const extractChildData = async ( if (element.hasAttribute('type')) { selector += `[type=${CSS.escape(element.getAttribute('type') || '')}]`; } - + return selector; } - - - // Function to prioritize elements based on relevance - function determineImportance(element: HTMLElement): number { - let importance = 0; - // Assign importance based on tag type - if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(element.tagName.toLowerCase())) { - importance = 2; // Importance 2 for all headings - } else if (['p', 'span', 'a'].includes(element.tagName.toLowerCase())) { - importance = 1; // Importance 1 for p, span, a - } - - return importance; + // Function to determine robustness of a selector + function selectorRobustness(selector: string): number { + // A simple metric: robustness is based on the number of parts in the selector + return selector.split(/[#.:\[\]]/).length; } - function resolveURL(url: string | null): string | null { - if (!url) return null; - try { - return new URL(url, baseHref).href; - } catch { - return url; + // Assign importance + function determineImportance(element: HTMLElement): number { + if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(element.tagName.toLowerCase())) { + return 2; // Importance for headings + } else if (['p', 'span', 'a', 'img'].includes(element.tagName.toLowerCase())) { + return 1; // Importance for p, span, a } + return 0; // Importance for all others } function cleanText(text: string): string { return text.replace(/\s+/g, ' ').trim(); } - // Extract relevant data from elements - function extractElementData(element: HTMLElement): ElementData[] { + function extractElementData(element: HTMLElement, baseHref: string): ElementData[] { const selector = getNonUniqueSelector(element); const importance = determineImportance(element); const results: ElementData[] = []; - - // Only process elements with importance >= 1 + + // Utility to resolve URLs + function resolveURL(url: string | null): string | null { + if (!url) return null; + try { + return new URL(url, baseHref).href; + } catch { + return url; + } + } + + // Include text content if importance is sufficient if (importance >= 1) { const textContent = cleanText(element.textContent || ''); if (textContent) { results.push({ data: textContent, selector, importance }); } - - if (element.tagName.toLowerCase() === 'a') { - const href = resolveURL(element.getAttribute('href')); - if (href) { - results.push({ data: href, selector, importance: importance + 1 }); + } + + // Handle links (a tags) + if (element.tagName.toLowerCase() === 'a') { + const href = element.getAttribute('href'); + if (href) { + const resolvedHref = resolveURL(href); + if (resolvedHref) { + results.push({ data: resolvedHref, selector, importance }); } } - - if (element.tagName.toLowerCase() === 'img') { - const src = resolveURL(element.getAttribute('src')); - if (src) { - results.push({ data: src, selector, importance }); - } - const alt = cleanText(element.getAttribute('alt') || ''); - if (alt) { - results.push({ data: alt, selector, importance }); + } + + // Handle images (img tags) + if (element.tagName.toLowerCase() === 'img') { + const src = element.getAttribute('src'); + if (src) { + const resolvedSrc = resolveURL(src); + if (resolvedSrc) { + results.push({ data: resolvedSrc, selector, importance }); } } - - const title = cleanText(element.getAttribute('title') || ''); - if (title) { - results.push({ data: title, selector, importance }); - } } - + return results; } + + - // Collect descendant elements data with their importance function getAllDescendantData(element: HTMLElement): ElementData[] { let data: ElementData[] = []; const children = Array.from(element.children) as HTMLElement[]; for (const child of children) { - data = data.concat(extractElementData(child)); + data = data.concat(extractElementData(child, baseHref)); data = data.concat(getAllDescendantData(child)); } @@ -914,15 +914,29 @@ export const extractChildData = async ( const allData = getAllDescendantData(parentElement); - // Deduplicate and prioritize the most important data + // Deduplicate and prioritize by importance and robustness const uniqueData = Array.from( - new Map(allData.map(item => [`${item.data}-${item.selector}`, item])).values() - ).sort((a, b) => b.importance - a.importance); // Sort by importance + allData.reduce((map, item) => { + if (!map.has(item.data)) { + map.set(item.data, item); // Add new data + } else { + const existing = map.get(item.data); + if ( + existing && item.importance > existing.importance || // Prefer higher importance + (existing && item.importance === existing.importance && + selectorRobustness(item.selector) > selectorRobustness(existing.selector)) // If equal importance, prefer robust selector + ) { + map.set(item.data, item); // Replace with better element + } + } + return map; + }, new Map()) + ); return uniqueData; }, { parentSelector, baseHref: baseURL.href }); - return relevantData || []; + return uniqueData.map(([_, item]) => item) || []; } catch (error) { console.error('Error in extractRelevantData:', error); return []; From ab16ae305fd5732730d3bfd8458bc3d891bddf0a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 21 Nov 2024 19:29:18 +0530 Subject: [PATCH 17/37] feat: generate identical non unique selectors --- server/src/workflow-management/selector.ts | 23 +++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 6dcb97c17..603060689 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -807,21 +807,16 @@ export const extractChildData = async ( // Utility function to get a unique selector function getNonUniqueSelector(element: HTMLElement): string { let selector = element.tagName.toLowerCase(); - const id = element.id ? `#${element.id}` : ''; - const className = element.classList instanceof DOMTokenList - ? Array.from(element.classList).join(' ') // Join classes into a string if it's a DOMTokenList - : element.className; // Else, use className directly (it should already be a string) - const classSelector = className ? `.${className.replace(/\s+/g, '.')}` : ''; - - selector += id + classSelector; - - // Handle other attributes (e.g., name, type) - if (element.hasAttribute('name')) { - selector += `[name=${CSS.escape(element.getAttribute('name') || '')}]`; - } - if (element.hasAttribute('type')) { - selector += `[type=${CSS.escape(element.getAttribute('type') || '')}]`; + const className = typeof element.className === 'string' ? element.className : ''; + if (className) { + const classes = className.split(/\s+/).filter((cls: string) => Boolean(cls)); + if (classes.length > 0) { + const validClasses = classes.filter((cls: string) => !cls.startsWith('!') && !cls.includes(':')); + if (validClasses.length > 0) { + selector += '.' + validClasses.map(cls => CSS.escape(cls)).join('.'); + } + } } return selector; From b406dd7e03aee4ead7b0fea5e6e1f7bf94111e07 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 21 Nov 2024 19:54:41 +0530 Subject: [PATCH 18/37] chore: whitespace cleanup --- .../src/workflow-management/classes/Generator.ts | 2 +- server/src/workflow-management/selector.ts | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 77d730bf7..1e3fcf419 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -569,7 +569,7 @@ export class WorkflowGenerator { this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); } } else if ( this.getListAuto === true) { - console.log(this.listSelector) + console.log(`LisT Selector:`,this.listSelector) //const childSelectors = await getChildSelectors(page, this.listSelector || ''); const childData = await extractChildData(page, this.listSelector); console.log(`child data is: ${JSON.stringify(childData)}`) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 603060689..1eec305db 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -804,7 +804,6 @@ export const extractChildData = async ( importance: number; } - // Utility function to get a unique selector function getNonUniqueSelector(element: HTMLElement): string { let selector = element.tagName.toLowerCase(); @@ -828,14 +827,13 @@ export const extractChildData = async ( return selector.split(/[#.:\[\]]/).length; } - // Assign importance function determineImportance(element: HTMLElement): number { if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(element.tagName.toLowerCase())) { - return 2; // Importance for headings + return 2; } else if (['p', 'span', 'a', 'img'].includes(element.tagName.toLowerCase())) { - return 1; // Importance for p, span, a + return 1; } - return 0; // Importance for all others + return 0; // Importance for all other tags } function cleanText(text: string): string { @@ -847,7 +845,6 @@ export const extractChildData = async ( const importance = determineImportance(element); const results: ElementData[] = []; - // Utility to resolve URLs function resolveURL(url: string | null): string | null { if (!url) return null; try { @@ -889,8 +886,6 @@ export const extractChildData = async ( return results; } - - function getAllDescendantData(element: HTMLElement): ElementData[] { let data: ElementData[] = []; @@ -938,9 +933,6 @@ export const extractChildData = async ( } }; - - - export const getChildSelectors = async (page: Page, parentSelector: string): Promise => { try { const childSelectors = await page.evaluate((parentSelector: string) => { From 85f3c61d0a600f0f8b0e929d2c408ac721a599af Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 21 Nov 2024 20:00:16 +0530 Subject: [PATCH 19/37] feat: handle selector null check --- server/src/workflow-management/classes/Generator.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 1e3fcf419..0898d8a46 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -569,11 +569,14 @@ export class WorkflowGenerator { this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); } } else if ( this.getListAuto === true) { - console.log(`LisT Selector:`,this.listSelector) - //const childSelectors = await getChildSelectors(page, this.listSelector || ''); + if (this.listSelector !== '') { + console.log(`list selector is: ${this.listSelector}`) const childData = await extractChildData(page, this.listSelector); console.log(`child data is: ${JSON.stringify(childData)}`) this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo, childData }); + } else { + this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); + } } else { this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); From 38a1e56368b285ff10f947f3b275b2b51799d5d8 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 21 Nov 2024 22:50:06 +0530 Subject: [PATCH 20/37] chore: remove console log --- src/components/molecules/RobotEdit.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index 26317260a..f68beee24 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -58,7 +58,6 @@ interface RobotSettingsProps { } export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => { - console.log("robot edit"); const [robot, setRobot] = useState(null); const { recordingId, notify } = useGlobalInfoStore(); From 14106168c508f8afe84f9f851685ee8b2278df75 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 21 Nov 2024 23:10:01 +0530 Subject: [PATCH 21/37] feat: get list auto ref --- src/components/atoms/canvas.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 1dd88e190..37366efdc 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -27,9 +27,10 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { const canvasRef = useRef(null); const { socket } = useSocketStore(); const { setLastAction, lastAction } = useGlobalInfoStore(); - const { getText, getList } = useActionContext(); + const { getText, getList, getListAuto } = useActionContext(); const getTextRef = useRef(getText); const getListRef = useRef(getList); + const getListAutoRef = useRef(getListAuto); const notifyLastAction = (action: string) => { if (lastAction !== action) { @@ -59,7 +60,10 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { console.log('Capturing Text...'); } else if (getListRef.current === true) { console.log('Capturing List...'); - } else { + } else if (getListAutoRef.current === true) { + console.log('Capturing List Automatically...'); + } + else { socket.emit('input:mousedown', clickCoordinates); } notifyLastAction('click'); From baa7acfa6b5385d06b497f94cee8ee66e4ae608d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 21 Nov 2024 23:43:47 +0530 Subject: [PATCH 22/37] feat: pass getListAuto ref in useEffect --- src/components/atoms/canvas.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 37366efdc..2860813ec 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -43,6 +43,7 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { useEffect(() => { getTextRef.current = getText; getListRef.current = getList; + getListAutoRef.current = getListAuto; }, [getText, getList]); const onMouseEvent = useCallback((event: MouseEvent) => { From 339de5bd52e37565a47eed62d4995331584b0bfa Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 21 Nov 2024 23:53:10 +0530 Subject: [PATCH 23/37] feat: pass getListAuto in dependency array --- src/components/atoms/canvas.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 2860813ec..1555e6369 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -44,7 +44,7 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { getTextRef.current = getText; getListRef.current = getList; getListAutoRef.current = getListAuto; - }, [getText, getList]); + }, [getText, getList, getListAuto]); const onMouseEvent = useCallback((event: MouseEvent) => { if (socket && canvasRef.current) { From 2f03f965c2eb57ff578ccbba26482c242ce13b65 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 01:31:39 +0530 Subject: [PATCH 24/37] feat(temp): -rm getListAuto standalone --- .../workflow-management/classes/Generator.ts | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 0898d8a46..65bd86b1c 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -62,7 +62,7 @@ export class WorkflowGenerator { */ private getList: boolean = false; - private getListAuto: boolean = false; + // private getListAuto: boolean = false; private listSelector: string = ''; @@ -119,9 +119,9 @@ export class WorkflowGenerator { this.socket.on('setGetList', (data: { getList: boolean }) => { this.getList = data.getList; }); - this.socket.on('setGetListAuto', (data: { getListAuto: boolean }) => { - this.getListAuto = data.getListAuto; - }); + // this.socket.on('setGetListAuto', (data: { getListAuto: boolean }) => { + // this.getListAuto = data.getListAuto; + // }); this.socket.on('listSelector', (data: { selector: string }) => { this.listSelector = data.selector; }) @@ -530,7 +530,7 @@ export class WorkflowGenerator { private generateSelector = async (page: Page, coordinates: Coordinates, action: ActionType) => { const elementInfo = await getElementInformation(page, coordinates); - const selectorBasedOnCustomAction = (this.getList === true || this.getListAuto === true) + const selectorBasedOnCustomAction = (this.getList === true) ? await getNonUniqueSelectors(page, coordinates) : await getSelectors(page, coordinates); @@ -564,20 +564,23 @@ export class WorkflowGenerator { if (this.getList === true) { if (this.listSelector !== '') { const childSelectors = await getChildSelectors(page, this.listSelector || ''); - this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo, childSelectors }) - } else { - this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); - } - } else if ( this.getListAuto === true) { - if (this.listSelector !== '') { - console.log(`list selector is: ${this.listSelector}`) - const childData = await extractChildData(page, this.listSelector); - console.log(`child data is: ${JSON.stringify(childData)}`) - this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo, childData }); + const childData = await extractChildData(page, this.listSelector || ''); + this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo, childSelectors, childData }) } else { this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); } - } + } + // else if (this.getListAuto === true) { + // if (this.listSelector !== '') { + // console.log(`list selector is: ${this.listSelector}`) + // const childData = await extractChildData(page, this.listSelector || ''); + // console.log(`Data From Backend: ${JSON.stringify({ rect, selector: displaySelector, elementInfo, childData })}`) + // this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo, childData }); + // } + // else { + // this.socket.emit('highlighter', { ayo:'ayo', rect, selector: displaySelector, elementInfo }); + // } + // } else { this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); } From 086ffce57508ca5b5cbe50b1ac4bab5e34c0869b Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 01:32:41 +0530 Subject: [PATCH 25/37] feat(temp): -rm setGetListAuto socket events --- src/context/browserActions.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/context/browserActions.tsx b/src/context/browserActions.tsx index f0619ac5b..17b43cb1d 100644 --- a/src/context/browserActions.tsx +++ b/src/context/browserActions.tsx @@ -88,13 +88,13 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { const startGetListAuto = () => { setGetListAuto(true); - socket?.emit('setGetListAuto', { getListAuto: true }); + // socket?.emit('setGetListAuto', { getListAuto: true }); setCaptureStage('initial'); }; const stopGetListAuto = () => { setGetListAuto(false); - socket?.emit('setGetListAuto', { getListAuto: false }); + // socket?.emit('setGetListAuto', { getListAuto: false }); setPaginationType(''); setLimitType(''); setCustomLimit(''); From 3daa8909f29d36d1fcc142798924e8b69219caf4 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 01:58:50 +0530 Subject: [PATCH 26/37] chore: lint --- src/components/organisms/BrowserWindow.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 0113ae83b..4597e4f28 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -91,10 +91,10 @@ export const BrowserWindow = () => { }, []); useEffect(() => { - if (!getList) { + if (!getList || !getListAuto) { resetListState(); } - }, [getList, resetListState]); + }, [getList, getListAuto, resetListState]); const screencastHandler = useCallback((data: string) => { setScreenShot(data); @@ -114,7 +114,7 @@ export const BrowserWindow = () => { } }, [screenShot, canvasRef, socket, screencastHandler]); - const highlighterHandler = useCallback((data: { rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[], childData?: string[] }) => { + const highlighterHandler = useCallback((data: { rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[], childData?: string[]}) => { if (getList === true || getListAuto === true) { if (listSelector) { socket?.emit('listSelector', { selector: listSelector }); @@ -144,7 +144,6 @@ export const BrowserWindow = () => { } }, [highlighterData, getList, getListAuto, socket, listSelector, paginationMode, paginationType]); - useEffect(() => { document.addEventListener('mousemove', onMouseMove, false); if (socket) { From a5455a62fd2e7d63309d4c7d69712e134b6c70b9 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 01:59:49 +0530 Subject: [PATCH 27/37] feat: remove attribute selection for getListAuto --- src/components/organisms/BrowserWindow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 4597e4f28..524121088 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -276,7 +276,7 @@ export const BrowserWindow = () => { attribute: attribute }); } - if ((getList === true || getListAuto === true) && listSelector && currentListId) { + if ((getList === true) && listSelector && currentListId) { const newField: TextStep = { id: Date.now(), type: 'text', From 7b6b4eeb8575278eb3dabb530550383e66e3cbce Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 02:00:23 +0530 Subject: [PATCH 28/37] feat: remove getListAuto condition for generic modal --- src/components/organisms/BrowserWindow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 524121088..ebadf2f5a 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -320,7 +320,7 @@ export const BrowserWindow = () => { return (
{ - getText === true || getList === true || getListAuto === true ? ( + getText === true || getList === true ? ( { }} From 2d8ab35056864467154ca350ea75656698bc3a7e Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 03:22:20 +0530 Subject: [PATCH 29/37] feat: emit socket event for getList --- src/context/browserActions.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/context/browserActions.tsx b/src/context/browserActions.tsx index 17b43cb1d..29b1b8aa5 100644 --- a/src/context/browserActions.tsx +++ b/src/context/browserActions.tsx @@ -48,6 +48,8 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { const [customLimit, setCustomLimit] = useState(''); const [captureStage, setCaptureStage] = useState('initial'); + console.log(`get list auto value: ${getListAuto}`) + const { socket } = useSocketStore(); const updatePaginationType = (type: PaginationType) => setPaginationType(type); @@ -89,12 +91,14 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { const startGetListAuto = () => { setGetListAuto(true); // socket?.emit('setGetListAuto', { getListAuto: true }); + socket?.emit('setGetList', { getList: true }); setCaptureStage('initial'); }; const stopGetListAuto = () => { setGetListAuto(false); // socket?.emit('setGetListAuto', { getListAuto: false }); + socket?.emit('setGetList', { getList: true }); setPaginationType(''); setLimitType(''); setCustomLimit(''); From 0a0e27e38402c6c1197786e300862e21719b5414 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 03:23:17 +0530 Subject: [PATCH 30/37] feat: auto populate getListAuto fields --- src/components/organisms/BrowserWindow.tsx | 110 ++++++++++++++------- 1 file changed, 76 insertions(+), 34 deletions(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index ebadf2f5a..60ff2bfd7 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -25,6 +25,12 @@ interface AttributeOption { value: string; } +interface ChildData { + data: string; + selector: string; + importance?: number; // Add other properties if needed +} + const getAttributeOptions = (tagName: string, elementInfo: ElementInfo | null): AttributeOption[] => { if (!elementInfo) return []; switch (tagName.toLowerCase()) { @@ -54,7 +60,7 @@ const getAttributeOptions = (tagName: string, elementInfo: ElementInfo | null): export const BrowserWindow = () => { const [canvasRef, setCanvasReference] = useState | undefined>(undefined); const [screenShot, setScreenShot] = useState(""); - const [highlighterData, setHighlighterData] = useState<{ rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[], childData?: string[] } | null>(null); + const [highlighterData, setHighlighterData] = useState<{ rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[], childData?: ChildData[] } | null>(null); const [showAttributeModal, setShowAttributeModal] = useState(false); const [attributeOptions, setAttributeOptions] = useState([]); const [selectedElement, setSelectedElement] = useState<{ selector: string, info: ElementInfo | null } | null>(null); @@ -114,7 +120,7 @@ export const BrowserWindow = () => { } }, [screenShot, canvasRef, socket, screencastHandler]); - const highlighterHandler = useCallback((data: { rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[], childData?: string[]}) => { + const highlighterHandler = useCallback((data: { rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[], childData?: ChildData[] }) => { if (getList === true || getListAuto === true) { if (listSelector) { socket?.emit('listSelector', { selector: listSelector }); @@ -211,46 +217,82 @@ export const BrowserWindow = () => { setCurrentListId(Date.now()); setFields({}); } else if ((getList === true || getListAuto === true) && listSelector && currentListId) { - const attribute = options[0].value; - const data = attribute === 'href' ? highlighterData.elementInfo?.url || '' : - attribute === 'src' ? highlighterData.elementInfo?.imageUrl || '' : - highlighterData.elementInfo?.innerText || ''; - // Add fields to the list - if (options.length === 1) { - const attribute = options[0].value; - const newField: TextStep = { - id: Date.now(), - type: 'text', - label: `Label ${Object.keys(fields).length + 1}`, - data: data, - selectorObj: { - selector: highlighterData.selector, - tag: highlighterData.elementInfo?.tagName, - attribute - } - }; - - setFields(prevFields => { - const updatedFields = { - ...prevFields, - [newField.id]: newField + if (getListAuto && highlighterData.childData) { + // Automatically populate fields with childData + const newFields: Record = {}; // To hold all the new fields + highlighterData.childData.forEach(child => { + const newField: TextStep = { + id: Date.now() + Math.random(), // Unique ID for each field + type: 'text', + label: `Label ${Object.keys(fields).length + 1}`, + data: child.data, // Populate with child data + selectorObj: { + selector: child.selector, // Child selector + tag: '', // Empty tag + attribute: '' // Empty attribute + } }; - return updatedFields; + + console.log(`New field: ${newField}`) + + // Add newField to the newFields object + newFields[newField.id] = newField; }); + + // Update the fields state + setFields(prevFields => ({ + ...prevFields, + ...newFields + })); + console.log('Fields:', fields); + if (listSelector) { - addListStep(listSelector, { ...fields, [newField.id]: newField }, currentListId, { type: '', selector: paginationSelector }); + // Add the fields to the list step + addListStep(listSelector, { ...fields, ...newFields }, currentListId, { type: '', selector: paginationSelector }); } - } else { - setAttributeOptions(options); - setSelectedElement({ - selector: highlighterData.selector, - info: highlighterData.elementInfo - }); - setShowAttributeModal(true); + const attribute = options[0].value; + const data = + attribute === 'href' + ? highlighterData.elementInfo?.url || '' + : attribute === 'src' + ? highlighterData.elementInfo?.imageUrl || '' + : highlighterData.elementInfo?.innerText || ''; + + // Add single field to the list + if (options.length === 1) { + const newField: TextStep = { + id: Date.now(), + type: 'text', + label: `Label ${Object.keys(fields).length + 1}`, + data: data, + selectorObj: { + selector: highlighterData.selector, + tag: highlighterData.elementInfo?.tagName, + attribute + } + }; + + setFields(prevFields => ({ + ...prevFields, + [newField.id]: newField + })); + + if (listSelector) { + addListStep(listSelector, { ...fields, [newField.id]: newField }, currentListId, { type: '', selector: paginationSelector }); + } + } else { + setAttributeOptions(options); + setSelectedElement({ + selector: highlighterData.selector, + info: highlighterData.elementInfo + }); + setShowAttributeModal(true); + } } } + } } }; From e18968584ff8daa0c71115280c530a9c7530c092 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 03:46:03 +0530 Subject: [PATCH 31/37] feat: auto populate getListAuto fields --- src/components/organisms/BrowserWindow.tsx | 96 ++++++++++++++-------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 60ff2bfd7..d7231347f 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -216,42 +216,44 @@ export const BrowserWindow = () => { notify(`info`, `List selected succesfully. Select the text data for extraction.`) setCurrentListId(Date.now()); setFields({}); - } else if ((getList === true || getListAuto === true) && listSelector && currentListId) { - if (getListAuto && highlighterData.childData) { - // Automatically populate fields with childData - const newFields: Record = {}; // To hold all the new fields - highlighterData.childData.forEach(child => { - const newField: TextStep = { - id: Date.now() + Math.random(), // Unique ID for each field - type: 'text', - label: `Label ${Object.keys(fields).length + 1}`, - data: child.data, // Populate with child data - selectorObj: { - selector: child.selector, // Child selector - tag: '', // Empty tag - attribute: '' // Empty attribute - } - }; - - console.log(`New field: ${newField}`) + } else if ((getList === true) && listSelector && currentListId) { + // if (getListAuto && highlighterData.childData) { + // // Automatically populate fields with childData + // const newFields: Record = {}; // To hold all the new fields + // highlighterData.childData.forEach(child => { + // const newField: TextStep = { + // id: Date.now() + Math.random(), // Unique ID for each field + // type: 'text', + // label: `Label ${Object.keys(fields).length + 1}`, + // data: child.data, // Populate with child data + // selectorObj: { + // selector: child.selector, // Child selector + // tag: '', // Empty tag + // attribute: '' // Empty attribute + // } + // }; + + // console.log(`New field: ${newField}`) - // Add newField to the newFields object - newFields[newField.id] = newField; - }); + // // Add newField to the newFields object + // newFields[newField.id] = newField; + // }); - // Update the fields state - setFields(prevFields => ({ - ...prevFields, - ...newFields - })); + // // Update the fields state + // setFields(prevFields => ({ + // ...prevFields, + // ...newFields + // })); - console.log('Fields:', fields); + // console.log('Fields:', fields); - if (listSelector) { - // Add the fields to the list step - addListStep(listSelector, { ...fields, ...newFields }, currentListId, { type: '', selector: paginationSelector }); - } - } else { + // if (listSelector) { + // // Add the fields to the list step + // addListStep(listSelector, { ...fields, ...newFields }, currentListId, { type: '', selector: paginationSelector }); + // } + // } + + // else { const attribute = options[0].value; const data = attribute === 'href' @@ -290,7 +292,7 @@ export const BrowserWindow = () => { }); setShowAttributeModal(true); } - } + // } } } @@ -358,6 +360,34 @@ export const BrowserWindow = () => { } }, [paginationMode, resetPaginationSelector]); + useEffect(() => { + // Automatically populate fields when listSelector is set + if (listSelector && getListAuto && highlighterData?.childData && currentListId) { + const newFields: Record = {}; + highlighterData.childData.forEach(child => { + const newField: TextStep = { + id: Date.now(), + type: 'text', + label: `Label ${Object.keys(fields).length + 1}`, + data: child.data, + selectorObj: { + selector: child.selector, + tag: '', + attribute: '' + } + }; + + newFields[newField.id] = newField; + }); + + setFields(prevFields => ({ ...prevFields, ...newFields })); + + if (listSelector) { + addListStep(listSelector, { ...fields, ...newFields }, currentListId, { type: '', selector: paginationSelector }); + } + } + }, [listSelector, highlighterData?.childData, getListAuto]); + return (
From fcc0a13da30d9f4360ff323b75b272335e0b70c6 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 04:08:21 +0530 Subject: [PATCH 32/37] feat: return attribute and tag in auto extract --- server/src/workflow-management/selector.ts | 77 +++++++++++----------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 1eec305db..63312e033 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -793,7 +793,7 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates export const extractChildData = async ( page: Page, parentSelector: string -): Promise<{ data: string; selector: string }[]> => { +): Promise<{ data: string; selector: string; attribute: string; tag: string }[]> => { try { const baseURL = new URL(page.url()); @@ -801,7 +801,9 @@ export const extractChildData = async ( interface ElementData { data: string; selector: string; - importance: number; + attribute: string; // New field for the attribute + tag: string; // New field for the tag name + importance: number; // Used internally but excluded in the return } function getNonUniqueSelector(element: HTMLElement): string { @@ -821,69 +823,64 @@ export const extractChildData = async ( return selector; } - // Function to determine robustness of a selector - function selectorRobustness(selector: string): number { - // A simple metric: robustness is based on the number of parts in the selector - return selector.split(/[#.:\[\]]/).length; - } - function determineImportance(element: HTMLElement): number { if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(element.tagName.toLowerCase())) { return 2; } else if (['p', 'span', 'a', 'img'].includes(element.tagName.toLowerCase())) { return 1; } - return 0; // Importance for all other tags + return 0; } function cleanText(text: string): string { return text.replace(/\s+/g, ' ').trim(); } + function resolveURL(url: string | null, baseHref: string): string | null { + if (!url) return null; + try { + return new URL(url, baseHref).href; + } catch { + return url; + } + } + function extractElementData(element: HTMLElement, baseHref: string): ElementData[] { const selector = getNonUniqueSelector(element); const importance = determineImportance(element); + const tag = element.tagName.toLowerCase(); const results: ElementData[] = []; - - function resolveURL(url: string | null): string | null { - if (!url) return null; - try { - return new URL(url, baseHref).href; - } catch { - return url; - } - } - + // Include text content if importance is sufficient if (importance >= 1) { const textContent = cleanText(element.textContent || ''); if (textContent) { - results.push({ data: textContent, selector, importance }); + results.push({ data: textContent, selector, attribute: 'innerText', tag, importance }); } } - + // Handle links (a tags) - if (element.tagName.toLowerCase() === 'a') { + if (tag === 'a') { const href = element.getAttribute('href'); if (href) { - const resolvedHref = resolveURL(href); + const resolvedHref = resolveURL(href, baseHref); if (resolvedHref) { - results.push({ data: resolvedHref, selector, importance }); + results.push({ data: resolvedHref, selector, attribute: 'href', tag, importance }); } } } - + // Handle images (img tags) - if (element.tagName.toLowerCase() === 'img') { + if (tag === 'img') { const src = element.getAttribute('src'); if (src) { - const resolvedSrc = resolveURL(src); + const resolvedSrc = resolveURL(src, baseHref); if (resolvedSrc) { - results.push({ data: resolvedSrc, selector, importance }); + results.push({ data: resolvedSrc, selector, attribute: 'src', tag, importance }); } } } - + return results; } @@ -904,31 +901,33 @@ export const extractChildData = async ( const allData = getAllDescendantData(parentElement); - // Deduplicate and prioritize by importance and robustness + // Deduplicate and prioritize by importance const uniqueData = Array.from( allData.reduce((map, item) => { if (!map.has(item.data)) { map.set(item.data, item); // Add new data } else { const existing = map.get(item.data); - if ( - existing && item.importance > existing.importance || // Prefer higher importance - (existing && item.importance === existing.importance && - selectorRobustness(item.selector) > selectorRobustness(existing.selector)) // If equal importance, prefer robust selector - ) { - map.set(item.data, item); // Replace with better element + if (existing && item.importance > existing.importance) { + map.set(item.data, item); // Replace with higher importance } } return map; }, new Map()) ); - return uniqueData; + // Remove importance field before returning + return uniqueData.map(([_, item]) => ({ + data: item.data, + selector: item.selector, + attribute: item.attribute, + tag: item.tag, + })); }, { parentSelector, baseHref: baseURL.href }); - return uniqueData.map(([_, item]) => item) || []; + return uniqueData; } catch (error) { - console.error('Error in extractRelevantData:', error); + console.error('Error in extractChildData:', error); return []; } }; From a30c3f262f3c957eab5ae4bad0ea46d8efc6ef64 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 08:22:43 +0530 Subject: [PATCH 33/37] feat: auto extract UI --- src/components/organisms/BrowserWindow.tsx | 271 ++++++++++++--------- 1 file changed, 157 insertions(+), 114 deletions(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index d7231347f..947cb0059 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState, useRef } from 'react'; import { useSocketStore } from '../../context/socket'; import { Button } from '@mui/material'; import Canvas from "../atoms/canvas"; @@ -62,6 +62,7 @@ export const BrowserWindow = () => { const [screenShot, setScreenShot] = useState(""); const [highlighterData, setHighlighterData] = useState<{ rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[], childData?: ChildData[] } | null>(null); const [showAttributeModal, setShowAttributeModal] = useState(false); + const [showAutoExtractModal, setShowAutoExtractModal] = useState(false); const [attributeOptions, setAttributeOptions] = useState([]); const [selectedElement, setSelectedElement] = useState<{ selector: string, info: ElementInfo | null } | null>(null); const [currentListId, setCurrentListId] = useState(null); @@ -70,6 +71,8 @@ export const BrowserWindow = () => { const [fields, setFields] = useState>({}); const [paginationSelector, setPaginationSelector] = useState(''); + const listSelectorRef = useRef(null); + const { socket } = useSocketStore(); const { notify } = useGlobalInfoStore(); const { getText, getList, getListAuto, paginationMode, paginationType, limitMode } = useActionContext(); @@ -92,6 +95,7 @@ export const BrowserWindow = () => { const resetListState = useCallback(() => { setListSelector(null); + listSelectorRef.current = null; setFields({}); setCurrentListId(null); }, []); @@ -122,33 +126,40 @@ export const BrowserWindow = () => { const highlighterHandler = useCallback((data: { rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[], childData?: ChildData[] }) => { if (getList === true || getListAuto === true) { - if (listSelector) { - socket?.emit('listSelector', { selector: listSelector }); + if (listSelectorRef.current) { + socket?.emit('listSelector', { selector: listSelectorRef.current }); + if (limitMode) { setHighlighterData(null); } else if (paginationMode) { - // only set highlighterData if type is not empty, 'none', 'scrollDown', or 'scrollUp' + // Only set highlighterData if type is valid for pagination if (paginationType !== '' && !['none', 'scrollDown', 'scrollUp'].includes(paginationType)) { setHighlighterData(data); } else { setHighlighterData(null); } - } else if (data.childSelectors && data.childSelectors.includes(data.selector)) { - // highlight only valid child elements within the listSelector + } else if (getList && data.childSelectors && data.childSelectors.includes(data.selector)) { + // For `getList`, highlight only valid child elements within the listSelector + setHighlighterData(data); + } else if (getListAuto && data.childData) { + // For `getListAuto`, set highlighterData if childData is present + //onst { childSelectors, ...rest } = data; setHighlighterData(data); } else { - // if !valid child in normal mode, clear the highlighter + // Clear the highlighter if not valid setHighlighterData(null); } } else { - // set highlighterData for the initial listSelector selection + // Set highlighterData for the initial listSelector selection setHighlighterData(data); } } else { - // for non-list steps + // For non-list steps setHighlighterData(data); } }, [highlighterData, getList, getListAuto, socket, listSelector, paginationMode, paginationType]); + + useEffect(() => { document.addEventListener('mousemove', onMouseMove, false); @@ -211,94 +222,109 @@ export const BrowserWindow = () => { return; } - if ((getList === true || getListAuto === true) && !listSelector) { - setListSelector(highlighterData.selector); - notify(`info`, `List selected succesfully. Select the text data for extraction.`) + if ((getList === true || getListAuto === true) && !listSelectorRef.current) { + // Set listSelectorRef and state + const newSelector = highlighterData.selector; + listSelectorRef.current = newSelector; + setListSelector(newSelector); + notify(`info`, `List selected successfully. Proceed to extract data.`); + setCurrentListId(Date.now()); setFields({}); - } else if ((getList === true) && listSelector && currentListId) { - // if (getListAuto && highlighterData.childData) { - // // Automatically populate fields with childData - // const newFields: Record = {}; // To hold all the new fields - // highlighterData.childData.forEach(child => { - // const newField: TextStep = { - // id: Date.now() + Math.random(), // Unique ID for each field - // type: 'text', - // label: `Label ${Object.keys(fields).length + 1}`, - // data: child.data, // Populate with child data - // selectorObj: { - // selector: child.selector, // Child selector - // tag: '', // Empty tag - // attribute: '' // Empty attribute - // } - // }; - - // console.log(`New field: ${newField}`) - - // // Add newField to the newFields object - // newFields[newField.id] = newField; - // }); - // // Update the fields state - // setFields(prevFields => ({ - // ...prevFields, - // ...newFields - // })); - - // console.log('Fields:', fields); - - // if (listSelector) { - // // Add the fields to the list step - // addListStep(listSelector, { ...fields, ...newFields }, currentListId, { type: '', selector: paginationSelector }); - // } - // } - - // else { - const attribute = options[0].value; - const data = - attribute === 'href' - ? highlighterData.elementInfo?.url || '' - : attribute === 'src' - ? highlighterData.elementInfo?.imageUrl || '' - : highlighterData.elementInfo?.innerText || ''; - - // Add single field to the list - if (options.length === 1) { - const newField: TextStep = { - id: Date.now(), - type: 'text', - label: `Label ${Object.keys(fields).length + 1}`, - data: data, - selectorObj: { - selector: highlighterData.selector, - tag: highlighterData.elementInfo?.tagName, - attribute - } - }; - - setFields(prevFields => ({ - ...prevFields, - [newField.id]: newField - })); - - if (listSelector) { - addListStep(listSelector, { ...fields, [newField.id]: newField }, currentListId, { type: '', selector: paginationSelector }); - } - } else { - setAttributeOptions(options); - setSelectedElement({ - selector: highlighterData.selector, - info: highlighterData.elementInfo - }); - setShowAttributeModal(true); - } + // Automatically handle field population for getListAuto mode + // if (getListAuto === true && highlighterData.childData) { + // handleAutoFieldPopulation(highlighterData.childData, newSelector); // } + } else if ((getList === true || getListAuto === true) && listSelectorRef.current && currentListId) { + if (getListAuto === true && highlighterData.childData) { + handleAutoFieldPopulation(highlighterData.childData, listSelectorRef.current); + } else { + handleManualFieldPopulation(options, highlighterData, listSelectorRef.current); + } } + } } }; + const handleAutoFieldPopulation = (childData: Array, listSelector: string) => { + setShowAutoExtractModal(true); + setTimeout(() => { + setShowAutoExtractModal(false); + }, 3000); + + const newFields: Record = {}; + childData.forEach((child, index) => { + const newField: TextStep = { + id: Date.now() + index, + type: 'text', + label: `Label ${index + 1}`, + data: child.data, + selectorObj: { + selector: child.selector, + tag: '', // Can be updated if necessary + attribute: '', // Can be updated if necessary + }, + }; + + newFields[newField.id] = newField; + }); + + setFields(prevFields => ({ + ...prevFields, + ...newFields, + })); + + if (listSelectorRef.current) { + addListStep(listSelectorRef.current, newFields, currentListId || Date.now(), { type: '', selector: paginationSelector }); + } + }; + + const handleManualFieldPopulation = ( + options: AttributeOption[], + highlighterData: any, + listSelector: string + ) => { + const attribute = options[0].value; + const data = + attribute === 'href' + ? highlighterData.elementInfo?.url || '' + : attribute === 'src' + ? highlighterData.elementInfo?.imageUrl || '' + : highlighterData.elementInfo?.innerText || ''; + + if (options.length === 1) { + const newField: TextStep = { + id: Date.now(), + type: 'text', + label: `Label ${Object.keys(fields).length + 1}`, + data: data, + selectorObj: { + selector: highlighterData.selector, + tag: highlighterData.elementInfo?.tagName, + attribute, + }, + }; + + setFields(prevFields => ({ + ...prevFields, + [newField.id]: newField, + })); + + addListStep(listSelector, { ...fields, [newField.id]: newField }, currentListId || Date.now(), { type: '', selector: paginationSelector }); + } else { + setAttributeOptions(options); + setSelectedElement({ + selector: highlighterData.selector, + info: highlighterData.elementInfo, + }); + setShowAttributeModal(true); + } + }; + + const handleAttributeSelection = (attribute: string) => { if (selectedElement) { let data = ''; @@ -360,37 +386,54 @@ export const BrowserWindow = () => { } }, [paginationMode, resetPaginationSelector]); - useEffect(() => { - // Automatically populate fields when listSelector is set - if (listSelector && getListAuto && highlighterData?.childData && currentListId) { - const newFields: Record = {}; - highlighterData.childData.forEach(child => { - const newField: TextStep = { - id: Date.now(), - type: 'text', - label: `Label ${Object.keys(fields).length + 1}`, - data: child.data, - selectorObj: { - selector: child.selector, - tag: '', - attribute: '' - } - }; - - newFields[newField.id] = newField; - }); - - setFields(prevFields => ({ ...prevFields, ...newFields })); - - if (listSelector) { - addListStep(listSelector, { ...fields, ...newFields }, currentListId, { type: '', selector: paginationSelector }); - } - } - }, [listSelector, highlighterData?.childData, getListAuto]); + // useEffect(() => { + // // Automatically populate fields when listSelector and childData are available + // if (listSelector && getListAuto && highlighterData?.childData && currentListId) { + // notify(`info`, `Auto extracting data...`); + + // const newFields: Record = {}; + // highlighterData.childData.forEach(child => { + // const newField: TextStep = { + // id: Date.now(), + // type: 'text', + // label: `Label ${Object.keys(fields).length + 1}`, + // data: child.data, + // selectorObj: { + // selector: child.selector, + // tag: '', + // attribute: '' + // } + // }; + + // newFields[newField.id] = newField; + // }); + + // setFields(prevFields => ({ ...prevFields, ...newFields })); + + // if (listSelector) { + // addListStep(listSelector, { ...fields, ...newFields }, currentListId, { type: '', selector: paginationSelector }); + // } + // } + // }, [listSelector, getListAuto, highlighterData?.childData, currentListId, fields]); + return (
+ {getListAuto === true ? ( + { }} + canBeClosed={false} + modalStyle={modalStyle} + > +
+

Auto Extracting

+

Most useful data, please wait for a few seconds...

+

Found {highlighterData?.childData?.length} items...

+
+
+ ): null} { getText === true || getList === true ? ( Date: Fri, 22 Nov 2024 09:50:03 +0530 Subject: [PATCH 34/37] wip: error handling --- src/components/organisms/BrowserWindow.tsx | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 947cb0059..bb9863742 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -66,6 +66,7 @@ export const BrowserWindow = () => { const [attributeOptions, setAttributeOptions] = useState([]); const [selectedElement, setSelectedElement] = useState<{ selector: string, info: ElementInfo | null } | null>(null); const [currentListId, setCurrentListId] = useState(null); + const [isChildDataAvailable, setIsChildDataAvailable] = useState(false); const [listSelector, setListSelector] = useState(null); const [fields, setFields] = useState>({}); @@ -144,7 +145,8 @@ export const BrowserWindow = () => { } else if (getListAuto && data.childData) { // For `getListAuto`, set highlighterData if childData is present //onst { childSelectors, ...rest } = data; - setHighlighterData(data); + setHighlighterData({ rect: data.rect, selector: data.selector, elementInfo: data.elementInfo, childData: data.childData }); + setIsChildDataAvailable(true); } else { // Clear the highlighter if not valid setHighlighterData(null); @@ -159,7 +161,8 @@ export const BrowserWindow = () => { } }, [highlighterData, getList, getListAuto, socket, listSelector, paginationMode, paginationType]); - + // console.log('highlighterData', highlighterData); + console.log('is child data available', isChildDataAvailable); useEffect(() => { document.addEventListener('mousemove', onMouseMove, false); @@ -217,7 +220,7 @@ export const BrowserWindow = () => { if (paginationType !== '' && paginationType !== 'scrollDown' && paginationType !== 'scrollUp' && paginationType !== 'none') { setPaginationSelector(highlighterData.selector); notify(`info`, `Pagination element selected successfully.`); - addListStep(listSelector!, fields, currentListId || 0, { type: paginationType, selector: highlighterData.selector }); + addListStep(listSelectorRef.current!, fields, currentListId || 0, { type: paginationType, selector: highlighterData.selector }); } return; } @@ -231,6 +234,15 @@ export const BrowserWindow = () => { setCurrentListId(Date.now()); setFields({}); + + if (getListAuto) { + notify(`info`, `List container selected! Now click on any item inside the list to auto-extract all similar items.`); + if (highlighterData.childData) { + handleAutoFieldPopulation(highlighterData.childData, newSelector); + } else { + notify(`error`, `No child data found for auto-extraction.`); + } + } // Automatically handle field population for getListAuto mode // if (getListAuto === true && highlighterData.childData) { @@ -277,9 +289,7 @@ export const BrowserWindow = () => { ...newFields, })); - if (listSelectorRef.current) { - addListStep(listSelectorRef.current, newFields, currentListId || Date.now(), { type: '', selector: paginationSelector }); - } + addListStep(listSelector, newFields, currentListId || Date.now(), { type: '', selector: paginationSelector }); }; const handleManualFieldPopulation = ( From 2400aa332eae20186b006fff390bf4c25b25cfdf Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 09:51:03 +0530 Subject: [PATCH 35/37] feat: use child attribute & tag --- src/components/organisms/BrowserWindow.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index bb9863742..b5aca1a85 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -276,8 +276,8 @@ export const BrowserWindow = () => { data: child.data, selectorObj: { selector: child.selector, - tag: '', // Can be updated if necessary - attribute: '', // Can be updated if necessary + tag: child.tag, + attribute: child.attribute }, }; From b92cfbfbae182ffa9d45f7fb7134a6deff15f433 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 18:37:44 +0530 Subject: [PATCH 36/37] fix: resolve merge conflicts --- src/components/molecules/RecordingsTable.tsx | 107 ++++++++++--------- src/components/molecules/RobotSettings.tsx | 11 ++ src/components/molecules/RunContent.tsx | 16 ++- src/components/molecules/RunsTable.tsx | 48 +++++++-- 4 files changed, 114 insertions(+), 68 deletions(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index a3a6ee08a..09ee22394 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -9,8 +9,12 @@ import TablePagination from '@mui/material/TablePagination'; import TableRow from '@mui/material/TableRow'; import { useEffect } from "react"; import { WorkflowFile } from "maxun-core"; + + +import SearchIcon from '@mui/icons-material/Search'; import { IconButton, Button, Box, Typography, TextField, MenuItem, Menu, ListItemIcon, ListItemText } from "@mui/material"; import { Schedule, DeleteForever, Edit, PlayCircle, Settings, Power, ContentCopy, } from "@mui/icons-material"; + import LinkIcon from '@mui/icons-material/Link'; import { useGlobalInfoStore } from "../../context/globalInfo"; import { checkRunsForRecording, deleteRecordingFromStorage, getStoredRecordings } from "../../api/storage"; @@ -18,10 +22,12 @@ import { Add } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { stopRecording } from "../../api/recording"; import { GenericModal } from '../atoms/GenericModal'; + import axios from 'axios'; import { apiUrl } from '../../apiConfig'; import { Menu as MenuIcon } from '@mui/icons-material'; + /** TODO: * 1. allow editing existing robot after persisting browser steps * 2. show robot settings: id, url, etc. @@ -38,17 +44,6 @@ interface Column { const columns: readonly Column[] = [ { id: 'interpret', label: 'Run', minWidth: 80 }, { id: 'name', label: 'Name', minWidth: 80 }, - // { - // id: 'createdAt', - // label: 'Created at', - // minWidth: 80, - // //format: (value: string) => value.toLocaleString('en-US'), - // }, - // { - // id: 'edit', - // label: 'Edit', - // minWidth: 80, - // }, { id: 'schedule', label: 'Schedule', @@ -59,12 +54,6 @@ const columns: readonly Column[] = [ label: 'Integrate', minWidth: 80, }, - // { - // id: 'updatedAt', - // label: 'Updated at', - // minWidth: 80, - // //format: (value: string) => value.toLocaleString('en-US'), - // }, { id: 'settings', label: 'Settings', @@ -101,6 +90,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl const [rowsPerPage, setRowsPerPage] = React.useState(10); const [rows, setRows] = React.useState([]); const [isModalOpen, setModalOpen] = React.useState(false); + const [searchTerm, setSearchTerm] = React.useState(''); const { notify, setRecordings, browserId, setBrowserId, recordingUrl, setRecordingUrl, recordingName, setRecordingName, recordingId, setRecordingId } = useGlobalInfoStore(); const navigate = useNavigate(); @@ -114,6 +104,11 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl setPage(0); }; + const handleSearchChange = (event: React.ChangeEvent) => { + setSearchTerm(event.target.value); + setPage(0); + }; + const fetchRecordings = async () => { const recordings = await getStoredRecordings(); if (recordings) { @@ -152,7 +147,6 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl const startRecording = () => { setModalOpen(false); handleStartRecording(); - // notify('info', 'New Recording started for ' + recordingUrl); }; useEffect(() => { @@ -161,36 +155,54 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl } }, []); + + // Filter rows based on search term + const filteredRows = rows.filter((row) => + row.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + return ( My Robots - - Create Robot - + + + }} + sx={{ width: '250px' }} + /> + + Create Robot + + @@ -208,7 +220,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl - {rows.length !== 0 ? rows + {filteredRows.length !== 0 ? filteredRows .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) .map((row) => { return ( @@ -230,16 +242,6 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl handleRunRecording(row.id, row.name, row.params || [])} /> ); - // case 'edit': - // return ( - // - // { - // handleEditRecording(row.id, row.name); - // }} sx={{ '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}> - // - // - // - // ); case 'schedule': return ( @@ -300,7 +302,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl { ) } - interface ScheduleButtonProps { handleSchedule: () => void; } diff --git a/src/components/molecules/RobotSettings.tsx b/src/components/molecules/RobotSettings.tsx index ffb7a2d09..53d21d7bb 100644 --- a/src/components/molecules/RobotSettings.tsx +++ b/src/components/molecules/RobotSettings.tsx @@ -120,6 +120,17 @@ export const RobotSettingsModal = ({ isOpen, handleStart, handleClose, initialSe }} style={{ marginBottom: '20px' }} /> + {robot.recording.workflow?.[0]?.what?.[0]?.args?.[0]?.limit !== undefined && ( + + )} 0) { - setColumns(Object.keys(data[0])); + // Filter out completely empty rows + const filteredData = data.filter(row => + Object.values(row).some(value => value !== undefined && value !== "") + ); + setTableData(filteredData); + if (filteredData.length > 0) { + setColumns(Object.keys(filteredData[0])); } } } }, [row.serializableOutput]); + // Function to convert table data to CSV format const convertToCSV = (data: any[], columns: string[]): string => { const header = columns.join(','); @@ -53,7 +58,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe return [header, ...rows].join('\n'); }; - // Function to download CSV file when called const downloadCSV = () => { const csvContent = convertToCSV(tableData, columns); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); @@ -140,7 +144,9 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {tableData.map((row, index) => ( {columns.map((column) => ( - {row[column]} + + {row[column] === undefined || row[column] === "" ? "-" : row[column]} + ))} ))} diff --git a/src/components/molecules/RunsTable.tsx b/src/components/molecules/RunsTable.tsx index dcb0cdd15..669cecd61 100644 --- a/src/components/molecules/RunsTable.tsx +++ b/src/components/molecules/RunsTable.tsx @@ -12,8 +12,9 @@ import { useGlobalInfoStore } from "../../context/globalInfo"; import { getStoredRuns } from "../../api/storage"; import { RunSettings } from "./RunSettings"; import { CollapsibleRow } from "./ColapsibleRow"; -import { Accordion, AccordionSummary, AccordionDetails, Typography } from '@mui/material'; +import { Accordion, AccordionSummary, AccordionDetails, Typography, Box, TextField } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import SearchIcon from '@mui/icons-material/Search'; interface Column { id: 'runStatus' | 'name' | 'startedAt' | 'finishedAt' | 'delete' | 'settings'; @@ -28,7 +29,6 @@ export const columns: readonly Column[] = [ { id: 'name', label: 'Robot Name', minWidth: 80 }, { id: 'startedAt', label: 'Started at', minWidth: 80 }, { id: 'finishedAt', label: 'Finished at', minWidth: 80 }, - // { id: 'task', label: 'Task', minWidth: 80 }, { id: 'settings', label: 'Settings', minWidth: 80 }, { id: 'delete', label: 'Delete', minWidth: 80 }, ]; @@ -42,7 +42,6 @@ export interface Data { runByUserId?: string; runByScheduleId?: string; runByAPI?: boolean; - // task: string; log: string; runId: string; robotId: string; @@ -65,6 +64,9 @@ export const RunsTable = ( const [rowsPerPage, setRowsPerPage] = useState(10); const [rows, setRows] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); + + const { notify, rerenderRuns, setRerenderRuns } = useGlobalInfoStore(); const handleChangePage = (event: unknown, newPage: number) => { @@ -76,6 +78,11 @@ export const RunsTable = ( setPage(0); }; + const handleSearchChange = (event: React.ChangeEvent) => { + setSearchTerm(event.target.value); + setPage(0); + }; + const fetchRuns = async () => { const runs = await getStoredRuns(); if (runs) { @@ -105,8 +112,15 @@ export const RunsTable = ( fetchRuns(); }; - // Group runs by robot meta id - const groupedRows = rows.reduce((acc, row) => { + + // Filter rows based on search term + const filteredRows = rows.filter((row) => + row.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + // Group filtered rows by robot meta id + const groupedRows = filteredRows.reduce((acc, row) => { + if (!acc[row.robotMetaId]) { acc[row.robotMetaId] = []; } @@ -116,14 +130,28 @@ export const RunsTable = ( return ( - - All Runs - + + + All Runs + + + }} + sx={{ width: '250px' }} + /> + {Object.entries(groupedRows).map(([id, data]) => ( }> - {data[data.length - 1].name} + + {data[data.length - 1].name} +
@@ -164,7 +192,7 @@ export const RunsTable = ( Date: Fri, 22 Nov 2024 18:38:58 +0530 Subject: [PATCH 37/37] fix: resolve merge conflicts --- src/context/browserActions.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/context/browserActions.tsx b/src/context/browserActions.tsx index 29b1b8aa5..1698845b1 100644 --- a/src/context/browserActions.tsx +++ b/src/context/browserActions.tsx @@ -48,8 +48,6 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { const [customLimit, setCustomLimit] = useState(''); const [captureStage, setCaptureStage] = useState('initial'); - console.log(`get list auto value: ${getListAuto}`) - const { socket } = useSocketStore(); const updatePaginationType = (type: PaginationType) => setPaginationType(type);