diff --git a/api/Pipfile b/api/Pipfile
index d5c52995c..ffd5906f5 100644
--- a/api/Pipfile
+++ b/api/Pipfile
@@ -5,7 +5,7 @@ name = "pypi"
[packages]
sqlparse = "==0.5.2"
-urllib3 = "==1.26.16"
+urllib3 = "==2.2.3"
requests = "==2.32.3"
boto3 = "==1.35.76"
pyjwt = "==2.10.1"
@@ -21,7 +21,7 @@ uvicorn = {extras = ["standard"], version = "==0.32.1"}
python-decouple = "==3.8"
pydantic = {extras = ["email"], version = "==2.10.3"}
apscheduler = "==3.11.0"
-redis = "==5.2.0"
+redis = "==5.2.1"
[dev-packages]
diff --git a/api/auth/auth_jwt.py b/api/auth/auth_jwt.py
index fd4d145b1..2e30e6975 100644
--- a/api/auth/auth_jwt.py
+++ b/api/auth/auth_jwt.py
@@ -45,8 +45,6 @@ class JWTAuth(HTTPBearer):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid authentication scheme.")
jwt_payload = authorizers.jwt_authorizer(scheme=credentials.scheme, token=credentials.credentials)
- logger.info("------ jwt_payload ------")
- logger.info(jwt_payload)
auth_exists = jwt_payload is not None and users.auth_exists(user_id=jwt_payload.get("userId", -1),
jwt_iat=jwt_payload.get("iat", 100))
if jwt_payload is None \
@@ -120,8 +118,7 @@ class JWTAuth(HTTPBearer):
jwt_payload = None
else:
jwt_payload = authorizers.jwt_refresh_authorizer(scheme="Bearer", token=request.cookies["spotRefreshToken"])
- logger.info("__process_spot_refresh_call")
- logger.info(jwt_payload)
+
if jwt_payload is None or jwt_payload.get("jti") is None:
logger.warning("Null spotRefreshToken's payload, or null JTI.")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,
diff --git a/api/chalicelib/core/custom_metrics.py b/api/chalicelib/core/custom_metrics.py
index c3ac00b69..01f04803a 100644
--- a/api/chalicelib/core/custom_metrics.py
+++ b/api/chalicelib/core/custom_metrics.py
@@ -12,20 +12,6 @@ from chalicelib.utils.TimeUTC import TimeUTC
logger = logging.getLogger(__name__)
-# TODO: refactor this to split
-# timeseries /
-# table of errors / table of issues / table of browsers / table of devices / table of countries / table of URLs
-# remove "table of" calls from this function
-def __try_live(project_id, data: schemas.CardSchema):
- results = []
- for i, s in enumerate(data.series):
- results.append(sessions.search2_series(data=s.filter, project_id=project_id, density=data.density,
- view_type=data.view_type, metric_type=data.metric_type,
- metric_of=data.metric_of, metric_value=data.metric_value))
-
- return results
-
-
def __get_table_of_series(project_id, data: schemas.CardSchema):
results = []
for i, s in enumerate(data.series):
@@ -43,9 +29,6 @@ def __get_funnel_chart(project: schemas.ProjectContext, data: schemas.CardFunnel
"totalDropDueToIssues": 0
}
- # return funnels.get_top_insights_on_the_fly_widget(project_id=project_id,
- # data=data.series[0].filter,
- # metric_format=data.metric_format)
return funnels.get_simple_funnel(project=project,
data=data.series[0].filter,
metric_format=data.metric_format)
@@ -93,7 +76,12 @@ def __get_path_analysis_chart(project: schemas.ProjectContext, user_id: int, dat
def __get_timeseries_chart(project: schemas.ProjectContext, data: schemas.CardTimeSeries, user_id: int = None):
- series_charts = __try_live(project_id=project.project_id, data=data)
+ series_charts = []
+ for i, s in enumerate(data.series):
+ series_charts.append(sessions.search2_series(data=s.filter, project_id=project.project_id, density=data.density,
+ view_type=data.view_type, metric_type=data.metric_type,
+ metric_of=data.metric_of, metric_value=data.metric_value))
+
results = [{}] * len(series_charts[0])
for i in range(len(results)):
for j, series_chart in enumerate(series_charts):
@@ -179,12 +167,6 @@ def get_chart(project: schemas.ProjectContext, data: schemas.CardSchema, user_id
def get_sessions_by_card_id(project_id, user_id, metric_id, data: schemas.CardSessionsSchema):
- # No need for this because UI is sending the full payload
- # card: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
- # if card is None:
- # return None
- # metric: schemas.CardSchema = schemas.CardSchema(**card)
- # metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
if not card_exists(metric_id=metric_id, project_id=project_id, user_id=user_id):
return None
results = []
@@ -553,17 +535,7 @@ def change_state(project_id, metric_id, user_id, status):
def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id,
- data: schemas.CardSessionsSchema
- # , range_value=None, start_date=None, end_date=None
- ):
- # No need for this because UI is sending the full payload
- # card: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
- # if card is None:
- # return None
- # metric: schemas.CardSchema = schemas.CardSchema(**card)
- # metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
- # if metric is None:
- # return None
+ data: schemas.CardSessionsSchema):
if not card_exists(metric_id=metric_id, project_id=project_id, user_id=user_id):
return None
for s in data.series:
diff --git a/api/chalicelib/utils/ch_client_exp.py b/api/chalicelib/utils/ch_client_exp.py
index edb666d09..8bdb4c20b 100644
--- a/api/chalicelib/utils/ch_client_exp.py
+++ b/api/chalicelib/utils/ch_client_exp.py
@@ -2,13 +2,22 @@ import logging
import threading
import time
from functools import wraps
-from queue import Queue
+from queue import Queue, Empty
import clickhouse_connect
from clickhouse_connect.driver.query import QueryContext
+from clickhouse_connect.driver.exceptions import DatabaseError
from decouple import config
logger = logging.getLogger(__name__)
+
+_CH_CONFIG = {"host": config("ch_host"),
+ "user": config("ch_user", default="default"),
+ "password": config("ch_password", default=""),
+ "port": config("ch_port_http", cast=int),
+ "client_name": config("APP_NAME", default="PY")}
+CH_CONFIG = dict(_CH_CONFIG)
+
settings = {}
if config('ch_timeout', cast=int, default=-1) > 0:
logging.info(f"CH-max_execution_time set to {config('ch_timeout')}s")
@@ -26,6 +35,7 @@ if config("CH_COMPRESSION", cast=bool, default=True):
def transform_result(original_function):
@wraps(original_function)
def wrapper(*args, **kwargs):
+ logger.info("Executing query on CH")
result = original_function(*args, **kwargs)
if isinstance(result, clickhouse_connect.driver.query.QueryResult):
column_names = result.column_names
@@ -38,21 +48,17 @@ def transform_result(original_function):
class ClickHouseConnectionPool:
- def __init__(self, min_size, max_size, settings):
+ def __init__(self, min_size, max_size):
self.min_size = min_size
self.max_size = max_size
self.pool = Queue()
self.lock = threading.Lock()
self.total_connections = 0
- self.settings = settings
# Initialize the pool with min_size connections
for _ in range(self.min_size):
- client = clickhouse_connect.get_client(host=config("ch_host"),
+ client = clickhouse_connect.get_client(**CH_CONFIG,
database=config("ch_database", default="default"),
- user=config("ch_user", default="default"),
- password=config("ch_password", default=""),
- port=config("ch_port_http", cast=int),
settings=settings,
**extra_args)
self.pool.put(client)
@@ -66,15 +72,10 @@ class ClickHouseConnectionPool:
except Empty:
with self.lock:
if self.total_connections < self.max_size:
- client = clickhouse_connect.get_client(
- host=config("ch_host"),
- database=config("ch_database", default="default"),
- user=config("ch_user", default="default"),
- password=config("ch_password", default=""),
- port=config("ch_port_http", cast=int),
- settings=settings,
- **extra_args
- )
+ client = clickhouse_connect.get_client(**CH_CONFIG,
+ database=config("ch_database", default="default"),
+ settings=settings,
+ **extra_args)
self.total_connections += 1
return client
# If max_size reached, wait until a connection is available
@@ -110,12 +111,11 @@ def make_pool():
except Exception as error:
logger.error("Error while closing all connexions to CH", error)
try:
- CH_pool = ClickHouseConnectionPool(min_size=config("PG_MINCONN", cast=int, default=4),
- max_size=config("PG_MAXCONN", cast=int, default=8),
- settings=settings)
+ CH_pool = ClickHouseConnectionPool(min_size=config("CH_MINCONN", cast=int, default=4),
+ max_size=config("CH_MAXCONN", cast=int, default=8))
if CH_pool is not None:
logger.info("Connection pool created successfully for CH")
- except Exception as error:
+ except ConnectionError as error:
logger.error("Error while connecting to CH", error)
if RETRY < RETRY_MAX:
RETRY += 1
@@ -131,15 +131,12 @@ class ClickHouseClient:
def __init__(self, database=None):
if self.__client is None:
- if config('CH_POOL', cast=bool, default=True):
+ if database is None and config('CH_POOL', cast=bool, default=True):
self.__client = CH_pool.get_connection()
else:
- self.__client = clickhouse_connect.get_client(host=config("ch_host"),
+ self.__client = clickhouse_connect.get_client(**CH_CONFIG,
database=database if database else config("ch_database",
default="default"),
- user=config("ch_user", default="default"),
- password=config("ch_password", default=""),
- port=config("ch_port_http", cast=int),
settings=settings,
**extra_args)
self.__client.execute = transform_result(self.__client.query)
@@ -164,7 +161,7 @@ class ClickHouseClient:
async def init():
- logger.info(f">CH_POOL:{config('CH_POOL', default=None)}")
+ logger.info(f">use CH_POOL:{config('CH_POOL', default=True)}")
if config('CH_POOL', cast=bool, default=True):
make_pool()
diff --git a/api/chalicelib/utils/pg_client.py b/api/chalicelib/utils/pg_client.py
index b97ab005e..29ea84873 100644
--- a/api/chalicelib/utils/pg_client.py
+++ b/api/chalicelib/utils/pg_client.py
@@ -166,7 +166,7 @@ class PostgresClient:
async def init():
- logger.info(f">PG_POOL:{config('PG_POOL', default=None)}")
+ logger.info(f">use PG_POOL:{config('PG_POOL', default=True)}")
if config('PG_POOL', cast=bool, default=True):
make_pool()
diff --git a/api/env.default b/api/env.default
index f6ca1872b..4e52e5c57 100644
--- a/api/env.default
+++ b/api/env.default
@@ -10,8 +10,8 @@ captcha_key=
captcha_server=
CH_COMPRESSION=true
ch_host=
-ch_port=
-ch_port_http=
+ch_port=9000
+ch_port_http=8123
ch_receive_timeout=10
ch_timeout=30
change_password_link=/reset-password?invitation=%s&&pass=%s
diff --git a/api/requirements-alerts.txt b/api/requirements-alerts.txt
index 4f356e46c..ee8bbc950 100644
--- a/api/requirements-alerts.txt
+++ b/api/requirements-alerts.txt
@@ -1,5 +1,4 @@
-# Keep this version to not have conflicts between requests and boto3
-urllib3==1.26.16
+urllib3==2.2.3
requests==2.32.3
boto3==1.35.76
pyjwt==2.10.1
diff --git a/api/requirements.txt b/api/requirements.txt
index 943a36e40..d643061f1 100644
--- a/api/requirements.txt
+++ b/api/requirements.txt
@@ -1,5 +1,4 @@
-# Keep this version to not have conflicts between requests and boto3
-urllib3==1.26.16
+urllib3==2.2.3
requests==2.32.3
boto3==1.35.76
pyjwt==2.10.1
@@ -19,4 +18,4 @@ python-decouple==3.8
pydantic[email]==2.10.3
apscheduler==3.11.0
-redis==5.2.0
+redis==5.2.1
diff --git a/backend/pkg/integrations/clients/elastic.go b/backend/pkg/integrations/clients/elastic.go
index e854860b3..2d3ffc4e2 100644
--- a/backend/pkg/integrations/clients/elastic.go
+++ b/backend/pkg/integrations/clients/elastic.go
@@ -5,7 +5,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
- "log"
"strings"
"github.com/elastic/go-elasticsearch/v8"
@@ -55,7 +54,7 @@ func (e *elasticsearchClient) FetchSessionData(credentials interface{}, sessionI
// Create Elasticsearch client
es, err := elasticsearch.NewClient(clientCfg)
if err != nil {
- log.Fatalf("Error creating the client: %s", err)
+ return nil, fmt.Errorf("error creating the client: %s", err)
}
var buf strings.Builder
@@ -79,17 +78,17 @@ func (e *elasticsearchClient) FetchSessionData(credentials interface{}, sessionI
es.Search.WithTrackTotalHits(true),
)
if err != nil {
- log.Fatalf("Error getting response: %s", err)
+ return nil, fmt.Errorf("error getting response: %s", err)
}
defer res.Body.Close()
if res.IsError() {
- log.Fatalf("Error: %s", res.String())
+ return nil, fmt.Errorf("error: %s", res.String())
}
var r map[string]interface{}
if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
- log.Fatalf("Error parsing the response body: %s", err)
+ return nil, fmt.Errorf("error parsing the response body: %s", err)
}
if r["hits"] == nil {
return nil, fmt.Errorf("no logs found")
diff --git a/backend/pkg/integrations/clients/sentry.go b/backend/pkg/integrations/clients/sentry.go
index a115e9558..99ddc995c 100644
--- a/backend/pkg/integrations/clients/sentry.go
+++ b/backend/pkg/integrations/clients/sentry.go
@@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io"
- "log"
"net/http"
"net/url"
)
@@ -62,7 +61,7 @@ func (s *sentryClient) FetchSessionData(credentials interface{}, sessionID uint6
// Create a new request
req, err := http.NewRequest("GET", requestUrl, nil)
if err != nil {
- log.Fatalf("Failed to create request: %v", err)
+ return nil, fmt.Errorf("failed to create request: %v", err)
}
// Add Authorization header
@@ -72,26 +71,26 @@ func (s *sentryClient) FetchSessionData(credentials interface{}, sessionID uint6
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
- log.Fatalf("Failed to send request: %v", err)
+ return nil, fmt.Errorf("failed to send request: %v", err)
}
defer resp.Body.Close()
// Check if the response status is OK
if resp.StatusCode != http.StatusOK {
- log.Fatalf("Failed to fetch logs, status code: %v", resp.StatusCode)
+ return nil, fmt.Errorf("failed to fetch logs, status code: %v", resp.StatusCode)
}
// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
- log.Fatalf("Failed to read response body: %v", err)
+ return nil, fmt.Errorf("failed to read response body: %v", err)
}
// Parse the JSON response
var events []SentryEvent
err = json.Unmarshal(body, &events)
if err != nil {
- log.Fatalf("Failed to parse JSON: %v", err)
+ return nil, fmt.Errorf("failed to parse JSON: %v", err)
}
if events == nil || len(events) == 0 {
return nil, fmt.Errorf("no logs found")
diff --git a/ee/api/Pipfile b/ee/api/Pipfile
index ee2617767..a32b99a8e 100644
--- a/ee/api/Pipfile
+++ b/ee/api/Pipfile
@@ -4,12 +4,12 @@ verify_ssl = true
name = "pypi"
[packages]
-urllib3 = "==1.26.16"
+urllib3 = "==2.2.3"
requests = "==2.32.3"
boto3 = "==1.35.76"
pyjwt = "==2.10.1"
psycopg2-binary = "==2.9.10"
-psycopg = {extras = ["binary", "pool"], version = "==3.2.3"}
+psycopg = {extras = ["pool", "binary"], version = "==3.2.3"}
clickhouse-driver = {extras = ["lz4"], version = "==0.2.9"}
clickhouse-connect = "==0.8.9"
elasticsearch = "==8.16.0"
@@ -21,10 +21,10 @@ gunicorn = "==23.0.0"
python-decouple = "==3.8"
pydantic = {extras = ["email"], version = "==2.10.3"}
apscheduler = "==3.11.0"
+redis = "==5.2.1"
python3-saml = "==1.16.0"
python-multipart = "==0.0.17"
-redis = "==5.2.0"
-azure-storage-blob = "==12.23.1"
+azure-storage-blob = "==12.24.0"
[dev-packages]
diff --git a/ee/api/env.default b/ee/api/env.default
index 9d30b0e0b..527bb56e3 100644
--- a/ee/api/env.default
+++ b/ee/api/env.default
@@ -11,8 +11,8 @@ captcha_key=
captcha_server=
CH_COMPRESSION=true
ch_host=
-ch_port=
-ch_port_http=
+ch_port=9000
+ch_port_http=8123
ch_receive_timeout=10
ch_timeout=30
change_password_link=/reset-password?invitation=%s&&pass=%s
diff --git a/ee/api/requirements-alerts.txt b/ee/api/requirements-alerts.txt
index ebf1aea0d..adfc42bba 100644
--- a/ee/api/requirements-alerts.txt
+++ b/ee/api/requirements-alerts.txt
@@ -1,5 +1,4 @@
-# Keep this version to not have conflicts between requests and boto3
-urllib3==1.26.16
+urllib3==2.2.3
requests==2.32.3
boto3==1.35.76
pyjwt==2.10.1
@@ -19,4 +18,4 @@ python-decouple==3.8
pydantic[email]==2.10.3
apscheduler==3.11.0
-azure-storage-blob==12.23.1
\ No newline at end of file
+azure-storage-blob==12.24.0
\ No newline at end of file
diff --git a/ee/api/requirements-crons.txt b/ee/api/requirements-crons.txt
index 6aa2370d8..68c115f1c 100644
--- a/ee/api/requirements-crons.txt
+++ b/ee/api/requirements-crons.txt
@@ -1,5 +1,4 @@
-# Keep this version to not have conflicts between requests and boto3
-urllib3==1.26.16
+urllib3==2.2.3
requests==2.32.3
boto3==1.35.76
pyjwt==2.10.1
@@ -19,4 +18,4 @@ pydantic[email]==2.10.3
apscheduler==3.11.0
redis==5.2.0
-azure-storage-blob==12.23.1
+azure-storage-blob==12.24.0
diff --git a/ee/api/requirements.txt b/ee/api/requirements.txt
index 0c0b2902a..538364050 100644
--- a/ee/api/requirements.txt
+++ b/ee/api/requirements.txt
@@ -1,5 +1,4 @@
-# Keep this version to not have conflicts between requests and boto3
-urllib3==1.26.16
+urllib3==2.2.3
requests==2.32.3
boto3==1.35.76
pyjwt==2.10.1
@@ -20,12 +19,13 @@ python-decouple==3.8
pydantic[email]==2.10.3
apscheduler==3.11.0
+redis==5.2.1
+
# TODO: enable after xmlsec fix https://github.com/xmlsec/python-xmlsec/issues/252
#--no-binary is used to avoid libxml2 library version incompatibilities between xmlsec and lxml
python3-saml==1.16.0
--no-binary=lxml
-python-multipart==0.0.17
+python-multipart==0.0.18
-redis==5.2.0
#confluent-kafka==2.1.0
-azure-storage-blob==12.23.1
+azure-storage-blob==12.24.0
diff --git a/frontend/app/components/Session/Player/MobilePlayer/MobileControls.tsx b/frontend/app/components/Session/Player/MobilePlayer/MobileControls.tsx
index 78f9d47d4..275f3e6e2 100644
--- a/frontend/app/components/Session/Player/MobilePlayer/MobileControls.tsx
+++ b/frontend/app/components/Session/Player/MobilePlayer/MobileControls.tsx
@@ -28,6 +28,7 @@ import {
import { useStore } from 'App/mstore';
import { session as sessionRoute, withSiteId } from 'App/routes';
import { SummaryButton } from 'Components/Session_/Player/Controls/Controls';
+import { MobEventsList, WebEventsList } from "../../../Session_/Player/Controls/EventsList";
import useShortcuts from '../ReplayPlayer/useShortcuts';
export const SKIP_INTERVALS = {
diff --git a/frontend/app/components/Session/Player/SharedComponents/BackendLogs/BackendLogsPanel.tsx b/frontend/app/components/Session/Player/SharedComponents/BackendLogs/BackendLogsPanel.tsx
index bc3880dc7..45807f5b2 100644
--- a/frontend/app/components/Session/Player/SharedComponents/BackendLogs/BackendLogsPanel.tsx
+++ b/frontend/app/components/Session/Player/SharedComponents/BackendLogs/BackendLogsPanel.tsx
@@ -91,6 +91,7 @@ function BackendLogsPanel() {
) : null}
+
{
+ if (showSingleTab) {
+ const stackEventList = tabStates[currentTab].stackList;
+ const frustrationsList = tabStates[currentTab].frustrationsList;
+ const exceptionsList = tabStates[currentTab].exceptionsList;
+ const resourceListUnmap = tabStates[currentTab].resourceList;
+ const fetchList = tabStates[currentTab].fetchList;
+ const graphqlList = tabStates[currentTab].graphqlList;
+ const performanceChartData =
+ tabStates[currentTab].performanceChartData;
+
+ return {
+ stackEventList,
+ frustrationsList,
+ exceptionsList,
+ resourceListUnmap,
+ fetchList,
+ graphqlList,
+ performanceChartData,
+ }
+ } else {
+ const stackEventList = tabValues.flatMap((tab) => tab.stackList);
+ // these two are global
+ const frustrationsList = tabValues[0].frustrationsList;
+ const exceptionsList = tabValues[0].exceptionsList;
+ // we can't compute global chart data because some tabs coexist
+ const performanceChartData: any = [];
+ const resourceListUnmap = tabValues.flatMap((tab) => tab.resourceList);
+ const fetchList = tabValues.flatMap((tab) => tab.fetchList);
+ const graphqlList = tabValues.flatMap((tab) => tab.graphqlList);
+
+ return {
+ stackEventList,
+ frustrationsList,
+ exceptionsList,
+ resourceListUnmap,
+ fetchList,
+ graphqlList,
+ performanceChartData,
+ }
+ }
+ }, [tabStates, currentTab, dataSource, tabValues]);
+
+ console.log(showSingleTab, frustrationsList, performanceChartData);
const fetchPresented = fetchList.length > 0;
const resourceList = resourceListUnmap
@@ -168,7 +218,18 @@ function WebOverviewPanelCont() {
PERFORMANCE: checkInZoomRange(performanceChartData),
FRUSTRATIONS: checkInZoomRange(frustrationsList),
};
- }, [tabStates, currentTab, zoomEnabled, zoomStartTs, zoomEndTs]);
+ }, [
+ tabStates,
+ currentTab,
+ zoomEnabled,
+ zoomStartTs,
+ zoomEndTs,
+ resourceList.length,
+ exceptionsList.length,
+ stackEventList.length,
+ performanceChartData.length,
+ frustrationsList.length,
+ ]);
const originStr = window.env.ORIGIN || window.location.origin;
const isSaas = /app\.openreplay\.com/.test(originStr);
@@ -187,6 +248,7 @@ function WebOverviewPanelCont() {
sessionId={sessionId}
setZoomTab={setZoomTab}
zoomTab={zoomTab}
+ showSingleTab={showSingleTab}
/>
);
}
@@ -238,6 +300,7 @@ function PanelComponent({
spotTime,
spotEndTime,
onClose,
+ showSingleTab,
}: any) {
return (
@@ -281,6 +344,7 @@ function PanelComponent({
{isSpot ? null : (
+
(
{
- const { title, className, list = [], endTime = 0, isGraph = false, message = '' } = props;
+ const { title, className, list = [], endTime = 0, isGraph = false, message = '', disabled } = props;
const scale = 100 / endTime;
const _list =
isGraph ? [] :
@@ -82,7 +83,7 @@ const EventRow = React.memo((props: Props) => {
}
return groupedItems;
- }, [list]);
+ }, [list.length]);
return (
{
{isGraph ? (
-
+
) : _list.length > 0 ? (
_list.map((item: { items: any[], left: number, isGrouped: boolean }, index: number) => {
const left = item.left
diff --git a/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx
index 2e719f377..f46d89d60 100644
--- a/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx
+++ b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx
@@ -2,81 +2,103 @@ import React from 'react';
import { AreaChart, Area, ResponsiveContainer } from 'recharts';
interface Props {
- list: any;
+ list: any;
+ disabled?: boolean;
}
const PerformanceGraph = React.memo((props: Props) => {
- const { list } = props;
+ const { list, disabled } = props;
- const finalValues = React.useMemo(() => {
- const cpuMax = list.reduce((acc: number, item: any) => {
- return Math.max(acc, item.cpu);
- }, 0);
- const cpuMin = list.reduce((acc: number, item: any) => {
- return Math.min(acc, item.cpu);
- }, Infinity);
+ const finalValues = React.useMemo(() => {
+ const cpuMax = list.reduce((acc: number, item: any) => {
+ return Math.max(acc, item.cpu);
+ }, 0);
+ const cpuMin = list.reduce((acc: number, item: any) => {
+ return Math.min(acc, item.cpu);
+ }, Infinity);
- const memoryMin = list.reduce((acc: number, item: any) => {
- return Math.min(acc, item.usedHeap);
- }, Infinity);
- const memoryMax = list.reduce((acc: number, item: any) => {
- return Math.max(acc, item.usedHeap);
- }, 0);
+ const memoryMin = list.reduce((acc: number, item: any) => {
+ return Math.min(acc, item.usedHeap);
+ }, Infinity);
+ const memoryMax = list.reduce((acc: number, item: any) => {
+ return Math.max(acc, item.usedHeap);
+ }, 0);
- const convertToPercentage = (val: number, max: number, min: number) => {
- return ((val - min) / (max - min)) * 100;
- };
- const cpuValues = list.map((item: any) => convertToPercentage(item.cpu, cpuMax, cpuMin));
- const memoryValues = list.map((item: any) => convertToPercentage(item.usedHeap, memoryMax, memoryMin));
- const mergeArraysWithMaxNumber = (arr1: any[], arr2: any[]) => {
- const maxLength = Math.max(arr1.length, arr2.length);
- const result = [];
- for (let i = 0; i < maxLength; i++) {
- const num = Math.round(Math.max(arr1[i] || 0, arr2[i] || 0));
- result.push(num > 60 ? num : 1);
- }
- return result;
- };
- const finalValues = mergeArraysWithMaxNumber(cpuValues, memoryValues);
- return finalValues;
- }, []);
-
- const data = list.map((item: any, index: number) => {
- return {
- time: item.time,
- cpu: finalValues[index],
- };
- });
-
- return (
-
-
-
-
-
-
-
-
- {/* */}
-
-
-
+ const convertToPercentage = (val: number, max: number, min: number) => {
+ return ((val - min) / (max - min)) * 100;
+ };
+ const cpuValues = list.map((item: any) =>
+ convertToPercentage(item.cpu, cpuMax, cpuMin)
);
+ const memoryValues = list.map((item: any) =>
+ convertToPercentage(item.usedHeap, memoryMax, memoryMin)
+ );
+ const mergeArraysWithMaxNumber = (arr1: any[], arr2: any[]) => {
+ const maxLength = Math.max(arr1.length, arr2.length);
+ const result = [];
+ for (let i = 0; i < maxLength; i++) {
+ const num = Math.round(Math.max(arr1[i] || 0, arr2[i] || 0));
+ result.push(num > 60 ? num : 1);
+ }
+ return result;
+ };
+ const finalValues = mergeArraysWithMaxNumber(cpuValues, memoryValues);
+ return finalValues;
+ }, [list.length]);
+
+ const data = list.map((item: any, index: number) => {
+ return {
+ time: item.time,
+ cpu: finalValues[index],
+ };
+ });
+
+ return (
+
+ {disabled ? (
+
+
Disabled for all tabs
+
+ ) : null}
+
+
+
+
+
+
+
+
+ {/* */}
+
+
+
+
+ );
});
export default PerformanceGraph;
diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx
index cc7f13f63..4518ddf1c 100644
--- a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx
+++ b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx
@@ -168,7 +168,7 @@ function GroupedIssue({
{items.length}
diff --git a/frontend/app/components/Session_/Performance/Performance.tsx b/frontend/app/components/Session_/Performance/Performance.tsx
index c0cdceb39..4227c234f 100644
--- a/frontend/app/components/Session_/Performance/Performance.tsx
+++ b/frontend/app/components/Session_/Performance/Performance.tsx
@@ -23,6 +23,7 @@ import stl from './performance.module.css';
import BottomBlock from '../BottomBlock';
import InfoLine from '../BottomBlock/InfoLine';
import { useStore } from 'App/mstore'
+import { Segmented } from 'antd'
const CPU_VISUAL_OFFSET = 10;
@@ -459,13 +460,16 @@ function Performance() {
Performance
-
-
-
+
+
+
+
+
+
diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx
index 0bddb0a23..d2dd042c0 100644
--- a/frontend/app/components/Session_/Player/Controls/Controls.tsx
+++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx
@@ -34,6 +34,7 @@ import { Icon } from 'UI';
import LogsButton from 'App/components/Session/Player/SharedComponents/BackendLogs/LogsButton';
import ControlButton from './ControlButton';
+import { WebEventsList } from "./EventsList";
import Timeline from './Timeline';
import PlayerControls from './components/PlayerControls';
import styles from './controls.module.css';
diff --git a/frontend/app/components/Session_/Player/Controls/EventsList.tsx b/frontend/app/components/Session_/Player/Controls/EventsList.tsx
index 41010c766..e9994771a 100644
--- a/frontend/app/components/Session_/Player/Controls/EventsList.tsx
+++ b/frontend/app/components/Session_/Player/Controls/EventsList.tsx
@@ -4,10 +4,12 @@ import { PlayerContext, MobilePlayerContext } from 'Components/Session/playerCon
import { observer } from 'mobx-react-lite';
import { getTimelinePosition } from './getTimelinePosition'
-function EventsList({ scale }: { scale: number }) {
+function EventsList() {
const { store } = useContext(PlayerContext);
- const { tabStates, eventCount } = store.get();
+ const { eventCount, endTime } = store.get();
+ const tabStates = store.get().tabStates;
+ const scale = 100 / endTime;
const events = React.useMemo(() => {
return Object.values(tabStates)[0]?.eventList.filter(e => e.time) || [];
}, [eventCount]);
@@ -34,11 +36,12 @@ function EventsList({ scale }: { scale: number }) {
);
}
-function MobileEventsList({ scale }: { scale: number }) {
+function MobileEventsList() {
const { store } = useContext(MobilePlayerContext);
- const { eventList } = store.get();
+ const { eventList, endTime } = store.get();
const events = eventList.filter(e => e.type !== 'SWIPE')
+ const scale = 100/endTime;
return (
<>
{events.map((e) => (
diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.tsx b/frontend/app/components/Session_/Player/Controls/Timeline.tsx
index 0888df62b..1d5d56467 100644
--- a/frontend/app/components/Session_/Player/Controls/Timeline.tsx
+++ b/frontend/app/components/Session_/Player/Controls/Timeline.tsx
@@ -13,11 +13,7 @@ import NotesList from './NotesList';
import SkipIntervalsList from './SkipIntervalsList';
import TimelineTracker from 'Components/Session_/Player/Controls/TimelineTracker';
-interface IProps {
- isMobile?: boolean;
-}
-
-function Timeline(props: IProps) {
+function Timeline({ isMobile }: { isMobile: boolean }) {
const { player, store } = useContext(PlayerContext);
const [wasPlaying, setWasPlaying] = useState(false);
const [maxWidth, setMaxWidth] = useState(0);
@@ -126,6 +122,7 @@ function Timeline(props: IProps) {
return Math.max(Math.round(p * targetTime), 0);
};
+ console.log(devtoolsLoading , domLoading, !ready)
return (
: null}
- {props.isMobile ?
:
}
+ {isMobile ?
:
}
diff --git a/frontend/app/components/Session_/Storage/Storage.tsx b/frontend/app/components/Session_/Storage/Storage.tsx
index 2b138728e..43c5fd851 100644
--- a/frontend/app/components/Session_/Storage/Storage.tsx
+++ b/frontend/app/components/Session_/Storage/Storage.tsx
@@ -1,18 +1,24 @@
import React from 'react';
-import { useStore } from 'App/mstore'
+import { useStore } from 'App/mstore';
import { PlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
import { JSONTree, NoContent, Tooltip } from 'UI';
import { formatMs } from 'App/date';
-import diff from 'microdiff'
-import { STORAGE_TYPES, selectStorageList, selectStorageListNow, selectStorageType } from 'Player';
+import diff from 'microdiff';
+import {
+ STORAGE_TYPES,
+ selectStorageList,
+ selectStorageListNow,
+ selectStorageType,
+} from 'Player';
import Autoscroll from '../Autoscroll';
import BottomBlock from '../BottomBlock/index';
import DiffRow from './DiffRow';
import cn from 'classnames';
import stl from './storage.module.css';
-import logger from "App/logger";
-import ReduxViewer from './ReduxViewer'
+import logger from 'App/logger';
+import ReduxViewer from './ReduxViewer';
+import { Segmented } from 'antd'
function getActionsName(type: string) {
switch (type) {
@@ -31,7 +37,7 @@ const storageDecodeKeys = {
[STORAGE_TYPES.ZUSTAND]: ['state', 'mutation'],
[STORAGE_TYPES.MOBX]: ['payload'],
[STORAGE_TYPES.NONE]: ['state, action', 'payload', 'mutation'],
-}
+};
function Storage() {
const { uiPlayerStore } = useStore();
@@ -42,49 +48,48 @@ function Storage() {
const [stateObject, setState] = React.useState({});
const { player, store } = React.useContext(PlayerContext);
- const { tabStates, currentTab } = store.get()
- const state = tabStates[currentTab] || {}
+ const { tabStates, currentTab } = store.get();
+ const state = tabStates[currentTab] || {};
const listNow = selectStorageListNow(state) || [];
const list = selectStorageList(state) || [];
- const type = selectStorageType(state) || STORAGE_TYPES.NONE
+ const type = selectStorageType(state) || STORAGE_TYPES.NONE;
React.useEffect(() => {
let currentState;
if (listNow.length === 0) {
- currentState = decodeMessage(list[0])
+ currentState = decodeMessage(list[0]);
} else {
- currentState = decodeMessage(listNow[listNow.length - 1])
+ currentState = decodeMessage(listNow[listNow.length - 1]);
}
- const stateObj = currentState?.state || currentState?.payload?.state || {}
+ const stateObj = currentState?.state || currentState?.payload?.state || {};
const newState = Object.assign(stateObject, stateObj);
setState(newState);
-
}, [listNow.length]);
const decodeMessage = (msg: any) => {
const decoded = {};
- const pureMSG = { ...msg }
+ const pureMSG = { ...msg };
const keys = storageDecodeKeys[type];
try {
- keys.forEach(key => {
+ keys.forEach((key) => {
if (pureMSG[key]) {
// @ts-ignore TODO: types for decoder
decoded[key] = player.decodeMessage(pureMSG[key]);
}
});
} catch (e) {
- logger.error("Error on message decoding: ", e, pureMSG);
+ logger.error('Error on message decoding: ', e, pureMSG);
return null;
}
return { ...pureMSG, ...decoded };
- }
+ };
const decodedList = React.useMemo(() => {
- return listNow.map(msg => {
- return decodeMessage(msg)
- })
- }, [listNow.length])
+ return listNow.map((msg) => {
+ return decodeMessage(msg);
+ });
+ }, [listNow.length]);
const focusNextButton = () => {
if (lastBtnRef.current) {
@@ -99,7 +104,10 @@ function Storage() {
focusNextButton();
}, [listNow]);
- const renderDiff = (item: Record
, prevItem?: Record) => {
+ const renderDiff = (
+ item: Record,
+ prevItem?: Record
+ ) => {
if (!showDiffs) {
return;
}
@@ -113,7 +121,10 @@ function Storage() {
if (!stateDiff) {
return (
-
+
No diff
);
@@ -121,13 +132,15 @@ function Storage() {
return (
- {stateDiff.map((d: Record, i: number) => renderDiffs(d, i))}
+ {stateDiff.map((d: Record, i: number) =>
+ renderDiffs(d, i)
+ )}
);
};
const renderDiffs = (diff: Record
, i: number) => {
- const path = diff.path.join('.')
+ const path = diff.path.join('.');
return (
@@ -145,12 +158,16 @@ function Storage() {
player.jump(list[listNow.length].time);
};
- const renderItem = (item: Record, i: number, prevItem?: Record) => {
+ const renderItem = (
+ item: Record,
+ i: number,
+ prevItem?: Record
+ ) => {
let src;
let name;
- const itemD = item
- const prevItemD = prevItem ? prevItem : undefined
+ const itemD = item;
+ const prevItemD = prevItem ? prevItem : undefined;
switch (type) {
case STORAGE_TYPES.REDUX:
@@ -177,7 +194,10 @@ function Storage() {
return (
{src === null ? (
@@ -187,7 +207,10 @@ function Storage() {
) : (
<>
{renderDiff(itemD, prevItemD)}
-
+
{typeof item?.duration === 'number' && (
- {formatMs(itemD.duration)}
+
+ {formatMs(itemD.duration)}
+
)}
{i + 1 < listNow.length && (
-
@@ -342,7 +377,6 @@ function Storage() {
export default observer(Storage);
-
/**
* TODO: compute diff and only decode the required parts
* WIP example
@@ -384,4 +418,4 @@ export default observer(Storage);
* }, [list.length])
* }
*
- * */
\ No newline at end of file
+ * */
diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx
index b96b94e07..193c7556f 100644
--- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx
+++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx
@@ -9,6 +9,7 @@ import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal';
import { useModal } from 'App/components/Modal';
+import TabSelector from "../TabSelector";
import useAutoscroll, { getLastItemTime } from '../useAutoscroll';
import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter';
import { VList, VListHandle } from "virtua";
@@ -93,6 +94,7 @@ function ConsolePanel({
sessionStore: { devTools },
uiPlayerStore,
} = useStore();
+
const zoomEnabled = uiPlayerStore.timelineZoom.enabled;
const zoomStartTs = uiPlayerStore.timelineZoom.startTs;
const zoomEndTs = uiPlayerStore.timelineZoom.endTs;
@@ -109,12 +111,22 @@ function ConsolePanel({
const jump = (t: number) => player.jump(t);
const { currentTab, tabStates } = store.get();
- const {
- logList = [],
- exceptionsList = [],
- logListNow = [],
- exceptionsListNow = [],
- } = tabStates[currentTab] ?? {};
+ const tabsArr = Object.keys(tabStates);
+ const tabValues = Object.values(tabStates);
+ const dataSource = uiPlayerStore.dataSource;
+ const showSingleTab = dataSource === 'current';
+ const { logList = [], exceptionsList = [], logListNow = [], exceptionsListNow = [] } = React.useMemo(() => {
+ if (showSingleTab) {
+ return tabStates[currentTab] ?? {};
+ } else {
+ const logList = tabValues.flatMap(tab => tab.logList);
+ const exceptionsList = tabValues.flatMap(tab => tab.exceptionsList);
+ const logListNow = isLive ? tabValues.flatMap(tab => tab.logListNow) : [];
+ const exceptionsListNow = isLive ? tabValues.flatMap(tab => tab.exceptionsListNow) : [];
+ return { logList, exceptionsList, logListNow, exceptionsListNow }
+ }
+ }, [currentTab, tabStates, dataSource, tabValues, isLive])
+ const getTabNum = (tab: string) => (tabsArr.findIndex((t) => t === tab) + 1);
const list = isLive
? (useMemo(
@@ -180,15 +192,18 @@ function ConsolePanel({
Console
-
+
+
+
+
{/* @ts-ignore */}
{/* @ts-ignore */}
@@ -211,6 +226,8 @@ function ConsolePanel({
iconProps={getIconProps(log.level)}
renderWithNL={renderWithNL}
onClick={() => showDetails(log)}
+ showSingleTab={showSingleTab}
+ getTabNum={getTabNum}
/>
))}
diff --git a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx
index f61e04a9c..9581dd87f 100644
--- a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx
+++ b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx
@@ -2,6 +2,8 @@ import React, { useState } from 'react';
import cn from 'classnames';
import { Icon } from 'UI';
import JumpButton from 'Shared/DevTools/JumpButton';
+import { Tag } from 'antd';
+import TabTag from "../TabTag";
interface Props {
log: any;
@@ -10,6 +12,8 @@ interface Props {
renderWithNL?: any;
style?: any;
onClick?: () => void;
+ getTabNum: (tab: string) => number;
+ showSingleTab: boolean;
}
function ConsoleRow(props: Props) {
const { log, iconProps, jump, renderWithNL, style } = props;
@@ -41,11 +45,12 @@ function ConsoleRow(props: Props) {
const titleLine = lines[0];
const restLines = lines.slice(1);
+ const logSource = props.showSingleTab ? -1 : props.getTabNum(log.tabId);
return (
(!!log.errorId ? props.onClick?.() : toggleExpand()) : undefined}
>
-
-
-
+ {logSource !== -1 &&
}
+
-
+
{canExpand && (
diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx
index bb00633e6..8d15b713c 100644
--- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx
+++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx
@@ -1,7 +1,7 @@
import { ResourceType, Timed } from 'Player';
import MobilePlayer from 'Player/mobile/IOSPlayer';
import WebPlayer from 'Player/web/WebPlayer';
-import { Duration } from 'luxon';
+import TabTag from "../TabTag";
import { observer } from 'mobx-react-lite';
import React, { useMemo, useState } from 'react';
@@ -20,10 +20,10 @@ import { WsChannel } from "App/player/web/messages";
import BottomBlock from '../BottomBlock';
import InfoLine from '../BottomBlock/InfoLine';
+import TabSelector from "../TabSelector";
import TimeTable from '../TimeTable';
import useAutoscroll, { getLastItemTime } from '../useAutoscroll';
import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter';
-import WSModal from './WSModal';
import WSPanel from './WSPanel';
const INDEX_KEY = 'network';
@@ -57,12 +57,6 @@ export const NETWORK_TABS = TAP_KEYS.map((tab) => ({
const DOM_LOADED_TIME_COLOR = 'teal';
const LOAD_TIME_COLOR = 'red';
-function compare(a: any, b: any, key: string) {
- if (a[key] > b[key]) return 1;
- if (a[key] < b[key]) return -1;
- return 0;
-}
-
export function renderType(r: any) {
return (
{r.type}
}>
@@ -79,14 +73,6 @@ export function renderName(r: any) {
);
}
-export function renderStart(r: any) {
- return (
-
- {Duration.fromMillis(r.time).toFormat('mm:ss.SSS')}
-
- );
-}
-
function renderSize(r: any) {
if (r.responseBodySize) return formatBytes(r.responseBodySize);
let triggerText;
@@ -125,13 +111,10 @@ export function renderDuration(r: any) {
if (!r.isRed && !r.isYellow) return text;
let tooltipText;
- let className = 'w-full h-full flex items-center ';
if (r.isYellow) {
tooltipText = 'Slower than average';
- className += 'warn color-orange';
} else {
tooltipText = 'Much slower than average';
- className += 'error color-red';
}
return (
@@ -184,7 +167,8 @@ function NetworkPanelCont({
panelHeight: number;
}) {
const { player, store } = React.useContext(PlayerContext);
- const { sessionStore } = useStore();
+ const { sessionStore, uiPlayerStore } = useStore();
+
const startedAt = sessionStore.current.startedAt;
const {
domContentLoadedTime,
@@ -193,6 +177,10 @@ function NetworkPanelCont({
tabStates,
currentTab,
} = store.get();
+ const tabsArr = Object.keys(tabStates);
+ const tabValues = Object.values(tabStates);
+ const dataSource = uiPlayerStore.dataSource;
+ const showSingleTab = dataSource === 'current';
const {
fetchList = [],
resourceList = [],
@@ -200,7 +188,20 @@ function NetworkPanelCont({
resourceListNow = [],
websocketList = [],
websocketListNow = [],
- } = tabStates[currentTab];
+ } = React.useMemo(() => {
+ if (showSingleTab) {
+ return tabStates[currentTab] ?? {};
+ } else {
+ const fetchList = tabValues.flatMap((tab) => tab.fetchList);
+ const resourceList = tabValues.flatMap((tab) => tab.resourceList);
+ const fetchListNow = tabValues.flatMap((tab) => tab.fetchListNow).filter(Boolean);
+ const resourceListNow = tabValues.flatMap((tab) => tab.resourceListNow).filter(Boolean);
+ const websocketList = tabValues.flatMap((tab) => tab.websocketList);
+ const websocketListNow = tabValues.flatMap((tab) => tab.websocketListNow).filter(Boolean);
+ return { fetchList, resourceList, fetchListNow, resourceListNow, websocketList, websocketListNow };
+ }
+ }, [currentTab, tabStates, dataSource, tabValues]);
+ const getTabNum = (tab: string) => (tabsArr.findIndex((t) => t === tab) + 1);
return (
);
}
@@ -301,6 +304,8 @@ interface Props {
onClose?: () => void;
activeOutsideIndex?: number;
isSpot?: boolean;
+ getTabNum?: (tab: string) => number;
+ showSingleTab?: boolean;
}
export const NetworkPanelComp = observer(
@@ -323,6 +328,8 @@ export const NetworkPanelComp = observer(
onClose,
activeOutsideIndex,
isSpot,
+ getTabNum,
+ showSingleTab,
}: Props) => {
const [selectedWsChannel, setSelectedWsChannel] = React.useState
(null)
const { showModal } = useModal();
@@ -507,6 +514,55 @@ export const NetworkPanelComp = observer(
stopAutoscroll();
};
+ const tableCols = React.useMemo(() => {
+ const cols: any[] = [
+ {
+ label: 'Status',
+ dataKey: 'status',
+ width: 90,
+ render: renderStatus,
+ },
+ {
+ label: 'Type',
+ dataKey: 'type',
+ width: 90,
+ render: renderType,
+ },
+ {
+ label: 'Method',
+ width: 80,
+ dataKey: 'method',
+ },
+ {
+ label: 'Name',
+ width: 240,
+ dataKey: 'name',
+ render: renderName,
+ },
+ {
+ label: 'Size',
+ width: 80,
+ dataKey: 'decodedBodySize',
+ render: renderSize,
+ hidden: activeTab === XHR,
+ },
+ {
+ label: 'Duration',
+ width: 80,
+ dataKey: 'duration',
+ render: renderDuration,
+ },
+ ]
+ if (!showSingleTab) {
+ cols.unshift({
+ label: 'Source',
+ width: 64,
+ render: (r: Record) => Tab {getTabNum?.(r.tabId) ?? 0}
,
+ })
+ }
+ return cols
+ }, [showSingleTab])
+
return (
)}
-
+
+
+
+
@@ -613,49 +672,7 @@ export const NetworkPanelComp = observer(
}}
activeIndex={activeIndex}
>
- {[
- // {
- // label: 'Start',
- // width: 120,
- // render: renderStart,
- // },
- {
- label: 'Status',
- dataKey: 'status',
- width: 90,
- render: renderStatus,
- },
- {
- label: 'Type',
- dataKey: 'type',
- width: 90,
- render: renderType,
- },
- {
- label: 'Method',
- width: 80,
- dataKey: 'method',
- },
- {
- label: 'Name',
- width: 240,
- dataKey: 'name',
- render: renderName,
- },
- {
- label: 'Size',
- width: 80,
- dataKey: 'decodedBodySize',
- render: renderSize,
- hidden: activeTab === XHR,
- },
- {
- label: 'Duration',
- width: 80,
- dataKey: 'duration',
- render: renderDuration,
- },
- ]}
+ {tableCols}
{selectedWsChannel ? (
setSelectedWsChannel(null)} />
diff --git a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx
index 1ee227ae1..711504672 100644
--- a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx
+++ b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx
@@ -10,6 +10,7 @@ import { typeList } from 'Types/session/stackEvent';
import StackEventRow from 'Shared/DevTools/StackEventRow';
import StackEventModal from '../StackEventModal';
+import { Segmented } from 'antd'
import useAutoscroll, { getLastItemTime } from '../useAutoscroll';
import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter';
import { VList, VListHandle } from 'virtua';
@@ -175,15 +176,18 @@ const EventsPanel = observer(({
border={false}
/>
-
+
+
+
+
{
+ uiPlayerStore.changeDataSource(value)
+ }
+ return (
+
+ )
+}
+
+export default observer(TabSelector)
\ No newline at end of file
diff --git a/frontend/app/components/shared/DevTools/TabTag.tsx b/frontend/app/components/shared/DevTools/TabTag.tsx
new file mode 100644
index 000000000..a2e6f33b1
--- /dev/null
+++ b/frontend/app/components/shared/DevTools/TabTag.tsx
@@ -0,0 +1,11 @@
+import React from 'react'
+
+function TabTag({ tabNum }: { tabNum?: React.ReactNode }) {
+ return (
+
+ {tabNum}
+
+ )
+}
+
+export default TabTag
\ No newline at end of file
diff --git a/frontend/app/mstore/uiPlayerStore.ts b/frontend/app/mstore/uiPlayerStore.ts
index dd642f047..d79e13c45 100644
--- a/frontend/app/mstore/uiPlayerStore.ts
+++ b/frontend/app/mstore/uiPlayerStore.ts
@@ -66,11 +66,16 @@ export default class UiPlayerStore {
endTs: 0,
}
zoomTab: 'overview' | 'journey' | 'issues' | 'errors' = 'overview'
+ dataSource: 'all' | 'current' = 'all'
constructor() {
makeAutoObservable(this);
}
+ changeDataSource = (source: 'all' | 'current') => {
+ this.dataSource = source;
+ }
+
toggleFullscreen = (val?: boolean) => {
this.fullscreen = val ?? !this.fullscreen;
}
diff --git a/frontend/app/player/common/SimpleStore.ts b/frontend/app/player/common/SimpleStore.ts
index 82ddd3433..c1194df96 100644
--- a/frontend/app/player/common/SimpleStore.ts
+++ b/frontend/app/player/common/SimpleStore.ts
@@ -1,13 +1,18 @@
import { Store } from './types'
-export default class SimpleSore implements Store {
+export default class SimpleStore, S extends Record = G> implements Store {
constructor(private state: G){}
get(): G {
return this.state
}
- update(newState: Partial) {
+ update = (newState: Partial) => {
Object.assign(this.state, newState)
}
+ updateTabStates = (id: string, newState: Partial) => {
+ try {
+ Object.assign(this.state.tabStates[id], newState)
+ } catch (e) {
+ console.log('Error updating tab state', e, id, newState, this.state, this)
+ }
+ }
}
-
-
diff --git a/frontend/app/player/common/types.ts b/frontend/app/player/common/types.ts
index ad34097cb..e3f06f1dc 100644
--- a/frontend/app/player/common/types.ts
+++ b/frontend/app/player/common/types.ts
@@ -27,6 +27,7 @@ export interface Interval {
export interface Store {
get(): G
update(state: Partial): void
+ updateTabStates(id: string, state: Partial): void
}
diff --git a/frontend/app/player/web/MessageLoader.ts b/frontend/app/player/web/MessageLoader.ts
index aa4bf58ad..4ff59cbf6 100644
--- a/frontend/app/player/web/MessageLoader.ts
+++ b/frontend/app/player/web/MessageLoader.ts
@@ -236,6 +236,7 @@ export default class MessageLoader {
try {
await this.loadMobs();
} catch (sessionLoadError) {
+ console.info('!', sessionLoadError);
try {
await this.loadEFSMobs();
} catch (unprocessedLoadError) {
diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts
index d6ed2ed92..ba074a1cc 100644
--- a/frontend/app/player/web/MessageManager.ts
+++ b/frontend/app/player/web/MessageManager.ts
@@ -99,7 +99,7 @@ export default class MessageManager {
closedTabs: [],
sessionStart: 0,
tabNames: {},
- };
+};
private clickManager: ListWalker = new ListWalker();
private mouseThrashingManager: ListWalker = new ListWalker();
@@ -179,6 +179,7 @@ export default class MessageManager {
this.activityManager.end();
this.state.update({ skipIntervals: this.activityManager.list });
}
+
Object.values(this.tabs).forEach((tab) => tab.onFileReadSuccess?.());
};
@@ -317,6 +318,7 @@ export default class MessageManager {
if (msg.tp === 9999) return;
if (!this.tabs[msg.tabId]) {
this.tabsAmount++;
+ this.state.update({ tabStates: { ...this.state.get().tabStates, [msg.tabId]: TabSessionManager.INITIAL_STATE } });
this.tabs[msg.tabId] = new TabSessionManager(
this.session,
this.state,
diff --git a/frontend/app/player/web/TabManager.ts b/frontend/app/player/web/TabManager.ts
index baa0176fb..200d5f9b6 100644
--- a/frontend/app/player/web/TabManager.ts
+++ b/frontend/app/player/web/TabManager.ts
@@ -163,15 +163,7 @@ export default class TabSessionManager {
* Because we use main state (from messageManager), we have to update it this way
* */
updateLocalState(state: Partial) {
- this.state.update({
- tabStates: {
- ...this.state.get().tabStates,
- [this.id]: {
- ...this.state.get().tabStates[this.id],
- ...state,
- },
- },
- });
+ this.state.updateTabStates(this.id, state);
}
private setCSSLoading = (cssLoading: boolean) => {
@@ -414,8 +406,9 @@ export default class TabSessionManager {
}
Object.assign(stateToUpdate, this.lists.moveGetState(t));
- Object.keys(stateToUpdate).length > 0 &&
+ if (Object.keys(stateToUpdate).length > 0) {
this.updateLocalState(stateToUpdate);
+ }
/* Sequence of the managers is important here */
// Preparing the size of "screen"
const lastResize = this.resizeManager.moveGetLast(t, index);
diff --git a/scripts/helmcharts/vars.yaml b/scripts/helmcharts/vars.yaml
index fa74a65fa..d2e89e1ec 100644
--- a/scripts/helmcharts/vars.yaml
+++ b/scripts/helmcharts/vars.yaml
@@ -29,6 +29,7 @@ clickhouse: &clickhouse
password: ""
service:
webPort: 9000
+ dataPort: 8123
# For enterpriseEdition
quickwit: &quickwit