Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reset remote browser recording state #314

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
11 changes: 8 additions & 3 deletions public/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
"confirm_limit": "Bestätigen",
"finish_capture": "Erfassung abschließen",
"back": "Zurück",
"reset": "Zurücksetzen",
"finish": "Fertig",
"cancel": "Abbrechen",
"delete": "Löschen"
Expand Down Expand Up @@ -225,10 +226,14 @@
},
"browser_recording": {
"modal": {
"confirm_discard": "Sind Sie sicher, dass Sie die Aufnahme verwerfen möchten?"
},
"confirm_discard": "Sind Sie sicher, dass Sie die Aufnahme verwerfen möchten?",
"confirm_reset": "Sind Sie sicher, dass Sie zurücksetzen möchten?",
"reset_warning": "Dies wird alle aktuellen Aufnahmen löschen und Sie zur Startseite zurückbringen. Ihre Aufnahme wird nicht verworfen."
},
"notifications": {
"terminated": "Aktuelle Aufnahme wurde beendet"
"terminated": "Aktuelle Aufnahme wurde beendet",
"environment_reset": "Browser-Umgebung wurde zurückgesetzt",
"reset_successful": "Alle Aufnahmen erfolgreich zurückgesetzt und zum Ausgangszustand zurückgekehrt"
}
},
"interpretation_log": {
Expand Down
10 changes: 8 additions & 2 deletions public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,10 @@
"confirm_capture": "Confirm Capture",
"confirm_pagination": "Confirm",
"confirm_limit": "Confirm",
"confirm_reset": "Confirm",
"finish_capture": "Finish Capture",
"back": "Back",
"reset": "Reset",
"finish": "Finish",
"cancel": "Cancel",
"delete": "Delete"
Expand Down Expand Up @@ -226,10 +228,14 @@
},
"browser_recording": {
"modal": {
"confirm_discard": "Are you sure you want to discard the recording?"
"confirm_discard": "Are you sure you want to discard the recording?",
"confirm_reset": "Are you sure you want to reset?",
"reset_warning": "This will clear all current captures and return you to the home page. Your recording will not be discarded."
},
"notifications": {
"terminated": "Current Recording was terminated"
"terminated": "Current Recording was terminated",
"environment_reset": "Browser environment has been reset",
"reset_successful": "Successfully reset all captures and returned to initial state"
}
},
"interpretation_log": {
Expand Down
9 changes: 7 additions & 2 deletions public/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@
"confirm_limit": "Confirmar",
"finish_capture": "Finalizar Captura",
"back": "Atrás",
"reset": "Reiniciar",
"finish": "Finalizar",
"cancel": "Cancelar",
"delete": "Eliminar"
Expand Down Expand Up @@ -226,10 +227,14 @@
},
"browser_recording": {
"modal": {
"confirm_discard": "¿Está seguro de que desea descartar la grabación?"
"confirm_discard": "¿Está seguro de que desea descartar la grabación?",
"confirm_reset": "¿Está seguro de que desea reiniciar?",
"reset_warning": "Esto borrará todas las capturas actuales y lo devolverá a la página de inicio. Su grabación no se descartará."
},
"notifications": {
"terminated": "La grabación actual fue terminada"
"terminated": "La grabación actual fue terminada",
"environment_reset": "El entorno del navegador ha sido reiniciado",
"reset_successful": "Se reiniciaron correctamente todas las capturas y se volvió al estado inicial"
}
},
"interpretation_log": {
Expand Down
9 changes: 7 additions & 2 deletions public/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@
"confirm_limit": "確認",
"finish_capture": "取得を完了",
"back": "戻る",
"reset": "リセット",
"finish": "完了",
"cancel": "キャンセル",
"delete": "削除"
Expand Down Expand Up @@ -226,10 +227,14 @@
},
"browser_recording": {
"modal": {
"confirm_discard": "録画を破棄してもよろしいですか?"
"confirm_discard": "録画を破棄してもよろしいですか?",
"confirm_reset": "リセットしてもよろしいですか?",
"reset_warning": "現在のキャプチャーがすべてクリアされ、ホームページに戻ります。録画は破棄されません。"
},
"notifications": {
"terminated": "現在の録画は終了しました"
"terminated": "現在の録画は終了しました",
"environment_reset": "ブラウザー環境がリセットされました",
"reset_successful": "すべてのキャプチャーを正常にリセットし、初期状態に戻りました"
}
},
"interpretation_log": {
Expand Down
9 changes: 7 additions & 2 deletions public/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@
"confirm_limit": "确认",
"finish_capture": "完成捕获",
"back": "返回",
"reset": "重置",
"finish": "完成",
"cancel": "取消",
"delete": "删除"
Expand Down Expand Up @@ -226,10 +227,14 @@
},
"browser_recording": {
"modal": {
"confirm_discard": "您确定要放弃录制吗?"
"confirm_discard": "您确定要放弃此录制吗?",
"confirm_reset": "您确定要重置吗?",
"reset_warning": "这将清除所有当前捕获并返回首页。您的录制不会被放弃。"
},
"notifications": {
"terminated": "当前录制已终止"
"terminated": "当前录制已终止",
"environment_reset": "浏览器环境已重置",
"reset_successful": "已成功重置所有捕获并返回初始状态"
}
},
"interpretation_log": {
Expand Down
140 changes: 132 additions & 8 deletions src/components/browser/BrowserRecordingSave.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
import React, { useState } from 'react'
import { Grid, Button, Box, Typography } from '@mui/material';
import { Grid, Button, Box, Typography, IconButton, Menu, MenuItem, ListItemText } from '@mui/material';
import { SaveRecording } from "../recorder/SaveRecording";
import { useGlobalInfoStore } from '../../context/globalInfo';
import { useActionContext } from '../../context/browserActions';
import { useBrowserSteps } from '../../context/browserSteps';
import { stopRecording } from "../../api/recording";
import { useNavigate } from 'react-router-dom';
import { GenericModal } from "../ui/GenericModal";
import { useTranslation } from 'react-i18next';
import { emptyWorkflow } from '../../shared/constants';
import { useSocketStore } from '../../context/socket';
import { MoreHoriz } from '@mui/icons-material';

const BrowserRecordingSave = () => {
const { t } = useTranslation();
const [openModal, setOpenModal] = useState<boolean>(false);
const { recordingName, browserId, setBrowserId, notify } = useGlobalInfoStore();
const [openDiscardModal, setOpenDiscardModal] = useState<boolean>(false);
const [openResetModal, setOpenResetModal] = useState<boolean>(false);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const { recordingName, browserId, initialUrl, setRecordingUrl, setBrowserId, notify, setCurrentWorkflowActionsState, resetInterpretationLog } = useGlobalInfoStore();
const navigate = useNavigate();

const { socket } = useSocketStore();

const {
stopGetText,
stopGetList,
stopGetScreenshot,
stopPaginationMode,
stopLimitMode,
setCaptureStage,
updatePaginationType,
updateLimitType,
updateCustomLimit,
setShowLimitOptions,
setShowPaginationOptions,
setWorkflow,
} = useActionContext();

const { browserSteps, deleteBrowserStep } = useBrowserSteps();

const goToMainMenu = async () => {
if (browserId) {
await stopRecording(browserId);
Expand All @@ -22,6 +48,58 @@ const BrowserRecordingSave = () => {
navigate('/');
};

const performReset = () => {
stopGetText();
stopGetList();
stopGetScreenshot();
stopPaginationMode();
stopLimitMode();

setShowLimitOptions(false);
setShowPaginationOptions(false);
setCaptureStage('initial');

updatePaginationType('');
updateLimitType('');
updateCustomLimit('');

setCurrentWorkflowActionsState({
hasScrapeListAction: false,
hasScreenshotAction: false,
hasScrapeSchemaAction: false
});

setWorkflow(emptyWorkflow);

resetInterpretationLog();

// Clear all browser steps
browserSteps.forEach(step => {
deleteBrowserStep(step.id);
});

if (socket) {
socket?.emit('new-recording');
socket.emit('input:url', initialUrl);
// Update the URL in the navbar to match
setRecordingUrl(initialUrl);
}

// Close the reset confirmation modal
setOpenResetModal(false);

// Notify user
notify('info', t('browser_recording.notifications.environment_reset'));
};

const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};

return (
<Grid container>
<Grid item xs={12} md={3} lg={3}>
Expand All @@ -40,7 +118,7 @@ const BrowserRecordingSave = () => {
height: "48px"
}}>
<Button
onClick={() => setOpenModal(true)}
onClick={() => setOpenDiscardModal(true)}
variant="outlined"
color="error"
sx={{
Expand All @@ -53,15 +131,62 @@ const BrowserRecordingSave = () => {
>
{t('right_panel.buttons.discard')}
</Button>
<GenericModal isOpen={openModal} onClose={() => setOpenModal(false)} modalStyle={modalStyle}>

{/* Reset Button */}
<IconButton
aria-label="options"
size="small"
onClick={handleClick}
style={{
color: 'whitesmoke',
}}
>
<MoreHoriz />
</IconButton>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={() => { setOpenResetModal(true); handleClose(); }}>
<ListItemText>{t('right_panel.buttons.reset')}</ListItemText>
</MenuItem>
</Menu>

<SaveRecording fileName={recordingName} />

{/* Discard Confirmation Modal */}
<GenericModal isOpen={openDiscardModal} onClose={() => setOpenDiscardModal(false)} modalStyle={modalStyle}>
<Box p={2}>
<Typography variant="h6">{t('browser_recording.modal.confirm_discard')}</Typography>
<Box display="flex" justifyContent="space-between" mt={2}>
<Button onClick={goToMainMenu} variant="contained" color="error">
{t('right_panel.buttons.discard')}
</Button>
<Button onClick={() => setOpenDiscardModal(false)} variant="outlined">
{t('right_panel.buttons.cancel')}
</Button>
</Box>
</Box>
</GenericModal>

{/* Reset Confirmation Modal */}
<GenericModal isOpen={openResetModal} onClose={() => setOpenResetModal(false)} modalStyle={modalStyle}>
<Box p={2}>
<Typography variant="h6">{t('browser_recording.modal.confirm_reset')}</Typography>
<Typography variant="body2" sx={{ mt: 1, mb: 2 }}>
{t('browser_recording.modal.reset_warning')}
</Typography>
<Box display="flex" justifyContent="space-between" mt={2}>
<Button
onClick={performReset}
variant="contained"
color="primary"
>
{t('right_panel.buttons.confirm_reset')}
</Button>
<Button
onClick={() => setOpenModal(false)}
onClick={() => setOpenResetModal(false)}
variant="outlined"
sx={{
color: '#ff00c3 !important',
Expand All @@ -73,12 +198,11 @@ const BrowserRecordingSave = () => {
</Box>
</Box>
</GenericModal>
<SaveRecording fileName={recordingName} />
</div>
</Grid>
</Grid>
);
}
};

export default BrowserRecordingSave;

Expand Down
9 changes: 4 additions & 5 deletions src/components/recorder/RightSidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,12 @@ interface RightSidePanelProps {
}

export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture }) => {
const [workflow, setWorkflow] = useState<WorkflowFile>(emptyWorkflow);
const [textLabels, setTextLabels] = useState<{ [id: string]: string }>({});
const [errors, setErrors] = useState<{ [id: string]: string }>({});
const [confirmedTextSteps, setConfirmedTextSteps] = useState<{ [id: string]: boolean }>({});
const [confirmedListTextFields, setConfirmedListTextFields] = useState<{ [listId: string]: { [fieldKey: string]: boolean } }>({});
const [showPaginationOptions, setShowPaginationOptions] = useState(false);
const [showLimitOptions, setShowLimitOptions] = useState(false);
// const [showPaginationOptions, setShowPaginationOptions] = useState(false);
// const [showLimitOptions, setShowLimitOptions] = useState(false);
const [showCaptureList, setShowCaptureList] = useState(true);
const [showCaptureScreenshot, setShowCaptureScreenshot] = useState(true);
const [showCaptureText, setShowCaptureText] = useState(true);
Expand All @@ -61,15 +60,15 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
const [isCaptureListConfirmed, setIsCaptureListConfirmed] = useState(false);

const { lastAction, notify, currentWorkflowActionsState, setCurrentWorkflowActionsState, resetInterpretationLog } = 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, startPaginationMode, stopPaginationMode, paginationType, updatePaginationType, limitType, customLimit, updateLimitType, updateCustomLimit, stopLimitMode, startLimitMode, captureStage, setCaptureStage, showPaginationOptions, setShowPaginationOptions, showLimitOptions, setShowLimitOptions, workflow, setWorkflow } = useActionContext();
const { browserSteps, updateBrowserTextStepLabel, deleteBrowserStep, addScreenshotStep, updateListTextFieldLabel, removeListTextField } = useBrowserSteps();
const { id, socket } = useSocketStore();
const { t } = useTranslation();

const workflowHandler = useCallback((data: WorkflowFile) => {
setWorkflow(data);
//setRecordingLength(data.workflow.length);
}, [workflow])
}, [])

useEffect(() => {
if (socket) {
Expand Down
9 changes: 7 additions & 2 deletions src/components/robot/RecordingsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl
},
];

const { notify, setRecordings, browserId, setBrowserId, recordingUrl, setRecordingUrl, recordingName, setRecordingName, recordingId, setRecordingId } = useGlobalInfoStore();
const { notify, setRecordings, browserId, setBrowserId, setInitialUrl, recordingUrl, setRecordingUrl, recordingName, setRecordingName, recordingId, setRecordingId } = useGlobalInfoStore();
const navigate = useNavigate();

const handleChangePage = (event: unknown, newPage: number) => {
Expand Down Expand Up @@ -142,6 +142,11 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl
handleStartRecording();
};

const setBrowserRecordingUrl = (event: React.ChangeEvent<HTMLInputElement>) => {
setInitialUrl(event.target.value);
setRecordingUrl(event.target.value);
}
Comment on lines +145 to +148
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add URL validation and consider renaming the function.

  1. The function updates both initialUrl and recordingUrl but lacks URL validation.
  2. The function name could be more specific about its dual purpose.

Consider this implementation:

- const setBrowserRecordingUrl = (event: React.ChangeEvent<HTMLInputElement>) => {
-   setInitialUrl(event.target.value);
-   setRecordingUrl(event.target.value);
- }
+ const syncBrowserRecordingUrls = (event: React.ChangeEvent<HTMLInputElement>) => {
+   const url = event.target.value;
+   try {
+     // Basic URL validation
+     if (url && url !== 'https://' && !url.match(/^https?:\/\/.+/)) {
+       throw new Error('Invalid URL format');
+     }
+     setInitialUrl(url);
+     setRecordingUrl(url);
+   } catch (error) {
+     notify('error', 'Please enter a valid URL starting with http:// or https://');
+   }
+ }

Committable suggestion skipped: line range outside the PR's diff.


useEffect(() => {
if (rows.length === 0) {
fetchRecordings();
Expand Down Expand Up @@ -307,7 +312,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl
variant="outlined"
fullWidth
value={recordingUrl}
onChange={(e: any) => setRecordingUrl(e.target.value)}
onChange={setBrowserRecordingUrl}
style={{ marginBottom: '20px', marginTop: '20px' }}
/>
<Button
Expand Down
Loading