From d7d9dde22f78b99cbc04cb473915a5d5c895981d Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Tue, 31 Dec 2024 16:52:00 +0530 Subject: [PATCH 1/7] Introduce environment tabs for secrets Co-authored-by: Saikrishna321 --- app/common/public/locales/en/translation.json | 5 +- app/common/renderer/actions/Inspector.js | 21 + app/common/renderer/actions/Session.js | 6 + .../Inspector/EnvironmentVariables.jsx | 93 +++ .../Inspector/EnvironmentVariables.module.css | 9 + .../components/Inspector/Inspector.jsx | 26 +- .../components/Session/Inspector.module.css | 734 ++++++++++++++++++ .../renderer/components/Session/Session.jsx | 24 +- .../renderer/constants/session-inspector.js | 1 + .../reducers/{Inspector.js => Inspector.jsx} | 34 + app/common/renderer/utils/env-utils.js | 27 + 11 files changed, 965 insertions(+), 15 deletions(-) create mode 100644 app/common/renderer/components/Inspector/EnvironmentVariables.jsx create mode 100644 app/common/renderer/components/Inspector/EnvironmentVariables.module.css create mode 100644 app/common/renderer/components/Session/Inspector.module.css rename app/common/renderer/reducers/{Inspector.js => Inspector.jsx} (94%) create mode 100644 app/common/renderer/utils/env-utils.js diff --git a/app/common/public/locales/en/translation.json b/app/common/public/locales/en/translation.json index 6ad2110bfb..a053978b93 100644 --- a/app/common/public/locales/en/translation.json +++ b/app/common/public/locales/en/translation.json @@ -295,5 +295,8 @@ "gestureNameCannotBeEmptyError": "Gesture name cannot be empty", "gestureInvalidJsonError": "Invalid JSON file. Unable to parse the file content", "gestureImportedFrom": "Gesture imported from '{{fileName}}'", - "invalidSessionFile": "Invalid session file" + "invalidSessionFile": "Invalid session file", + "Environment Variables": "Environment Variables", + "Variable Name": "Variable Name", + "Delete Variable": "Delete Variable" } diff --git a/app/common/renderer/actions/Inspector.js b/app/common/renderer/actions/Inspector.js index e2c8ce76fa..7300551d43 100644 --- a/app/common/renderer/actions/Inspector.js +++ b/app/common/renderer/actions/Inspector.js @@ -116,6 +116,9 @@ export const TOGGLE_SHOW_ATTRIBUTES = 'TOGGLE_SHOW_ATTRIBUTES'; export const TOGGLE_REFRESHING_STATE = 'TOGGLE_REFRESHING_STATE'; export const SET_GESTURE_UPLOAD_ERROR = 'SET_GESTURE_UPLOAD_ERROR'; +export const SET_ENVIRONMENT_VARIABLES = 'SET_ENVIRONMENT_VARIABLES'; +export const ADD_ENVIRONMENT_VARIABLE = 'ADD_ENVIRONMENT_VARIABLE'; +export const DELETE_ENVIRONMENT_VARIABLE = 'DELETE_ENVIRONMENT_VARIABLE'; const KEEP_ALIVE_PING_INTERVAL = 20 * 1000; const NO_NEW_COMMAND_LIMIT = 24 * 60 * 60 * 1000; // Set timeout to 24 hours @@ -919,6 +922,24 @@ export function setGestureUploadErrors(errors) { }; } +export function setEnvironmentVariables(envVars) { + return (dispatch) => { + dispatch({type: SET_ENVIRONMENT_VARIABLES, envVars}); + }; +} + +export function addEnvironmentVariable(key, value) { + return (dispatch) => { + dispatch({type: ADD_ENVIRONMENT_VARIABLE, key, value}); + }; +} + +export function deleteEnvironmentVariable(key) { + return (dispatch) => { + dispatch({type: DELETE_ENVIRONMENT_VARIABLE, key}); + }; +} + export function uploadGesturesFromFile(fileList) { return async (dispatch) => { const gestures = await readTextFromUploadedFiles(fileList); diff --git a/app/common/renderer/actions/Session.js b/app/common/renderer/actions/Session.js index bc21f07429..3ec4755d74 100644 --- a/app/common/renderer/actions/Session.js +++ b/app/common/renderer/actions/Session.js @@ -1,5 +1,6 @@ import {notification} from 'antd'; import {includes, isPlainObject, isUndefined, toPairs, union, values, without} from 'lodash'; +import {interpolateEnvironmentVariables} from '../utils/env-utils'; import moment from 'moment'; import {Web2Driver} from 'web2driver'; @@ -229,7 +230,12 @@ export function newSession(caps, attachSessId = null) { dispatch({type: NEW_SESSION_REQUESTED, caps}); + // Get environment variables from state + const environmentVariables = getState().inspector.environmentVariables || []; + + // Get capabilities and interpolate environment variables let desiredCapabilities = caps ? getCapsObject(caps) : {}; + desiredCapabilities = interpolateEnvironmentVariables(desiredCapabilities, environmentVariables); let host, port, username, accessKey, https, path, token; desiredCapabilities = addCustomCaps(desiredCapabilities); diff --git a/app/common/renderer/components/Inspector/EnvironmentVariables.jsx b/app/common/renderer/components/Inspector/EnvironmentVariables.jsx new file mode 100644 index 0000000000..b95e466707 --- /dev/null +++ b/app/common/renderer/components/Inspector/EnvironmentVariables.jsx @@ -0,0 +1,93 @@ +import {PlusOutlined, DeleteOutlined} from '@ant-design/icons'; +import {Button, Input, Space, Table, Tooltip} from 'antd'; +import {useState} from 'react'; +import {useDispatch, useSelector} from 'react-redux'; +import {setEnvironmentVariables, addEnvironmentVariable, deleteEnvironmentVariable} from '../../actions/Inspector'; +import styles from './EnvironmentVariables.module.css'; + +const EnvironmentVariables = ({t}) => { + const dispatch = useDispatch(); + const envVars = useSelector(state => state.inspector.environmentVariables || []); + const [newVar, setNewVar] = useState({key: '', value: ''}); + + const columns = [ + { + title: t('Variable Name'), + dataIndex: 'key', + key: 'key', + width: '40%', + }, + { + title: t('Value'), + dataIndex: 'value', + key: 'value', + width: '40%', + render: (text) => , + }, + { + title: t('Actions'), + key: 'action', + width: '20%', + render: (_, record) => ( + + + + + + + ); +}; + +export default EnvironmentVariables; diff --git a/app/common/renderer/components/Inspector/EnvironmentVariables.module.css b/app/common/renderer/components/Inspector/EnvironmentVariables.module.css new file mode 100644 index 0000000000..9c11f4dce1 --- /dev/null +++ b/app/common/renderer/components/Inspector/EnvironmentVariables.module.css @@ -0,0 +1,9 @@ +.container { + display: flex; + flex-direction: column; + gap: 16px; +} + +.addForm { + padding: 8px 0; +} \ No newline at end of file diff --git a/app/common/renderer/components/Inspector/Inspector.jsx b/app/common/renderer/components/Inspector/Inspector.jsx index 8799bbfd1f..e847c4918b 100644 --- a/app/common/renderer/components/Inspector/Inspector.jsx +++ b/app/common/renderer/components/Inspector/Inspector.jsx @@ -15,6 +15,7 @@ import { import {Button, Card, Modal, Space, Spin, Switch, Tabs, Tooltip} from 'antd'; import {debounce} from 'lodash'; import {useEffect, useRef, useState} from 'react'; +import styles from './Inspector.module.css'; import {useNavigate} from 'react-router'; import {BUTTON} from '../../constants/antd-types'; @@ -30,7 +31,6 @@ import {downloadFile} from '../../utils/file-handling'; import Commands from './Commands.jsx'; import GestureEditor from './GestureEditor.jsx'; import HeaderButtons from './HeaderButtons.jsx'; -import InspectorStyles from './Inspector.module.css'; import Recorder from './Recorder.jsx'; import SavedGestures from './SavedGestures.jsx'; import Screenshot from './Screenshot.jsx'; @@ -221,7 +221,7 @@ const Inspector = (props) => { }, [showKeepAlivePrompt]); const screenShotControls = ( -
+
{ ); const main = ( -
+
{screenShotControls} @@ -274,11 +274,11 @@ const Inspector = (props) => { {screenshotError && t('couldNotObtainScreenshot', {screenshotError})} {!showScreenshot && ( -
+
)}
-
+
{
{ {t('selectedElement')} } - className={InspectorStyles['selected-element-card']} + className={styles['selected-element-card']} > {selectedElement.path && } {!selectedElement.path && {t('selectElementInSource')}} @@ -359,7 +359,7 @@ const Inspector = (props) => { {t('Execute Commands')} } - className={InspectorStyles['interaction-tab-card']} + className={styles['interaction-tab-card']} > @@ -376,7 +376,7 @@ const Inspector = (props) => { {t('Gesture Builder')} } - className={InspectorStyles['interaction-tab-card']} + className={styles['interaction-tab-card']} > @@ -387,7 +387,7 @@ const Inspector = (props) => { {t('Saved Gestures')} } - className={InspectorStyles['interaction-tab-card']} + className={styles['interaction-tab-card']} > @@ -410,7 +410,7 @@ const Inspector = (props) => { {t('Session Information')} } - className={InspectorStyles['interaction-tab-card']} + className={styles['interaction-tab-card']} > @@ -423,7 +423,7 @@ const Inspector = (props) => { ); return ( -
+
{main} .ant-steps-item-container + > .ant-steps-item-content + > .ant-steps-item-title::after + ) { + background-color: #fffefe; +} + +.gesture-header-timeline + :global( + .ant-steps-item-finish + .ant-steps-item-container + .ant-steps-item-content + .ant-steps-item-title::after + ) { + background-color: var(--timelineColor); +} + +.gesture-header-icon { + font-size: 20px; +} + +.pointer-title { + max-width: 65px; + padding: 0 0 0 0; + text-align: center; + text-decoration: underline; + text-decoration-thickness: 20%; +} + +.tick-card { + overflow: hidden; +} + +.tick-card :global(.ant-card-head) { + margin-top: -16px; + padding-right: 0px; + border-bottom: none; +} + +.tick-card :global(.ant-card-body) { + margin-top: -12px; + min-height: 167px; + margin-right: -12px; +} + +.tick-plus-card { + min-height: 220px; +} + +.tick-plus-btn { + position: absolute; + top: 40%; +} + +.spaceContainer { + display: flex; + word-break: break-word; +} + +.tick-pointer-input { + width: 145px; +} + +.tick-button-group { + display: block; +} + +.tick-button-input { + width: 73px; +} + +.tick-input-box { + width: 145px; +} + +.tick-coord-box { + width: 72px; + text-align: center; +} + +.tick-input-box :global(.ant-input-group-addon) { + position: sticky; + width: 50px; +} + +.option-inpt { + text-align: center; +} + +.error-icon { + color: red; + margin: 5px; +} diff --git a/app/common/renderer/components/Session/Session.jsx b/app/common/renderer/components/Session/Session.jsx index 4005cb12b7..ec67682458 100644 --- a/app/common/renderer/components/Session/Session.jsx +++ b/app/common/renderer/components/Session/Session.jsx @@ -1,5 +1,6 @@ import {LinkOutlined} from '@ant-design/icons'; -import {Badge, Button, Spin, Tabs} from 'antd'; +import {Badge, Button, Spin, Tabs, Card} from 'antd'; +import { SettingOutlined } from '@ant-design/icons'; import _ from 'lodash'; import {useEffect} from 'react'; import {useNavigate} from 'react-router'; @@ -11,6 +12,9 @@ import { SERVER_TYPES, SESSION_BUILDER_TABS, } from '../../constants/session-builder'; +import { + INSPECTOR_TABS, +} from '../../constants/session-inspector'; import {ipcRenderer, openLink} from '../../polyfills'; import {log} from '../../utils/logger'; import AdvancedServerParams from './AdvancedServerParams.jsx'; @@ -21,6 +25,8 @@ import CloudProviderSelector from './CloudProviderSelector.jsx'; import SavedSessions from './SavedSessions.jsx'; import ServerTabCustom from './ServerTabCustom.jsx'; import SessionStyles from './Session.module.css'; +import EnvironmentVariables from '../Inspector/EnvironmentVariables.jsx'; +import InspectorStyles from './Inspector.module.css'; const Session = (props) => { const { @@ -157,6 +163,22 @@ const Session = (props) => { className: SessionStyles.scrollingTab, children: , }, + { + label: t('Environment Variables'), + key: INSPECTOR_TABS.ENV_VARS, + children: ( + + {t('Environment Variables')} + + } + className={InspectorStyles['interaction-tab-card']} + > + + + ), + }, ]} /> diff --git a/app/common/renderer/constants/session-inspector.js b/app/common/renderer/constants/session-inspector.js index 9c2e2b5945..c5472cbd2f 100644 --- a/app/common/renderer/constants/session-inspector.js +++ b/app/common/renderer/constants/session-inspector.js @@ -42,4 +42,5 @@ export const INSPECTOR_TABS = { GESTURES: 'gestures', RECORDER: 'recorder', SESSION_INFO: 'sessionInfo', + ENV_VARS: 'envVars', }; diff --git a/app/common/renderer/reducers/Inspector.js b/app/common/renderer/reducers/Inspector.jsx similarity index 94% rename from app/common/renderer/reducers/Inspector.js rename to app/common/renderer/reducers/Inspector.jsx index ddae04d2ee..fd4362f1b9 100644 --- a/app/common/renderer/reducers/Inspector.js +++ b/app/common/renderer/reducers/Inspector.jsx @@ -48,6 +48,9 @@ import { SET_CONTEXT, SET_COORD_END, SET_COORD_START, + SET_ENVIRONMENT_VARIABLES, + ADD_ENVIRONMENT_VARIABLE, + DELETE_ENVIRONMENT_VARIABLE, SET_EXPANDED_PATHS, SET_GESTURE_TAP_COORDS_MODE, SET_GESTURE_UPLOAD_ERROR, @@ -94,6 +97,7 @@ const INITIAL_STATE = { savedGestures: [], driver: null, automationName: null, + environmentVariables: [], keepAliveInterval: null, showKeepAlivePrompt: false, userWaitTimeout: null, @@ -133,8 +137,38 @@ const INITIAL_STATE = { let nextState; +// Helper function to interpolate environment variables in a string +const interpolateEnvVars = (str, envVars) => { + if (typeof str !== 'string') return str; + return str.replace(/\${([^}]+)}/g, (match, key) => { + const envVar = envVars.find(v => v.key === key); + return envVar ? envVar.value : match; + }); +}; + export default function inspector(state = INITIAL_STATE, action) { switch (action.type) { + case SET_ENVIRONMENT_VARIABLES: + return { + ...state, + environmentVariables: action.envVars, + }; + + case ADD_ENVIRONMENT_VARIABLE: + return { + ...state, + environmentVariables: [ + ...state.environmentVariables, + {key: action.key, value: action.value} + ], + }; + + case DELETE_ENVIRONMENT_VARIABLE: + return { + ...state, + environmentVariables: state.environmentVariables.filter(v => v.key !== action.key), + }; + case SET_SOURCE_AND_SCREENSHOT: return { ...state, diff --git a/app/common/renderer/utils/env-utils.js b/app/common/renderer/utils/env-utils.js new file mode 100644 index 0000000000..485d0e029f --- /dev/null +++ b/app/common/renderer/utils/env-utils.js @@ -0,0 +1,27 @@ +/** + * Recursively interpolate environment variables in an object + * @param {*} obj - The object to interpolate + * @param {Array} envVars - Array of environment variables [{key: string, value: string}] + * @returns {*} - The interpolated object + */ +export function interpolateEnvironmentVariables(obj, envVars) { + if (typeof obj === 'string') { + return obj.replace(/\${([^}]+)}/g, (match, key) => { + const envVar = envVars.find(v => v.key === key); + return envVar ? envVar.value : match; + }); + } + + if (Array.isArray(obj)) { + return obj.map(item => interpolateEnvironmentVariables(item, envVars)); + } + + if (obj !== null && typeof obj === 'object') { + return Object.keys(obj).reduce((acc, key) => { + acc[key] = interpolateEnvironmentVariables(obj[key], envVars); + return acc; + }, {}); + } + + return obj; +} From 5f3e1631782646a19ccb7331e546fcc5fc73d5cb Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Tue, 31 Dec 2024 16:55:28 +0530 Subject: [PATCH 2/7] Maintain environment variables state Co-authored-by: Saikrishna321 --- app/common/renderer/reducers/Inspector.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/common/renderer/reducers/Inspector.jsx b/app/common/renderer/reducers/Inspector.jsx index fd4362f1b9..5d947bd56a 100644 --- a/app/common/renderer/reducers/Inspector.jsx +++ b/app/common/renderer/reducers/Inspector.jsx @@ -196,6 +196,7 @@ export default function inspector(state = INITIAL_STATE, action) { case QUIT_SESSION_DONE: return { ...INITIAL_STATE, + environmentVariables: state.environmentVariables, }; case SESSION_DONE: From 9b2688fc97ddb0e29e5eef481daa7536e971050c Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Fri, 3 Jan 2025 11:42:28 +0530 Subject: [PATCH 3/7] fix review comments Co-authored-by: Saikrishna321 --- app/common/renderer/actions/Inspector.js | 25 +- .../Inspector/EnvironmentVariables.jsx | 47 +- .../Inspector/EnvironmentVariables.module.css | 9 - .../components/Inspector/Inspector.module.css | 10 + .../components/Session/Inspector.module.css | 734 ------------------ .../renderer/components/Session/Session.jsx | 11 +- .../components/Session/Session.module.css | 6 + .../renderer/constants/session-builder.js | 1 + .../renderer/constants/session-inspector.js | 3 +- app/common/renderer/reducers/Inspector.jsx | 8 +- app/common/shared/setting-defs.js | 2 + 11 files changed, 82 insertions(+), 774 deletions(-) delete mode 100644 app/common/renderer/components/Inspector/EnvironmentVariables.module.css delete mode 100644 app/common/renderer/components/Session/Inspector.module.css diff --git a/app/common/renderer/actions/Inspector.js b/app/common/renderer/actions/Inspector.js index 7300551d43..d34ec27a30 100644 --- a/app/common/renderer/actions/Inspector.js +++ b/app/common/renderer/actions/Inspector.js @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {SAVED_FRAMEWORK, SET_SAVED_GESTURES} from '../../shared/setting-defs'; +import {SAVED_FRAMEWORK, SET_SAVED_GESTURES, ENVIRONMENT_VARIABLES} from '../../shared/setting-defs'; import {POINTER_TYPES} from '../constants/gestures'; import {APP_MODE, NATIVE_APP} from '../constants/session-inspector'; import i18n from '../i18next'; @@ -360,6 +360,9 @@ export function quitSession(reason, killedByUser = true) { const applyAction = applyClientMethod({methodName: 'quit'}); await applyAction(dispatch, getState); dispatch({type: QUIT_SESSION_DONE}); + // Reload environment variables from persistent settings after session ends + const loadEnvAction = loadEnvironmentVariables(); + await loadEnvAction(dispatch); if (!killedByUser) { showError(new Error(reason || i18n.t('Session has been terminated')), {secs: 0}); } @@ -923,23 +926,37 @@ export function setGestureUploadErrors(errors) { } export function setEnvironmentVariables(envVars) { - return (dispatch) => { + return async (dispatch) => { + await setSetting(ENVIRONMENT_VARIABLES, envVars); dispatch({type: SET_ENVIRONMENT_VARIABLES, envVars}); }; } export function addEnvironmentVariable(key, value) { - return (dispatch) => { + return async (dispatch, getState) => { + const currentEnvVars = getState().inspector.environmentVariables || []; + const newEnvVars = [...currentEnvVars, {key, value}]; + await setSetting(ENVIRONMENT_VARIABLES, newEnvVars); dispatch({type: ADD_ENVIRONMENT_VARIABLE, key, value}); }; } export function deleteEnvironmentVariable(key) { - return (dispatch) => { + return async (dispatch, getState) => { + const currentEnvVars = getState().inspector.environmentVariables || []; + const newEnvVars = currentEnvVars.filter(v => v.key !== key); + await setSetting(ENVIRONMENT_VARIABLES, newEnvVars); dispatch({type: DELETE_ENVIRONMENT_VARIABLE, key}); }; } +export function loadEnvironmentVariables() { + return async (dispatch) => { + const envVars = await getSetting(ENVIRONMENT_VARIABLES) || []; + dispatch({type: SET_ENVIRONMENT_VARIABLES, envVars}); + }; +} + export function uploadGesturesFromFile(fileList) { return async (dispatch) => { const gestures = await readTextFromUploadedFiles(fileList); diff --git a/app/common/renderer/components/Inspector/EnvironmentVariables.jsx b/app/common/renderer/components/Inspector/EnvironmentVariables.jsx index b95e466707..31f9ddde5e 100644 --- a/app/common/renderer/components/Inspector/EnvironmentVariables.jsx +++ b/app/common/renderer/components/Inspector/EnvironmentVariables.jsx @@ -1,13 +1,11 @@ import {PlusOutlined, DeleteOutlined} from '@ant-design/icons'; -import {Button, Input, Space, Table, Tooltip} from 'antd'; +import {Button, Input, Space, Table, Tooltip, Popconfirm} from 'antd'; import {useState} from 'react'; -import {useDispatch, useSelector} from 'react-redux'; +import {connect} from 'react-redux'; import {setEnvironmentVariables, addEnvironmentVariable, deleteEnvironmentVariable} from '../../actions/Inspector'; -import styles from './EnvironmentVariables.module.css'; +import styles from './Inspector.module.css'; -const EnvironmentVariables = ({t}) => { - const dispatch = useDispatch(); - const envVars = useSelector(state => state.inspector.environmentVariables || []); +const EnvironmentVariables = ({t, envVars, addVariable, deleteVariable}) => { const [newVar, setNewVar] = useState({key: '', value: ''}); const columns = [ @@ -29,15 +27,21 @@ const EnvironmentVariables = ({t}) => { key: 'action', width: '20%', render: (_, record) => ( - -
-
+
); }; const mapStateToProps = (state) => ({ - envVars: state.inspector.environmentVariables || [] + envVars: state.inspector.environmentVariables || [], }); const mapDispatchToProps = (dispatch) => ({ addVariable: (key, value) => dispatch(addEnvironmentVariable(key, value)), - deleteVariable: (key) => dispatch(deleteEnvironmentVariable(key)) + deleteVariable: (key) => dispatch(deleteEnvironmentVariable(key)), }); export default connect(mapStateToProps, mapDispatchToProps)(EnvironmentVariables); diff --git a/app/common/renderer/components/Inspector/Inspector.jsx b/app/common/renderer/components/Inspector/Inspector.jsx index e847c4918b..258c6bfca8 100644 --- a/app/common/renderer/components/Inspector/Inspector.jsx +++ b/app/common/renderer/components/Inspector/Inspector.jsx @@ -15,7 +15,6 @@ import { import {Button, Card, Modal, Space, Spin, Switch, Tabs, Tooltip} from 'antd'; import {debounce} from 'lodash'; import {useEffect, useRef, useState} from 'react'; -import styles from './Inspector.module.css'; import {useNavigate} from 'react-router'; import {BUTTON} from '../../constants/antd-types'; @@ -31,6 +30,7 @@ import {downloadFile} from '../../utils/file-handling'; import Commands from './Commands.jsx'; import GestureEditor from './GestureEditor.jsx'; import HeaderButtons from './HeaderButtons.jsx'; +import styles from './Inspector.module.css'; import Recorder from './Recorder.jsx'; import SavedGestures from './SavedGestures.jsx'; import Screenshot from './Screenshot.jsx'; @@ -263,7 +263,7 @@ const Inspector = (props) => { ); const main = ( -
+
{ const { diff --git a/app/common/renderer/constants/session-inspector.js b/app/common/renderer/constants/session-inspector.js index e9a8db6bc5..9c2e2b5945 100644 --- a/app/common/renderer/constants/session-inspector.js +++ b/app/common/renderer/constants/session-inspector.js @@ -41,5 +41,5 @@ export const INSPECTOR_TABS = { COMMANDS: 'commands', GESTURES: 'gestures', RECORDER: 'recorder', - SESSION_INFO: 'sessionInfo', + SESSION_INFO: 'sessionInfo', }; diff --git a/app/common/renderer/reducers/Inspector.jsx b/app/common/renderer/reducers/Inspector.jsx index 1be91e6712..60911e2be5 100644 --- a/app/common/renderer/reducers/Inspector.jsx +++ b/app/common/renderer/reducers/Inspector.jsx @@ -1,7 +1,9 @@ import {omit} from 'lodash'; +import {ENVIRONMENT_VARIABLES} from '../../shared/setting-defs'; import { ADD_ASSIGNED_VAR_CACHE, + ADD_ENVIRONMENT_VARIABLE, CANCEL_PENDING_COMMAND, CLEAR_ASSIGNED_VAR_CACHE, CLEAR_COORD_ACTION, @@ -9,6 +11,7 @@ import { CLEAR_SEARCH_RESULTS, CLEAR_SEARCHED_FOR_ELEMENT_BOUNDS, CLEAR_TAP_COORDINATES, + DELETE_ENVIRONMENT_VARIABLE, DELETE_SAVED_GESTURES_DONE, DELETE_SAVED_GESTURES_REQUESTED, ENTERING_COMMAND_ARGS, @@ -49,8 +52,6 @@ import { SET_COORD_END, SET_COORD_START, SET_ENVIRONMENT_VARIABLES, - ADD_ENVIRONMENT_VARIABLE, - DELETE_ENVIRONMENT_VARIABLE, SET_EXPANDED_PATHS, SET_GESTURE_TAP_COORDS_MODE, SET_GESTURE_UPLOAD_ERROR, @@ -89,14 +90,13 @@ import { UNSELECT_TICK_ELEMENT, } from '../actions/Inspector'; import {SCREENSHOT_INTERACTION_MODE} from '../constants/screenshot'; -import {ENVIRONMENT_VARIABLES} from '../../shared/setting-defs'; import {APP_MODE, INSPECTOR_TABS, NATIVE_APP} from '../constants/session-inspector'; import {getSetting} from '../polyfills'; const DEFAULT_FRAMEWORK = 'java'; // Load initial environment variables from persistent settings -const envVars = await getSetting(ENVIRONMENT_VARIABLES) || []; +const envVars = (await getSetting(ENVIRONMENT_VARIABLES)) || []; const INITIAL_STATE = { savedGestures: [], @@ -144,9 +144,11 @@ let nextState; // Helper function to interpolate environment variables in a string const interpolateEnvVars = (str, envVars) => { - if (typeof str !== 'string') return str; + if (typeof str !== 'string') { + return str; + } return str.replace(/\${([^}]+)}/g, (match, key) => { - const envVar = envVars.find(v => v.key === key); + const envVar = envVars.find((v) => v.key === key); return envVar ? envVar.value : match; }); }; @@ -164,14 +166,14 @@ export default function inspector(state = INITIAL_STATE, action) { ...state, environmentVariables: [ ...state.environmentVariables, - {key: action.key, value: action.value} + {key: action.key, value: action.value}, ], }; case DELETE_ENVIRONMENT_VARIABLE: return { ...state, - environmentVariables: state.environmentVariables.filter(v => v.key !== action.key), + environmentVariables: state.environmentVariables.filter((v) => v.key !== action.key), }; case SET_SOURCE_AND_SCREENSHOT: diff --git a/app/common/renderer/utils/env-utils.js b/app/common/renderer/utils/env-utils.js index 485d0e029f..6ed7432610 100644 --- a/app/common/renderer/utils/env-utils.js +++ b/app/common/renderer/utils/env-utils.js @@ -7,21 +7,21 @@ export function interpolateEnvironmentVariables(obj, envVars) { if (typeof obj === 'string') { return obj.replace(/\${([^}]+)}/g, (match, key) => { - const envVar = envVars.find(v => v.key === key); + const envVar = envVars.find((v) => v.key === key); return envVar ? envVar.value : match; }); } - + if (Array.isArray(obj)) { - return obj.map(item => interpolateEnvironmentVariables(item, envVars)); + return obj.map((item) => interpolateEnvironmentVariables(item, envVars)); } - + if (obj !== null && typeof obj === 'object') { return Object.keys(obj).reduce((acc, key) => { acc[key] = interpolateEnvironmentVariables(obj[key], envVars); return acc; }, {}); } - + return obj; } diff --git a/test/unit/utils-env.spec.js b/test/unit/utils-env.spec.js new file mode 100644 index 0000000000..8bfa003358 --- /dev/null +++ b/test/unit/utils-env.spec.js @@ -0,0 +1,79 @@ +import {describe, expect, it} from 'vitest'; + +import {interpolateEnvironmentVariables} from '../../app/common/renderer/utils/env-utils'; + +describe('interpolateEnvironmentVariables', function () { + const envVars = [ + {key: 'HOME', value: '/home/user'}, + {key: 'PATH', value: '/usr/bin'}, + {key: 'APP_ENV', value: 'test'}, + ]; + + it('should interpolate environment variables in strings', function () { + const input = 'My home is ${HOME} and path is ${PATH}'; + const expected = 'My home is /home/user and path is /usr/bin'; + expect(interpolateEnvironmentVariables(input, envVars)).to.equal(expected); + }); + + it('should leave unmatched variables unchanged', function () { + const input = 'Value: ${UNKNOWN_VAR}'; + expect(interpolateEnvironmentVariables(input, envVars)).to.equal('Value: ${UNKNOWN_VAR}'); + }); + + it('should interpolate variables in arrays', function () { + const input = ['${HOME}/files', '${PATH}/python']; + const expected = ['/home/user/files', '/usr/bin/python']; + expect(interpolateEnvironmentVariables(input, envVars)).to.deep.equal(expected); + }); + + it('should interpolate variables in nested objects', function () { + const input = { + home: '${HOME}', + paths: { + bin: '${PATH}', + config: '${HOME}/.config', + }, + env: '${APP_ENV}', + }; + const expected = { + home: '/home/user', + paths: { + bin: '/usr/bin', + config: '/home/user/.config', + }, + env: 'test', + }; + expect(interpolateEnvironmentVariables(input, envVars)).to.deep.equal(expected); + }); + + it('should handle null values', function () { + expect(interpolateEnvironmentVariables(null, envVars)).to.equal(null); + }); + + it('should handle undefined values', function () { + expect(interpolateEnvironmentVariables(undefined, envVars)).to.equal(undefined); + }); + + it('should handle number values', function () { + expect(interpolateEnvironmentVariables(42, envVars)).to.equal(42); + }); + + it('should handle boolean values', function () { + expect(interpolateEnvironmentVariables(true, envVars)).to.equal(true); + expect(interpolateEnvironmentVariables(false, envVars)).to.equal(false); + }); + + it('should handle empty arrays', function () { + expect(interpolateEnvironmentVariables([], envVars)).to.deep.equal([]); + }); + + it('should handle empty objects', function () { + expect(interpolateEnvironmentVariables({}, envVars)).to.deep.equal({}); + }); + + it('should handle multiple variables in single string', function () { + const input = '${HOME}:${PATH}:${APP_ENV}'; + const expected = '/home/user:/usr/bin:test'; + expect(interpolateEnvironmentVariables(input, envVars)).to.equal(expected); + }); +}); From 9e8f5a48bc80ac63c90d6233c2cc6b5d9528e900 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Mon, 6 Jan 2025 16:41:14 +0530 Subject: [PATCH 5/7] Fixed review comments Co-authored-by: Saikrishna321 --- app/common/renderer/actions/Inspector.js | 27 ----- app/common/renderer/actions/Session.js | 58 +++++++++- .../Inspector/EnvironmentVariables.jsx | 103 ------------------ .../components/Inspector/Inspector.module.css | 10 -- .../Session/EnvironmentVariables.jsx | 95 ++++++++++++++++ .../renderer/components/Session/Session.jsx | 23 ++-- .../components/Session/Session.module.css | 10 ++ .../reducers/{Inspector.jsx => Inspector.js} | 69 +++++------- app/common/renderer/reducers/Session.js | 24 ++++ app/common/renderer/utils/env-utils.js | 34 ++++-- 10 files changed, 246 insertions(+), 207 deletions(-) delete mode 100644 app/common/renderer/components/Inspector/EnvironmentVariables.jsx create mode 100644 app/common/renderer/components/Session/EnvironmentVariables.jsx rename app/common/renderer/reducers/{Inspector.jsx => Inspector.js} (95%) diff --git a/app/common/renderer/actions/Inspector.js b/app/common/renderer/actions/Inspector.js index dbf0bb5ce5..9eebb75cd1 100644 --- a/app/common/renderer/actions/Inspector.js +++ b/app/common/renderer/actions/Inspector.js @@ -121,8 +121,6 @@ export const TOGGLE_REFRESHING_STATE = 'TOGGLE_REFRESHING_STATE'; export const SET_GESTURE_UPLOAD_ERROR = 'SET_GESTURE_UPLOAD_ERROR'; export const SET_ENVIRONMENT_VARIABLES = 'SET_ENVIRONMENT_VARIABLES'; -export const ADD_ENVIRONMENT_VARIABLE = 'ADD_ENVIRONMENT_VARIABLE'; -export const DELETE_ENVIRONMENT_VARIABLE = 'DELETE_ENVIRONMENT_VARIABLE'; const KEEP_ALIVE_PING_INTERVAL = 20 * 1000; const NO_NEW_COMMAND_LIMIT = 24 * 60 * 60 * 1000; // Set timeout to 24 hours @@ -929,31 +927,6 @@ export function setGestureUploadErrors(errors) { }; } -export function setEnvironmentVariables(envVars) { - return async (dispatch) => { - await setSetting(ENVIRONMENT_VARIABLES, envVars); - dispatch({type: SET_ENVIRONMENT_VARIABLES, envVars}); - }; -} - -export function addEnvironmentVariable(key, value) { - return async (dispatch, getState) => { - const currentEnvVars = getState().inspector.environmentVariables || []; - const newEnvVars = [...currentEnvVars, {key, value}]; - await setSetting(ENVIRONMENT_VARIABLES, newEnvVars); - dispatch({type: ADD_ENVIRONMENT_VARIABLE, key, value}); - }; -} - -export function deleteEnvironmentVariable(key) { - return async (dispatch, getState) => { - const currentEnvVars = getState().inspector.environmentVariables || []; - const newEnvVars = currentEnvVars.filter((v) => v.key !== key); - await setSetting(ENVIRONMENT_VARIABLES, newEnvVars); - dispatch({type: DELETE_ENVIRONMENT_VARIABLE, key}); - }; -} - export function loadEnvironmentVariables() { return async (dispatch) => { const envVars = (await getSetting(ENVIRONMENT_VARIABLES)) || []; diff --git a/app/common/renderer/actions/Session.js b/app/common/renderer/actions/Session.js index 8b454d2834..a6faedbc12 100644 --- a/app/common/renderer/actions/Session.js +++ b/app/common/renderer/actions/Session.js @@ -21,6 +21,8 @@ import {log} from '../utils/logger'; import {addVendorPrefixes} from '../utils/other'; import {quitSession, setSessionDetails} from './Inspector'; +export const ENVIRONMENT_VARIABLES = 'ENVIRONMENT_VARIABLES'; + export const NEW_SESSION_REQUESTED = 'NEW_SESSION_REQUESTED'; export const NEW_SESSION_LOADING = 'NEW_SESSION_LOADING'; export const NEW_SESSION_DONE = 'NEW_SESSION_DONE'; @@ -69,6 +71,10 @@ export const SET_CAPABILITY_NAME_ERROR = 'SET_CAPABILITY_NAME_ERROR'; export const SET_STATE_FROM_URL = 'SET_STATE_FROM_URL'; export const SET_STATE_FROM_FILE = 'SET_STATE_FROM_FILE'; +export const SET_ENVIRONMENT_VARIABLES = 'SET_ENVIRONMENT_VARIABLES'; +export const ADD_ENVIRONMENT_VARIABLE = 'ADD_ENVIRONMENT_VARIABLE'; +export const DELETE_ENVIRONMENT_VARIABLE = 'DELETE_ENVIRONMENT_VARIABLE'; + const APPIUM_SESSION_FILE_VERSION = '1.0'; const CAPS_NEW_COMMAND = 'appium:newCommandTimeout'; @@ -231,14 +237,24 @@ export function newSession(caps, attachSessId = null) { dispatch({type: NEW_SESSION_REQUESTED, caps}); // Get environment variables from state - const environmentVariables = getState().inspector.environmentVariables || []; + const environmentVariables = session.environmentVariables || []; // Get capabilities and interpolate environment variables let desiredCapabilities = caps ? getCapsObject(caps) : {}; - desiredCapabilities = interpolateEnvironmentVariables( - desiredCapabilities, - environmentVariables, - ); + + // Modify this section to handle W3C capabilities format + if (desiredCapabilities.alwaysMatch) { + desiredCapabilities.alwaysMatch = interpolateEnvironmentVariables( + desiredCapabilities.alwaysMatch, + environmentVariables + ); + } else { + desiredCapabilities = interpolateEnvironmentVariables( + desiredCapabilities, + environmentVariables + ); + } + let host, port, username, accessKey, https, path, token; desiredCapabilities = addCustomCaps(desiredCapabilities); @@ -1195,3 +1211,35 @@ export function initFromQueryString(loadNewSession) { } }; } + +export function setEnvironmentVariables(envVars) { + return async (dispatch) => { + await setSetting(ENVIRONMENT_VARIABLES, envVars); + dispatch({type: SET_ENVIRONMENT_VARIABLES, envVars}); + }; +} + +export function loadEnvironmentVariables() { + return async (dispatch) => { + const envVars = await getSetting(ENVIRONMENT_VARIABLES) || []; + dispatch({type: SET_ENVIRONMENT_VARIABLES, envVars}); + }; +} + +export function addEnvironmentVariable(key, value) { + return async (dispatch, getState) => { + const currentEnvVars = getState().inspector.environmentVariables || []; + const newEnvVars = [...currentEnvVars, {key, value}]; + await setSetting(ENVIRONMENT_VARIABLES, newEnvVars); + dispatch({type: ADD_ENVIRONMENT_VARIABLE, key, value}); + }; +} + +export function deleteEnvironmentVariable(key) { + return async (dispatch, getState) => { + const currentEnvVars = getState().inspector.environmentVariables || []; + const newEnvVars = currentEnvVars.filter((v) => v.key !== key); + await setSetting(ENVIRONMENT_VARIABLES, newEnvVars); + dispatch({type: DELETE_ENVIRONMENT_VARIABLE, key}); + }; +} diff --git a/app/common/renderer/components/Inspector/EnvironmentVariables.jsx b/app/common/renderer/components/Inspector/EnvironmentVariables.jsx deleted file mode 100644 index 1747c38738..0000000000 --- a/app/common/renderer/components/Inspector/EnvironmentVariables.jsx +++ /dev/null @@ -1,103 +0,0 @@ -import {DeleteOutlined, PlusOutlined} from '@ant-design/icons'; -import {Button, Input, Popconfirm, Space, Table, Tooltip} from 'antd'; -import {useState} from 'react'; -import {connect} from 'react-redux'; - -import { - addEnvironmentVariable, - deleteEnvironmentVariable, - setEnvironmentVariables, -} from '../../actions/Inspector'; -import styles from './Inspector.module.css'; - -const EnvironmentVariables = ({t, envVars, addVariable, deleteVariable}) => { - const [newVar, setNewVar] = useState({key: '', value: ''}); - - const columns = [ - { - title: t('Variable Name'), - dataIndex: 'key', - key: 'key', - width: '40%', - }, - { - title: t('Value'), - dataIndex: 'value', - key: 'value', - width: '40%', - render: (text) => , - }, - { - title: t('Actions'), - key: 'action', - width: '20%', - render: (_, record) => ( - - deleteVariable(record.key)} - > - - -
-
- - ); -}; - -const mapStateToProps = (state) => ({ - envVars: state.inspector.environmentVariables || [], -}); - -const mapDispatchToProps = (dispatch) => ({ - addVariable: (key, value) => dispatch(addEnvironmentVariable(key, value)), - deleteVariable: (key) => dispatch(deleteEnvironmentVariable(key)), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(EnvironmentVariables); diff --git a/app/common/renderer/components/Inspector/Inspector.module.css b/app/common/renderer/components/Inspector/Inspector.module.css index 8c7851f166..69190192e4 100644 --- a/app/common/renderer/components/Inspector/Inspector.module.css +++ b/app/common/renderer/components/Inspector/Inspector.module.css @@ -732,13 +732,3 @@ color: red; margin: 5px; } - -.container { - display: flex; - flex-direction: column; - gap: 16px; -} - -.addForm { - padding: 8px 0; -} diff --git a/app/common/renderer/components/Session/EnvironmentVariables.jsx b/app/common/renderer/components/Session/EnvironmentVariables.jsx new file mode 100644 index 0000000000..2691fe3ca0 --- /dev/null +++ b/app/common/renderer/components/Session/EnvironmentVariables.jsx @@ -0,0 +1,95 @@ +import {DeleteOutlined, PlusOutlined, SettingOutlined} from '@ant-design/icons'; +import {Button, Card, Input, Popconfirm, Space, Table, Tooltip} from 'antd'; +import {useState} from 'react'; +import SessionStyles from '../Session/Session.module.css'; +import styles from './Session.module.css'; + +const EnvironmentVariables = ({t, envVars, addVariable, deleteVariable}) => { + const [newVar, setNewVar] = useState({key: '', value: ''}); + + const tableData = Array.from(envVars, ([key, value]) => ({key, value})); + + const columns = [ + { + title: t('Variable Name'), + dataIndex: 'key', + key: 'key', + width: '40%', + }, + { + title: t('Value'), + dataIndex: 'value', + key: 'value', + width: '40%', + render: (text) => , + }, + { + title: t('Actions'), + key: 'action', + width: '20%', + render: (_, record) => ( + + deleteVariable(record.key)} + > + + + +
+ + + ); +}; + +export default EnvironmentVariables; diff --git a/app/common/renderer/components/Session/Session.jsx b/app/common/renderer/components/Session/Session.jsx index 3ee3a0a77b..b459aeda78 100644 --- a/app/common/renderer/components/Session/Session.jsx +++ b/app/common/renderer/components/Session/Session.jsx @@ -13,7 +13,7 @@ import { } from '../../constants/session-builder'; import {ipcRenderer, openLink} from '../../polyfills'; import {log} from '../../utils/logger'; -import EnvironmentVariables from '../Inspector/EnvironmentVariables.jsx'; +import EnvironmentVariables from './EnvironmentVariables.jsx'; import AdvancedServerParams from './AdvancedServerParams.jsx'; import AttachToSession from './AttachToSession.jsx'; import CapabilityEditor from './CapabilityEditor.jsx'; @@ -41,8 +41,10 @@ const Session = (props) => { savedSessions, newSessionLoading, attachSessId, - loadEnvironmentVariables, t, + environmentVariables, + addEnvironmentVariable, + deleteEnvironmentVariable, } = props; const navigate = useNavigate(); @@ -164,17 +166,14 @@ const Session = (props) => { { label: t('Environment Variables'), key: SESSION_BUILDER_TABS.ENV_VARS, + className: SessionStyles.scrollingTab, children: ( - - {t('Environment Variables')} - - } - className={SessionStyles['interaction-tab-card']} - > - - + ), }, ]} diff --git a/app/common/renderer/components/Session/Session.module.css b/app/common/renderer/components/Session/Session.module.css index 349862dc2f..aaa9317f61 100644 --- a/app/common/renderer/components/Session/Session.module.css +++ b/app/common/renderer/components/Session/Session.module.css @@ -322,3 +322,13 @@ flex: 2; height: 100%; } + +.container { + display: flex; + flex-direction: column; + gap: 16px; +} + +.addForm { + padding: 8px 0; +} diff --git a/app/common/renderer/reducers/Inspector.jsx b/app/common/renderer/reducers/Inspector.js similarity index 95% rename from app/common/renderer/reducers/Inspector.jsx rename to app/common/renderer/reducers/Inspector.js index 60911e2be5..25e0deb44d 100644 --- a/app/common/renderer/reducers/Inspector.jsx +++ b/app/common/renderer/reducers/Inspector.js @@ -1,9 +1,7 @@ import {omit} from 'lodash'; -import {ENVIRONMENT_VARIABLES} from '../../shared/setting-defs'; import { ADD_ASSIGNED_VAR_CACHE, - ADD_ENVIRONMENT_VARIABLE, CANCEL_PENDING_COMMAND, CLEAR_ASSIGNED_VAR_CACHE, CLEAR_COORD_ACTION, @@ -11,7 +9,6 @@ import { CLEAR_SEARCH_RESULTS, CLEAR_SEARCHED_FOR_ELEMENT_BOUNDS, CLEAR_TAP_COORDINATES, - DELETE_ENVIRONMENT_VARIABLE, DELETE_SAVED_GESTURES_DONE, DELETE_SAVED_GESTURES_REQUESTED, ENTERING_COMMAND_ARGS, @@ -51,7 +48,6 @@ import { SET_CONTEXT, SET_COORD_END, SET_COORD_START, - SET_ENVIRONMENT_VARIABLES, SET_EXPANDED_PATHS, SET_GESTURE_TAP_COORDS_MODE, SET_GESTURE_UPLOAD_ERROR, @@ -89,20 +85,21 @@ import { UNSELECT_HOVERED_ELEMENT, UNSELECT_TICK_ELEMENT, } from '../actions/Inspector'; +import { + SET_ENVIRONMENT_VARIABLES, + ADD_ENVIRONMENT_VARIABLE, + DELETE_ENVIRONMENT_VARIABLE, +} from '../actions/Session'; import {SCREENSHOT_INTERACTION_MODE} from '../constants/screenshot'; import {APP_MODE, INSPECTOR_TABS, NATIVE_APP} from '../constants/session-inspector'; -import {getSetting} from '../polyfills'; const DEFAULT_FRAMEWORK = 'java'; -// Load initial environment variables from persistent settings -const envVars = (await getSetting(ENVIRONMENT_VARIABLES)) || []; - const INITIAL_STATE = { savedGestures: [], driver: null, automationName: null, - environmentVariables: envVars, + environmentVariables: [], keepAliveInterval: null, showKeepAlivePrompt: false, userWaitTimeout: null, @@ -142,39 +139,8 @@ const INITIAL_STATE = { let nextState; -// Helper function to interpolate environment variables in a string -const interpolateEnvVars = (str, envVars) => { - if (typeof str !== 'string') { - return str; - } - return str.replace(/\${([^}]+)}/g, (match, key) => { - const envVar = envVars.find((v) => v.key === key); - return envVar ? envVar.value : match; - }); -}; - export default function inspector(state = INITIAL_STATE, action) { switch (action.type) { - case SET_ENVIRONMENT_VARIABLES: - return { - ...state, - environmentVariables: action.envVars, - }; - - case ADD_ENVIRONMENT_VARIABLE: - return { - ...state, - environmentVariables: [ - ...state.environmentVariables, - {key: action.key, value: action.value}, - ], - }; - - case DELETE_ENVIRONMENT_VARIABLE: - return { - ...state, - environmentVariables: state.environmentVariables.filter((v) => v.key !== action.key), - }; case SET_SOURCE_AND_SCREENSHOT: return { @@ -700,6 +666,29 @@ export default function inspector(state = INITIAL_STATE, action) { case SET_GESTURE_UPLOAD_ERROR: return {...state, gestureUploadErrors: action.errors}; + case SET_ENVIRONMENT_VARIABLES: + return { + ...state, + environmentVariables: action.envVars, + }; + + case ADD_ENVIRONMENT_VARIABLE: + return { + ...state, + environmentVariables: [ + ...(state.environmentVariables || []), + {key: action.key, value: action.value} + ], + }; + + case DELETE_ENVIRONMENT_VARIABLE: + return { + ...state, + environmentVariables: (state.environmentVariables || []).filter( + (envVar) => envVar.key !== action.key + ), + }; + default: return {...state}; } diff --git a/app/common/renderer/reducers/Session.js b/app/common/renderer/reducers/Session.js index 33fa730f6b..edff309341 100644 --- a/app/common/renderer/reducers/Session.js +++ b/app/common/renderer/reducers/Session.js @@ -39,6 +39,9 @@ import { SET_STATE_FROM_URL, SHOW_DESIRED_CAPS_JSON_ERROR, SWITCHED_TABS, + SET_ENVIRONMENT_VARIABLES, + ADD_ENVIRONMENT_VARIABLE, + DELETE_ENVIRONMENT_VARIABLE } from '../actions/Session'; import {SERVER_TYPES, SESSION_BUILDER_TABS} from '../constants/session-builder'; @@ -75,12 +78,33 @@ const INITIAL_STATE = { isValidatingCapsJson: false, isAddingCloudProvider: false, addVendorPrefixes: true, + environmentVariables: [], }; let nextState; export default function session(state = INITIAL_STATE, action) { switch (action.type) { + case SET_ENVIRONMENT_VARIABLES: + return { + ...state, + environmentVariables: new Map(action.envVars.map(({key, value}) => [key, value])), + }; + + case ADD_ENVIRONMENT_VARIABLE: + return { + ...state, + environmentVariables: new Map(state.environmentVariables).set(action.key, action.value), + }; + + case DELETE_ENVIRONMENT_VARIABLE: + const newEnvVars = new Map(state.environmentVariables); + newEnvVars.delete(action.key); + return { + ...state, + environmentVariables: newEnvVars, + }; + case NEW_SESSION_REQUESTED: return { ...state, diff --git a/app/common/renderer/utils/env-utils.js b/app/common/renderer/utils/env-utils.js index 6ed7432610..c2c7555edb 100644 --- a/app/common/renderer/utils/env-utils.js +++ b/app/common/renderer/utils/env-utils.js @@ -1,26 +1,40 @@ /** * Recursively interpolate environment variables in an object * @param {*} obj - The object to interpolate - * @param {Array} envVars - Array of environment variables [{key: string, value: string}] + * @param {Map} envVars - Map of environment variables * @returns {*} - The interpolated object */ export function interpolateEnvironmentVariables(obj, envVars) { + // Handle primitive types + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'number' || typeof obj === 'boolean') { + return obj; + } + + // Handle strings if (typeof obj === 'string') { - return obj.replace(/\${([^}]+)}/g, (match, key) => { - const envVar = envVars.find((v) => v.key === key); - return envVar ? envVar.value : match; - }); + let result = obj; + for (const [key, value] of envVars) { + result = result.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), value); + } + return result; } + // Handle arrays if (Array.isArray(obj)) { return obj.map((item) => interpolateEnvironmentVariables(item, envVars)); } - if (obj !== null && typeof obj === 'object') { - return Object.keys(obj).reduce((acc, key) => { - acc[key] = interpolateEnvironmentVariables(obj[key], envVars); - return acc; - }, {}); + // Handle objects + if (typeof obj === 'object') { + const result = {}; + for (const [key, value] of Object.entries(obj)) { + result[key] = interpolateEnvironmentVariables(value, envVars); + } + return result; } return obj; From f8d5a9c8bb32796f2d763323365f6446f566a68e Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Mon, 6 Jan 2025 16:48:42 +0530 Subject: [PATCH 6/7] Fix unit tests Co-authored-by: Saikrishna321 --- test/unit/utils-env.spec.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/utils-env.spec.js b/test/unit/utils-env.spec.js index 8bfa003358..d38839e953 100644 --- a/test/unit/utils-env.spec.js +++ b/test/unit/utils-env.spec.js @@ -3,11 +3,11 @@ import {describe, expect, it} from 'vitest'; import {interpolateEnvironmentVariables} from '../../app/common/renderer/utils/env-utils'; describe('interpolateEnvironmentVariables', function () { - const envVars = [ - {key: 'HOME', value: '/home/user'}, - {key: 'PATH', value: '/usr/bin'}, - {key: 'APP_ENV', value: 'test'}, - ]; + const envVars = new Map([ + ['HOME', '/home/user'], + ['PATH', '/usr/bin'], + ['APP_ENV', 'test'], + ]); it('should interpolate environment variables in strings', function () { const input = 'My home is ${HOME} and path is ${PATH}'; From 34971625592984c2fd44d62403075825200baab4 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Mon, 6 Jan 2025 16:53:37 +0530 Subject: [PATCH 7/7] making prettier happy Co-authored-by: Saikrishna321 --- app/common/renderer/actions/Session.js | 8 ++++---- .../components/Session/EnvironmentVariables.jsx | 16 +++++++++++----- .../renderer/components/Session/Session.jsx | 6 +++--- app/common/renderer/reducers/Inspector.js | 7 +++---- app/common/renderer/reducers/Session.js | 9 +++++---- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/app/common/renderer/actions/Session.js b/app/common/renderer/actions/Session.js index d5c6a7a818..129590e7d2 100644 --- a/app/common/renderer/actions/Session.js +++ b/app/common/renderer/actions/Session.js @@ -241,17 +241,17 @@ export function newSession(caps, attachSessId = null) { // Get capabilities and interpolate environment variables let desiredCapabilities = caps ? getCapsObject(caps) : {}; - + // Modify this section to handle W3C capabilities format if (desiredCapabilities.alwaysMatch) { desiredCapabilities.alwaysMatch = interpolateEnvironmentVariables( desiredCapabilities.alwaysMatch, - environmentVariables + environmentVariables, ); } else { desiredCapabilities = interpolateEnvironmentVariables( desiredCapabilities, - environmentVariables + environmentVariables, ); } @@ -1225,7 +1225,7 @@ export function setEnvironmentVariables(envVars) { export function loadEnvironmentVariables() { return async (dispatch) => { - const envVars = await getSetting(ENVIRONMENT_VARIABLES) || []; + const envVars = (await getSetting(ENVIRONMENT_VARIABLES)) || []; dispatch({type: SET_ENVIRONMENT_VARIABLES, envVars}); }; } diff --git a/app/common/renderer/components/Session/EnvironmentVariables.jsx b/app/common/renderer/components/Session/EnvironmentVariables.jsx index 2691fe3ca0..07f9110c6c 100644 --- a/app/common/renderer/components/Session/EnvironmentVariables.jsx +++ b/app/common/renderer/components/Session/EnvironmentVariables.jsx @@ -1,8 +1,8 @@ import {DeleteOutlined, PlusOutlined, SettingOutlined} from '@ant-design/icons'; import {Button, Card, Input, Popconfirm, Space, Table, Tooltip} from 'antd'; import {useState} from 'react'; -import SessionStyles from '../Session/Session.module.css'; -import styles from './Session.module.css'; + +import SessionStyles from './Session.module.css'; const EnvironmentVariables = ({t, envVars, addVariable, deleteVariable}) => { const [newVar, setNewVar] = useState({key: '', value: ''}); @@ -63,8 +63,8 @@ const EnvironmentVariables = ({t, envVars, addVariable, deleteVariable}) => { } className={SessionStyles['interaction-tab-card']} > -
-
+
+
{
-
+
); diff --git a/app/common/renderer/components/Session/Session.jsx b/app/common/renderer/components/Session/Session.jsx index b459aeda78..9915812b97 100644 --- a/app/common/renderer/components/Session/Session.jsx +++ b/app/common/renderer/components/Session/Session.jsx @@ -1,5 +1,5 @@ -import {LinkOutlined, SettingOutlined} from '@ant-design/icons'; -import {Badge, Button, Card, Spin, Tabs} from 'antd'; +import {LinkOutlined} from '@ant-design/icons'; +import {Badge, Button, Spin, Tabs} from 'antd'; import _ from 'lodash'; import {useEffect} from 'react'; import {useNavigate} from 'react-router'; @@ -13,12 +13,12 @@ import { } from '../../constants/session-builder'; import {ipcRenderer, openLink} from '../../polyfills'; import {log} from '../../utils/logger'; -import EnvironmentVariables from './EnvironmentVariables.jsx'; import AdvancedServerParams from './AdvancedServerParams.jsx'; import AttachToSession from './AttachToSession.jsx'; import CapabilityEditor from './CapabilityEditor.jsx'; import CloudProviders from './CloudProviders.jsx'; import CloudProviderSelector from './CloudProviderSelector.jsx'; +import EnvironmentVariables from './EnvironmentVariables.jsx'; import SavedSessions from './SavedSessions.jsx'; import ServerTabCustom from './ServerTabCustom.jsx'; import SessionStyles from './Session.module.css'; diff --git a/app/common/renderer/reducers/Inspector.js b/app/common/renderer/reducers/Inspector.js index 42c8e9f182..e360bb5192 100644 --- a/app/common/renderer/reducers/Inspector.js +++ b/app/common/renderer/reducers/Inspector.js @@ -86,9 +86,9 @@ import { UNSELECT_TICK_ELEMENT, } from '../actions/Inspector'; import { - SET_ENVIRONMENT_VARIABLES, ADD_ENVIRONMENT_VARIABLE, DELETE_ENVIRONMENT_VARIABLE, + SET_ENVIRONMENT_VARIABLES, } from '../actions/Session'; import {SCREENSHOT_INTERACTION_MODE} from '../constants/screenshot'; import {APP_MODE, INSPECTOR_TABS, NATIVE_APP} from '../constants/session-inspector'; @@ -141,7 +141,6 @@ let nextState; export default function inspector(state = INITIAL_STATE, action) { switch (action.type) { - case SET_SOURCE_AND_SCREENSHOT: return { ...state, @@ -677,7 +676,7 @@ export default function inspector(state = INITIAL_STATE, action) { ...state, environmentVariables: [ ...(state.environmentVariables || []), - {key: action.key, value: action.value} + {key: action.key, value: action.value}, ], }; @@ -685,7 +684,7 @@ export default function inspector(state = INITIAL_STATE, action) { return { ...state, environmentVariables: (state.environmentVariables || []).filter( - (envVar) => envVar.key !== action.key + (envVar) => envVar.key !== action.key, ), }; diff --git a/app/common/renderer/reducers/Session.js b/app/common/renderer/reducers/Session.js index 12541daa17..3c37a60d58 100644 --- a/app/common/renderer/reducers/Session.js +++ b/app/common/renderer/reducers/Session.js @@ -4,7 +4,9 @@ import { ABORT_DESIRED_CAPS_EDITOR, ABORT_DESIRED_CAPS_NAME_EDITOR, ADD_CAPABILITY, + ADD_ENVIRONMENT_VARIABLE, CHANGE_SERVER_TYPE, + DELETE_ENVIRONMENT_VARIABLE, DELETE_SAVED_SESSION_DONE, DELETE_SAVED_SESSION_REQUESTED, ENABLE_DESIRED_CAPS_EDITOR, @@ -30,6 +32,7 @@ import { SET_CAPABILITY_PARAM, SET_CAPS_AND_SERVER, SET_DESIRED_CAPS_NAME, + SET_ENVIRONMENT_VARIABLES, SET_PROVIDERS, SET_RAW_DESIRED_CAPS, SET_SAVE_AS_TEXT, @@ -39,9 +42,6 @@ import { SET_STATE_FROM_URL, SHOW_DESIRED_CAPS_JSON_ERROR, SWITCHED_TABS, - SET_ENVIRONMENT_VARIABLES, - ADD_ENVIRONMENT_VARIABLE, - DELETE_ENVIRONMENT_VARIABLE } from '../actions/Session'; import {SERVER_TYPES, SESSION_BUILDER_TABS} from '../constants/session-builder'; @@ -97,13 +97,14 @@ export default function session(state = INITIAL_STATE, action) { environmentVariables: new Map(state.environmentVariables).set(action.key, action.value), }; - case DELETE_ENVIRONMENT_VARIABLE: + case DELETE_ENVIRONMENT_VARIABLE: { const newEnvVars = new Map(state.environmentVariables); newEnvVars.delete(action.key); return { ...state, environmentVariables: newEnvVars, }; + } case NEW_SESSION_REQUESTED: return {