diff --git a/api/chalicelib/core/alerts/alerts_processor_ch.py b/api/chalicelib/core/alerts/alerts_processor_ch.py index 982518be1..316c0f166 100644 --- a/api/chalicelib/core/alerts/alerts_processor_ch.py +++ b/api/chalicelib/core/alerts/alerts_processor_ch.py @@ -173,7 +173,7 @@ def process(): logger.debug(alert) logger.debug(query) try: - result = ch_cur.execute(query) + result = ch_cur.execute(query=query) if len(result) > 0: result = result[0] diff --git a/api/chalicelib/core/errors/errors_ch.py b/api/chalicelib/core/errors/errors_ch.py index 83954fc16..ba728769f 100644 --- a/api/chalicelib/core/errors/errors_ch.py +++ b/api/chalicelib/core/errors/errors_ch.py @@ -400,7 +400,7 @@ def search(data: schemas.SearchErrorsSchema, project: schemas.ProjectContext, us # print("------------") query = ch.format(query=main_ch_query, parameters=params) - rows = ch.execute(query) + rows = ch.execute(query=query) total = rows[0]["total"] if len(rows) > 0 else 0 for r in rows: diff --git a/api/chalicelib/core/metrics/heatmaps_ch.py b/api/chalicelib/core/metrics/heatmaps_ch.py index cfbd1b89c..3b78ef551 100644 --- a/api/chalicelib/core/metrics/heatmaps_ch.py +++ b/api/chalicelib/core/metrics/heatmaps_ch.py @@ -84,7 +84,7 @@ def get_by_url(project_id, data: schemas.GetHeatMapPayloadSchema): logger.debug(query) logger.debug("---------") try: - rows = cur.execute(query) + rows = cur.execute(query=query) except Exception as err: logger.warning("--------- HEATMAP 2 SEARCH QUERY EXCEPTION CH -----------") logger.warning(query) @@ -122,7 +122,7 @@ def get_x_y_by_url_and_session_id(project_id, session_id, data: schemas.GetHeatM logger.debug(query) logger.debug("---------") try: - rows = cur.execute(query) + rows = cur.execute(query=query) except Exception as err: logger.warning("--------- HEATMAP-session_id SEARCH QUERY EXCEPTION CH -----------") logger.warning(query) @@ -160,7 +160,7 @@ def get_selectors_by_url_and_session_id(project_id, session_id, data: schemas.Ge logger.debug(query) logger.debug("---------") try: - rows = cur.execute(query) + rows = cur.execute(query=query) except Exception as err: logger.warning("--------- HEATMAP-session_id SEARCH QUERY EXCEPTION CH -----------") logger.warning(query) @@ -221,7 +221,7 @@ def __get_1_url(location_condition: schemas.SessionSearchEventSchema2 | None, se logger.debug(main_query) logger.debug("--------------------") try: - url = cur.execute(main_query) + url = cur.execute(query=main_query) except Exception as err: logger.warning("--------- CLICK MAP BEST URL SEARCH QUERY EXCEPTION CH-----------") logger.warning(main_query.decode('UTF-8')) @@ -295,7 +295,7 @@ def search_short_session(data: schemas.HeatMapSessionsSearch, project_id, user_i logger.debug(main_query) logger.debug("--------------------") try: - session = cur.execute(main_query) + session = cur.execute(query=main_query) except Exception as err: logger.warning("--------- CLICK MAP SHORT SESSION SEARCH QUERY EXCEPTION CH -----------") logger.warning(main_query) @@ -342,7 +342,7 @@ def get_selected_session(project_id, session_id): logger.debug(main_query) logger.debug("--------------------") try: - session = cur.execute(main_query) + session = cur.execute(query=main_query) except Exception as err: logger.warning("--------- CLICK MAP GET SELECTED SESSION QUERY EXCEPTION -----------") logger.warning(main_query.decode('UTF-8')) diff --git a/api/chalicelib/core/metrics/modules/significance/significance_ch.py b/api/chalicelib/core/metrics/modules/significance/significance_ch.py index 6d920ce59..f309841c0 100644 --- a/api/chalicelib/core/metrics/modules/significance/significance_ch.py +++ b/api/chalicelib/core/metrics/modules/significance/significance_ch.py @@ -243,7 +243,7 @@ def get_simple_funnel(filter_d: schemas.CardSeriesFilterSchema, project: schemas logger.debug(query) logger.debug("---------------------------------------------------") try: - row = cur.execute(query) + row = cur.execute(query=query) except Exception as err: logger.warning("--------- SIMPLE FUNNEL SEARCH QUERY EXCEPTION CH-----------") logger.warning(query) diff --git a/api/chalicelib/core/metrics/product_analytics_ch.py b/api/chalicelib/core/metrics/product_analytics_ch.py index 3bc997525..44c94952e 100644 --- a/api/chalicelib/core/metrics/product_analytics_ch.py +++ b/api/chalicelib/core/metrics/product_analytics_ch.py @@ -428,7 +428,7 @@ def path_analysis(project_id: int, data: schemas.CardPathAnalysis): SELECT event_number_in_session, `$event_name`, e_value, - SUM(sessions_count) AS sessions_count + SUM(n{i}.sessions_count) AS sessions_count FROM n{i} GROUP BY event_number_in_session, `$event_name`, e_value ORDER BY sessions_count DESC @@ -487,10 +487,11 @@ WITH {initial_sessions_cte} SELECT * FROM pre_ranked_events;""" logger.debug("---------Q1-----------") - ch.execute(query=ch_query1, parameters=params) + ch_query1 = ch.format(query=ch_query1, parameters=params) + ch.execute(query=ch_query1) if time() - _now > 2: logger.warning(f">>>>>>>>>PathAnalysis long query EE ({int(time() - _now)}s)<<<<<<<<<") - logger.warning(ch.format(query=ch_query1, parameters=params)) + logger.warning(str.encode(ch_query1)) logger.warning("----------------------") _now = time() @@ -512,10 +513,11 @@ SELECT * FROM ranked_events {q2_extra_condition if q2_extra_condition else ""};""" logger.debug("---------Q2-----------") - ch.execute(query=ch_query2, parameters=params) + ch_query2 = ch.format(query=ch_query2, parameters=params) + ch.execute(query=ch_query2) if time() - _now > 2: logger.warning(f">>>>>>>>>PathAnalysis long query EE ({int(time() - _now)}s)<<<<<<<<<") - logger.warning(ch.format(query=ch_query2, parameters=params)) + logger.warning(str.encode(ch_query2)) logger.warning("----------------------") _now = time() @@ -590,7 +592,7 @@ FROM ranked_events NULL AS e_value, 'OTHER' AS next_type, NULL AS next_value, - SUM(sessions_count) AS sessions_count + SUM(others_n.sessions_count) AS sessions_count FROM others_n WHERE isNotNull(others_n.next_type) AND others_n.event_number_in_session < %(density)s @@ -625,10 +627,11 @@ FROM ranked_events ) AS chart_steps ORDER BY event_number_in_session, sessions_count DESC;""" logger.debug("---------Q3-----------") - rows = ch.execute(query=ch_query3, parameters=params) + ch_query3 = ch.format(query=ch_query3, parameters=params) + rows = ch.execute(query=ch_query3) if time() - _now > 2: logger.warning(f">>>>>>>>>PathAnalysis long query EE ({int(time() - _now)}s)<<<<<<<<<") - logger.warning(ch.format(query=ch_query3, parameters=params)) + logger.warning(str.encode(ch_query3)) logger.warning("----------------------") return __transform_journey(rows=rows, reverse_path=reverse) diff --git a/api/chalicelib/core/sessions/sessions_ch.py b/api/chalicelib/core/sessions/sessions_ch.py index 6a987a2d3..4ebc8723d 100644 --- a/api/chalicelib/core/sessions/sessions_ch.py +++ b/api/chalicelib/core/sessions/sessions_ch.py @@ -64,7 +64,7 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d logging.debug("--------------------") logging.debug(main_query) logging.debug("--------------------") - sessions = cur.execute(main_query) + sessions = cur.execute(query=main_query) elif metric_type == schemas.MetricType.TABLE: full_args["limit_s"] = 0 @@ -112,7 +112,7 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d logging.debug("--------------------") logging.debug(main_query) logging.debug("--------------------") - sessions = cur.execute(main_query) + sessions = cur.execute(query=main_query) # cur.fetchone() count = 0 if len(sessions) > 0: @@ -121,8 +121,10 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d s.pop("main_count") sessions = {"count": count, "values": helper.list_to_camel_case(sessions)} - return helper.complete_missing_steps(rows=sessions, start_timestamp=data.startTimestamp, - end_timestamp=data.endTimestamp, step=step_size, neutral={"count": 0}) + return metrics_helper.complete_missing_steps(rows=sessions, + start_timestamp=data.startTimestamp, + end_timestamp=data.endTimestamp, step=step_size, + neutral={"count": 0}) def search2_table(data: schemas.SessionsSearchPayloadSchema, project_id: int, density: int, @@ -242,7 +244,7 @@ def search2_table(data: schemas.SessionsSearchPayloadSchema, project_id: int, de {extra_where} GROUP BY {main_col} ORDER BY total DESC - LIMIT %(limit_e)s OFFSET %(limit_s)s;""" + LIMIT %(limit)s OFFSET %(limit_s)s;""" else: main_query = f"""SELECT COUNT(DISTINCT {main_col}) OVER () AS main_count, {main_col} AS name, @@ -255,13 +257,13 @@ def search2_table(data: schemas.SessionsSearchPayloadSchema, project_id: int, de {extra_where} GROUP BY {main_col} ORDER BY total DESC - LIMIT %(limit_e)s OFFSET %(limit_s)s;""" + LIMIT %(limit)s OFFSET %(limit_s)s;""" main_query = cur.format(query=main_query, parameters=full_args) logging.debug("--------------------") logging.debug(main_query) logging.debug("--------------------") - sessions = cur.execute(main_query) + sessions = cur.execute(query=main_query) count = 0 total = 0 if len(sessions) > 0: @@ -1503,7 +1505,7 @@ def session_exists(project_id, session_id): AND project_id=%(project_id)s LIMIT 1""", parameters={"project_id": project_id, "session_id": session_id}) - row = cur.execute(query) + row = cur.execute(query=query) return row is not None diff --git a/api/chalicelib/utils/ch_client_exp.py b/api/chalicelib/utils/ch_client_exp.py index f91b0361e..f0d631420 100644 --- a/api/chalicelib/utils/ch_client_exp.py +++ b/api/chalicelib/utils/ch_client_exp.py @@ -34,7 +34,7 @@ if config("CH_COMPRESSION", cast=bool, default=True): def transform_result(self, original_function): @wraps(original_function) def wrapper(*args, **kwargs): - logger.debug(self.format(query=kwargs.get("query"), parameters=kwargs.get("parameters"))) + logger.debug(str.encode(self.format(query=kwargs.get("query", ""), parameters=kwargs.get("parameters")))) result = original_function(*args, **kwargs) if isinstance(result, clickhouse_connect.driver.query.QueryResult): column_names = result.column_names diff --git a/api/chalicelib/utils/helper.py b/api/chalicelib/utils/helper.py index 6710100ba..4d0d09427 100644 --- a/api/chalicelib/utils/helper.py +++ b/api/chalicelib/utils/helper.py @@ -336,19 +336,3 @@ def cast_session_id_to_string(data): return data -from typing import List - - -def complete_missing_steps(rows: List[dict], start_timestamp: int, end_timestamp: int, step: int, neutral: dict, - time_key: str = "timestamp") -> List[dict]: - result = [] - i = 0 - for t in range(start_timestamp, end_timestamp, step): - if i >= len(rows) or rows[i][time_key] > t: - neutral[time_key] = t - result.append(neutral.copy()) - elif i < len(rows) and rows[i][time_key] == t: - result.append(rows[i]) - i += 1 - - return result diff --git a/api/chalicelib/utils/metrics_helper.py b/api/chalicelib/utils/metrics_helper.py index fa9bca6a6..7bf9c94f8 100644 --- a/api/chalicelib/utils/metrics_helper.py +++ b/api/chalicelib/utils/metrics_helper.py @@ -1,3 +1,6 @@ +from typing import List + + def get_step_size(startTimestamp, endTimestamp, density, decimal=False, factor=1000): step_size = (endTimestamp // factor - startTimestamp // factor) if density <= 1: @@ -5,3 +8,17 @@ def get_step_size(startTimestamp, endTimestamp, density, decimal=False, factor=1 if decimal: return step_size / density return step_size // density + + +def complete_missing_steps(rows: List[dict], start_timestamp: int, end_timestamp: int, step: int, neutral: dict, + time_key: str = "timestamp") -> List[dict]: + result = [] + i = 0 + for t in range(start_timestamp, end_timestamp, step): + if i >= len(rows) or rows[i][time_key] > t: + neutral[time_key] = t + result.append(neutral.copy()) + elif i < len(rows) and rows[i][time_key] == t: + result.append(rows[i]) + i += 1 + return result diff --git a/api/schemas/schemas.py b/api/schemas/schemas.py index 51d8660e1..ceee5df7c 100644 --- a/api/schemas/schemas.py +++ b/api/schemas/schemas.py @@ -541,7 +541,7 @@ class RequestGraphqlFilterSchema(BaseModel): @classmethod def _transform_data(cls, values): if values.get("type") in [FetchFilterType.FETCH_DURATION, FetchFilterType.FETCH_STATUS_CODE]: - values["value"] = [int(v) for v in values["value"] if v is not None and v.isnumeric()] + values["value"] = [int(v) for v in values["value"] if v is not None and str(v).isnumeric()] return values @@ -851,18 +851,21 @@ class MetricTimeseriesViewType(str, Enum): LINE_CHART = "lineChart" AREA_CHART = "areaChart" BAR_CHART = "barChart" - PIE_CHART = "pieChart" PROGRESS_CHART = "progressChart" - TABLE_CHART = "table" + PIE_CHART = "pieChart" METRIC_CHART = "metric" + TABLE_CHART = "table" class MetricTableViewType(str, Enum): - TABLE = "table" + TABLE_CHART = "table" class MetricOtherViewType(str, Enum): OTHER_CHART = "chart" + COLUMN_CHART = "columnChart" + METRIC_CHART = "metric" + TABLE_CHART = "table" LIST_CHART = "list" @@ -876,8 +879,6 @@ class MetricType(str, Enum): HEAT_MAP = "heatMap" - - class MetricOfTable(str, Enum): USER_BROWSER = FilterType.USER_BROWSER.value USER_DEVICE = FilterType.USER_DEVICE.value @@ -1086,7 +1087,7 @@ class CardFunnel(__CardSchema): def __enforce_default(cls, values): if values.get("metricOf") and not MetricOfFunnels.has_value(values["metricOf"]): values["metricOf"] = MetricOfFunnels.SESSION_COUNT - values["viewType"] = MetricOtherViewType.OTHER_CHART + # values["viewType"] = MetricOtherViewType.OTHER_CHART if values.get("series") is not None and len(values["series"]) > 0: values["series"] = [values["series"][0]] return values diff --git a/backend/cmd/canvas-handler/main.go b/backend/cmd/canvas-handler/main.go index 568ff42e5..92666ebc7 100644 --- a/backend/cmd/canvas-handler/main.go +++ b/backend/cmd/canvas-handler/main.go @@ -4,6 +4,7 @@ import ( "context" "os" "os/signal" + "strings" "syscall" "time" @@ -58,7 +59,9 @@ func main() { if isSessionEnd(data) { if err := srv.PackSessionCanvases(sessCtx, sessID); err != nil { - log.Error(sessCtx, "can't pack session's canvases: %s", err) + if !strings.Contains(err.Error(), "no such file or directory") { + log.Error(sessCtx, "can't pack session's canvases: %s", err) + } } } else { if err := srv.SaveCanvasToDisk(sessCtx, sessID, data); err != nil { diff --git a/backend/go.mod b/backend/go.mod index 4046f8136..995f0ae2b 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -5,7 +5,7 @@ go 1.23 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 - github.com/ClickHouse/clickhouse-go/v2 v2.30.1 + github.com/ClickHouse/clickhouse-go/v2 v2.32.1 github.com/DataDog/datadog-api-client-go/v2 v2.34.0 github.com/Masterminds/semver v1.5.0 github.com/andybalholm/brotli v1.1.1 @@ -36,12 +36,12 @@ require ( github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce github.com/ua-parser/uap-go v0.0.0-20250126222208-a52596c19dff go.uber.org/zap v1.27.0 - golang.org/x/net v0.34.0 + golang.org/x/net v0.35.0 ) require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect - github.com/ClickHouse/ch-go v0.63.1 // indirect + github.com/ClickHouse/ch-go v0.65.0 // indirect github.com/DataDog/zstd v1.5.6 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -77,10 +77,10 @@ require ( go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.33.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 // indirect google.golang.org/protobuf v1.36.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index d4180aaf2..4ecc6225b 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -12,20 +12,21 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xP github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0 h1:mlmW46Q0B79I+Aj4azKC6xDMFN9a9SyZWESlGWYXbFs= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0/go.mod h1:PXe2h+LKcWTX9afWdZoHyODqR4fBa5boUM/8uJfZ0Jo= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/ClickHouse/ch-go v0.63.1 h1:s2JyZvWLTCSAGdtjMBBmAgQQHMco6pawLJMOXi0FODM= github.com/ClickHouse/ch-go v0.63.1/go.mod h1:I1kJJCL3WJcBMGe1m+HVK0+nREaG+JOYYBWjrDrF3R0= +github.com/ClickHouse/ch-go v0.65.0 h1:vZAXfTQliuNNefqkPDewX3kgRxN6Q4vUENnnY+ynTRY= +github.com/ClickHouse/ch-go v0.65.0/go.mod h1:tCM0XEH5oWngoi9Iu/8+tjPBo04I/FxNIffpdjtwx3k= github.com/ClickHouse/clickhouse-go/v2 v2.30.1 h1:Dy0n0l+cMbPXs8hFkeeWGaPKrB+MDByUNQBSmRO3W6k= github.com/ClickHouse/clickhouse-go/v2 v2.30.1/go.mod h1:szk8BMoQV/NgHXZ20ZbwDyvPWmpfhRKjFkc6wzASGxM= +github.com/ClickHouse/clickhouse-go/v2 v2.32.1 h1:RLhkxA6iH/bLTXeDtEj/u4yUx9Q03Y95P+cjHScQK78= +github.com/ClickHouse/clickhouse-go/v2 v2.32.1/go.mod h1:YtaiIFlHCGNPbOpAvFGYobtcVnmgYvD/WmzitixxWYc= github.com/DataDog/datadog-api-client-go/v2 v2.34.0 h1:0VVmv8uZg8vdBuEpiF2nBGUezl2QITrxdEsLgh38j8M= github.com/DataDog/datadog-api-client-go/v2 v2.34.0/go.mod h1:d3tOEgUd2kfsr9uuHQdY+nXrWp4uikgTgVCPdKNK30U= github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY= @@ -198,8 +199,6 @@ github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8w github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= -github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= @@ -527,8 +526,6 @@ github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/ github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw= github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= -github.com/ua-parser/uap-go v0.0.0-20241012191800-bbb40edc15aa h1:VzPR4xFM7HARqNocjdHg75ZL9SAgFtaF3P57ZdDcG6I= -github.com/ua-parser/uap-go v0.0.0-20241012191800-bbb40edc15aa/go.mod h1:BUbeWZiieNxAuuADTBNb3/aeje6on3DhU3rpWsQSB1E= github.com/ua-parser/uap-go v0.0.0-20250126222208-a52596c19dff h1:NwMEGwb7JJ8wPjT8OPKP5hO1Xz6AQ7Z00+GLSJfW21s= github.com/ua-parser/uap-go v0.0.0-20250126222208-a52596c19dff/go.mod h1:BUbeWZiieNxAuuADTBNb3/aeje6on3DhU3rpWsQSB1E= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -618,6 +615,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -642,6 +641,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -676,6 +677,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -695,6 +698,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -720,16 +725,12 @@ google.golang.org/genproto v0.0.0-20240325203815-454cdb8f5daa h1:ePqxpG3LVx+feAU google.golang.org/genproto v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:CnZenrTdRJb7jc+jOm0Rkywq+9wh0QC4U8tyiRbEPPM= google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8= google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= -google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y= diff --git a/backend/internal/canvas-handler/service.go b/backend/internal/canvas-handler/service.go index d7e42a965..13ee46f68 100644 --- a/backend/internal/canvas-handler/service.go +++ b/backend/internal/canvas-handler/service.go @@ -80,7 +80,7 @@ func (v *ImageStorage) SaveCanvasToDisk(ctx context.Context, sessID uint64, data func (v *ImageStorage) writeToDisk(payload interface{}) { task := payload.(*saveTask) - path := fmt.Sprintf("%s/%d/", v.basePath, task.sessionID) + path := fmt.Sprintf("%s%d/", v.basePath, task.sessionID) // Ensure the directory exists if err := os.MkdirAll(path, 0755); err != nil { @@ -102,7 +102,7 @@ func (v *ImageStorage) writeToDisk(payload interface{}) { } func (v *ImageStorage) PackSessionCanvases(ctx context.Context, sessID uint64) error { - path := fmt.Sprintf("%s/%d/", v.basePath, sessID) + path := fmt.Sprintf("%s%d/", v.basePath, sessID) // Check that the directory exists files, err := os.ReadDir(path) diff --git a/ee/api/Pipfile b/ee/api/Pipfile index 2b640c877..46f4da33e 100644 --- a/ee/api/Pipfile +++ b/ee/api/Pipfile @@ -9,7 +9,7 @@ requests = "==2.32.3" boto3 = "==1.36.12" pyjwt = "==2.10.1" psycopg2-binary = "==2.9.10" -psycopg = {extras = ["binary", "pool"], version = "==3.2.4"} +psycopg = {extras = ["pool", "binary"], version = "==3.2.4"} clickhouse-driver = {extras = ["lz4"], version = "==0.2.9"} clickhouse-connect = "==0.8.15" elasticsearch = "==8.17.1" diff --git a/ee/api/routers/ee.py b/ee/api/routers/ee.py index 2b8ba0f37..2b7f51ead 100644 --- a/ee/api/routers/ee.py +++ b/ee/api/routers/ee.py @@ -1,6 +1,7 @@ from typing import Optional -from chalicelib.core import roles, traces, assist_records, sessions +from chalicelib.core import roles, traces, assist_records +from chalicelib.core.sessions import sessions from chalicelib.core import assist_stats from chalicelib.core import unlock, signals from chalicelib.utils import assist_helper diff --git a/ee/scripts/schema/db/init_dbs/postgresql/1.22.0/1.22.0.sql b/ee/scripts/schema/db/init_dbs/postgresql/1.22.0/1.22.0.sql index d7ed6c5ba..fbedb48a0 100644 --- a/ee/scripts/schema/db/init_dbs/postgresql/1.22.0/1.22.0.sql +++ b/ee/scripts/schema/db/init_dbs/postgresql/1.22.0/1.22.0.sql @@ -27,10 +27,10 @@ DROP TABLE IF EXISTS public.user_favorite_errors; DROP TABLE IF EXISTS public.user_viewed_errors; ALTER TABLE IF EXISTS public.sessions_notes - ADD COLUMN start_at integer, - ADD COLUMN end_at integer, - ADD COLUMN thumbnail text, - ADD COLUMN updated_at timestamp DEFAULT NULL, + ADD COLUMN IF NOT EXISTS start_at integer, + ADD COLUMN IF NOT EXISTS end_at integer, + ADD COLUMN IF NOT EXISTS thumbnail text, + ADD COLUMN IF NOT EXISTS updated_at timestamp DEFAULT NULL, ALTER COLUMN message DROP NOT NULL; DELETE diff --git a/frontend/.env.sample b/frontend/.env.sample index 303fd65a0..c61fa2169 100644 --- a/frontend/.env.sample +++ b/frontend/.env.sample @@ -22,5 +22,5 @@ MINIO_ACCESS_KEY = '' MINIO_SECRET_KEY = '' # APP and TRACKER VERSIONS -VERSION = 1.21.0 -TRACKER_VERSION = '15.0.0' +VERSION = 1.22.0 +TRACKER_VERSION = '15.0.5' diff --git a/frontend/app/PrivateRoutes.tsx b/frontend/app/PrivateRoutes.tsx index 7e4a85955..5ddcf8ee4 100644 --- a/frontend/app/PrivateRoutes.tsx +++ b/frontend/app/PrivateRoutes.tsx @@ -59,7 +59,7 @@ const enhancedComponents: any = { SpotsList: withSiteIdUpdater(components.SpotsListPure), Spot: components.SpotPure, ScopeSetup: components.ScopeSetup, - Highlights: components.HighlightsPure, + Highlights: withSiteIdUpdater(components.HighlightsPure), }; const withSiteId = routes.withSiteId; diff --git a/frontend/app/components/Charts/BarChart.tsx b/frontend/app/components/Charts/BarChart.tsx index 5527ea978..386770505 100644 --- a/frontend/app/components/Charts/BarChart.tsx +++ b/frontend/app/components/Charts/BarChart.tsx @@ -53,7 +53,7 @@ function ORBarChart(props: BarChartProps) { type: 'value', data: undefined, name: props.label ?? 'Number of Sessions', - nameLocation: 'middle', + nameLocation: 'center', nameGap: 45, }; diff --git a/frontend/app/components/Charts/ColumnChart.tsx b/frontend/app/components/Charts/ColumnChart.tsx index 50482b7c9..f16992761 100644 --- a/frontend/app/components/Charts/ColumnChart.tsx +++ b/frontend/app/components/Charts/ColumnChart.tsx @@ -74,8 +74,8 @@ function ColumnChart(props: ColumnChartProps) { xAxis: { type: 'value', name: label ?? 'Total', - nameLocation: 'middle', - nameGap: 45, + nameLocation: 'center', + nameGap: 35, }, yAxis: { type: 'category', diff --git a/frontend/app/components/Charts/LineChart.tsx b/frontend/app/components/Charts/LineChart.tsx index 0629cc9db..0c2d1ead9 100644 --- a/frontend/app/components/Charts/LineChart.tsx +++ b/frontend/app/components/Charts/LineChart.tsx @@ -69,8 +69,11 @@ function ORLineChart(props: Props) { }, yAxis: { name: props.label ?? 'Number of Sessions', - nameLocation: 'middle', - nameGap: 45, + // nameLocation: 'center', + // nameGap: 40, + nameTextStyle: { + padding: [0, 0, 0, 15], + } }, tooltip: { ...defaultOptions.tooltip, diff --git a/frontend/app/components/Charts/SankeyChart.tsx b/frontend/app/components/Charts/SankeyChart.tsx index 48c508e88..33197da5e 100644 --- a/frontend/app/components/Charts/SankeyChart.tsx +++ b/frontend/app/components/Charts/SankeyChart.tsx @@ -77,13 +77,18 @@ const EChartsSankey: React.FC = (props) => { }); setFinalNodeCount(filteredNodes.length); - + const nodeValues: Record = {}; const echartNodes = filteredNodes - .map((n) => { + .map((n, i) => { let computedName = getNodeName(n.eventType || 'Minor Paths', n.name); if (computedName === 'Other') { computedName = 'Others'; } + if (n.id) { + nodeValues[n.id] = 0; + } else { + nodeValues[i] = 0; + } const itemColor = computedName === 'Others' ? 'rgba(34,44,154,.9)' @@ -124,6 +129,17 @@ const EChartsSankey: React.FC = (props) => { .filter((link) => link.source === 0) .reduce((sum, link) => sum + link.value, 0); + Object.keys(nodeValues).forEach((nodeId) => { + const intId = parseInt(nodeId as string); + const outgoingValues = echartLinks + .filter((l) => l.source === intId) + .reduce((p, c) => p + c.value, 0); + const incomingValues = echartLinks + .filter((l) => l.target === intId) + .reduce((p, c) => p + c.value, 0); + nodeValues[nodeId] = Math.max(outgoingValues, incomingValues); + }); + const option = { ...defaultOptions, tooltip: { @@ -172,10 +188,10 @@ const EChartsSankey: React.FC = (props) => { params.name.slice(-(maxLen / 2 - 2)) : params.name; const nodeType = params.data.type; - + const icon = getIcon(nodeType) return ( - `${icon}{header|${safeName}}\n` + + `${icon}{header| ${safeName}}\n` + `{body|}{percentage|${percentage}} {sessions|${nodeVal}}` ); }, @@ -233,11 +249,25 @@ const EChartsSankey: React.FC = (props) => { }, height: 20, width: 14, + }, + dropEventIcon: { + backgroundColor: { + image: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWNpcmNsZS1hcnJvdy1kb3duIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIvPjxwYXRoIGQ9Ik0xMiA4djgiLz48cGF0aCBkPSJtOCAxMiA0IDQgNC00Ii8+PC9zdmc+', + }, + height: 20, + width: 14, + }, + groupIcon: { + backgroundColor: { + image: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWNvbXBvbmVudCI+PHBhdGggZD0iTTE1LjUzNiAxMS4yOTNhMSAxIDAgMCAwIDAgMS40MTRsMi4zNzYgMi4zNzdhMSAxIDAgMCAwIDEuNDE0IDBsMi4zNzctMi4zNzdhMSAxIDAgMCAwIDAtMS40MTRsLTIuMzc3LTIuMzc3YTEgMSAwIDAgMC0xLjQxNCAweiIvPjxwYXRoIGQ9Ik0yLjI5NyAxMS4yOTNhMSAxIDAgMCAwIDAgMS40MTRsMi4zNzcgMi4zNzdhMSAxIDAgMCAwIDEuNDE0IDBsMi4zNzctMi4zNzdhMSAxIDAgMCAwIDAtMS40MTRMNi4wODggOC45MTZhMSAxIDAgMCAwLTEuNDE0IDB6Ii8+PHBhdGggZD0iTTguOTE2IDE3LjkxMmExIDEgMCAwIDAgMCAxLjQxNWwyLjM3NyAyLjM3NmExIDEgMCAwIDAgMS40MTQgMGwyLjM3Ny0yLjM3NmExIDEgMCAwIDAgMC0xLjQxNWwtMi4zNzctMi4zNzZhMSAxIDAgMCAwLTEuNDE0IDB6Ii8+PHBhdGggZD0iTTguOTE2IDQuNjc0YTEgMSAwIDAgMCAwIDEuNDE0bDIuMzc3IDIuMzc2YTEgMSAwIDAgMCAxLjQxNCAwbDIuMzc3LTIuMzc2YTEgMSAwIDAgMCAwLTEuNDE0bC0yLjM3Ny0yLjM3N2ExIDEgMCAwIDAtMS40MTQgMHoiLz48L3N2Zz4=', + }, + height: 20, + width: 14, } }, }, tooltip: { - formatter: sankeyTooltip(echartNodes, []), + formatter: sankeyTooltip(echartNodes, nodeValues), }, nodeAlign: 'left', nodeWidth: 40, @@ -415,7 +445,7 @@ const EChartsSankey: React.FC = (props) => { const dynamicMinHeight = finalNodeCount * 15; containerStyle = { width: '100%', - minHeight: dynamicMinHeight, + minHeight: Math.max(550, dynamicMinHeight), height: '100%', overflowY: 'auto', }; @@ -427,7 +457,7 @@ const EChartsSankey: React.FC = (props) => { } return ( -
+
+) { return (params: any) => { if ('source' in params.data && 'target' in params.data) { const sourceName = echartNodes[params.data.source].name; const targetName = echartNodes[params.data.target].name; const sourceValue = nodeValues[params.data.source]; + + const safeSourceName = shortenString(sourceName); + const safeTargetName = shortenString(targetName); return `
- ${sourceName} + ${safeSourceName}
${sourceValue} Sessions
- ${targetName} + ${safeTargetName}
- ${params.data.value} ( ${params.data.percentage.toFixed(2)}% ) + ${params.data.value} ( ${params.data.percentage.toFixed( + 2 + )}% ) Sessions
@@ -38,6 +45,21 @@ export function sankeyTooltip(echartNodes: any[], nodeValues: number[]) { }; } +const shortenString = (str: string) => { + const limit = 60; + const leftPart = 25; + const rightPart = 20; + const safeStr = + str.length > limit + ? `${str.slice(0, leftPart)}...${str.slice( + str.length - rightPart, + str.length + )}` + : str; + + return safeStr; +}; + export const getEventPriority = (type: string): number => { switch (type) { case 'DROP': @@ -49,9 +71,12 @@ export const getEventPriority = (type: string): number => { } }; -export const getNodeName = (eventType: string, nodeName: string | null): string => { +export const getNodeName = ( + eventType: string, + nodeName: string | null +): string => { if (!nodeName) { return eventType.charAt(0) + eventType.slice(1).toLowerCase(); } return nodeName; -}; \ No newline at end of file +}; diff --git a/frontend/app/components/Client/CustomFields/CustomFields.tsx b/frontend/app/components/Client/CustomFields/CustomFields.tsx index d4c3ed218..d7e978d20 100644 --- a/frontend/app/components/Client/CustomFields/CustomFields.tsx +++ b/frontend/app/components/Client/CustomFields/CustomFields.tsx @@ -4,11 +4,11 @@ import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import { useModal } from 'App/components/Modal'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; -import { List, Space, Typography, Button, Tooltip } from 'antd'; -import { PencilIcon, PlusIcon, Tags } from 'lucide-react'; +import { List, Space, Typography, Button, Tooltip, Empty } from 'antd'; +import { PlusIcon, Tags } from 'lucide-react'; import {EditOutlined } from '@ant-design/icons'; import usePageTitle from '@/hooks/usePageTitle'; -import { Empty } from '.store/antd-virtual-7db13b4af6/package'; + const CustomFields = () => { usePageTitle('Metadata - OpenReplay Preferences'); diff --git a/frontend/app/components/Client/Modules/index.ts b/frontend/app/components/Client/Modules/index.ts index 6bc118ef7..a95b10cec 100644 --- a/frontend/app/components/Client/Modules/index.ts +++ b/frontend/app/components/Client/Modules/index.ts @@ -2,7 +2,7 @@ export { default } from './Modules'; export const enum MODULES { ASSIST = 'assist', - NOTES = 'notes', + HIGHLIGHTS = 'notes', BUG_REPORTS = 'bug-reports', OFFLINE_RECORDINGS = 'offline-recordings', ALERTS = 'alerts', @@ -43,10 +43,10 @@ export const modules = [ enterprise: true }, { - label: 'Notes', - description: 'Add notes to sessions and share with your team.', - key: MODULES.NOTES, - icon: 'stickies', + label: 'Highlights', + description: 'Add highlights to sessions and share with your team.', + key: MODULES.HIGHLIGHTS, + icon: 'chat-square-quote', isEnabled: true }, { diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx index 1b0e35d35..3eed642e6 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx @@ -18,7 +18,6 @@ function ClickMapCard() { const url = metricStore.instance.data.path; const operator = metricStore.instance.series[0]?.filter.filters[0]?.operator ? metricStore.instance.series[0].filter.filters[0].operator : 'startsWith' - React.useEffect(() => { return () => setCustomSession(null); }, []); diff --git a/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx b/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx index 751d7505d..6781aeedb 100644 --- a/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx +++ b/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx @@ -289,11 +289,13 @@ const AddCardSection = observer( ) : null}
- setTab(value)} - /> + {options.length > 1 ? ( + setTab(value)} + /> + ) : null}
diff --git a/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx b/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx index 66937d564..033fe55da 100644 --- a/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx +++ b/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx @@ -22,6 +22,10 @@ type Props = IProps & RouteComponentProps; function DashboardHeader(props: Props) { const { siteId } = props; + const [popoverOpen, setPopoverOpen] = React.useState(false); + const handleOpenChange = (open: boolean) => { + setPopoverOpen(open); + }; const { dashboardStore } = useStore(); const [focusTitle, setFocusedInput] = React.useState(true); const [showEditModal, setShowEditModal] = React.useState(false); @@ -82,7 +86,9 @@ function DashboardHeader(props: Props) { } + open={popoverOpen} + onOpenChange={handleOpenChange} + content={} overlayInnerStyle={{ padding: 0, borderRadius: '0.75rem' }} > + + ) : ( + <> + setIsModalVisible(true)} /> + + )} + + ); +}; diff --git a/frontend/app/layout/data.ts b/frontend/app/layout/data.ts index 611ae148f..b5e5d49d8 100644 --- a/frontend/app/layout/data.ts +++ b/frontend/app/layout/data.ts @@ -39,7 +39,6 @@ export const enum MENU { RECOMMENDATIONS = 'recommendations', VAULT = 'vault', BOOKMARKS = 'bookmarks', - NOTES = 'notes', HIGHLIGHTS = 'highlights', LIVE_SESSIONS = 'live-sessions', DASHBOARDS = 'dashboards', @@ -64,7 +63,6 @@ export const categories: Category[] = [ { label: 'Recommendations', key: MENU.RECOMMENDATIONS, icon: 'magic', hidden: true }, { label: 'Vault', key: MENU.VAULT, icon: 'safe', hidden: true }, { label: 'Bookmarks', key: MENU.BOOKMARKS, icon: 'bookmark' }, - //{ label: 'Notes', key: MENU.NOTES, icon: 'stickies' }, { label: 'Highlights', key: MENU.HIGHLIGHTS, icon: 'chat-square-quote' } ] }, diff --git a/frontend/app/mstore/filterStore.ts b/frontend/app/mstore/filterStore.ts index 0af2be151..eca477c79 100644 --- a/frontend/app/mstore/filterStore.ts +++ b/frontend/app/mstore/filterStore.ts @@ -23,6 +23,10 @@ export default class FilterStore { this.topValues[key] = vals?.filter((value) => value !== null && value.value !== ''); }; + resetValues = () => { + this.topValues = {}; + } + fetchTopValues = async (key: string, source?: string) => { if (this.topValues.hasOwnProperty(key)) { return Promise.resolve(this.topValues[key]); diff --git a/frontend/app/mstore/types/filter.ts b/frontend/app/mstore/types/filter.ts index ccde4e386..9bb135b2d 100644 --- a/frontend/app/mstore/types/filter.ts +++ b/frontend/app/mstore/types/filter.ts @@ -19,6 +19,7 @@ export interface IFilter { eventsHeader: string; page: number; limit: number; + autoOpen: boolean; merge(filter: any): void; @@ -62,6 +63,7 @@ export default class Filter implements IFilter { filterId: string = ''; name: string = ''; + autoOpen = false; filters: FilterItem[] = []; excludes: FilterItem[] = []; eventsOrder: string = 'then'; diff --git a/frontend/app/mstore/types/filterItem.ts b/frontend/app/mstore/types/filterItem.ts index 18716fb55..31f7ff94f 100644 --- a/frontend/app/mstore/types/filterItem.ts +++ b/frontend/app/mstore/types/filterItem.ts @@ -11,6 +11,7 @@ import { pageUrlOperators } from '../../constants/filterOptions'; export default class FilterItem { type: string = ''; category: FilterCategory = FilterCategory.METADATA; + subCategory: string = ''; key: string = ''; label: string = ''; value: any = ['']; @@ -63,6 +64,7 @@ export default class FilterItem { this.operatorOptions = data.operatorOptions; this.hasSource = data.hasSource; this.category = data.category; + this.subCategory = data.subCategory; this.sourceOperatorOptions = data.sourceOperatorOptions; this.value = data.value; this.isEvent = Boolean(data.isEvent); @@ -109,6 +111,7 @@ export default class FilterItem { this.operatorOptions = _filter.operatorOptions; this.hasSource = _filter.hasSource; this.category = _filter.category; + this.subCategory = _filter.subCategory; this.sourceOperatorOptions = _filter.sourceOperatorOptions; if (isHeatmap && this.key === FilterKey.LOCATION) { this.operatorOptions = pageUrlOperators; diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index fef672469..8d2a3921a 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -260,6 +260,7 @@ export default class Widget { updateStartPoint(startPoint: any) { runInAction(() => { this.startPoint = new FilterItem(startPoint); + this.hasChanged = true; }); } @@ -314,6 +315,20 @@ export default class Widget { } if (this.metricType === HEATMAP) { + const defaults = { + domURL: undefined, + duration: 0, + events: [], + mobsUrl: [], + path: '', + projectId: 0, + sessionId: null, + startTs: 0 + }; + if (!data || !data.domURL) { + this.data = defaults; + } + Object.assign(this.data, data); return; } diff --git a/frontend/app/types/app/period.js b/frontend/app/types/app/period.js index 50119c27d..e614f2dd2 100644 --- a/frontend/app/types/app/period.js +++ b/frontend/app/types/app/period.js @@ -20,12 +20,11 @@ function getRange(rangeName, offset) { const now = DateTime.now().setZone(offset); switch (rangeName) { case TODAY: - return Interval.fromDateTimes(now.startOf("day"), now.endOf("day")); + return Interval.fromDateTimes(now.startOf("day"), now.plus({ days:1 }).startOf("day")); case YESTERDAY: - const yesterday = now.minus({ days: 1 }); return Interval.fromDateTimes( - yesterday.startOf("day"), - yesterday.endOf("day") + now.minus({ days: 1 }).startOf("day"), + now.startOf("day") ); case LAST_24_HOURS: return Interval.fromDateTimes(now.minus({ hours: 24 }), now); @@ -36,13 +35,13 @@ function getRange(rangeName, offset) { ); case LAST_7_DAYS: return Interval.fromDateTimes( - now.minus({ days: 7 }).endOf("day"), - now.endOf("day") + now.minus({ days: 6 }).startOf("day"), + now.plus({ days: 1 }).startOf("day") ); case LAST_30_DAYS: return Interval.fromDateTimes( - now.minus({ days: 30 }).startOf("day"), - now.endOf("day") + now.minus({ days: 29 }).startOf("day"), + now.plus({ days: 1 }).startOf("day") ); case THIS_MONTH: return Interval.fromDateTimes(now.startOf("month"), now.endOf("month")); @@ -55,13 +54,13 @@ function getRange(rangeName, offset) { return Interval.fromDateTimes(now.minus({ hours: 48 }), now.minus({ hours: 24 })); case PREV_7_DAYS: return Interval.fromDateTimes( - now.minus({ days: 14 }).startOf("day"), - now.minus({ days: 7 }).endOf("day") + now.minus({ days: 13 }).startOf("day"), + now.minus({ days: 6 }).startOf("day") ); case PREV_30_DAYS: return Interval.fromDateTimes( - now.minus({ days: 60 }).startOf("day"), - now.minus({ days: 30 }).endOf("day") + now.minus({ days: 59 }).startOf("day"), + now.minus({ days: 29 }).startOf("day") ); default: return Interval.fromDateTimes(now, now); diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index 8ae2b0019..345bbeaa9 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -902,7 +902,8 @@ export const clickmapFilter = { type: FilterType.MULTIPLE, category: FilterCategory.EVENTS, subCategory: FilterCategory.AUTOCAPTURE, - label: 'Visited URL', placeholder: 'Enter URL or path', + label: 'Visited URL', + placeholder: 'Enter URL or path', operator: filterOptions.pageUrlOperators[0].value, operatorOptions: filterOptions.pageUrlOperators, icon: 'filters/location', diff --git a/scripts/helmcharts/databases/charts/clickhouse/values.yaml b/scripts/helmcharts/databases/charts/clickhouse/values.yaml index 2e37f0d80..0d5c3daad 100644 --- a/scripts/helmcharts/databases/charts/clickhouse/values.yaml +++ b/scripts/helmcharts/databases/charts/clickhouse/values.yaml @@ -72,9 +72,9 @@ service: dataPort: 8123 resources: - requests: - cpu: 1 - memory: 4Gi + requests: {} + # cpu: 1 + # memory: 4Gi limits: {} nodeSelector: {} diff --git a/scripts/helmcharts/databases/values.yaml b/scripts/helmcharts/databases/values.yaml index f1df0ece2..16ef521bf 100644 --- a/scripts/helmcharts/databases/values.yaml +++ b/scripts/helmcharts/databases/values.yaml @@ -166,7 +166,7 @@ kafka: # Enterprise dbs clickhouse: image: - tag: "24.12-alpine" + tag: "25.1-alpine" enabled: false postgreql: diff --git a/scripts/helmcharts/openreplay/templates/job.yaml b/scripts/helmcharts/openreplay/templates/job.yaml index a094f8951..0aa71f332 100644 --- a/scripts/helmcharts/openreplay/templates/job.yaml +++ b/scripts/helmcharts/openreplay/templates/job.yaml @@ -227,8 +227,8 @@ spec: - -c args: - | - lowVersion=24.9 - highVersion=24 + lowVersion=25.1 + highVersion=25 [[ "${CH_PASSWORD}" == "" ]] || { CH_PASSWORD="--password $CH_PASSWORD" } diff --git a/scripts/helmcharts/vars.yaml b/scripts/helmcharts/vars.yaml index 870325d13..43c1375dd 100644 --- a/scripts/helmcharts/vars.yaml +++ b/scripts/helmcharts/vars.yaml @@ -130,6 +130,8 @@ global: pvcRWXName: "hostPath" s3: region: "us-east-1" + # if you're using iam roles for authentication, keep the value empty. + # endpoint: "" endpoint: "http://minio.db.svc.cluster.local:9000" assetsBucket: "sessions-assets" recordingsBucket: "mobs" diff --git a/scripts/schema/db/init_dbs/postgresql/1.22.0/1.22.0.sql b/scripts/schema/db/init_dbs/postgresql/1.22.0/1.22.0.sql index 340929e68..a08ba5048 100644 --- a/scripts/schema/db/init_dbs/postgresql/1.22.0/1.22.0.sql +++ b/scripts/schema/db/init_dbs/postgresql/1.22.0/1.22.0.sql @@ -27,10 +27,10 @@ DROP TABLE IF EXISTS public.user_favorite_errors; DROP TABLE IF EXISTS public.user_viewed_errors; ALTER TABLE IF EXISTS public.sessions_notes - ADD COLUMN start_at integer, - ADD COLUMN end_at integer, - ADD COLUMN thumbnail text, - ADD COLUMN updated_at timestamp DEFAULT NULL, + ADD COLUMN IF NOT EXISTS start_at integer, + ADD COLUMN IF NOT EXISTS end_at integer, + ADD COLUMN IF NOT EXISTS thumbnail text, + ADD COLUMN IF NOT EXISTS updated_at timestamp DEFAULT NULL, ALTER COLUMN message DROP NOT NULL; DELETE diff --git a/tracker/tracker/CHANGELOG.md b/tracker/tracker/CHANGELOG.md index 54d8f73dc..1502e32b6 100644 --- a/tracker/tracker/CHANGELOG.md +++ b/tracker/tracker/CHANGELOG.md @@ -3,6 +3,7 @@ - update medv/finder to 4.0.2 for better support of css-in-js libs - fixes for single tab recording - add option to disable network completely `{ network: { disabled: true } }` +- fix for batching during offline recording syncs ## 15.0.4 diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index 96232cb65..325d9a576 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "15.0.5", + "version": "15.0.5-beta.1", "keywords": [ "logging", "replay" diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index 24c40b5c5..086c64f00 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -918,7 +918,6 @@ export default class App { private postToWorker(messages: Array) { this.worker?.postMessage(messages) this.commitCallbacks.forEach((cb) => cb(messages)) - messages.length = 0 } private delay = 0 @@ -1638,16 +1637,18 @@ export default class App { flushBuffer = async (buffer: Message[]) => { return new Promise((res) => { - let ended = false - const messagesBatch: Message[] = [buffer.shift() as unknown as Message] - while (!ended) { - const nextMsg = buffer[0] - if (!nextMsg || nextMsg[0] === MType.Timestamp) { - ended = true - } else { - messagesBatch.push(buffer.shift() as unknown as Message) - } + if (buffer.length === 0) { + res(null) + return } + + // Since the first element is always a Timestamp, include it by default. + let endIndex = 1 + while (endIndex < buffer.length && buffer[endIndex][0] !== MType.Timestamp) { + endIndex++ + } + + const messagesBatch = buffer.splice(0, endIndex) this.postToWorker(messagesBatch) res(null) }) diff --git a/tracker/tracker/src/main/index.ts b/tracker/tracker/src/main/index.ts index 8c5807035..27aaa242f 100644 --- a/tracker/tracker/src/main/index.ts +++ b/tracker/tracker/src/main/index.ts @@ -1,4 +1,4 @@ -import App, { DEFAULT_INGEST_POINT } from './app/index.js' +import App from './app/index.js' export { default as App } from './app/index.js'