diff --git a/api/app_alerts.py b/api/app_alerts.py index 9587048dd..863fb3967 100644 --- a/api/app_alerts.py +++ b/api/app_alerts.py @@ -5,7 +5,7 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from decouple import config from fastapi import FastAPI -from chalicelib.core import alerts_processor +from chalicelib.core.alerts import alerts_processor from chalicelib.utils import pg_client diff --git a/api/chalicelib/core/alerts/__init__.py b/api/chalicelib/core/alerts/__init__.py new file mode 100644 index 000000000..fad7c108a --- /dev/null +++ b/api/chalicelib/core/alerts/__init__.py @@ -0,0 +1,10 @@ +import logging + +from decouple import config + +logger = logging.getLogger(__name__) +if config("EXP_ALERTS", cast=bool, default=False): + logging.info(">>> Using experimental alerts") + from . import alerts_processor_ch as alerts_processor +else: + from . import alerts_processor as alerts_processor diff --git a/api/chalicelib/core/alerts.py b/api/chalicelib/core/alerts/alerts.py similarity index 100% rename from api/chalicelib/core/alerts.py rename to api/chalicelib/core/alerts/alerts.py diff --git a/api/chalicelib/core/alerts_listener.py b/api/chalicelib/core/alerts/alerts_listener.py similarity index 100% rename from api/chalicelib/core/alerts_listener.py rename to api/chalicelib/core/alerts/alerts_listener.py diff --git a/api/chalicelib/core/alerts_processor.py b/api/chalicelib/core/alerts/alerts_processor.py similarity index 98% rename from api/chalicelib/core/alerts_processor.py rename to api/chalicelib/core/alerts/alerts_processor.py index 1735a64ca..c78d7caa6 100644 --- a/api/chalicelib/core/alerts_processor.py +++ b/api/chalicelib/core/alerts/alerts_processor.py @@ -4,13 +4,14 @@ import logging from pydantic_core._pydantic_core import ValidationError import schemas -from chalicelib.core import alerts -from chalicelib.core import alerts_listener -from chalicelib.core import sessions +from chalicelib.core.alerts import alerts +from chalicelib.core.alerts import alerts_listener +from chalicelib.core.alerts import sessions from chalicelib.utils import pg_client from chalicelib.utils.TimeUTC import TimeUTC logger = logging.getLogger(__name__) + LeftToDb = { schemas.AlertColumn.PERFORMANCE__DOM_CONTENT_LOADED__AVERAGE: { "table": "events.pages INNER JOIN public.sessions USING(session_id)", diff --git a/ee/api/chalicelib/core/alerts_processor_exp.py b/api/chalicelib/core/alerts/alerts_processor_ch.py similarity index 98% rename from ee/api/chalicelib/core/alerts_processor_exp.py rename to api/chalicelib/core/alerts/alerts_processor_ch.py index 13e047206..04e04f6f9 100644 --- a/ee/api/chalicelib/core/alerts_processor_exp.py +++ b/api/chalicelib/core/alerts/alerts_processor_ch.py @@ -3,9 +3,9 @@ import logging from pydantic_core._pydantic_core import ValidationError import schemas -from chalicelib.core import alerts -from chalicelib.core import alerts_listener, alerts_processor -from chalicelib.core import sessions_exp as sessions +from chalicelib.core.alerts import alerts +from chalicelib.core.alerts import alerts_listener, alerts_processor +from chalicelib.core.alerts import sessions from chalicelib.utils import pg_client, ch_client, exp_ch_helper from chalicelib.utils.TimeUTC import TimeUTC diff --git a/api/chalicelib/core/alerts/sessions/__init__.py b/api/chalicelib/core/alerts/sessions/__init__.py new file mode 100644 index 000000000..c6772a531 --- /dev/null +++ b/api/chalicelib/core/alerts/sessions/__init__.py @@ -0,0 +1,6 @@ +from decouple import config + +if config("EXP_ALERTS", cast=bool, default=False): + from chalicelib.core.sessions_ch import * +else: + from chalicelib.core.sessions import * diff --git a/ee/api/chalicelib/core/sessions_exp.py b/api/chalicelib/core/sessions_ch.py similarity index 98% rename from ee/api/chalicelib/core/sessions_exp.py rename to api/chalicelib/core/sessions_ch.py index 78124c923..825f67dac 100644 --- a/ee/api/chalicelib/core/sessions_exp.py +++ b/api/chalicelib/core/sessions_ch.py @@ -3,11 +3,12 @@ import logging from typing import List, Union import schemas -from chalicelib.core import events, metadata, projects, performance_event, metrics, sessions_favorite, sessions_legacy +from chalicelib.core import events, metadata, projects, performance_event, metrics, sessions_favorite, sessions from chalicelib.utils import pg_client, helper, metrics_helper, ch_client, exp_ch_helper from chalicelib.utils import sql_helper as sh logger = logging.getLogger(__name__) + SESSION_PROJECTION_COLS_CH = """\ s.project_id, s.session_id AS session_id, @@ -1690,24 +1691,4 @@ def check_recording_status(project_id: int) -> dict: # TODO: rewrite this function to use ClickHouse def search_sessions_by_ids(project_id: int, session_ids: list, sort_by: str = 'session_id', ascending: bool = False) -> dict: - if session_ids is None or len(session_ids) == 0: - return {"total": 0, "sessions": []} - with pg_client.PostgresClient() as cur: - meta_keys = metadata.get(project_id=project_id) - params = {"project_id": project_id, "session_ids": tuple(session_ids)} - order_direction = 'ASC' if ascending else 'DESC' - main_query = cur.mogrify(f"""SELECT {sessions_legacy.SESSION_PROJECTION_BASE_COLS} - {"," if len(meta_keys) > 0 else ""}{",".join([f'metadata_{m["index"]}' for m in meta_keys])} - FROM public.sessions AS s - WHERE project_id=%(project_id)s - AND session_id IN %(session_ids)s - ORDER BY {sort_by} {order_direction};""", params) - - cur.execute(main_query) - rows = cur.fetchall() - if len(meta_keys) > 0: - for s in rows: - s["metadata"] = {} - for m in meta_keys: - s["metadata"][m["key"]] = s.pop(f'metadata_{m["index"]}') - return {"total": len(rows), "sessions": helper.list_to_camel_case(rows)} + return sessions.search_sessions_by_ids(project_id, session_ids, sort_by, ascending) diff --git a/api/chalicelib/core/significance.py b/api/chalicelib/core/significance.py index fd8a3af17..d3ae2a443 100644 --- a/api/chalicelib/core/significance.py +++ b/api/chalicelib/core/significance.py @@ -765,30 +765,6 @@ def get_issues(stages, rows, first_stage=None, last_stage=None, drop_only=False) return n_critical_issues, issues_dict, total_drop_due_to_issues -def get_top_insights(filter_d: schemas.CardSeriesFilterSchema, project_id, - metric_format: schemas.MetricExtendedFormatType): - output = [] - stages = filter_d.events - - if len(stages) == 0: - logger.debug("no stages found") - return output, 0 - - # The result of the multi-stage query - rows = get_stages_and_events(filter_d=filter_d, project_id=project_id) - # Obtain the first part of the output - stages_list = get_stages(stages, rows, metric_format=metric_format) - if len(rows) == 0: - return stages_list, 0 - - # Obtain the second part of the output - total_drop_due_to_issues = get_issues(stages, rows, - first_stage=1, - last_stage=len(filter_d.events), - drop_only=True) - return stages_list, total_drop_due_to_issues - - def get_issues_list(filter_d: schemas.CardSeriesFilterSchema, project_id, first_stage=None, last_stage=None): output = dict({"total_drop_due_to_issues": 0, "critical_issues_count": 0, "significant": [], "insignificant": []}) stages = filter_d.events diff --git a/api/chalicelib/utils/exp_ch_helper.py b/api/chalicelib/utils/exp_ch_helper.py index a15672614..cd8fb052f 100644 --- a/api/chalicelib/utils/exp_ch_helper.py +++ b/api/chalicelib/utils/exp_ch_helper.py @@ -17,17 +17,6 @@ def get_main_sessions_table(timestamp=0): return "experimental.sessions" -def get_user_favorite_sessions_table(timestamp=0): - return "experimental.user_favorite_sessions" - - -def get_user_viewed_sessions_table(timestamp=0): - return "experimental.user_viewed_sessions" - - -def get_user_viewed_errors_table(timestamp=0): - return "experimental.user_viewed_errors" - def get_main_js_errors_sessions_table(timestamp=0): return get_main_events_table(timestamp=timestamp) diff --git a/api/env.default b/api/env.default index 5340e1f13..e54f9dfb4 100644 --- a/api/env.default +++ b/api/env.default @@ -71,4 +71,5 @@ sourcemaps_reader=http://sourcemapreader-openreplay.app.svc.cluster.local:9000/s STAGE=default-foss TZ=UTC EXP_CH_DRIVER=true -EXP_AUTOCOMPLETE=true \ No newline at end of file +EXP_AUTOCOMPLETE=true +EXP_ALERTS=true \ No newline at end of file diff --git a/api/schemas/schemas.py b/api/schemas/schemas.py index 25d60fcf4..44ffc9335 100644 --- a/api/schemas/schemas.py +++ b/api/schemas/schemas.py @@ -11,59 +11,6 @@ from .transformers_validators import transform_email, remove_whitespace, remove_ force_is_event, NAME_PATTERN, int_to_string, check_alphanumeric -def transform_old_filter_type(cls, values): - if values.get("type") is None: - return values - values["type"] = { - # filters - "USEROS": FilterType.USER_OS.value, - "USERBROWSER": FilterType.USER_BROWSER.value, - "USERDEVICE": FilterType.USER_DEVICE.value, - "USERCOUNTRY": FilterType.USER_COUNTRY.value, - "USERID": FilterType.USER_ID.value, - "USERANONYMOUSID": FilterType.USER_ANONYMOUS_ID.value, - "REFERRER": FilterType.REFERRER.value, - "REVID": FilterType.REV_ID.value, - "USEROS_IOS": FilterType.USER_OS_MOBILE.value, - "USERDEVICE_IOS": FilterType.USER_DEVICE_MOBILE.value, - "USERCOUNTRY_IOS": FilterType.USER_COUNTRY_MOBILE.value, - "USERID_IOS": FilterType.USER_ID_MOBILE.value, - "USERANONYMOUSID_IOS": FilterType.USER_ANONYMOUS_ID_MOBILE.value, - "REVID_IOS": FilterType.REV_ID_MOBILE.value, - "DURATION": FilterType.DURATION.value, - "PLATFORM": FilterType.PLATFORM.value, - "METADATA": FilterType.METADATA.value, - "ISSUE": FilterType.ISSUE.value, - "EVENTS_COUNT": FilterType.EVENTS_COUNT.value, - "UTM_SOURCE": FilterType.UTM_SOURCE.value, - "UTM_MEDIUM": FilterType.UTM_MEDIUM.value, - "UTM_CAMPAIGN": FilterType.UTM_CAMPAIGN.value, - # events: - "CLICK": EventType.CLICK.value, - "INPUT": EventType.INPUT.value, - "LOCATION": EventType.LOCATION.value, - "CUSTOM": EventType.CUSTOM.value, - "REQUEST": EventType.REQUEST.value, - "FETCH": EventType.REQUEST_DETAILS.value, - "GRAPHQL": EventType.GRAPHQL.value, - "STATEACTION": EventType.STATE_ACTION.value, - "ERROR": EventType.ERROR.value, - "CLICK_IOS": EventType.CLICK_MOBILE.value, - "INPUT_IOS": EventType.INPUT_MOBILE.value, - "VIEW_IOS": EventType.VIEW_MOBILE.value, - "CUSTOM_IOS": EventType.CUSTOM_MOBILE.value, - "REQUEST_IOS": EventType.REQUEST_MOBILE.value, - "ERROR_IOS": EventType.ERROR_MOBILE.value, - "DOM_COMPLETE": PerformanceEventType.LOCATION_DOM_COMPLETE.value, - "LARGEST_CONTENTFUL_PAINT_TIME": PerformanceEventType.LOCATION_LARGEST_CONTENTFUL_PAINT_TIME.value, - "TTFB": PerformanceEventType.LOCATION_TTFB.value, - "AVG_CPU_LOAD": PerformanceEventType.LOCATION_AVG_CPU_LOAD.value, - "AVG_MEMORY_USAGE": PerformanceEventType.LOCATION_AVG_MEMORY_USAGE.value, - "FETCH_FAILED": PerformanceEventType.FETCH_FAILED.value, - }.get(values["type"], values["type"]) - return values - - class _GRecaptcha(BaseModel): g_recaptcha_response: Optional[str] = Field(default=None, alias='g-recaptcha-response') @@ -602,7 +549,6 @@ class SessionSearchEventSchema2(BaseModel): _remove_duplicate_values = field_validator('value', mode='before')(remove_duplicate_values) _single_to_list_values = field_validator('value', mode='before')(single_to_list) - _transform = model_validator(mode='before')(transform_old_filter_type) @model_validator(mode="after") def event_validator(self): @@ -639,7 +585,6 @@ class SessionSearchFilterSchema(BaseModel): source: Optional[Union[ErrorSource, str]] = Field(default=None) _remove_duplicate_values = field_validator('value', mode='before')(remove_duplicate_values) - _transform = model_validator(mode='before')(transform_old_filter_type) _single_to_list_values = field_validator('value', mode='before')(single_to_list) @model_validator(mode="before") @@ -898,6 +843,11 @@ class CardSeriesSchema(BaseModel): class MetricTimeseriesViewType(str, Enum): LINE_CHART = "lineChart" AREA_CHART = "areaChart" + BAR_CHART = "barChart" + PIE_CHART = "pieChart" + PROGRESS_CHART = "progressChart" + TABLE_CHART = "table" + METRIC_CHART = "metric" class MetricTableViewType(str, Enum): @@ -1356,8 +1306,6 @@ class LiveSessionSearchFilterSchema(BaseModel): operator: Literal[SearchEventOperator.IS, SearchEventOperator.CONTAINS] \ = Field(default=SearchEventOperator.CONTAINS) - _transform = model_validator(mode='before')(transform_old_filter_type) - @model_validator(mode="after") def __validator(self): if self.type is not None and self.type == LiveFilterType.METADATA: diff --git a/backend/cmd/analytics/main.go b/backend/cmd/analytics/main.go new file mode 100644 index 000000000..8ea792438 --- /dev/null +++ b/backend/cmd/analytics/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "context" + analyticsConfig "openreplay/backend/internal/config/analytics" + "openreplay/backend/pkg/analytics" + "openreplay/backend/pkg/db/postgres/pool" + "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/metrics" + analyticsMetrics "openreplay/backend/pkg/metrics/analytics" + databaseMetrics "openreplay/backend/pkg/metrics/database" + "openreplay/backend/pkg/metrics/web" + "openreplay/backend/pkg/server" + "openreplay/backend/pkg/server/api" +) + +func main() { + ctx := context.Background() + log := logger.New() + cfg := analyticsConfig.New(log) + webMetrics := web.New("analytics") + metrics.New(log, append(webMetrics.List(), append(analyticsMetrics.List(), databaseMetrics.List()...)...)) + + pgConn, err := pool.New(cfg.Postgres.String()) + if err != nil { + log.Fatal(ctx, "can't init postgres connection: %s", err) + } + defer pgConn.Close() + + builder, err := analytics.NewServiceBuilder(log, cfg, webMetrics, pgConn) + if err != nil { + log.Fatal(ctx, "can't init services: %s", err) + } + + router, err := api.NewRouter(&cfg.HTTP, log) + if err != nil { + log.Fatal(ctx, "failed while creating router: %s", err) + } + router.AddHandlers(api.NoPrefix, builder.AnalyticsAPI) + router.AddMiddlewares(builder.Auth.Middleware, builder.RateLimiter.Middleware, builder.AuditTrail.Middleware) + + server.Run(ctx, log, &cfg.HTTP, router) +} diff --git a/backend/cmd/db/main.go b/backend/cmd/db/main.go index 5d75b02d7..a3eac941c 100644 --- a/backend/cmd/db/main.go +++ b/backend/cmd/db/main.go @@ -6,6 +6,7 @@ import ( config "openreplay/backend/internal/config/db" "openreplay/backend/internal/db" "openreplay/backend/internal/db/datasaver" + "openreplay/backend/pkg/db/clickhouse" "openreplay/backend/pkg/db/postgres" "openreplay/backend/pkg/db/postgres/pool" "openreplay/backend/pkg/db/redis" @@ -33,9 +34,15 @@ func main() { } defer pgConn.Close() - // Init events module - pg := postgres.NewConn(log, pgConn) - defer pg.Close() + chConn := clickhouse.NewConnector(cfg.Clickhouse) + if err := chConn.Prepare(); err != nil { + log.Fatal(ctx, "can't prepare clickhouse: %s", err) + } + defer chConn.Stop() + + // Init db proxy module (postgres + clickhouse + batches) + dbProxy := postgres.NewConn(log, pgConn, chConn) + defer dbProxy.Close() // Init redis connection redisClient, err := redis.New(&cfg.Redis) @@ -49,7 +56,7 @@ func main() { tagsManager := tags.New(log, pgConn) // Init data saver - saver := datasaver.New(log, cfg, pg, sessManager, tagsManager) + saver := datasaver.New(log, cfg, dbProxy, chConn, sessManager, tagsManager) // Message filter msgFilter := []int{ diff --git a/backend/internal/config/analytics/config.go b/backend/internal/config/analytics/config.go new file mode 100644 index 000000000..b6ca5ce4c --- /dev/null +++ b/backend/internal/config/analytics/config.go @@ -0,0 +1,29 @@ +package analytics + +import ( + "time" + + "openreplay/backend/internal/config/common" + "openreplay/backend/internal/config/configurator" + "openreplay/backend/internal/config/objectstorage" + "openreplay/backend/internal/config/redis" + "openreplay/backend/pkg/env" + "openreplay/backend/pkg/logger" +) + +type Config struct { + common.Config + common.Postgres + redis.Redis + objectstorage.ObjectsConfig + common.HTTP + FSDir string `env:"FS_DIR,required"` + ProjectExpiration time.Duration `env:"PROJECT_EXPIRATION,default=10m"` + WorkerID uint16 +} + +func New(log logger.Logger) *Config { + cfg := &Config{WorkerID: env.WorkerID()} + configurator.Process(log, cfg) + return cfg +} diff --git a/backend/internal/config/common/config.go b/backend/internal/config/common/config.go index dd21d2ae0..a2db40c48 100644 --- a/backend/internal/config/common/config.go +++ b/backend/internal/config/common/config.go @@ -57,10 +57,18 @@ type Redshift struct { // Clickhouse config type Clickhouse struct { - URL string `env:"CLICKHOUSE_STRING"` - Database string `env:"CLICKHOUSE_DATABASE,default=default"` - UserName string `env:"CLICKHOUSE_USERNAME,default=default"` - Password string `env:"CLICKHOUSE_PASSWORD,default="` + URL string `env:"CLICKHOUSE_STRING"` + Database string `env:"CLICKHOUSE_DATABASE,default=default"` + UserName string `env:"CLICKHOUSE_USERNAME,default=default"` + Password string `env:"CLICKHOUSE_PASSWORD,default="` + LegacyUserName string `env:"CH_USERNAME,default=default"` + LegacyPassword string `env:"CH_PASSWORD,default="` +} + +func (cfg *Clickhouse) GetTrimmedURL() string { + chUrl := strings.TrimPrefix(cfg.URL, "tcp://") + chUrl = strings.TrimSuffix(chUrl, "/default") + return chUrl } // ElasticSearch config diff --git a/backend/internal/config/db/config.go b/backend/internal/config/db/config.go index 48d49dc62..e6f45e18a 100644 --- a/backend/internal/config/db/config.go +++ b/backend/internal/config/db/config.go @@ -11,6 +11,7 @@ import ( type Config struct { common.Config common.Postgres + common.Clickhouse redis.Redis ProjectExpiration time.Duration `env:"PROJECT_EXPIRATION,default=10m"` LoggerTimeout int `env:"LOG_QUEUE_STATS_INTERVAL_SEC,required"` diff --git a/backend/internal/db/datasaver/fts.go b/backend/internal/db/datasaver/fts.go new file mode 100644 index 000000000..64ca17bc4 --- /dev/null +++ b/backend/internal/db/datasaver/fts.go @@ -0,0 +1,9 @@ +package datasaver + +import ( + "openreplay/backend/pkg/messages" +) + +func (s *saverImpl) init() {} + +func (s *saverImpl) sendToFTS(msg messages.Message, projID uint32) {} diff --git a/backend/internal/db/datasaver/methods.go b/backend/internal/db/datasaver/methods.go deleted file mode 100644 index 07a8b6ba2..000000000 --- a/backend/internal/db/datasaver/methods.go +++ /dev/null @@ -1,17 +0,0 @@ -package datasaver - -import ( - . "openreplay/backend/pkg/messages" -) - -func (s *saverImpl) init() { - // noop -} - -func (s *saverImpl) handleExtraMessage(msg Message) error { - switch m := msg.(type) { - case *PerformanceTrackAggr: - return s.pg.InsertWebStatsPerformance(m) - } - return nil -} diff --git a/backend/internal/db/datasaver/mobile.go b/backend/internal/db/datasaver/mobile.go new file mode 100644 index 000000000..3c9e01a0a --- /dev/null +++ b/backend/internal/db/datasaver/mobile.go @@ -0,0 +1,72 @@ +package datasaver + +import ( + "context" + + "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/sessions" +) + +func (s *saverImpl) handleMobileMessage(sessCtx context.Context, session *sessions.Session, msg messages.Message) error { + switch m := msg.(type) { + case *messages.MobileSessionEnd: + return s.ch.InsertMobileSession(session) + case *messages.MobileUserID: + if err := s.sessions.UpdateUserID(session.SessionID, m.ID); err != nil { + return err + } + s.pg.InsertAutocompleteValue(session.SessionID, session.ProjectID, "USERIDMOBILE", m.ID) + return nil + case *messages.MobileUserAnonymousID: + if err := s.sessions.UpdateAnonymousID(session.SessionID, m.ID); err != nil { + return err + } + s.pg.InsertAutocompleteValue(session.SessionID, session.ProjectID, "USERANONYMOUSIDMOBILE", m.ID) + return nil + case *messages.MobileMetadata: + return s.sessions.UpdateMetadata(m.SessionID(), m.Key, m.Value) + case *messages.MobileEvent: + if err := s.pg.InsertMobileEvent(session, m); err != nil { + return err + } + return s.ch.InsertMobileCustom(session, m) + case *messages.MobileClickEvent: + if err := s.pg.InsertMobileClickEvent(session, m); err != nil { + return err + } + if err := s.sessions.UpdateEventsStats(session.SessionID, 1, 0); err != nil { + return err + } + return s.ch.InsertMobileClick(session, m) + case *messages.MobileSwipeEvent: + if err := s.pg.InsertMobileSwipeEvent(session, m); err != nil { + return err + } + if err := s.sessions.UpdateEventsStats(session.SessionID, 1, 0); err != nil { + return err + } + return s.ch.InsertMobileSwipe(session, m) + case *messages.MobileInputEvent: + if err := s.pg.InsertMobileInputEvent(session, m); err != nil { + return err + } + if err := s.sessions.UpdateEventsStats(session.SessionID, 1, 0); err != nil { + return err + } + return s.ch.InsertMobileInput(session, m) + case *messages.MobileNetworkCall: + if err := s.pg.InsertMobileNetworkCall(session, m); err != nil { + return err + } + return s.ch.InsertMobileRequest(session, m, session.SaveRequestPayload) + case *messages.MobileCrash: + if err := s.pg.InsertMobileCrash(session.SessionID, session.ProjectID, m); err != nil { + return err + } + if err := s.sessions.UpdateIssuesStats(session.SessionID, 1, 1000); err != nil { + return err + } + return s.ch.InsertMobileCrash(session, m) + } + return nil +} diff --git a/backend/internal/db/datasaver/saver.go b/backend/internal/db/datasaver/saver.go index d3d217e4b..476a81e9b 100644 --- a/backend/internal/db/datasaver/saver.go +++ b/backend/internal/db/datasaver/saver.go @@ -30,11 +30,18 @@ type saverImpl struct { tags tags.Tags } -func New(log logger.Logger, cfg *db.Config, pg *postgres.Conn, session sessions.Sessions, tags tags.Tags) Saver { +func New(log logger.Logger, cfg *db.Config, pg *postgres.Conn, ch clickhouse.Connector, session sessions.Sessions, tags tags.Tags) Saver { + switch { + case pg == nil: + log.Fatal(context.Background(), "pg pool is empty") + case ch == nil: + log.Fatal(context.Background(), "ch pool is empty") + } s := &saverImpl{ log: log, cfg: cfg, pg: pg, + ch: ch, sessions: session, tags: tags, } @@ -43,21 +50,34 @@ func New(log logger.Logger, cfg *db.Config, pg *postgres.Conn, session sessions. } func (s *saverImpl) Handle(msg Message) { - sessCtx := context.WithValue(context.Background(), "sessionID", msg.SessionID()) if msg.TypeID() == MsgCustomEvent { defer s.Handle(types.WrapCustomEvent(msg.(*CustomEvent))) } + + var ( + sessCtx = context.WithValue(context.Background(), "sessionID", msg.SessionID()) + session *sessions.Session + err error + ) + if msg.TypeID() == MsgSessionEnd || msg.TypeID() == MsgMobileSessionEnd { + session, err = s.sessions.GetUpdated(msg.SessionID(), true) + } else { + session, err = s.sessions.Get(msg.SessionID()) + } + if err != nil || session == nil { + s.log.Error(sessCtx, "error on session retrieving from cache: %v, SessionID: %v, Message: %v", err, msg.SessionID(), msg) + return + } + if IsMobileType(msg.TypeID()) { - // Handle Mobile messages - if err := s.handleMobileMessage(msg); err != nil { + if err := s.handleMobileMessage(sessCtx, session, msg); err != nil { if !postgres.IsPkeyViolation(err) { s.log.Error(sessCtx, "mobile message insertion error, msg: %+v, err: %s", msg, err) } return } } else { - // Handle Web messages - if err := s.handleMessage(msg); err != nil { + if err := s.handleWebMessage(sessCtx, session, msg); err != nil { if !postgres.IsPkeyViolation(err) { s.log.Error(sessCtx, "web message insertion error, msg: %+v, err: %s", msg, err) } @@ -65,180 +85,22 @@ func (s *saverImpl) Handle(msg Message) { } } - if err := s.handleExtraMessage(msg); err != nil { - s.log.Error(sessCtx, "extra message insertion error, msg: %+v, err: %s", msg, err) - } + s.sendToFTS(msg, session.ProjectID) return } -func (s *saverImpl) handleMobileMessage(msg Message) error { - session, err := s.sessions.Get(msg.SessionID()) - if err != nil { - return err - } - switch m := msg.(type) { - case *MobileUserID: - if err = s.sessions.UpdateUserID(session.SessionID, m.ID); err != nil { - return err - } - s.pg.InsertAutocompleteValue(session.SessionID, session.ProjectID, "USERIDMOBILE", m.ID) - return nil - case *MobileUserAnonymousID: - if err = s.sessions.UpdateAnonymousID(session.SessionID, m.ID); err != nil { - return err - } - s.pg.InsertAutocompleteValue(session.SessionID, session.ProjectID, "USERANONYMOUSIDMOBILE", m.ID) - return nil - case *MobileMetadata: - return s.sessions.UpdateMetadata(m.SessionID(), m.Key, m.Value) - case *MobileEvent: - return s.pg.InsertMobileEvent(session, m) - case *MobileClickEvent: - if err := s.pg.InsertMobileClickEvent(session, m); err != nil { - return err - } - return s.sessions.UpdateEventsStats(session.SessionID, 1, 0) - case *MobileSwipeEvent: - if err := s.pg.InsertMobileSwipeEvent(session, m); err != nil { - return err - } - return s.sessions.UpdateEventsStats(session.SessionID, 1, 0) - case *MobileInputEvent: - if err := s.pg.InsertMobileInputEvent(session, m); err != nil { - return err - } - return s.sessions.UpdateEventsStats(session.SessionID, 1, 0) - case *MobileNetworkCall: - return s.pg.InsertMobileNetworkCall(session, m) - case *MobileCrash: - if err := s.pg.InsertMobileCrash(session.SessionID, session.ProjectID, m); err != nil { - return err - } - return s.sessions.UpdateIssuesStats(session.SessionID, 1, 1000) - } - return nil -} - -func (s *saverImpl) handleMessage(msg Message) error { - session, err := s.sessions.Get(msg.SessionID()) - if err != nil { - return err - } - sessCtx := context.WithValue(context.Background(), "sessionID", msg.SessionID()) - switch m := msg.(type) { - case *SessionStart: - return s.pg.HandleStartEvent(m) - case *SessionEnd: - return s.pg.HandleEndEvent(m.SessionID()) - case *Metadata: - return s.sessions.UpdateMetadata(m.SessionID(), m.Key, m.Value) - case *IssueEvent: - if m.Type == "dead_click" || m.Type == "click_rage" { - if s.tags.ShouldIgnoreTag(session.ProjectID, m.Context) { - return nil - } - } - err = s.pg.InsertIssueEvent(session, m) - if err != nil { - return err - } - return s.sessions.UpdateIssuesStats(session.SessionID, 0, postgres.GetIssueScore(m.Type)) - case *CustomIssue: - ie := &IssueEvent{ - Type: "custom", - Timestamp: m.Timestamp, - MessageID: m.Index, - ContextString: m.Name, - Payload: m.Payload, - } - ie.SetMeta(m.Meta()) - if err = s.pg.InsertIssueEvent(session, ie); err != nil { - return err - } - return s.sessions.UpdateIssuesStats(session.SessionID, 0, postgres.GetIssueScore(ie.Type)) - case *UserID: - if err = s.sessions.UpdateUserID(session.SessionID, m.ID); err != nil { - return err - } - s.pg.InsertAutocompleteValue(session.SessionID, session.ProjectID, "USERID", m.ID) - return nil - case *UserAnonymousID: - if err = s.sessions.UpdateAnonymousID(session.SessionID, m.ID); err != nil { - return err - } - s.pg.InsertAutocompleteValue(session.SessionID, session.ProjectID, "USERANONYMOUSID", m.ID) - return nil - case *CustomEvent: - return s.pg.InsertWebCustomEvent(session, m) - case *MouseClick: - if err = s.pg.InsertWebClickEvent(session, m); err != nil { - return err - } - return s.sessions.UpdateEventsStats(session.SessionID, 1, 0) - case *PageEvent: - if err = s.pg.InsertWebPageEvent(session, m); err != nil { - return err - } - s.sessions.UpdateReferrer(session.SessionID, m.Referrer) - s.sessions.UpdateUTM(session.SessionID, m.URL) - return s.sessions.UpdateEventsStats(session.SessionID, 1, 1) - case *NetworkRequest: - return s.pg.InsertWebNetworkRequest(session, m) - case *GraphQL: - return s.pg.InsertWebGraphQL(session, m) - case *JSException: - wrapper, err := types.WrapJSException(m) - if err != nil { - s.log.Warn(sessCtx, "error on wrapping JSException: %v", err) - } - if err = s.pg.InsertWebErrorEvent(session, wrapper); err != nil { - return err - } - return s.sessions.UpdateIssuesStats(session.SessionID, 1, 1000) - case *IntegrationEvent: - return s.pg.InsertWebErrorEvent(session, types.WrapIntegrationEvent(m)) - case *InputChange: - if err = s.pg.InsertInputChangeEvent(session, m); err != nil { - return err - } - return s.sessions.UpdateEventsStats(session.SessionID, 1, 0) - case *MouseThrashing: - if err = s.pg.InsertMouseThrashing(session, m); err != nil { - return err - } - return s.sessions.UpdateIssuesStats(session.SessionID, 0, 50) - case *CanvasNode: - if err = s.pg.InsertCanvasNode(session, m); err != nil { - return err - } - case *TagTrigger: - if err = s.pg.InsertTagTrigger(session, m); err != nil { - return err - } - } - return nil -} - func (s *saverImpl) Commit() error { - if s.pg != nil { - s.pg.Commit() - } - if s.ch != nil { - s.ch.Commit() - } + s.pg.Commit() + s.ch.Commit() return nil } func (s *saverImpl) Close() error { - if s.pg != nil { - if err := s.pg.Close(); err != nil { - s.log.Error(context.Background(), "pg.Close error: %s", err) - } + if err := s.pg.Close(); err != nil { + s.log.Error(context.Background(), "pg.Close error: %s", err) } - if s.ch != nil { - if err := s.ch.Stop(); err != nil { - s.log.Error(context.Background(), "ch.Close error: %s", err) - } + if err := s.ch.Stop(); err != nil { + s.log.Error(context.Background(), "ch.Close error: %s", err) } return nil } diff --git a/backend/internal/db/datasaver/web.go b/backend/internal/db/datasaver/web.go new file mode 100644 index 000000000..439bcec32 --- /dev/null +++ b/backend/internal/db/datasaver/web.go @@ -0,0 +1,146 @@ +package datasaver + +import ( + "context" + + "openreplay/backend/pkg/db/postgres" + "openreplay/backend/pkg/db/types" + "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/sessions" +) + +func (s *saverImpl) handleWebMessage(sessCtx context.Context, session *sessions.Session, msg messages.Message) error { + switch m := msg.(type) { + case *messages.SessionStart: + return s.pg.HandleStartEvent(m) + case *messages.SessionEnd: + if err := s.pg.HandleEndEvent(m.SessionID()); err != nil { + return err + } + session, err := s.sessions.GetUpdated(m.SessionID(), true) + if err != nil { + return err + } + return s.ch.InsertWebSession(session) + case *messages.Metadata: + return s.sessions.UpdateMetadata(m.SessionID(), m.Key, m.Value) + case *messages.IssueEvent: + if m.Type == "dead_click" || m.Type == "click_rage" { + if s.tags.ShouldIgnoreTag(session.ProjectID, m.Context) { + return nil + } + } + if err := s.pg.InsertIssueEvent(session, m); err != nil { + return err + } + if err := s.sessions.UpdateIssuesStats(session.SessionID, 0, postgres.GetIssueScore(m.Type)); err != nil { + return err + } + return s.ch.InsertIssue(session, m) + case *messages.CustomIssue: + ie := &messages.IssueEvent{ + Type: "custom", + Timestamp: m.Timestamp, + MessageID: m.Index, + ContextString: m.Name, + Payload: m.Payload, + } + ie.SetMeta(m.Meta()) + if err := s.pg.InsertIssueEvent(session, ie); err != nil { + return err + } + return s.sessions.UpdateIssuesStats(session.SessionID, 0, postgres.GetIssueScore(ie.Type)) + case *messages.UserID: + if err := s.sessions.UpdateUserID(session.SessionID, m.ID); err != nil { + return err + } + s.pg.InsertAutocompleteValue(session.SessionID, session.ProjectID, "USERID", m.ID) + return nil + case *messages.UserAnonymousID: + if err := s.sessions.UpdateAnonymousID(session.SessionID, m.ID); err != nil { + return err + } + s.pg.InsertAutocompleteValue(session.SessionID, session.ProjectID, "USERANONYMOUSID", m.ID) + return nil + case *messages.CustomEvent: + if err := s.pg.InsertWebCustomEvent(session, m); err != nil { + return err + } + return s.ch.InsertCustom(session, m) + case *messages.MouseClick: + if err := s.pg.InsertWebClickEvent(session, m); err != nil { + return err + } + if err := s.sessions.UpdateEventsStats(session.SessionID, 1, 0); err != nil { + return err + } + return s.ch.InsertWebClickEvent(session, m) + case *messages.PageEvent: + if err := s.pg.InsertWebPageEvent(session, m); err != nil { + return err + } + s.sessions.UpdateReferrer(session.SessionID, m.Referrer) + s.sessions.UpdateUTM(session.SessionID, m.URL) + if err := s.sessions.UpdateEventsStats(session.SessionID, 1, 1); err != nil { + return err + } + return s.ch.InsertWebPageEvent(session, m) + case *messages.NetworkRequest: + if err := s.pg.InsertWebNetworkRequest(session, m); err != nil { + return err + } + return s.ch.InsertRequest(session, m, session.SaveRequestPayload) + case *messages.GraphQL: + if err := s.pg.InsertWebGraphQL(session, m); err != nil { + return err + } + return s.ch.InsertGraphQL(session, m) + case *messages.JSException: + wrapper, err := types.WrapJSException(m) + if err != nil { + s.log.Warn(sessCtx, "error on wrapping JSException: %v", err) + } + if err = s.pg.InsertWebErrorEvent(session, wrapper); err != nil { + return err + } + if err := s.sessions.UpdateIssuesStats(session.SessionID, 1, 1000); err != nil { + return err + } + return s.ch.InsertWebErrorEvent(session, wrapper) + case *messages.IntegrationEvent: + if err := s.pg.InsertWebErrorEvent(session, types.WrapIntegrationEvent(m)); err != nil { + return err + } + return s.ch.InsertWebErrorEvent(session, types.WrapIntegrationEvent(m)) + case *messages.InputChange: + if err := s.pg.InsertInputChangeEvent(session, m); err != nil { + return err + } + if err := s.sessions.UpdateEventsStats(session.SessionID, 1, 0); err != nil { + return err + } + return s.ch.InsertWebInputDuration(session, m) + case *messages.MouseThrashing: + if err := s.pg.InsertMouseThrashing(session, m); err != nil { + return err + } + if err := s.sessions.UpdateIssuesStats(session.SessionID, 0, 50); err != nil { + return err + } + return s.ch.InsertMouseThrashing(session, m) + case *messages.CanvasNode: + if err := s.pg.InsertCanvasNode(session, m); err != nil { + return err + } + case *messages.TagTrigger: + if err := s.pg.InsertTagTrigger(session, m); err != nil { + return err + } + case *messages.PerformanceTrackAggr: + if err := s.pg.InsertWebStatsPerformance(m); err != nil { + return err + } + return s.ch.InsertWebPerformanceTrackAggr(session, m) + } + return nil +} diff --git a/backend/pkg/analytics/api/dashboard-handlers.go b/backend/pkg/analytics/api/dashboard-handlers.go new file mode 100644 index 000000000..ca8e22ba2 --- /dev/null +++ b/backend/pkg/analytics/api/dashboard-handlers.go @@ -0,0 +1,205 @@ +package api + +import ( + "encoding/json" + "fmt" + "github.com/gorilla/mux" + "net/http" + "openreplay/backend/pkg/server/api" + "openreplay/backend/pkg/server/user" + "strconv" + "time" +) + +func getId(r *http.Request) (int, error) { + vars := mux.Vars(r) + idStr := vars["id"] + if idStr == "" { + return 0, fmt.Errorf("invalid dashboard ID") + } + + id, err := strconv.Atoi(idStr) + if err != nil { + return 0, fmt.Errorf("invalid dashboard ID") + } + + return id, nil +} + +func (e *handlersImpl) createDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + bodyBytes, err := api.ReadBody(e.log, w, r, e.jsonSizeLimit) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize) + return + } + bodySize = len(bodyBytes) + + req := &CreateDashboardRequest{} + if err := json.Unmarshal(bodyBytes, req); err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp := &GetDashboardResponse{ + Dashboard: Dashboard{ + DashboardID: 1, + Name: req.Name, + Description: req.Description, + IsPublic: req.IsPublic, + IsPinned: req.IsPinned, + }, + } + + currentUser := r.Context().Value("userData").(*user.User) + e.log.Info(r.Context(), "User ID: ", currentUser.ID) + + e.responser.ResponseWithJSON(e.log, r.Context(), w, resp, startTime, r.URL.Path, bodySize) +} + +// getDashboards +func (e *handlersImpl) getDashboards(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + //id, err := getId(r) + //if err != nil { + // e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + // return + //} + + resp := &GetDashboardsResponse{ + Dashboards: []Dashboard{ + { + DashboardID: 1, + Name: "Dashboard", + Description: "Description", + IsPublic: true, + IsPinned: false, + }, + }, + Total: 1, + } + + e.responser.ResponseWithJSON(e.log, r.Context(), w, resp, startTime, r.URL.Path, bodySize) +} + +func (e *handlersImpl) getDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + id, err := getId(r) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp := &GetDashboardResponse{ + Dashboard: Dashboard{ + DashboardID: id, + Name: "Dashboard", + Description: "Description", + IsPublic: true, + IsPinned: false, + }, + } + + e.responser.ResponseWithJSON(e.log, r.Context(), w, resp, startTime, r.URL.Path, bodySize) +} + +func (e *handlersImpl) updateDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + //id, err := getId(r) + //if err != nil { + // e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + // return + //} + + bodyBytes, err := api.ReadBody(e.log, w, r, e.jsonSizeLimit) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize) + return + } + bodySize = len(bodyBytes) + + req := &UpdateDashboardRequest{} + if err := json.Unmarshal(bodyBytes, req); err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp := &GetDashboardResponse{ + Dashboard: Dashboard{ + DashboardID: 1, + Name: req.Name, + Description: req.Description, + IsPublic: req.IsPublic, + IsPinned: req.IsPinned, + }, + } + + e.responser.ResponseWithJSON(e.log, r.Context(), w, resp, startTime, r.URL.Path, bodySize) +} + +func (e *handlersImpl) deleteDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + //id, err := getId(r) + //if err != nil { + // e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + // return + //} + e.log.Info(r.Context(), "Dashboard deleted") + + e.responser.ResponseOK(e.log, r.Context(), w, startTime, r.URL.Path, bodySize) +} + +func (e *handlersImpl) pinDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + //id, err := getId(r) + //if err != nil { + // e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + // return + //} + + e.log.Info(r.Context(), "Dashboard pinned") + + e.responser.ResponseOK(e.log, r.Context(), w, startTime, r.URL.Path, bodySize) +} + +// add card to dashboard +func (e *handlersImpl) addCardToDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + //id, err := getId(r) + //if err != nil { + // e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + // return + //} + + e.log.Info(r.Context(), "Card added to dashboard") + + e.responser.ResponseOK(e.log, r.Context(), w, startTime, r.URL.Path, bodySize) +} + +// remove card from dashboard +func (e *handlersImpl) removeCardFromDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + //id, err := getId(r) + //if err != nil { + // e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + // return + //} + + e.responser.ResponseOK(e.log, r.Context(), w, startTime, r.URL.Path, bodySize) +} diff --git a/backend/pkg/analytics/api/handlers.go b/backend/pkg/analytics/api/handlers.go new file mode 100644 index 000000000..05ee6dbfc --- /dev/null +++ b/backend/pkg/analytics/api/handlers.go @@ -0,0 +1,40 @@ +package api + +import ( + config "openreplay/backend/internal/config/analytics" + "openreplay/backend/pkg/analytics/service" + "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/objectstorage" + "openreplay/backend/pkg/server/api" + "openreplay/backend/pkg/server/keys" +) + +type handlersImpl struct { + log logger.Logger + responser *api.Responser + objStorage objectstorage.ObjectStorage + jsonSizeLimit int64 + keys keys.Keys + service service.Service +} + +func (e *handlersImpl) GetAll() []*api.Description { + return []*api.Description{ + {"/v1/analytics/{projectId}/dashboards", e.createDashboard, "POST"}, + {"/v1/analytics/{projectId}/dashboards", e.getDashboards, "GET"}, + {"/v1/analytics/{projectId}/dashboards/{id}", e.getDashboard, "GET"}, + {"/v1/analytics/{projectId}/dashboards/{id}", e.updateDashboard, "PUT"}, + {"/v1/analytics/{projectId}/dashboards/{id}", e.deleteDashboard, "DELETE"}, + } +} + +func NewHandlers(log logger.Logger, cfg *config.Config, responser *api.Responser, objStore objectstorage.ObjectStorage, keys keys.Keys, service service.Service) (api.Handlers, error) { + return &handlersImpl{ + log: log, + responser: responser, + objStorage: objStore, + jsonSizeLimit: cfg.JsonSizeLimit, + keys: keys, + service: service, + }, nil +} diff --git a/backend/pkg/analytics/api/model.go b/backend/pkg/analytics/api/model.go new file mode 100644 index 000000000..3342a4b81 --- /dev/null +++ b/backend/pkg/analytics/api/model.go @@ -0,0 +1,60 @@ +package api + +type Dashboard struct { + DashboardID int `json:"dashboard_id"` + Name string `json:"name"` + Description string `json:"description"` + IsPublic bool `json:"is_public"` + IsPinned bool `json:"is_pinned"` +} + +type CreateDashboardResponse struct { + DashboardID int `json:"dashboard_id"` +} + +type GetDashboardResponse struct { + Dashboard +} + +type GetDashboardsResponse struct { + Dashboards []Dashboard `json:"dashboards"` + Total uint64 `json:"total"` +} + +// REQUESTS + +type CreateDashboardRequest struct { + Name string `json:"name"` + Description string `json:"description"` + IsPublic bool `json:"is_public"` + IsPinned bool `json:"is_pinned"` + Metrics []int `json:"metrics"` +} + +type GetDashboardsRequest struct { + Page uint64 `json:"page"` + Limit uint64 `json:"limit"` + Order string `json:"order"` + Query string `json:"query"` + FilterBy string `json:"filterBy"` +} + +type UpdateDashboardRequest struct { + Name string `json:"name"` + Description string `json:"description"` + IsPublic bool `json:"is_public"` + IsPinned bool `json:"is_pinned"` + Metrics []int `json:"metrics"` +} + +type PinDashboardRequest struct { + IsPinned bool `json:"is_pinned"` +} + +type AddCardToDashboardRequest struct { + CardIDs []int `json:"card_ids"` +} + +type DeleteCardFromDashboardRequest struct { + CardIDs []int `json:"card_ids"` +} diff --git a/backend/pkg/analytics/builder.go b/backend/pkg/analytics/builder.go new file mode 100644 index 000000000..333921e0a --- /dev/null +++ b/backend/pkg/analytics/builder.go @@ -0,0 +1,57 @@ +package analytics + +import ( + "openreplay/backend/pkg/metrics/web" + "openreplay/backend/pkg/server/tracer" + "time" + + "openreplay/backend/internal/config/analytics" + analyticsAPI "openreplay/backend/pkg/analytics/api" + "openreplay/backend/pkg/analytics/service" + "openreplay/backend/pkg/db/postgres/pool" + "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/objectstorage/store" + "openreplay/backend/pkg/server/api" + "openreplay/backend/pkg/server/auth" + "openreplay/backend/pkg/server/keys" + "openreplay/backend/pkg/server/limiter" +) + +type ServicesBuilder struct { + Auth auth.Auth + RateLimiter *limiter.UserRateLimiter + AuditTrail tracer.Tracer + AnalyticsAPI api.Handlers +} + +func NewServiceBuilder(log logger.Logger, cfg *analytics.Config, webMetrics web.Web, pgconn pool.Pool) (*ServicesBuilder, error) { + objStore, err := store.NewStore(&cfg.ObjectsConfig) + if err != nil { + return nil, err + } + + newKeys := keys.NewKeys(log, pgconn) + responser := api.NewResponser(webMetrics) + + audiTrail, err := tracer.NewTracer(log, pgconn) + if err != nil { + return nil, err + } + + analyticsService, err := service.NewService(log, pgconn, objStore) + if err != nil { + return nil, err + } + + handlers, err := analyticsAPI.NewHandlers(log, cfg, responser, objStore, keys.NewKeys(log, pgconn), analyticsService) + if err != nil { + return nil, err + } + + return &ServicesBuilder{ + Auth: auth.NewAuth(log, cfg.JWTSecret, cfg.JWTSpotSecret, pgconn, newKeys), + RateLimiter: limiter.NewUserRateLimiter(10, 30, 1*time.Minute, 5*time.Minute), + AuditTrail: audiTrail, + AnalyticsAPI: handlers, + }, nil +} diff --git a/backend/pkg/analytics/service/analytics.go b/backend/pkg/analytics/service/analytics.go new file mode 100644 index 000000000..ce36b0958 --- /dev/null +++ b/backend/pkg/analytics/service/analytics.go @@ -0,0 +1,34 @@ +package service + +import ( + "errors" + "openreplay/backend/pkg/db/postgres/pool" + "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/objectstorage" +) + +type Service interface { +} + +type serviceImpl struct { + log logger.Logger + conn pool.Pool + storage objectstorage.ObjectStorage +} + +func NewService(log logger.Logger, conn pool.Pool, storage objectstorage.ObjectStorage) (Service, error) { + switch { + case log == nil: + return nil, errors.New("logger is empty") + case conn == nil: + return nil, errors.New("connection pool is empty") + case storage == nil: + return nil, errors.New("object storage is empty") + } + + return &serviceImpl{ + log: log, + conn: conn, + storage: storage, + }, nil +} diff --git a/ee/backend/pkg/db/clickhouse/bulk.go b/backend/pkg/db/clickhouse/bulk.go similarity index 99% rename from ee/backend/pkg/db/clickhouse/bulk.go rename to backend/pkg/db/clickhouse/bulk.go index 6eb8d98fd..f070f4a15 100644 --- a/ee/backend/pkg/db/clickhouse/bulk.go +++ b/backend/pkg/db/clickhouse/bulk.go @@ -5,10 +5,11 @@ import ( "errors" "fmt" "log" - "openreplay/backend/pkg/metrics/database" "time" "github.com/ClickHouse/clickhouse-go/v2/lib/driver" + + "openreplay/backend/pkg/metrics/database" ) type Bulk interface { diff --git a/backend/pkg/db/clickhouse/connector.go b/backend/pkg/db/clickhouse/connector.go index 727ad7f7b..71d94ab85 100644 --- a/backend/pkg/db/clickhouse/connector.go +++ b/backend/pkg/db/clickhouse/connector.go @@ -1,19 +1,31 @@ package clickhouse import ( + "errors" + "fmt" + "log" + "strings" + "time" + + "github.com/ClickHouse/clickhouse-go/v2" + "github.com/ClickHouse/clickhouse-go/v2/lib/driver" + + "openreplay/backend/internal/config/common" "openreplay/backend/pkg/db/types" + "openreplay/backend/pkg/hashid" "openreplay/backend/pkg/messages" "openreplay/backend/pkg/sessions" + "openreplay/backend/pkg/url" ) type Connector interface { Prepare() error Commit() error Stop() error + // Web InsertWebSession(session *sessions.Session) error InsertWebPageEvent(session *sessions.Session, msg *messages.PageEvent) error InsertWebClickEvent(session *sessions.Session, msg *messages.MouseClick) error - InsertWebInputEvent(session *sessions.Session, msg *messages.InputEvent) error InsertWebErrorEvent(session *sessions.Session, msg *types.ErrorEvent) error InsertWebPerformanceTrackAggr(session *sessions.Session, msg *messages.PerformanceTrackAggr) error InsertAutocomplete(session *sessions.Session, msgType, msgValue string) error @@ -21,4 +33,669 @@ type Connector interface { InsertCustom(session *sessions.Session, msg *messages.CustomEvent) error InsertGraphQL(session *sessions.Session, msg *messages.GraphQL) error InsertIssue(session *sessions.Session, msg *messages.IssueEvent) error + InsertWebInputDuration(session *sessions.Session, msg *messages.InputChange) error + InsertMouseThrashing(session *sessions.Session, msg *messages.MouseThrashing) error + // Mobile + InsertMobileSession(session *sessions.Session) error + InsertMobileCustom(session *sessions.Session, msg *messages.MobileEvent) error + InsertMobileClick(session *sessions.Session, msg *messages.MobileClickEvent) error + InsertMobileSwipe(session *sessions.Session, msg *messages.MobileSwipeEvent) error + InsertMobileInput(session *sessions.Session, msg *messages.MobileInputEvent) error + InsertMobileRequest(session *sessions.Session, msg *messages.MobileNetworkCall, savePayload bool) error + InsertMobileCrash(session *sessions.Session, msg *messages.MobileCrash) error +} + +type task struct { + bulks []Bulk +} + +func NewTask() *task { + return &task{bulks: make([]Bulk, 0, 21)} +} + +type connectorImpl struct { + conn driver.Conn + batches map[string]Bulk //driver.Batch + workerTask chan *task + done chan struct{} + finished chan struct{} +} + +func NewConnector(cfg common.Clickhouse) Connector { + conn, err := clickhouse.Open(&clickhouse.Options{ + Addr: []string{cfg.GetTrimmedURL()}, + Auth: clickhouse.Auth{ + Database: cfg.Database, + Username: cfg.LegacyUserName, + Password: cfg.LegacyPassword, + }, + MaxOpenConns: 20, + MaxIdleConns: 15, + ConnMaxLifetime: 3 * time.Minute, + Compression: &clickhouse.Compression{ + Method: clickhouse.CompressionLZ4, + }, + }) + if err != nil { + log.Fatal(err) + } + + c := &connectorImpl{ + conn: conn, + batches: make(map[string]Bulk, 20), + workerTask: make(chan *task, 1), + done: make(chan struct{}), + finished: make(chan struct{}), + } + go c.worker() + return c +} + +func (c *connectorImpl) newBatch(name, query string) error { + batch, err := NewBulk(c.conn, name, query) + if err != nil { + return fmt.Errorf("can't create new batch: %s", err) + } + c.batches[name] = batch + return nil +} + +var batches = map[string]string{ + // Web + "sessions": "INSERT INTO experimental.sessions (session_id, project_id, user_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, user_state, user_city, datetime, duration, pages_count, events_count, errors_count, issue_score, referrer, issue_types, tracker_version, user_browser, user_browser_version, metadata_1, metadata_2, metadata_3, metadata_4, metadata_5, metadata_6, metadata_7, metadata_8, metadata_9, metadata_10, timezone, utm_source, utm_medium, utm_campaign) VALUES (?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), ?, ?, ?, ?)", + "autocompletes": "INSERT INTO experimental.autocomplete (project_id, type, value) VALUES (?, ?, SUBSTR(?, 1, 8000))", + "pages": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, request_start, response_start, response_end, dom_content_loaded_event_start, dom_content_loaded_event_end, load_event_start, load_event_end, first_paint, first_contentful_paint_time, speed_index, visually_complete, time_to_interactive, url_path, event_type) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), ?)", + "clicks": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, label, hesitation_time, event_type, selector, normalized_x, normalized_y, url, url_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000))", + "inputs": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, label, event_type, duration, hesitation_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + "errors": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, source, name, message, error_id, event_type, error_tags_keys, error_tags_values) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "performance": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_total_js_heap_size, avg_total_js_heap_size, max_total_js_heap_size, min_used_js_heap_size, avg_used_js_heap_size, max_used_js_heap_size, event_type) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "requests": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, request_body, response_body, status, method, duration, success, event_type, transfer_size, url_path) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000))", + "custom": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, name, payload, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", + "graphql": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, name, request_body, response_body, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + "issuesEvents": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, issue_id, issue_type, event_type, url, url_path) VALUES (?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000))", + "issues": "INSERT INTO experimental.issues (project_id, issue_id, type, context_string) VALUES (?, ?, ?, ?)", + //Mobile + "ios_sessions": "INSERT INTO experimental.sessions (session_id, project_id, user_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, user_state, user_city, datetime, duration, pages_count, events_count, errors_count, issue_score, referrer, issue_types, tracker_version, user_browser, user_browser_version, metadata_1, metadata_2, metadata_3, metadata_4, metadata_5, metadata_6, metadata_7, metadata_8, metadata_9, metadata_10, platform, timezone) VALUES (?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), ?, ?)", + "ios_custom": "INSERT INTO experimental.ios_events (session_id, project_id, message_id, datetime, name, payload, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", + "ios_clicks": "INSERT INTO experimental.ios_events (session_id, project_id, message_id, datetime, label, event_type) VALUES (?, ?, ?, ?, ?, ?)", + "ios_swipes": "INSERT INTO experimental.ios_events (session_id, project_id, message_id, datetime, label, direction, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", + "ios_inputs": "INSERT INTO experimental.ios_events (session_id, project_id, message_id, datetime, label, event_type) VALUES (?, ?, ?, ?, ?, ?)", + "ios_requests": "INSERT INTO experimental.ios_events (session_id, project_id, message_id, datetime, url, request_body, response_body, status, method, duration, success, event_type) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?)", + "ios_crashes": "INSERT INTO experimental.ios_events (session_id, project_id, message_id, datetime, name, reason, stacktrace, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", +} + +func (c *connectorImpl) Prepare() error { + for table, query := range batches { + if err := c.newBatch(table, query); err != nil { + return fmt.Errorf("can't create %s batch: %s", table, err) + } + } + return nil +} + +func (c *connectorImpl) Commit() error { + newTask := NewTask() + for _, b := range c.batches { + newTask.bulks = append(newTask.bulks, b) + } + c.batches = make(map[string]Bulk, 20) + if err := c.Prepare(); err != nil { + log.Printf("can't prepare new CH batch set: %s", err) + } + c.workerTask <- newTask + return nil +} + +func (c *connectorImpl) Stop() error { + c.done <- struct{}{} + <-c.finished + return c.conn.Close() +} + +func (c *connectorImpl) sendBulks(t *task) { + for _, b := range t.bulks { + if err := b.Send(); err != nil { + log.Printf("can't send batch: %s", err) + } + } +} + +func (c *connectorImpl) worker() { + for { + select { + case t := <-c.workerTask: + c.sendBulks(t) + case <-c.done: + for t := range c.workerTask { + c.sendBulks(t) + } + c.finished <- struct{}{} + return + } + } +} + +func (c *connectorImpl) checkError(name string, err error) { + if err != clickhouse.ErrBatchAlreadySent { + log.Printf("can't create %s batch after failed append operation: %s", name, err) + } +} + +func (c *connectorImpl) InsertWebInputDuration(session *sessions.Session, msg *messages.InputChange) error { + if msg.Label == "" { + return nil + } + if err := c.batches["inputs"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.MsgID(), + datetime(msg.Timestamp), + msg.Label, + "INPUT", + nullableUint16(uint16(msg.InputDuration)), + nullableUint32(uint32(msg.HesitationTime)), + ); err != nil { + c.checkError("inputs", err) + return fmt.Errorf("can't append to inputs batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertMouseThrashing(session *sessions.Session, msg *messages.MouseThrashing) error { + issueID := hashid.MouseThrashingID(session.ProjectID, session.SessionID, msg.Timestamp) + // Insert issue event to batches + if err := c.batches["issuesEvents"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.MsgID(), + datetime(msg.Timestamp), + issueID, + "mouse_thrashing", + "ISSUE", + msg.Url, + extractUrlPath(msg.Url), + ); err != nil { + c.checkError("issuesEvents", err) + return fmt.Errorf("can't append to issuesEvents batch: %s", err) + } + if err := c.batches["issues"].Append( + uint16(session.ProjectID), + issueID, + "mouse_thrashing", + msg.Url, + ); err != nil { + c.checkError("issues", err) + return fmt.Errorf("can't append to issues batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertIssue(session *sessions.Session, msg *messages.IssueEvent) error { + issueID := hashid.IssueID(session.ProjectID, msg) + // Check issue type before insert to avoid panic from clickhouse lib + switch msg.Type { + case "click_rage", "dead_click", "excessive_scrolling", "bad_request", "missing_resource", "memory", "cpu", "slow_resource", "slow_page_load", "crash", "ml_cpu", "ml_memory", "ml_dead_click", "ml_click_rage", "ml_mouse_thrashing", "ml_excessive_scrolling", "ml_slow_resources", "custom", "js_exception", "mouse_thrashing", "app_crash": + default: + return fmt.Errorf("unknown issueType: %s", msg.Type) + } + // Insert issue event to batches + if err := c.batches["issuesEvents"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.MessageID, + datetime(msg.Timestamp), + issueID, + msg.Type, + "ISSUE", + msg.URL, + extractUrlPath(msg.URL), + ); err != nil { + c.checkError("issuesEvents", err) + return fmt.Errorf("can't append to issuesEvents batch: %s", err) + } + if err := c.batches["issues"].Append( + uint16(session.ProjectID), + issueID, + msg.Type, + msg.ContextString, + ); err != nil { + c.checkError("issues", err) + return fmt.Errorf("can't append to issues batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertWebSession(session *sessions.Session) error { + if session.Duration == nil { + return errors.New("trying to insert session with nil duration") + } + if err := c.batches["sessions"].Append( + session.SessionID, + uint16(session.ProjectID), + session.UserID, + session.UserUUID, + session.UserOS, + nullableString(session.UserOSVersion), + nullableString(session.UserDevice), + session.UserDeviceType, + session.UserCountry, + session.UserState, + session.UserCity, + datetime(session.Timestamp), + uint32(*session.Duration), + uint16(session.PagesCount), + uint16(session.EventsCount), + uint16(session.ErrorsCount), + uint32(session.IssueScore), + session.Referrer, + session.IssueTypes, + session.TrackerVersion, + session.UserBrowser, + nullableString(session.UserBrowserVersion), + session.Metadata1, + session.Metadata2, + session.Metadata3, + session.Metadata4, + session.Metadata5, + session.Metadata6, + session.Metadata7, + session.Metadata8, + session.Metadata9, + session.Metadata10, + session.Timezone, + session.UtmSource, + session.UtmMedium, + session.UtmCampaign, + ); err != nil { + c.checkError("sessions", err) + return fmt.Errorf("can't append to sessions batch: %s", err) + } + return nil +} + +func extractUrlPath(fullUrl string) string { + _, path, query, err := url.GetURLParts(fullUrl) + if err != nil { + log.Printf("can't parse url: %s", err) + return "" + } + pathQuery := path + if query != "" { + pathQuery += "?" + query + } + return strings.ToLower(pathQuery) +} + +func (c *connectorImpl) InsertWebPageEvent(session *sessions.Session, msg *messages.PageEvent) error { + if err := c.batches["pages"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.MessageID, + datetime(msg.Timestamp), + msg.URL, + nullableUint16(uint16(msg.RequestStart)), + nullableUint16(uint16(msg.ResponseStart)), + nullableUint16(uint16(msg.ResponseEnd)), + nullableUint16(uint16(msg.DomContentLoadedEventStart)), + nullableUint16(uint16(msg.DomContentLoadedEventEnd)), + nullableUint16(uint16(msg.LoadEventStart)), + nullableUint16(uint16(msg.LoadEventEnd)), + nullableUint16(uint16(msg.FirstPaint)), + nullableUint16(uint16(msg.FirstContentfulPaint)), + nullableUint16(uint16(msg.SpeedIndex)), + nullableUint16(uint16(msg.VisuallyComplete)), + nullableUint16(uint16(msg.TimeToInteractive)), + extractUrlPath(msg.URL), + "LOCATION", + ); err != nil { + c.checkError("pages", err) + return fmt.Errorf("can't append to pages batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertWebClickEvent(session *sessions.Session, msg *messages.MouseClick) error { + if msg.Label == "" { + return nil + } + var nX *float32 = nil + var nY *float32 = nil + if msg.NormalizedX != 101 && msg.NormalizedY != 101 { + // To support previous versions of tracker + if msg.NormalizedX <= 100 && msg.NormalizedY <= 100 { + msg.NormalizedX *= 100 + msg.NormalizedY *= 100 + } + normalizedX := float32(msg.NormalizedX) / 100.0 + normalizedY := float32(msg.NormalizedY) / 100.0 + nXVal := normalizedX + nX = &nXVal + nYVal := normalizedY + nY = &nYVal + } + if err := c.batches["clicks"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.MsgID(), + datetime(msg.Timestamp), + msg.Label, + nullableUint32(uint32(msg.HesitationTime)), + "CLICK", + msg.Selector, + nX, + nY, + msg.Url, + extractUrlPath(msg.Url), + ); err != nil { + c.checkError("clicks", err) + return fmt.Errorf("can't append to clicks batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertWebErrorEvent(session *sessions.Session, msg *types.ErrorEvent) error { + keys, values := make([]string, 0, len(msg.Tags)), make([]*string, 0, len(msg.Tags)) + for k, v := range msg.Tags { + keys = append(keys, k) + values = append(values, v) + } + // Check error source before insert to avoid panic from clickhouse lib + switch msg.Source { + case "js_exception", "bugsnag", "cloudwatch", "datadog", "elasticsearch", "newrelic", "rollbar", "sentry", "stackdriver", "sumologic": + default: + return fmt.Errorf("unknown error source: %s", msg.Source) + } + msgID, _ := msg.ID(session.ProjectID) + // Insert event to batch + if err := c.batches["errors"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.MessageID, + datetime(msg.Timestamp), + msg.Source, + nullableString(msg.Name), + msg.Message, + msgID, + "ERROR", + keys, + values, + ); err != nil { + c.checkError("errors", err) + return fmt.Errorf("can't append to errors batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertWebPerformanceTrackAggr(session *sessions.Session, msg *messages.PerformanceTrackAggr) error { + var timestamp uint64 = (msg.TimestampStart + msg.TimestampEnd) / 2 + if err := c.batches["performance"].Append( + session.SessionID, + uint16(session.ProjectID), + uint64(0), // TODO: find messageID for performance events + datetime(timestamp), + nullableString(msg.Meta().Url), + uint8(msg.MinFPS), + uint8(msg.AvgFPS), + uint8(msg.MaxFPS), + uint8(msg.MinCPU), + uint8(msg.AvgCPU), + uint8(msg.MaxCPU), + msg.MinTotalJSHeapSize, + msg.AvgTotalJSHeapSize, + msg.MaxTotalJSHeapSize, + msg.MinUsedJSHeapSize, + msg.AvgUsedJSHeapSize, + msg.MaxUsedJSHeapSize, + "PERFORMANCE", + ); err != nil { + c.checkError("performance", err) + return fmt.Errorf("can't append to performance batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertAutocomplete(session *sessions.Session, msgType, msgValue string) error { + if len(msgValue) == 0 { + return nil + } + if err := c.batches["autocompletes"].Append( + uint16(session.ProjectID), + msgType, + msgValue, + ); err != nil { + c.checkError("autocompletes", err) + return fmt.Errorf("can't append to autocompletes batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertRequest(session *sessions.Session, msg *messages.NetworkRequest, savePayload bool) error { + urlMethod := url.EnsureMethod(msg.Method) + if urlMethod == "" { + return fmt.Errorf("can't parse http method. sess: %d, method: %s", session.SessionID, msg.Method) + } + var request, response *string + if savePayload { + request = &msg.Request + response = &msg.Response + } + if err := c.batches["requests"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.Meta().Index, + datetime(uint64(msg.Meta().Timestamp)), + msg.URL, + request, + response, + uint16(msg.Status), + url.EnsureMethod(msg.Method), + uint16(msg.Duration), + msg.Status < 400, + "REQUEST", + uint32(msg.TransferredBodySize), + extractUrlPath(msg.URL), + ); err != nil { + c.checkError("requests", err) + return fmt.Errorf("can't append to requests batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertCustom(session *sessions.Session, msg *messages.CustomEvent) error { + if err := c.batches["custom"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.Meta().Index, + datetime(uint64(msg.Meta().Timestamp)), + msg.Name, + msg.Payload, + "CUSTOM", + ); err != nil { + c.checkError("custom", err) + return fmt.Errorf("can't append to custom batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertGraphQL(session *sessions.Session, msg *messages.GraphQL) error { + if err := c.batches["graphql"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.Meta().Index, + datetime(uint64(msg.Meta().Timestamp)), + msg.OperationName, + nullableString(msg.Variables), + nullableString(msg.Response), + "GRAPHQL", + ); err != nil { + c.checkError("graphql", err) + return fmt.Errorf("can't append to graphql batch: %s", err) + } + return nil +} + +// Mobile events + +func (c *connectorImpl) InsertMobileSession(session *sessions.Session) error { + if session.Duration == nil { + return errors.New("trying to insert mobile session with nil duration") + } + if err := c.batches["ios_sessions"].Append( + session.SessionID, + uint16(session.ProjectID), + session.UserID, + session.UserUUID, + session.UserOS, + nullableString(session.UserOSVersion), + nullableString(session.UserDevice), + session.UserDeviceType, + session.UserCountry, + session.UserState, + session.UserCity, + datetime(session.Timestamp), + uint32(*session.Duration), + uint16(session.PagesCount), + uint16(session.EventsCount), + uint16(session.ErrorsCount), + uint32(session.IssueScore), + session.Referrer, + session.IssueTypes, + session.TrackerVersion, + session.UserBrowser, + nullableString(session.UserBrowserVersion), + session.Metadata1, + session.Metadata2, + session.Metadata3, + session.Metadata4, + session.Metadata5, + session.Metadata6, + session.Metadata7, + session.Metadata8, + session.Metadata9, + session.Metadata10, + "ios", + session.Timezone, + ); err != nil { + c.checkError("ios_sessions", err) + return fmt.Errorf("can't append to sessions batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertMobileCustom(session *sessions.Session, msg *messages.MobileEvent) error { + if err := c.batches["ios_custom"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.Meta().Index, + datetime(uint64(msg.Meta().Timestamp)), + msg.Name, + msg.Payload, + "CUSTOM", + ); err != nil { + c.checkError("ios_custom", err) + return fmt.Errorf("can't append to mobile custom batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertMobileClick(session *sessions.Session, msg *messages.MobileClickEvent) error { + if msg.Label == "" { + return nil + } + if err := c.batches["ios_clicks"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.MsgID(), + datetime(msg.Timestamp), + msg.Label, + "TAP", + ); err != nil { + c.checkError("ios_clicks", err) + return fmt.Errorf("can't append to mobile clicks batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertMobileSwipe(session *sessions.Session, msg *messages.MobileSwipeEvent) error { + if msg.Label == "" { + return nil + } + if err := c.batches["ios_swipes"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.MsgID(), + datetime(msg.Timestamp), + msg.Label, + nullableString(msg.Direction), + "SWIPE", + ); err != nil { + c.checkError("ios_clicks", err) + return fmt.Errorf("can't append to mobile clicks batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertMobileInput(session *sessions.Session, msg *messages.MobileInputEvent) error { + if msg.Label == "" { + return nil + } + if err := c.batches["ios_inputs"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.MsgID(), + datetime(msg.Timestamp), + msg.Label, + "INPUT", + ); err != nil { + c.checkError("ios_inputs", err) + return fmt.Errorf("can't append to mobile inputs batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertMobileRequest(session *sessions.Session, msg *messages.MobileNetworkCall, savePayload bool) error { + urlMethod := url.EnsureMethod(msg.Method) + if urlMethod == "" { + return fmt.Errorf("can't parse http method. sess: %d, method: %s", session.SessionID, msg.Method) + } + var request, response *string + if savePayload { + request = &msg.Request + response = &msg.Response + } + if err := c.batches["ios_requests"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.Meta().Index, + datetime(uint64(msg.Meta().Timestamp)), + msg.URL, + request, + response, + uint16(msg.Status), + url.EnsureMethod(msg.Method), + uint16(msg.Duration), + msg.Status < 400, + "REQUEST", + ); err != nil { + c.checkError("ios_requests", err) + return fmt.Errorf("can't append to mobile requests batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertMobileCrash(session *sessions.Session, msg *messages.MobileCrash) error { + if err := c.batches["ios_crashes"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.MsgID(), + datetime(msg.Timestamp), + msg.Name, + msg.Reason, + msg.Stacktrace, + "CRASH", + ); err != nil { + c.checkError("ios_crashes", err) + return fmt.Errorf("can't append to mobile crashges batch: %s", err) + } + return nil } diff --git a/ee/backend/pkg/db/clickhouse/insert_type.go b/backend/pkg/db/clickhouse/insert_type.go similarity index 100% rename from ee/backend/pkg/db/clickhouse/insert_type.go rename to backend/pkg/db/clickhouse/insert_type.go diff --git a/backend/pkg/db/postgres/connector.go b/backend/pkg/db/postgres/connector.go index cda778d7c..7ee1f997f 100644 --- a/backend/pkg/db/postgres/connector.go +++ b/backend/pkg/db/postgres/connector.go @@ -19,20 +19,17 @@ type Conn struct { Pool pool.Pool batches *batch.BatchSet bulks *BulkSet - chConn CH // hack for autocomplete inserts, TODO: rewrite + chConn CH } -func (conn *Conn) SetClickHouse(ch CH) { - conn.chConn = ch -} - -func NewConn(log logger.Logger, pool pool.Pool) *Conn { +func NewConn(log logger.Logger, pool pool.Pool, ch CH) *Conn { if pool == nil { log.Fatal(context.Background(), "pg pool is empty") } return &Conn{ log: log, Pool: pool, + chConn: ch, bulks: NewBulkSet(log, pool), batches: batch.NewBatchSet(log, pool), } diff --git a/backend/pkg/metrics/analytics/analytics.go b/backend/pkg/metrics/analytics/analytics.go new file mode 100644 index 000000000..7919e77fb --- /dev/null +++ b/backend/pkg/metrics/analytics/analytics.go @@ -0,0 +1,22 @@ +package analytics + +import ( + "github.com/prometheus/client_golang/prometheus" + + "openreplay/backend/pkg/metrics/common" +) + +var cardCreated = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "card", + Name: "created", + Help: "Histogram for tracking card creation", + Buckets: common.DefaultBuckets, + }, +) + +func List() []prometheus.Collector { + return []prometheus.Collector{ + cardCreated, + } +} diff --git a/backend/pkg/sessions/sessions.go b/backend/pkg/sessions/sessions.go index 446fd1b1f..bd2519cc6 100644 --- a/backend/pkg/sessions/sessions.go +++ b/backend/pkg/sessions/sessions.go @@ -16,7 +16,7 @@ type Sessions interface { AddUnStarted(session *UnStartedSession) error AddCached(sessionID uint64, data map[string]string) error Get(sessionID uint64) (*Session, error) - GetUpdated(sessionID uint64) (*Session, error) + GetUpdated(sessionID uint64, keepInCache bool) (*Session, error) GetCached(sessionID uint64) (map[string]string, error) GetDuration(sessionID uint64) (uint64, error) UpdateDuration(sessionID uint64, timestamp uint64) (uint64, error) @@ -104,11 +104,14 @@ func (s *sessionsImpl) Get(sessionID uint64) (*Session, error) { } // Special method for clickhouse connector -func (s *sessionsImpl) GetUpdated(sessionID uint64) (*Session, error) { +func (s *sessionsImpl) GetUpdated(sessionID uint64, keepInCache bool) (*Session, error) { session, err := s.getFromDB(sessionID) if err != nil { return nil, err } + if !keepInCache { + return session, nil + } if err := s.cache.Set(session); err != nil { ctx := context.WithValue(context.Background(), "sessionID", sessionID) s.log.Warn(ctx, "failed to cache session: %s", err) diff --git a/backend/pkg/spot/builder.go b/backend/pkg/spot/builder.go index 14ae61365..209777f46 100644 --- a/backend/pkg/spot/builder.go +++ b/backend/pkg/spot/builder.go @@ -1,19 +1,19 @@ package spot import ( - "openreplay/backend/pkg/metrics/web" - "openreplay/backend/pkg/server/tracer" "time" "openreplay/backend/internal/config/spot" "openreplay/backend/pkg/db/postgres/pool" "openreplay/backend/pkg/flakeid" "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/metrics/web" "openreplay/backend/pkg/objectstorage/store" "openreplay/backend/pkg/server/api" "openreplay/backend/pkg/server/auth" "openreplay/backend/pkg/server/keys" "openreplay/backend/pkg/server/limiter" + "openreplay/backend/pkg/server/tracer" spotAPI "openreplay/backend/pkg/spot/api" "openreplay/backend/pkg/spot/service" "openreplay/backend/pkg/spot/transcoder" diff --git a/ee/api/.gitignore b/ee/api/.gitignore index 9cef962d6..f4223629f 100644 --- a/ee/api/.gitignore +++ b/ee/api/.gitignore @@ -184,7 +184,6 @@ Pipfile.lock /build.sh /build_alerts.sh /build_crons.sh -/chalicelib/core/alerts.py /chalicelib/core/announcements.py /chalicelib/core/assist.py /chalicelib/core/authorizers.py @@ -286,3 +285,8 @@ Pipfile.lock /chalicelib/utils/ch_client.py /chalicelib/utils/ch_client_exp.py /routers/subs/product_anaytics.py +/chalicelib/core/alerts/__init__.py +/chalicelib/core/alerts/alerts.py +/chalicelib/core/alerts/alerts_processor.py +/chalicelib/core/alerts/alerts_processor_ch.py +/chalicelib/core/sessions_ch.py diff --git a/ee/api/chalicelib/core/__init__.py b/ee/api/chalicelib/core/__init__.py index 1f0feb085..f0afce260 100644 --- a/ee/api/chalicelib/core/__init__.py +++ b/ee/api/chalicelib/core/__init__.py @@ -11,7 +11,7 @@ from . import metrics as metrics_legacy if config("EXP_SESSIONS_SEARCH", cast=bool, default=False): logging.info(">>> Using experimental sessions search") - from . import sessions_exp as sessions + from . import sessions_ch as sessions else: from . import sessions as sessions @@ -34,12 +34,6 @@ else: if config("EXP_SESSIONS_SEARCH_METRIC", cast=bool, default=False): logging.info(">>> Using experimental sessions search for metrics") -if config("EXP_ALERTS", cast=bool, default=False): - logging.info(">>> Using experimental alerts") - from . import alerts_processor_exp as alerts_processor -else: - from . import alerts_processor as alerts_processor - if config("EXP_FUNNELS", cast=bool, default=False): logging.info(">>> Using experimental funnels") if not config("EXP_SESSIONS_SEARCH", cast=bool, default=False): diff --git a/ee/api/chalicelib/core/alerts_listener.py b/ee/api/chalicelib/core/alerts/alerts_listener.py similarity index 100% rename from ee/api/chalicelib/core/alerts_listener.py rename to ee/api/chalicelib/core/alerts/alerts_listener.py diff --git a/ee/api/chalicelib/core/alerts/sessions/__init__.py b/ee/api/chalicelib/core/alerts/sessions/__init__.py new file mode 100644 index 000000000..973014c8a --- /dev/null +++ b/ee/api/chalicelib/core/alerts/sessions/__init__.py @@ -0,0 +1,12 @@ +from decouple import config + +if config("EXP_ALERTS", cast=bool, default=False): + if config("EXP_SESSIONS_SEARCH", cast=bool, default=False): + from chalicelib.core.sessions import * + else: + from chalicelib.core.sessions_legacy import * +else: + if config("EXP_SESSIONS_SEARCH", cast=bool, default=False): + from chalicelib.core.sessions_legacy import * + else: + from chalicelib.core.sessions import * diff --git a/ee/api/chalicelib/core/alerts_processor.py b/ee/api/chalicelib/core/alerts_processor.py deleted file mode 100644 index 629a18a37..000000000 --- a/ee/api/chalicelib/core/alerts_processor.py +++ /dev/null @@ -1,242 +0,0 @@ -import decimal -import logging - -from decouple import config -from pydantic_core._pydantic_core import ValidationError - -import schemas -from chalicelib.core import alerts -from chalicelib.core import alerts_listener -from chalicelib.utils import pg_client -from chalicelib.utils.TimeUTC import TimeUTC - -if config("EXP_SESSIONS_SEARCH", cast=bool, default=False): - from chalicelib.core import sessions_legacy as sessions -else: - from chalicelib.core import sessions - -logging.basicConfig(level=config("LOGLEVEL", default=logging.INFO)) - -LeftToDb = { - schemas.AlertColumn.PERFORMANCE__DOM_CONTENT_LOADED__AVERAGE: { - "table": "events.pages INNER JOIN public.sessions USING(session_id)", - "formula": "COALESCE(AVG(NULLIF(dom_content_loaded_time ,0)),0)"}, - schemas.AlertColumn.PERFORMANCE__FIRST_MEANINGFUL_PAINT__AVERAGE: { - "table": "events.pages INNER JOIN public.sessions USING(session_id)", - "formula": "COALESCE(AVG(NULLIF(first_contentful_paint_time,0)),0)"}, - schemas.AlertColumn.PERFORMANCE__PAGE_LOAD_TIME__AVERAGE: { - "table": "events.pages INNER JOIN public.sessions USING(session_id)", "formula": "AVG(NULLIF(load_time ,0))"}, - schemas.AlertColumn.PERFORMANCE__DOM_BUILD_TIME__AVERAGE: { - "table": "events.pages INNER JOIN public.sessions USING(session_id)", - "formula": "AVG(NULLIF(dom_building_time,0))"}, - schemas.AlertColumn.PERFORMANCE__SPEED_INDEX__AVERAGE: { - "table": "events.pages INNER JOIN public.sessions USING(session_id)", "formula": "AVG(NULLIF(speed_index,0))"}, - schemas.AlertColumn.PERFORMANCE__PAGE_RESPONSE_TIME__AVERAGE: { - "table": "events.pages INNER JOIN public.sessions USING(session_id)", - "formula": "AVG(NULLIF(response_time,0))"}, - schemas.AlertColumn.PERFORMANCE__TTFB__AVERAGE: { - "table": "events.pages INNER JOIN public.sessions USING(session_id)", - "formula": "AVG(NULLIF(first_paint_time,0))"}, - schemas.AlertColumn.PERFORMANCE__TIME_TO_RENDER__AVERAGE: { - "table": "events.pages INNER JOIN public.sessions USING(session_id)", - "formula": "AVG(NULLIF(visually_complete,0))"}, - schemas.AlertColumn.PERFORMANCE__CRASHES__COUNT: { - "table": "public.sessions", - "formula": "COUNT(DISTINCT session_id)", - "condition": "errors_count > 0 AND duration>0"}, - schemas.AlertColumn.ERRORS__JAVASCRIPT__COUNT: { - "table": "events.errors INNER JOIN public.errors AS m_errors USING (error_id)", - "formula": "COUNT(DISTINCT session_id)", "condition": "source='js_exception'", "joinSessions": False}, - schemas.AlertColumn.ERRORS__BACKEND__COUNT: { - "table": "events.errors INNER JOIN public.errors AS m_errors USING (error_id)", - "formula": "COUNT(DISTINCT session_id)", "condition": "source!='js_exception'", "joinSessions": False}, -} - -# This is the frequency of execution for each threshold -TimeInterval = { - 15: 3, - 30: 5, - 60: 10, - 120: 20, - 240: 30, - 1440: 60, -} - - -def can_check(a) -> bool: - now = TimeUTC.now() - - repetitionBase = a["options"]["currentPeriod"] \ - if a["detectionMethod"] == schemas.AlertDetectionMethod.CHANGE \ - and a["options"]["currentPeriod"] > a["options"]["previousPeriod"] \ - else a["options"]["previousPeriod"] - - if TimeInterval.get(repetitionBase) is None: - logging.error(f"repetitionBase: {repetitionBase} NOT FOUND") - return False - - return (a["options"]["renotifyInterval"] <= 0 or - a["options"].get("lastNotification") is None or - a["options"]["lastNotification"] <= 0 or - ((now - a["options"]["lastNotification"]) > a["options"]["renotifyInterval"] * 60 * 1000)) \ - and ((now - a["createdAt"]) % (TimeInterval[repetitionBase] * 60 * 1000)) < 60 * 1000 - - -def Build(a): - now = TimeUTC.now() - params = {"project_id": a["projectId"], "now": now} - full_args = {} - j_s = True - main_table = "" - if a["seriesId"] is not None: - a["filter"]["sort"] = "session_id" - a["filter"]["order"] = schemas.SortOrderType.DESC - a["filter"]["startDate"] = 0 - a["filter"]["endDate"] = TimeUTC.now() - try: - data = schemas.SessionsSearchPayloadSchema.model_validate(a["filter"]) - except ValidationError: - logging.warning("Validation error for:") - logging.warning(a["filter"]) - raise - - full_args, query_part = sessions.search_query_parts(data=data, error_status=None, errors_only=False, - issue=None, project_id=a["projectId"], user_id=None, - favorite_only=False) - subQ = f"""SELECT COUNT(session_id) AS value - {query_part}""" - else: - colDef = LeftToDb[a["query"]["left"]] - subQ = f"""SELECT {colDef["formula"]} AS value - FROM {colDef["table"]} - WHERE project_id = %(project_id)s - {"AND " + colDef["condition"] if colDef.get("condition") else ""}""" - j_s = colDef.get("joinSessions", True) - main_table = colDef["table"] - is_ss = main_table == "public.sessions" - q = f"""SELECT coalesce(value,0) AS value, coalesce(value,0) {a["query"]["operator"]} {a["query"]["right"]} AS valid""" - - if a["detectionMethod"] == schemas.AlertDetectionMethod.THRESHOLD: - if a["seriesId"] is not None: - q += f""" FROM ({subQ}) AS stat""" - else: - q += f""" FROM ({subQ} {"AND timestamp >= %(startDate)s AND timestamp <= %(now)s" if not is_ss else ""} - {"AND start_ts >= %(startDate)s AND start_ts <= %(now)s" if j_s else ""}) AS stat""" - params = {**params, **full_args, "startDate": TimeUTC.now() - a["options"]["currentPeriod"] * 60 * 1000} - else: - if a["change"] == schemas.AlertDetectionType.CHANGE: - if a["seriesId"] is not None: - sub2 = subQ.replace("%(startDate)s", "%(timestamp_sub2)s").replace("%(endDate)s", "%(startDate)s") - sub1 = f"SELECT (({subQ})-({sub2})) AS value" - q += f" FROM ( {sub1} ) AS stat" - params = {**params, **full_args, - "startDate": TimeUTC.now() - a["options"]["currentPeriod"] * 60 * 1000, - "timestamp_sub2": TimeUTC.now() - 2 * a["options"]["currentPeriod"] * 60 * 1000} - else: - sub1 = f"""{subQ} {"AND timestamp >= %(startDate)s AND timestamp <= %(now)s" if not is_ss else ""} - {"AND start_ts >= %(startDate)s AND start_ts <= %(now)s" if j_s else ""}""" - params["startDate"] = TimeUTC.now() - a["options"]["currentPeriod"] * 60 * 1000 - sub2 = f"""{subQ} {"AND timestamp < %(startDate)s AND timestamp >= %(timestamp_sub2)s" if not is_ss else ""} - {"AND start_ts < %(startDate)s AND start_ts >= %(timestamp_sub2)s" if j_s else ""}""" - params["timestamp_sub2"] = TimeUTC.now() - 2 * a["options"]["currentPeriod"] * 60 * 1000 - sub1 = f"SELECT (( {sub1} )-( {sub2} )) AS value" - q += f" FROM ( {sub1} ) AS stat" - - else: - if a["seriesId"] is not None: - sub2 = subQ.replace("%(startDate)s", "%(timestamp_sub2)s").replace("%(endDate)s", "%(startDate)s") - sub1 = f"SELECT (({subQ})/NULLIF(({sub2}),0)-1)*100 AS value" - q += f" FROM ({sub1}) AS stat" - params = {**params, **full_args, - "startDate": TimeUTC.now() - a["options"]["currentPeriod"] * 60 * 1000, - "timestamp_sub2": TimeUTC.now() \ - - (a["options"]["currentPeriod"] + a["options"]["currentPeriod"]) \ - * 60 * 1000} - else: - sub1 = f"""{subQ} {"AND timestamp >= %(startDate)s AND timestamp <= %(now)s" if not is_ss else ""} - {"AND start_ts >= %(startDate)s AND start_ts <= %(now)s" if j_s else ""}""" - params["startDate"] = TimeUTC.now() - a["options"]["currentPeriod"] * 60 * 1000 - sub2 = f"""{subQ} {"AND timestamp < %(startDate)s AND timestamp >= %(timestamp_sub2)s" if not is_ss else ""} - {"AND start_ts < %(startDate)s AND start_ts >= %(timestamp_sub2)s" if j_s else ""}""" - params["timestamp_sub2"] = TimeUTC.now() \ - - (a["options"]["currentPeriod"] + a["options"]["currentPeriod"]) * 60 * 1000 - sub1 = f"SELECT (({sub1})/NULLIF(({sub2}),0)-1)*100 AS value" - q += f" FROM ({sub1}) AS stat" - - return q, params - - -def process(): - notifications = [] - all_alerts = alerts_listener.get_all_alerts() - with pg_client.PostgresClient() as cur: - for alert in all_alerts: - if can_check(alert): - query, params = Build(alert) - try: - query = cur.mogrify(query, params) - except Exception as e: - logging.error( - f"!!!Error while building alert query for alertId:{alert['alertId']} name: {alert['name']}") - logging.error(e) - continue - logging.debug(alert) - logging.debug(query) - try: - cur.execute(query) - result = cur.fetchone() - if result["valid"]: - logging.info(f"Valid alert, notifying users, alertId:{alert['alertId']} name: {alert['name']}") - notifications.append(generate_notification(alert, result)) - except Exception as e: - logging.error( - f"!!!Error while running alert query for alertId:{alert['alertId']} name: {alert['name']}") - logging.error(query) - logging.error(e) - cur = cur.recreate(rollback=True) - if len(notifications) > 0: - cur.execute( - cur.mogrify(f"""UPDATE public.alerts - SET options = options||'{{"lastNotification":{TimeUTC.now()}}}'::jsonb - WHERE alert_id IN %(ids)s;""", {"ids": tuple([n["alertId"] for n in notifications])})) - if len(notifications) > 0: - alerts.process_notifications(notifications) - - -def __format_value(x): - if x % 1 == 0: - x = int(x) - else: - x = round(x, 2) - return f"{x:,}" - - -def generate_notification(alert, result): - left = __format_value(result['value']) - right = __format_value(alert['query']['right']) - return { - "alertId": alert["alertId"], - "tenantId": alert["tenantId"], - "title": alert["name"], - "description": f"{alert['seriesName']} = {left} ({alert['query']['operator']} {right}).", - "buttonText": "Check metrics for more details", - "buttonUrl": f"/{alert['projectId']}/metrics", - "imageUrl": None, - "projectId": alert["projectId"], - "projectName": alert["projectName"], - "options": {"source": "ALERT", "sourceId": alert["alertId"], - "sourceMeta": alert["detectionMethod"], - "message": alert["options"]["message"], "projectId": alert["projectId"], - "data": {"title": alert["name"], - "limitValue": alert["query"]["right"], - "actualValue": float(result["value"]) \ - if isinstance(result["value"], decimal.Decimal) \ - else result["value"], - "operator": alert["query"]["operator"], - "trigger": alert["query"]["left"], - "alertId": alert["alertId"], - "detectionMethod": alert["detectionMethod"], - "currentPeriod": alert["options"]["currentPeriod"], - "previousPeriod": alert["options"]["previousPeriod"], - "createdAt": TimeUTC.now()}}, - } diff --git a/ee/api/chalicelib/core/custom_metrics_ee.py b/ee/api/chalicelib/core/custom_metrics_ee.py index d80a97b9d..dcfadfb0f 100644 --- a/ee/api/chalicelib/core/custom_metrics_ee.py +++ b/ee/api/chalicelib/core/custom_metrics_ee.py @@ -11,11 +11,13 @@ from chalicelib.utils import helper, pg_client from chalicelib.utils.TimeUTC import TimeUTC from chalicelib.utils.storage import extra -if config("EXP_ERRORS_SEARCH", cast=bool, default=False): - logging.info(">>> Using experimental error search") - from . import errors_exp as errors -else: - from . import errors as errors +# TODO: fix this import +from . import errors as errors +# if config("EXP_ERRORS_SEARCH", cast=bool, default=False): +# logging.info(">>> Using experimental error search") +# from . import errors_exp as errors +# else: +# from . import errors as errors if config("EXP_SESSIONS_SEARCH_METRIC", cast=bool, default=False): from chalicelib.core import sessions diff --git a/ee/api/chalicelib/core/heatmaps.py b/ee/api/chalicelib/core/heatmaps.py index 35ac03751..41cffa237 100644 --- a/ee/api/chalicelib/core/heatmaps.py +++ b/ee/api/chalicelib/core/heatmaps.py @@ -7,7 +7,7 @@ from chalicelib.core import sessions_mobs, events from chalicelib.utils import sql_helper as sh if config("EXP_SESSIONS_SEARCH", cast=bool, default=False): - from chalicelib.core import sessions_exp as sessions + from chalicelib.core import sessions_ch as sessions else: from chalicelib.core import sessions diff --git a/ee/api/chalicelib/core/product_analytics.py b/ee/api/chalicelib/core/product_analytics.py index 9e7aa23e5..d027c1da4 100644 --- a/ee/api/chalicelib/core/product_analytics.py +++ b/ee/api/chalicelib/core/product_analytics.py @@ -1,8 +1,8 @@ from typing import List import schemas -from chalicelib.core.metrics import __get_basic_constraints, __get_meta_constraint -from chalicelib.core.metrics import __get_constraint_values, __complete_missing_steps +from chalicelib.core.metrics_ch import __get_basic_constraints, __get_meta_constraint +from chalicelib.core.metrics_ch import __get_constraint_values, __complete_missing_steps from chalicelib.utils import ch_client, exp_ch_helper from chalicelib.utils import helper, dev from chalicelib.utils.TimeUTC import TimeUTC diff --git a/ee/api/clean-dev.sh b/ee/api/clean-dev.sh index df5176eef..a7ad138b4 100755 --- a/ee/api/clean-dev.sh +++ b/ee/api/clean-dev.sh @@ -6,7 +6,6 @@ rm -rf ./auth/auth_apikey.py rm -rf ./build.sh rm -rf ./build_alerts.sh rm -rf ./build_crons.sh -rm -rf ./chalicelib/core/alerts.py rm -rf ./chalicelib/core/announcements.py rm -rf ./chalicelib/core/assist.py rm -rf ./chalicelib/core/authorizers.py @@ -105,3 +104,8 @@ rm -rf ./chalicelib/core/product_anaytics2.py rm -rf ./chalicelib/utils/ch_client.py rm -rf ./chalicelib/utils/ch_client_exp.py rm -rf ./routers/subs/product_anaytics.py +rm -rf ./chalicelib/core/alerts/__init__.py +rm -rf ./chalicelib/core/alerts/alerts.py +rm -rf ./chalicelib/core/alerts/alerts_processor.py +rm -rf ./chalicelib/core/alerts/alerts_processor_ch.py +rm -rf ./chalicelib/core/sessions_ch.py diff --git a/ee/backend/internal/db/datasaver/fts.go b/ee/backend/internal/db/datasaver/fts.go index 34f75b006..15f0fd1e9 100644 --- a/ee/backend/internal/db/datasaver/fts.go +++ b/ee/backend/internal/db/datasaver/fts.go @@ -3,7 +3,9 @@ package datasaver import ( "encoding/json" "log" + "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/queue" ) type NetworkRequestFTS struct { @@ -98,6 +100,12 @@ func WrapGraphQL(m *messages.GraphQL, projID uint32) *GraphQLFTS { } } +func (s *saverImpl) init() { + if s.cfg.UseQuickwit { + s.producer = queue.NewProducer(s.cfg.MessageSizeLimit, true) + } +} + func (s *saverImpl) sendToFTS(msg messages.Message, projID uint32) { // Skip, if FTS is disabled if s.producer == nil { diff --git a/ee/backend/internal/db/datasaver/methods.go b/ee/backend/internal/db/datasaver/methods.go deleted file mode 100644 index 1644a1fc0..000000000 --- a/ee/backend/internal/db/datasaver/methods.go +++ /dev/null @@ -1,93 +0,0 @@ -package datasaver - -import ( - "log" - - "openreplay/backend/pkg/db/clickhouse" - "openreplay/backend/pkg/db/types" - "openreplay/backend/pkg/env" - "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/queue" - "openreplay/backend/pkg/sessions" -) - -func (s *saverImpl) init() { - s.ch = clickhouse.NewConnector(env.String("CLICKHOUSE_STRING")) - if err := s.ch.Prepare(); err != nil { - log.Fatalf("can't prepare clickhouse: %s", err) - } - s.pg.SetClickHouse(s.ch) - if s.cfg.UseQuickwit { - s.producer = queue.NewProducer(s.cfg.MessageSizeLimit, true) - } -} - -func (s *saverImpl) handleExtraMessage(msg messages.Message) error { - // Get session data - var ( - session *sessions.Session - err error - ) - - if msg.TypeID() == messages.MsgSessionEnd || msg.TypeID() == messages.MsgMobileSessionEnd { - session, err = s.sessions.GetUpdated(msg.SessionID()) - } else { - session, err = s.sessions.Get(msg.SessionID()) - } - if err != nil || session == nil { - log.Printf("Error on session retrieving from cache: %v, SessionID: %v, Message: %v", err, msg.SessionID(), msg) - return err - } - - // Send data to quickwit - s.sendToFTS(msg, session.ProjectID) - - // Handle message - switch m := msg.(type) { - case *messages.SessionEnd: - return s.ch.InsertWebSession(session) - case *messages.PerformanceTrackAggr: - return s.ch.InsertWebPerformanceTrackAggr(session, m) - case *messages.MouseClick: - return s.ch.InsertWebClickEvent(session, m) - // Unique for Web - case *messages.PageEvent: - return s.ch.InsertWebPageEvent(session, m) - case *messages.JSException: - wrapper, _ := types.WrapJSException(m) - return s.ch.InsertWebErrorEvent(session, wrapper) - case *messages.IntegrationEvent: - return s.ch.InsertWebErrorEvent(session, types.WrapIntegrationEvent(m)) - case *messages.IssueEvent: - return s.ch.InsertIssue(session, m) - case *messages.CustomEvent: - return s.ch.InsertCustom(session, m) - case *messages.NetworkRequest: - if err := s.ch.InsertRequest(session, m, session.SaveRequestPayload); err != nil { - log.Printf("can't insert request event into clickhouse: %s", err) - } - case *messages.GraphQL: - return s.ch.InsertGraphQL(session, m) - case *messages.InputChange: - return s.ch.InsertWebInputDuration(session, m) - case *messages.MouseThrashing: - return s.ch.InsertMouseThrashing(session, m) - - // Mobile messages - case *messages.MobileSessionEnd: - return s.ch.InsertMobileSession(session) - case *messages.MobileEvent: - return s.ch.InsertMobileCustom(session, m) - case *messages.MobileClickEvent: - return s.ch.InsertMobileClick(session, m) - case *messages.MobileSwipeEvent: - return s.ch.InsertMobileSwipe(session, m) - case *messages.MobileInputEvent: - return s.ch.InsertMobileInput(session, m) - case *messages.MobileNetworkCall: - return s.ch.InsertMobileRequest(session, m, session.SaveRequestPayload) - case *messages.MobileCrash: - return s.ch.InsertMobileCrash(session, m) - } - return nil -} diff --git a/ee/backend/pkg/db/clickhouse/connector.go b/ee/backend/pkg/db/clickhouse/connector.go deleted file mode 100644 index b61acd547..000000000 --- a/ee/backend/pkg/db/clickhouse/connector.go +++ /dev/null @@ -1,713 +0,0 @@ -package clickhouse - -import ( - "errors" - "fmt" - "github.com/ClickHouse/clickhouse-go/v2" - "github.com/ClickHouse/clickhouse-go/v2/lib/driver" - "log" - "openreplay/backend/pkg/db/types" - "openreplay/backend/pkg/hashid" - "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/sessions" - "openreplay/backend/pkg/url" - "os" - "strings" - "time" - - "openreplay/backend/pkg/license" -) - -type Connector interface { - Prepare() error - Commit() error - Stop() error - // Web - InsertWebSession(session *sessions.Session) error - InsertWebPageEvent(session *sessions.Session, msg *messages.PageEvent) error - InsertWebClickEvent(session *sessions.Session, msg *messages.MouseClick) error - InsertWebErrorEvent(session *sessions.Session, msg *types.ErrorEvent) error - InsertWebPerformanceTrackAggr(session *sessions.Session, msg *messages.PerformanceTrackAggr) error - InsertAutocomplete(session *sessions.Session, msgType, msgValue string) error - InsertRequest(session *sessions.Session, msg *messages.NetworkRequest, savePayload bool) error - InsertCustom(session *sessions.Session, msg *messages.CustomEvent) error - InsertGraphQL(session *sessions.Session, msg *messages.GraphQL) error - InsertIssue(session *sessions.Session, msg *messages.IssueEvent) error - InsertWebInputDuration(session *sessions.Session, msg *messages.InputChange) error - InsertMouseThrashing(session *sessions.Session, msg *messages.MouseThrashing) error - // Mobile - InsertMobileSession(session *sessions.Session) error - InsertMobileCustom(session *sessions.Session, msg *messages.MobileEvent) error - InsertMobileClick(session *sessions.Session, msg *messages.MobileClickEvent) error - InsertMobileSwipe(session *sessions.Session, msg *messages.MobileSwipeEvent) error - InsertMobileInput(session *sessions.Session, msg *messages.MobileInputEvent) error - InsertMobileRequest(session *sessions.Session, msg *messages.MobileNetworkCall, savePayload bool) error - InsertMobileCrash(session *sessions.Session, msg *messages.MobileCrash) error -} - -type task struct { - bulks []Bulk -} - -func NewTask() *task { - return &task{bulks: make([]Bulk, 0, 21)} -} - -type connectorImpl struct { - conn driver.Conn - batches map[string]Bulk //driver.Batch - workerTask chan *task - done chan struct{} - finished chan struct{} -} - -func getEnv(key, fallback string) string { - if value, ok := os.LookupEnv(key); ok { - return value - } - return fallback -} - -func NewConnector(url string) Connector { - license.CheckLicense() - url = strings.TrimPrefix(url, "tcp://") - url = strings.TrimSuffix(url, "/default") - userName := getEnv("CH_USERNAME", "default") - password := getEnv("CH_PASSWORD", "") - conn, err := clickhouse.Open(&clickhouse.Options{ - Addr: []string{url}, - Auth: clickhouse.Auth{ - Database: "default", - Username: userName, - Password: password, - }, - MaxOpenConns: 20, - MaxIdleConns: 15, - ConnMaxLifetime: 3 * time.Minute, - Compression: &clickhouse.Compression{ - Method: clickhouse.CompressionLZ4, - }, - }) - if err != nil { - log.Fatal(err) - } - - c := &connectorImpl{ - conn: conn, - batches: make(map[string]Bulk, 20), - workerTask: make(chan *task, 1), - done: make(chan struct{}), - finished: make(chan struct{}), - } - go c.worker() - return c -} - -func (c *connectorImpl) newBatch(name, query string) error { - batch, err := NewBulk(c.conn, name, query) - if err != nil { - return fmt.Errorf("can't create new batch: %s", err) - } - c.batches[name] = batch - return nil -} - -var batches = map[string]string{ - // Web - "sessions": "INSERT INTO experimental.sessions (session_id, project_id, user_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, user_state, user_city, datetime, duration, pages_count, events_count, errors_count, issue_score, referrer, issue_types, tracker_version, user_browser, user_browser_version, metadata_1, metadata_2, metadata_3, metadata_4, metadata_5, metadata_6, metadata_7, metadata_8, metadata_9, metadata_10, timezone, utm_source, utm_medium, utm_campaign) VALUES (?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), ?, ?, ?, ?)", - "autocompletes": "INSERT INTO experimental.autocomplete (project_id, type, value) VALUES (?, ?, SUBSTR(?, 1, 8000))", - "pages": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, request_start, response_start, response_end, dom_content_loaded_event_start, dom_content_loaded_event_end, load_event_start, load_event_end, first_paint, first_contentful_paint_time, speed_index, visually_complete, time_to_interactive, url_path, event_type) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), ?)", - "clicks": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, label, hesitation_time, event_type, selector, normalized_x, normalized_y, url, url_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000))", - "inputs": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, label, event_type, duration, hesitation_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", - "errors": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, source, name, message, error_id, event_type, error_tags_keys, error_tags_values) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - "performance": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_total_js_heap_size, avg_total_js_heap_size, max_total_js_heap_size, min_used_js_heap_size, avg_used_js_heap_size, max_used_js_heap_size, event_type) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - "requests": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, request_body, response_body, status, method, duration, success, event_type, transfer_size, url_path) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000))", - "custom": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, name, payload, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", - "graphql": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, name, request_body, response_body, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", - "issuesEvents": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, issue_id, issue_type, event_type, url, url_path) VALUES (?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000))", - "issues": "INSERT INTO experimental.issues (project_id, issue_id, type, context_string) VALUES (?, ?, ?, ?)", - //Mobile - "ios_sessions": "INSERT INTO experimental.sessions (session_id, project_id, user_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, user_state, user_city, datetime, duration, pages_count, events_count, errors_count, issue_score, referrer, issue_types, tracker_version, user_browser, user_browser_version, metadata_1, metadata_2, metadata_3, metadata_4, metadata_5, metadata_6, metadata_7, metadata_8, metadata_9, metadata_10, platform, timezone) VALUES (?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), ?, ?)", - "ios_custom": "INSERT INTO experimental.ios_events (session_id, project_id, message_id, datetime, name, payload, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", - "ios_clicks": "INSERT INTO experimental.ios_events (session_id, project_id, message_id, datetime, label, event_type) VALUES (?, ?, ?, ?, ?, ?)", - "ios_swipes": "INSERT INTO experimental.ios_events (session_id, project_id, message_id, datetime, label, direction, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", - "ios_inputs": "INSERT INTO experimental.ios_events (session_id, project_id, message_id, datetime, label, event_type) VALUES (?, ?, ?, ?, ?, ?)", - "ios_requests": "INSERT INTO experimental.ios_events (session_id, project_id, message_id, datetime, url, request_body, response_body, status, method, duration, success, event_type) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?)", - "ios_crashes": "INSERT INTO experimental.ios_events (session_id, project_id, message_id, datetime, name, reason, stacktrace, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", -} - -func (c *connectorImpl) Prepare() error { - for table, query := range batches { - if err := c.newBatch(table, query); err != nil { - return fmt.Errorf("can't create %s batch: %s", table, err) - } - } - return nil -} - -func (c *connectorImpl) Commit() error { - newTask := NewTask() - for _, b := range c.batches { - newTask.bulks = append(newTask.bulks, b) - } - c.batches = make(map[string]Bulk, 20) - if err := c.Prepare(); err != nil { - log.Printf("can't prepare new CH batch set: %s", err) - } - c.workerTask <- newTask - return nil -} - -func (c *connectorImpl) Stop() error { - c.done <- struct{}{} - <-c.finished - return c.conn.Close() -} - -func (c *connectorImpl) sendBulks(t *task) { - for _, b := range t.bulks { - if err := b.Send(); err != nil { - log.Printf("can't send batch: %s", err) - } - } -} - -func (c *connectorImpl) worker() { - for { - select { - case t := <-c.workerTask: - c.sendBulks(t) - case <-c.done: - for t := range c.workerTask { - c.sendBulks(t) - } - c.finished <- struct{}{} - return - } - } -} - -func (c *connectorImpl) checkError(name string, err error) { - if err != clickhouse.ErrBatchAlreadySent { - log.Printf("can't create %s batch after failed append operation: %s", name, err) - } -} - -func (c *connectorImpl) InsertWebInputDuration(session *sessions.Session, msg *messages.InputChange) error { - if msg.Label == "" { - return nil - } - if err := c.batches["inputs"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.MsgID(), - datetime(msg.Timestamp), - msg.Label, - "INPUT", - nullableUint16(uint16(msg.InputDuration)), - nullableUint32(uint32(msg.HesitationTime)), - ); err != nil { - c.checkError("inputs", err) - return fmt.Errorf("can't append to inputs batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertMouseThrashing(session *sessions.Session, msg *messages.MouseThrashing) error { - issueID := hashid.MouseThrashingID(session.ProjectID, session.SessionID, msg.Timestamp) - // Insert issue event to batches - if err := c.batches["issuesEvents"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.MsgID(), - datetime(msg.Timestamp), - issueID, - "mouse_thrashing", - "ISSUE", - msg.Url, - extractUrlPath(msg.Url), - ); err != nil { - c.checkError("issuesEvents", err) - return fmt.Errorf("can't append to issuesEvents batch: %s", err) - } - if err := c.batches["issues"].Append( - uint16(session.ProjectID), - issueID, - "mouse_thrashing", - msg.Url, - ); err != nil { - c.checkError("issues", err) - return fmt.Errorf("can't append to issues batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertIssue(session *sessions.Session, msg *messages.IssueEvent) error { - issueID := hashid.IssueID(session.ProjectID, msg) - // Check issue type before insert to avoid panic from clickhouse lib - switch msg.Type { - case "click_rage", "dead_click", "excessive_scrolling", "bad_request", "missing_resource", "memory", "cpu", "slow_resource", "slow_page_load", "crash", "ml_cpu", "ml_memory", "ml_dead_click", "ml_click_rage", "ml_mouse_thrashing", "ml_excessive_scrolling", "ml_slow_resources", "custom", "js_exception", "mouse_thrashing", "app_crash": - default: - return fmt.Errorf("unknown issueType: %s", msg.Type) - } - // Insert issue event to batches - if err := c.batches["issuesEvents"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.MessageID, - datetime(msg.Timestamp), - issueID, - msg.Type, - "ISSUE", - msg.URL, - extractUrlPath(msg.URL), - ); err != nil { - c.checkError("issuesEvents", err) - return fmt.Errorf("can't append to issuesEvents batch: %s", err) - } - if err := c.batches["issues"].Append( - uint16(session.ProjectID), - issueID, - msg.Type, - msg.ContextString, - ); err != nil { - c.checkError("issues", err) - return fmt.Errorf("can't append to issues batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertWebSession(session *sessions.Session) error { - if session.Duration == nil { - return errors.New("trying to insert session with nil duration") - } - if err := c.batches["sessions"].Append( - session.SessionID, - uint16(session.ProjectID), - session.UserID, - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - session.UserState, - session.UserCity, - datetime(session.Timestamp), - uint32(*session.Duration), - uint16(session.PagesCount), - uint16(session.EventsCount), - uint16(session.ErrorsCount), - uint32(session.IssueScore), - session.Referrer, - session.IssueTypes, - session.TrackerVersion, - session.UserBrowser, - nullableString(session.UserBrowserVersion), - session.Metadata1, - session.Metadata2, - session.Metadata3, - session.Metadata4, - session.Metadata5, - session.Metadata6, - session.Metadata7, - session.Metadata8, - session.Metadata9, - session.Metadata10, - session.Timezone, - session.UtmSource, - session.UtmMedium, - session.UtmCampaign, - ); err != nil { - c.checkError("sessions", err) - return fmt.Errorf("can't append to sessions batch: %s", err) - } - return nil -} - -func extractUrlPath(fullUrl string) string { - _, path, query, err := url.GetURLParts(fullUrl) - if err != nil { - log.Printf("can't parse url: %s", err) - return "" - } - pathQuery := path - if query != "" { - pathQuery += "?" + query - } - return strings.ToLower(pathQuery) -} - -func (c *connectorImpl) InsertWebPageEvent(session *sessions.Session, msg *messages.PageEvent) error { - if err := c.batches["pages"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.MessageID, - datetime(msg.Timestamp), - msg.URL, - nullableUint16(uint16(msg.RequestStart)), - nullableUint16(uint16(msg.ResponseStart)), - nullableUint16(uint16(msg.ResponseEnd)), - nullableUint16(uint16(msg.DomContentLoadedEventStart)), - nullableUint16(uint16(msg.DomContentLoadedEventEnd)), - nullableUint16(uint16(msg.LoadEventStart)), - nullableUint16(uint16(msg.LoadEventEnd)), - nullableUint16(uint16(msg.FirstPaint)), - nullableUint16(uint16(msg.FirstContentfulPaint)), - nullableUint16(uint16(msg.SpeedIndex)), - nullableUint16(uint16(msg.VisuallyComplete)), - nullableUint16(uint16(msg.TimeToInteractive)), - extractUrlPath(msg.URL), - "LOCATION", - ); err != nil { - c.checkError("pages", err) - return fmt.Errorf("can't append to pages batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertWebClickEvent(session *sessions.Session, msg *messages.MouseClick) error { - if msg.Label == "" { - return nil - } - var nX *float32 = nil - var nY *float32 = nil - if msg.NormalizedX != 101 && msg.NormalizedY != 101 { - // To support previous versions of tracker - if msg.NormalizedX <= 100 && msg.NormalizedY <= 100 { - msg.NormalizedX *= 100 - msg.NormalizedY *= 100 - } - normalizedX := float32(msg.NormalizedX) / 100.0 - normalizedY := float32(msg.NormalizedY) / 100.0 - nXVal := normalizedX - nX = &nXVal - nYVal := normalizedY - nY = &nYVal - } - if err := c.batches["clicks"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.MsgID(), - datetime(msg.Timestamp), - msg.Label, - nullableUint32(uint32(msg.HesitationTime)), - "CLICK", - msg.Selector, - nX, - nY, - msg.Url, - extractUrlPath(msg.Url), - ); err != nil { - c.checkError("clicks", err) - return fmt.Errorf("can't append to clicks batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertWebErrorEvent(session *sessions.Session, msg *types.ErrorEvent) error { - keys, values := make([]string, 0, len(msg.Tags)), make([]*string, 0, len(msg.Tags)) - for k, v := range msg.Tags { - keys = append(keys, k) - values = append(values, v) - } - // Check error source before insert to avoid panic from clickhouse lib - switch msg.Source { - case "js_exception", "bugsnag", "cloudwatch", "datadog", "elasticsearch", "newrelic", "rollbar", "sentry", "stackdriver", "sumologic": - default: - return fmt.Errorf("unknown error source: %s", msg.Source) - } - msgID, _ := msg.ID(session.ProjectID) - // Insert event to batch - if err := c.batches["errors"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.MessageID, - datetime(msg.Timestamp), - msg.Source, - nullableString(msg.Name), - msg.Message, - msgID, - "ERROR", - keys, - values, - ); err != nil { - c.checkError("errors", err) - return fmt.Errorf("can't append to errors batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertWebPerformanceTrackAggr(session *sessions.Session, msg *messages.PerformanceTrackAggr) error { - var timestamp uint64 = (msg.TimestampStart + msg.TimestampEnd) / 2 - if err := c.batches["performance"].Append( - session.SessionID, - uint16(session.ProjectID), - uint64(0), // TODO: find messageID for performance events - datetime(timestamp), - nullableString(msg.Meta().Url), - uint8(msg.MinFPS), - uint8(msg.AvgFPS), - uint8(msg.MaxFPS), - uint8(msg.MinCPU), - uint8(msg.AvgCPU), - uint8(msg.MaxCPU), - msg.MinTotalJSHeapSize, - msg.AvgTotalJSHeapSize, - msg.MaxTotalJSHeapSize, - msg.MinUsedJSHeapSize, - msg.AvgUsedJSHeapSize, - msg.MaxUsedJSHeapSize, - "PERFORMANCE", - ); err != nil { - c.checkError("performance", err) - return fmt.Errorf("can't append to performance batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertAutocomplete(session *sessions.Session, msgType, msgValue string) error { - if len(msgValue) == 0 { - return nil - } - if err := c.batches["autocompletes"].Append( - uint16(session.ProjectID), - msgType, - msgValue, - ); err != nil { - c.checkError("autocompletes", err) - return fmt.Errorf("can't append to autocompletes batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertRequest(session *sessions.Session, msg *messages.NetworkRequest, savePayload bool) error { - urlMethod := url.EnsureMethod(msg.Method) - if urlMethod == "" { - return fmt.Errorf("can't parse http method. sess: %d, method: %s", session.SessionID, msg.Method) - } - var request, response *string - if savePayload { - request = &msg.Request - response = &msg.Response - } - if err := c.batches["requests"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.Meta().Index, - datetime(uint64(msg.Meta().Timestamp)), - msg.URL, - request, - response, - uint16(msg.Status), - url.EnsureMethod(msg.Method), - uint16(msg.Duration), - msg.Status < 400, - "REQUEST", - uint32(msg.TransferredBodySize), - extractUrlPath(msg.URL), - ); err != nil { - c.checkError("requests", err) - return fmt.Errorf("can't append to requests batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertCustom(session *sessions.Session, msg *messages.CustomEvent) error { - if err := c.batches["custom"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.Meta().Index, - datetime(uint64(msg.Meta().Timestamp)), - msg.Name, - msg.Payload, - "CUSTOM", - ); err != nil { - c.checkError("custom", err) - return fmt.Errorf("can't append to custom batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertGraphQL(session *sessions.Session, msg *messages.GraphQL) error { - if err := c.batches["graphql"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.Meta().Index, - datetime(uint64(msg.Meta().Timestamp)), - msg.OperationName, - nullableString(msg.Variables), - nullableString(msg.Response), - "GRAPHQL", - ); err != nil { - c.checkError("graphql", err) - return fmt.Errorf("can't append to graphql batch: %s", err) - } - return nil -} - -// Mobile events - -func (c *connectorImpl) InsertMobileSession(session *sessions.Session) error { - if session.Duration == nil { - return errors.New("trying to insert mobile session with nil duration") - } - if err := c.batches["ios_sessions"].Append( - session.SessionID, - uint16(session.ProjectID), - session.UserID, - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - session.UserState, - session.UserCity, - datetime(session.Timestamp), - uint32(*session.Duration), - uint16(session.PagesCount), - uint16(session.EventsCount), - uint16(session.ErrorsCount), - uint32(session.IssueScore), - session.Referrer, - session.IssueTypes, - session.TrackerVersion, - session.UserBrowser, - nullableString(session.UserBrowserVersion), - session.Metadata1, - session.Metadata2, - session.Metadata3, - session.Metadata4, - session.Metadata5, - session.Metadata6, - session.Metadata7, - session.Metadata8, - session.Metadata9, - session.Metadata10, - "ios", - session.Timezone, - ); err != nil { - c.checkError("ios_sessions", err) - return fmt.Errorf("can't append to sessions batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertMobileCustom(session *sessions.Session, msg *messages.MobileEvent) error { - if err := c.batches["ios_custom"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.Meta().Index, - datetime(uint64(msg.Meta().Timestamp)), - msg.Name, - msg.Payload, - "CUSTOM", - ); err != nil { - c.checkError("ios_custom", err) - return fmt.Errorf("can't append to mobile custom batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertMobileClick(session *sessions.Session, msg *messages.MobileClickEvent) error { - if msg.Label == "" { - return nil - } - if err := c.batches["ios_clicks"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.MsgID(), - datetime(msg.Timestamp), - msg.Label, - "TAP", - ); err != nil { - c.checkError("ios_clicks", err) - return fmt.Errorf("can't append to mobile clicks batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertMobileSwipe(session *sessions.Session, msg *messages.MobileSwipeEvent) error { - if msg.Label == "" { - return nil - } - if err := c.batches["ios_swipes"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.MsgID(), - datetime(msg.Timestamp), - msg.Label, - nullableString(msg.Direction), - "SWIPE", - ); err != nil { - c.checkError("ios_clicks", err) - return fmt.Errorf("can't append to mobile clicks batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertMobileInput(session *sessions.Session, msg *messages.MobileInputEvent) error { - if msg.Label == "" { - return nil - } - if err := c.batches["ios_inputs"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.MsgID(), - datetime(msg.Timestamp), - msg.Label, - "INPUT", - ); err != nil { - c.checkError("ios_inputs", err) - return fmt.Errorf("can't append to mobile inputs batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertMobileRequest(session *sessions.Session, msg *messages.MobileNetworkCall, savePayload bool) error { - urlMethod := url.EnsureMethod(msg.Method) - if urlMethod == "" { - return fmt.Errorf("can't parse http method. sess: %d, method: %s", session.SessionID, msg.Method) - } - var request, response *string - if savePayload { - request = &msg.Request - response = &msg.Response - } - if err := c.batches["ios_requests"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.Meta().Index, - datetime(uint64(msg.Meta().Timestamp)), - msg.URL, - request, - response, - uint16(msg.Status), - url.EnsureMethod(msg.Method), - uint16(msg.Duration), - msg.Status < 400, - "REQUEST", - ); err != nil { - c.checkError("ios_requests", err) - return fmt.Errorf("can't append to mobile requests batch: %s", err) - } - return nil -} - -func (c *connectorImpl) InsertMobileCrash(session *sessions.Session, msg *messages.MobileCrash) error { - if err := c.batches["ios_crashes"].Append( - session.SessionID, - uint16(session.ProjectID), - msg.MsgID(), - datetime(msg.Timestamp), - msg.Name, - msg.Reason, - msg.Stacktrace, - "CRASH", - ); err != nil { - c.checkError("ios_crashes", err) - return fmt.Errorf("can't append to mobile crashges batch: %s", err) - } - return nil -} diff --git a/ee/backend/pkg/spot/auth/authorizer.go b/ee/backend/pkg/server/auth/authorizer.go similarity index 83% rename from ee/backend/pkg/spot/auth/authorizer.go rename to ee/backend/pkg/server/auth/authorizer.go index 244961318..fe416b8c5 100644 --- a/ee/backend/pkg/spot/auth/authorizer.go +++ b/ee/backend/pkg/server/auth/authorizer.go @@ -1,8 +1,12 @@ package auth -import "fmt" +import ( + "fmt" -func (a *authImpl) IsAuthorized(authHeader string, permissions []string, isExtension bool) (*User, error) { + "openreplay/backend/pkg/server/user" +) + +func (a *authImpl) IsAuthorized(authHeader string, permissions []string, isExtension bool) (*user.User, error) { secret := a.secret if isExtension { secret = a.spotSecret diff --git a/ee/backend/pkg/spot/api/permissions.go b/ee/backend/pkg/server/auth/permissions.go similarity index 93% rename from ee/backend/pkg/spot/api/permissions.go rename to ee/backend/pkg/server/auth/permissions.go index 1da671bf5..776be57d3 100644 --- a/ee/backend/pkg/spot/api/permissions.go +++ b/ee/backend/pkg/server/auth/permissions.go @@ -1,4 +1,4 @@ -package api +package auth import "strings" diff --git a/ee/backend/pkg/spot/auth/storage.go b/ee/backend/pkg/server/auth/storage.go similarity index 87% rename from ee/backend/pkg/spot/auth/storage.go rename to ee/backend/pkg/server/auth/storage.go index 25d623c34..531321eef 100644 --- a/ee/backend/pkg/spot/auth/storage.go +++ b/ee/backend/pkg/server/auth/storage.go @@ -2,11 +2,13 @@ package auth import ( "fmt" - "openreplay/backend/pkg/db/postgres/pool" "strings" + + "openreplay/backend/pkg/db/postgres/pool" + "openreplay/backend/pkg/server/user" ) -func authUser(conn pool.Pool, userID, tenantID, jwtIAT int, isExtension bool) (*User, error) { +func authUser(conn pool.Pool, userID, tenantID, jwtIAT int, isExtension bool) (*user.User, error) { sql := `SELECT user_id, users.tenant_id, users.name, email, EXTRACT(epoch FROM spot_jwt_iat)::BIGINT AS spot_jwt_iat, roles.permissions FROM users JOIN tenants on users.tenant_id = tenants.tenant_id @@ -15,7 +17,7 @@ func authUser(conn pool.Pool, userID, tenantID, jwtIAT int, isExtension bool) (* if !isExtension { sql = strings.ReplaceAll(sql, "spot_jwt_iat", "jwt_iat") } - user := &User{} + user := &user.User{} var permissions []string if err := conn.QueryRow(sql, userID, tenantID). Scan(&user.ID, &user.TenantID, &user.Name, &user.Email, &user.JwtIat, &permissions); err != nil { @@ -33,3 +35,10 @@ func authUser(conn pool.Pool, userID, tenantID, jwtIAT int, isExtension bool) (* } return user, nil } + +func abs(x int) int { + if x < 0 { + return -x + } + return x +} diff --git a/ee/backend/pkg/spot/service/user.go b/ee/backend/pkg/server/keys/user.go similarity index 88% rename from ee/backend/pkg/spot/service/user.go rename to ee/backend/pkg/server/keys/user.go index ec9e2bb69..b2857d3e7 100644 --- a/ee/backend/pkg/spot/service/user.go +++ b/ee/backend/pkg/server/keys/user.go @@ -1,3 +1,3 @@ -package service +package keys var getUserSQL = `SELECT tenant_id, name, email FROM public.users WHERE user_id = $1 AND deleted_at IS NULL LIMIT 1` diff --git a/ee/backend/pkg/spot/api/tracer.go b/ee/backend/pkg/spot/api/tracer.go deleted file mode 100644 index 3a4fd9647..000000000 --- a/ee/backend/pkg/spot/api/tracer.go +++ /dev/null @@ -1,61 +0,0 @@ -package api - -import ( - "encoding/json" - "net/http" - - "github.com/gorilla/mux" - - "openreplay/backend/pkg/spot/auth" - "openreplay/backend/pkg/spot/service" -) - -var routeMatch = map[string]string{ - "POST" + "/v1/spots": "createSpot", - "GET" + "/v1/spots/{id}": "getSpot", - "PATCH" + "/v1/spots/{id}": "updateSpot", - "GET" + "/v1/spots": "getSpots", - "DELETE" + "/v1/spots": "deleteSpots", - "POST" + "/v1/spots/{id}/comment": "addComment", - "GET" + "/v1/spots/{id}/video": "getSpotVideo", - "PATCH" + "/v1/spots/{id}/public-key": "updatePublicKey", -} - -func (e *Router) logRequest(r *http.Request, bodyBytes []byte, statusCode int) { - pathTemplate, err := mux.CurrentRoute(r).GetPathTemplate() - if err != nil { - e.log.Error(r.Context(), "failed to get path template: %s", err) - } - e.log.Info(r.Context(), "path template: %s", pathTemplate) - if _, ok := routeMatch[r.Method+pathTemplate]; !ok { - e.log.Debug(r.Context(), "no match for route: %s %s", r.Method, pathTemplate) - return - } - // Convert the parameters to json - query := r.URL.Query() - params := make(map[string]interface{}) - for key, values := range query { - if len(values) > 1 { - params[key] = values - } else { - params[key] = values[0] - } - } - jsonData, err := json.Marshal(params) - if err != nil { - e.log.Error(r.Context(), "failed to marshal query parameters: %s", err) - } - requestData := &service.RequestData{ - Action: routeMatch[r.Method+pathTemplate], - Method: r.Method, - PathFormat: pathTemplate, - Endpoint: r.URL.Path, - Payload: bodyBytes, - Parameters: jsonData, - Status: statusCode, - } - userData := r.Context().Value("userData").(*auth.User) - e.services.Tracer.Trace(userData, requestData) - // DEBUG - e.log.Info(r.Context(), "request data: %v", requestData) -} diff --git a/ee/backend/pkg/spot/builder.go b/ee/backend/pkg/spot/builder.go deleted file mode 100644 index b1827897d..000000000 --- a/ee/backend/pkg/spot/builder.go +++ /dev/null @@ -1,45 +0,0 @@ -package spot - -import ( - "openreplay/backend/internal/config/spot" - "openreplay/backend/pkg/db/postgres/pool" - "openreplay/backend/pkg/flakeid" - "openreplay/backend/pkg/logger" - "openreplay/backend/pkg/objectstorage" - "openreplay/backend/pkg/objectstorage/store" - "openreplay/backend/pkg/spot/auth" - "openreplay/backend/pkg/spot/service" - "openreplay/backend/pkg/spot/transcoder" -) - -type ServicesBuilder struct { - Flaker *flakeid.Flaker - ObjStorage objectstorage.ObjectStorage - Auth auth.Auth - Spots service.Spots - Keys service.Keys - Transcoder transcoder.Transcoder - Tracer service.Tracer -} - -func NewServiceBuilder(log logger.Logger, cfg *spot.Config, pgconn pool.Pool) (*ServicesBuilder, error) { - objStore, err := store.NewStore(&cfg.ObjectsConfig) - if err != nil { - return nil, err - } - flaker := flakeid.NewFlaker(cfg.WorkerID) - tracer, err := service.NewTracer(log, pgconn) - if err != nil { - return nil, err - } - spots := service.NewSpots(log, pgconn, flaker) - return &ServicesBuilder{ - Flaker: flaker, - ObjStorage: objStore, - Auth: auth.NewAuth(log, cfg.JWTSecret, cfg.JWTSpotSecret, pgconn), - Spots: spots, - Keys: service.NewKeys(log, pgconn), - Transcoder: transcoder.NewTranscoder(cfg, log, objStore, pgconn, spots), - Tracer: tracer, - }, nil -} diff --git a/ee/backend/pkg/spot/service/tracer.go b/ee/backend/pkg/spot/service/tracer.go deleted file mode 100644 index 8c3342470..000000000 --- a/ee/backend/pkg/spot/service/tracer.go +++ /dev/null @@ -1,104 +0,0 @@ -package service - -import ( - "context" - "errors" - "openreplay/backend/pkg/db/postgres" - db "openreplay/backend/pkg/db/postgres/pool" - "openreplay/backend/pkg/logger" - "openreplay/backend/pkg/pool" - "openreplay/backend/pkg/spot/auth" -) - -type Tracer interface { - Trace(user *auth.User, data *RequestData) error - Close() error -} - -type tracerImpl struct { - log logger.Logger - conn db.Pool - traces postgres.Bulk - saver pool.WorkerPool -} - -func NewTracer(log logger.Logger, conn db.Pool) (Tracer, error) { - switch { - case log == nil: - return nil, errors.New("logger is required") - case conn == nil: - return nil, errors.New("connection is required") - } - tracer := &tracerImpl{ - log: log, - conn: conn, - } - if err := tracer.initBulk(); err != nil { - return nil, err - } - tracer.saver = pool.NewPool(1, 200, tracer.sendTraces) - return tracer, nil -} - -func (t *tracerImpl) initBulk() (err error) { - t.traces, err = postgres.NewBulk(t.conn, - "traces", - "(user_id, tenant_id, auth, action, method, path_format, endpoint, payload, parameters, status)", - "($%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d)", - 10, 50) - if err != nil { - return err - } - return nil -} - -type Task struct { - UserID *uint64 - TenantID uint64 - Auth *string - Data *RequestData -} - -func (t *tracerImpl) sendTraces(payload interface{}) { - rec := payload.(*Task) - t.log.Info(context.Background(), "Sending traces, %v", rec) - if err := t.traces.Append(rec.UserID, rec.TenantID, rec.Auth, rec.Data.Action, rec.Data.Method, rec.Data.PathFormat, - rec.Data.Endpoint, rec.Data.Payload, rec.Data.Parameters, rec.Data.Status); err != nil { - t.log.Error(context.Background(), "can't append trace: %s", err) - } -} - -type RequestData struct { - Action string - Method string - PathFormat string - Endpoint string - Payload []byte - Parameters []byte - Status int -} - -func (t *tracerImpl) Trace(user *auth.User, data *RequestData) error { - switch { - case user == nil: - return errors.New("user is required") - case data == nil: - return errors.New("request is required") - } - trace := &Task{ - UserID: &user.ID, - TenantID: user.TenantID, - Auth: &user.AuthMethod, - Data: data, - } - t.saver.Submit(trace) - return nil -} - -func (t *tracerImpl) Close() error { - t.saver.Stop() - if err := t.traces.Send(); err != nil { - return err - } - return nil -} diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx index 3ca5d1d78..5e0a9abe2 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx @@ -8,6 +8,8 @@ import { observer } from 'mobx-react-lite'; import { NoContent, Icon } from 'UI'; import { Tag, Tooltip } from 'antd'; import { useModal } from 'App/components/Modal'; +import { useStore } from '@/mstore'; +import Filter from '@/mstore/types/filter'; interface Props { metric?: Widget; @@ -17,19 +19,30 @@ interface Props { } function FunnelWidget(props: Props) { + const { dashboardStore, searchStore } = useStore(); const [focusedFilter, setFocusedFilter] = React.useState(null); const { isWidget = false, data, metric, compData } = props; const funnel = data.funnel || { stages: [] }; const totalSteps = funnel.stages.length; - const stages = isWidget - ? [...funnel.stages.slice(0, 1), funnel.stages[funnel.stages.length - 1]] - : funnel.stages; + const stages = isWidget ? [...funnel.stages.slice(0, 1), funnel.stages[funnel.stages.length - 1]] : funnel.stages; const hasMoreSteps = funnel.stages.length > 2; const lastStage = funnel.stages[funnel.stages.length - 1]; const remainingSteps = totalSteps - 2; const { hideModal } = useModal(); - const metricLabel = - metric?.metricFormat == 'userCount' ? 'Users' : 'Sessions'; + const metricLabel = metric?.metricFormat == 'userCount' ? 'Users' : 'Sessions'; + const drillDownFilter = dashboardStore.drillDownFilter; + const drillDownPeriod = dashboardStore.drillDownPeriod; + const metricFilters = metric?.series[0]?.filter.filters || []; + + const applyDrillDown = (index: number) => { + const filter = new Filter().fromData({ filters: metricFilters.slice(0, index + 1) }); + const periodTimestamps = drillDownPeriod.toTimestamps(); + drillDownFilter.merge({ + filters: filter.toJson().filters, + startTimestamp: periodTimestamps.startTimestamp, + endTimestamp: periodTimestamps.endTimestamp + }); + }; useEffect(() => { return () => { @@ -53,6 +66,8 @@ function FunnelWidget(props: Props) { } } }); + + applyDrillDown(focusedFilter === index ? -1 : index); }; const shownStages = React.useMemo(() => { diff --git a/frontend/app/components/Session/Player/SharedComponents/BackendLogs/BackendLogsPanel.tsx b/frontend/app/components/Session/Player/SharedComponents/BackendLogs/BackendLogsPanel.tsx index b62c975ea..918f82a27 100644 --- a/frontend/app/components/Session/Player/SharedComponents/BackendLogs/BackendLogsPanel.tsx +++ b/frontend/app/components/Session/Player/SharedComponents/BackendLogs/BackendLogsPanel.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@tanstack/react-query'; import React from 'react'; import { VList, VListHandle } from 'virtua'; -import { PlayerContext } from "App/components/Session/playerContext"; +import { PlayerContext } from 'App/components/Session/playerContext'; import { processLog, UnifiedLog } from './utils'; import { observer } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; @@ -13,13 +13,10 @@ import BottomBlock from 'App/components/shared/DevTools/BottomBlock'; import { capitalize } from 'App/utils'; import { Icon } from 'UI'; import { Segmented, Input, Tooltip } from 'antd'; -import {SearchOutlined} from '@ant-design/icons'; +import { SearchOutlined } from '@ant-design/icons'; import { client } from 'App/mstore'; -import { FailedFetch, LoadingFetch } from "./StatusMessages"; -import { - TableHeader, - LogRow -} from './Table' +import { FailedFetch, LoadingFetch } from './StatusMessages'; +import { TableHeader, LogRow } from './Table'; async function fetchLogs( tab: string, @@ -31,23 +28,24 @@ async function fetchLogs( ); const json = await data.json(); try { - const logsResp = await fetch(json.url) + const logsResp = await fetch(json.url); if (logsResp.ok) { - const logJson = await logsResp.json() - if (logJson.length === 0) return [] - return processLog(logJson) + const logJson = await logsResp.json(); + if (logJson.length === 0) return []; + return processLog(logJson); } else { - throw new Error('Failed to fetch logs') + throw new Error('Failed to fetch logs'); } } catch (e) { - console.log(e) - throw e + console.log(e); + throw e; } } function BackendLogsPanel() { const { projectsStore, sessionStore, integrationsStore } = useStore(); - const integratedServices = integrationsStore.integrations.backendLogIntegrations; + const integratedServices = + integrationsStore.integrations.backendLogIntegrations; const defaultTab = integratedServices[0]!.name; const sessionId = sessionStore.currentId; const projectId = projectsStore.siteId!; @@ -83,59 +81,59 @@ function BackendLogsPanel() { return ( -
-
-
Traces
- {tabs.length && tab ? ( -
- -
- ) : null} -
- -
- - Current Tab - ), - value: 'current', disabled: true}, - ]} - defaultValue="all" - size="small" - className="rounded-full font-medium" - /> - - - } +
+
+
Traces
+ {tabs.length && tab ? ( +
+
- -
+ ) : null} +
+ +
+ + Current Tab + + ), + value: 'current', + disabled: true, + }, + ]} + defaultValue="all" + size="small" + className="rounded-full font-medium" + /> + + } + /> +
+
- {isPending ? ( - - ) : null} + {isPending ? : null} {isError ? ( - - ) : null} - {isSuccess ? ( - + ) : null} + {isSuccess ? : null} ); @@ -148,8 +146,10 @@ const LogsTable = observer(({ data }: { data: UnifiedLog[] }) => { const _list = React.useRef(null); const activeIndex = React.useMemo(() => { const currTs = time + sessionStart; - const index = data.findIndex( - (log) => log.timestamp !== 'N/A' ? new Date(log.timestamp).getTime() >= currTs : false + const index = data.findIndex((log) => + log.timestamp !== 'N/A' + ? new Date(log.timestamp).getTime() >= currTs + : false ); return index === -1 ? data.length - 1 : index; }, [time, data.length]); @@ -161,17 +161,22 @@ const LogsTable = observer(({ data }: { data: UnifiedLog[] }) => { const onJump = (ts: number) => { player.jump(ts - sessionStart); - } + }; return ( <> {data.map((log, index) => ( - + ))} - ) + ); }); export default observer(BackendLogsPanel); diff --git a/frontend/app/components/Session/Tabs/Tabs.tsx b/frontend/app/components/Session/Tabs/Tabs.tsx index 65b95fd42..1bd88d627 100644 --- a/frontend/app/components/Session/Tabs/Tabs.tsx +++ b/frontend/app/components/Session/Tabs/Tabs.tsx @@ -22,7 +22,6 @@ const Tabs = ({ tabs, active, onClick, border = true, className }: Props) => { return (
)} @@ -370,7 +369,14 @@ function PanelComponent({
} > - {isSpot ? : } + {isSpot ? ( + + ) : ( + + )} {selectedFeatures.map((feature: any, index: number) => (
( { {disabled ? (
- Multi-tab performance overview is not available.
+ Multi-tab performance overview is not available. +
) : null} diff --git a/frontend/app/components/Session_/WarnBadge.tsx b/frontend/app/components/Session_/WarnBadge.tsx index 704ae619e..3d6706e5f 100644 --- a/frontend/app/components/Session_/WarnBadge.tsx +++ b/frontend/app/components/Session_/WarnBadge.tsx @@ -106,11 +106,11 @@ const WarnBadge = React.memo( >
- Tracker version({version}) for this recording is{' '} + Tracker version ({version}) for this recording is{' '} {trackerVerDiff === VersionComparison.Lower ? 'lower ' : 'ahead of '} - the current({trackerVersion}) version. + the current ({trackerVersion}) version.
Some recording might display incorrectly. diff --git a/frontend/app/components/Spots/SpotPlayer/components/Panels/SpotConsole.tsx b/frontend/app/components/Spots/SpotPlayer/components/Panels/SpotConsole.tsx index b5cec283c..10893b8fa 100644 --- a/frontend/app/components/Spots/SpotPlayer/components/Panels/SpotConsole.tsx +++ b/frontend/app/components/Spots/SpotPlayer/components/Panels/SpotConsole.tsx @@ -70,6 +70,7 @@ function SpotConsole({ onClose }: { onClose: () => void }) { jump={jump} iconProps={getIconProps(log.level)} renderWithNL={renderWithNL} + showSingleTab /> ))} diff --git a/frontend/app/components/Spots/SpotPlayer/components/SpotPlayerHeader.tsx b/frontend/app/components/Spots/SpotPlayer/components/SpotPlayerHeader.tsx index 427e95d8d..e46a5547b 100644 --- a/frontend/app/components/Spots/SpotPlayer/components/SpotPlayerHeader.tsx +++ b/frontend/app/components/Spots/SpotPlayer/components/SpotPlayerHeader.tsx @@ -143,7 +143,7 @@ function SpotPlayerHeader({ {browserVersion && ( <>
·
-
Chrome v{browserVersion}
+
Chromium v{browserVersion}
)} {resolution && ( diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index b9f12c81c..ce6975684 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -130,22 +130,17 @@ function ConsolePanel({ }, [currentTab, tabStates, dataSource, tabValues, isLive]) const getTabNum = (tab: string) => (tabsArr.findIndex((t) => t === tab) + 1); - const list = isLive - ? (useMemo( - () => logListNow.concat(exceptionsListNow).sort((a, b) => a.time - b.time), - [logListNow.length, exceptionsListNow.length] - ) as ILog[]) - : (useMemo( - () => logList.concat(exceptionsList).sort((a, b) => a.time - b.time), - [logList.length, exceptionsList.length] - ).filter((l) => - zoomEnabled ? l.time >= zoomStartTs && l.time <= zoomEndTs : true - ) as ILog[]); + const list = useMemo(() => { + if (isLive) { + return logListNow.concat(exceptionsListNow).sort((a, b) => a.time - b.time) + } else { + const logs = logList.concat(exceptionsList).sort((a, b) => a.time - b.time) + return zoomEnabled ? logs.filter(l => l.time >= zoomStartTs && l.time <= zoomEndTs) : logs + } + }, [isLive, logList.length, exceptionsList.length, logListNow.length, exceptionsListNow.length, zoomEnabled, zoomStartTs, zoomEndTs]) let filteredList = useRegExListFilterMemo(list, (l) => l.value, filter); filteredList = useTabListFilterMemo(filteredList, (l) => LEVEL_TAB[l.level], ALL, activeTab); - React.useEffect(() => { - }, [activeTab, filter]); const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }); const onFilterChange = ({ target: { value } }: any) => devTools.update(INDEX_KEY, { filter: value }); diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/MobileConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/MobileConsolePanel.tsx index f2424b0ae..ea1aa4923 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/MobileConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/MobileConsolePanel.tsx @@ -1,10 +1,13 @@ import React, { useEffect, useRef, useState } from 'react'; import { LogLevel, ILog } from 'Player'; import BottomBlock from '../BottomBlock'; -import { Tabs, Input, Icon, NoContent } from 'UI'; +import { Tabs, Input, NoContent } from 'UI'; import cn from 'classnames'; import ConsoleRow from '../ConsoleRow'; -import { IOSPlayerContext, MobilePlayerContext } from 'App/components/Session/playerContext'; +import { + IOSPlayerContext, + MobilePlayerContext, +} from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import { VList, VListHandle } from 'virtua'; import { useStore } from 'App/mstore'; @@ -12,7 +15,7 @@ import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorD import { useModal } from 'App/components/Modal'; import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'; -import {InfoCircleOutlined} from '@ant-design/icons' +import { InfoCircleOutlined, SearchOutlined } from '@ant-design/icons'; const ALL = 'ALL'; const INFO = 'INFO'; @@ -27,7 +30,10 @@ const LEVEL_TAB = { [LogLevel.EXCEPTION]: ERRORS, } as const; -const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ text: tab, key: tab })); +const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ + text: tab, + key: tab, +})); function renderWithNL(s: string | null = '') { if (typeof s !== 'string') return ''; @@ -74,20 +80,23 @@ function MobileConsolePanel() { const [isDetailsModalActive, setIsDetailsModalActive] = useState(false); const { showModal } = useModal(); - const { player, store } = React.useContext(MobilePlayerContext); + const { player, store } = + React.useContext(MobilePlayerContext); const jump = (t: number) => player.jump(t); - const { - logList, - logListNow, - exceptionsListNow, - } = store.get(); + const { logList, logListNow, exceptionsListNow } = store.get(); const list = logList as ILog[]; let filteredList = useRegExListFilterMemo(list, (l) => l.value, filter); - filteredList = useTabListFilterMemo(filteredList, (l) => LEVEL_TAB[l.level], ALL, activeTab); + filteredList = useTabListFilterMemo( + filteredList, + (l) => LEVEL_TAB[l.level], + ALL, + activeTab + ); - const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }); + const onTabClick = (activeTab: any) => + devTools.update(INDEX_KEY, { activeTab }); const onFilterChange = ({ target: { value } }: any) => devTools.update(INDEX_KEY, { filter: value }); @@ -137,7 +146,12 @@ function MobileConsolePanel() {
Console - +
} + size="small" + prefix={} />
@@ -160,11 +174,7 @@ function MobileConsolePanel() { size="small" show={filteredList.length === 0} > - + {filteredList.map((log, index) => ( showDetails(log)} + showSingleTab /> ))} diff --git a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx index 8fa735df6..699c5e807 100644 --- a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx +++ b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx @@ -12,7 +12,7 @@ interface Props { renderWithNL?: any; style?: any; onClick?: () => void; - getTabNum: (tab: string) => number; + getTabNum?: (tab: string) => number; showSingleTab: boolean; } function ConsoleRow(props: Props) { @@ -45,7 +45,7 @@ function ConsoleRow(props: Props) { const titleLine = lines[0]; const restLines = lines.slice(1); - const logSource = props.showSingleTab ? -1 : props.getTabNum(log.tabId); + const logSource = props.showSingleTab ? -1 : props.getTabNum?.(log.tabId); const logTabId = log.tabId return (
- + {!isMobile && !isSpot ? : null} ({ text: tab, key: tab })); -type EventsList = Array; +type EventsList = Array< + Timed & { name: string; source: string; key: string; payload?: string[] } +>; -const WebStackEventPanelComp = observer( - () => { - const { uiPlayerStore } = useStore(); - const zoomEnabled = uiPlayerStore.timelineZoom.enabled; - const zoomStartTs = uiPlayerStore.timelineZoom.startTs; - const zoomEndTs = uiPlayerStore.timelineZoom.endTs; - const { player, store } = React.useContext(PlayerContext); - const jump = (t: number) => player.jump(t); - const { currentTab, tabStates } = store.get(); +const WebStackEventPanelComp = observer(() => { + const { uiPlayerStore } = useStore(); + const zoomEnabled = uiPlayerStore.timelineZoom.enabled; + const zoomStartTs = uiPlayerStore.timelineZoom.startTs; + const zoomEndTs = uiPlayerStore.timelineZoom.endTs; + const { player, store } = React.useContext(PlayerContext); + const jump = (t: number) => player.jump(t); + const { currentTab, tabStates } = store.get(); - const { stackList: list = [], stackListNow: listNow = [] } = tabStates[currentTab]; + const { stackList: list = [], stackListNow: listNow = [] } = + tabStates[currentTab]; - return ( - - ); - } -); + return ( + + ); +}); export const WebStackEventPanel = WebStackEventPanelComp; -const MobileStackEventPanelComp = observer( - () => { - const { uiPlayerStore } = useStore(); - const zoomEnabled = uiPlayerStore.timelineZoom.enabled; - const zoomStartTs = uiPlayerStore.timelineZoom.startTs; - const zoomEndTs = uiPlayerStore.timelineZoom.endTs; - const { player, store } = React.useContext(MobilePlayerContext); - const jump = (t: number) => player.jump(t); - const { eventList: list = [], eventListNow: listNow = [] } = store.get(); +const MobileStackEventPanelComp = observer(() => { + const { uiPlayerStore } = useStore(); + const zoomEnabled = uiPlayerStore.timelineZoom.enabled; + const zoomStartTs = uiPlayerStore.timelineZoom.startTs; + const zoomEndTs = uiPlayerStore.timelineZoom.endTs; + const { player, store } = React.useContext(MobilePlayerContext); + const jump = (t: number) => player.jump(t); + const { eventList: list = [], eventListNow: listNow = [] } = store.get(); - return ( - - ); - } -); + return ( + + ); +}); export const MobileStackEventPanel = MobileStackEventPanelComp; -const EventsPanel = observer(({ - list, - listNow, - jump, - zoomEnabled, - zoomStartTs, - zoomEndTs, -}: { - list: EventsList; - listNow: EventsList; - jump: (t: number) => void; - zoomEnabled: boolean; - zoomStartTs: number; - zoomEndTs: number; -}) => { - const { - sessionStore: { devTools }, - } = useStore(); - const { showModal } = useModal(); - const [isDetailsModalActive, setIsDetailsModalActive] = useState(false); // TODO:embed that into useModal - const filter = devTools[INDEX_KEY].filter; - const activeTab = devTools[INDEX_KEY].activeTab; - const activeIndex = devTools[INDEX_KEY].index; +const EventsPanel = observer( + ({ + list, + listNow, + jump, + zoomEnabled, + zoomStartTs, + zoomEndTs, + isMobile, + }: { + list: EventsList; + listNow: EventsList; + jump: (t: number) => void; + zoomEnabled: boolean; + zoomStartTs: number; + zoomEndTs: number; + isMobile?: boolean; + }) => { + const { + sessionStore: { devTools }, + } = useStore(); + const { showModal } = useModal(); + const [isDetailsModalActive, setIsDetailsModalActive] = useState(false); // TODO:embed that into useModal + const filter = devTools[INDEX_KEY].filter; + const activeTab = devTools[INDEX_KEY].activeTab; + const activeIndex = devTools[INDEX_KEY].index; - const inZoomRangeList = list.filter(({ time }) => - zoomEnabled ? zoomStartTs <= time && time <= zoomEndTs : true - ); - const inZoomRangeListNow = listNow.filter(({ time }) => - zoomEnabled ? zoomStartTs <= time && time <= zoomEndTs : true - ); + const inZoomRangeList = list.filter(({ time }) => + zoomEnabled ? zoomStartTs <= time && time <= zoomEndTs : true + ); + const inZoomRangeListNow = listNow.filter(({ time }) => + zoomEnabled ? zoomStartTs <= time && time <= zoomEndTs : true + ); - let filteredList = useRegExListFilterMemo(inZoomRangeList, (it) => { - const searchBy = [it.name] - if (it.payload) { - const payload = Array.isArray(it.payload) ? it.payload.join(',') : JSON.stringify(it.payload); - searchBy.push(payload); - } - return searchBy - }, filter); - filteredList = useTabListFilterMemo(filteredList, (it) => it.source, ALL, activeTab); - - const onTabClick = (activeTab: (typeof TAB_KEYS)[number]) => - devTools.update(INDEX_KEY, { activeTab }); - const onFilterChange = ({ target: { value } }: React.ChangeEvent) => devTools.update(INDEX_KEY, { filter: value }); - const tabs = useMemo( - () => TABS.filter(({ key }) => key === ALL || inZoomRangeList.some(({ source }) => key === source)), - [inZoomRangeList.length] - ); - - const [timeoutStartAutoscroll, stopAutoscroll] = useAutoscroll( - filteredList, - getLastItemTime(inZoomRangeListNow), - activeIndex, - (index) => devTools.update(INDEX_KEY, { index }) - ); - const onMouseEnter = stopAutoscroll; - const onMouseLeave = () => { - if (isDetailsModalActive) { - return; - } - timeoutStartAutoscroll(); - }; - - const showDetails = (item: any) => { - setIsDetailsModalActive(true); - showModal(, { - right: true, - width: 500, - onClose: () => { - setIsDetailsModalActive(false); - timeoutStartAutoscroll(); + let filteredList = useRegExListFilterMemo( + inZoomRangeList, + (it) => { + const searchBy = [it.name]; + if (it.payload) { + const payload = Array.isArray(it.payload) + ? it.payload.join(',') + : JSON.stringify(it.payload); + searchBy.push(payload); + } + return searchBy; }, - }); - devTools.update(INDEX_KEY, { index: filteredList.indexOf(item) }); - stopAutoscroll(); - }; + filter + ); + filteredList = useTabListFilterMemo( + filteredList, + (it) => it.source, + ALL, + activeTab + ); - const _list = React.useRef(null); - useEffect(() => { - if (_list.current) { - _list.current.scrollToIndex(activeIndex); - } - }, [activeIndex]); + const onTabClick = (activeTab: (typeof TAB_KEYS)[number]) => + devTools.update(INDEX_KEY, { activeTab }); + const onFilterChange = ({ + target: { value }, + }: React.ChangeEvent) => + devTools.update(INDEX_KEY, { filter: value }); + const tabs = useMemo( + () => + TABS.filter( + ({ key }) => + key === ALL || inZoomRangeList.some(({ source }) => key === source) + ), + [inZoomRangeList.length] + ); - return ( - - -
- Stack Events - -
-
- - Current Tab - ), - value: 'current', disabled: true}, - ]} - defaultValue="all" - size="small" - className="rounded-full font-medium" - /> - } - /> -
-
- - - - No Data -
- } - size="small" - show={filteredList.length === 0} - > - - {filteredList.map((item, index) => ( - { - stopAutoscroll(); - devTools.update(INDEX_KEY, { index: filteredList.indexOf(item) }); - jump(item.time); - }} - onClick={() => showDetails(item)} + const [timeoutStartAutoscroll, stopAutoscroll] = useAutoscroll( + filteredList, + getLastItemTime(inZoomRangeListNow), + activeIndex, + (index) => devTools.update(INDEX_KEY, { index }) + ); + const onMouseEnter = stopAutoscroll; + const onMouseLeave = () => { + if (isDetailsModalActive) { + return; + } + timeoutStartAutoscroll(); + }; + + const showDetails = (item: any) => { + setIsDetailsModalActive(true); + showModal(, { + right: true, + width: 500, + onClose: () => { + setIsDetailsModalActive(false); + timeoutStartAutoscroll(); + }, + }); + devTools.update(INDEX_KEY, { index: filteredList.indexOf(item) }); + stopAutoscroll(); + }; + + const _list = React.useRef(null); + useEffect(() => { + if (_list.current) { + _list.current.scrollToIndex(activeIndex); + } + }, [activeIndex]); + + return ( + + +
+ + Stack Events + + +
+
+ {isMobile ? null : ( + + Current Tab + + ), + value: 'current', + disabled: true, + }, + ]} + defaultValue="all" + size="small" + className="rounded-full font-medium" /> - ))} - - - - - ); -}); + )} + } + /> +
+
+ + + + No Data +
+ } + size="small" + show={filteredList.length === 0} + > + + {filteredList.map((item, index) => ( + { + stopAutoscroll(); + devTools.update(INDEX_KEY, { + index: filteredList.indexOf(item), + }); + jump(item.time); + }} + onClick={() => showDetails(item)} + /> + ))} + + +
+ + ); + } +); diff --git a/frontend/app/components/shared/DevTools/TabTag.tsx b/frontend/app/components/shared/DevTools/TabTag.tsx index 48410c2a0..4478cfb6f 100644 --- a/frontend/app/components/shared/DevTools/TabTag.tsx +++ b/frontend/app/components/shared/DevTools/TabTag.tsx @@ -3,12 +3,12 @@ import { Tooltip } from 'antd'; import { observer } from 'mobx-react-lite'; import { PlayerContext } from 'Components/Session/playerContext'; -function TabTag({ logSource }: { logSource: number; logTabId: string }) { +function TabTag({ logSource, logTabId }: { logSource: number; logTabId: string }) { const { store } = React.useContext(PlayerContext); const { tabNames } = store.get(); return ( - +
{ super.clean(); - this.screen.clean(); + this.screen?.clean(); // @ts-ignore this.screen = undefined; this.messageLoader.clean(); diff --git a/frontend/package.json b/frontend/package.json index b2a1f7e60..8324e1761 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -91,7 +91,7 @@ "@babel/preset-typescript": "^7.23.2", "@babel/runtime": "^7.23.2", "@jest/globals": "^29.7.0", - "@openreplay/sourcemap-uploader": "^3.0.8", + "@openreplay/sourcemap-uploader": "^3.0.10", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/luxon": "^3.4.2", "@types/node": "^22.7.8", @@ -116,6 +116,7 @@ "cypress": "^13.3.0", "cypress-image-snapshot": "^4.0.1", "dotenv": "^6.2.0", + "esbuild-loader": "^4.2.2", "eslint": "^8.15.0", "eslint-plugin-react": "^7.29.4", "file-loader": "^6.2.0", diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index a492316c4..a3c2a1ddf 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -6,6 +6,8 @@ import CopyWebpackPlugin from 'copy-webpack-plugin'; import HtmlWebpackPlugin from "html-webpack-plugin"; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import CompressionPlugin from "compression-webpack-plugin"; +import { EsbuildPlugin } from 'esbuild-loader'; + const dotenv = require('dotenv').config({ path: __dirname + '/.env' }) const isDevelopment = process.env.NODE_ENV !== 'production' const stylesHandler = MiniCssExtractPlugin.loader; @@ -28,23 +30,32 @@ const config: Configuration = { splitChunks: { chunks: 'all', }, + minimizer: [ + new EsbuildPlugin({ + target: 'es2020', + css: true + }) + ] }, module: { exprContextCritical: false, rules: [ { - test: /\.(ts|js)x?$/i, + test: /\.tsx?$/i, exclude: isDevelopment ? /node_modules/ : undefined, - use: ['thread-loader', { - loader: "babel-loader", - options: { - presets: [ - "@babel/preset-env", - "@babel/preset-react", - "@babel/preset-typescript", - ], - }, - }], + loader: "esbuild-loader", + options: { + target: 'es2020', + }, + }, + { + test: /\.jsx?$/i, + exclude: isDevelopment ? /node_modules/ : undefined, + loader: "esbuild-loader", + options: { + loader: 'jsx', + target: 'es2020', + }, }, { test: /\.s[ac]ss$/i, @@ -111,7 +122,11 @@ const config: Configuration = { }, }, plugins: [ - new CompressionPlugin(), + (isDevelopment ? false : new CompressionPlugin({ + test: /\.(js|css|html|svg)$/, + algorithm: 'brotliCompress', + threshold: 10240, + })), new webpack.DefinePlugin({ // 'process.env': ENV_VARIABLES, 'window.env': ENV_VARIABLES, @@ -131,6 +146,7 @@ const config: Configuration = { performance: { hints: false, }, + watchOptions: { ignored: "**/node_modules/**" }, devServer: { // static: path.join(__dirname, "public"), historyApiFallback: true, @@ -138,7 +154,6 @@ const config: Configuration = { open: true, port: 3333, hot: true, - compress: true, allowedHosts: "all", client: { overlay: { diff --git a/frontend/yarn.lock b/frontend/yarn.lock index c1cf850a0..f1b91f871 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -31,9 +31,9 @@ __metadata: languageName: node linkType: hard -"@ant-design/cssinjs-utils@npm:^1.1.3": - version: 1.1.3 - resolution: "@ant-design/cssinjs-utils@npm:1.1.3" +"@ant-design/cssinjs-utils@npm:^1.1.1": + version: 1.1.1 + resolution: "@ant-design/cssinjs-utils@npm:1.1.1" dependencies: "@ant-design/cssinjs": "npm:^1.21.0" "@babel/runtime": "npm:^7.23.2" @@ -41,13 +41,13 @@ __metadata: peerDependencies: react: ">=16.9.0" react-dom: ">=16.9.0" - checksum: 10c1/56b9f02f8527a4e382a96bf0a72a3b4843caa86378838488e583b2be6f4b004bc004a1c4640b04fac670df78651ca2b086938bf3f3a67305e2fff858c5ac318d + checksum: 10c1/215ae0ee6f928ab35a4a8d6e6eeffd93dc90c2464e3cf652f3d5bb7351b04aad6be4fdc75d5458fa2cab195f2342f1c5e8c33e93e22191729da03c09e47153a3 languageName: node linkType: hard "@ant-design/cssinjs@npm:^1.21.0, @ant-design/cssinjs@npm:^1.21.1": - version: 1.22.1 - resolution: "@ant-design/cssinjs@npm:1.22.1" + version: 1.22.0 + resolution: "@ant-design/cssinjs@npm:1.22.0" dependencies: "@babel/runtime": "npm:^7.11.1" "@emotion/hash": "npm:^0.8.0" @@ -59,7 +59,7 @@ __metadata: peerDependencies: react: ">=16.0.0" react-dom: ">=16.0.0" - checksum: 10c1/45cbdb72ff0f6089d279e1d0eeff86f286785549079e60d3f679fd33aa920e169b107e61d27076726fdb10ab4aa0f236d17cd9f8015a63fcc44056182b666c09 + checksum: 10c1/c1235c0d86a5d2eb9140ac665768ba2348ac825e66912bb816edf2b88c2cc29c1cda0ac3d8e454e0e6549663976b110e8bf6621d5397006c8d3d7e8865cdaa17 languageName: node linkType: hard @@ -79,9 +79,9 @@ __metadata: languageName: node linkType: hard -"@ant-design/icons@npm:^5.2.5, @ant-design/icons@npm:^5.5.2": - version: 5.5.2 - resolution: "@ant-design/icons@npm:5.5.2" +"@ant-design/icons@npm:^5.2.5, @ant-design/icons@npm:^5.5.1": + version: 5.5.1 + resolution: "@ant-design/icons@npm:5.5.1" dependencies: "@ant-design/colors": "npm:^7.0.0" "@ant-design/icons-svg": "npm:^4.4.0" @@ -91,7 +91,7 @@ __metadata: peerDependencies: react: ">=16.0.0" react-dom: ">=16.0.0" - checksum: 10c1/ac783d838e9f376fe1ab9a75d6eb1bd5ce8516323dd731153ce828da152e1b7c79ee988737df09938944a82c5dbf8f3adc37439ee0b432db3fc745467a358398 + checksum: 10c1/e197a5d2b88d62e3ea3193bb656b3f4ac4b7dabd1065af731ffb8ddd4058565a017424cf65f5f30443a03da5fa6a4517e55e92a462473c53ecfe0848f677c255 languageName: node linkType: hard @@ -111,8 +111,8 @@ __metadata: linkType: hard "@babel/cli@npm:^7.23.0": - version: 7.26.4 - resolution: "@babel/cli@npm:7.26.4" + version: 7.25.9 + resolution: "@babel/cli@npm:7.25.9" dependencies: "@jridgewell/trace-mapping": "npm:^0.3.25" "@nicolo-ribaudo/chokidar-2": "npm:2.1.8-no-fsevents.3" @@ -133,11 +133,11 @@ __metadata: bin: babel: ./bin/babel.js babel-external-helpers: ./bin/babel-external-helpers.js - checksum: 10c1/c149aacda4d24dbd6bf7b865cec1c98a9a017f5edf103182fd5e6bfac1a21216a1b6ccafe1b256c40c036906486021e82c66cdc06c0494dbc5520b283e6a2c6b + checksum: 10c1/a9c370b74ce34add4ddd060ebfcc3d9b206794b5f4947e2bee6c440e7c85af877f5d4e423202b45c3fe6ef0bed52295b298ee1c3f4be82824c883756482996d8 languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0, @babel/code-frame@npm:^7.26.2": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0": version: 7.26.2 resolution: "@babel/code-frame@npm:7.26.2" dependencies: @@ -149,9 +149,9 @@ __metadata: linkType: hard "@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.9, @babel/compat-data@npm:^7.26.0": - version: 7.26.3 - resolution: "@babel/compat-data@npm:7.26.3" - checksum: 10c1/dcc4277c6fede0c195d0e11563f15e63eea9391bfe1f933a3e79342920e3478ff6575bee2ab837035b35fbc4ac359bbcaeeebc93664a38d52282dbb321d56e09 + version: 7.26.2 + resolution: "@babel/compat-data@npm:7.26.2" + checksum: 10c1/764a057e4de585ccf726192bd8c568bbea2dd4c18f35c232be70a78bed5382f7fc024fd7bba43c4576cd2cbf00d22c07e1e82e26fea8d52b80a9e290d4f261ed languageName: node linkType: hard @@ -189,16 +189,16 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.23.0, @babel/generator@npm:^7.26.0, @babel/generator@npm:^7.26.3, @babel/generator@npm:^7.7.2": - version: 7.26.3 - resolution: "@babel/generator@npm:7.26.3" +"@babel/generator@npm:^7.23.0, @babel/generator@npm:^7.25.9, @babel/generator@npm:^7.26.0, @babel/generator@npm:^7.7.2": + version: 7.26.2 + resolution: "@babel/generator@npm:7.26.2" dependencies: - "@babel/parser": "npm:^7.26.3" - "@babel/types": "npm:^7.26.3" + "@babel/parser": "npm:^7.26.2" + "@babel/types": "npm:^7.26.0" "@jridgewell/gen-mapping": "npm:^0.3.5" "@jridgewell/trace-mapping": "npm:^0.3.25" jsesc: "npm:^3.0.2" - checksum: 10c1/8f3f385d7b06eef60cf7e486bdd1fced120d668eb480138351ef43c91593b16dd40e4c430098b6657f6a0660913bc98cd2f9be1bb8b4306eb9b45f84b69dd700 + checksum: 10c1/ab995339633b0edd65f22456e9d5577b271112c4d473ca8627296afdda06f5dcb03a22b44a05f3796c062826b1de0229efa43758a5f728bc885a979b15d8af8a languageName: node linkType: hard @@ -211,6 +211,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.25.9" + dependencies: + "@babel/traverse": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + checksum: 10c1/a70b903d70b8712f04f1432e72a5556709f268395f0fbe5a212272f89c1874f5a3dc7cd8869a3c05f1d058ed2725fbb9b306deea4b56acd2d4bfe7ccbc649a50 + languageName: node + linkType: hard + "@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-compilation-targets@npm:7.25.9" @@ -242,15 +252,15 @@ __metadata: linkType: hard "@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.25.9": - version: 7.26.3 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.26.3" + version: 7.25.9 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.9" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.25.9" - regexpu-core: "npm:^6.2.0" + regexpu-core: "npm:^6.1.1" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10c1/babbe3b2a9f58b1953173fb8312d31ac4621a3e9416eb45011debe9670297e94b7eb6590e6cb8d88765dc9bcea47db82009ceaa6da33e30bd9f275654fc7119a + checksum: 10c1/7583f076e91373f73ac66dc27a06d60c3c717a17a2410873426707eb10abd01a32a6f11b6af866629d17f4bdb6bf9617276e3494bc85fdee7863e964d3b40429 languageName: node linkType: hard @@ -372,6 +382,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-simple-access@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-simple-access@npm:7.25.9" + dependencies: + "@babel/traverse": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + checksum: 10c1/5adfb9f1710b6404ce7b881b5cac0928f9d103d24cd4b006715ff7031c1cb9aeffb0a50c8ef7ba2adb9160fc36aed133c0c7ab8ab232f7709d62041b48db5f81 + languageName: node + linkType: hard + "@babel/helper-skip-transparent-expression-wrappers@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.25.9" @@ -451,14 +471,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.10, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.5, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.3": - version: 7.26.3 - resolution: "@babel/parser@npm:7.26.3" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.10, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.5, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.2": + version: 7.26.2 + resolution: "@babel/parser@npm:7.26.2" dependencies: - "@babel/types": "npm:^7.26.3" + "@babel/types": "npm:^7.26.0" bin: parser: ./bin/babel-parser.js - checksum: 10c1/2ab0aeaa0ce4cbbfa9d0e1ea271ebb8c26c2d8cbd9cf8e66b29e353e5d3038d2c50c2c075f50a5893517e906694934eebaf7f0f06b27dbcd1d3ca0c1163665b9 + checksum: 10c1/09a2b9441011e7f1ebfd5b5465e5392e3ea1e9a30703d6d01e846cb1c9088b7070d7c8d1f5c31ec9621d31c7aa75f5aca757e0bb39a3ed9e0b6262a41204e023 languageName: node linkType: hard @@ -945,13 +965,14 @@ __metadata: linkType: hard "@babel/plugin-transform-exponentiation-operator@npm:^7.25.9": - version: 7.26.3 - resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.26.3" + version: 7.25.9 + resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.25.9" dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor": "npm:^7.25.9" "@babel/helper-plugin-utils": "npm:^7.25.9" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c1/1b473469d2d264e8dc18ba514a5a3c692aa949e4aaaa2e36104aa660fb813993af8039c86b9978294d2197589927387d27e5850605e828abfc2d2c627a6177c1 + checksum: 10c1/8bae7a31fdcac489d4e7b5ee04eef06020956bda45a7e28c720c94c466286b6617fa7ee1ba42f46da60e7d38f54d955db78e6bd3992cfff1edccc0e5b7e9f027 languageName: node linkType: hard @@ -1048,14 +1069,15 @@ __metadata: linkType: hard "@babel/plugin-transform-modules-commonjs@npm:^7.25.9": - version: 7.26.3 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.26.3" + version: 7.25.9 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.25.9" dependencies: - "@babel/helper-module-transforms": "npm:^7.26.0" + "@babel/helper-module-transforms": "npm:^7.25.9" "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-simple-access": "npm:^7.25.9" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c1/57afda9ac0ce0b751276f1db556a11db4830389154e8fbaed4a0d1f5729fb79b91d36be9b0a576511428c2b470bba5c25e8b381b76d9b8cb15363f2b05886205 + checksum: 10c1/f5b9b742ff0a37e633faef0737c7ebc6e50f4faad8379d906f96a1d9f0795c5710ba9362b36f2c13821f23109a568090bd4e82a51269d5d8d05f5410c325420b languageName: node linkType: hard @@ -1382,8 +1404,8 @@ __metadata: linkType: hard "@babel/plugin-transform-typescript@npm:^7.25.9": - version: 7.26.3 - resolution: "@babel/plugin-transform-typescript@npm:7.26.3" + version: 7.25.9 + resolution: "@babel/plugin-transform-typescript@npm:7.25.9" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.25.9" "@babel/helper-create-class-features-plugin": "npm:^7.25.9" @@ -1392,7 +1414,7 @@ __metadata: "@babel/plugin-syntax-typescript": "npm:^7.25.9" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c1/a29087e28e1ffe902e0f18a5205563b5bf222f1b4d009e6350ab5c28c76c676fdbf0930f76bb5dc854590e576999946b4ee6f1dbe6debdf553032186f7de3b9a + checksum: 10c1/01607154ecfedee2f516ddca873979ca4fe86dde3de79c5b9890a53ab5e32daf6c46aa02dea4a88c0b1e113df4cdeae83b03ee82f481cf92c4488e3ac5d516d6 languageName: node linkType: hard @@ -1536,8 +1558,8 @@ __metadata: linkType: hard "@babel/preset-react@npm:^7.22.15": - version: 7.26.3 - resolution: "@babel/preset-react@npm:7.26.3" + version: 7.25.9 + resolution: "@babel/preset-react@npm:7.25.9" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.9" "@babel/helper-validator-option": "npm:^7.25.9" @@ -1547,7 +1569,7 @@ __metadata: "@babel/plugin-transform-react-pure-annotations": "npm:^7.25.9" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c1/7b290f61ab3f562ba8316239743e10043a287a23dda5c78153f5c8096756aa3367d72712d451343ebe708016a2ce8b9c1201eb4d5de4bfc8fc591d61f965424c + checksum: 10c1/b2cd7645c8ca4df9b5aa388addda8830c3a2d41aca81e56ab1b93af7579f3ea6efd76298473c59ed695b9a64f11e05c45384bbd9103c995de996b81f660bf8c6 languageName: node linkType: hard @@ -1620,17 +1642,17 @@ __metadata: linkType: hard "@babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.25.9": - version: 7.26.4 - resolution: "@babel/traverse@npm:7.26.4" + version: 7.25.9 + resolution: "@babel/traverse@npm:7.25.9" dependencies: - "@babel/code-frame": "npm:^7.26.2" - "@babel/generator": "npm:^7.26.3" - "@babel/parser": "npm:^7.26.3" + "@babel/code-frame": "npm:^7.25.9" + "@babel/generator": "npm:^7.25.9" + "@babel/parser": "npm:^7.25.9" "@babel/template": "npm:^7.25.9" - "@babel/types": "npm:^7.26.3" + "@babel/types": "npm:^7.25.9" debug: "npm:^4.3.1" globals: "npm:^11.1.0" - checksum: 10c1/fab8a5eced3de69f1dc0a624fa8aefdaf189916fd1c4dadd38d381d731e28509b2ce7df8d3400cf92752362d11be3598293a16422d922d9a6a75148de9b7bc23 + checksum: 10c1/ce115b3db715df503a988a8551f310d9de58637f7921d535fc88a5bb50f6a8b35ace080ab7d07607a636d308511baf4cb760f0c02700ece90ad819a0ead3d196 languageName: node linkType: hard @@ -1644,13 +1666,13 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.13.0, @babel/types@npm:^7.17.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.23.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.26.3, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": - version: 7.26.3 - resolution: "@babel/types@npm:7.26.3" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.13.0, @babel/types@npm:^7.17.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.23.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": + version: 7.26.0 + resolution: "@babel/types@npm:7.26.0" dependencies: "@babel/helper-string-parser": "npm:^7.25.9" "@babel/helper-validator-identifier": "npm:^7.25.9" - checksum: 10c1/fbdd43f50be74307be6d8ca04690224009f027cf94c6ecfca2dc98c1fde50b6cfbf117d189caadb0cc5a2355158edb741b6ece51a9d1cfb4c951eb71d7c54bce + checksum: 10c1/c8084e6daa15a5e2aa07cca00e8d31cc454985401063f882692946088d69b5d3b3193cef465b70b050b0f913824c6e40597817168f6e75fc32b78416d0fafcb9 languageName: node linkType: hard @@ -1703,8 +1725,8 @@ __metadata: linkType: hard "@cypress/request@npm:^3.0.6": - version: 3.0.7 - resolution: "@cypress/request@npm:3.0.7" + version: 3.0.6 + resolution: "@cypress/request@npm:3.0.6" dependencies: aws-sign2: "npm:~0.7.0" aws4: "npm:^1.8.0" @@ -1719,12 +1741,12 @@ __metadata: json-stringify-safe: "npm:~5.0.1" mime-types: "npm:~2.1.19" performance-now: "npm:^2.1.0" - qs: "npm:6.13.1" + qs: "npm:6.13.0" safe-buffer: "npm:^5.1.2" tough-cookie: "npm:^5.0.0" tunnel-agent: "npm:^0.6.0" uuid: "npm:^8.3.2" - checksum: 10c1/0da12c59d78def09a2b4d3f3c55980ba9f75b44f41865fe1b41d5e15f5811cfc305b3b1d207369c48a55a78af1776aa230dd70d27b87978d56d1e6a88ccd5b2e + checksum: 10c1/e309e417cb0430379ca507a596320c5c8d44f52b0e2900614977e633cfdc0bd1324bd1e3b626981b390b210bf89090c2791c5ac7e5ef7d82fcfc6c6a7a9db4ac languageName: node linkType: hard @@ -1745,35 +1767,35 @@ __metadata: languageName: node linkType: hard -"@emotion/babel-plugin@npm:^11.13.5": - version: 11.13.5 - resolution: "@emotion/babel-plugin@npm:11.13.5" +"@emotion/babel-plugin@npm:^11.12.0": + version: 11.12.0 + resolution: "@emotion/babel-plugin@npm:11.12.0" dependencies: "@babel/helper-module-imports": "npm:^7.16.7" "@babel/runtime": "npm:^7.18.3" "@emotion/hash": "npm:^0.9.2" "@emotion/memoize": "npm:^0.9.0" - "@emotion/serialize": "npm:^1.3.3" + "@emotion/serialize": "npm:^1.2.0" babel-plugin-macros: "npm:^3.1.0" convert-source-map: "npm:^1.5.0" escape-string-regexp: "npm:^4.0.0" find-root: "npm:^1.1.0" source-map: "npm:^0.5.7" stylis: "npm:4.2.0" - checksum: 10c1/85d19012974cb252f03378b157385fe43fc75c4935b55615729728c27d6148d175098004ccf157e10b1e659dcb11a5103db6c98952afc5356c52a4cc4ac5e892 + checksum: 10c1/8834cac40ab41f9b2fa8867b45f5bb035ad5cb36a0ff5a99d5b597f41482bc220f30abe4c340adf106d658c81ab249b0d64f61d92a749780c3e90cfc6474a296 languageName: node linkType: hard -"@emotion/cache@npm:^11.14.0, @emotion/cache@npm:^11.4.0": - version: 11.14.0 - resolution: "@emotion/cache@npm:11.14.0" +"@emotion/cache@npm:^11.13.0, @emotion/cache@npm:^11.4.0": + version: 11.13.1 + resolution: "@emotion/cache@npm:11.13.1" dependencies: "@emotion/memoize": "npm:^0.9.0" "@emotion/sheet": "npm:^1.4.0" - "@emotion/utils": "npm:^1.4.2" + "@emotion/utils": "npm:^1.4.0" "@emotion/weak-memoize": "npm:^0.4.0" stylis: "npm:4.2.0" - checksum: 10c1/841d7d6b39c93034f835d9b3c1f9f6807a6ebb89b9d3675cf9b2e74b1713c3a944549fd049a62ce78aca1a6a55bdd84f545e43375c5107b9ec73cf2ff913c8e4 + checksum: 10c1/12cccfa4c098ed905249b9506ebc04a8c4400e91c39fb3ea3375754cec7121b10f37de49112a76c70a0319d7328f2eb05f29229febe7755ac07f1c8e92a8d98c languageName: node linkType: hard @@ -1799,15 +1821,15 @@ __metadata: linkType: hard "@emotion/react@npm:^11.8.1": - version: 11.14.0 - resolution: "@emotion/react@npm:11.14.0" + version: 11.13.3 + resolution: "@emotion/react@npm:11.13.3" dependencies: "@babel/runtime": "npm:^7.18.3" - "@emotion/babel-plugin": "npm:^11.13.5" - "@emotion/cache": "npm:^11.14.0" - "@emotion/serialize": "npm:^1.3.3" - "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" - "@emotion/utils": "npm:^1.4.2" + "@emotion/babel-plugin": "npm:^11.12.0" + "@emotion/cache": "npm:^11.13.0" + "@emotion/serialize": "npm:^1.3.1" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.1.0" + "@emotion/utils": "npm:^1.4.0" "@emotion/weak-memoize": "npm:^0.4.0" hoist-non-react-statics: "npm:^3.3.1" peerDependencies: @@ -1815,20 +1837,20 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10c1/cca195cee7676269b570f9f2a398fa1126f7df072adf8275cc982c1533db9266f5d44aebe6d2f7e3945216e88fe5770e624a7f6291e80d8b2b84be05e06ee2a3 + checksum: 10c1/456d6d80a79cd8dda98b1cfcf0f9a517483bc003008da707cb143121cb48ebe5c2404e8599895fbd086c7ed96ede1bf6a29cde617e357777e73ad3a196ca5857 languageName: node linkType: hard -"@emotion/serialize@npm:^1.3.3": - version: 1.3.3 - resolution: "@emotion/serialize@npm:1.3.3" +"@emotion/serialize@npm:^1.2.0, @emotion/serialize@npm:^1.3.1": + version: 1.3.2 + resolution: "@emotion/serialize@npm:1.3.2" dependencies: "@emotion/hash": "npm:^0.9.2" "@emotion/memoize": "npm:^0.9.0" "@emotion/unitless": "npm:^0.10.0" - "@emotion/utils": "npm:^1.4.2" + "@emotion/utils": "npm:^1.4.1" csstype: "npm:^3.0.2" - checksum: 10c1/19a9f2520319fc134ce8b58899c2d8ec58ca35e86bc65747df93334eb4cdfefe316dc7ddc0c9cbbc5323afb7e2116456d77d48a91c7f598985c91cc6557b0003 + checksum: 10c1/d104b1ae2e08442639bc33a9a56b9c3a90886de9b53b1f1a9edf3e23688f828c337f620645a5082b79d8b7b48fa8f4a35924765df83bf18702ad0cd3af5a806a languageName: node linkType: hard @@ -1853,19 +1875,19 @@ __metadata: languageName: node linkType: hard -"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": - version: 1.2.0 - resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0" +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.1.0": + version: 1.1.0 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.1.0" peerDependencies: react: ">=16.8.0" - checksum: 10c1/06067764b7bee7551f17928f7e07c51d90511220c9b8458f838d721a61feb2f7f49ddbdba8a86e544d00d3b5e4b2db860b9d3fed0db48c3780d1c215fbc3a428 + checksum: 10c1/4b56507e0da111e2c782df26e7c3779043705cc3267b8ef4e77e677cf9c00a60452e8b78ee1df59aea2088cec9fddf3ba444b47a8d6a288edf8a7e7b50d82086 languageName: node linkType: hard -"@emotion/utils@npm:^1.4.2": - version: 1.4.2 - resolution: "@emotion/utils@npm:1.4.2" - checksum: 10c1/b493d89ebbca16008013e47cc2ad5f894cc426252b14ce15b02c5b0b4eaecee4aa482ec6cc95a0d24f3ce9151bf3b8db4380a75fe485ba877074b13a16612c72 +"@emotion/utils@npm:^1.4.0, @emotion/utils@npm:^1.4.1": + version: 1.4.1 + resolution: "@emotion/utils@npm:1.4.1" + checksum: 10c1/43799d739b9aaaa71c181f67fe943b22b5af0e3748a708592cebc3cb5879108e5c457fc54822f22bb80e9ab0a15a185d36f9ee8d77f2b6fb9fa0c046f03f5c3a languageName: node linkType: hard @@ -1876,6 +1898,167 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/aix-ppc64@npm:0.21.5" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm64@npm:0.21.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm@npm:0.21.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-x64@npm:0.21.5" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-arm64@npm:0.21.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-x64@npm:0.21.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-arm64@npm:0.21.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-x64@npm:0.21.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm64@npm:0.21.5" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm@npm:0.21.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ia32@npm:0.21.5" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-loong64@npm:0.21.5" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-mips64el@npm:0.21.5" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ppc64@npm:0.21.5" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-riscv64@npm:0.21.5" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-s390x@npm:0.21.5" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-x64@npm:0.21.5" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/netbsd-x64@npm:0.21.5" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/openbsd-x64@npm:0.21.5" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/sunos-x64@npm:0.21.5" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-arm64@npm:0.21.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-ia32@npm:0.21.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-x64@npm:0.21.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0": version: 4.4.1 resolution: "@eslint-community/eslint-utils@npm:4.4.1" @@ -2015,15 +2198,6 @@ __metadata: languageName: node linkType: hard -"@isaacs/fs-minipass@npm:^4.0.0": - version: 4.0.1 - resolution: "@isaacs/fs-minipass@npm:4.0.1" - dependencies: - minipass: "npm:^7.0.4" - checksum: 10c1/f67deb88004e211751eb8f70d958848fd86878fba64ba3b323749022be88e2f66e6a573d9fbb4cacb6551f9c9be4b5e6ea2c484187192a8578f8b48762d17a4b - languageName: node - linkType: hard - "@istanbuljs/load-nyc-config@npm:^1.0.0": version: 1.1.0 resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" @@ -2346,8 +2520,8 @@ __metadata: linkType: hard "@jsonjoy.com/json-pack@npm:^1.0.3": - version: 1.1.1 - resolution: "@jsonjoy.com/json-pack@npm:1.1.1" + version: 1.1.0 + resolution: "@jsonjoy.com/json-pack@npm:1.1.0" dependencies: "@jsonjoy.com/base64": "npm:^1.1.1" "@jsonjoy.com/util": "npm:^1.1.2" @@ -2355,7 +2529,7 @@ __metadata: thingies: "npm:^1.20.0" peerDependencies: tslib: 2 - checksum: 10c1/e293adbf3f7118821bf8e5b8dac4b88a4321d7b47e6f756fba23eef18555b2077f55e5e6bd560d488176a1fd891664558790181f5dfc6d48ef6cbf332e4e29e4 + checksum: 10c1/319627f8c28769943274e93c1bc82375335b5bfc06b9359e35e1766f04b680fbd5ff8c7e317a92b73cfbb64a9c1e630158fec2eb56836e4285bc46451a125cae languageName: node linkType: hard @@ -2416,16 +2590,16 @@ __metadata: languageName: node linkType: hard -"@npmcli/agent@npm:^3.0.0": - version: 3.0.0 - resolution: "@npmcli/agent@npm:3.0.0" +"@npmcli/agent@npm:^2.0.0": + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" dependencies: agent-base: "npm:^7.1.0" http-proxy-agent: "npm:^7.0.0" https-proxy-agent: "npm:^7.0.1" lru-cache: "npm:^10.0.1" socks-proxy-agent: "npm:^8.0.3" - checksum: 10c1/490f877dba09d99feb438bcecbc2598c4a3c35bd9c54894d3b1123c7675bb5166804431e7016e9fb2896a616c87acaa6d47f57e1e254b77b6d7079e08a2af7e7 + checksum: 10c1/e775f1ee14038147a9d23984452b253e63970323e6b42ddb399138fb9328959a3eb192d7b3760b7add9a5ac90cfd7a8d3892e0f7b7c2eea5ae8ef394e9b4ff86 languageName: node linkType: hard @@ -2439,12 +2613,12 @@ __metadata: languageName: node linkType: hard -"@npmcli/fs@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/fs@npm:4.0.0" +"@npmcli/fs@npm:^3.1.0": + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" dependencies: semver: "npm:^7.3.5" - checksum: 10c1/ebeb013e6f5b29cd527ba5a1083ac32d7a106649735b62419fe3c9a2ec446e334c6d33a09b240a8ac6f43cbbf62c32affdfc6693952d49e1fe09366df660b7ff + checksum: 10c1/e80ac30f15c26c9d31695fc10316b27ee2391a39bf4a6bd67fe342b21001e7b5446242b903dcc36cdcec34c3841f9fbeea92b9b16cb41bbc9ee8210c876cfa39 languageName: node linkType: hard @@ -2458,7 +2632,7 @@ __metadata: languageName: node linkType: hard -"@openreplay/sourcemap-uploader@npm:^3.0.8": +"@openreplay/sourcemap-uploader@npm:^3.0.10": version: 3.0.13 resolution: "@openreplay/sourcemap-uploader@npm:3.0.13" dependencies: @@ -2727,19 +2901,19 @@ __metadata: linkType: hard "@rc-component/trigger@npm:^2.0.0, @rc-component/trigger@npm:^2.1.1, @rc-component/trigger@npm:^2.2.5": - version: 2.2.6 - resolution: "@rc-component/trigger@npm:2.2.6" + version: 2.2.5 + resolution: "@rc-component/trigger@npm:2.2.5" dependencies: "@babel/runtime": "npm:^7.23.2" "@rc-component/portal": "npm:^1.1.0" classnames: "npm:^2.3.2" rc-motion: "npm:^2.0.0" rc-resize-observer: "npm:^1.3.1" - rc-util: "npm:^5.44.0" + rc-util: "npm:^5.38.0" peerDependencies: react: ">=16.9.0" react-dom: ">=16.9.0" - checksum: 10c1/ead7c8cd270f9eb48bc4bebf421006228f51b1d83f460c176171251c0178a30cd005a19d529ac4daf795c787a8195b8a5a6118d197463431feeab2c99fa6ef26 + checksum: 10c1/a063256fad408fc45c3406c819c6fc6bc45c46886e48e0bcb78d445eb2d5eb8cf85b2c131250fe9d33aece67176e56833e7297be7e78c9386e3bffbb92ec1c31 languageName: node linkType: hard @@ -2888,21 +3062,21 @@ __metadata: languageName: node linkType: hard -"@tanstack/query-core@npm:5.62.3": - version: 5.62.3 - resolution: "@tanstack/query-core@npm:5.62.3" - checksum: 10c1/ce69f853ce2c60a2014cf7029408fe3e77f57daa253dd18863e563ec7778a03983c00dc56e0dbcda677f04de3a34a0ae3048b35406358937a778a69237d9514a +"@tanstack/query-core@npm:5.59.20": + version: 5.59.20 + resolution: "@tanstack/query-core@npm:5.59.20" + checksum: 10c1/bfac064d0344ab32e2718c1f75552072283793780f9e22a7fb3c84d0bb560de3311c8b9c33505a20904ade136acba74f9f56f837297dbb1619f2db179b27d4da languageName: node linkType: hard "@tanstack/react-query@npm:^5.56.2": - version: 5.62.3 - resolution: "@tanstack/react-query@npm:5.62.3" + version: 5.60.2 + resolution: "@tanstack/react-query@npm:5.60.2" dependencies: - "@tanstack/query-core": "npm:5.62.3" + "@tanstack/query-core": "npm:5.59.20" peerDependencies: react: ^18 || ^19 - checksum: 10c1/8eac6bb880bf205c6775c3be297f8253c9a66231c72d62044d1db416256f7b7300862f62af09a3ba36939c53ea702d0e4f862f14d074f02fb67bf7444231e884 + checksum: 10c1/a3f01498078002dddb4dcca09ae21b0cb33e6923bc54a4d5c4b8426d50bff6c1e566346180f0f7b51bdd88d7ed33311936dee09c8d24258fe871591e3558a0d4 languageName: node linkType: hard @@ -3103,9 +3277,9 @@ __metadata: linkType: hard "@types/d3-time@npm:*, @types/d3-time@npm:^3.0.0": - version: 3.0.4 - resolution: "@types/d3-time@npm:3.0.4" - checksum: 10c1/ecc4bc8d0122817af0183966a5db1dbb885b7b75349d5353217287d8eaae05f44eb5142a7fb33f640d8085d4e62c1835f541dc34330561d599b5eba8cff87799 + version: 3.0.3 + resolution: "@types/d3-time@npm:3.0.3" + checksum: 10c1/cae0f10f22bca9eccd352db405c3f91a6fe148e3b553ab6a84704226a487b950ecdb63cfc219dbf89eb7943c2af1179f9e6bc356358ea3f55386b41ee0639ca9 languageName: node linkType: hard @@ -3144,14 +3318,14 @@ __metadata: linkType: hard "@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^5.0.0": - version: 5.0.2 - resolution: "@types/express-serve-static-core@npm:5.0.2" + version: 5.0.1 + resolution: "@types/express-serve-static-core@npm:5.0.1" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" "@types/send": "npm:*" - checksum: 10c1/80160a71bdc7891070611ce32aff53eb9e264d9a56d7dc522860cb2c40f0a8bb804fccb24c8ce6e58d815a0fd437f56bfed96a8f69f7ffec89b74f4bea6e4e19 + checksum: 10c1/8d74d22a47d00f9cc1835ab2c2f97cfae4a8688d317754148d9855e1bfc70a69bbcf459864dc2ff87d0b4d8a1bfc2589933a91a38ed4bf54389d96b0b2425575 languageName: node linkType: hard @@ -3297,11 +3471,11 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:^22.7.8": - version: 22.10.1 - resolution: "@types/node@npm:22.10.1" + version: 22.9.0 + resolution: "@types/node@npm:22.9.0" dependencies: - undici-types: "npm:~6.20.0" - checksum: 10c1/e553191da1352052ba68f8226847efec0098c2ff03c3320ceeb1c1b809e4af5c22c9bfa7c68db9ad4e1aadaab29d1e3166b0a0e8e5dfe3985f893c3ac19536e3 + undici-types: "npm:~6.19.8" + checksum: 10c1/eb486fcb843a2cc0667ba45a409cc6974f3607d4389c1429dca956b07352d6649e44dc85c0582af386c77e99146173f63c2b8d43cf91c7ab0815d01beea83e3e languageName: node linkType: hard @@ -3327,9 +3501,9 @@ __metadata: linkType: hard "@types/prop-types@npm:*": - version: 15.7.14 - resolution: "@types/prop-types@npm:15.7.14" - checksum: 10c1/7b1baa6b13a9df252daa1e88001e75e3595875f349b791aa423ceedebf4a8e05b4ebae07bcb182350add3a9180de6adb8103d9cf4a893056ccd39cde72886073 + version: 15.7.13 + resolution: "@types/prop-types@npm:15.7.13" + checksum: 10c1/1e0412f9198eec64f60ddad1fc3138810da1530b927fa0d6ac766ebb1a8ba8f0eb538d1f300aaab63284fcf5780d9cde0e71f3caa418ca3896aa756cd578a474 languageName: node linkType: hard @@ -3364,11 +3538,11 @@ __metadata: linkType: hard "@types/react-dom@npm:^18.0.4": - version: 18.3.3 - resolution: "@types/react-dom@npm:18.3.3" - peerDependencies: - "@types/react": ^18.0.0 - checksum: 10c1/503728fdcbf22ef82cdb499e0708fb5cc53c62e3bd328da26a652736b54d2e2232f8ae29b2bf59a8bcbcd9d30023c6aa608c2ce9f550be99705d7def0576a755 + version: 18.3.1 + resolution: "@types/react-dom@npm:18.3.1" + dependencies: + "@types/react": "npm:*" + checksum: 10c1/b3edc672be011854e609c8d15ecabd2279cafd7a22b4ec1a03d770403d503dedbb3ad4bbfdd75592f3436e25cc0f7e90de3f4788b6e5179a40b5010531c8e4d1 languageName: node linkType: hard @@ -3403,31 +3577,22 @@ __metadata: linkType: hard "@types/react-virtualized@npm:^9.21.21": - version: 9.22.0 - resolution: "@types/react-virtualized@npm:9.22.0" + version: 9.21.30 + resolution: "@types/react-virtualized@npm:9.21.30" dependencies: "@types/prop-types": "npm:*" "@types/react": "npm:*" - checksum: 10c1/bc28a9fc43e11c3f7b4de3af55bcbc39fa5f117aa2f3487a011ef60a636beb21c80444306e10928aa00d613a5def193bc128e39cff2f679ff6b911c8bb290b9a + checksum: 10c1/7c745a62e092be4d5b7d37bd180f0152a92ff7903080fb24c9f5c97559836a5346e0e9bd0a27e2f789f0fdb63b76ab029902181aef0e7c2975ff7b92f6697a5b languageName: node linkType: hard -"@types/react@npm:*": - version: 19.0.1 - resolution: "@types/react@npm:19.0.1" - dependencies: - csstype: "npm:^3.0.2" - checksum: 10c1/ff8a71222cf009a7497be0d2b1cea969abbc8ac1ad87104aa3158ecb2219346f0d5cd456a3c3c37ada58cd771a7f2e49502a56b0bb7d1dc8615562531b99d361 - languageName: node - linkType: hard - -"@types/react@npm:^18.0.9": - version: 18.3.14 - resolution: "@types/react@npm:18.3.14" +"@types/react@npm:*, @types/react@npm:^18.0.9": + version: 18.3.12 + resolution: "@types/react@npm:18.3.12" dependencies: "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10c1/0dd554766df4aff48e42847342fe0b6268db9cc468630912326b776ceb531136e4d35b8553a0e312470a2077422db05eafdb131c017820e373bbbe41d4fb67b0 + checksum: 10c1/1a0cdc87a013f1f4481b522509a2db1b6e89273e0c0d09d995b7bca9585fe705bfd157e898709b3bc5194ac8057aaae04fada43e818de52e74158dea76a19311 languageName: node linkType: hard @@ -3668,13 +3833,13 @@ __metadata: linkType: hard "@ungap/structured-clone@npm:^1.2.0": - version: 1.2.1 - resolution: "@ungap/structured-clone@npm:1.2.1" - checksum: 10c1/8d8a388a528c79daa030561f1673e9f4103b5f55e5c8179703d1219d68729f3578aeb85c7eba503bf208ed294f2af4008561e3d0c9b37acad9c069fd68e5c5f3 + version: 1.2.0 + resolution: "@ungap/structured-clone@npm:1.2.0" + checksum: 10c1/02bcb6b139ec19c2f92e07482763d1e397f6927af89ab2e8d46e499d877c3700be25e16c36e6d8b4c8e0e3ef697ea15638bd4a1163447a50fb3c4da13ab86d50 languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.14.1": +"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.12.1": version: 1.14.1 resolution: "@webassemblyjs/ast@npm:1.14.1" dependencies: @@ -3760,7 +3925,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:^1.14.1": +"@webassemblyjs/wasm-edit@npm:^1.12.1": version: 1.14.1 resolution: "@webassemblyjs/wasm-edit@npm:1.14.1" dependencies: @@ -3801,7 +3966,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.14.1": +"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.12.1": version: 1.14.1 resolution: "@webassemblyjs/wasm-parser@npm:1.14.1" dependencies: @@ -3999,10 +4164,12 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": - version: 7.1.3 - resolution: "agent-base@npm:7.1.3" - checksum: 10c1/9320ee56f96a3fb421d578636eadab33b4a085b3b950e52c6a4b09205db051e263105f14678433fa95f8bd2b890f2277901fc9bd0400935b92742881ee48be57 +"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" + dependencies: + debug: "npm:^4.3.4" + checksum: 10c1/eec6ff5a5e2ab816d1160151055c42cacc44fe738118cbdc405f5158968e47edc856f8f27b344a370aacf32a2926feec53103f5ec80c4b2da86a15bd093fc460 languageName: node linkType: hard @@ -4169,13 +4336,13 @@ __metadata: linkType: hard "antd@npm:^5.21.2": - version: 5.22.4 - resolution: "antd@npm:5.22.4" + version: 5.22.1 + resolution: "antd@npm:5.22.1" dependencies: "@ant-design/colors": "npm:^7.1.0" "@ant-design/cssinjs": "npm:^1.21.1" - "@ant-design/cssinjs-utils": "npm:^1.1.3" - "@ant-design/icons": "npm:^5.5.2" + "@ant-design/cssinjs-utils": "npm:^1.1.1" + "@ant-design/icons": "npm:^5.5.1" "@ant-design/react-slick": "npm:~1.1.2" "@babel/runtime": "npm:^7.25.7" "@ctrl/tinycolor": "npm:^3.6.1" @@ -4193,38 +4360,38 @@ __metadata: rc-dialog: "npm:~9.6.0" rc-drawer: "npm:~7.2.0" rc-dropdown: "npm:~4.2.0" - rc-field-form: "npm:~2.6.0" + rc-field-form: "npm:~2.5.0" rc-image: "npm:~7.11.0" - rc-input: "npm:~1.6.4" + rc-input: "npm:~1.6.3" rc-input-number: "npm:~9.3.0" rc-mentions: "npm:~2.17.0" rc-menu: "npm:~9.16.0" rc-motion: "npm:^2.9.3" rc-notification: "npm:~5.6.2" rc-pagination: "npm:~4.3.0" - rc-picker: "npm:~4.8.3" + rc-picker: "npm:~4.8.0" rc-progress: "npm:~4.0.0" rc-rate: "npm:~2.13.0" rc-resize-observer: "npm:^1.4.0" rc-segmented: "npm:~2.5.0" - rc-select: "npm:~14.16.4" + rc-select: "npm:~14.16.3" rc-slider: "npm:~11.1.7" rc-steps: "npm:~6.0.1" rc-switch: "npm:~4.1.0" - rc-table: "npm:~7.49.0" + rc-table: "npm:~7.48.1" rc-tabs: "npm:~15.4.0" rc-textarea: "npm:~1.8.2" rc-tooltip: "npm:~6.2.1" rc-tree: "npm:~5.10.1" - rc-tree-select: "npm:~5.24.5" + rc-tree-select: "npm:~5.24.4" rc-upload: "npm:~4.8.1" - rc-util: "npm:^5.44.0" + rc-util: "npm:^5.43.0" scroll-into-view-if-needed: "npm:^3.1.0" throttle-debounce: "npm:^5.0.2" peerDependencies: react: ">=16.9.0" react-dom: ">=16.9.0" - checksum: 10c1/98c3d734a07af8d73a1080db36d32f9561f5cb12f7333ef4de9707708989049746e04632ae13856dae4edb25104b6573f064da587759d1f958199059d7ee223c + checksum: 10c1/fed6ca40f5e8a84a9ecfbde160051c4fd5af3c54dfcc5b82ee522914148d4d7acc221c44fc2d34e1dacfcd5c873a0ac741b0f9c7b2441a4a9b9c7d206a703dcb languageName: node linkType: hard @@ -4802,12 +4969,12 @@ __metadata: linkType: hard "bonjour-service@npm:^1.2.1": - version: 1.3.0 - resolution: "bonjour-service@npm:1.3.0" + version: 1.2.1 + resolution: "bonjour-service@npm:1.2.1" dependencies: fast-deep-equal: "npm:^3.1.3" multicast-dns: "npm:^7.2.5" - checksum: 10c1/c6e6c7a15df4aa4c932ec9df2eb301e38aaecd561151a384181863866d357074a69240280be8210828995ac5262fe82721af46ac1725c89c0c84db2b75a9ee06 + checksum: 10c1/5a3415004efa60ce805845a18fb7b2232097b708cbba21e5432612dbe6a5912013fac9e30f27fa80d0804975e9f20fa22d61e82957d1d171b648cf0967e3336d languageName: node linkType: hard @@ -4960,11 +5127,11 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^19.0.1": - version: 19.0.1 - resolution: "cacache@npm:19.0.1" +"cacache@npm:^18.0.0": + version: 18.0.4 + resolution: "cacache@npm:18.0.4" dependencies: - "@npmcli/fs": "npm:^4.0.0" + "@npmcli/fs": "npm:^3.1.0" fs-minipass: "npm:^3.0.0" glob: "npm:^10.2.2" lru-cache: "npm:^10.0.1" @@ -4972,11 +5139,11 @@ __metadata: minipass-collect: "npm:^2.0.1" minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^7.0.2" - ssri: "npm:^12.0.0" - tar: "npm:^7.4.3" - unique-filename: "npm:^4.0.0" - checksum: 10c1/1e6298a5105ec34cb89daafffb914013fd22d76edc27a127f6e8ef809ffa0807f5c5fa789fcd84fec1285dc2c3fb1450f0f1b87331a3b0025fab4a5ae3be12aa + p-map: "npm:^4.0.0" + ssri: "npm:^10.0.0" + tar: "npm:^6.1.11" + unique-filename: "npm:^3.0.0" + checksum: 10c1/fbc78bf9021c956a7a4d11b3694dc616cde17e049a3efea732b4704335545967e9829849f349c282bd21037a4f96954e67af8639f145a521d503f5d093587cb3 languageName: node linkType: hard @@ -4994,25 +5161,16 @@ __metadata: languageName: node linkType: hard -"call-bind-apply-helpers@npm:^1.0.0": - version: 1.0.1 - resolution: "call-bind-apply-helpers@npm:1.0.1" +"call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7": + version: 1.0.7 + resolution: "call-bind@npm:1.0.7" dependencies: + es-define-property: "npm:^1.0.0" es-errors: "npm:^1.3.0" function-bind: "npm:^1.1.2" - checksum: 10c1/bbdd27f1ac5aaf265f373f2549fde9550696609bea306e0b7363159ff118eda1d6996a9ffd60b0fe00c9ff3f223b352a2fc4c2b788406d05138a3a4036113a97 - languageName: node - linkType: hard - -"call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": - version: 1.0.8 - resolution: "call-bind@npm:1.0.8" - dependencies: - call-bind-apply-helpers: "npm:^1.0.0" - es-define-property: "npm:^1.0.0" get-intrinsic: "npm:^1.2.4" - set-function-length: "npm:^1.2.2" - checksum: 10c1/f9c349071f07c3349a1d7c114de056e0f8b58f4ea4df0673946308b1bd2809c43cf4cab4625d9a7e197868a60cbe13f36de5fc651e246e49691404ad655d53e5 + set-function-length: "npm:^1.2.1" + checksum: 10c1/f2c7c2fa6a182764ea2f4f2abea9d2cdf580530c7ad51300900b39d756df21515dc352f95408c75ed45e6472932d95ac0836217e7d4c54701ca923be26f20b59 languageName: node linkType: hard @@ -5067,9 +5225,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001646, caniuse-lite@npm:^1.0.30001669": - version: 1.0.30001687 - resolution: "caniuse-lite@npm:1.0.30001687" - checksum: 10c1/f16ab4d63d14feee724a58d51e72efc451cdb20672d9853d8a9a57a63e177561b9b172c8ff27115acf4636c26a94858bb42a4c54f7ef8cfecd9bc58e383833cf + version: 1.0.30001680 + resolution: "caniuse-lite@npm:1.0.30001680" + checksum: 10c1/6ae408ba5d2380570909969246d538c9808b841a5f363984a41795fcd5bfb37291f0135c43a0201aefcbf2bc3b4c5e5e8cf5ce5a68859fd229e922c300634baa languageName: node linkType: hard @@ -5179,13 +5337,6 @@ __metadata: languageName: node linkType: hard -"chownr@npm:^3.0.0": - version: 3.0.0 - resolution: "chownr@npm:3.0.0" - checksum: 10c1/fb6222329bc781e8599e7b7942b0b728ec35cae7df7300b5372abb77037b833e7bc38f120e8e5b66ff6831213fae21e7cda7e870224edc047415332f70922060 - languageName: node - linkType: hard - "chroma-js@npm:^2.4.2": version: 2.6.0 resolution: "chroma-js@npm:2.6.0" @@ -5740,26 +5891,26 @@ __metadata: linkType: hard "cross-spawn@npm:^6.0.0": - version: 6.0.6 - resolution: "cross-spawn@npm:6.0.6" + version: 6.0.5 + resolution: "cross-spawn@npm:6.0.5" dependencies: nice-try: "npm:^1.0.4" path-key: "npm:^2.0.1" semver: "npm:^5.5.0" shebang-command: "npm:^1.2.0" which: "npm:^1.2.9" - checksum: 10c1/aa1908a9201525d1fb0a67f0d0d5c1abd26fc6e052a05da243c8320d0bf5f4711174682271ea030a547c10202c7ef72af3f7db0c47dfb94132213697fb370412 + checksum: 10c1/ea71fd2b6b32cb716f2fb2768377b7cf531bffec212be25b04d6826af95d6973dcb9fb8ee056932e32549a2b5b07d632f0fe9ec93d1ebf2ec66b10903b15848d languageName: node linkType: hard "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": - version: 7.0.6 - resolution: "cross-spawn@npm:7.0.6" + version: 7.0.5 + resolution: "cross-spawn@npm:7.0.5" dependencies: path-key: "npm:^3.1.0" shebang-command: "npm:^2.0.0" which: "npm:^2.0.1" - checksum: 10c1/16d66c65e6e190a063cd75a3a90fd8396a843cb9151e862f28fd952ca4ca6d8821e4d44e0cbd455c20627993ae6c903130928d6c0e6ed2ae88534444f1c16d86 + checksum: 10c1/c5f8aff024602dfd280da19f91065d42c3b472cf9ef275dce1b640648b06420a110072fffd379910c07d8d18b1f81f9eb3ffbc4f069cde6586605445dc609cb9 languageName: node linkType: hard @@ -6020,8 +6171,8 @@ __metadata: linkType: hard "cypress@npm:^13.3.0": - version: 13.16.1 - resolution: "cypress@npm:13.16.1" + version: 13.15.2 + resolution: "cypress@npm:13.15.2" dependencies: "@cypress/request": "npm:^3.0.6" "@cypress/xvfb": "npm:^1.2.4" @@ -6068,7 +6219,7 @@ __metadata: yauzl: "npm:^2.10.0" bin: cypress: bin/cypress - checksum: 10c1/7121f14422ee0d778322d8e1c86cfa238861c5c42c28343ed117a960ac23495bd6acd3688aba3bd74407401ede8ecce68db6f2b48b2533d40ddae5028fb99e69 + checksum: 10c1/e0a23aafc6a7ab882b3396351387843de4cdeb68f83e829a96bbe8b954c398a943e7f71e201272c61a3c66eb3f704b5efb63876d319b9b83fe556e9e4bf50e16 languageName: node linkType: hard @@ -6241,15 +6392,15 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": - version: 4.4.0 - resolution: "debug@npm:4.4.0" +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.1, debug@npm:~4.3.2": + version: 4.3.7 + resolution: "debug@npm:4.3.7" dependencies: ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10c1/453947a63c91afc0278f56546679a7d4235d27484b3ca6ea13109b88101572f53be3e6bfbd54cb328d4a91864b9dd2da81b4f929caa40b0b39afd5c2d961c4e2 + checksum: 10c1/48b9d420654f9c1a7e7caef48efa0cc6e275e6a72e7ac2a594c4e2390b6bce011de281f8fb73dc659b6bb815ad2d1ff449779d9395adab16df4d614edf2837eb languageName: node linkType: hard @@ -6262,18 +6413,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:~4.3.1, debug@npm:~4.3.2": - version: 4.3.7 - resolution: "debug@npm:4.3.7" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c1/48b9d420654f9c1a7e7caef48efa0cc6e275e6a72e7ac2a594c4e2390b6bce011de281f8fb73dc659b6bb815ad2d1ff449779d9395adab16df4d614edf2837eb - languageName: node - linkType: hard - "decimal.js-light@npm:^2.4.1": version: 2.5.1 resolution: "decimal.js-light@npm:2.5.1" @@ -6627,9 +6766,9 @@ __metadata: linkType: hard "dompurify@npm:^2.5.4": - version: 2.5.8 - resolution: "dompurify@npm:2.5.8" - checksum: 10c1/0f108b9523b9f7a59883a9c65f2936973e15c67fbe4c01510109a8d523e47a74ddd5e0d0684c4bbdba96ac4c7fc3186da2f60c1451825ae7d80f238ee1e08f36 + version: 2.5.7 + resolution: "dompurify@npm:2.5.7" + checksum: 10c1/29ece66863662362952ba304ec0e28de8a20cc0000b80c752152d6d70127a7a2c91e50f04347f505c259adcb7451253cdd46ab503a4ac68e32f0c518050dee39 languageName: node linkType: hard @@ -6682,17 +6821,6 @@ __metadata: languageName: node linkType: hard -"dunder-proto@npm:^1.0.0": - version: 1.0.0 - resolution: "dunder-proto@npm:1.0.0" - dependencies: - call-bind-apply-helpers: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.2.0" - checksum: 10c1/4a10f93a6615309e19cf7e53c4430f685c3b6c7987c75fa2a3032bf9e9f3cb827059a8f519fb9cd4ee5e995fd85830efacb7d950935525cad717dd1dfbdf920d - languageName: node - linkType: hard - "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -6729,9 +6857,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.5.41": - version: 1.5.72 - resolution: "electron-to-chromium@npm:1.5.72" - checksum: 10c1/d14f577c54b79d9b5352ff990e89ad2636ef44f007dd8321313bced2002ade910492fe33641925b374915e8c26824ea332a2354a0c2240e113aeb9d16a14fe6f + version: 1.5.60 + resolution: "electron-to-chromium@npm:1.5.60" + checksum: 10c1/5677c9062d1b577afa920d7f9e401270d44d19b5791b51044f174f8218aa3cb2af3e39be422d544687110fe70edbe3da60ba832a1a930d4bf99a7ff3f7064964 languageName: node linkType: hard @@ -6888,7 +7016,7 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.22.1, es-abstract@npm:^1.22.3, es-abstract@npm:^1.23.0, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5": +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.22.1, es-abstract@npm:^1.22.3, es-abstract@npm:^1.23.0, es-abstract@npm:^1.23.1, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3": version: 1.23.5 resolution: "es-abstract@npm:1.23.5" dependencies: @@ -6949,10 +7077,12 @@ __metadata: languageName: node linkType: hard -"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": - version: 1.0.1 - resolution: "es-define-property@npm:1.0.1" - checksum: 10c1/bd04c5c1f042ecf90e6e7932158bc8c1b26c80f73566a99d7d3f39c7bf2831e39b2f8e21699eb1b1e713cee07c4ff7912ceb74c2343854311450d5cc697e9ffc +"es-define-property@npm:^1.0.0": + version: 1.0.0 + resolution: "es-define-property@npm:1.0.0" + dependencies: + get-intrinsic: "npm:^1.2.4" + checksum: 10c1/c8e1be7746127d1f4c914dc1ff7d3816ca3aaa85b6524b1026813380d0318a0a0d075b680e1ef2f4b7ad38adecd305119c92bf25a791266dc019e46034672892 languageName: node linkType: hard @@ -7023,13 +7153,107 @@ __metadata: linkType: hard "es-to-primitive@npm:^1.2.1": - version: 1.3.0 - resolution: "es-to-primitive@npm:1.3.0" + version: 1.2.1 + resolution: "es-to-primitive@npm:1.2.1" dependencies: - is-callable: "npm:^1.2.7" - is-date-object: "npm:^1.0.5" - is-symbol: "npm:^1.0.4" - checksum: 10c1/dedafc56d21356c25c155baae88e90f001dc9e9759e3e471bb49e123bb6290e7dca21456b6b9f1a3a0213c3d58d0431fe669b76db016679c1a467b495ef37f61 + is-callable: "npm:^1.1.4" + is-date-object: "npm:^1.0.1" + is-symbol: "npm:^1.0.2" + checksum: 10c1/61077c934cf30fde5c0ccc42b8c270d28f14371cb7a0bacc657db6637c15b7e766d9e756e9d6c3b5692233d3d21e8242f9042f1be33d815762afa02261318ff4 + languageName: node + linkType: hard + +"esbuild-loader@npm:^4.2.2": + version: 4.2.2 + resolution: "esbuild-loader@npm:4.2.2" + dependencies: + esbuild: "npm:^0.21.0" + get-tsconfig: "npm:^4.7.0" + loader-utils: "npm:^2.0.4" + webpack-sources: "npm:^1.4.3" + peerDependencies: + webpack: ^4.40.0 || ^5.0.0 + checksum: 10c1/2e29724312e75ffdb06d6421536078f36c135e9d8563bd413d78b061bf24459e06661f6baadd11a7f8e630e22abdf0d9d18921d51460bd2eeee47e000e29fd17 + languageName: node + linkType: hard + +"esbuild@npm:^0.21.0": + version: 0.21.5 + resolution: "esbuild@npm:0.21.5" + dependencies: + "@esbuild/aix-ppc64": "npm:0.21.5" + "@esbuild/android-arm": "npm:0.21.5" + "@esbuild/android-arm64": "npm:0.21.5" + "@esbuild/android-x64": "npm:0.21.5" + "@esbuild/darwin-arm64": "npm:0.21.5" + "@esbuild/darwin-x64": "npm:0.21.5" + "@esbuild/freebsd-arm64": "npm:0.21.5" + "@esbuild/freebsd-x64": "npm:0.21.5" + "@esbuild/linux-arm": "npm:0.21.5" + "@esbuild/linux-arm64": "npm:0.21.5" + "@esbuild/linux-ia32": "npm:0.21.5" + "@esbuild/linux-loong64": "npm:0.21.5" + "@esbuild/linux-mips64el": "npm:0.21.5" + "@esbuild/linux-ppc64": "npm:0.21.5" + "@esbuild/linux-riscv64": "npm:0.21.5" + "@esbuild/linux-s390x": "npm:0.21.5" + "@esbuild/linux-x64": "npm:0.21.5" + "@esbuild/netbsd-x64": "npm:0.21.5" + "@esbuild/openbsd-x64": "npm:0.21.5" + "@esbuild/sunos-x64": "npm:0.21.5" + "@esbuild/win32-arm64": "npm:0.21.5" + "@esbuild/win32-ia32": "npm:0.21.5" + "@esbuild/win32-x64": "npm:0.21.5" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c1/1bed0f5871043244bc2033f323a7e86e06d808df955b47bc5579bb3855d107b67f0adace7d4c747deea856cfc7bb798c3320a4b96ebb832112c915f377b5c9ed languageName: node linkType: hard @@ -7370,8 +7594,8 @@ __metadata: linkType: hard "express@npm:^4.19.2": - version: 4.21.2 - resolution: "express@npm:4.21.2" + version: 4.21.1 + resolution: "express@npm:4.21.1" dependencies: accepts: "npm:~1.3.8" array-flatten: "npm:1.1.1" @@ -7392,7 +7616,7 @@ __metadata: methods: "npm:~1.1.2" on-finished: "npm:2.4.1" parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.12" + path-to-regexp: "npm:0.1.10" proxy-addr: "npm:~2.0.7" qs: "npm:6.13.0" range-parser: "npm:~1.2.1" @@ -7404,7 +7628,7 @@ __metadata: type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 10c1/cd6d283c83304a50a8dea63a77cd5144042d41e2b4315a5372dc7efdd95024141131be8ac0ca92d3bc96a96586a6b0579ccbe77ae0d5b31d9ac47503793ed3ea + checksum: 10c1/05168a726f92f21e95b5f04ac9e5ff42d42da914c9c094d1037dc19828455804a6fd5230ad4efd4c2e2fc66f2870dcdea563e3c3d4dbbcec47a0cf83ad09c515 languageName: node linkType: hard @@ -7725,9 +7949,9 @@ __metadata: linkType: hard "flatted@npm:^3.2.9": - version: 3.3.2 - resolution: "flatted@npm:3.3.2" - checksum: 10c1/e3a2065b2741881be6cf16b64c28be87b5c99d4544b3702a93e54b2aa9a792c85974fb8b485c3a26ff60359f2f85e97d124b9e56b018b2eba2a047706cefbf0a + version: 3.3.1 + resolution: "flatted@npm:3.3.1" + checksum: 10c1/b844833190688feb603d789f42c1413395cb3246abff628739a2d2b86a464a81793b09d11d6cb5a521023bc2cf657aec053793db0acabac47601ded34220977a languageName: node linkType: hard @@ -7936,19 +8160,16 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": - version: 1.2.5 - resolution: "get-intrinsic@npm:1.2.5" +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": + version: 1.2.4 + resolution: "get-intrinsic@npm:1.2.4" dependencies: - call-bind-apply-helpers: "npm:^1.0.0" - dunder-proto: "npm:^1.0.0" - es-define-property: "npm:^1.0.1" es-errors: "npm:^1.3.0" function-bind: "npm:^1.1.2" - gopd: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - hasown: "npm:^2.0.2" - checksum: 10c1/d0ec6e2b50cb72199fd532e3183b985c94e38e3561ed5e38f1ad624a43075c8e6b7acf91e05906615e6f21b38406ef8fe46044e43c50b2999105cf99af16f153 + has-proto: "npm:^1.0.1" + has-symbols: "npm:^1.0.3" + hasown: "npm:^2.0.0" + checksum: 10c1/8fc598288f9587f2045f020b2160ff4b4cfdead6931d1a475a04286f4addfa70f5ab6fb602d032bf28945c3a2304b44bcb537bc2489d3cd096c039b6112a93b7 languageName: node linkType: hard @@ -8002,6 +8223,15 @@ __metadata: languageName: node linkType: hard +"get-tsconfig@npm:^4.7.0": + version: 4.8.1 + resolution: "get-tsconfig@npm:4.8.1" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c1/680263b7ee8ceb66e88d6625d5b62fe432e280cf313e962e52bfaaae65d286ffe8f22ee35032a40ba0f9988469bcac92b1146ac837da33b8fad97a5b5b763806 + languageName: node + linkType: hard + "get-user-locale@npm:^2.2.1": version: 2.3.2 resolution: "get-user-locale@npm:2.3.2" @@ -8063,7 +8293,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7": +"glob@npm:^10.2.2, glob@npm:^10.3.10": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -8131,7 +8361,7 @@ __metadata: languageName: node linkType: hard -"globalthis@npm:^1.0.4": +"globalthis@npm:^1.0.3, globalthis@npm:^1.0.4": version: 1.0.4 resolution: "globalthis@npm:1.0.4" dependencies: @@ -8176,10 +8406,12 @@ __metadata: languageName: node linkType: hard -"gopd@npm:^1.0.1, gopd@npm:^1.1.0, gopd@npm:^1.2.0": - version: 1.2.0 - resolution: "gopd@npm:1.2.0" - checksum: 10c1/fe7c13f452ab1fefec0c465d23e5861d52d737f7e3a6a5b6d7a0d6635e3c507eadf4ae3fe4fc84abc32942305b5b1775eda595ba2283d500645aa02dc501fdec +"gopd@npm:^1.0.1": + version: 1.0.1 + resolution: "gopd@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.1.3" + checksum: 10c1/ae14f1fb888d8dcfe9be816a0808789311a5e2909db47f1d299c434425267a12e92b4962d13fabe72e7d640197e365c0cdeb3dbc95cb2ea06f3cf2155d352bae languageName: node linkType: hard @@ -8213,7 +8445,7 @@ __metadata: languageName: node linkType: hard -"has-bigints@npm:^1.0.2": +"has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2": version: 1.0.2 resolution: "has-bigints@npm:1.0.2" checksum: 10c1/6f06fc58ef304ebbb17903fd1dc2b18ad585e3e860b4e74dde1011adde88079f985cb18305bf2128131437cdef3bb03ca5425909a557e888d16b50f39008d202 @@ -8243,19 +8475,17 @@ __metadata: languageName: node linkType: hard -"has-proto@npm:^1.0.3": - version: 1.2.0 - resolution: "has-proto@npm:1.2.0" - dependencies: - dunder-proto: "npm:^1.0.0" - checksum: 10c1/b8ddd83c5227d372d44debb9484171c0155e7d0d3daecdc26914aa3ce7e366d9d00292476aabd2936109fe727efef6491b1435ed5461aac41ba8d62b35f42601 +"has-proto@npm:^1.0.1, has-proto@npm:^1.0.3": + version: 1.0.3 + resolution: "has-proto@npm:1.0.3" + checksum: 10c1/2efa39d4abbe4d1faec53ba758cd598f5b49ba4454d0501968c4c1caf838c60f8b8d8549a2fb92cfe9b7183e8440fd768d722b163b0859acd770af365de6fe8f languageName: node linkType: hard -"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": - version: 1.1.0 - resolution: "has-symbols@npm:1.1.0" - checksum: 10c1/4521297dd90e653f353de7cbaae997c3bd013d3167f14c83832ca25890e61c04816961250477de759a339ec343938627e86a9694c6103df64a8e954781a3153e +"has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": + version: 1.0.3 + resolution: "has-symbols@npm:1.0.3" + checksum: 10c1/af737e966152a39f2c4d841085902cbd05a8ecc414707869c35c6ef77912d057003375d579a0f203c11812c82244dbbfe9619e503954f21138be7bd468cd2831 languageName: node linkType: hard @@ -8565,12 +8795,12 @@ __metadata: linkType: hard "https-proxy-agent@npm:^7.0.1": - version: 7.0.6 - resolution: "https-proxy-agent@npm:7.0.6" + version: 7.0.5 + resolution: "https-proxy-agent@npm:7.0.5" dependencies: - agent-base: "npm:^7.1.2" + agent-base: "npm:^7.0.2" debug: "npm:4" - checksum: 10c1/73bf295395fe3de711f1f5cdd493f0e194e4909344ffa44da2be2502fbf7d7834202036aefdda89fb22066b18f9e595721dea083aa022ad2ac78a12356a31f2c + checksum: 10c1/cf04d424bc27b940514c46a1a889958a178ecf495e279dacfb3f4dd447239c0a1d5d9f41cf81dffa04d6c2b5da6b22a241121725c9209f40c443e5441fcee786 languageName: node linkType: hard @@ -8660,9 +8890,9 @@ __metadata: linkType: hard "immutable@npm:^5.0.2": - version: 5.0.3 - resolution: "immutable@npm:5.0.3" - checksum: 10c1/edc2ccd93f55e7cdfd186903c9679f384fb53c2d7fba15e180f221f597c63406ae1afb63d2f50219cbc8970ad5f104fec332727c6f2cfa4dff193f69127ac34c + version: 5.0.2 + resolution: "immutable@npm:5.0.2" + checksum: 10c1/1839cc1f18a193b4dcab1648d35bf959571d4f642023329e0993cbd22e54c3417b44fb398fb8d0f1e77de64044e1da5ca686ad48618d500853d24f002d97a34f languageName: node linkType: hard @@ -8825,12 +9055,12 @@ __metadata: languageName: node linkType: hard -"is-bigint@npm:^1.1.0": - version: 1.1.0 - resolution: "is-bigint@npm:1.1.0" +"is-bigint@npm:^1.0.1": + version: 1.0.4 + resolution: "is-bigint@npm:1.0.4" dependencies: - has-bigints: "npm:^1.0.2" - checksum: 10c1/9d7c40dd7ba04ef4dee103b3487345d85024e8fd0b1d23555ffd9a1ea00e8ca0932ebb584970061296f197ee511945dbc1fa4438cb0ae464580012b688399d6a + has-bigints: "npm:^1.0.1" + checksum: 10c1/69c1141b3c3377592fbf72f4ab933e2463354ae60ba8f8f1137261501282111b2b346aacf55f31df5a1b8d029d89642d8890803c2b0905e3f047eef86cfd15cb languageName: node linkType: hard @@ -8843,17 +9073,17 @@ __metadata: languageName: node linkType: hard -"is-boolean-object@npm:^1.2.0": - version: 1.2.0 - resolution: "is-boolean-object@npm:1.2.0" +"is-boolean-object@npm:^1.1.0": + version: 1.1.2 + resolution: "is-boolean-object@npm:1.1.2" dependencies: - call-bind: "npm:^1.0.7" - has-tostringtag: "npm:^1.0.2" - checksum: 10c1/2111e36958189758360115f94be49507c589d3ebc85f45d3d16f65e97a40a4c07e3dbb475d0b52f864d3d06b3659560ffd936edcedc4f3c369d827bbe0678649 + call-bind: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.0" + checksum: 10c1/25c0c33ca38cb673b015fe9011d78a960877d64825654a5ded295c500ceafd792e6fabd83b32dc3b6ce71fba471247256db1825a07a640cd91b19a46f3726929 languageName: node linkType: hard -"is-callable@npm:^1.1.3, is-callable@npm:^1.2.7": +"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": version: 1.2.7 resolution: "is-callable@npm:1.2.7" checksum: 10c1/eb7d0a7358c155f779ef67a5356244afa92cab1ba6f33a9d06e93e84ddc170b281075b0f42b6d5a89a53f73f5e9f90de1b6e461c0f6d830263c691a68d3ac782 @@ -8878,7 +9108,7 @@ __metadata: languageName: node linkType: hard -"is-date-object@npm:^1.0.5": +"is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.5": version: 1.0.5 resolution: "is-date-object@npm:1.0.5" dependencies: @@ -8903,12 +9133,12 @@ __metadata: languageName: node linkType: hard -"is-finalizationregistry@npm:^1.1.0": - version: 1.1.0 - resolution: "is-finalizationregistry@npm:1.1.0" +"is-finalizationregistry@npm:^1.0.2": + version: 1.0.2 + resolution: "is-finalizationregistry@npm:1.0.2" dependencies: - call-bind: "npm:^1.0.7" - checksum: 10c1/348c41f32fa10dbb264f0d542082a662a7d82671a42a3487d8e3c7b789b2a3d05d6640706f9a262e668e7aa3078877bcd6f0602dab32481ba6898aff65932959 + call-bind: "npm:^1.0.2" + checksum: 10c1/c618b438dbf022c4f40b633bfe782f7d87155d71a696475f035e3d19d363088c8b5ac43d58280fb484f49f36f1aa35ff2f5b11db02f9bbca2d2cc54534fc0dff languageName: node linkType: hard @@ -8993,13 +9223,12 @@ __metadata: languageName: node linkType: hard -"is-number-object@npm:^1.1.0": - version: 1.1.0 - resolution: "is-number-object@npm:1.1.0" +"is-number-object@npm:^1.0.4": + version: 1.0.7 + resolution: "is-number-object@npm:1.0.7" dependencies: - call-bind: "npm:^1.0.7" - has-tostringtag: "npm:^1.0.2" - checksum: 10c1/7b3b7c5cf6cd2c2b478e86dbf4c1273d5afcb1d5e0be19444f910014996665b02bda25244c9b152bc07adbe5b69de7422c6d4b26312ff48f5800cd05d9160920 + has-tostringtag: "npm:^1.0.0" + checksum: 10c1/9257914ef9909ad8e374b55316b08a81a19f213e5189ca4940920d0626893050ae7107c7edf21e695c85009610ccdfbc27d39c58cef14cb3fe88b3143336abbc languageName: node linkType: hard @@ -9041,14 +9270,12 @@ __metadata: linkType: hard "is-regex@npm:^1.1.4": - version: 1.2.0 - resolution: "is-regex@npm:1.2.0" + version: 1.1.4 + resolution: "is-regex@npm:1.1.4" dependencies: - call-bind: "npm:^1.0.7" - gopd: "npm:^1.1.0" - has-tostringtag: "npm:^1.0.2" - hasown: "npm:^2.0.2" - checksum: 10c1/dc59ebfb1a7bd2fc00018a02855cacfef293dbd1d2b0568d8502e12f08bd735e89a26a8e465ede4fabc078f163cf0a3ab41c2f189789bf456bbb22cb36c293ca + call-bind: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.0" + checksum: 10c1/84f38629c5561c16a8b46668ce9854f50e4d18b91e87d864e75040733138a3f09b59cfad329987c9f05b4af6c3ea5596d40f5ee086a6e4c40d1a19f4b3727a97 languageName: node linkType: hard @@ -9082,24 +9309,21 @@ __metadata: languageName: node linkType: hard -"is-string@npm:^1.0.7, is-string@npm:^1.1.0": - version: 1.1.0 - resolution: "is-string@npm:1.1.0" +"is-string@npm:^1.0.5, is-string@npm:^1.0.7": + version: 1.0.7 + resolution: "is-string@npm:1.0.7" dependencies: - call-bind: "npm:^1.0.7" - has-tostringtag: "npm:^1.0.2" - checksum: 10c1/e2deaed0cdc5b765e799e57cc4556bfb3967d6881dd03bad3ee8ad09acedb111721355295719fd5cd438eceb6910626424a1ad7d6aba512e44423cd815aa0a16 + has-tostringtag: "npm:^1.0.0" + checksum: 10c1/2672a432f2d8dcd2f69926f5a23a3b0cca7c6735ade0289671035e29052905395cb4b6641f2c4caa884d431a5a2a77d62303e9458e8ac12ed728a756b7b531ec languageName: node linkType: hard -"is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.0": - version: 1.1.0 - resolution: "is-symbol@npm:1.1.0" +"is-symbol@npm:^1.0.2, is-symbol@npm:^1.0.3": + version: 1.0.4 + resolution: "is-symbol@npm:1.0.4" dependencies: - call-bind: "npm:^1.0.7" - has-symbols: "npm:^1.0.3" - safe-regex-test: "npm:^1.0.3" - checksum: 10c1/21ce336392dfce13462965c2fd831f358f5b0625ddae7779d15558d7b96372a40d67b6e95a151a0ffbb6dc7baf3c91913df0361a5f3b69bb7053bcdf1cd19766 + has-symbols: "npm:^1.0.2" + checksum: 10c1/4cf5a1a89acd749e2485ca5c02f9d3523dc4bfbcd8648ad557c8c0f46d466ad079b36fa7762e38d818cb63b0d58d2e97fdd914c2cdcae1384d4aaf2bde586028 languageName: node linkType: hard @@ -10179,10 +10403,17 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:^3.0.0, lilconfig@npm:^3.1.2, lilconfig@npm:^3.1.3": - version: 3.1.3 - resolution: "lilconfig@npm:3.1.3" - checksum: 10c1/c24f58bc163a45f850740c2608cfd74f04ea2d290276cf96f327cd7d2ac336e044a9e1b6f1284aad7667f705f68b94384dda81aa540159650887e05677146cfc +"lilconfig@npm:^2.1.0": + version: 2.1.0 + resolution: "lilconfig@npm:2.1.0" + checksum: 10c1/717942dfb6df83db6d9fab1616b2bee0d33be9571fc7446afcb004229a8e72915a0df0ef85e92b082cfab4d5ea273adcb554066de765df631bd311d0fce2e364 + languageName: node + linkType: hard + +"lilconfig@npm:^3.0.0, lilconfig@npm:^3.1.2": + version: 3.1.2 + resolution: "lilconfig@npm:3.1.2" + checksum: 10c1/e0ff8fc36f5210fc191da90a4911045df93a44fe5de38eaa34325cb4589188bed94bcb1734369ac40e396bc3f327a0f57117e72d17cd17fea6faebff7a9776aa languageName: node linkType: hard @@ -10232,7 +10463,7 @@ __metadata: languageName: node linkType: hard -"loader-utils@npm:^2.0.0": +"loader-utils@npm:^2.0.0, loader-utils@npm:^2.0.4": version: 2.0.4 resolution: "loader-utils@npm:2.0.4" dependencies: @@ -10406,12 +10637,12 @@ __metadata: languageName: node linkType: hard -"lucide-react@npm:^0.454.0": - version: 0.454.0 - resolution: "lucide-react@npm:0.454.0" +"lucide-react@npm:^0.396.0": + version: 0.396.0 + resolution: "lucide-react@npm:0.396.0" peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc - checksum: 10c1/74a826345610813f909a52e0c74c4ec374fb3c23fdae598670a7b80001610672afc97b7e887546164e7464e5ba69e2eb386ba1dbfbca73e4cb1f707a9a69dc3c + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + checksum: 10c1/f07aa9b36cf50488120893d9e7b6198927fb0dfce09a78a8e1c3051754d5a822f5b06088abd398a78c8b70b3723da5209e2b35eb0744f2f53e6c1995d9d885f2 languageName: node linkType: hard @@ -10479,22 +10710,23 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^14.0.3": - version: 14.0.3 - resolution: "make-fetch-happen@npm:14.0.3" +"make-fetch-happen@npm:^13.0.0": + version: 13.0.1 + resolution: "make-fetch-happen@npm:13.0.1" dependencies: - "@npmcli/agent": "npm:^3.0.0" - cacache: "npm:^19.0.1" + "@npmcli/agent": "npm:^2.0.0" + cacache: "npm:^18.0.0" http-cache-semantics: "npm:^4.1.1" + is-lambda: "npm:^1.0.1" minipass: "npm:^7.0.2" - minipass-fetch: "npm:^4.0.0" + minipass-fetch: "npm:^3.0.0" minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^1.0.0" - proc-log: "npm:^5.0.0" + negotiator: "npm:^0.6.3" + proc-log: "npm:^4.2.0" promise-retry: "npm:^2.0.1" - ssri: "npm:^12.0.0" - checksum: 10c1/008b5bccaa81528ac03e7537e4f4d67e7e2bca09fe9accab3089696ff23445a6ce1796401a5c3217e37022cee9b84705c9a4e1c85a5232296d6e64fe3f96c1e1 + ssri: "npm:^10.0.0" + checksum: 10c1/d4e46041068994fe3c37e2eae540bb4a0fc7d4df9c4ef992ee49f9e62a9361e28cf1453ede6cbc12106267d39414bf493e4360dad7d73587b37cfb6fe429a691 languageName: node linkType: hard @@ -10555,14 +10787,14 @@ __metadata: linkType: hard "memfs@npm:^4.6.0": - version: 4.15.0 - resolution: "memfs@npm:4.15.0" + version: 4.14.0 + resolution: "memfs@npm:4.14.0" dependencies: "@jsonjoy.com/json-pack": "npm:^1.0.3" "@jsonjoy.com/util": "npm:^1.3.0" tree-dump: "npm:^1.0.1" tslib: "npm:^2.0.0" - checksum: 10c1/91743a1f41be180446d41e9dcca4166dbc16db0c391655ce05a104cf37b590d7493d5312360a2164f625daa965f00aabe76ed327213d5a5c09858e2170538c6d + checksum: 10c1/5c11644a9485db399c59c68371d101d868a8c60a11fdbbffdec7bb4ccc25969daedd00ce949273bb072ae0ebfe47196a1a1e589773882974c7cc39b2c208c777 languageName: node linkType: hard @@ -10781,18 +11013,18 @@ __metadata: languageName: node linkType: hard -"minipass-fetch@npm:^4.0.0": - version: 4.0.0 - resolution: "minipass-fetch@npm:4.0.0" +"minipass-fetch@npm:^3.0.0": + version: 3.0.5 + resolution: "minipass-fetch@npm:3.0.5" dependencies: encoding: "npm:^0.1.13" minipass: "npm:^7.0.3" minipass-sized: "npm:^1.0.3" - minizlib: "npm:^3.0.1" + minizlib: "npm:^2.1.2" dependenciesMeta: encoding: optional: true - checksum: 10c1/5831cf620b43a062ae180be3d59b7e7d694c4ae4d71a5ed6edb39a0bfe0c8cbb3aba6b95d9e61cb04bcc2ea112924f9f653a06a6e2de26bb9c92f37c32795b2c + checksum: 10c1/1c0b2027bb409a5e9b0c0765bda4d196b7ea192a1cfc2287a64c238dd3327696789944ce03317d180f83099890045efde6978dc2e2fed66990ffa0a207b84fbc languageName: node linkType: hard @@ -10839,7 +11071,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": version: 7.1.2 resolution: "minipass@npm:7.1.2" checksum: 10c1/a2a359ad8f26f9bd33faafe7408fc19299fb2fa5706f76ffa126a2fce0dc0b864eefcd81608500624a570b04be755bd73caea23fcbb2ae600af87df3908b74be @@ -10856,16 +11088,6 @@ __metadata: languageName: node linkType: hard -"minizlib@npm:^3.0.1": - version: 3.0.1 - resolution: "minizlib@npm:3.0.1" - dependencies: - minipass: "npm:^7.0.4" - rimraf: "npm:^5.0.5" - checksum: 10c1/c330f6f576d8c2f33f4bf2b691935d6025cacd31838a325b81a671eca9add09e64b2116bcd734516428dcbefa7772e05eed808a276ca58cfcc5bb43ca2d85110 - languageName: node - linkType: hard - "mkdirp@npm:^0.5.1": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" @@ -10886,15 +11108,6 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^3.0.1": - version: 3.0.1 - resolution: "mkdirp@npm:3.0.1" - bin: - mkdirp: dist/cjs/src/bin.js - checksum: 10c1/4cec797001838117da190c148d9c0b4aa387e1808302a1f1adf4dfdace42a5af1e083fc88739413a7d42c93e9a9a4971b25e090c943638801803f9ed35bb1162 - languageName: node - linkType: hard - "mobx-persist-store@npm:^1.1.5": version: 1.1.5 resolution: "mobx-persist-store@npm:1.1.5" @@ -10973,11 +11186,11 @@ __metadata: linkType: hard "nanoid@npm:^3.3.7": - version: 3.3.8 - resolution: "nanoid@npm:3.3.8" + version: 3.3.7 + resolution: "nanoid@npm:3.3.7" bin: nanoid: bin/nanoid.cjs - checksum: 10c1/b7f68bc6a105c13b0429d990ceecacffef79dd7ec2759a26220d91d73f693a73ab9888b2ba7e4c24160e4273de977d87d40b4be461a5f2e5fd2f55095d922fe2 + checksum: 10c1/f0f6b94f1084ebfd13003698273665aef653c4a7adbb822e82383ce723c4856149597de033b339b523d124b55763a60a6b2c74349b98fd25f71387968cce49d0 languageName: node linkType: hard @@ -11009,13 +11222,6 @@ __metadata: languageName: node linkType: hard -"negotiator@npm:^1.0.0": - version: 1.0.0 - resolution: "negotiator@npm:1.0.0" - checksum: 10c1/d41f8172b009e0712a70182f1194aee914c84a5649037a630df69f8253f28aa809c59396cffa1cdc1a0f488461c4abb61ab16d8a321ea1968639cf6982921625 - languageName: node - linkType: hard - "neo-async@npm:^2.6.2": version: 2.6.2 resolution: "neo-async@npm:2.6.2" @@ -11088,22 +11294,22 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 11.0.0 - resolution: "node-gyp@npm:11.0.0" + version: 10.2.0 + resolution: "node-gyp@npm:10.2.0" dependencies: env-paths: "npm:^2.2.0" exponential-backoff: "npm:^3.1.1" glob: "npm:^10.3.10" graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^14.0.3" - nopt: "npm:^8.0.0" - proc-log: "npm:^5.0.0" + make-fetch-happen: "npm:^13.0.0" + nopt: "npm:^7.0.0" + proc-log: "npm:^4.1.0" semver: "npm:^7.3.5" - tar: "npm:^7.4.3" - which: "npm:^5.0.0" + tar: "npm:^6.2.1" + which: "npm:^4.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10c1/794a73493bc559811c774ad9f8bcd00894ee4a2ef22d2d87b0cb149e279ca56226a74660ffa6c3c3d91f4622d5a7abd5d13310a988326e44f10d90057b9bc748 + checksum: 10c1/93d360015a637e87d5d07e6d12d963229b671abeabe63c3bc92209208e8e4a7619966938d4217456a39d5ce4f8278b338977bdb992bdb6e9dea75ab116a9f88a languageName: node linkType: hard @@ -11115,9 +11321,9 @@ __metadata: linkType: hard "node-releases@npm:^2.0.18": - version: 2.0.19 - resolution: "node-releases@npm:2.0.19" - checksum: 10c1/fab1370a1a2f3dd9318831f2fb3f83c077a0c0de4c22007e44a11018f3e8686588ad4231f205e8658dd1d8f0d45bf3cc336d5126ac4e2c0facea5043ce073a48 + version: 2.0.18 + resolution: "node-releases@npm:2.0.18" + checksum: 10c1/b468611ce19eddc9463341c22d8c18f34d85c9f0d0ceb37df80fd95bde91129d05650700b9106462060c50c452fc53c8a825580e10a02d6c7a451bb1a1d3be1c languageName: node linkType: hard @@ -11132,14 +11338,14 @@ __metadata: languageName: node linkType: hard -"nopt@npm:^8.0.0": - version: 8.0.0 - resolution: "nopt@npm:8.0.0" +"nopt@npm:^7.0.0": + version: 7.2.1 + resolution: "nopt@npm:7.2.1" dependencies: abbrev: "npm:^2.0.0" bin: nopt: bin/nopt.js - checksum: 10c1/c6f8cc706bdbecdb9bae1ec6b0defd8a975956ac1d29d3e554ba63d7f9fd17f637306421606421a0a24eb5d5490e503e964be853f89651df9afcf0f2cf5eb9f5 + checksum: 10c1/411a24ca0f2bf0ceb044daeccf79bc1b307708c90c418533edad96437aa52528fa8f65833cbaa58ea7351bacf4ca5732f0634388441c51c4fcaa204e72845bf2 languageName: node linkType: hard @@ -11197,9 +11403,9 @@ __metadata: linkType: hard "nwsapi@npm:^2.2.2": - version: 2.2.16 - resolution: "nwsapi@npm:2.2.16" - checksum: 10c1/5036999ba1e7967e06cd7446160be409e29883762bbfcb3f7b4c71968353605593d79e7f490bb0a440ddaf7f3ca930978cd2dcd0f5fa97afe3e9f4cd98a07ec5 + version: 2.2.13 + resolution: "nwsapi@npm:2.2.13" + checksum: 10c1/bcb184ac5c22a85e0b6951cb2cdc4b9f3d275a55b9b7bec2cc7c1616d2d36f3722bc56d7d6e7eeb0279c85fb6d73a98a2b7cee6940bdbd4e8bc6f6eb4ed9236e languageName: node linkType: hard @@ -11367,7 +11573,7 @@ __metadata: "@floating-ui/react-dom-interactions": "npm:^0.10.3" "@jest/globals": "npm:^29.7.0" "@medv/finder": "npm:^3.1.0" - "@openreplay/sourcemap-uploader": "npm:^3.0.8" + "@openreplay/sourcemap-uploader": "npm:^3.0.10" "@sentry/browser": "npm:^5.21.1" "@svg-maps/world": "npm:^1.0.1" "@tanstack/react-query": "npm:^5.56.2" @@ -11401,6 +11607,7 @@ __metadata: cypress: "npm:^13.3.0" cypress-image-snapshot: "npm:^4.0.1" dotenv: "npm:^6.2.0" + esbuild-loader: "npm:^4.2.2" eslint: "npm:^8.15.0" eslint-plugin-react: "npm:^7.29.4" fflate: "npm:^0.8.2" @@ -11417,7 +11624,7 @@ __metadata: jshint: "npm:^2.11.1" jspdf: "npm:^2.5.1" lottie-react: "npm:^2.4.0" - lucide-react: "npm:^0.454.0" + lucide-react: "npm:^0.396.0" luxon: "npm:^3.5.0" microdiff: "npm:^1.4.0" mini-css-extract-plugin: "npm:^2.6.0" @@ -11579,13 +11786,6 @@ __metadata: languageName: node linkType: hard -"p-map@npm:^7.0.2": - version: 7.0.3 - resolution: "p-map@npm:7.0.3" - checksum: 10c1/61e1d1f8054af8164dc26646cabef26b6062f75e6cc6924c0c93bfe66e14f53109a3c3784f401b5038f748d4228a1d152f35b014c1f8b59902af2dd1b5d2f8ff - languageName: node - linkType: hard - "p-retry@npm:^6.2.0": version: 6.2.1 resolution: "p-retry@npm:6.2.1" @@ -11734,10 +11934,10 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.12": - version: 0.1.12 - resolution: "path-to-regexp@npm:0.1.12" - checksum: 10c1/77b961783c22075e1cbdff45c34b08ba0b55dd48a5f9f8628660ae9addb4f206df916f076219c6cbbcd88706ad1136fb942971aa346108f3c5de9741c17438ad +"path-to-regexp@npm:0.1.10": + version: 0.1.10 + resolution: "path-to-regexp@npm:0.1.10" + checksum: 10c1/78f8c61e15518c1d993e59aa847d670db1f33f857cb644dbca5111abe85a8acf80b714f08892e40697512668e3bba3122a141ab5c7d5ce17dd600ee12f32f250 languageName: node linkType: hard @@ -12480,10 +12680,10 @@ __metadata: languageName: node linkType: hard -"proc-log@npm:^5.0.0": - version: 5.0.0 - resolution: "proc-log@npm:5.0.0" - checksum: 10c1/1817053443e805eb93e5a9cbd4b9f68315858c2efbbedaf6d2455343dbc0474d66b22cca0986d408564a69f96d61528beab4f3da98febac85d6736f9db1c101e +"proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 10c1/9dfb2f164f660fce32bc54da5da9fad3d9e5ae4a29677448b97b50e1b3460e1f378e8273142e81092d27755bc14c3eaf448e6ddca425ac146abd35baa794941a languageName: node linkType: hard @@ -12557,11 +12757,11 @@ __metadata: linkType: hard "psl@npm:^1.1.33": - version: 1.15.0 - resolution: "psl@npm:1.15.0" + version: 1.10.0 + resolution: "psl@npm:1.10.0" dependencies: punycode: "npm:^2.3.1" - checksum: 10c1/3b2ce531a01b44c6887a46dd2a6a5702ba3143d738442cb05f3662a79f9eaa0fd051992c8d5d7795d4b3638bf8a0e7c71dd4ddc25b66f3d6eab64746f29e2073 + checksum: 10c1/f55d7282cd2ef95c6ee8233dfb2f1056e20cf688569aeb08597be81fc2ef5aadaee9221d5e7eed0d221607c2e6120f1db96cfe1ada2ad315b359007475a53664 languageName: node linkType: hard @@ -12598,15 +12798,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.13.1": - version: 6.13.1 - resolution: "qs@npm:6.13.1" - dependencies: - side-channel: "npm:^1.0.6" - checksum: 10c1/dce2dcf08d102194ae31b00a1ac3f3ef3a287b063147161787b786b86b29e2a67e604a81348b1b66566a8bf3d4ae61c7ceb3022aca1b71965cbb26d14c6aaa84 - languageName: node - linkType: hard - "query-string@npm:^7.1.3": version: 7.1.3 resolution: "query-string@npm:7.1.3" @@ -12789,9 +12980,9 @@ __metadata: languageName: node linkType: hard -"rc-field-form@npm:~2.6.0": - version: 2.6.0 - resolution: "rc-field-form@npm:2.6.0" +"rc-field-form@npm:~2.5.0": + version: 2.5.1 + resolution: "rc-field-form@npm:2.5.1" dependencies: "@babel/runtime": "npm:^7.18.0" "@rc-component/async-validator": "npm:^5.0.3" @@ -12799,7 +12990,7 @@ __metadata: peerDependencies: react: ">=16.9.0" react-dom: ">=16.9.0" - checksum: 10c1/f15dba6613388adc7583396de8db3f954b55f1d9b1e996e6962a2017c7fdda160dc0c7bc19ed16a69bc934be8a7c8d49692de59705ee499de48de4b084693e03 + checksum: 10c1/36b02857ae673ef6de72cbf1cb7d17cbe9d371ccf62333318fb44b9dc70e563683237f8a0726c6ea76445f3bf8c2eb832d19e14f69a7649c5b9ba94ccd173a66 languageName: node linkType: hard @@ -12836,9 +13027,9 @@ __metadata: languageName: node linkType: hard -"rc-input@npm:~1.6.0, rc-input@npm:~1.6.4": - version: 1.6.4 - resolution: "rc-input@npm:1.6.4" +"rc-input@npm:~1.6.0, rc-input@npm:~1.6.3": + version: 1.6.3 + resolution: "rc-input@npm:1.6.3" dependencies: "@babel/runtime": "npm:^7.11.1" classnames: "npm:^2.2.1" @@ -12846,7 +13037,7 @@ __metadata: peerDependencies: react: ">=16.0.0" react-dom: ">=16.0.0" - checksum: 10c1/a82e9b8f2c0a30fbea0e53369b69fa55ab77106890b39cbf61f725a72325efa8ff27e1f40a051557ec5320c39d613cf2ed5b8e13a1f795c36c4dcc5e3abde73a + checksum: 10c1/3a8772ca09019ae9b8e0bbe23fa5a4eb0bbfb1332ab6916c88090d1330cae5a5426154fde9efdf5535905a3eb990c49e36995842e4dbe8268e9a1152df2883f2 languageName: node linkType: hard @@ -12943,9 +13134,9 @@ __metadata: languageName: node linkType: hard -"rc-picker@npm:~4.8.3": - version: 4.8.3 - resolution: "rc-picker@npm:4.8.3" +"rc-picker@npm:~4.8.0": + version: 4.8.1 + resolution: "rc-picker@npm:4.8.1" dependencies: "@babel/runtime": "npm:^7.24.7" "@rc-component/trigger": "npm:^2.0.0" @@ -12969,7 +13160,7 @@ __metadata: optional: true moment: optional: true - checksum: 10c1/84ed9ba04d1c75349839361b0393deb1bf81a2a99a4553890ef1b695fa8ba3081a1bc040a1fb492c5a0e6b34ba1c31da51c0d642ee2d8387b002910a1db4936e + checksum: 10c1/4ff2fe867b922fc916d92a56d5e2b4566f4ad2c76afd9efd7b0f48daee5a23c95ea3881b03d2e8db099374788e3cb0ff4da075aaf2e4625e7fe0417ad6924873 languageName: node linkType: hard @@ -13031,9 +13222,9 @@ __metadata: languageName: node linkType: hard -"rc-select@npm:~14.16.2, rc-select@npm:~14.16.4": - version: 14.16.4 - resolution: "rc-select@npm:14.16.4" +"rc-select@npm:~14.16.2, rc-select@npm:~14.16.3": + version: 14.16.3 + resolution: "rc-select@npm:14.16.3" dependencies: "@babel/runtime": "npm:^7.10.1" "@rc-component/trigger": "npm:^2.1.1" @@ -13045,7 +13236,7 @@ __metadata: peerDependencies: react: "*" react-dom: "*" - checksum: 10c1/ecc3bb7d9226fca8b59f15d4c3a25616141b82ab61d9e1d58b60bedf7a32ae93d5bd99d7be3ba37e50087a6ab847462a9763fa9bf3ff3aab88d10338787e46bf + checksum: 10c1/913768cc08286ea9ee204c72eb0a3a0d32008c0cc9f19746a1f605f01301b287315cea35170cef3f326d13a30b3296dbefcbb5b002ce43eb76036bda1fd0f0db languageName: node linkType: hard @@ -13091,9 +13282,9 @@ __metadata: languageName: node linkType: hard -"rc-table@npm:~7.49.0": - version: 7.49.0 - resolution: "rc-table@npm:7.49.0" +"rc-table@npm:~7.48.1": + version: 7.48.1 + resolution: "rc-table@npm:7.48.1" dependencies: "@babel/runtime": "npm:^7.10.1" "@rc-component/context": "npm:^1.4.0" @@ -13104,7 +13295,7 @@ __metadata: peerDependencies: react: ">=16.9.0" react-dom: ">=16.9.0" - checksum: 10c1/4bb7f27017775b40ed8999aeab120d0a15f5c271d2e4643d08863e22a85f47d1decf6637a61a12149ba1e52fa18972d6bb0e9f7be35c902f108a8e02ae2b10ea + checksum: 10c1/81ac226b13d22f65136ebe05ee6e3475a1eb5fd5e5c1da39192f7f717e7e3d045623089e3ca7960dfb644c2a7a5ef324a6fe73f65291c503af9acd8e837b2702 languageName: node linkType: hard @@ -13170,9 +13361,9 @@ __metadata: languageName: node linkType: hard -"rc-tree-select@npm:~5.24.5": - version: 5.24.5 - resolution: "rc-tree-select@npm:5.24.5" +"rc-tree-select@npm:~5.24.4": + version: 5.24.4 + resolution: "rc-tree-select@npm:5.24.4" dependencies: "@babel/runtime": "npm:^7.25.7" classnames: "npm:2.x" @@ -13182,7 +13373,7 @@ __metadata: peerDependencies: react: "*" react-dom: "*" - checksum: 10c1/e80b587a3c0b7f3618f05cc1ee55cabc228696b4552f5249d37f65830d5d5948becdefc2bd1014914c833cd9a34fcaf5e34fa31eb5ba80536b021bc652f20de6 + checksum: 10c1/24cdf8a693de3b035a53efcd4ef23e8ef45d0de1a2e9e4c30a815518ff7808dc93648297961e5ba5a70e31c7bcc3950ab3ec5f4bc79be577824c9fec2f2257f4 languageName: node linkType: hard @@ -13244,22 +13435,22 @@ __metadata: languageName: node linkType: hard -"rc-util@npm:^5.0.1, rc-util@npm:^5.16.1, rc-util@npm:^5.17.0, rc-util@npm:^5.18.1, rc-util@npm:^5.2.0, rc-util@npm:^5.20.1, rc-util@npm:^5.21.0, rc-util@npm:^5.24.4, rc-util@npm:^5.25.2, rc-util@npm:^5.27.0, rc-util@npm:^5.30.0, rc-util@npm:^5.31.1, rc-util@npm:^5.32.2, rc-util@npm:^5.34.1, rc-util@npm:^5.35.0, rc-util@npm:^5.36.0, rc-util@npm:^5.37.0, rc-util@npm:^5.38.0, rc-util@npm:^5.38.1, rc-util@npm:^5.40.1, rc-util@npm:^5.41.0, rc-util@npm:^5.43.0, rc-util@npm:^5.44.0": - version: 5.44.0 - resolution: "rc-util@npm:5.44.0" +"rc-util@npm:^5.0.1, rc-util@npm:^5.16.1, rc-util@npm:^5.17.0, rc-util@npm:^5.18.1, rc-util@npm:^5.2.0, rc-util@npm:^5.20.1, rc-util@npm:^5.21.0, rc-util@npm:^5.24.4, rc-util@npm:^5.25.2, rc-util@npm:^5.27.0, rc-util@npm:^5.30.0, rc-util@npm:^5.31.1, rc-util@npm:^5.32.2, rc-util@npm:^5.34.1, rc-util@npm:^5.35.0, rc-util@npm:^5.36.0, rc-util@npm:^5.37.0, rc-util@npm:^5.38.0, rc-util@npm:^5.38.1, rc-util@npm:^5.40.1, rc-util@npm:^5.41.0, rc-util@npm:^5.43.0": + version: 5.43.0 + resolution: "rc-util@npm:5.43.0" dependencies: "@babel/runtime": "npm:^7.18.3" react-is: "npm:^18.2.0" peerDependencies: react: ">=16.9.0" react-dom: ">=16.9.0" - checksum: 10c1/7836497ac4168c7b131d1a1780655931e343920d293eef74b35ed8f39961813a3c7667874083bfa059f161eecae585566ff5f8afbab90f707addd6b5ebc54f45 + checksum: 10c1/b74a6b2c079613fa3a6d08a9d4dd2deb777648bf05f5cd8c565717c12f46119d123355b05ee179020fa65324350354e3dd8159fc9613bfc03335bcd9e6abfb26 languageName: node linkType: hard "rc-virtual-list@npm:^3.14.2, rc-virtual-list@npm:^3.5.1, rc-virtual-list@npm:^3.5.2": - version: 3.16.0 - resolution: "rc-virtual-list@npm:3.16.0" + version: 3.15.0 + resolution: "rc-virtual-list@npm:3.15.0" dependencies: "@babel/runtime": "npm:^7.20.0" classnames: "npm:^2.2.6" @@ -13268,7 +13459,7 @@ __metadata: peerDependencies: react: ">=16.9.0" react-dom: ">=16.9.0" - checksum: 10c1/46320a57bdf86e306091d8d16a98c0dc3a45111e38de47e22bb0591c8a9646ec51550ecb4e23b91750974c85d7363d875c699af0d895de8903540040f7889f26 + checksum: 10c1/00b9de6ef57c15e6ec20788c3655d84065f792a7102f0a1e920facb7d2b5870f011f0261baafca3646c914392683a7a4c3abd289c81c1174dc8b56d14794ee9a languageName: node linkType: hard @@ -13551,8 +13742,8 @@ __metadata: linkType: hard "react-smooth@npm:^4.0.0": - version: 4.0.3 - resolution: "react-smooth@npm:4.0.3" + version: 4.0.1 + resolution: "react-smooth@npm:4.0.1" dependencies: fast-equals: "npm:^5.0.1" prop-types: "npm:^15.8.1" @@ -13560,7 +13751,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10c1/48367c8fd455b9ecb81f865f1425acc65e2ebf7095d8828d123ecb1664522981eb87aa814119712e0942b227addb194908ca4cd290cc089b0aec17b4a8c6cf70 + checksum: 10c1/b794baa21f7234b115119095e56b4e5c63760327210ebbf6a4906f9ae1b809995520dc119a36cdcbe1b52e7a0af177290975ac6aefe84c0d88b7a78562d7b79b languageName: node linkType: hard @@ -13694,8 +13885,8 @@ __metadata: linkType: hard "recharts@npm:^2.12.7": - version: 2.14.1 - resolution: "recharts@npm:2.14.1" + version: 2.13.3 + resolution: "recharts@npm:2.13.3" dependencies: clsx: "npm:^2.0.0" eventemitter3: "npm:^4.0.1" @@ -13708,7 +13899,7 @@ __metadata: peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 - checksum: 10c1/b328008f8ea041554587a1fffdaf45fd782c5c8ba165aee2f4ea505097ca99a8cd687736921647f3739601b3006ad3049a9beebfa87564de5df73c10971fa19c + checksum: 10c1/773a927b857a02af80e508999ba21990d200495fe6e9ca3489d996fca368b3220a548d4358eb29e3c7a2e10618855a51329490ed7280c35da863737e80e219bc languageName: node linkType: hard @@ -13730,19 +13921,18 @@ __metadata: languageName: node linkType: hard -"reflect.getprototypeof@npm:^1.0.4, reflect.getprototypeof@npm:^1.0.6": - version: 1.0.8 - resolution: "reflect.getprototypeof@npm:1.0.8" +"reflect.getprototypeof@npm:^1.0.4": + version: 1.0.6 + resolution: "reflect.getprototypeof@npm:1.0.6" dependencies: - call-bind: "npm:^1.0.8" + call-bind: "npm:^1.0.7" define-properties: "npm:^1.2.1" - dunder-proto: "npm:^1.0.0" - es-abstract: "npm:^1.23.5" + es-abstract: "npm:^1.23.1" es-errors: "npm:^1.3.0" get-intrinsic: "npm:^1.2.4" - gopd: "npm:^1.2.0" - which-builtin-type: "npm:^1.2.0" - checksum: 10c1/0395baf08804449608f33e52c8610de2719f4b11e68bf3fbc9786d89125c843bbb2e62ce890b0f41f9e15c77bad6e86ec1643b40807dbddb7bcf931bca8b80a3 + globalthis: "npm:^1.0.3" + which-builtin-type: "npm:^1.1.3" + checksum: 10c1/b33c9db4f9ca5b3d7b5d24e20453bd52c772213ba80c3a7adad5f1e1a76bdcceefcaf9d5df345cd405c8966aa35bdda22752494aade635a0f2cdd43a610f1f07 languageName: node linkType: hard @@ -13804,17 +13994,17 @@ __metadata: languageName: node linkType: hard -"regexpu-core@npm:^6.2.0": - version: 6.2.0 - resolution: "regexpu-core@npm:6.2.0" +"regexpu-core@npm:^6.1.1": + version: 6.1.1 + resolution: "regexpu-core@npm:6.1.1" dependencies: regenerate: "npm:^1.4.2" regenerate-unicode-properties: "npm:^10.2.0" regjsgen: "npm:^0.8.0" - regjsparser: "npm:^0.12.0" + regjsparser: "npm:^0.11.0" unicode-match-property-ecmascript: "npm:^2.0.0" unicode-match-property-value-ecmascript: "npm:^2.1.0" - checksum: 10c1/e3dc8cf29a8da7b41e8c104d0f63b41a13e3a8f5359718ec5c736212530ae4bfef4644013d870a114ebf83a00e3b2de4c404dabf6c6ef6169c196e5ffb2826f3 + checksum: 10c1/f4073a06b5787ef49bf7eb321813dc7bf87ca32944fdf26284042e2f6f0b6ccf21aaa14e2f29d7b7e6b2d990602b1e8edc7d8e9c524f4dc18520968a64c842a3 languageName: node linkType: hard @@ -13825,14 +14015,14 @@ __metadata: languageName: node linkType: hard -"regjsparser@npm:^0.12.0": - version: 0.12.0 - resolution: "regjsparser@npm:0.12.0" +"regjsparser@npm:^0.11.0": + version: 0.11.2 + resolution: "regjsparser@npm:0.11.2" dependencies: jsesc: "npm:~3.0.2" bin: regjsparser: bin/parser - checksum: 10c1/5610688d64a46f7d74c629d426ea641d71afb8e67be1b03ddd41552f87a7272c17d0b1c59a5813de063d29c8b1f4554b57316f918e857dcb4cd955f84601fb4c + checksum: 10c1/59b7c283120e89a181c3968984afae839616177675918d00b7dbfdcd2010b4faaaf86c9f5a33501f6cfe2a977d3df7672dd36d240f6431d2c0f9acd398db37e9 languageName: node linkType: hard @@ -13923,10 +14113,17 @@ __metadata: languageName: node linkType: hard +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10c1/e6f8dbe20ebbfdea61503670e5f325782e6d983e59e33c81b314a48910f2edcb9534975c5a14d789d2830c3ab3ae49f022dd6e2fdb56330f242ee3fbd60b46c5 + languageName: node + linkType: hard + "resolve.exports@npm:^2.0.0": - version: 2.0.3 - resolution: "resolve.exports@npm:2.0.3" - checksum: 10c1/2efa681fa50283a90917dece836454367e179f69106fe412a2dee5aae37a62ff3d6913f82073faf3841ff5fe51390b68a22621fd64326d90b8d87c76fce93b6d + version: 2.0.2 + resolution: "resolve.exports@npm:2.0.2" + checksum: 10c1/569870d3e57256d666b1f131fa610cfd9d97ef7121b21e9c0945b0ad699515c7c629df0b123ade91dc1e8ed85c5c065b47fbce7f6f55efaaa779922cb1009d05 languageName: node linkType: hard @@ -14049,17 +14246,6 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^5.0.5": - version: 5.0.10 - resolution: "rimraf@npm:5.0.10" - dependencies: - glob: "npm:^10.3.7" - bin: - rimraf: dist/esm/bin.mjs - checksum: 10c1/2e284052f1f291d9d8d4abf4a1d592d82912e381ae015e45a265100a467306e62f4e30d43ec32aa237c78f7a8271535da956b23d38dfe66fba801cb655ade488 - languageName: node - linkType: hard - "rtcpeerconnection-shim@npm:^1.2.15": version: 1.2.15 resolution: "rtcpeerconnection-shim@npm:1.2.15" @@ -14163,8 +14349,8 @@ __metadata: linkType: hard "sass@npm:^1.51.0": - version: 1.82.0 - resolution: "sass@npm:1.82.0" + version: 1.81.0 + resolution: "sass@npm:1.81.0" dependencies: "@parcel/watcher": "npm:^2.4.1" chokidar: "npm:^4.0.0" @@ -14175,7 +14361,7 @@ __metadata: optional: true bin: sass: sass.js - checksum: 10c1/3ebf8ab5d7d32e844776945e87759a670d96657a3e15d4a6f40f81ee831ad1428ccd7793b300fa0782390f58f008fa7d1d389f99413002a84bf5ea10c08e53ac + checksum: 10c1/3708b095d151b6fff9839b0d8e8b567eb47a88f79d3d47e54bbf0e6bd6c6b3cd3aa5e3db94ade2e2bb5378ca60fc21119d961d8170f8e3971e33f7c6480c0833 languageName: node linkType: hard @@ -14351,7 +14537,7 @@ __metadata: languageName: node linkType: hard -"set-function-length@npm:^1.2.2": +"set-function-length@npm:^1.2.1": version: 1.2.2 resolution: "set-function-length@npm:1.2.2" dependencies: @@ -14440,9 +14626,9 @@ __metadata: linkType: hard "shell-quote@npm:^1.8.1": - version: 1.8.2 - resolution: "shell-quote@npm:1.8.2" - checksum: 10c1/b3e15fbeae707770de937952d1b971794900f3a71b62cc52a7f66a9cc8ab68ef1817f1c1ff7d4d2875bbd47fdb3c402702c0b95353f441f0d027684e7ab13e44 + version: 1.8.1 + resolution: "shell-quote@npm:1.8.1" + checksum: 10c1/d74e85861f5d8e6b42138978aa3e63031a37595b2397c8775b1d0bdff4bc7cc72a2af8861e2bb6504f089e7b6ac2caf40d864487a4dccd84578c02045a0c4f68 languageName: node linkType: hard @@ -14581,13 +14767,13 @@ __metadata: linkType: hard "socks-proxy-agent@npm:^8.0.3": - version: 8.0.5 - resolution: "socks-proxy-agent@npm:8.0.5" + version: 8.0.4 + resolution: "socks-proxy-agent@npm:8.0.4" dependencies: - agent-base: "npm:^7.1.2" + agent-base: "npm:^7.1.1" debug: "npm:^4.3.4" socks: "npm:^2.8.3" - checksum: 10c1/64e0d4e021c2eaf5a2cc8629d9ad56b899d263535407ab65e12566ce11d89227a04d2f3ca4957ac910150b5976de70581361fbbcab810afbc43366dc529c9601 + checksum: 10c1/e8384370e674f10be774955abe35a1891cfadf532139371946a2054b767a4c61e1f9a5558ba743868fcacc0a76660ff39db9819575243373e61480dfddc557d1 languageName: node linkType: hard @@ -14601,6 +14787,13 @@ __metadata: languageName: node linkType: hard +"source-list-map@npm:^2.0.0": + version: 2.0.1 + resolution: "source-list-map@npm:2.0.1" + checksum: 10c1/bb3b0d59f90518f20b996eb57a29a62fbd0735f84649f2949591f01ef43a31a27b095a4c9b2f27edce4ea4c8d5b757b86476c532c813e1de84c340d7f2a41264 + languageName: node + linkType: hard + "source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" @@ -14718,12 +14911,12 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^12.0.0": - version: 12.0.0 - resolution: "ssri@npm:12.0.0" +"ssri@npm:^10.0.0": + version: 10.0.6 + resolution: "ssri@npm:10.0.6" dependencies: minipass: "npm:^7.0.3" - checksum: 10c1/8e6b9774dfac78e094fabb7ef1f2470172f994fe7086dc89cb1b82a1652b16cd73a83f69979365c2b9100ed9d32c1f416f1b17e29ce9121fb85e5d8461201276 + checksum: 10c1/11280a45cbdf1043c7b4f84790b0bbdd41f12579da743982c30ec2ad2df750bf376abd01a99a019c9b5260bb073cd17f7aa71032626dcbf3f15e82c2c3db81fd languageName: node linkType: hard @@ -15149,8 +15342,8 @@ __metadata: linkType: hard "tailwindcss@npm:^3.4.3": - version: 3.4.16 - resolution: "tailwindcss@npm:3.4.16" + version: 3.4.15 + resolution: "tailwindcss@npm:3.4.15" dependencies: "@alloc/quick-lru": "npm:^5.2.0" arg: "npm:^5.0.2" @@ -15161,7 +15354,7 @@ __metadata: glob-parent: "npm:^6.0.2" is-glob: "npm:^4.0.3" jiti: "npm:^1.21.6" - lilconfig: "npm:^3.1.3" + lilconfig: "npm:^2.1.0" micromatch: "npm:^4.0.8" normalize-path: "npm:^3.0.0" object-hash: "npm:^3.0.0" @@ -15177,7 +15370,7 @@ __metadata: bin: tailwind: lib/cli.js tailwindcss: lib/cli.js - checksum: 10c1/d8e350583239d54946ff1eb2b0b9051bc004a7ce2775470d796461adbe718851b7f044f398eee069b7e47314d919bfb439b1a5c4dc931a7ed282702504d61f52 + checksum: 10c1/61243c061fcd8839344254e0621f619e0dab0f80774e102f5eaed06350446b8467998676643bbef4571c923d84e1fed64a71308043c49d07c42e2ec21d5a9b92 languageName: node linkType: hard @@ -15188,7 +15381,7 @@ __metadata: languageName: node linkType: hard -"tar@npm:^6.1.11, tar@npm:^6.1.2": +"tar@npm:^6.1.11, tar@npm:^6.1.2, tar@npm:^6.2.1": version: 6.2.1 resolution: "tar@npm:6.2.1" dependencies: @@ -15202,20 +15395,6 @@ __metadata: languageName: node linkType: hard -"tar@npm:^7.4.3": - version: 7.4.3 - resolution: "tar@npm:7.4.3" - dependencies: - "@isaacs/fs-minipass": "npm:^4.0.0" - chownr: "npm:^3.0.0" - minipass: "npm:^7.1.2" - minizlib: "npm:^3.0.1" - mkdirp: "npm:^3.0.1" - yallist: "npm:^5.0.0" - checksum: 10c1/c61f0cd546860d743aef4692b10c83d2b823517b5d67f963588180954758a4e0aa1e2e5d26127c293dd9233192fda6ac99ea9f0e7af0c469414b12ee5dd02408 - languageName: node - linkType: hard - "term-img@npm:^4.0.0": version: 4.1.0 resolution: "term-img@npm:4.1.0" @@ -15249,8 +15428,8 @@ __metadata: linkType: hard "terser@npm:^5.10.0, terser@npm:^5.26.0": - version: 5.37.0 - resolution: "terser@npm:5.37.0" + version: 5.36.0 + resolution: "terser@npm:5.36.0" dependencies: "@jridgewell/source-map": "npm:^0.3.3" acorn: "npm:^8.8.2" @@ -15258,7 +15437,7 @@ __metadata: source-map-support: "npm:~0.5.20" bin: terser: bin/terser - checksum: 10c1/2d45cbf93135889c1b702628c7738d1e62d74a496b03048f2b91a49e58d245147d269430f4141b41d1280f17429460f331fd0e38f299d8d13b01b1f9f35509c7 + checksum: 10c1/67f5136619af64a7dff959cd792e1bb5682a9a75f4543a741cbf3345e9fc7aa2c1533bfd28a4aeebb3104321d5bdc19cccfd9ea05be168709dc3bcc07168ba06 languageName: node linkType: hard @@ -15391,21 +15570,21 @@ __metadata: languageName: node linkType: hard -"tldts-core@npm:^6.1.66": - version: 6.1.66 - resolution: "tldts-core@npm:6.1.66" - checksum: 10c1/eea093836ba71013e345ce63dcc5a5e13ba98909319421d2cd202ebd4ff69c3d57125cd90e562e9a96c7c9f33cc4331d30dd02717208824fd6eac93fbec4a50e +"tldts-core@npm:^6.1.61": + version: 6.1.61 + resolution: "tldts-core@npm:6.1.61" + checksum: 10c1/358c673af7697f38cb8837635fd2ac978dc24577cac96c1f2c1b03298752ca9c8d43f54a91ae7593d261c08abd3df8a22048732c1c22864e2612b98414946d18 languageName: node linkType: hard "tldts@npm:^6.1.32": - version: 6.1.66 - resolution: "tldts@npm:6.1.66" + version: 6.1.61 + resolution: "tldts@npm:6.1.61" dependencies: - tldts-core: "npm:^6.1.66" + tldts-core: "npm:^6.1.61" bin: tldts: bin/cli.js - checksum: 10c1/8ed2f7458d34cb6697f7d2ee6b450ab9a778bbfff54028b533cb0af113efb3108cbec10fe4c0066a8fd2a86ea24fe4f3414443b4d1b086c6fd739e001f919bb2 + checksum: 10c1/b638d1f9dbd29ceb16b055cc6faa393f3054f76c0cbcd6fd24fe18643779df5024e127b7e89f7ffad793667f83443bd20cf25b1f93bd5884d97264661b12c5a9 languageName: node linkType: hard @@ -15689,8 +15868,8 @@ __metadata: linkType: hard "typed-array-byte-offset@npm:^1.0.2": - version: 1.0.3 - resolution: "typed-array-byte-offset@npm:1.0.3" + version: 1.0.2 + resolution: "typed-array-byte-offset@npm:1.0.2" dependencies: available-typed-arrays: "npm:^1.0.7" call-bind: "npm:^1.0.7" @@ -15698,22 +15877,21 @@ __metadata: gopd: "npm:^1.0.1" has-proto: "npm:^1.0.3" is-typed-array: "npm:^1.1.13" - reflect.getprototypeof: "npm:^1.0.6" - checksum: 10c1/915e58e74c3d06a9eba27e0fe4d02b11882406dee484d3eebb1e00119981bc0d2f3ce66d7ef62ac516c263440dfd40696bc38aecd01973d5a4b9254c0efcc138 + checksum: 10c1/1a5cfe89447ea352520007d2d301a161d1e4138014a250173177cf3e457a80e514d3c2afbdf152159d10ca60e218939decde2d5f9c479e7121463a68e8da30c2 languageName: node linkType: hard "typed-array-length@npm:^1.0.6": - version: 1.0.7 - resolution: "typed-array-length@npm:1.0.7" + version: 1.0.6 + resolution: "typed-array-length@npm:1.0.6" dependencies: call-bind: "npm:^1.0.7" for-each: "npm:^0.3.3" gopd: "npm:^1.0.1" + has-proto: "npm:^1.0.3" is-typed-array: "npm:^1.1.13" possible-typed-array-names: "npm:^1.0.0" - reflect.getprototypeof: "npm:^1.0.6" - checksum: 10c1/8fb4acbfca60e6932e009ca9f176eced7077141dffe5579df297f863f9020156f33ff2d3900f330c72249b9784d6831c4a6b6688cddaf4d2324b4bc68f7c3f96 + checksum: 10c1/70822e3af9e85ac8dcc460f1d7d4b9faf527148887fdf108b3962600ce85afbc91310089a5fe9dca7bb5cd84fe08051c83c0df53478ec5f34735e3f70900fc39 languageName: node linkType: hard @@ -15756,10 +15934,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.20.0": - version: 6.20.0 - resolution: "undici-types@npm:6.20.0" - checksum: 10c1/b687f77207cead6b9d88da2b0ee2ba4154b4ac782635ed7803c8f1a15b2a5705b3c2f26d674fa80f2c26f5030ac2a425434b2462365ea3eee3beb5dad9cb73b6 +"undici-types@npm:~6.19.8": + version: 6.19.8 + resolution: "undici-types@npm:6.19.8" + checksum: 10c1/3b4edf7c8c5ba8c93f7e298e293fee95d24294c6523b3c5e81e7d754aa18008e5efe2949473ca2c05175cd5805160adb4efcd6b715b478c8d1b74f93b02417f0 languageName: node linkType: hard @@ -15810,12 +15988,12 @@ __metadata: languageName: node linkType: hard -"unique-filename@npm:^4.0.0": - version: 4.0.0 - resolution: "unique-filename@npm:4.0.0" +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" dependencies: - unique-slug: "npm:^5.0.0" - checksum: 10c1/649efbcc4a7080d0be238b788bf6536c0c9bc046318066cd44537e6947b54a255735fa5d85e5d243429d7bfec58d3a17c826f1639d0d2f51bfd56c7204cedb9a + unique-slug: "npm:^4.0.0" + checksum: 10c1/ad9defec358840603b8720e7813766d31b1a288788fd126b45b1e3c63056d186e5281164ad6bec61fd868cfef2c0b4f21e62931aa0259b54fe363c3f3ed94061 languageName: node linkType: hard @@ -15828,12 +16006,12 @@ __metadata: languageName: node linkType: hard -"unique-slug@npm:^5.0.0": - version: 5.0.0 - resolution: "unique-slug@npm:5.0.0" +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" dependencies: imurmurhash: "npm:^0.1.4" - checksum: 10c1/a1428735be030811a70571cf9d5cd79a5667887f34f69476801667269f23e8acecf1bd98634aee4b1c83cfb8cf53ce9b5da618d3558f1bc98ce770652f1f199d + checksum: 10c1/ed42be21f08611d3e0f5e98cb405c99aabfafc923c3bf065a1d6f9e15d7e1b16af979b72c6f0f4b3b30794321dd4497aa241b011477ef7a284f5d7e8db145591 languageName: node linkType: hard @@ -15913,23 +16091,23 @@ __metadata: linkType: hard "use-isomorphic-layout-effect@npm:^1.1.2": - version: 1.2.0 - resolution: "use-isomorphic-layout-effect@npm:1.2.0" + version: 1.1.2 + resolution: "use-isomorphic-layout-effect@npm:1.1.2" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c1/decd26ea500f6fa93319167b9b674532107c621ad7bf0e3ca79b5265289dc70e1979e49c4cb497595de1478706a0166bd43406378a0b1a6ce6143c02152ec311 + checksum: 10c1/fe447bec8d87c1af9c5a06cc01d0e985f0df95bde655b6addac36bec3af8e3b56b7b8997e90cf4f6b8567816d1d43db9cdb9e8a420f87d3b08d4a2a476f589b8 languageName: node linkType: hard "use-sync-external-store@npm:^1.2.0": - version: 1.4.0 - resolution: "use-sync-external-store@npm:1.4.0" + version: 1.2.2 + resolution: "use-sync-external-store@npm:1.2.2" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c1/25b3c65325a7f76104b6c79c5823fc5d52bad853b5fa8f64d4afca8ec4f66fd21f7b83ea224ee642dfc9fa69a89803eca1c939f8e3e6f0fdc548a5f50195dc50 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 10c1/7da66b44e231ded6fd5504534e9b1fbcc428d68c2b352dcc01e1c2724a71c609c58ad7979c983cf84b1343417875d966ac6af73bac80472059d9f5118e33d301 languageName: node linkType: hard @@ -16256,6 +16434,16 @@ __metadata: languageName: node linkType: hard +"webpack-sources@npm:^1.4.3": + version: 1.4.3 + resolution: "webpack-sources@npm:1.4.3" + dependencies: + source-list-map: "npm:^2.0.0" + source-map: "npm:~0.6.1" + checksum: 10c1/fc3c601c48df84178b6e8a297b3d844ea5580011b8cd7d382ffe0241b9fae1f44124337a2981d55f314cd4517f25d9fda20549cd96b279b47a00ac0727cea80f + languageName: node + linkType: hard + "webpack-sources@npm:^3.2.3": version: 3.2.3 resolution: "webpack-sources@npm:3.2.3" @@ -16264,14 +16452,14 @@ __metadata: linkType: hard "webpack@npm:^5.96.0": - version: 5.97.1 - resolution: "webpack@npm:5.97.1" + version: 5.96.1 + resolution: "webpack@npm:5.96.1" dependencies: "@types/eslint-scope": "npm:^3.7.7" "@types/estree": "npm:^1.0.6" - "@webassemblyjs/ast": "npm:^1.14.1" - "@webassemblyjs/wasm-edit": "npm:^1.14.1" - "@webassemblyjs/wasm-parser": "npm:^1.14.1" + "@webassemblyjs/ast": "npm:^1.12.1" + "@webassemblyjs/wasm-edit": "npm:^1.12.1" + "@webassemblyjs/wasm-parser": "npm:^1.12.1" acorn: "npm:^8.14.0" browserslist: "npm:^4.24.0" chrome-trace-event: "npm:^1.0.2" @@ -16295,7 +16483,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: 10c1/cd9c9afafd07fc11af877eb55e7f4703f54eaacc9453600547b5c61083972a5660536faf5e8539d410817de3ce790e102f067ba23e0f18b9049c882a0804acc3 + checksum: 10c1/04e558e12764c0216b0ec34554b27b5f0bb968886975e861d26d781f5fe44a6207b339f43f8baa4602bb9b085507d54e14bb9656b7a9c53dd6a9ea37c1b5a5f0 languageName: node linkType: hard @@ -16354,28 +16542,27 @@ __metadata: linkType: hard "which-boxed-primitive@npm:^1.0.2": - version: 1.1.0 - resolution: "which-boxed-primitive@npm:1.1.0" + version: 1.0.2 + resolution: "which-boxed-primitive@npm:1.0.2" dependencies: - is-bigint: "npm:^1.1.0" - is-boolean-object: "npm:^1.2.0" - is-number-object: "npm:^1.1.0" - is-string: "npm:^1.1.0" - is-symbol: "npm:^1.1.0" - checksum: 10c1/a888b54d184dfb1b8f901254b7d6f484231fe5141bf70953a0f5737fff9f52c9f6976f9eecb25cb6d5d15179ce8302ab48bac7994a723da22642f1cf524e45e6 + is-bigint: "npm:^1.0.1" + is-boolean-object: "npm:^1.1.0" + is-number-object: "npm:^1.0.4" + is-string: "npm:^1.0.5" + is-symbol: "npm:^1.0.3" + checksum: 10c1/99fdf145c47dab43190ea7a9a1c1b49079196b5f113383e285517d62ec26e324534abb1fbae45ad7088a95894f1afc77e6d3b5a182f43dc3c91680686b16474d languageName: node linkType: hard -"which-builtin-type@npm:^1.2.0": - version: 1.2.0 - resolution: "which-builtin-type@npm:1.2.0" +"which-builtin-type@npm:^1.1.3": + version: 1.1.4 + resolution: "which-builtin-type@npm:1.1.4" dependencies: - call-bind: "npm:^1.0.7" function.prototype.name: "npm:^1.1.6" has-tostringtag: "npm:^1.0.2" is-async-function: "npm:^2.0.0" is-date-object: "npm:^1.0.5" - is-finalizationregistry: "npm:^1.1.0" + is-finalizationregistry: "npm:^1.0.2" is-generator-function: "npm:^1.0.10" is-regex: "npm:^1.1.4" is-weakref: "npm:^1.0.2" @@ -16383,7 +16570,7 @@ __metadata: which-boxed-primitive: "npm:^1.0.2" which-collection: "npm:^1.0.2" which-typed-array: "npm:^1.1.15" - checksum: 10c1/1835de9511903b44e079472ef2641d13ca7957f05943848e7832b5a163a9fa3b5c02a4544ee3a335addeaa8ea32083ae68c228d2aa6ab9a41d4290e6354b1ecf + checksum: 10c1/e6f328b8bc49648cf1f4dc0bb64c92c465aab82971fb51ccddb3e711cb285cc228a1bf80dc8727421695a5299434fea7e1b13aacdb20781742d3ac91da39bb64 languageName: node linkType: hard @@ -16400,15 +16587,15 @@ __metadata: linkType: hard "which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15, which-typed-array@npm:^1.1.2": - version: 1.1.16 - resolution: "which-typed-array@npm:1.1.16" + version: 1.1.15 + resolution: "which-typed-array@npm:1.1.15" dependencies: available-typed-arrays: "npm:^1.0.7" call-bind: "npm:^1.0.7" for-each: "npm:^0.3.3" gopd: "npm:^1.0.1" has-tostringtag: "npm:^1.0.2" - checksum: 10c1/786c86250e41e4c550d1f8314eb3bc9df4258c8b12986745fc3ad96c7163f428062be799c87362d4ef092ac9073d8ebfc8837224a7a2d1a22e05160e709c1cc4 + checksum: 10c1/1c70dc3aec0ead7ce52f2532e8da3d8cc7ccaf006091159fb4393dd926cbd67eb546585528af68450279004faed1e33bee75747139fb8639f49a7249a84c4827 languageName: node linkType: hard @@ -16434,14 +16621,14 @@ __metadata: languageName: node linkType: hard -"which@npm:^5.0.0": - version: 5.0.0 - resolution: "which@npm:5.0.0" +"which@npm:^4.0.0": + version: 4.0.0 + resolution: "which@npm:4.0.0" dependencies: isexe: "npm:^3.1.1" bin: node-which: bin/which.js - checksum: 10c1/6795295e7145329c255302b580215a14527a9a267e4e72717a99d7b589bd849275e6d9db7a9fe415106c8a9361380525e76457ee93e6e4ca634dcd6f46e1a5b5 + checksum: 10c1/13754c6d676ba12a26c6bdb7a685c5aa939743366d8dc04ae6d99cd3660456333b3b20d7f26684b1abb444d0e5ed9b22263e6f846e573e738d4850b4caeb99f6 languageName: node linkType: hard @@ -16621,13 +16808,6 @@ __metadata: languageName: node linkType: hard -"yallist@npm:^5.0.0": - version: 5.0.0 - resolution: "yallist@npm:5.0.0" - checksum: 10c1/0982b922dd395bd537a4a2fabcff11142a327a3258a2923c0e10d382ebe5bd2f4874b4d2c188f60d109db3d056563faae91165f721462a3dbccc5fcb364aeaf4 - languageName: node - linkType: hard - "yaml@npm:^1.10.0": version: 1.10.2 resolution: "yaml@npm:1.10.2" @@ -16636,11 +16816,11 @@ __metadata: linkType: hard "yaml@npm:^2.3.4": - version: 2.6.1 - resolution: "yaml@npm:2.6.1" + version: 2.6.0 + resolution: "yaml@npm:2.6.0" bin: yaml: bin.mjs - checksum: 10c1/788cb0a03c091654798798c42eb580c36e1050440884d315e1d9971d9ff476e19fe43e54c5406a05c476e01f0eec43d0e10329442b6b582f82c1b771b1a4ecb8 + checksum: 10c1/c63edcbe8c2ec2ef0c49c91dec77dd4952106080a63abd786e171f609b5d2168200df2c2b6cc9a3169579f057a4a6f30367631714dcb172b010b795d23f86209 languageName: node linkType: hard diff --git a/scripts/schema/db/init_dbs/clickhouse/1.22.0/1.22.0.sql b/scripts/schema/db/init_dbs/clickhouse/1.22.0/1.22.0.sql new file mode 100644 index 000000000..7a48eba7e --- /dev/null +++ b/scripts/schema/db/init_dbs/clickhouse/1.22.0/1.22.0.sql @@ -0,0 +1,195 @@ +CREATE OR REPLACE FUNCTION openreplay_version AS() -> 'v1.22.0'; +CREATE DATABASE IF NOT EXISTS experimental; + +CREATE TABLE IF NOT EXISTS experimental.autocomplete +( + project_id UInt16, + type LowCardinality(String), + value String, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMM(_timestamp) + ORDER BY (project_id, type, value) + TTL _timestamp + INTERVAL 1 MONTH; + +CREATE TABLE IF NOT EXISTS experimental.events +( + session_id UInt64, + project_id UInt16, + event_type Enum8('CLICK'=0, 'INPUT'=1, 'LOCATION'=2,'REQUEST'=3,'PERFORMANCE'=4,'ERROR'=5,'CUSTOM'=6, 'GRAPHQL'=7, 'STATEACTION'=8, 'ISSUE'=9), + datetime DateTime, + label Nullable(String), + hesitation_time Nullable(UInt32), + name Nullable(String), + payload Nullable(String), + level Nullable(Enum8('info'=0, 'error'=1)) DEFAULT if(event_type == 'CUSTOM', 'info', null), + source Nullable(Enum8('js_exception'=0, 'bugsnag'=1, 'cloudwatch'=2, 'datadog'=3, 'elasticsearch'=4, 'newrelic'=5, 'rollbar'=6, 'sentry'=7, 'stackdriver'=8, 'sumologic'=9)), + message Nullable(String), + error_id Nullable(String), + duration Nullable(UInt16), + context Nullable(Enum8('unknown'=0, 'self'=1, 'same-origin-ancestor'=2, 'same-origin-descendant'=3, 'same-origin'=4, 'cross-origin-ancestor'=5, 'cross-origin-descendant'=6, 'cross-origin-unreachable'=7, 'multiple-contexts'=8)), + url Nullable(String), + url_host Nullable(String) MATERIALIZED lower(domain(url)), + url_path Nullable(String), + url_hostpath Nullable(String) MATERIALIZED concat(url_host, url_path), + request_start Nullable(UInt16), + response_start Nullable(UInt16), + response_end Nullable(UInt16), + dom_content_loaded_event_start Nullable(UInt16), + dom_content_loaded_event_end Nullable(UInt16), + load_event_start Nullable(UInt16), + load_event_end Nullable(UInt16), + first_paint Nullable(UInt16), + first_contentful_paint_time Nullable(UInt16), + speed_index Nullable(UInt16), + visually_complete Nullable(UInt16), + time_to_interactive Nullable(UInt16), + ttfb Nullable(UInt16) MATERIALIZED if(greaterOrEquals(response_start, request_start), + minus(response_start, request_start), Null), + ttlb Nullable(UInt16) MATERIALIZED if(greaterOrEquals(response_end, request_start), + minus(response_end, request_start), Null), + response_time Nullable(UInt16) MATERIALIZED if(greaterOrEquals(response_end, response_start), + minus(response_end, response_start), Null), + dom_building_time Nullable(UInt16) MATERIALIZED if( + greaterOrEquals(dom_content_loaded_event_start, response_end), + minus(dom_content_loaded_event_start, response_end), Null), + dom_content_loaded_event_time Nullable(UInt16) MATERIALIZED if( + greaterOrEquals(dom_content_loaded_event_end, dom_content_loaded_event_start), + minus(dom_content_loaded_event_end, dom_content_loaded_event_start), Null), + load_event_time Nullable(UInt16) MATERIALIZED if(greaterOrEquals(load_event_end, load_event_start), + minus(load_event_end, load_event_start), Null), + min_fps Nullable(UInt8), + avg_fps Nullable(UInt8), + max_fps Nullable(UInt8), + min_cpu Nullable(UInt8), + avg_cpu Nullable(UInt8), + max_cpu Nullable(UInt8), + min_total_js_heap_size Nullable(UInt64), + avg_total_js_heap_size Nullable(UInt64), + max_total_js_heap_size Nullable(UInt64), + min_used_js_heap_size Nullable(UInt64), + avg_used_js_heap_size Nullable(UInt64), + max_used_js_heap_size Nullable(UInt64), + method Nullable(Enum8('GET' = 0, 'HEAD' = 1, 'POST' = 2, 'PUT' = 3, 'DELETE' = 4, 'CONNECT' = 5, 'OPTIONS' = 6, 'TRACE' = 7, 'PATCH' = 8)), + status Nullable(UInt16), + success Nullable(UInt8), + request_body Nullable(String), + response_body Nullable(String), + issue_type Nullable(Enum8('click_rage'=1,'dead_click'=2,'excessive_scrolling'=3,'bad_request'=4,'missing_resource'=5,'memory'=6,'cpu'=7,'slow_resource'=8,'slow_page_load'=9,'crash'=10,'ml_cpu'=11,'ml_memory'=12,'ml_dead_click'=13,'ml_click_rage'=14,'ml_mouse_thrashing'=15,'ml_excessive_scrolling'=16,'ml_slow_resources'=17,'custom'=18,'js_exception'=19,'mouse_thrashing'=20,'app_crash'=21)), + issue_id Nullable(String), + error_tags_keys Array(String), + error_tags_values Array(Nullable(String)), + transfer_size Nullable(UInt32), + selector Nullable(String), + normalized_x Nullable(Float32), + normalized_y Nullable(Float32), + message_id UInt64 DEFAULT 0, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMM(datetime) + ORDER BY (project_id, datetime, event_type, session_id, message_id) + TTL datetime + INTERVAL 1 MONTH; + + + +CREATE TABLE IF NOT EXISTS experimental.sessions +( + session_id UInt64, + project_id UInt16, + tracker_version LowCardinality(String), + rev_id LowCardinality(Nullable(String)), + user_uuid UUID, + user_os LowCardinality(String), + user_os_version LowCardinality(Nullable(String)), + user_browser LowCardinality(String), + user_browser_version LowCardinality(Nullable(String)), + user_device Nullable(String), + user_device_type Enum8('other'=0, 'desktop'=1, 'mobile'=2,'tablet'=3), + user_country Enum8('UN'=-128, 'RW'=-127, 'SO'=-126, 'YE'=-125, 'IQ'=-124, 'SA'=-123, 'IR'=-122, 'CY'=-121, 'TZ'=-120, 'SY'=-119, 'AM'=-118, 'KE'=-117, 'CD'=-116, 'DJ'=-115, 'UG'=-114, 'CF'=-113, 'SC'=-112, 'JO'=-111, 'LB'=-110, 'KW'=-109, 'OM'=-108, 'QA'=-107, 'BH'=-106, 'AE'=-105, 'IL'=-104, 'TR'=-103, 'ET'=-102, 'ER'=-101, 'EG'=-100, 'SD'=-99, 'GR'=-98, 'BI'=-97, 'EE'=-96, 'LV'=-95, 'AZ'=-94, 'LT'=-93, 'SJ'=-92, 'GE'=-91, 'MD'=-90, 'BY'=-89, 'FI'=-88, 'AX'=-87, 'UA'=-86, 'MK'=-85, 'HU'=-84, 'BG'=-83, 'AL'=-82, 'PL'=-81, 'RO'=-80, 'XK'=-79, 'ZW'=-78, 'ZM'=-77, 'KM'=-76, 'MW'=-75, 'LS'=-74, 'BW'=-73, 'MU'=-72, 'SZ'=-71, 'RE'=-70, 'ZA'=-69, 'YT'=-68, 'MZ'=-67, 'MG'=-66, 'AF'=-65, 'PK'=-64, 'BD'=-63, 'TM'=-62, 'TJ'=-61, 'LK'=-60, 'BT'=-59, 'IN'=-58, 'MV'=-57, 'IO'=-56, 'NP'=-55, 'MM'=-54, 'UZ'=-53, 'KZ'=-52, 'KG'=-51, 'TF'=-50, 'HM'=-49, 'CC'=-48, 'PW'=-47, 'VN'=-46, 'TH'=-45, 'ID'=-44, 'LA'=-43, 'TW'=-42, 'PH'=-41, 'MY'=-40, 'CN'=-39, 'HK'=-38, 'BN'=-37, 'MO'=-36, 'KH'=-35, 'KR'=-34, 'JP'=-33, 'KP'=-32, 'SG'=-31, 'CK'=-30, 'TL'=-29, 'RU'=-28, 'MN'=-27, 'AU'=-26, 'CX'=-25, 'MH'=-24, 'FM'=-23, 'PG'=-22, 'SB'=-21, 'TV'=-20, 'NR'=-19, 'VU'=-18, 'NC'=-17, 'NF'=-16, 'NZ'=-15, 'FJ'=-14, 'LY'=-13, 'CM'=-12, 'SN'=-11, 'CG'=-10, 'PT'=-9, 'LR'=-8, 'CI'=-7, 'GH'=-6, 'GQ'=-5, 'NG'=-4, 'BF'=-3, 'TG'=-2, 'GW'=-1, 'MR'=0, 'BJ'=1, 'GA'=2, 'SL'=3, 'ST'=4, 'GI'=5, 'GM'=6, 'GN'=7, 'TD'=8, 'NE'=9, 'ML'=10, 'EH'=11, 'TN'=12, 'ES'=13, 'MA'=14, 'MT'=15, 'DZ'=16, 'FO'=17, 'DK'=18, 'IS'=19, 'GB'=20, 'CH'=21, 'SE'=22, 'NL'=23, 'AT'=24, 'BE'=25, 'DE'=26, 'LU'=27, 'IE'=28, 'MC'=29, 'FR'=30, 'AD'=31, 'LI'=32, 'JE'=33, 'IM'=34, 'GG'=35, 'SK'=36, 'CZ'=37, 'NO'=38, 'VA'=39, 'SM'=40, 'IT'=41, 'SI'=42, 'ME'=43, 'HR'=44, 'BA'=45, 'AO'=46, 'NA'=47, 'SH'=48, 'BV'=49, 'BB'=50, 'CV'=51, 'GY'=52, 'GF'=53, 'SR'=54, 'PM'=55, 'GL'=56, 'PY'=57, 'UY'=58, 'BR'=59, 'FK'=60, 'GS'=61, 'JM'=62, 'DO'=63, 'CU'=64, 'MQ'=65, 'BS'=66, 'BM'=67, 'AI'=68, 'TT'=69, 'KN'=70, 'DM'=71, 'AG'=72, 'LC'=73, 'TC'=74, 'AW'=75, 'VG'=76, 'VC'=77, 'MS'=78, 'MF'=79, 'BL'=80, 'GP'=81, 'GD'=82, 'KY'=83, 'BZ'=84, 'SV'=85, 'GT'=86, 'HN'=87, 'NI'=88, 'CR'=89, 'VE'=90, 'EC'=91, 'CO'=92, 'PA'=93, 'HT'=94, 'AR'=95, 'CL'=96, 'BO'=97, 'PE'=98, 'MX'=99, 'PF'=100, 'PN'=101, 'KI'=102, 'TK'=103, 'TO'=104, 'WF'=105, 'WS'=106, 'NU'=107, 'MP'=108, 'GU'=109, 'PR'=110, 'VI'=111, 'UM'=112, 'AS'=113, 'CA'=114, 'US'=115, 'PS'=116, 'RS'=117, 'AQ'=118, 'SX'=119, 'CW'=120, 'BQ'=121, 'SS'=122,'BU'=123, 'VD'=124, 'YD'=125, 'DD'=126), + user_city LowCardinality(String), + user_state LowCardinality(String), + platform Enum8('web'=1,'ios'=2,'android'=3) DEFAULT 'web', + datetime DateTime, + timezone LowCardinality(Nullable(String)), + duration UInt32, + pages_count UInt16, + events_count UInt16, + errors_count UInt16, + utm_source Nullable(String), + utm_medium Nullable(String), + utm_campaign Nullable(String), + user_id Nullable(String), + user_anonymous_id Nullable(String), + issue_types Array(LowCardinality(String)), + referrer Nullable(String), + base_referrer Nullable(String) MATERIALIZED lower(concat(domain(referrer), path(referrer))), + issue_score Nullable(UInt32), + screen_width Nullable(Int16), + screen_height Nullable(Int16), + metadata_1 Nullable(String), + metadata_2 Nullable(String), + metadata_3 Nullable(String), + metadata_4 Nullable(String), + metadata_5 Nullable(String), + metadata_6 Nullable(String), + metadata_7 Nullable(String), + metadata_8 Nullable(String), + metadata_9 Nullable(String), + metadata_10 Nullable(String), + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMMDD(datetime) + ORDER BY (project_id, datetime, session_id) + TTL datetime + INTERVAL 1 MONTH + SETTINGS index_granularity = 512; + +CREATE TABLE IF NOT EXISTS experimental.issues +( + project_id UInt16, + issue_id String, + type Enum8('click_rage'=1,'dead_click'=2,'excessive_scrolling'=3,'bad_request'=4,'missing_resource'=5,'memory'=6,'cpu'=7,'slow_resource'=8,'slow_page_load'=9,'crash'=10,'ml_cpu'=11,'ml_memory'=12,'ml_dead_click'=13,'ml_click_rage'=14,'ml_mouse_thrashing'=15,'ml_excessive_scrolling'=16,'ml_slow_resources'=17,'custom'=18,'js_exception'=19,'mouse_thrashing'=20,'app_crash'=21), + context_string String, + context_keys Array(String), + context_values Array(Nullable(String)), + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMM(_timestamp) + ORDER BY (project_id, issue_id, type) + TTL _timestamp + INTERVAL 1 MONTH; + +CREATE TABLE IF NOT EXISTS experimental.ios_events +( + session_id UInt64, + project_id UInt16, + event_type Enum8('TAP'=0, 'INPUT'=1, 'SWIPE'=2, 'VIEW'=3,'REQUEST'=4,'CRASH'=5,'CUSTOM'=6, 'STATEACTION'=8, 'ISSUE'=9), + datetime DateTime, + label Nullable(String), + name Nullable(String), + payload Nullable(String), + level Nullable(Enum8('info'=0, 'error'=1)) DEFAULT if(event_type == 'CUSTOM', 'info', null), + context Nullable(Enum8('unknown'=0, 'self'=1, 'same-origin-ancestor'=2, 'same-origin-descendant'=3, 'same-origin'=4, 'cross-origin-ancestor'=5, 'cross-origin-descendant'=6, 'cross-origin-unreachable'=7, 'multiple-contexts'=8)), + url Nullable(String), + url_host Nullable(String) MATERIALIZED lower(domain(url)), + url_path Nullable(String), + url_hostpath Nullable(String) MATERIALIZED concat(url_host, url_path), + request_start Nullable(UInt16), + response_start Nullable(UInt16), + response_end Nullable(UInt16), + method Nullable(Enum8('GET' = 0, 'HEAD' = 1, 'POST' = 2, 'PUT' = 3, 'DELETE' = 4, 'CONNECT' = 5, 'OPTIONS' = 6, 'TRACE' = 7, 'PATCH' = 8)), + status Nullable(UInt16), + duration Nullable(UInt16), + success Nullable(UInt8), + request_body Nullable(String), + response_body Nullable(String), + issue_type Nullable(Enum8('tap_rage'=1,'dead_click'=2,'excessive_scrolling'=3,'bad_request'=4,'missing_resource'=5,'memory'=6,'cpu'=7,'slow_resource'=8,'slow_page_load'=9,'crash'=10,'ml_cpu'=11,'ml_memory'=12,'ml_dead_click'=13,'ml_click_rage'=14,'ml_mouse_thrashing'=15,'ml_excessive_scrolling'=16,'ml_slow_resources'=17,'custom'=18,'js_exception'=19,'mouse_thrashing'=20,'app_crash'=21)), + issue_id Nullable(String), + transfer_size Nullable(UInt32), + direction Nullable(String), + reason Nullable(String), + stacktrace Nullable(String), + message_id UInt64 DEFAULT 0, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMM(datetime) + ORDER BY (project_id, datetime, event_type, session_id, message_id) + TTL datetime + INTERVAL 1 MONTH; \ No newline at end of file diff --git a/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql b/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql new file mode 100644 index 000000000..7a48eba7e --- /dev/null +++ b/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql @@ -0,0 +1,195 @@ +CREATE OR REPLACE FUNCTION openreplay_version AS() -> 'v1.22.0'; +CREATE DATABASE IF NOT EXISTS experimental; + +CREATE TABLE IF NOT EXISTS experimental.autocomplete +( + project_id UInt16, + type LowCardinality(String), + value String, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMM(_timestamp) + ORDER BY (project_id, type, value) + TTL _timestamp + INTERVAL 1 MONTH; + +CREATE TABLE IF NOT EXISTS experimental.events +( + session_id UInt64, + project_id UInt16, + event_type Enum8('CLICK'=0, 'INPUT'=1, 'LOCATION'=2,'REQUEST'=3,'PERFORMANCE'=4,'ERROR'=5,'CUSTOM'=6, 'GRAPHQL'=7, 'STATEACTION'=8, 'ISSUE'=9), + datetime DateTime, + label Nullable(String), + hesitation_time Nullable(UInt32), + name Nullable(String), + payload Nullable(String), + level Nullable(Enum8('info'=0, 'error'=1)) DEFAULT if(event_type == 'CUSTOM', 'info', null), + source Nullable(Enum8('js_exception'=0, 'bugsnag'=1, 'cloudwatch'=2, 'datadog'=3, 'elasticsearch'=4, 'newrelic'=5, 'rollbar'=6, 'sentry'=7, 'stackdriver'=8, 'sumologic'=9)), + message Nullable(String), + error_id Nullable(String), + duration Nullable(UInt16), + context Nullable(Enum8('unknown'=0, 'self'=1, 'same-origin-ancestor'=2, 'same-origin-descendant'=3, 'same-origin'=4, 'cross-origin-ancestor'=5, 'cross-origin-descendant'=6, 'cross-origin-unreachable'=7, 'multiple-contexts'=8)), + url Nullable(String), + url_host Nullable(String) MATERIALIZED lower(domain(url)), + url_path Nullable(String), + url_hostpath Nullable(String) MATERIALIZED concat(url_host, url_path), + request_start Nullable(UInt16), + response_start Nullable(UInt16), + response_end Nullable(UInt16), + dom_content_loaded_event_start Nullable(UInt16), + dom_content_loaded_event_end Nullable(UInt16), + load_event_start Nullable(UInt16), + load_event_end Nullable(UInt16), + first_paint Nullable(UInt16), + first_contentful_paint_time Nullable(UInt16), + speed_index Nullable(UInt16), + visually_complete Nullable(UInt16), + time_to_interactive Nullable(UInt16), + ttfb Nullable(UInt16) MATERIALIZED if(greaterOrEquals(response_start, request_start), + minus(response_start, request_start), Null), + ttlb Nullable(UInt16) MATERIALIZED if(greaterOrEquals(response_end, request_start), + minus(response_end, request_start), Null), + response_time Nullable(UInt16) MATERIALIZED if(greaterOrEquals(response_end, response_start), + minus(response_end, response_start), Null), + dom_building_time Nullable(UInt16) MATERIALIZED if( + greaterOrEquals(dom_content_loaded_event_start, response_end), + minus(dom_content_loaded_event_start, response_end), Null), + dom_content_loaded_event_time Nullable(UInt16) MATERIALIZED if( + greaterOrEquals(dom_content_loaded_event_end, dom_content_loaded_event_start), + minus(dom_content_loaded_event_end, dom_content_loaded_event_start), Null), + load_event_time Nullable(UInt16) MATERIALIZED if(greaterOrEquals(load_event_end, load_event_start), + minus(load_event_end, load_event_start), Null), + min_fps Nullable(UInt8), + avg_fps Nullable(UInt8), + max_fps Nullable(UInt8), + min_cpu Nullable(UInt8), + avg_cpu Nullable(UInt8), + max_cpu Nullable(UInt8), + min_total_js_heap_size Nullable(UInt64), + avg_total_js_heap_size Nullable(UInt64), + max_total_js_heap_size Nullable(UInt64), + min_used_js_heap_size Nullable(UInt64), + avg_used_js_heap_size Nullable(UInt64), + max_used_js_heap_size Nullable(UInt64), + method Nullable(Enum8('GET' = 0, 'HEAD' = 1, 'POST' = 2, 'PUT' = 3, 'DELETE' = 4, 'CONNECT' = 5, 'OPTIONS' = 6, 'TRACE' = 7, 'PATCH' = 8)), + status Nullable(UInt16), + success Nullable(UInt8), + request_body Nullable(String), + response_body Nullable(String), + issue_type Nullable(Enum8('click_rage'=1,'dead_click'=2,'excessive_scrolling'=3,'bad_request'=4,'missing_resource'=5,'memory'=6,'cpu'=7,'slow_resource'=8,'slow_page_load'=9,'crash'=10,'ml_cpu'=11,'ml_memory'=12,'ml_dead_click'=13,'ml_click_rage'=14,'ml_mouse_thrashing'=15,'ml_excessive_scrolling'=16,'ml_slow_resources'=17,'custom'=18,'js_exception'=19,'mouse_thrashing'=20,'app_crash'=21)), + issue_id Nullable(String), + error_tags_keys Array(String), + error_tags_values Array(Nullable(String)), + transfer_size Nullable(UInt32), + selector Nullable(String), + normalized_x Nullable(Float32), + normalized_y Nullable(Float32), + message_id UInt64 DEFAULT 0, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMM(datetime) + ORDER BY (project_id, datetime, event_type, session_id, message_id) + TTL datetime + INTERVAL 1 MONTH; + + + +CREATE TABLE IF NOT EXISTS experimental.sessions +( + session_id UInt64, + project_id UInt16, + tracker_version LowCardinality(String), + rev_id LowCardinality(Nullable(String)), + user_uuid UUID, + user_os LowCardinality(String), + user_os_version LowCardinality(Nullable(String)), + user_browser LowCardinality(String), + user_browser_version LowCardinality(Nullable(String)), + user_device Nullable(String), + user_device_type Enum8('other'=0, 'desktop'=1, 'mobile'=2,'tablet'=3), + user_country Enum8('UN'=-128, 'RW'=-127, 'SO'=-126, 'YE'=-125, 'IQ'=-124, 'SA'=-123, 'IR'=-122, 'CY'=-121, 'TZ'=-120, 'SY'=-119, 'AM'=-118, 'KE'=-117, 'CD'=-116, 'DJ'=-115, 'UG'=-114, 'CF'=-113, 'SC'=-112, 'JO'=-111, 'LB'=-110, 'KW'=-109, 'OM'=-108, 'QA'=-107, 'BH'=-106, 'AE'=-105, 'IL'=-104, 'TR'=-103, 'ET'=-102, 'ER'=-101, 'EG'=-100, 'SD'=-99, 'GR'=-98, 'BI'=-97, 'EE'=-96, 'LV'=-95, 'AZ'=-94, 'LT'=-93, 'SJ'=-92, 'GE'=-91, 'MD'=-90, 'BY'=-89, 'FI'=-88, 'AX'=-87, 'UA'=-86, 'MK'=-85, 'HU'=-84, 'BG'=-83, 'AL'=-82, 'PL'=-81, 'RO'=-80, 'XK'=-79, 'ZW'=-78, 'ZM'=-77, 'KM'=-76, 'MW'=-75, 'LS'=-74, 'BW'=-73, 'MU'=-72, 'SZ'=-71, 'RE'=-70, 'ZA'=-69, 'YT'=-68, 'MZ'=-67, 'MG'=-66, 'AF'=-65, 'PK'=-64, 'BD'=-63, 'TM'=-62, 'TJ'=-61, 'LK'=-60, 'BT'=-59, 'IN'=-58, 'MV'=-57, 'IO'=-56, 'NP'=-55, 'MM'=-54, 'UZ'=-53, 'KZ'=-52, 'KG'=-51, 'TF'=-50, 'HM'=-49, 'CC'=-48, 'PW'=-47, 'VN'=-46, 'TH'=-45, 'ID'=-44, 'LA'=-43, 'TW'=-42, 'PH'=-41, 'MY'=-40, 'CN'=-39, 'HK'=-38, 'BN'=-37, 'MO'=-36, 'KH'=-35, 'KR'=-34, 'JP'=-33, 'KP'=-32, 'SG'=-31, 'CK'=-30, 'TL'=-29, 'RU'=-28, 'MN'=-27, 'AU'=-26, 'CX'=-25, 'MH'=-24, 'FM'=-23, 'PG'=-22, 'SB'=-21, 'TV'=-20, 'NR'=-19, 'VU'=-18, 'NC'=-17, 'NF'=-16, 'NZ'=-15, 'FJ'=-14, 'LY'=-13, 'CM'=-12, 'SN'=-11, 'CG'=-10, 'PT'=-9, 'LR'=-8, 'CI'=-7, 'GH'=-6, 'GQ'=-5, 'NG'=-4, 'BF'=-3, 'TG'=-2, 'GW'=-1, 'MR'=0, 'BJ'=1, 'GA'=2, 'SL'=3, 'ST'=4, 'GI'=5, 'GM'=6, 'GN'=7, 'TD'=8, 'NE'=9, 'ML'=10, 'EH'=11, 'TN'=12, 'ES'=13, 'MA'=14, 'MT'=15, 'DZ'=16, 'FO'=17, 'DK'=18, 'IS'=19, 'GB'=20, 'CH'=21, 'SE'=22, 'NL'=23, 'AT'=24, 'BE'=25, 'DE'=26, 'LU'=27, 'IE'=28, 'MC'=29, 'FR'=30, 'AD'=31, 'LI'=32, 'JE'=33, 'IM'=34, 'GG'=35, 'SK'=36, 'CZ'=37, 'NO'=38, 'VA'=39, 'SM'=40, 'IT'=41, 'SI'=42, 'ME'=43, 'HR'=44, 'BA'=45, 'AO'=46, 'NA'=47, 'SH'=48, 'BV'=49, 'BB'=50, 'CV'=51, 'GY'=52, 'GF'=53, 'SR'=54, 'PM'=55, 'GL'=56, 'PY'=57, 'UY'=58, 'BR'=59, 'FK'=60, 'GS'=61, 'JM'=62, 'DO'=63, 'CU'=64, 'MQ'=65, 'BS'=66, 'BM'=67, 'AI'=68, 'TT'=69, 'KN'=70, 'DM'=71, 'AG'=72, 'LC'=73, 'TC'=74, 'AW'=75, 'VG'=76, 'VC'=77, 'MS'=78, 'MF'=79, 'BL'=80, 'GP'=81, 'GD'=82, 'KY'=83, 'BZ'=84, 'SV'=85, 'GT'=86, 'HN'=87, 'NI'=88, 'CR'=89, 'VE'=90, 'EC'=91, 'CO'=92, 'PA'=93, 'HT'=94, 'AR'=95, 'CL'=96, 'BO'=97, 'PE'=98, 'MX'=99, 'PF'=100, 'PN'=101, 'KI'=102, 'TK'=103, 'TO'=104, 'WF'=105, 'WS'=106, 'NU'=107, 'MP'=108, 'GU'=109, 'PR'=110, 'VI'=111, 'UM'=112, 'AS'=113, 'CA'=114, 'US'=115, 'PS'=116, 'RS'=117, 'AQ'=118, 'SX'=119, 'CW'=120, 'BQ'=121, 'SS'=122,'BU'=123, 'VD'=124, 'YD'=125, 'DD'=126), + user_city LowCardinality(String), + user_state LowCardinality(String), + platform Enum8('web'=1,'ios'=2,'android'=3) DEFAULT 'web', + datetime DateTime, + timezone LowCardinality(Nullable(String)), + duration UInt32, + pages_count UInt16, + events_count UInt16, + errors_count UInt16, + utm_source Nullable(String), + utm_medium Nullable(String), + utm_campaign Nullable(String), + user_id Nullable(String), + user_anonymous_id Nullable(String), + issue_types Array(LowCardinality(String)), + referrer Nullable(String), + base_referrer Nullable(String) MATERIALIZED lower(concat(domain(referrer), path(referrer))), + issue_score Nullable(UInt32), + screen_width Nullable(Int16), + screen_height Nullable(Int16), + metadata_1 Nullable(String), + metadata_2 Nullable(String), + metadata_3 Nullable(String), + metadata_4 Nullable(String), + metadata_5 Nullable(String), + metadata_6 Nullable(String), + metadata_7 Nullable(String), + metadata_8 Nullable(String), + metadata_9 Nullable(String), + metadata_10 Nullable(String), + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMMDD(datetime) + ORDER BY (project_id, datetime, session_id) + TTL datetime + INTERVAL 1 MONTH + SETTINGS index_granularity = 512; + +CREATE TABLE IF NOT EXISTS experimental.issues +( + project_id UInt16, + issue_id String, + type Enum8('click_rage'=1,'dead_click'=2,'excessive_scrolling'=3,'bad_request'=4,'missing_resource'=5,'memory'=6,'cpu'=7,'slow_resource'=8,'slow_page_load'=9,'crash'=10,'ml_cpu'=11,'ml_memory'=12,'ml_dead_click'=13,'ml_click_rage'=14,'ml_mouse_thrashing'=15,'ml_excessive_scrolling'=16,'ml_slow_resources'=17,'custom'=18,'js_exception'=19,'mouse_thrashing'=20,'app_crash'=21), + context_string String, + context_keys Array(String), + context_values Array(Nullable(String)), + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMM(_timestamp) + ORDER BY (project_id, issue_id, type) + TTL _timestamp + INTERVAL 1 MONTH; + +CREATE TABLE IF NOT EXISTS experimental.ios_events +( + session_id UInt64, + project_id UInt16, + event_type Enum8('TAP'=0, 'INPUT'=1, 'SWIPE'=2, 'VIEW'=3,'REQUEST'=4,'CRASH'=5,'CUSTOM'=6, 'STATEACTION'=8, 'ISSUE'=9), + datetime DateTime, + label Nullable(String), + name Nullable(String), + payload Nullable(String), + level Nullable(Enum8('info'=0, 'error'=1)) DEFAULT if(event_type == 'CUSTOM', 'info', null), + context Nullable(Enum8('unknown'=0, 'self'=1, 'same-origin-ancestor'=2, 'same-origin-descendant'=3, 'same-origin'=4, 'cross-origin-ancestor'=5, 'cross-origin-descendant'=6, 'cross-origin-unreachable'=7, 'multiple-contexts'=8)), + url Nullable(String), + url_host Nullable(String) MATERIALIZED lower(domain(url)), + url_path Nullable(String), + url_hostpath Nullable(String) MATERIALIZED concat(url_host, url_path), + request_start Nullable(UInt16), + response_start Nullable(UInt16), + response_end Nullable(UInt16), + method Nullable(Enum8('GET' = 0, 'HEAD' = 1, 'POST' = 2, 'PUT' = 3, 'DELETE' = 4, 'CONNECT' = 5, 'OPTIONS' = 6, 'TRACE' = 7, 'PATCH' = 8)), + status Nullable(UInt16), + duration Nullable(UInt16), + success Nullable(UInt8), + request_body Nullable(String), + response_body Nullable(String), + issue_type Nullable(Enum8('tap_rage'=1,'dead_click'=2,'excessive_scrolling'=3,'bad_request'=4,'missing_resource'=5,'memory'=6,'cpu'=7,'slow_resource'=8,'slow_page_load'=9,'crash'=10,'ml_cpu'=11,'ml_memory'=12,'ml_dead_click'=13,'ml_click_rage'=14,'ml_mouse_thrashing'=15,'ml_excessive_scrolling'=16,'ml_slow_resources'=17,'custom'=18,'js_exception'=19,'mouse_thrashing'=20,'app_crash'=21)), + issue_id Nullable(String), + transfer_size Nullable(UInt32), + direction Nullable(String), + reason Nullable(String), + stacktrace Nullable(String), + message_id UInt64 DEFAULT 0, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMM(datetime) + ORDER BY (project_id, datetime, event_type, session_id, message_id) + TTL datetime + INTERVAL 1 MONTH; \ No newline at end of file diff --git a/scripts/schema/db/rollback_dbs/clickhouse/1.22.0/1.22.0.sql b/scripts/schema/db/rollback_dbs/clickhouse/1.22.0/1.22.0.sql new file mode 100644 index 000000000..56d236a3b --- /dev/null +++ b/scripts/schema/db/rollback_dbs/clickhouse/1.22.0/1.22.0.sql @@ -0,0 +1,2 @@ +DROP DATABASE IF EXISTS experimental; +DROP FUNCTION IF EXISTS openreplay_version(); \ No newline at end of file diff --git a/tracker/tracker-reactnative/android/build.gradle b/tracker/tracker-reactnative/android/build.gradle index 23ba00c0a..2890718fd 100644 --- a/tracker/tracker-reactnative/android/build.gradle +++ b/tracker/tracker-reactnative/android/build.gradle @@ -91,7 +91,7 @@ dependencies { //noinspection GradleDynamicVersion implementation("com.facebook.react:react-native:0.20.1") implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - implementation("com.github.openreplay:android-tracker:v1.1.2") + implementation("com.github.openreplay:android-tracker:v1.1.3") } //allprojects { diff --git a/tracker/tracker-reactnative/android/src/main/java/com/openreplay/reactnative/ReactNativeModule.kt b/tracker/tracker-reactnative/android/src/main/java/com/openreplay/reactnative/ReactNativeModule.kt index 758d83978..10037a91f 100644 --- a/tracker/tracker-reactnative/android/src/main/java/com/openreplay/reactnative/ReactNativeModule.kt +++ b/tracker/tracker-reactnative/android/src/main/java/com/openreplay/reactnative/ReactNativeModule.kt @@ -10,18 +10,10 @@ import com.openreplay.tracker.models.OROptions class ReactNativeModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { - // private val context = reactContext.acti override fun getName(): String { return NAME } - // Example method - // See https://reactnative.dev/docs/native-modules-android - @ReactMethod - fun multiply(a: Double, b: Double, promise: Promise) { - promise.resolve(a * b * 2) - } - companion object { const val NAME = "ORTrackerConnector" } @@ -33,14 +25,13 @@ class ReactNativeModule(reactContext: ReactApplicationContext) : val logs: Boolean = true, val screen: Boolean = true, val debugLogs: Boolean = false, - val wifiOnly: Boolean = true // assuming you want this as well + val wifiOnly: Boolean = true ) private fun getBooleanOrDefault(map: ReadableMap, key: String, default: Boolean): Boolean { return if (map.hasKey(key)) map.getBoolean(key) else default } - // optionsMap: ReadableMap?, @ReactMethod fun startSession( projectKey: String, @@ -97,8 +88,8 @@ class ReactNativeModule(reactContext: ReactApplicationContext) : @ReactMethod fun getSessionID(promise: Promise) { try { - val sessionId = OpenReplay.getSessionID() ?: "" - promise.resolve(sessionId) // Resolve the promise with the session ID + val sessionId = OpenReplay.getSessionID() + promise.resolve(sessionId) } catch (e: Exception) { promise.reject("GET_SESSION_ID_ERROR", "Failed to retrieve session ID", e) } @@ -111,8 +102,9 @@ class ReactNativeModule(reactContext: ReactApplicationContext) : requestJSON: String, responseJSON: String, status: Int, - duration: ULong + duration: Double ) { - OpenReplay.networkRequest(url, method, requestJSON, responseJSON, status, duration) + val durationULong = duration.toLong().toULong() + OpenReplay.networkRequest(url, method, requestJSON, responseJSON, status, durationULong) } } diff --git a/tracker/tracker-reactnative/android/src/main/java/com/openreplay/reactnative/RntrackerTouchManager.kt b/tracker/tracker-reactnative/android/src/main/java/com/openreplay/reactnative/RntrackerTouchManager.kt index bdfcf3d7a..b861a18e4 100644 --- a/tracker/tracker-reactnative/android/src/main/java/com/openreplay/reactnative/RntrackerTouchManager.kt +++ b/tracker/tracker-reactnative/android/src/main/java/com/openreplay/reactnative/RntrackerTouchManager.kt @@ -1,13 +1,13 @@ package com.openreplay.reactnative -import android.annotation.SuppressLint import android.content.Context import android.graphics.PointF +import android.util.Log +import android.view.GestureDetector import android.view.MotionEvent import android.view.View +import android.view.ViewGroup import android.widget.FrameLayout -import android.widget.Toast -import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.ViewGroupManager import com.openreplay.tracker.listeners.Analytics @@ -15,151 +15,16 @@ import com.openreplay.tracker.listeners.SwipeDirection import kotlin.math.abs import kotlin.math.sqrt - -import android.os.Handler -import android.os.Looper -import android.util.Log -import android.view.GestureDetector -import com.facebook.react.ReactRootView - -//class RnTrackerTouchManager : ViewGroupManager() { -// override fun getName(): String = "RnTrackerTouchView" -// -// override fun createViewInstance(reactContext: ThemedReactContext): TouchableFrameLayout { -// return TouchableFrameLayout(reactContext) -// } -//} -// -//class TouchableFrameLayout(context: Context) : FrameLayout(context) { -// private var gestureDetector: GestureDetector -// private var handler = Handler(Looper.getMainLooper()) -// private var isScrolling = false -// private var lastX: Float = 0f -// private var lastY: Float = 0f -// private var swipeDirection: SwipeDirection = SwipeDirection.UNDEFINED -// -// init { -// gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { -// override fun onSingleTapUp(e: MotionEvent): Boolean { -// Analytics.sendClick(e) -// return true -// } -// -// override fun onDown(e: MotionEvent): Boolean = true -// -// override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean { -// if (!isScrolling) { -// isScrolling = true -// } -// -// swipeDirection = SwipeDirection.fromDistances(distanceX, distanceY) -// lastX = e2.x -// lastY = e2.y -// -// handler.removeCallbacksAndMessages(null) -// handler.postDelayed({ -// if (isScrolling) { -// isScrolling = false -// Analytics.sendSwipe(swipeDirection, lastX, lastY) -// } -// }, 200) -// return true -// } -// }) -// -// setOnTouchListener { _, event -> -// Log.d("TouchEvent", "Event: ${event.actionMasked}, X: ${event.x}, Y: ${event.y}") -// gestureDetector.onTouchEvent(event) -// this.performClick() -// } -// } -//} - - class RnTrackerTouchManager : ViewGroupManager() { + override fun getName(): String = "RnTrackerTouchView" override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout { - return ReactRootView(reactContext).apply { -// layoutParams = FrameLayout.LayoutParams( -// FrameLayout.LayoutParams.MATCH_PARENT, -// FrameLayout.LayoutParams.MATCH_PARENT -// ) -// isClickable = true -// val touchStart = PointF() -// setOnTouchListener { view, event -> -// when (event.action) { -// MotionEvent.ACTION_DOWN -> { -// touchStart.set(event.x, event.y) -// true -// } -// -// MotionEvent.ACTION_UP -> { -// val deltaX = event.x - touchStart.x -// val deltaY = event.y - touchStart.y -// val distance = sqrt(deltaX * deltaX + deltaY * deltaY) -// -// if (distance > 10) { -// val direction = if (abs(deltaX) > abs(deltaY)) { -// if (deltaX > 0) "RIGHT" else "LEFT" -// } else { -// if (deltaY > 0) "DOWN" else "UP" -// } -// Analytics.sendSwipe(SwipeDirection.valueOf(direction), event.x, event.y) -// } else { -// Analytics.sendClick(event) -// view.performClick() // Perform click for accessibility -// } -// true -// } -// -// else -> false -// } -// } - } + return RnTrackerRootLayout(reactContext) } override fun addView(parent: FrameLayout, child: View, index: Int) { - child.isClickable = true - child.isFocusable = true -// child.layoutParams = FrameLayout.LayoutParams( -// FrameLayout.LayoutParams.MATCH_PARENT, -// FrameLayout.LayoutParams.MATCH_PARENT -// ) - val touchStart = PointF() - child.setOnTouchListener( - View.OnTouchListener { view, event -> - when (event.action) { - MotionEvent.ACTION_DOWN -> { - view.performClick() - Analytics.sendClick(event) - true - } - - MotionEvent.ACTION_UP -> { - val deltaX = event.x - touchStart.x - val deltaY = event.y - touchStart.y - val distance = sqrt(deltaX * deltaX + deltaY * deltaY) - - if (distance > 10) { - val direction = if (abs(deltaX) > abs(deltaY)) { - if (deltaX > 0) "RIGHT" else "LEFT" - } else { - if (deltaY > 0) "DOWN" else "UP" - } - Analytics.sendSwipe(SwipeDirection.valueOf(direction), event.x, event.y) - } else { - Analytics.sendClick(event) - view.performClick() // Perform click for accessibility - } - true - } - - else -> false - } - } - ) - parent.addView(child) + parent.addView(child, index) } override fun getChildCount(parent: FrameLayout): Int = parent.childCount @@ -175,63 +40,102 @@ class RnTrackerTouchManager : ViewGroupManager() { } } -//class RnTrackerTouchManager : ViewGroupManager() { -// override fun getName(): String = "RnTrackerTouchView" -// -// override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout { -// return FrameLayout(reactContext).apply { -// layoutParams = FrameLayout.LayoutParams( -// FrameLayout.LayoutParams.MATCH_PARENT, -// FrameLayout.LayoutParams.MATCH_PARENT -// ) -// isClickable = true -// val touchStart = PointF() -// setOnTouchListener { view, event -> -// when (event.action) { -// MotionEvent.ACTION_DOWN -> { -// touchStart.set(event.x, event.y) -// view.performClick() -// } -// -// MotionEvent.ACTION_UP -> { -// val deltaX = event.x - touchStart.x -// val deltaY = event.y - touchStart.y -// val distance = sqrt(deltaX * deltaX + deltaY * deltaY) -// -// if (distance > 10) { -// val direction = if (abs(deltaX) > abs(deltaY)) { -// if (deltaX > 0) "RIGHT" else "LEFT" -// } else { -// if (deltaY > 0) "DOWN" else "UP" -// } -// Analytics.sendSwipe(SwipeDirection.valueOf(direction), event.x, event.y) -// view.performClick() -// } else { -// Analytics.sendClick(event) -// view.performClick() -// } -// true -// } -// -// else -> false -// } -// } -// } -// } -// -// override fun addView(parent: FrameLayout, child: View, index: Int) { -// parent.addView(child, index) -// } -// -// override fun getChildCount(parent: FrameLayout): Int = parent.childCount -// -// override fun getChildAt(parent: FrameLayout, index: Int): View = parent.getChildAt(index) -// -// override fun removeViewAt(parent: FrameLayout, index: Int) { -// parent.removeViewAt(index) -// } -// -// override fun removeAllViews(parent: FrameLayout) { -// parent.removeAllViews() -// } -//} +class RnTrackerRootLayout(context: Context) : FrameLayout(context) { + private val touchStart = PointF() + private val gestureDetector: GestureDetector + + private var currentTappedView: View? = null + + // Variables to track total movement + private var totalDeltaX: Float = 0f + private var totalDeltaY: Float = 0f + + init { + gestureDetector = GestureDetector(context, GestureListener()) + } + + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { + // Pass all touch events to the GestureDetector + gestureDetector.onTouchEvent(ev) + + when (ev.action) { + MotionEvent.ACTION_DOWN -> { + // Record the starting point for potential swipe + touchStart.x = ev.x + touchStart.y = ev.y + // Reset total movement + totalDeltaX = 0f + totalDeltaY = 0f + // Find and store the view that was touched + currentTappedView = findViewAt(this, ev.x.toInt(), ev.y.toInt()) +// Log.d( +// "RnTrackerRootLayout", +// "ACTION_DOWN at global: (${ev.rawX}, ${ev.rawY}) on view: $currentTappedView" +// ) + } + MotionEvent.ACTION_MOVE -> { + // Accumulate movement + val deltaX = ev.x - touchStart.x + val deltaY = ev.y - touchStart.y + totalDeltaX += deltaX + totalDeltaY += deltaY + // Update touchStart for the next move event + touchStart.x = ev.x + touchStart.y = ev.y +// Log.d("RnTrackerRootLayout", "Accumulated movement - X: $totalDeltaX, Y: $totalDeltaY") + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + // Determine if the accumulated movement qualifies as a swipe + val distance = sqrt(totalDeltaX * totalDeltaX + totalDeltaY * totalDeltaY) + + if (distance > SWIPE_DISTANCE_THRESHOLD) { + val direction = if (abs(totalDeltaX) > abs(totalDeltaY)) { + if (totalDeltaX > 0) "RIGHT" else "LEFT" + } else { + if (totalDeltaY > 0) "DOWN" else "UP" + } + Log.d("RnTrackerRootLayout", "Swipe detected: $direction") + Analytics.sendSwipe(SwipeDirection.valueOf(direction), ev.rawX, ev.rawY) + } + } + } + + // Ensure normal event propagation + return super.dispatchTouchEvent(ev) + } + + companion object { + private const val SWIPE_DISTANCE_THRESHOLD = 100f // Adjust as needed + } + + private fun findViewAt(parent: ViewGroup, x: Int, y: Int): View? { + for (i in parent.childCount - 1 downTo 0) { + val child = parent.getChildAt(i) + if (isPointInsideView(x, y, child)) { + if (child is ViewGroup) { + val childX = x - child.left + val childY = y - child.top + val result = findViewAt(child, childX, childY) + return result ?: child + } else { + return child + } + } + } + return null + } + + private fun isPointInsideView(x: Int, y: Int, view: View): Boolean { + return x >= view.left && x <= view.right && y >= view.top && y <= view.bottom + } + + inner class GestureListener : GestureDetector.SimpleOnGestureListener() { + override fun onSingleTapUp(e: MotionEvent): Boolean { + Log.d("GestureListener", "Single tap detected at: (${e.rawX}, ${e.rawY})") + val label = currentTappedView?.contentDescription?.toString() ?: "Button" + Analytics.sendClick(e, label) + currentTappedView?.performClick() + return super.onSingleTapUp(e) + } + } +} diff --git a/tracker/tracker-reactnative/app.plugin.js b/tracker/tracker-reactnative/app.plugin.js new file mode 100644 index 000000000..596913e52 --- /dev/null +++ b/tracker/tracker-reactnative/app.plugin.js @@ -0,0 +1,22 @@ +const { withMainApplication } = require('@expo/config-plugins'); + +function addPackageToMainApplication(src) { + console.log('Adding OpenReplay package to MainApplication.java', src); + // Insert `packages.add(new ReactNativePackage());` before return packages; + if (src.includes('packages.add(new ReactNativePackage())')) { + return src; + } + return src.replace( + 'return packages;', + `packages.add(new com.openreplay.reactnative.ReactNativePackage());\n return packages;` + ); +} + +module.exports = function configPlugin(config) { + return withMainApplication(config, (config) => { + if (config.modResults.contents) { + config.modResults.contents = addPackageToMainApplication(config.modResults.contents); + } + return config; + }); +}; diff --git a/tracker/tracker-reactnative/package.json b/tracker/tracker-reactnative/package.json index c455a07c2..f8de5a324 100644 --- a/tracker/tracker-reactnative/package.json +++ b/tracker/tracker-reactnative/package.json @@ -1,6 +1,6 @@ { "name": "@openreplay/react-native", - "version": "0.6.6", + "version": "0.6.10", "description": "Openreplay React-native connector for iOS applications", "main": "lib/commonjs/index", "module": "lib/module/index", @@ -13,6 +13,7 @@ "android", "ios", "cpp", + "app.plugin.js", "*.podspec", "!ios/build", "!android/build", @@ -71,7 +72,8 @@ "react-native-builder-bob": "^0.20.0", "release-it": "^15.0.0", "turbo": "^1.10.7", - "typescript": "^5.2.2" + "typescript": "^5.2.2", + "@expo/config-plugins": "^9.0.12" }, "resolutions": { "@types/react": "^18.2.44"