Skip to content

Commit

Permalink
Modify event data using the debugger (#533)
Browse files Browse the repository at this point in the history
Added the possibility to modify the logs
that each MIR node sends by
modifying the incoming message.
(For now, only the JSON leaves are modifiable. )
All the JSON types have been taken care of:

it's important to notice that once
a 'null' field is clicked to modify,
it will become a 'string type afterward'.
For other types, the type will be preserved
if the newly modified value is acceptable for that specific type.
For example, inserting 'true' into a boolean field
will remain a boolean, but using 'tru' will result
in it becoming a string field.

Co-authored-by: Andrea Jiang <jianga@ethz.ch>
  • Loading branch information
andreaj00 and andreaj00 authored Feb 16, 2024
1 parent 1d36644 commit fab12b4
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 8 deletions.
12 changes: 12 additions & 0 deletions frontend/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,16 @@ button {
border-radius: 5px;
margin-top: 5px;
overflow: auto;
}

/* EditableText*/
/* Style for spans with text content */
.editable-text-span {
border: 1px solid #000;
}

/* Style for empty spans with padding */
.editable-text-span:empty {
padding-left: 10px;
padding-right: 10px;
}
213 changes: 205 additions & 8 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@

<script data-plugins="transform-modules-umd" type="text/babel" data-presets="react" data-type="module">
// Import React and its hooks
const { useState, useEffect } = React;
const { useState, useEffect, useRef } = React;

// WebSocketConsole component
const WebSocketConsole = ({ port }) => {
const [ws, setWs] = useState(null);
const [incomingMessage, setIncomingMessage] = useState('');
const [editableText, setEditableText] = useState('');
const [errors, setErrors] = useState([]);
const [loggedMessages, setLoggedMessages] = useState([]);
let incomingMessageJSON= useRef({});

// After the element rendering, connect to the given websocket port
useEffect(() => {
Expand All @@ -50,6 +52,14 @@
};
}, []);

// When a new incomingMessage log is given, parse it to editable
useEffect(() => {
if (incomingMessage !== null && incomingMessage !== "" ){
incomingMessageJSON.current = JSON.parse(incomingMessage);
setEditableText(jsonToEditableText(incomingMessageJSON.current, null, '', 0));
}
}, [incomingMessage]);

function handleOpenWebSocket(){
const message = `WebSocket connection for Port ${port} established.`
setLoggedMessages(prevMessages => [...prevMessages, message]);
Expand All @@ -62,7 +72,7 @@
function handleMessageWebSocket(event){
const message = decomposeJSON(event.data);
console.log("currently in sync and waiting", port);
setIncomingMessage(message);
setIncomingMessage(message); // This will launch the useEffect dependent on IncomingMessage
}

function handleErrorWebSocket(event) {
Expand Down Expand Up @@ -91,15 +101,20 @@
return JSON.stringify(parsedMessage, null, 2);
}

const clearIncomingLogMessages = () => {
setIncomingMessage("");
setEditableText("");
incomingMessageJSON = {};
}

const acceptIncomingLog = () => {
if(incomingMessage === ""){
return;
}
console.log("new message accepted on port: ", port);

// Accept Log and clear the input
// Accept ORIGINAL Log (without modifications)
setLoggedMessages(prevMessages => [...prevMessages, incomingMessage]);
setIncomingMessage("");

// Send the response on the WebSocket
let webSocketResponse = {
Expand All @@ -108,23 +123,204 @@
};
const webSocketResponseJSON = JSON.stringify(webSocketResponse);
ws.send(webSocketResponseJSON);

// Clear the input
clearIncomingLogMessages();
};

const declineIncomingLog = () => {
const replaceIncomingLog = () => {
if(incomingMessage === ""){
return;
}

// Decline Log and clear the input
setIncomingMessage("");
// Accept REPLACED Log
const acceptingModifiedMessage = JSON.stringify(incomingMessageJSON.current, null, 2)
setLoggedMessages(prevMessages => [...prevMessages, acceptingModifiedMessage]);

// Send the response on the WebSocket

// Note: This parsing function is closely tied to the composition in websocketwriter.go.
// We expect the following structure:
// {
// event: string(message),
// timestamp: "sending_timestamp"
// }
// Here, we need to perform double parsing for the 'event' value as it was originally also in JSON format.
incomingMessageJSON.current.event = JSON.stringify(incomingMessageJSON.current.event);
let webSocketResponse = {
"Type": "replace",
"Value": JSON.stringify(incomingMessageJSON.current)
};
const webSocketResponseJSON = JSON.stringify(webSocketResponse);
console.log("webSocketResponseJSON", webSocketResponseJSON, webSocketResponse)
ws.send(webSocketResponseJSON);

// Clear the input
clearIncomingLogMessages();
};

const declineIncomingLog = () => {
if(incomingMessage === ""){
return;
}

// Send the decline response on the WebSocket
let webSocketResponse = {
"Type": "decline",
"Value": ""
};
const webSocketResponseJSON = JSON.stringify(webSocketResponse);
ws.send(webSocketResponseJSON);

// Clear the input
clearIncomingLogMessages();
};

// Modifiable text component: When clicking on it, it becomes an input text
const EditableText = ({ text, textType, jsonReference, jsonReferenceKey, onUpdate }) => {
if(text === null || text === 'undefined'){
text = ""; // Set text to an empty string if it's null or undefined
}
const [isEditing, setIsEditing] = useState(false);
const [editedText, setEditedText] = useState(text);
const editedTextType = useRef(textType);
const textRef = useRef();
const myJsonReference = useRef(jsonReference);
const myJsonReferenceKey = useRef(jsonReferenceKey);
console.log("Initializing myJsonReference", myJsonReference, text, jsonReference);


const handleDoubleClick = () => {
setIsEditing(true);
};

const handleBlur = () => {
setIsEditing(false);

// Update parent with the new text
onUpdate(editedText, editedTextType.current, myJsonReference.current, myJsonReferenceKey.current);
};

const handleChange = (e) => {
setEditedText(e.target.value);
};

return (
<label onClick={handleDoubleClick}>
{isEditing ? (
<input
type="text"
value={editedText}
onChange={handleChange}
onBlur={handleBlur}
ref={textRef}
/>
) : (
<span className="editable-text-span">{editedText}</span>
)}
</label>
);
};

// Recursive function to parse the JSON and convert the leafs into EditableText
// Remember that the JSON should be passed by reference in JavaScript; therefore, we can modify the same object.
// Note that if the text is a boolean, it will be converted firstly to a string. After modification,
// if it is still an acceptable value for a boolean, it will be converted back.
// Note that if the text is a number, it will be converted firstly to a string. After modification,
// if it is still an acceptable value for a number, it will be converted back.
// Note that if the text is null, once the text is clicked, it will become an empty string.
const jsonToEditableText = (json, parentJson, parentKey, depth) => {
const space = ' '; // Two spaces for each level of depth
const indentation = space.repeat(depth);
if (typeof json === 'object' && json !== null) {
if (Array.isArray(json)) {
// Array object
return (
<div>
{indentation}&#91;
{
json.map((item, index) => {
return (
<div key={index}>
{indentation}{space}
{jsonToEditableText(item, json, index, depth + 1)}
</div>
);
})
}
{indentation}&#93;
</div>
);
} else if (Object.keys(json).length === 0) {
// Empty object
// Return inline {}
return (
<span> &#123;&#125;
</span>
);
}
else{
// Normal non-empty Object
return (
<div>
{indentation}&#123;
{
Object.keys(json).map((key) => {
return (
<div key={key}>
{indentation}
<strong>{space}"{key}":</strong> {jsonToEditableText(json[key], json, key, depth + 1)}
</div>
);
})
}
{indentation}&#125;
</div>
);
}
} else {
// Manage json type edge cases
// Skipped the edge case of given a number, as a number will be automatically converted to string
// Manage the case is given a boolean and convert it back to string to print
const textType = typeof json;
if (textType === 'boolean') {
json = json ? 'true' : 'false';
}
return (
<EditableText
text={json}
textType= {textType}
jsonReference={parentJson}
jsonReferenceKey = {parentKey}
onUpdate={(newText, textType, jsonReference, jsonReferenceKey) => {
// On screen print it as string but on the background save it with the actual type
json = newText; // String on screen
let mappingValue = newText;
if (textType === 'boolean') {
if (mappingValue === 'true') {
mappingValue = true
}
else if (mappingValue === 'false') {
mappingValue = false
}
else { // The boolean has been converted to a string
textType = 'string'
}
}
else if (textType === 'number') {
const temp = Number(mappingValue)
if (isNaN(temp)) { // Not a valid Number
textType = 'string'
}
else {
mappingValue = temp;
}
}
jsonReference[jsonReferenceKey] = mappingValue; // Actual value (NOT String)
}}
/>
);
}
};

return (
Expand All @@ -142,10 +338,11 @@
<div id="incoming-log-div" className="incoming-log-div">
Incoming Log:
<div id="incoming-log-placeholder" className="incoming-log-screen json-message">
<pre>{incomingMessage}</pre>
<pre>{editableText}</pre>
</div>
<div id="incoming-log-buttons" className="incoming-log-buttons">
<button onClick={acceptIncomingLog}>Accept</button>
<button onClick={replaceIncomingLog}>Replace</button>
<button onClick={declineIncomingLog}>Decline</button>
</div>
</div>
Expand Down

0 comments on commit fab12b4

Please sign in to comment.