Fix the selected entry behavior by propagating the focusedEntryId through WebSocket (before #452) TRA-3983 (#513)

* Revert the select entry behavior into its original state RACING! (before #452) [TRA-3983 alternative 3]

* Remove the remaining `forceSelect`(s)

* Add a missing `focusedEntryId` prop

* Fix the race condition

* Propagate the `focusedEntryId` through WebSocket to prevent racing
This commit is contained in:
M. Mert Yıldıran 2021-11-30 15:27:10 +03:00 committed by GitHub
parent 9696ad9bad
commit 873f252544
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 107 additions and 63 deletions

View File

@ -6,6 +6,7 @@ import (
"fmt"
"mizuserver/pkg/models"
"net/http"
"strconv"
"sync"
"time"
@ -94,6 +95,8 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
startTimeBytes, _ := models.CreateWebsocketStartTimeMessage(startTime)
SendToSocket(socketId, startTimeBytes)
queryRecieved := false
for {
_, msg, err := ws.ReadMessage()
if err != nil {
@ -101,65 +104,75 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
break
}
if !isTapper && !isQuerySet {
query := string(msg)
err = basenine.Validate(shared.BasenineHost, shared.BaseninePort, query)
if err != nil {
toastBytes, _ := models.CreateWebsocketToastMessage(&models.ToastMessage{
Type: "error",
AutoClose: 5000,
Text: fmt.Sprintf("Syntax error: %s", err.Error()),
})
SendToSocket(socketId, toastBytes)
break
}
isQuerySet = true
handleDataChannel := func(c *basenine.Connection, data chan []byte) {
for {
bytes := <-data
if string(bytes) == basenine.CloseChannel {
return
}
var dataMap map[string]interface{}
err = json.Unmarshal(bytes, &dataMap)
base := dataMap["base"].(map[string]interface{})
base["id"] = uint(dataMap["id"].(float64))
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(base)
SendToSocket(socketId, baseEntryBytes)
if !queryRecieved {
if !isTapper && !isQuerySet {
queryRecieved = true
query := string(msg)
err = basenine.Validate(shared.BasenineHost, shared.BaseninePort, query)
if err != nil {
toastBytes, _ := models.CreateWebsocketToastMessage(&models.ToastMessage{
Type: "error",
AutoClose: 5000,
Text: fmt.Sprintf("Syntax error: %s", err.Error()),
})
SendToSocket(socketId, toastBytes)
break
}
}
handleMetaChannel := func(c *basenine.Connection, meta chan []byte) {
for {
bytes := <-meta
isQuerySet = true
if string(bytes) == basenine.CloseChannel {
return
handleDataChannel := func(c *basenine.Connection, data chan []byte) {
for {
bytes := <-data
if string(bytes) == basenine.CloseChannel {
return
}
var dataMap map[string]interface{}
err = json.Unmarshal(bytes, &dataMap)
base := dataMap["base"].(map[string]interface{})
base["id"] = uint(dataMap["id"].(float64))
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(base)
SendToSocket(socketId, baseEntryBytes)
}
var metadata *basenine.Metadata
err = json.Unmarshal(bytes, &metadata)
if err != nil {
logger.Log.Debugf("Error recieving metadata: %v", err.Error())
}
metadataBytes, _ := models.CreateWebsocketQueryMetadataMessage(metadata)
SendToSocket(socketId, metadataBytes)
}
handleMetaChannel := func(c *basenine.Connection, meta chan []byte) {
for {
bytes := <-meta
if string(bytes) == basenine.CloseChannel {
return
}
var metadata *basenine.Metadata
err = json.Unmarshal(bytes, &metadata)
if err != nil {
logger.Log.Debugf("Error recieving metadata: %v", err.Error())
}
metadataBytes, _ := models.CreateWebsocketQueryMetadataMessage(metadata)
SendToSocket(socketId, metadataBytes)
}
}
go handleDataChannel(connection, data)
go handleMetaChannel(connection, meta)
connection.Query(query, data, meta)
} else {
eventHandlers.WebSocketMessage(socketId, msg)
}
go handleDataChannel(connection, data)
go handleMetaChannel(connection, meta)
connection.Query(query, data, meta)
} else {
eventHandlers.WebSocketMessage(socketId, msg)
id, err := strconv.Atoi(string(msg))
if err != nil {
continue
}
focusEntryBytes, _ := models.CreateWebsocketFocusEntry(id)
SendToSocket(socketId, focusEntryBytes)
}
}
}

View File

@ -57,6 +57,11 @@ type WebSocketStartTimeMessage struct {
Data int64 `json:"data"`
}
type WebSocketFocusEntryMessage struct {
*shared.WebSocketMessageMetadata
Id int `json:"id"`
}
func CreateBaseEntryWebSocketMessage(base map[string]interface{}) ([]byte, error) {
message := &WebSocketEntryMessage{
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
@ -117,6 +122,16 @@ func CreateWebsocketStartTimeMessage(base int64) ([]byte, error) {
return json.Marshal(message)
}
func CreateWebsocketFocusEntry(id int) ([]byte, error) {
message := &WebSocketFocusEntryMessage{
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
MessageType: shared.WebSocketMessageFocusEntry,
},
Id: id,
}
return json.Marshal(message)
}
// ExtendedHAR is the top level object of a HAR log.
type ExtendedHAR struct {
Log *ExtendedLog `json:"log"`

View File

@ -1,11 +1,12 @@
package shared
import (
"io/ioutil"
"strings"
"github.com/op/go-logging"
"github.com/up9inc/mizu/shared/logger"
"github.com/up9inc/mizu/tap/api"
"io/ioutil"
"strings"
"gopkg.in/yaml.v3"
)
@ -21,6 +22,7 @@ const (
WebSocketMessageTypeToast WebSocketMessageType = "toast"
WebSocketMessageTypeQueryMetadata WebSocketMessageType = "queryMetadata"
WebSocketMessageTypeStartTime WebSocketMessageType = "startTime"
WebSocketMessageFocusEntry WebSocketMessageType = "focusEntry"
)
type Resources struct {

View File

@ -69,10 +69,10 @@ const EntrySummary: React.FC<any> = ({data, updateQuery}) => {
return <EntryItem
key={entry.id}
entry={entry}
focusedEntryId={null}
setFocusedEntryId={null}
style={{}}
updateQuery={updateQuery}
forceSelect={false}
headingMode={true}
/>;
};

View File

@ -1,4 +1,4 @@
import React, {useState} from "react";
import React from "react";
import styles from './EntryListItem.module.sass';
import StatusCode, {getClassification, StatusCodeClassification} from "../UI/StatusCode";
import Protocol, {ProtocolInterface} from "../UI/Protocol"
@ -37,16 +37,16 @@ interface Rules {
interface EntryProps {
entry: Entry;
focusedEntryId: string;
setFocusedEntryId: (id: string) => void;
style: object;
updateQuery: any;
forceSelect: boolean;
headingMode: boolean;
}
export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, style, updateQuery, forceSelect, headingMode}) => {
export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocusedEntryId, style, updateQuery, headingMode}) => {
const [isSelected, setIsSelected] = useState(!forceSelect ? false : true);
const isSelected = focusedEntryId === entry.id.toString();
const classification = getClassification(entry.statusCode)
const numberOfRules = entry.rules.numberOfRules
@ -125,7 +125,6 @@ export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, style
${isSelected && !rule && !contractEnabled ? styles.rowSelected : additionalRulesProperties}`}
onClick={() => {
if (!setFocusedEntryId) return;
setIsSelected(!isSelected);
setFocusedEntryId(entry.id.toString());
}}
style={{

View File

@ -119,20 +119,20 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
switch (message.messageType) {
case "entry":
const entry = message.data;
var forceSelect = false;
var focusThis = false;
if (!focusedEntryId) {
focusThis = true;
setFocusedEntryId(entry.id.toString());
forceSelect = true;
}
setEntriesBuffer([
...entriesBuffer,
<EntryItem
key={entry.id}
entry={entry}
focusedEntryId={focusThis ? entry.id.toString() : focusedEntryId}
setFocusedEntryId={setFocusedEntryId}
style={{}}
updateQuery={updateQuery}
forceSelect={forceSelect}
headingMode={false}
/>
]);
@ -166,6 +166,16 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
case "startTime":
setStartTime(message.data);
break;
case "focusEntry":
// To achieve selecting only one entry, render all elements in the buffer
// with the current `focusedEntryId` value.
entriesBuffer.forEach((entry: any, i: number) => {
entriesBuffer[i] = React.cloneElement(entry, {
focusedEntryId: focusedEntryId
});
})
setEntries(entriesBuffer);
break;
default:
console.error(`unsupported websocket message type, Got: ${message.messageType}`)
}
@ -191,6 +201,11 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
useEffect(() => {
if (!focusedEntryId) return;
setSelectedEntryData(null);
if (ws.current.readyState === WebSocket.OPEN) {
ws.current.send(focusedEntryId);
}
(async () => {
try {
const entryData = await api.getEntry(focusedEntryId);