diff --git a/api/Dockerfile b/api/Dockerfile index 90ef3ad13..cdd8cd295 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,27 +1,20 @@ -FROM python:3.9.12-slim +FROM python:3.10-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" -ENV APP_NAME chalice +RUN apk add --no-cache build-base nodejs npm tini +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main +ARG envarg # Add Tini # Startup daemon -ENV TINI_VERSION=v0.19.0 \ - SOURCE_MAP_VERSION=0.7.4 -ARG envarg -ENV ENTERPRISE_BUILD ${envarg} -ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini -ADD https://unpkg.com/source-map@${SOURCE_MAP_VERSION}/lib/mappings.wasm /mappings.wasm -RUN chmod +x /tini +ENV SOURCE_MAP_VERSION=0.7.4 \ + APP_NAME=chalice \ + ENTERPRISE_BUILD=${envarg} -# Installing Nodejs -RUN apt update && apt install -y curl && \ - curl -fsSL https://deb.nodesource.com/setup_12.x | bash - && \ - apt install -y nodejs && \ - apt remove --purge -y curl && \ - rm -rf /var/lib/apt/lists/* +ADD https://unpkg.com/source-map@${SOURCE_MAP_VERSION}/lib/mappings.wasm /mappings.wasm WORKDIR /work_tmp COPY requirements.txt /work_tmp/requirements.txt -RUN pip install -r /work_tmp/requirements.txt +RUN pip install --no-cache-dir --upgrade -r /work_tmp/requirements.txt COPY sourcemap-reader/*.json /work_tmp/ RUN cd /work_tmp && npm install @@ -29,5 +22,8 @@ WORKDIR /work COPY . . RUN mv env.default .env && mv /work_tmp/node_modules sourcemap-reader/. -ENTRYPOINT ["/tini", "--"] +RUN adduser -u 1001 openreplay -D +USER 1001 + +ENTRYPOINT ["/sbin/tini", "--"] CMD ./entrypoint.sh diff --git a/api/Dockerfile.alerts b/api/Dockerfile.alerts index 19485fe38..c4614b3c1 100644 --- a/api/Dockerfile.alerts +++ b/api/Dockerfile.alerts @@ -1,23 +1,22 @@ -FROM python:3.9.12-slim +FROM python:3.10-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" -ENV APP_NAME alerts -ENV pg_minconn 2 -ENV pg_maxconn 10 -# Add Tini -# Startup daemon -ENV TINI_VERSION v0.19.0 +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main +RUN apk add --no-cache build-base tini ARG envarg -ENV ENTERPRISE_BUILD ${envarg} -ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini -RUN chmod +x /tini +ENV APP_NAME=alerts \ + pg_minconn=2 \ + pg_maxconn=10 \ + ENTERPRISE_BUILD=${envarg} COPY requirements.txt /work_tmp/requirements.txt -RUN pip install -r /work_tmp/requirements.txt +RUN pip install --no-cache-dir --upgrade -r /work_tmp/requirements.txt WORKDIR /work COPY . . RUN mv env.default .env && mv app_alerts.py app.py && mv entrypoint_alerts.sh entrypoint.sh -ENTRYPOINT ["/tini", "--"] -CMD ./entrypoint.sh \ No newline at end of file +RUN adduser -u 1001 openreplay -D +USER 1001 +ENTRYPOINT ["/sbin/tini", "--"] +CMD ./entrypoint.sh diff --git a/api/Dockerfile.alerts.dockerignore b/api/Dockerfile.alerts.dockerignore new file mode 100644 index 000000000..3539023b4 --- /dev/null +++ b/api/Dockerfile.alerts.dockerignore @@ -0,0 +1,10 @@ +# ignore .git and .cache folders +.git +.cache +**/build.sh +**/build_*.sh +**/*deploy.sh + +app.py +entrypoint_alerts.sh +requirements.txt \ No newline at end of file diff --git a/api/Dockerfile.bundle b/api/Dockerfile.bundle index 2f58635f2..b047f6d6c 100644 --- a/api/Dockerfile.bundle +++ b/api/Dockerfile.bundle @@ -23,5 +23,7 @@ ARG envarg ENV ENTERPRISE_BUILD ${envarg} ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini +RUN adduser -u 1001 openreplay -D +USER 1001 ENTRYPOINT ["/tini", "--"] CMD ./entrypoint.sh diff --git a/api/build_alerts.sh b/api/build_alerts.sh index 5a098b70c..b5aa759b0 100644 --- a/api/build_alerts.sh +++ b/api/build_alerts.sh @@ -7,35 +7,6 @@ # Usage: IMAGE_TAG=latest DOCKER_REPO=myDockerHubID bash build.sh -function make_submodule() { - [[ $1 != "ee" ]] && { - # -- this part was generated by modules_lister.py -- - mkdir alerts - cp -R ./{app_alerts,schemas}.py ./alerts/ - mkdir -p ./alerts/chalicelib/ - cp -R ./chalicelib/__init__.py ./alerts/chalicelib/ - mkdir -p ./alerts/chalicelib/core/ - cp -R ./chalicelib/core/{__init__,alerts_processor,alerts_listener,sessions,events,issues,sessions_metas,metadata,projects,users,authorizers,tenants,assist,events_ios,sessions_mobs,errors,sourcemaps,sourcemaps_parser,resources,performance_event,alerts,notifications,slack,collaboration_slack,webhook}.py ./alerts/chalicelib/core/ - mkdir -p ./alerts/chalicelib/utils/ - cp -R ./chalicelib/utils/{__init__,TimeUTC,pg_client,helper,event_filter_definition,dev,email_helper,email_handler,smtp,s3,metrics_helper}.py ./alerts/chalicelib/utils/ - # -- end of generated part - } - [[ $1 == "ee" ]] && { - # -- this part was generated by modules_lister.py -- - mkdir alerts - cp -R ./{app_alerts,schemas,schemas_ee}.py ./alerts/ - mkdir -p ./alerts/chalicelib/ - cp -R ./chalicelib/__init__.py ./alerts/chalicelib/ - mkdir -p ./alerts/chalicelib/core/ - cp -R ./chalicelib/core/{__init__,alerts_processor,alerts_listener,sessions,events,issues,sessions_metas,metadata,projects,users,authorizers,tenants,roles,assist,events_ios,sessions_mobs,errors,metrics,sourcemaps,sourcemaps_parser,resources,performance_event,alerts,notifications,slack,collaboration_slack,webhook}.py ./alerts/chalicelib/core/ - mkdir -p ./alerts/chalicelib/utils/ - cp -R ./chalicelib/utils/{__init__,TimeUTC,pg_client,helper,event_filter_definition,dev,SAML2_helper,email_helper,email_handler,smtp,s3,args_transformer,ch_client,metrics_helper}.py ./alerts/chalicelib/utils/ - # -- end of generated part - } - cp -R ./{Dockerfile.alerts,requirements.txt,env.default,entrypoint_alerts.sh} ./alerts/ - cp -R ./chalicelib/utils/html ./alerts/chalicelib/utils/html -} - git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} envarg="default-foss" check_prereq() { @@ -53,11 +24,9 @@ function build_api(){ envarg="default-ee" tag="ee-" } - make_submodule $1 - cd alerts - docker build -f ./Dockerfile.alerts --build-arg envarg=$envarg -t ${DOCKER_REPO:-'local'}/alerts:${git_sha1} . - cd .. - rm -rf alerts + cp -R ../api ../_alerts + docker build -f ../_alerts/Dockerfile.alerts --build-arg envarg=$envarg -t ${DOCKER_REPO:-'local'}/alerts:${git_sha1} . + rm -rf ../_alerts [[ $PUSH_IMAGE -eq 1 ]] && { docker push ${DOCKER_REPO:-'local'}/alerts:${git_sha1} docker tag ${DOCKER_REPO:-'local'}/alerts:${git_sha1} ${DOCKER_REPO:-'local'}/alerts:${tag}latest diff --git a/api/chalicelib/core/resources.py b/api/chalicelib/core/resources.py index 9873bd97e..e56c0cf74 100644 --- a/api/chalicelib/core/resources.py +++ b/api/chalicelib/core/resources.py @@ -4,6 +4,8 @@ from decouple import config def get_by_session_id(session_id, project_id, start_ts, duration): with pg_client.PostgresClient() as cur: + if duration is None or (type(duration) != 'int' and type(duration) != 'float') or duration < 0: + duration = 0 delta = config("events_ts_delta", cast=int, default=60 * 60) * 1000 ch_query = """\ SELECT diff --git a/api/chalicelib/core/sessions.py b/api/chalicelib/core/sessions.py index 3856acaa0..8dc87c90d 100644 --- a/api/chalicelib/core/sessions.py +++ b/api/chalicelib/core/sessions.py @@ -863,12 +863,12 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr full_args = {**full_args, **_multiple_values(f.value, value_key=e_k_f)} if f.type == schemas.FetchFilterType._url: event_where.append( - _multiple_conditions(f"main.{events.event_type.REQUEST.column} {op} %({e_k_f})s", f.value, - value_key=e_k_f)) + _multiple_conditions(f"main.{events.event_type.REQUEST.column} {op} %({e_k_f})s::text", + f.value, value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._status_code: event_where.append( - _multiple_conditions(f"main.status_code {f.operator} %({e_k_f})s", f.value, + _multiple_conditions(f"main.status_code {f.operator} %({e_k_f})s::integer", f.value, value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._method: @@ -877,15 +877,15 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr apply = True elif f.type == schemas.FetchFilterType._duration: event_where.append( - _multiple_conditions(f"main.duration {f.operator} %({e_k_f})s", f.value, value_key=e_k_f)) + _multiple_conditions(f"main.duration {f.operator} %({e_k_f})s::integer", f.value, value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._request_body: event_where.append( - _multiple_conditions(f"main.request_body {op} %({e_k_f})s", f.value, value_key=e_k_f)) + _multiple_conditions(f"main.request_body {op} %({e_k_f})s::text", f.value, value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._response_body: event_where.append( - _multiple_conditions(f"main.response_body {op} %({e_k_f})s", f.value, value_key=e_k_f)) + _multiple_conditions(f"main.response_body {op} %({e_k_f})s::text", f.value, value_key=e_k_f)) apply = True else: print(f"undefined FETCH filter: {f.type}") diff --git a/api/chalicelib/utils/helper.py b/api/chalicelib/utils/helper.py index 2716cf111..b04b8aa43 100644 --- a/api/chalicelib/utils/helper.py +++ b/api/chalicelib/utils/helper.py @@ -398,3 +398,7 @@ def __time_value(row): if "chart" in row and factor > 1: for r in row["chart"]: r["value"] /= factor + + +def is_saml2_available(): + return config("hastSAML2", default=False, cast=bool) diff --git a/api/entrypoint.sh b/api/entrypoint.sh index a41427181..94b56121a 100755 --- a/api/entrypoint.sh +++ b/api/entrypoint.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh cd sourcemap-reader nohup npm start &> /tmp/sourcemap-reader.log & cd .. diff --git a/api/entrypoint_alerts.sh b/api/entrypoint_alerts.sh index 5f15a78b7..861206589 100755 --- a/api/entrypoint_alerts.sh +++ b/api/entrypoint_alerts.sh @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh uvicorn app:app --host 0.0.0.0 --reload diff --git a/api/requirements-alerts.txt b/api/requirements-alerts.txt new file mode 100644 index 000000000..81198b0f3 --- /dev/null +++ b/api/requirements-alerts.txt @@ -0,0 +1,15 @@ +requests==2.28.1 +urllib3==1.26.10 +boto3==1.24.26 +pyjwt==2.4.0 +psycopg2-binary==2.9.3 +elasticsearch==8.3.1 +jira==3.3.0 + + + +fastapi==0.78.0 +uvicorn[standard]==0.18.2 +python-decouple==3.6 +pydantic[email]==1.9.1 +apscheduler==3.9.1 \ No newline at end of file diff --git a/api/requirements.txt b/api/requirements.txt index dd79c5324..81198b0f3 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,15 +1,15 @@ -requests==2.28.0 -urllib3==1.26.9 -boto3==1.24.11 +requests==2.28.1 +urllib3==1.26.10 +boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 -elasticsearch==8.2.3 -jira==3.2.0 +elasticsearch==8.3.1 +jira==3.3.0 fastapi==0.78.0 -uvicorn[standard]==0.17.6 +uvicorn[standard]==0.18.2 python-decouple==3.6 pydantic[email]==1.9.1 apscheduler==3.9.1 \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index f7e4b3711..fe8f9e7e4 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -19,14 +19,16 @@ RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o service -tags musl openrep FROM alpine AS entrypoint +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache ca-certificates +RUN adduser -u 1001 openreplay -D ENV TZ=UTC \ FS_ULIMIT=1000 \ FS_DIR=/mnt/efs \ - MAXMINDDB_FILE=/root/geoip.mmdb \ - UAPARSER_FILE=/root/regexes.yaml \ - HTTP_PORT=80 \ + MAXMINDDB_FILE=/home/openreplay/geoip.mmdb \ + UAPARSER_FILE=/home/openreplay/regexes.yaml \ + HTTP_PORT=8080 \ KAFKA_USE_SSL=true \ KAFKA_MAX_POLL_INTERVAL_MS=400000 \ REDIS_STREAMS_MAX_LEN=10000 \ @@ -67,5 +69,6 @@ RUN if [ "$SERVICE_NAME" = "http" ]; then \ wget https://static.openreplay.com/geoip/GeoLite2-Country.mmdb -O "$MAXMINDDB_FILE"; fi -COPY --from=build /root/service /root/service -ENTRYPOINT /root/service +COPY --from=build /root/service /home/openreplay/service +USER 1001 +ENTRYPOINT /home/openreplay/service diff --git a/backend/cmd/ender/main.go b/backend/cmd/ender/main.go index c0613fca0..1fd2f4e64 100644 --- a/backend/cmd/ender/main.go +++ b/backend/cmd/ender/main.go @@ -82,10 +82,20 @@ func main() { // Find ended sessions and send notification to other services sessions.HandleEndedSessions(func(sessionID uint64, timestamp int64) bool { msg := &messages.SessionEnd{Timestamp: uint64(timestamp)} - if err := pg.InsertSessionEnd(sessionID, msg.Timestamp); err != nil { + currDuration, err := pg.GetSessionDuration(sessionID) + if err != nil { + log.Printf("getSessionDuration failed, sessID: %d, err: %s", sessionID, err) + } + newDuration, err := pg.InsertSessionEnd(sessionID, msg.Timestamp) + if err != nil { log.Printf("can't save sessionEnd to database, sessID: %d, err: %s", sessionID, err) return false } + if currDuration == newDuration { + log.Printf("sessionEnd duplicate, sessID: %d, prevDur: %d, newDur: %d", sessionID, + currDuration, newDuration) + return true + } if err := producer.Produce(cfg.TopicRawWeb, sessionID, messages.Encode(msg)); err != nil { log.Printf("can't send sessionEnd to topic: %s; sessID: %d", err, sessionID) return false diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go index abe8089be..8d79468db 100644 --- a/backend/internal/storage/storage.go +++ b/backend/internal/storage/storage.go @@ -21,6 +21,7 @@ type Storage struct { startBytes []byte totalSessions syncfloat64.Counter sessionSize syncfloat64.Histogram + readingTime syncfloat64.Histogram archivingTime syncfloat64.Histogram } @@ -40,6 +41,10 @@ func New(cfg *config.Config, s3 *storage.S3, metrics *monitoring.Metrics) (*Stor if err != nil { log.Printf("can't create session_size metric: %s", err) } + readingTime, err := metrics.RegisterHistogram("reading_duration") + if err != nil { + log.Printf("can't create reading_duration metric: %s", err) + } archivingTime, err := metrics.RegisterHistogram("archiving_duration") if err != nil { log.Printf("can't create archiving_duration metric: %s", err) @@ -50,16 +55,17 @@ func New(cfg *config.Config, s3 *storage.S3, metrics *monitoring.Metrics) (*Stor startBytes: make([]byte, cfg.FileSplitSize), totalSessions: totalSessions, sessionSize: sessionSize, + readingTime: readingTime, archivingTime: archivingTime, }, nil } func (s *Storage) UploadKey(key string, retryCount int) error { - start := time.Now() if retryCount <= 0 { return nil } + start := time.Now() file, err := os.Open(s.cfg.FSDir + "/" + key) if err != nil { sessID, _ := strconv.ParseUint(key, 10, 64) @@ -84,6 +90,9 @@ func (s *Storage) UploadKey(key string, retryCount int) error { }) return nil } + s.readingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds())) + + start = time.Now() startReader := bytes.NewBuffer(s.startBytes[:nRead]) if err := s.s3.Upload(s.gzipFile(startReader), key, "application/octet-stream", true); err != nil { log.Fatalf("Storage: start upload failed. %v\n", err) @@ -93,6 +102,7 @@ func (s *Storage) UploadKey(key string, retryCount int) error { log.Fatalf("Storage: end upload failed. %v\n", err) } } + s.archivingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds())) // Save metrics var fileSize float64 = 0 @@ -103,7 +113,7 @@ func (s *Storage) UploadKey(key string, retryCount int) error { fileSize = float64(fileInfo.Size()) } ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*200) - s.archivingTime.Record(ctx, float64(time.Now().Sub(start).Milliseconds())) + s.sessionSize.Record(ctx, fileSize) s.totalSessions.Add(ctx, 1) return nil diff --git a/backend/pkg/db/cache/messages-common.go b/backend/pkg/db/cache/messages-common.go index cebdaf5e7..41cdb1895 100644 --- a/backend/pkg/db/cache/messages-common.go +++ b/backend/pkg/db/cache/messages-common.go @@ -7,12 +7,8 @@ import ( // . "openreplay/backend/pkg/db/types" ) -func (c *PGCache) InsertSessionEnd(sessionID uint64, timestamp uint64) error { - _, err := c.Conn.InsertSessionEnd(sessionID, timestamp) - if err != nil { - return err - } - return nil +func (c *PGCache) InsertSessionEnd(sessionID uint64, timestamp uint64) (uint64, error) { + return c.Conn.InsertSessionEnd(sessionID, timestamp) } func (c *PGCache) HandleSessionEnd(sessionID uint64) error { diff --git a/backend/pkg/db/cache/messages-ios.go b/backend/pkg/db/cache/messages-ios.go index 4195976c3..e0463c431 100644 --- a/backend/pkg/db/cache/messages-ios.go +++ b/backend/pkg/db/cache/messages-ios.go @@ -32,7 +32,8 @@ func (c *PGCache) InsertIOSSessionStart(sessionID uint64, s *IOSSessionStart) er } func (c *PGCache) InsertIOSSessionEnd(sessionID uint64, e *IOSSessionEnd) error { - return c.InsertSessionEnd(sessionID, e.Timestamp) + _, err := c.InsertSessionEnd(sessionID, e.Timestamp) + return err } func (c *PGCache) InsertIOSScreenEnter(sessionID uint64, screenEnter *IOSScreenEnter) error { @@ -84,13 +85,5 @@ func (c *PGCache) InsertIOSCrash(sessionID uint64, crash *IOSCrash) error { } func (c *PGCache) InsertIOSIssueEvent(sessionID uint64, issueEvent *IOSIssueEvent) error { - // session, err := c.GetSession(sessionID) - // if err != nil { - // return err - // } - // TODO: unite IssueEvent message for the all platforms - // if err := c.Conn.InsertIssueEvent(sessionID, session.ProjectID, issueEvent); err != nil { - // return err - // } return nil } diff --git a/backend/pkg/db/cache/messages-web.go b/backend/pkg/db/cache/messages-web.go index 7da7006af..0a864e6d3 100644 --- a/backend/pkg/db/cache/messages-web.go +++ b/backend/pkg/db/cache/messages-web.go @@ -63,7 +63,8 @@ func (c *PGCache) HandleWebSessionStart(sessionID uint64, s *SessionStart) error } func (c *PGCache) InsertWebSessionEnd(sessionID uint64, e *SessionEnd) error { - return c.InsertSessionEnd(sessionID, e.Timestamp) + _, err := c.InsertSessionEnd(sessionID, e.Timestamp) + return err } func (c *PGCache) HandleWebSessionEnd(sessionID uint64, e *SessionEnd) error { @@ -91,7 +92,7 @@ func (c *PGCache) InsertWebFetchEvent(sessionID uint64, e *FetchEvent) error { if err != nil { return err } - return c.Conn.InsertWebFetchEvent(sessionID, project.SaveRequestPayloads, e) + return c.Conn.InsertWebFetchEvent(sessionID, session.ProjectID, project.SaveRequestPayloads, e) } func (c *PGCache) InsertWebGraphQLEvent(sessionID uint64, e *GraphQLEvent) error { @@ -103,5 +104,53 @@ func (c *PGCache) InsertWebGraphQLEvent(sessionID uint64, e *GraphQLEvent) error if err != nil { return err } - return c.Conn.InsertWebGraphQLEvent(sessionID, project.SaveRequestPayloads, e) + return c.Conn.InsertWebGraphQLEvent(sessionID, session.ProjectID, project.SaveRequestPayloads, e) +} + +func (c *PGCache) InsertWebCustomEvent(sessionID uint64, e *CustomEvent) error { + session, err := c.GetSession(sessionID) + if err != nil { + return err + } + return c.Conn.InsertWebCustomEvent(sessionID, session.ProjectID, e) +} + +func (c *PGCache) InsertWebUserID(sessionID uint64, userID *UserID) error { + session, err := c.GetSession(sessionID) + if err != nil { + return err + } + return c.Conn.InsertWebUserID(sessionID, session.ProjectID, userID) +} + +func (c *PGCache) InsertWebUserAnonymousID(sessionID uint64, userAnonymousID *UserAnonymousID) error { + session, err := c.GetSession(sessionID) + if err != nil { + return err + } + return c.Conn.InsertWebUserAnonymousID(sessionID, session.ProjectID, userAnonymousID) +} + +func (c *PGCache) InsertWebPageEvent(sessionID uint64, e *PageEvent) error { + session, err := c.GetSession(sessionID) + if err != nil { + return err + } + return c.Conn.InsertWebPageEvent(sessionID, session.ProjectID, e) +} + +func (c *PGCache) InsertWebClickEvent(sessionID uint64, e *ClickEvent) error { + session, err := c.GetSession(sessionID) + if err != nil { + return err + } + return c.Conn.InsertWebClickEvent(sessionID, session.ProjectID, e) +} + +func (c *PGCache) InsertWebInputEvent(sessionID uint64, e *InputEvent) error { + session, err := c.GetSession(sessionID) + if err != nil { + return err + } + return c.Conn.InsertWebInputEvent(sessionID, session.ProjectID, e) } diff --git a/backend/pkg/db/cache/pg-cache.go b/backend/pkg/db/cache/pg-cache.go index 6422209d4..ca31bcd82 100644 --- a/backend/pkg/db/cache/pg-cache.go +++ b/backend/pkg/db/cache/pg-cache.go @@ -29,10 +29,9 @@ type PGCache struct { // TODO: create conn automatically func NewPGCache(pgConn *postgres.Conn, projectExpirationTimeoutMs int64) *PGCache { return &PGCache{ - Conn: pgConn, - sessions: make(map[uint64]*Session), - projects: make(map[uint32]*ProjectMeta), - //projectsByKeys: make(map[string]*ProjectMeta), + Conn: pgConn, + sessions: make(map[uint64]*Session), + projects: make(map[uint32]*ProjectMeta), projectExpirationTimeout: time.Duration(1000 * projectExpirationTimeoutMs), } } diff --git a/backend/pkg/db/postgres/bulk.go b/backend/pkg/db/postgres/bulk.go new file mode 100644 index 000000000..16f59efcd --- /dev/null +++ b/backend/pkg/db/postgres/bulk.go @@ -0,0 +1,93 @@ +package postgres + +import ( + "bytes" + "errors" + "fmt" +) + +const ( + insertPrefix = `INSERT INTO ` + insertValues = ` VALUES ` + insertSuffix = ` ON CONFLICT DO NOTHING;` +) + +type Bulk interface { + Append(args ...interface{}) error + Send() error +} + +type bulkImpl struct { + conn Pool + table string + columns string + template string + setSize int + sizeLimit int + values []interface{} +} + +func (b *bulkImpl) Append(args ...interface{}) error { + if len(args) != b.setSize { + return fmt.Errorf("wrong number of arguments, waited: %d, got: %d", b.setSize, len(args)) + } + b.values = append(b.values, args...) + if len(b.values)/b.setSize >= b.sizeLimit { + return b.send() + } + return nil +} + +func (b *bulkImpl) Send() error { + if len(b.values) == 0 { + return nil + } + return b.send() +} + +func (b *bulkImpl) send() error { + request := bytes.NewBufferString(insertPrefix + b.table + b.columns + insertValues) + args := make([]interface{}, b.setSize) + for i := 0; i < len(b.values)/b.setSize; i++ { + for j := 0; j < b.setSize; j++ { + args[j] = i*b.setSize + j + 1 + } + if i > 0 { + request.WriteByte(',') + } + request.WriteString(fmt.Sprintf(b.template, args...)) + } + request.WriteString(insertSuffix) + err := b.conn.Exec(request.String(), b.values...) + b.values = make([]interface{}, 0, b.setSize*b.sizeLimit) + if err != nil { + return fmt.Errorf("send bulk err: %s", err) + } + return nil +} + +func NewBulk(conn Pool, table, columns, template string, setSize, sizeLimit int) (Bulk, error) { + switch { + case conn == nil: + return nil, errors.New("db conn is empty") + case table == "": + return nil, errors.New("table is empty") + case columns == "": + return nil, errors.New("columns is empty") + case template == "": + return nil, errors.New("template is empty") + case setSize <= 0: + return nil, errors.New("set size is wrong") + case sizeLimit <= 0: + return nil, errors.New("size limit is wrong") + } + return &bulkImpl{ + conn: conn, + table: table, + columns: columns, + template: template, + setSize: setSize, + sizeLimit: sizeLimit, + values: make([]interface{}, 0, setSize*sizeLimit), + }, nil +} diff --git a/backend/pkg/db/postgres/connector.go b/backend/pkg/db/postgres/connector.go index e772b479d..1cc537982 100644 --- a/backend/pkg/db/postgres/connector.go +++ b/backend/pkg/db/postgres/connector.go @@ -13,21 +13,24 @@ import ( "github.com/jackc/pgx/v4/pgxpool" ) -func getTimeoutContext() context.Context { - ctx, _ := context.WithTimeout(context.Background(), time.Duration(time.Second*30)) - return ctx -} - type batchItem struct { query string arguments []interface{} } +// Conn contains batches, bulks and cache for all sessions type Conn struct { - c *pgxpool.Pool // TODO: conditional usage of Pool/Conn (use interface?) + c Pool batches map[uint64]*pgx.Batch batchSizes map[uint64]int rawBatches map[uint64][]*batchItem + autocompletes Bulk + requests Bulk + customEvents Bulk + webPageEvents Bulk + webInputEvents Bulk + webGraphQLEvents Bulk + sessionUpdates map[uint64]*sessionUpdates batchQueueLimit int batchSizeLimit int batchSizeBytes syncfloat64.Histogram @@ -46,14 +49,19 @@ func NewConn(url string, queueLimit, sizeLimit int, metrics *monitoring.Metrics) log.Fatalln("pgxpool.Connect Error") } conn := &Conn{ - c: c, batches: make(map[uint64]*pgx.Batch), batchSizes: make(map[uint64]int), rawBatches: make(map[uint64][]*batchItem), + sessionUpdates: make(map[uint64]*sessionUpdates), batchQueueLimit: queueLimit, batchSizeLimit: sizeLimit, } conn.initMetrics(metrics) + conn.c, err = NewPool(c, conn.sqlRequestTime, conn.sqlRequestCounter) + if err != nil { + log.Fatalf("can't create new pool wrapper: %s", err) + } + conn.initBulks() return conn } @@ -82,6 +90,70 @@ func (conn *Conn) initMetrics(metrics *monitoring.Metrics) { } } +func (conn *Conn) initBulks() { + var err error + conn.autocompletes, err = NewBulk(conn.c, + "autocomplete", + "(value, type, project_id)", + "($%d, $%d, $%d)", + 3, 100) + if err != nil { + log.Fatalf("can't create autocomplete bulk") + } + conn.requests, err = NewBulk(conn.c, + "events_common.requests", + "(session_id, timestamp, seq_index, url, duration, success)", + "($%d, $%d, $%d, left($%d, 2700), $%d, $%d)", + 6, 100) + if err != nil { + log.Fatalf("can't create requests bulk") + } + conn.customEvents, err = NewBulk(conn.c, + "events_common.customs", + "(session_id, timestamp, seq_index, name, payload)", + "($%d, $%d, $%d, left($%d, 2700), $%d)", + 5, 100) + if err != nil { + log.Fatalf("can't create customEvents bulk") + } + conn.webPageEvents, err = NewBulk(conn.c, + "events.pages", + "(session_id, message_id, timestamp, referrer, base_referrer, host, path, query, dom_content_loaded_time, "+ + "load_time, response_end, first_paint_time, first_contentful_paint_time, speed_index, visually_complete, "+ + "time_to_interactive, response_time, dom_building_time)", + "($%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d, NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0),"+ + " NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0))", + 18, 100) + if err != nil { + log.Fatalf("can't create webPageEvents bulk") + } + conn.webInputEvents, err = NewBulk(conn.c, + "events.inputs", + "(session_id, message_id, timestamp, value, label)", + "($%d, $%d, $%d, $%d, NULLIF($%d,''))", + 5, 100) + if err != nil { + log.Fatalf("can't create webPageEvents bulk") + } + conn.webGraphQLEvents, err = NewBulk(conn.c, + "events.graphql", + "(session_id, timestamp, message_id, name, request_body, response_body)", + "($%d, $%d, $%d, left($%d, 2700), $%d, $%d)", + 6, 100) + if err != nil { + log.Fatalf("can't create webPageEvents bulk") + } +} + +func (conn *Conn) insertAutocompleteValue(sessionID uint64, projectID uint32, tp string, value string) { + if len(value) == 0 { + return + } + if err := conn.autocompletes.Append(value, tp, projectID); err != nil { + log.Printf("autocomplete bulk err: %s", err) + } +} + func (conn *Conn) batchQueue(sessionID uint64, sql string, args ...interface{}) { batch, ok := conn.batches[sessionID] if !ok { @@ -90,6 +162,10 @@ func (conn *Conn) batchQueue(sessionID uint64, sql string, args ...interface{}) batch = conn.batches[sessionID] } batch.Queue(sql, args...) + conn.rawBatch(sessionID, sql, args...) +} + +func (conn *Conn) rawBatch(sessionID uint64, sql string, args ...interface{}) { // Temp raw batch store raw := conn.rawBatches[sessionID] raw = append(raw, &batchItem{ @@ -99,8 +175,45 @@ func (conn *Conn) batchQueue(sessionID uint64, sql string, args ...interface{}) conn.rawBatches[sessionID] = raw } +func (conn *Conn) updateSessionEvents(sessionID uint64, events, pages int) { + if _, ok := conn.sessionUpdates[sessionID]; !ok { + conn.sessionUpdates[sessionID] = NewSessionUpdates(sessionID) + } + conn.sessionUpdates[sessionID].add(pages, events) +} + +func (conn *Conn) sendBulks() { + if err := conn.autocompletes.Send(); err != nil { + log.Printf("autocomplete bulk send err: %s", err) + } + if err := conn.requests.Send(); err != nil { + log.Printf("requests bulk send err: %s", err) + } + if err := conn.customEvents.Send(); err != nil { + log.Printf("customEvents bulk send err: %s", err) + } + if err := conn.webPageEvents.Send(); err != nil { + log.Printf("webPageEvents bulk send err: %s", err) + } + if err := conn.webInputEvents.Send(); err != nil { + log.Printf("webInputEvents bulk send err: %s", err) + } + if err := conn.webGraphQLEvents.Send(); err != nil { + log.Printf("webGraphQLEvents bulk send err: %s", err) + } +} + func (conn *Conn) CommitBatches() { + conn.sendBulks() for sessID, b := range conn.batches { + // Append session update sql request to the end of batch + if update, ok := conn.sessionUpdates[sessID]; ok { + sql, args := update.request() + if sql != "" { + conn.batchQueue(sessID, sql, args...) + b, _ = conn.batches[sessID] + } + } // Record batch size in bytes and number of lines conn.batchSizeBytes.Record(context.Background(), float64(conn.batchSizes[sessID])) conn.batchSizeLines.Record(context.Background(), float64(b.Len())) @@ -109,7 +222,7 @@ func (conn *Conn) CommitBatches() { isFailed := false // Send batch to db and execute - br := conn.c.SendBatch(getTimeoutContext(), b) + br := conn.c.SendBatch(b) l := b.Len() for i := 0; i < l; i++ { if ct, err := br.Exec(); err != nil { @@ -125,10 +238,25 @@ func (conn *Conn) CommitBatches() { attribute.String("method", "batch"), attribute.Bool("failed", isFailed)) conn.sqlRequestCounter.Add(context.Background(), 1, attribute.String("method", "batch"), attribute.Bool("failed", isFailed)) + if !isFailed { + delete(conn.sessionUpdates, sessID) + } } conn.batches = make(map[uint64]*pgx.Batch) conn.batchSizes = make(map[uint64]int) conn.rawBatches = make(map[uint64][]*batchItem) + + // Session updates + for sessID, su := range conn.sessionUpdates { + sql, args := su.request() + if sql == "" { + continue + } + if err := conn.c.Exec(sql, args...); err != nil { + log.Printf("failed session update, sessID: %d, err: %s", sessID, err) + } + } + conn.sessionUpdates = make(map[uint64]*sessionUpdates) } func (conn *Conn) updateBatchSize(sessionID uint64, reqSize int) { @@ -145,6 +273,14 @@ func (conn *Conn) commitBatch(sessionID uint64) { log.Printf("can't find batch for session: %d", sessionID) return } + // Append session update sql request to the end of batch + if update, ok := conn.sessionUpdates[sessionID]; ok { + sql, args := update.request() + if sql != "" { + conn.batchQueue(sessionID, sql, args...) + b, _ = conn.batches[sessionID] + } + } // Record batch size in bytes and number of lines conn.batchSizeBytes.Record(context.Background(), float64(conn.batchSizes[sessionID])) conn.batchSizeLines.Record(context.Background(), float64(b.Len())) @@ -153,7 +289,7 @@ func (conn *Conn) commitBatch(sessionID uint64) { isFailed := false // Send batch to db and execute - br := conn.c.SendBatch(getTimeoutContext(), b) + br := conn.c.SendBatch(b) l := b.Len() for i := 0; i < l; i++ { if ct, err := br.Exec(); err != nil { @@ -175,117 +311,5 @@ func (conn *Conn) commitBatch(sessionID uint64) { delete(conn.batches, sessionID) delete(conn.batchSizes, sessionID) delete(conn.rawBatches, sessionID) -} - -func (conn *Conn) query(sql string, args ...interface{}) (pgx.Rows, error) { - start := time.Now() - res, err := conn.c.Query(getTimeoutContext(), sql, args...) - method, table := methodName(sql) - conn.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", method), attribute.String("table", table)) - conn.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", method), attribute.String("table", table)) - return res, err -} - -func (conn *Conn) queryRow(sql string, args ...interface{}) pgx.Row { - start := time.Now() - res := conn.c.QueryRow(getTimeoutContext(), sql, args...) - method, table := methodName(sql) - conn.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", method), attribute.String("table", table)) - conn.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", method), attribute.String("table", table)) - return res -} - -func (conn *Conn) exec(sql string, args ...interface{}) error { - start := time.Now() - _, err := conn.c.Exec(getTimeoutContext(), sql, args...) - method, table := methodName(sql) - conn.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", method), attribute.String("table", table)) - conn.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", method), attribute.String("table", table)) - return err -} - -type _Tx struct { - pgx.Tx - sqlRequestTime syncfloat64.Histogram - sqlRequestCounter syncfloat64.Counter -} - -func (conn *Conn) begin() (_Tx, error) { - start := time.Now() - tx, err := conn.c.Begin(context.Background()) - conn.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", "begin")) - conn.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", "begin")) - return _Tx{tx, conn.sqlRequestTime, conn.sqlRequestCounter}, err -} - -func (tx _Tx) exec(sql string, args ...interface{}) error { - start := time.Now() - _, err := tx.Exec(context.Background(), sql, args...) - method, table := methodName(sql) - tx.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", method), attribute.String("table", table)) - tx.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", method), attribute.String("table", table)) - return err -} - -func (tx _Tx) rollback() error { - start := time.Now() - err := tx.Rollback(context.Background()) - tx.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", "rollback")) - tx.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", "rollback")) - return err -} - -func (tx _Tx) commit() error { - start := time.Now() - err := tx.Commit(context.Background()) - tx.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", "commit")) - tx.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", "commit")) - return err -} - -func methodName(sql string) (string, string) { - cmd, table := "unknown", "unknown" - - // Prepare sql request for parsing - sql = strings.TrimSpace(sql) - sql = strings.ReplaceAll(sql, "\n", " ") - sql = strings.ReplaceAll(sql, "\t", "") - sql = strings.ToLower(sql) - - // Get sql command name - parts := strings.Split(sql, " ") - if parts[0] == "" { - return cmd, table - } else { - cmd = strings.TrimSpace(parts[0]) - } - - // Get table name - switch cmd { - case "select": - for i, p := range parts { - if strings.TrimSpace(p) == "from" { - table = strings.TrimSpace(parts[i+1]) - } - } - case "update": - table = strings.TrimSpace(parts[1]) - case "insert": - table = strings.TrimSpace(parts[2]) - } - return cmd, table + delete(conn.sessionUpdates, sessionID) } diff --git a/backend/pkg/db/postgres/integration.go b/backend/pkg/db/postgres/integration.go index e44bd726e..1556006c1 100644 --- a/backend/pkg/db/postgres/integration.go +++ b/backend/pkg/db/postgres/integration.go @@ -15,7 +15,7 @@ type Integration struct { } func (conn *Conn) IterateIntegrationsOrdered(iter func(integration *Integration, err error)) error { - rows, err := conn.query(` + rows, err := conn.c.Query(` SELECT project_id, provider, options, request_data FROM integrations `) @@ -40,7 +40,7 @@ func (conn *Conn) IterateIntegrationsOrdered(iter func(integration *Integration, } func (conn *Conn) UpdateIntegrationRequestData(i *Integration) error { - return conn.exec(` + return conn.c.Exec(` UPDATE integrations SET request_data = $1 WHERE project_id=$2 AND provider=$3`, diff --git a/backend/pkg/db/postgres/messages-common.go b/backend/pkg/db/postgres/messages-common.go index a68d2c814..2925acde3 100644 --- a/backend/pkg/db/postgres/messages-common.go +++ b/backend/pkg/db/postgres/messages-common.go @@ -18,31 +18,8 @@ func getAutocompleteType(baseType string, platform string) string { } -func (conn *Conn) insertAutocompleteValue(sessionID uint64, tp string, value string) { - if len(value) == 0 { - return - } - sqlRequest := ` - INSERT INTO autocomplete ( - value, - type, - project_id - ) (SELECT - $1, $2, project_id - FROM sessions - WHERE session_id = $3 - ) ON CONFLICT DO NOTHING` - if err := conn.exec(sqlRequest, value, tp, sessionID); err != nil { - log.Printf("can't insert autocomplete: %s", err) - } - //conn.batchQueue(sessionID, sqlRequest, value, tp, sessionID) - - // Record approximate message size - //conn.updateBatchSize(sessionID, len(sqlRequest)+len(value)+len(tp)+8) -} - func (conn *Conn) InsertSessionStart(sessionID uint64, s *types.Session) error { - return conn.exec(` + return conn.c.Exec(` INSERT INTO sessions ( session_id, project_id, start_ts, user_uuid, user_device, user_device_type, user_country, @@ -74,18 +51,26 @@ func (conn *Conn) InsertSessionStart(sessionID uint64, s *types.Session) error { } func (conn *Conn) HandleSessionStart(sessionID uint64, s *types.Session) error { - conn.insertAutocompleteValue(sessionID, getAutocompleteType("USEROS", s.Platform), s.UserOS) - conn.insertAutocompleteValue(sessionID, getAutocompleteType("USERDEVICE", s.Platform), s.UserDevice) - conn.insertAutocompleteValue(sessionID, getAutocompleteType("USERCOUNTRY", s.Platform), s.UserCountry) - conn.insertAutocompleteValue(sessionID, getAutocompleteType("REVID", s.Platform), s.RevID) + conn.insertAutocompleteValue(sessionID, s.ProjectID, getAutocompleteType("USEROS", s.Platform), s.UserOS) + conn.insertAutocompleteValue(sessionID, s.ProjectID, getAutocompleteType("USERDEVICE", s.Platform), s.UserDevice) + conn.insertAutocompleteValue(sessionID, s.ProjectID, getAutocompleteType("USERCOUNTRY", s.Platform), s.UserCountry) + conn.insertAutocompleteValue(sessionID, s.ProjectID, getAutocompleteType("REVID", s.Platform), s.RevID) // s.Platform == "web" - conn.insertAutocompleteValue(sessionID, "USERBROWSER", s.UserBrowser) + conn.insertAutocompleteValue(sessionID, s.ProjectID, "USERBROWSER", s.UserBrowser) return nil } +func (conn *Conn) GetSessionDuration(sessionID uint64) (uint64, error) { + var dur uint64 + if err := conn.c.QueryRow("SELECT COALESCE( duration, 0 ) FROM sessions WHERE session_id=$1", sessionID).Scan(&dur); err != nil { + return 0, err + } + return dur, nil +} + func (conn *Conn) InsertSessionEnd(sessionID uint64, timestamp uint64) (uint64, error) { var dur uint64 - if err := conn.queryRow(` + if err := conn.c.QueryRow(` UPDATE sessions SET duration=$2 - start_ts WHERE session_id=$1 RETURNING duration @@ -119,30 +104,16 @@ func (conn *Conn) HandleSessionEnd(sessionID uint64) error { } func (conn *Conn) InsertRequest(sessionID uint64, timestamp uint64, index uint64, url string, duration uint64, success bool) error { - sqlRequest := ` - INSERT INTO events_common.requests ( - session_id, timestamp, seq_index, url, duration, success - ) VALUES ( - $1, $2, $3, left($4, 2700), $5, $6 - )` - conn.batchQueue(sessionID, sqlRequest, sessionID, timestamp, getSqIdx(index), url, duration, success) - - // Record approximate message size - conn.updateBatchSize(sessionID, len(sqlRequest)+len(url)+8*4) + if err := conn.requests.Append(sessionID, timestamp, getSqIdx(index), url, duration, success); err != nil { + return fmt.Errorf("insert request in bulk err: %s", err) + } return nil } func (conn *Conn) InsertCustomEvent(sessionID uint64, timestamp uint64, index uint64, name string, payload string) error { - sqlRequest := ` - INSERT INTO events_common.customs ( - session_id, timestamp, seq_index, name, payload - ) VALUES ( - $1, $2, $3, left($4, 2700), $5 - )` - conn.batchQueue(sessionID, sqlRequest, sessionID, timestamp, getSqIdx(index), name, payload) - - // Record approximate message size - conn.updateBatchSize(sessionID, len(sqlRequest)+len(name)+len(payload)+8*3) + if err := conn.customEvents.Append(sessionID, timestamp, getSqIdx(index), name, payload); err != nil { + return fmt.Errorf("insert custom event in bulk err: %s", err) + } return nil } @@ -172,15 +143,21 @@ func (conn *Conn) InsertMetadata(sessionID uint64, keyNo uint, value string) err sqlRequest := ` UPDATE sessions SET metadata_%v = $1 WHERE session_id = $2` - return conn.exec(fmt.Sprintf(sqlRequest, keyNo), value, sessionID) + return conn.c.Exec(fmt.Sprintf(sqlRequest, keyNo), value, sessionID) } -func (conn *Conn) InsertIssueEvent(sessionID uint64, projectID uint32, e *messages.IssueEvent) error { - tx, err := conn.begin() +func (conn *Conn) InsertIssueEvent(sessionID uint64, projectID uint32, e *messages.IssueEvent) (err error) { + tx, err := conn.c.Begin() if err != nil { return err } - defer tx.rollback() + defer func() { + if err != nil { + if rollbackErr := tx.rollback(); rollbackErr != nil { + log.Printf("rollback err: %s", rollbackErr) + } + } + }() issueID := hashid.IssueID(projectID, e) // TEMP. TODO: nullable & json message field type @@ -237,5 +214,6 @@ func (conn *Conn) InsertIssueEvent(sessionID uint64, projectID uint32, e *messag return err } } - return tx.commit() + err = tx.commit() + return } diff --git a/backend/pkg/db/postgres/messages-ios.go b/backend/pkg/db/postgres/messages-ios.go index e75ff2acd..d7b2f58f3 100644 --- a/backend/pkg/db/postgres/messages-ios.go +++ b/backend/pkg/db/postgres/messages-ios.go @@ -9,7 +9,7 @@ import ( func (conn *Conn) InsertIOSCustomEvent(sessionID uint64, e *messages.IOSCustomEvent) error { err := conn.InsertCustomEvent(sessionID, e.Timestamp, e.Index, e.Name, e.Payload) if err == nil { - conn.insertAutocompleteValue(sessionID, "CUSTOM_IOS", e.Name) + conn.insertAutocompleteValue(sessionID, 0, "CUSTOM_IOS", e.Name) } return err } @@ -17,7 +17,7 @@ func (conn *Conn) InsertIOSCustomEvent(sessionID uint64, e *messages.IOSCustomEv func (conn *Conn) InsertIOSUserID(sessionID uint64, userID *messages.IOSUserID) error { err := conn.InsertUserID(sessionID, userID.Value) if err == nil { - conn.insertAutocompleteValue(sessionID, "USERID_IOS", userID.Value) + conn.insertAutocompleteValue(sessionID, 0, "USERID_IOS", userID.Value) } return err } @@ -25,7 +25,7 @@ func (conn *Conn) InsertIOSUserID(sessionID uint64, userID *messages.IOSUserID) func (conn *Conn) InsertIOSUserAnonymousID(sessionID uint64, userAnonymousID *messages.IOSUserAnonymousID) error { err := conn.InsertUserAnonymousID(sessionID, userAnonymousID.Value) if err == nil { - conn.insertAutocompleteValue(sessionID, "USERANONYMOUSID_IOS", userAnonymousID.Value) + conn.insertAutocompleteValue(sessionID, 0, "USERANONYMOUSID_IOS", userAnonymousID.Value) } return err } @@ -33,13 +33,13 @@ func (conn *Conn) InsertIOSUserAnonymousID(sessionID uint64, userAnonymousID *me func (conn *Conn) InsertIOSNetworkCall(sessionID uint64, e *messages.IOSNetworkCall) error { err := conn.InsertRequest(sessionID, e.Timestamp, e.Index, e.URL, e.Duration, e.Success) if err == nil { - conn.insertAutocompleteValue(sessionID, "REQUEST_IOS", url.DiscardURLQuery(e.URL)) + conn.insertAutocompleteValue(sessionID, 0, "REQUEST_IOS", url.DiscardURLQuery(e.URL)) } return err } func (conn *Conn) InsertIOSScreenEnter(sessionID uint64, screenEnter *messages.IOSScreenEnter) error { - tx, err := conn.begin() + tx, err := conn.c.Begin() if err != nil { return err } @@ -65,12 +65,12 @@ func (conn *Conn) InsertIOSScreenEnter(sessionID uint64, screenEnter *messages.I if err = tx.commit(); err != nil { return err } - conn.insertAutocompleteValue(sessionID, "VIEW_IOS", screenEnter.ViewName) + conn.insertAutocompleteValue(sessionID, 0, "VIEW_IOS", screenEnter.ViewName) return nil } func (conn *Conn) InsertIOSClickEvent(sessionID uint64, clickEvent *messages.IOSClickEvent) error { - tx, err := conn.begin() + tx, err := conn.c.Begin() if err != nil { return err } @@ -96,12 +96,12 @@ func (conn *Conn) InsertIOSClickEvent(sessionID uint64, clickEvent *messages.IOS if err = tx.commit(); err != nil { return err } - conn.insertAutocompleteValue(sessionID, "CLICK_IOS", clickEvent.Label) + conn.insertAutocompleteValue(sessionID, 0, "CLICK_IOS", clickEvent.Label) return nil } func (conn *Conn) InsertIOSInputEvent(sessionID uint64, inputEvent *messages.IOSInputEvent) error { - tx, err := conn.begin() + tx, err := conn.c.Begin() if err != nil { return err } @@ -132,13 +132,13 @@ func (conn *Conn) InsertIOSInputEvent(sessionID uint64, inputEvent *messages.IOS if err = tx.commit(); err != nil { return err } - conn.insertAutocompleteValue(sessionID, "INPUT_IOS", inputEvent.Label) + conn.insertAutocompleteValue(sessionID, 0, "INPUT_IOS", inputEvent.Label) // conn.insertAutocompleteValue(sessionID, "INPUT_VALUE", inputEvent.Label) return nil } func (conn *Conn) InsertIOSCrash(sessionID uint64, projectID uint32, crash *messages.IOSCrash) error { - tx, err := conn.begin() + tx, err := conn.c.Begin() if err != nil { return err } diff --git a/backend/pkg/db/postgres/messages-web.go b/backend/pkg/db/postgres/messages-web.go index e703ee933..c55344509 100644 --- a/backend/pkg/db/postgres/messages-web.go +++ b/backend/pkg/db/postgres/messages-web.go @@ -1,6 +1,7 @@ package postgres import ( + "log" "math" "openreplay/backend/pkg/hashid" @@ -13,104 +14,54 @@ func getSqIdx(messageID uint64) uint { return uint(messageID % math.MaxInt32) } -func (conn *Conn) InsertWebCustomEvent(sessionID uint64, e *CustomEvent) error { +func (conn *Conn) InsertWebCustomEvent(sessionID uint64, projectID uint32, e *CustomEvent) error { err := conn.InsertCustomEvent(sessionID, e.Timestamp, e.MessageID, e.Name, e.Payload) if err == nil { - conn.insertAutocompleteValue(sessionID, "CUSTOM", e.Name) + conn.insertAutocompleteValue(sessionID, projectID, "CUSTOM", e.Name) } return err } -func (conn *Conn) InsertWebUserID(sessionID uint64, userID *UserID) error { +func (conn *Conn) InsertWebUserID(sessionID uint64, projectID uint32, userID *UserID) error { err := conn.InsertUserID(sessionID, userID.ID) if err == nil { - conn.insertAutocompleteValue(sessionID, "USERID", userID.ID) + conn.insertAutocompleteValue(sessionID, projectID, "USERID", userID.ID) } return err } -func (conn *Conn) InsertWebUserAnonymousID(sessionID uint64, userAnonymousID *UserAnonymousID) error { +func (conn *Conn) InsertWebUserAnonymousID(sessionID uint64, projectID uint32, userAnonymousID *UserAnonymousID) error { err := conn.InsertUserAnonymousID(sessionID, userAnonymousID.ID) if err == nil { - conn.insertAutocompleteValue(sessionID, "USERANONYMOUSID", userAnonymousID.ID) + conn.insertAutocompleteValue(sessionID, projectID, "USERANONYMOUSID", userAnonymousID.ID) } return err } -// func (conn *Conn) InsertWebResourceEvent(sessionID uint64, e *ResourceEvent) error { -// if e.Type != "fetch" { -// return nil -// } -// err := conn.InsertRequest(sessionID, e.Timestamp, -// e.MessageID, -// e.URL, e.Duration, e.Success, -// ) -// if err == nil { -// conn.insertAutocompleteValue(sessionID, "REQUEST", url.DiscardURLQuery(e.URL)) -// } -// return err -// } - // TODO: fix column "dom_content_loaded_event_end" of relation "pages" -func (conn *Conn) InsertWebPageEvent(sessionID uint64, e *PageEvent) error { +func (conn *Conn) InsertWebPageEvent(sessionID uint64, projectID uint32, e *PageEvent) error { host, path, query, err := url.GetURLParts(e.URL) if err != nil { return err } - tx, err := conn.begin() - if err != nil { - return err + // base_path is deprecated + if err = conn.webPageEvents.Append(sessionID, e.MessageID, e.Timestamp, e.Referrer, url.DiscardURLQuery(e.Referrer), + host, path, query, e.DomContentLoadedEventEnd, e.LoadEventEnd, e.ResponseEnd, e.FirstPaint, e.FirstContentfulPaint, + e.SpeedIndex, e.VisuallyComplete, e.TimeToInteractive, calcResponseTime(e), calcDomBuildingTime(e)); err != nil { + log.Printf("insert web page event in bulk err: %s", err) } - defer tx.rollback() - // base_path is depricated - if err := tx.exec(` - INSERT INTO events.pages ( - session_id, message_id, timestamp, referrer, base_referrer, host, path, query, - dom_content_loaded_time, load_time, response_end, first_paint_time, first_contentful_paint_time, - speed_index, visually_complete, time_to_interactive, - response_time, dom_building_time - ) VALUES ( - $1, $2, $3, - $4, $5, - $6, $7, $8, - NULLIF($9, 0), NULLIF($10, 0), NULLIF($11, 0), NULLIF($12, 0), NULLIF($13, 0), - NULLIF($14, 0), NULLIF($15, 0), NULLIF($16, 0), - NULLIF($17, 0), NULLIF($18, 0) - ) - `, - sessionID, e.MessageID, e.Timestamp, - e.Referrer, url.DiscardURLQuery(e.Referrer), - host, path, query, - e.DomContentLoadedEventEnd, e.LoadEventEnd, e.ResponseEnd, e.FirstPaint, e.FirstContentfulPaint, - e.SpeedIndex, e.VisuallyComplete, e.TimeToInteractive, - calcResponseTime(e), calcDomBuildingTime(e), - ); err != nil { - return err - } - if err = tx.exec(` - UPDATE sessions SET (pages_count, events_count) = (pages_count + 1, events_count + 1) - WHERE session_id = $1`, - sessionID, - ); err != nil { - return err - } - if err = tx.commit(); err != nil { - return err - } - conn.insertAutocompleteValue(sessionID, "LOCATION", url.DiscardURLQuery(path)) - conn.insertAutocompleteValue(sessionID, "REFERRER", url.DiscardURLQuery(e.Referrer)) + // Accumulate session updates and exec inside batch with another sql commands + conn.updateSessionEvents(sessionID, 1, 1) + // Add new value set to autocomplete bulk + conn.insertAutocompleteValue(sessionID, projectID, "LOCATION", url.DiscardURLQuery(path)) + conn.insertAutocompleteValue(sessionID, projectID, "REFERRER", url.DiscardURLQuery(e.Referrer)) return nil } -func (conn *Conn) InsertWebClickEvent(sessionID uint64, e *ClickEvent) error { - tx, err := conn.begin() - if err != nil { - return err - } - defer tx.rollback() - if err = tx.exec(` +func (conn *Conn) InsertWebClickEvent(sessionID uint64, projectID uint32, e *ClickEvent) error { + sqlRequest := ` INSERT INTO events.clicks (session_id, message_id, timestamp, label, selector, url) (SELECT @@ -118,65 +69,40 @@ func (conn *Conn) InsertWebClickEvent(sessionID uint64, e *ClickEvent) error { FROM events.pages WHERE session_id = $1 AND timestamp <= $3 ORDER BY timestamp DESC LIMIT 1 ) - `, - sessionID, e.MessageID, e.Timestamp, e.Label, e.Selector, - ); err != nil { - return err - } - if err = tx.exec(` - UPDATE sessions SET events_count = events_count + 1 - WHERE session_id = $1`, - sessionID, - ); err != nil { - return err - } - if err = tx.commit(); err != nil { - return err - } - conn.insertAutocompleteValue(sessionID, "CLICK", e.Label) + ` + conn.batchQueue(sessionID, sqlRequest, sessionID, e.MessageID, e.Timestamp, e.Label, e.Selector) + // Accumulate session updates and exec inside batch with another sql commands + conn.updateSessionEvents(sessionID, 1, 0) + // Add new value set to autocomplete bulk + conn.insertAutocompleteValue(sessionID, projectID, "CLICK", e.Label) return nil } -func (conn *Conn) InsertWebInputEvent(sessionID uint64, e *InputEvent) error { - tx, err := conn.begin() - if err != nil { - return err - } - defer tx.rollback() +func (conn *Conn) InsertWebInputEvent(sessionID uint64, projectID uint32, e *InputEvent) error { value := &e.Value if e.ValueMasked { value = nil } - if err = tx.exec(` - INSERT INTO events.inputs - (session_id, message_id, timestamp, value, label) - VALUES - ($1, $2, $3, $4, NULLIF($5,'')) - `, - sessionID, e.MessageID, e.Timestamp, value, e.Label, - ); err != nil { - return err + if err := conn.webInputEvents.Append(sessionID, e.MessageID, e.Timestamp, value, e.Label); err != nil { + log.Printf("insert web input event err: %s", err) } - if err = tx.exec(` - UPDATE sessions SET events_count = events_count + 1 - WHERE session_id = $1`, - sessionID, - ); err != nil { - return err - } - if err = tx.commit(); err != nil { - return err - } - conn.insertAutocompleteValue(sessionID, "INPUT", e.Label) + conn.updateSessionEvents(sessionID, 1, 0) + conn.insertAutocompleteValue(sessionID, projectID, "INPUT", e.Label) return nil } -func (conn *Conn) InsertWebErrorEvent(sessionID uint64, projectID uint32, e *ErrorEvent) error { - tx, err := conn.begin() +func (conn *Conn) InsertWebErrorEvent(sessionID uint64, projectID uint32, e *ErrorEvent) (err error) { + tx, err := conn.c.Begin() if err != nil { return err } - defer tx.rollback() + defer func() { + if err != nil { + if rollbackErr := tx.rollback(); rollbackErr != nil { + log.Printf("rollback err: %s", rollbackErr) + } + } + }() errorID := hashid.WebErrorID(projectID, e) if err = tx.exec(` @@ -206,17 +132,18 @@ func (conn *Conn) InsertWebErrorEvent(sessionID uint64, projectID uint32, e *Err ); err != nil { return err } - return tx.commit() + err = tx.commit() + return } -func (conn *Conn) InsertWebFetchEvent(sessionID uint64, savePayload bool, e *FetchEvent) error { +func (conn *Conn) InsertWebFetchEvent(sessionID uint64, projectID uint32, savePayload bool, e *FetchEvent) error { var request, response *string if savePayload { request = &e.Request response = &e.Response } host, path, query, err := url.GetURLParts(e.URL) - conn.insertAutocompleteValue(sessionID, "REQUEST", path) + conn.insertAutocompleteValue(sessionID, projectID, "REQUEST", path) if err != nil { return err } @@ -246,29 +173,15 @@ func (conn *Conn) InsertWebFetchEvent(sessionID uint64, savePayload bool, e *Fet return nil } -func (conn *Conn) InsertWebGraphQLEvent(sessionID uint64, savePayload bool, e *GraphQLEvent) error { +func (conn *Conn) InsertWebGraphQLEvent(sessionID uint64, projectID uint32, savePayload bool, e *GraphQLEvent) error { var request, response *string if savePayload { request = &e.Variables response = &e.Response } - conn.insertAutocompleteValue(sessionID, "GRAPHQL", e.OperationName) - - sqlRequest := ` - INSERT INTO events.graphql ( - session_id, timestamp, message_id, - name, - request_body, response_body - ) VALUES ( - $1, $2, $3, - left($4, 2700), - $5, $6 - ) ON CONFLICT DO NOTHING` - conn.batchQueue(sessionID, sqlRequest, sessionID, e.Timestamp, e.MessageID, - e.OperationName, request, response, - ) - - // Record approximate message size - conn.updateBatchSize(sessionID, len(sqlRequest)+len(e.OperationName)+len(e.Variables)+len(e.Response)+8*3) + if err := conn.webGraphQLEvents.Append(sessionID, e.Timestamp, e.MessageID, e.OperationName, request, response); err != nil { + log.Printf("insert web graphQL event err: %s", err) + } + conn.insertAutocompleteValue(sessionID, projectID, "GRAPHQL", e.OperationName) return nil } diff --git a/backend/pkg/db/postgres/pool.go b/backend/pkg/db/postgres/pool.go new file mode 100644 index 000000000..5f9cbaa29 --- /dev/null +++ b/backend/pkg/db/postgres/pool.go @@ -0,0 +1,175 @@ +package postgres + +import ( + "context" + "errors" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/instrument/syncfloat64" + "strings" + "time" +) + +// Pool is a pgx.Pool wrapper with metrics integration +type Pool interface { + Query(sql string, args ...interface{}) (pgx.Rows, error) + QueryRow(sql string, args ...interface{}) pgx.Row + Exec(sql string, arguments ...interface{}) error + SendBatch(b *pgx.Batch) pgx.BatchResults + Begin() (*_Tx, error) + Close() +} + +type poolImpl struct { + conn *pgxpool.Pool + sqlRequestTime syncfloat64.Histogram + sqlRequestCounter syncfloat64.Counter +} + +func (p *poolImpl) Query(sql string, args ...interface{}) (pgx.Rows, error) { + start := time.Now() + res, err := p.conn.Query(getTimeoutContext(), sql, args...) + method, table := methodName(sql) + p.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), + attribute.String("method", method), attribute.String("table", table)) + p.sqlRequestCounter.Add(context.Background(), 1, + attribute.String("method", method), attribute.String("table", table)) + return res, err +} + +func (p *poolImpl) QueryRow(sql string, args ...interface{}) pgx.Row { + start := time.Now() + res := p.conn.QueryRow(getTimeoutContext(), sql, args...) + method, table := methodName(sql) + p.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), + attribute.String("method", method), attribute.String("table", table)) + p.sqlRequestCounter.Add(context.Background(), 1, + attribute.String("method", method), attribute.String("table", table)) + return res +} + +func (p *poolImpl) Exec(sql string, arguments ...interface{}) error { + start := time.Now() + _, err := p.conn.Exec(getTimeoutContext(), sql, arguments...) + method, table := methodName(sql) + p.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), + attribute.String("method", method), attribute.String("table", table)) + p.sqlRequestCounter.Add(context.Background(), 1, + attribute.String("method", method), attribute.String("table", table)) + return err +} + +func (p *poolImpl) SendBatch(b *pgx.Batch) pgx.BatchResults { + start := time.Now() + res := p.conn.SendBatch(getTimeoutContext(), b) + p.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), + attribute.String("method", "sendBatch")) + p.sqlRequestCounter.Add(context.Background(), 1, + attribute.String("method", "sendBatch")) + return res +} + +func (p *poolImpl) Begin() (*_Tx, error) { + start := time.Now() + tx, err := p.conn.Begin(context.Background()) + p.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), + attribute.String("method", "begin")) + p.sqlRequestCounter.Add(context.Background(), 1, + attribute.String("method", "begin")) + return &_Tx{tx, p.sqlRequestTime, p.sqlRequestCounter}, err +} + +func (p *poolImpl) Close() { + p.conn.Close() +} + +func NewPool(conn *pgxpool.Pool, sqlRequestTime syncfloat64.Histogram, sqlRequestCounter syncfloat64.Counter) (Pool, error) { + if conn == nil { + return nil, errors.New("conn is empty") + } + return &poolImpl{ + conn: conn, + sqlRequestTime: sqlRequestTime, + sqlRequestCounter: sqlRequestCounter, + }, nil +} + +// TX - start + +type _Tx struct { + pgx.Tx + sqlRequestTime syncfloat64.Histogram + sqlRequestCounter syncfloat64.Counter +} + +func (tx *_Tx) exec(sql string, args ...interface{}) error { + start := time.Now() + _, err := tx.Exec(context.Background(), sql, args...) + method, table := methodName(sql) + tx.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), + attribute.String("method", method), attribute.String("table", table)) + tx.sqlRequestCounter.Add(context.Background(), 1, + attribute.String("method", method), attribute.String("table", table)) + return err +} + +func (tx *_Tx) rollback() error { + start := time.Now() + err := tx.Rollback(context.Background()) + tx.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), + attribute.String("method", "rollback")) + tx.sqlRequestCounter.Add(context.Background(), 1, + attribute.String("method", "rollback")) + return err +} + +func (tx *_Tx) commit() error { + start := time.Now() + err := tx.Commit(context.Background()) + tx.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), + attribute.String("method", "commit")) + tx.sqlRequestCounter.Add(context.Background(), 1, + attribute.String("method", "commit")) + return err +} + +// TX - end + +func getTimeoutContext() context.Context { + ctx, _ := context.WithTimeout(context.Background(), time.Second*30) + return ctx +} + +func methodName(sql string) (string, string) { + cmd, table := "unknown", "unknown" + + // Prepare sql request for parsing + sql = strings.TrimSpace(sql) + sql = strings.ReplaceAll(sql, "\n", " ") + sql = strings.ReplaceAll(sql, "\t", "") + sql = strings.ToLower(sql) + + // Get sql command name + parts := strings.Split(sql, " ") + if parts[0] == "" { + return cmd, table + } else { + cmd = strings.TrimSpace(parts[0]) + } + + // Get table name + switch cmd { + case "select": + for i, p := range parts { + if strings.TrimSpace(p) == "from" { + table = strings.TrimSpace(parts[i+1]) + } + } + case "update": + table = strings.TrimSpace(parts[1]) + case "insert": + table = strings.TrimSpace(parts[2]) + } + return cmd, table +} diff --git a/backend/pkg/db/postgres/project.go b/backend/pkg/db/postgres/project.go index 066339791..f38161885 100644 --- a/backend/pkg/db/postgres/project.go +++ b/backend/pkg/db/postgres/project.go @@ -6,7 +6,7 @@ import ( func (conn *Conn) GetProjectByKey(projectKey string) (*Project, error) { p := &Project{ProjectKey: projectKey} - if err := conn.queryRow(` + if err := conn.c.QueryRow(` SELECT max_session_duration, sample_rate, project_id FROM projects WHERE project_key=$1 AND active = true @@ -21,7 +21,7 @@ func (conn *Conn) GetProjectByKey(projectKey string) (*Project, error) { // TODO: logical separation of metadata func (conn *Conn) GetProject(projectID uint32) (*Project, error) { p := &Project{ProjectID: projectID} - if err := conn.queryRow(` + if err := conn.c.QueryRow(` SELECT project_key, max_session_duration, save_request_payloads, metadata_1, metadata_2, metadata_3, metadata_4, metadata_5, metadata_6, metadata_7, metadata_8, metadata_9, metadata_10 diff --git a/backend/pkg/db/postgres/session-updates.go b/backend/pkg/db/postgres/session-updates.go new file mode 100644 index 000000000..14260c2c6 --- /dev/null +++ b/backend/pkg/db/postgres/session-updates.go @@ -0,0 +1,30 @@ +package postgres + +// Mechanism of combination several session updates into one +const sessionUpdateReq = `UPDATE sessions SET (pages_count, events_count) = (pages_count + $1, events_count + $2) WHERE session_id = $3` + +type sessionUpdates struct { + sessionID uint64 + pages int + events int +} + +func NewSessionUpdates(sessionID uint64) *sessionUpdates { + return &sessionUpdates{ + sessionID: sessionID, + pages: 0, + events: 0, + } +} + +func (su *sessionUpdates) add(pages, events int) { + su.pages += pages + su.events += events +} + +func (su *sessionUpdates) request() (string, []interface{}) { + if su.pages == 0 && su.events == 0 { + return "", nil + } + return sessionUpdateReq, []interface{}{su.pages, su.events, su.sessionID} +} diff --git a/backend/pkg/db/postgres/session.go b/backend/pkg/db/postgres/session.go index 7148d9871..9735cdc1a 100644 --- a/backend/pkg/db/postgres/session.go +++ b/backend/pkg/db/postgres/session.go @@ -1,14 +1,11 @@ package postgres -//import . "openreplay/backend/pkg/messages" import . "openreplay/backend/pkg/db/types" -//import "log" - func (conn *Conn) GetSession(sessionID uint64) (*Session, error) { s := &Session{SessionID: sessionID} var revID, userOSVersion *string - if err := conn.queryRow(` + if err := conn.c.QueryRow(` SELECT platform, duration, project_id, start_ts, user_uuid, user_os, user_os_version, @@ -39,69 +36,3 @@ func (conn *Conn) GetSession(sessionID uint64) (*Session, error) { } return s, nil } - -// func (conn *Conn) GetSessionClickEvents(sessionID uint64) (list []IOSClickEvent, err error) { -// rows, err := conn.query(` -// SELECT -// timestamp, seq_index, label -// FROM events_ios.clicks -// WHERE session_id=$1 -// `, sessionID) -// if err != nil { -// return err -// } -// defer rows.Close() -// for rows.Next() { -// e := new(IOSClickEvent) -// if err = rows.Scan(&e.Timestamp, &e.Index, &e.Label); err != nil { -// log.Printf("Error while scanning click events: %v", err) -// } else { -// list = append(list, e) -// } -// } -// return list -// } - -// func (conn *Conn) GetSessionInputEvents(sessionID uint64) (list []IOSInputEvent, err error) { -// rows, err := conn.query(` -// SELECT -// timestamp, seq_index, label, value -// FROM events_ios.inputs -// WHERE session_id=$1 -// `, sessionID) -// if err != nil { -// return err -// } -// defer rows.Close() -// for rows.Next() { -// e := new(IOSInputEvent) -// if err = rows.Scan(&e.Timestamp, &e.Index, &e.Label, &e.Value); err != nil { -// log.Printf("Error while scanning click events: %v", err) -// } else { -// list = append(list, e) -// } -// } -// return list -// } - -// func (conn *Conn) GetSessionCrashEvents(sessionID uint64) (list []IOSCrash, err error) { -// rows, err := conn.query(` -// SELECT -// timestamp, seq_index -// FROM events_ios.crashes -// WHERE session_id=$1 -// `, sessionID) -// if err != nil { -// return err -// } -// defer rows.Close() -// for rows.Next() { -// e := new(IOSCrash) -// if err = rows.Scan(&e.Timestamp, &e.Index, &e.Label, &e.Value); err != nil { -// log.Printf("Error while scanning click events: %v", err) -// } else { -// list = append(list, e) -// } -// } -// return list -// } diff --git a/backend/pkg/db/postgres/unstarted-session.go b/backend/pkg/db/postgres/unstarted-session.go index 2a9a71037..cc27e3f5d 100644 --- a/backend/pkg/db/postgres/unstarted-session.go +++ b/backend/pkg/db/postgres/unstarted-session.go @@ -16,7 +16,7 @@ type UnstartedSession struct { } func (conn *Conn) InsertUnstartedSession(s UnstartedSession) error { - return conn.exec(` + return conn.c.Exec(` INSERT INTO unstarted_sessions ( project_id, tracker_version, do_not_track, diff --git a/ee/api/.dockerignore b/ee/api/.dockerignore new file mode 100644 index 000000000..b456d908b --- /dev/null +++ b/ee/api/.dockerignore @@ -0,0 +1,15 @@ +# ignore .git and .cache folders +.git +.cache +**/build.sh +**/build_*.sh +**/*deploy.sh + +app_crons.py +app_alerts.py +requirements-crons.txt +requirements-alerts.txt +build_crons.sh +build_alerts.sh +entrypoint_crons.sh +entrypoint_alerts.sh \ No newline at end of file diff --git a/ee/api/Dockerfile b/ee/api/Dockerfile index 9c4faa962..f599bfc45 100644 --- a/ee/api/Dockerfile +++ b/ee/api/Dockerfile @@ -1,28 +1,18 @@ -FROM python:3.9.12-slim +FROM python:3.10-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" -ENV APP_NAME chalice -RUN apt-get update && apt-get install -y pkg-config libxmlsec1-dev gcc && rm -rf /var/lib/apt/lists/* -# Add Tini -# Startup daemon -ENV TINI_VERSION=v0.19.0 \ - SOURCE_MAP_VERSION=0.7.4 +RUN apk add --no-cache build-base libressl libffi-dev libressl-dev libxslt-dev libxml2-dev xmlsec-dev xmlsec nodejs npm tini +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main ARG envarg -ENV ENTERPRISE_BUILD ${envarg} -ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini -ADD https://unpkg.com/source-map@${SOURCE_MAP_VERSION}/lib/mappings.wasm /mappings.wasm -RUN chmod +x /tini +ENV SOURCE_MAP_VERSION=0.7.4 \ + APP_NAME=chalice \ + ENTERPRISE_BUILD=${envarg} -# Installing Nodejs -RUN apt update && apt install -y curl && \ - curl -fsSL https://deb.nodesource.com/setup_12.x | bash - && \ - apt install -y nodejs && \ - apt remove --purge -y curl && \ - rm -rf /var/lib/apt/lists/* +ADD https://unpkg.com/source-map@${SOURCE_MAP_VERSION}/lib/mappings.wasm /mappings.wasm WORKDIR /work_tmp COPY requirements.txt /work_tmp/requirements.txt -RUN pip install -r /work_tmp/requirements.txt +RUN pip install --no-cache-dir --upgrade -r /work_tmp/requirements.txt COPY sourcemap-reader/*.json /work_tmp/ RUN cd /work_tmp && npm install @@ -30,5 +20,8 @@ WORKDIR /work COPY . . RUN mv env.default .env && mv /work_tmp/node_modules sourcemap-reader/. -ENTRYPOINT ["/tini", "--"] +RUN adduser -u 1001 openreplay -D +USER 1001 + +ENTRYPOINT ["/sbin/tini", "--"] CMD ./entrypoint.sh diff --git a/ee/api/Dockerfile.alerts b/ee/api/Dockerfile.alerts index 9e43d81f0..785b0a5f9 100644 --- a/ee/api/Dockerfile.alerts +++ b/ee/api/Dockerfile.alerts @@ -1,25 +1,23 @@ -FROM python:3.9.12-slim +FROM python:3.10-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" -RUN apt-get update && apt-get install -y pkg-config libxmlsec1-dev gcc && rm -rf /var/lib/apt/lists/* -ENV APP_NAME alerts -ENV pg_minconn 2 -ENV pg_maxconn 10 - -# Add Tini -# Startup daemon -ENV TINI_VERSION v0.19.0 +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main +RUN apk add --no-cache build-base tini ARG envarg -ENV ENTERPRISE_BUILD ${envarg} -ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini -RUN chmod +x /tini +ENV APP_NAME=alerts \ + pg_minconn=2 \ + pg_maxconn=10 \ + ENTERPRISE_BUILD=${envarg} -COPY requirements.txt /work_tmp/requirements.txt -RUN pip install -r /work_tmp/requirements.txt +COPY requirements-alerts.txt /work_tmp/requirements.txt +RUN pip install --no-cache-dir --upgrade -r /work_tmp/requirements.txt WORKDIR /work COPY . . RUN mv env.default .env && mv app_alerts.py app.py && mv entrypoint_alerts.sh entrypoint.sh -ENTRYPOINT ["/tini", "--"] -CMD ./entrypoint.sh \ No newline at end of file +RUN adduser -u 1001 openreplay -D +USER 1001 + +ENTRYPOINT ["/sbin/tini", "--"] +CMD ./entrypoint.sh diff --git a/ee/api/Dockerfile.alerts.dockerignore b/ee/api/Dockerfile.alerts.dockerignore new file mode 100644 index 000000000..8e1cbef03 --- /dev/null +++ b/ee/api/Dockerfile.alerts.dockerignore @@ -0,0 +1,15 @@ +# ignore .git and .cache folders +.git +.cache +**/build.sh +**/build_*.sh +**/*deploy.sh + +app.py +app_crons.py +requirements.txt +requirements-crons.txt +build.sh +build_crons.sh +entrypoint.sh +entrypoint_crons.sh \ No newline at end of file diff --git a/ee/api/Dockerfile.crons b/ee/api/Dockerfile.crons index d4b8ed8f0..0647c6fc6 100644 --- a/ee/api/Dockerfile.crons +++ b/ee/api/Dockerfile.crons @@ -1,26 +1,25 @@ -FROM python:3.9.12-slim +FROM python:3.10-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" -ENV APP_NAME crons -ENV pg_minconn 2 -ENV pg_maxconn 10 -RUN apt-get update && apt-get install -y pkg-config libxmlsec1-dev gcc && rm -rf /var/lib/apt/lists/* -# Add Tini -# Startup daemon -ENV TINI_VERSION=v0.19.0 \ - ACTION="" +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main +RUN apk add --no-cache build-base tini ARG envarg -ENV ENTERPRISE_BUILD ${envarg} -ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini -RUN chmod +x /tini +ENV APP_NAME=crons \ + pg_minconn=2 \ + pg_maxconn=10 \ + ENTERPRISE_BUILD=${envarg} \ + ACTION="" WORKDIR /work_tmp -COPY requirements.txt /work_tmp/requirements.txt -RUN pip install -r /work_tmp/requirements.txt +COPY requirements-crons.txt /work_tmp/requirements.txt +RUN pip install --no-cache-dir --upgrade -r /work_tmp/requirements.txt WORKDIR /work COPY . . RUN mv env.default .env && mv entrypoint_crons.sh entrypoint.sh -ENTRYPOINT ["/tini", "--"] +RUN adduser -u 1001 openreplay -D +USER 1001 + +ENTRYPOINT ["/sbin/tini", "--"] CMD ./entrypoint.sh diff --git a/ee/api/Dockerfile.crons.dockerignore b/ee/api/Dockerfile.crons.dockerignore new file mode 100644 index 000000000..38fed8fd0 --- /dev/null +++ b/ee/api/Dockerfile.crons.dockerignore @@ -0,0 +1,15 @@ +# ignore .git and .cache folders +.git +.cache +**/build.sh +**/build_*.sh +**/*deploy.sh + +app.py +app_alerts.py +requirements.txt +requirements-alerts.txt +build.sh +build_alerts.sh +entrypoint.sh +entrypoint_alerts.sh \ No newline at end of file diff --git a/ee/api/app_crons.py b/ee/api/app_crons.py index 3dfea4fe4..97f4b5406 100644 --- a/ee/api/app_crons.py +++ b/ee/api/app_crons.py @@ -1,19 +1,28 @@ print("============= CRONS =============") import sys +import asyncio from routers.crons import core_dynamic_crons -def process(action): - { +def default_action(action): + async def _func(): + print(f"{action} not found in crons-definitions") + + return _func + + +async def process(action): + await { "TELEMETRY": core_dynamic_crons.telemetry_cron, "JOB": core_dynamic_crons.run_scheduled_jobs, "REPORT": core_dynamic_crons.weekly_report2 - }.get(action.upper(), lambda: print(f"{action} not found in crons-definitions"))() + }.get(action.upper(), default_action(action))() if __name__ == '__main__': if len(sys.argv) < 2 or len(sys.argv[1]) < 1: print("please provide actions as argument") else: - process(sys.argv[1]) + print(f"action: {sys.argv[1]}") + asyncio.run(process(sys.argv[1])) diff --git a/ee/api/build_crons.sh b/ee/api/build_crons.sh index 681d2187e..32bc7ec7f 100644 --- a/ee/api/build_crons.sh +++ b/ee/api/build_crons.sh @@ -6,23 +6,6 @@ # Default will be OSS build. # Usage: IMAGE_TAG=latest DOCKER_REPO=myDockerHubID bash build.sh -function make_submodule() { - # -- this part was generated by modules_lister.py -- - mkdir crons - cp -R ./{app_crons,schemas,schemas_ee}.py ./crons/ - mkdir -p ./crons/routers/crons/ - cp -R ./routers/crons/{__init__,core_dynamic_crons}.py ./crons/routers/crons/ - mkdir -p ./crons/chalicelib/ - cp -R ./chalicelib/__init__.py ./crons/chalicelib/ - mkdir -p ./crons/chalicelib/core/ - cp -R ./chalicelib/core/{__init__,telemetry,license,unlock,weekly_report,jobs,sessions,events,issues,sessions_metas,metadata,projects,users,authorizers,tenants,roles,assist,events_ios,sessions_mobs,errors,metrics,sourcemaps,sourcemaps_parser,resources,performance_event}.py ./crons/chalicelib/core/ - mkdir -p ./crons/chalicelib/utils/ - cp -R ./chalicelib/utils/{__init__,TimeUTC,pg_client,helper,event_filter_definition,dev,email_helper,email_handler,smtp,s3,args_transformer,ch_client,SAML2_helper,metrics_helper}.py ./crons/chalicelib/utils/ - # -- end of generated part - - cp -R ./{Dockerfile.crons,requirements.txt,env.default,entrypoint_crons.sh} ./crons/ - cp -R ./chalicelib/utils/html ./crons/chalicelib/utils/html -} git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} envarg="default-foss" @@ -42,11 +25,9 @@ function build_api(){ envarg="default-ee" tag="ee-" - make_submodule $1 - cd crons - docker build -f ./Dockerfile.crons --build-arg envarg=$envarg -t ${DOCKER_REPO:-'local'}/crons:${git_sha1} . - cd .. - rm -rf crons + cp -R ../api ../_crons + docker build -f ../_crons/Dockerfile.crons --build-arg envarg=$envarg -t ${DOCKER_REPO:-'local'}/crons:${git_sha1} . + rm -rf ../crons [[ $PUSH_IMAGE -eq 1 ]] && { docker push ${DOCKER_REPO:-'local'}/crons:${git_sha1} docker tag ${DOCKER_REPO:-'local'}/crons:${git_sha1} ${DOCKER_REPO:-'local'}/crons:${tag}latest diff --git a/ee/api/chalicelib/core/projects.py b/ee/api/chalicelib/core/projects.py index 6a06e8230..e6ef34760 100644 --- a/ee/api/chalicelib/core/projects.py +++ b/ee/api/chalicelib/core/projects.py @@ -52,14 +52,23 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st AND users.tenant_id = %(tenant_id)s AND (roles.all_projects OR roles_projects.project_id = s.project_id) ) AS role_project ON (TRUE)""" + pre_select = "" + if recorded: + pre_select = """WITH recorded_p AS (SELECT DISTINCT projects.project_id + FROM projects INNER JOIN sessions USING (project_id) + WHERE tenant_id =%(tenant_id)s + AND deleted_at IS NULL + AND duration > 0)""" cur.execute( cur.mogrify(f"""\ + {pre_select} SELECT s.project_id, s.name, s.project_key, s.save_request_payloads {',s.gdpr' if gdpr else ''} - {',COALESCE((SELECT TRUE FROM public.sessions WHERE sessions.project_id = s.project_id LIMIT 1), FALSE) AS recorded' if recorded else ''} + {',EXISTS(SELECT 1 FROM recorded_p WHERE recorded_p.project_id = s.project_id) AS recorded' if recorded else ''} {',stack_integrations.count>0 AS stack_integrations' if stack_integrations else ''} FROM public.projects AS s + {'LEFT JOIN recorded_p USING (project_id)' if recorded else ''} {'LEFT JOIN LATERAL (SELECT COUNT(*) AS count FROM public.integrations WHERE s.project_id = integrations.project_id LIMIT 1) AS stack_integrations ON TRUE' if stack_integrations else ''} {role_query if user_id is not None else ""} WHERE s.tenant_id =%(tenant_id)s @@ -76,7 +85,6 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st WHERE sessions.start_ts >= %(startDate)s AND sessions.start_ts <= %(endDate)s GROUP BY project_id;""", {"startDate": TimeUTC.now(delta_days=-3), "endDate": TimeUTC.now(delta_days=1)}) - cur.execute(query=query) status = cur.fetchall() for r in rows: diff --git a/ee/api/chalicelib/core/resources.py b/ee/api/chalicelib/core/resources.py index a1f966937..71e493a4d 100644 --- a/ee/api/chalicelib/core/resources.py +++ b/ee/api/chalicelib/core/resources.py @@ -6,6 +6,8 @@ from decouple import config def get_by_session_id(session_id, project_id, start_ts, duration): with ch_client.ClickHouseClient() as ch: + if duration is None or (type(duration) != 'int' and type(duration) != 'float') or duration < 0: + duration = 0 delta = config("events_ts_delta", cast=int, default=60 * 60) * 1000 ch_query = """\ SELECT diff --git a/ee/api/chalicelib/core/tenants.py b/ee/api/chalicelib/core/tenants.py index 3f810884e..71119fd13 100644 --- a/ee/api/chalicelib/core/tenants.py +++ b/ee/api/chalicelib/core/tenants.py @@ -34,7 +34,8 @@ def get_by_tenant_id(tenant_id): t.created_at, '{license.EDITION}' AS edition, t.version_number, - t.opt_out + t.opt_out, + t.tenant_key FROM public.tenants AS t WHERE t.tenant_id = %(tenantId)s AND t.deleted_at ISNULL LIMIT 1;""", diff --git a/ee/api/chalicelib/core/users.py b/ee/api/chalicelib/core/users.py index 908c913f6..815d39106 100644 --- a/ee/api/chalicelib/core/users.py +++ b/ee/api/chalicelib/core/users.py @@ -4,11 +4,9 @@ import secrets from decouple import config from fastapi import BackgroundTasks -import schemas import schemas_ee from chalicelib.core import authorizers, metadata, projects, roles from chalicelib.core import tenants, assist -from chalicelib.utils import dev, SAML2_helper from chalicelib.utils import helper, email_helper from chalicelib.utils import pg_client from chalicelib.utils.TimeUTC import TimeUTC @@ -170,7 +168,11 @@ def update(tenant_id, user_id, changes): (CASE WHEN users.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, (CASE WHEN users.role = 'admin' THEN TRUE ELSE FALSE END) AS admin, (CASE WHEN users.role = 'member' THEN TRUE ELSE FALSE END) AS member, - users.role_id;""", + users.role_id, + (SELECT roles.name + FROM roles + WHERE roles.tenant_id=%(tenant_id)s + AND roles.role_id=users.role_id) AS role_name;""", {"tenant_id": tenant_id, "user_id": user_id, **changes}) ) if len(sub_query_bauth) > 0: @@ -189,7 +191,11 @@ def update(tenant_id, user_id, changes): (CASE WHEN users.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, (CASE WHEN users.role = 'admin' THEN TRUE ELSE FALSE END) AS admin, (CASE WHEN users.role = 'member' THEN TRUE ELSE FALSE END) AS member, - users.role_id;""", + users.role_id, + (SELECT roles.name + FROM roles + WHERE roles.tenant_id=%(tenant_id)s + AND roles.role_id=users.role_id) AS role_name;""", {"tenant_id": tenant_id, "user_id": user_id, **changes}) ) @@ -649,7 +655,7 @@ def authenticate(email, password, for_change_password=False, for_plugin=False): cur.execute(query) r = cur.fetchone() - if r is None and SAML2_helper.is_saml2_available(): + if r is None and helper.is_saml2_available(): query = cur.mogrify( f"""SELECT 1 FROM public.users diff --git a/ee/api/chalicelib/utils/SAML2_helper.py b/ee/api/chalicelib/utils/SAML2_helper.py index 5d8ee5ffc..765c8639e 100644 --- a/ee/api/chalicelib/utils/SAML2_helper.py +++ b/ee/api/chalicelib/utils/SAML2_helper.py @@ -1,4 +1,5 @@ from http import cookies +from os import environ from urllib.parse import urlparse from decouple import config @@ -125,3 +126,6 @@ def get_saml2_provider(): def get_landing_URL(jwt): return config("SITE_URL") + config("sso_landing", default="/login?jwt=%s") % jwt + + +environ["hastSAML2"] = str(is_saml2_available()) diff --git a/ee/api/clean.sh b/ee/api/clean.sh index 549228366..fa1ab8cb5 100755 --- a/ee/api/clean.sh +++ b/ee/api/clean.sh @@ -70,7 +70,6 @@ rm -rf ./routers/subs/dashboard.py rm -rf ./db_changes.sql rm -rf ./Dockerfile.bundle rm -rf ./entrypoint.bundle.sh -rm -rf ./entrypoint.sh rm -rf ./chalicelib/core/heatmaps.py rm -rf ./routers/subs/insights.py rm -rf ./schemas.py diff --git a/ee/api/entrypoint.sh b/ee/api/entrypoint.sh index 60396491c..60aba72a6 100755 --- a/ee/api/entrypoint.sh +++ b/ee/api/entrypoint.sh @@ -1,5 +1,5 @@ -#!/bin/bash -bash env_vars.sh +#!/bin/sh +sh env_vars.sh source .env.override cd sourcemap-reader nohup npm start &> /tmp/sourcemap-reader.log & diff --git a/ee/api/entrypoint_alerts.sh b/ee/api/entrypoint_alerts.sh index 9d6c95358..d2e6d677b 100755 --- a/ee/api/entrypoint_alerts.sh +++ b/ee/api/entrypoint_alerts.sh @@ -1,4 +1,4 @@ -#!/bin/bash -bash env_vars.sh +#!/bin/sh +sh env_vars.sh source .env.override uvicorn app:app --host 0.0.0.0 --reload diff --git a/ee/api/entrypoint_crons.sh b/ee/api/entrypoint_crons.sh index 056feaa9f..ef29496b2 100755 --- a/ee/api/entrypoint_crons.sh +++ b/ee/api/entrypoint_crons.sh @@ -1,4 +1,4 @@ -#!/bin/bash -bash env_vars.sh +#!/bin/sh +sh env_vars.sh source .env.override python app_crons.py $ACTION diff --git a/ee/api/env_vars.sh b/ee/api/env_vars.sh index bb3d42646..a17610aab 100755 --- a/ee/api/env_vars.sh +++ b/ee/api/env_vars.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh touch .env.override if [[ -z "${ENV_CONFIG_OVERRIDE_PATH}" ]]; then diff --git a/ee/api/requirements-alerts.txt b/ee/api/requirements-alerts.txt new file mode 100644 index 000000000..66fa84713 --- /dev/null +++ b/ee/api/requirements-alerts.txt @@ -0,0 +1,18 @@ +requests==2.28.1 +urllib3==1.26.10 +boto3==1.24.26 +pyjwt==2.4.0 +psycopg2-binary==2.9.3 +elasticsearch==8.3.1 +jira==3.3.0 + + + +fastapi==0.78.0 +uvicorn[standard]==0.18.2 +python-decouple==3.6 +pydantic[email]==1.9.1 +apscheduler==3.9.1 + +clickhouse-driver==0.2.4 +python-multipart==0.0.5 \ No newline at end of file diff --git a/ee/api/requirements-crons.txt b/ee/api/requirements-crons.txt new file mode 100644 index 000000000..66fa84713 --- /dev/null +++ b/ee/api/requirements-crons.txt @@ -0,0 +1,18 @@ +requests==2.28.1 +urllib3==1.26.10 +boto3==1.24.26 +pyjwt==2.4.0 +psycopg2-binary==2.9.3 +elasticsearch==8.3.1 +jira==3.3.0 + + + +fastapi==0.78.0 +uvicorn[standard]==0.18.2 +python-decouple==3.6 +pydantic[email]==1.9.1 +apscheduler==3.9.1 + +clickhouse-driver==0.2.4 +python-multipart==0.0.5 \ No newline at end of file diff --git a/ee/api/requirements.txt b/ee/api/requirements.txt index 1593a9206..5ce044904 100644 --- a/ee/api/requirements.txt +++ b/ee/api/requirements.txt @@ -1,16 +1,19 @@ -requests==2.28.0 -urllib3==1.26.9 -boto3==1.24.11 +requests==2.28.1 +urllib3==1.26.10 +boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 -elasticsearch==8.2.3 -jira==3.2.0 -clickhouse-driver==0.2.4 -python3-saml==1.12.0 +elasticsearch==8.3.1 +jira==3.3.0 + + fastapi==0.78.0 -python-multipart==0.0.5 -uvicorn[standard]==0.17.6 +uvicorn[standard]==0.18.2 python-decouple==3.6 pydantic[email]==1.9.1 -apscheduler==3.9.1 \ No newline at end of file +apscheduler==3.9.1 + +clickhouse-driver==0.2.4 +python3-saml==1.14.0 +python-multipart==0.0.5 \ No newline at end of file diff --git a/ee/connectors/consumer.py b/ee/connectors/consumer.py index dfa856501..8a233ecb6 100644 --- a/ee/connectors/consumer.py +++ b/ee/connectors/consumer.py @@ -49,7 +49,7 @@ def main(): elif LEVEL == 'normal': n = handle_normal_message(message) - session_id = codec.decode_key(msg.key) + session_id = decode_key(msg.key) sessions[session_id] = handle_session(sessions[session_id], message) if sessions[session_id]: sessions[session_id].sessionid = session_id @@ -116,6 +116,15 @@ def attempt_batch_insert(batch): except Exception as e: print(repr(e)) +def decode_key(b) -> int: + """ + Decode the message key (encoded with little endian) + """ + try: + decoded = int.from_bytes(b, "little", signed=False) + except Exception as e: + raise UnicodeDecodeError(f"Error while decoding message key (SessionID) from {b}\n{e}") + return decoded if __name__ == '__main__': main() diff --git a/ee/connectors/main.py b/ee/connectors/main.py index 57349f6e9..ef3a824d9 100644 --- a/ee/connectors/main.py +++ b/ee/connectors/main.py @@ -49,7 +49,7 @@ def main(): elif LEVEL == 'normal': n = handle_normal_message(message) - session_id = codec.decode_key(msg.key) + session_id = decode_key(msg.key) sessions[session_id] = handle_session(sessions[session_id], message) if sessions[session_id]: sessions[session_id].sessionid = session_id @@ -116,6 +116,15 @@ def attempt_batch_insert(batch): except Exception as e: print(repr(e)) +def decode_key(b) -> int: + """ + Decode the message key (encoded with little endian) + """ + try: + decoded = int.from_bytes(b, "little", signed=False) + except Exception as e: + raise UnicodeDecodeError(f"Error while decoding message key (SessionID) from {b}\n{e}") + return decoded if __name__ == '__main__': main() diff --git a/ee/connectors/msgcodec/codec.py b/ee/connectors/msgcodec/codec.py index 18f074a33..5aeb0e4ed 100644 --- a/ee/connectors/msgcodec/codec.py +++ b/ee/connectors/msgcodec/codec.py @@ -1,8 +1,5 @@ import io -from msgcodec.messages import * - - class Codec: """ Implements encode/decode primitives @@ -63,608 +60,3 @@ class Codec: return s.decode("utf-8", errors="replace").replace("\x00", "\uFFFD") except UnicodeDecodeError: return None - - -class MessageCodec(Codec): - - def encode(self, m: Message) -> bytes: - ... - - def decode(self, b: bytes) -> Message: - reader = io.BytesIO(b) - message_id = self.read_message_id(reader) - - if message_id == 0: - return Timestamp( - timestamp=self.read_uint(reader) - ) - if message_id == 1: - return SessionStart( - timestamp=self.read_uint(reader), - project_id=self.read_uint(reader), - tracker_version=self.read_string(reader), - rev_id=self.read_string(reader), - user_uuid=self.read_string(reader), - user_agent=self.read_string(reader), - user_os=self.read_string(reader), - user_os_version=self.read_string(reader), - user_browser=self.read_string(reader), - user_browser_version=self.read_string(reader), - user_device=self.read_string(reader), - user_device_type=self.read_string(reader), - user_device_memory_size=self.read_uint(reader), - user_device_heap_size=self.read_uint(reader), - user_country=self.read_string(reader) - ) - - if message_id == 2: - return SessionDisconnect( - timestamp=self.read_uint(reader) - ) - - if message_id == 3: - return SessionEnd( - timestamp=self.read_uint(reader) - ) - - if message_id == 4: - return SetPageLocation( - url=self.read_string(reader), - referrer=self.read_string(reader), - navigation_start=self.read_uint(reader) - ) - - if message_id == 5: - return SetViewportSize( - width=self.read_uint(reader), - height=self.read_uint(reader) - ) - - if message_id == 6: - return SetViewportScroll( - x=self.read_int(reader), - y=self.read_int(reader) - ) - - if message_id == 7: - return CreateDocument() - - if message_id == 8: - return CreateElementNode( - id=self.read_uint(reader), - parent_id=self.read_uint(reader), - index=self.read_uint(reader), - tag=self.read_string(reader), - svg=self.read_boolean(reader), - ) - - if message_id == 9: - return CreateTextNode( - id=self.read_uint(reader), - parent_id=self.read_uint(reader), - index=self.read_uint(reader) - ) - - if message_id == 10: - return MoveNode( - id=self.read_uint(reader), - parent_id=self.read_uint(reader), - index=self.read_uint(reader) - ) - - if message_id == 11: - return RemoveNode( - id=self.read_uint(reader) - ) - - if message_id == 12: - return SetNodeAttribute( - id=self.read_uint(reader), - name=self.read_string(reader), - value=self.read_string(reader) - ) - - if message_id == 13: - return RemoveNodeAttribute( - id=self.read_uint(reader), - name=self.read_string(reader) - ) - - if message_id == 14: - return SetNodeData( - id=self.read_uint(reader), - data=self.read_string(reader) - ) - - if message_id == 15: - return SetCSSData( - id=self.read_uint(reader), - data=self.read_string(reader) - ) - - if message_id == 16: - return SetNodeScroll( - id=self.read_uint(reader), - x=self.read_int(reader), - y=self.read_int(reader), - ) - - if message_id == 17: - return SetInputTarget( - id=self.read_uint(reader), - label=self.read_string(reader) - ) - - if message_id == 18: - return SetInputValue( - id=self.read_uint(reader), - value=self.read_string(reader), - mask=self.read_int(reader), - ) - - if message_id == 19: - return SetInputChecked( - id=self.read_uint(reader), - checked=self.read_boolean(reader) - ) - - if message_id == 20: - return MouseMove( - x=self.read_uint(reader), - y=self.read_uint(reader) - ) - - if message_id == 21: - return MouseClick( - id=self.read_uint(reader), - hesitation_time=self.read_uint(reader), - label=self.read_string(reader) - ) - - if message_id == 22: - return ConsoleLog( - level=self.read_string(reader), - value=self.read_string(reader) - ) - - if message_id == 23: - return PageLoadTiming( - request_start=self.read_uint(reader), - response_start=self.read_uint(reader), - response_end=self.read_uint(reader), - dom_content_loaded_event_start=self.read_uint(reader), - dom_content_loaded_event_end=self.read_uint(reader), - load_event_start=self.read_uint(reader), - load_event_end=self.read_uint(reader), - first_paint=self.read_uint(reader), - first_contentful_paint=self.read_uint(reader) - ) - - if message_id == 24: - return PageRenderTiming( - speed_index=self.read_uint(reader), - visually_complete=self.read_uint(reader), - time_to_interactive=self.read_uint(reader), - ) - - if message_id == 25: - return JSException( - name=self.read_string(reader), - message=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 26: - return RawErrorEvent( - timestamp=self.read_uint(reader), - source=self.read_string(reader), - name=self.read_string(reader), - message=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 27: - return RawCustomEvent( - name=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 28: - return UserID( - id=self.read_string(reader) - ) - - if message_id == 29: - return UserAnonymousID( - id=self.read_string(reader) - ) - - if message_id == 30: - return Metadata( - key=self.read_string(reader), - value=self.read_string(reader) - ) - - if message_id == 31: - return PageEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - url=self.read_string(reader), - referrer=self.read_string(reader), - loaded=self.read_boolean(reader), - request_start=self.read_uint(reader), - response_start=self.read_uint(reader), - response_end=self.read_uint(reader), - dom_content_loaded_event_start=self.read_uint(reader), - dom_content_loaded_event_end=self.read_uint(reader), - load_event_start=self.read_uint(reader), - load_event_end=self.read_uint(reader), - first_paint=self.read_uint(reader), - first_contentful_paint=self.read_uint(reader), - speed_index=self.read_uint(reader), - visually_complete=self.read_uint(reader), - time_to_interactive=self.read_uint(reader) - ) - - if message_id == 32: - return InputEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - value=self.read_string(reader), - value_masked=self.read_boolean(reader), - label=self.read_string(reader), - ) - - if message_id == 33: - return ClickEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - hesitation_time=self.read_uint(reader), - label=self.read_string(reader) - ) - - if message_id == 34: - return ErrorEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - source=self.read_string(reader), - name=self.read_string(reader), - message=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 35: - - message_id = self.read_uint(reader) - ts = self.read_uint(reader) - if ts > 9999999999999: - ts = None - return ResourceEvent( - message_id=message_id, - timestamp=ts, - duration=self.read_uint(reader), - ttfb=self.read_uint(reader), - header_size=self.read_uint(reader), - encoded_body_size=self.read_uint(reader), - decoded_body_size=self.read_uint(reader), - url=self.read_string(reader), - type=self.read_string(reader), - success=self.read_boolean(reader), - method=self.read_string(reader), - status=self.read_uint(reader) - ) - - if message_id == 36: - return CustomEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - name=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 37: - return CSSInsertRule( - id=self.read_uint(reader), - rule=self.read_string(reader), - index=self.read_uint(reader) - ) - - if message_id == 38: - return CSSDeleteRule( - id=self.read_uint(reader), - index=self.read_uint(reader) - ) - - if message_id == 39: - return Fetch( - method=self.read_string(reader), - url=self.read_string(reader), - request=self.read_string(reader), - response=self.read_string(reader), - status=self.read_uint(reader), - timestamp=self.read_uint(reader), - duration=self.read_uint(reader) - ) - - if message_id == 40: - return Profiler( - name=self.read_string(reader), - duration=self.read_uint(reader), - args=self.read_string(reader), - result=self.read_string(reader) - ) - - if message_id == 41: - return OTable( - key=self.read_string(reader), - value=self.read_string(reader) - ) - - if message_id == 42: - return StateAction( - type=self.read_string(reader) - ) - - if message_id == 43: - return StateActionEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - type=self.read_string(reader) - ) - - if message_id == 44: - return Redux( - action=self.read_string(reader), - state=self.read_string(reader), - duration=self.read_uint(reader) - ) - - if message_id == 45: - return Vuex( - mutation=self.read_string(reader), - state=self.read_string(reader), - ) - - if message_id == 46: - return MobX( - type=self.read_string(reader), - payload=self.read_string(reader), - ) - - if message_id == 47: - return NgRx( - action=self.read_string(reader), - state=self.read_string(reader), - duration=self.read_uint(reader) - ) - - if message_id == 48: - return GraphQL( - operation_kind=self.read_string(reader), - operation_name=self.read_string(reader), - variables=self.read_string(reader), - response=self.read_string(reader) - ) - - if message_id == 49: - return PerformanceTrack( - frames=self.read_int(reader), - ticks=self.read_int(reader), - total_js_heap_size=self.read_uint(reader), - used_js_heap_size=self.read_uint(reader) - ) - - if message_id == 50: - return GraphQLEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - name=self.read_string(reader) - ) - - if message_id == 52: - return DomDrop( - timestamp=self.read_uint(reader) - ) - - if message_id == 53: - return ResourceTiming( - timestamp=self.read_uint(reader), - duration=self.read_uint(reader), - ttfb=self.read_uint(reader), - header_size=self.read_uint(reader), - encoded_body_size=self.read_uint(reader), - decoded_body_size=self.read_uint(reader), - url=self.read_string(reader), - initiator=self.read_string(reader) - ) - - if message_id == 54: - return ConnectionInformation( - downlink=self.read_uint(reader), - type=self.read_string(reader) - ) - - if message_id == 55: - return SetPageVisibility( - hidden=self.read_boolean(reader) - ) - - if message_id == 56: - return PerformanceTrackAggr( - timestamp_start=self.read_uint(reader), - timestamp_end=self.read_uint(reader), - min_fps=self.read_uint(reader), - avg_fps=self.read_uint(reader), - max_fps=self.read_uint(reader), - min_cpu=self.read_uint(reader), - avg_cpu=self.read_uint(reader), - max_cpu=self.read_uint(reader), - min_total_js_heap_size=self.read_uint(reader), - avg_total_js_heap_size=self.read_uint(reader), - max_total_js_heap_size=self.read_uint(reader), - min_used_js_heap_size=self.read_uint(reader), - avg_used_js_heap_size=self.read_uint(reader), - max_used_js_heap_size=self.read_uint(reader) - ) - - if message_id == 59: - return LongTask( - timestamp=self.read_uint(reader), - duration=self.read_uint(reader), - context=self.read_uint(reader), - container_type=self.read_uint(reader), - container_src=self.read_string(reader), - container_id=self.read_string(reader), - container_name=self.read_string(reader) - ) - - if message_id == 60: - return SetNodeURLBasedAttribute( - id=self.read_uint(reader), - name=self.read_string(reader), - value=self.read_string(reader), - base_url=self.read_string(reader) - ) - - if message_id == 61: - return SetStyleData( - id=self.read_uint(reader), - data=self.read_string(reader), - base_url=self.read_string(reader) - ) - - if message_id == 62: - return IssueEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - type=self.read_string(reader), - context_string=self.read_string(reader), - context=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 63: - return TechnicalInfo( - type=self.read_string(reader), - value=self.read_string(reader) - ) - - if message_id == 64: - return CustomIssue( - name=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 65: - return PageClose() - - if message_id == 90: - return IOSSessionStart( - timestamp=self.read_uint(reader), - project_id=self.read_uint(reader), - tracker_version=self.read_string(reader), - rev_id=self.read_string(reader), - user_uuid=self.read_string(reader), - user_os=self.read_string(reader), - user_os_version=self.read_string(reader), - user_device=self.read_string(reader), - user_device_type=self.read_string(reader), - user_country=self.read_string(reader) - ) - - if message_id == 91: - return IOSSessionEnd( - timestamp=self.read_uint(reader) - ) - - if message_id == 92: - return IOSMetadata( - timestamp=self.read_uint(reader), - length=self.read_uint(reader), - key=self.read_string(reader), - value=self.read_string(reader) - ) - - if message_id == 94: - return IOSUserID( - timestamp=self.read_uint(reader), - length=self.read_uint(reader), - value=self.read_string(reader) - ) - - if message_id == 95: - return IOSUserAnonymousID( - timestamp=self.read_uint(reader), - length=self.read_uint(reader), - value=self.read_string(reader) - ) - - if message_id == 99: - return IOSScreenLeave( - timestamp=self.read_uint(reader), - length=self.read_uint(reader), - title=self.read_string(reader), - view_name=self.read_string(reader) - ) - - if message_id == 103: - return IOSLog( - timestamp=self.read_uint(reader), - length=self.read_uint(reader), - severity=self.read_string(reader), - content=self.read_string(reader) - ) - - if message_id == 104: - return IOSInternalError( - timestamp=self.read_uint(reader), - length=self.read_uint(reader), - content=self.read_string(reader) - ) - - if message_id == 110: - return IOSPerformanceAggregated( - timestamp_start=self.read_uint(reader), - timestamp_end=self.read_uint(reader), - min_fps=self.read_uint(reader), - avg_fps=self.read_uint(reader), - max_fps=self.read_uint(reader), - min_cpu=self.read_uint(reader), - avg_cpu=self.read_uint(reader), - max_cpu=self.read_uint(reader), - min_memory=self.read_uint(reader), - avg_memory=self.read_uint(reader), - max_memory=self.read_uint(reader), - min_battery=self.read_uint(reader), - avg_battery=self.read_uint(reader), - max_battery=self.read_uint(reader) - ) - - def read_message_id(self, reader: io.BytesIO) -> int: - """ - Read and return the first byte where the message id is encoded - """ - id_ = self.read_uint(reader) - return id_ - - @staticmethod - def check_message_id(b: bytes) -> int: - """ - todo: make it static and without reader. It's just the first byte - Read and return the first byte where the message id is encoded - """ - reader = io.BytesIO(b) - id_ = Codec.read_uint(reader) - - return id_ - - @staticmethod - def decode_key(b) -> int: - """ - Decode the message key (encoded with little endian) - """ - try: - decoded = int.from_bytes(b, "little", signed=False) - except Exception as e: - raise UnicodeDecodeError(f"Error while decoding message key (SessionID) from {b}\n{e}") - return decoded diff --git a/ee/connectors/msgcodec/messages.py b/ee/connectors/msgcodec/messages.py index c6e53b445..b500424b7 100644 --- a/ee/connectors/msgcodec/messages.py +++ b/ee/connectors/msgcodec/messages.py @@ -1,13 +1,20 @@ -""" -Representations of Kafka messages -""" -from abc import ABC +# Auto-generated, do not edit +from abc import ABC class Message(ABC): pass +class BatchMeta(Message): + __id__ = 80 + + def __init__(self, page_no, first_index, timestamp): + self.page_no = page_no + self.first_index = first_index + self.timestamp = timestamp + + class Timestamp(Message): __id__ = 0 @@ -18,10 +25,7 @@ class Timestamp(Message): class SessionStart(Message): __id__ = 1 - def __init__(self, timestamp, project_id, tracker_version, rev_id, user_uuid, - user_agent, user_os, user_os_version, user_browser, user_browser_version, - user_device, user_device_type, user_device_memory_size, user_device_heap_size, - user_country): + def __init__(self, timestamp, project_id, tracker_version, rev_id, user_uuid, user_agent, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_device_memory_size, user_device_heap_size, user_country, user_id): self.timestamp = timestamp self.project_id = project_id self.tracker_version = tracker_version @@ -37,6 +41,7 @@ class SessionStart(Message): self.user_device_memory_size = user_device_memory_size self.user_device_heap_size = user_device_heap_size self.user_country = user_country + self.user_id = user_id class SessionDisconnect(Message): @@ -48,7 +53,6 @@ class SessionDisconnect(Message): class SessionEnd(Message): __id__ = 3 - __name__ = 'SessionEnd' def __init__(self, timestamp): self.timestamp = timestamp @@ -82,13 +86,16 @@ class SetViewportScroll(Message): class CreateDocument(Message): __id__ = 7 + def __init__(self, ): + + class CreateElementNode(Message): __id__ = 8 def __init__(self, id, parent_id, index, tag, svg): self.id = id - self.parent_id = parent_id, + self.parent_id = parent_id self.index = index self.tag = tag self.svg = svg @@ -122,7 +129,7 @@ class RemoveNode(Message): class SetNodeAttribute(Message): __id__ = 12 - def __init__(self, id, name: str, value: str): + def __init__(self, id, name, value): self.id = id self.name = name self.value = value @@ -131,7 +138,7 @@ class SetNodeAttribute(Message): class RemoveNodeAttribute(Message): __id__ = 13 - def __init__(self, id, name: str): + def __init__(self, id, name): self.id = id self.name = name @@ -139,7 +146,7 @@ class RemoveNodeAttribute(Message): class SetNodeData(Message): __id__ = 14 - def __init__(self, id, data: str): + def __init__(self, id, data): self.id = id self.data = data @@ -147,7 +154,7 @@ class SetNodeData(Message): class SetCSSData(Message): __id__ = 15 - def __init__(self, id, data: str): + def __init__(self, id, data): self.id = id self.data = data @@ -155,7 +162,7 @@ class SetCSSData(Message): class SetNodeScroll(Message): __id__ = 16 - def __init__(self, id, x: int, y: int): + def __init__(self, id, x, y): self.id = id self.x = x self.y = y @@ -164,7 +171,7 @@ class SetNodeScroll(Message): class SetInputTarget(Message): __id__ = 17 - def __init__(self, id, label: str): + def __init__(self, id, label): self.id = id self.label = label @@ -172,7 +179,7 @@ class SetInputTarget(Message): class SetInputValue(Message): __id__ = 18 - def __init__(self, id, value: str, mask: int): + def __init__(self, id, value, mask): self.id = id self.value = value self.mask = mask @@ -181,7 +188,7 @@ class SetInputValue(Message): class SetInputChecked(Message): __id__ = 19 - def __init__(self, id, checked: bool): + def __init__(self, id, checked): self.id = id self.checked = checked @@ -194,10 +201,10 @@ class MouseMove(Message): self.y = y -class MouseClick(Message): +class MouseClickDepricated(Message): __id__ = 21 - def __init__(self, id, hesitation_time, label: str): + def __init__(self, id, hesitation_time, label): self.id = id self.hesitation_time = hesitation_time self.label = label @@ -206,7 +213,7 @@ class MouseClick(Message): class ConsoleLog(Message): __id__ = 22 - def __init__(self, level: str, value: str): + def __init__(self, level, value): self.level = level self.value = value @@ -214,9 +221,7 @@ class ConsoleLog(Message): class PageLoadTiming(Message): __id__ = 23 - def __init__(self, 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): + def __init__(self, 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): self.request_start = request_start self.response_start = response_start self.response_end = response_end @@ -236,20 +241,20 @@ class PageRenderTiming(Message): self.visually_complete = visually_complete self.time_to_interactive = time_to_interactive + class JSException(Message): __id__ = 25 - def __init__(self, name: str, message: str, payload: str): + def __init__(self, name, message, payload): self.name = name self.message = message self.payload = payload -class RawErrorEvent(Message): +class IntegrationEvent(Message): __id__ = 26 - def __init__(self, timestamp, source: str, name: str, message: str, - payload: str): + def __init__(self, timestamp, source, name, message, payload): self.timestamp = timestamp self.source = source self.name = name @@ -260,7 +265,7 @@ class RawErrorEvent(Message): class RawCustomEvent(Message): __id__ = 27 - def __init__(self, name: str, payload: str): + def __init__(self, name, payload): self.name = name self.payload = payload @@ -268,44 +273,29 @@ class RawCustomEvent(Message): class UserID(Message): __id__ = 28 - def __init__(self, id: str): + def __init__(self, id): self.id = id class UserAnonymousID(Message): __id__ = 29 - def __init__(self, id: str): + def __init__(self, id): self.id = id class Metadata(Message): __id__ = 30 - def __init__(self, key: str, value: str): + def __init__(self, key, value): self.key = key self.value = value -class PerformanceTrack(Message): - __id__ = 49 - - def __init__(self, frames: int, ticks: int, total_js_heap_size, - used_js_heap_size): - self.frames = frames - self.ticks = ticks - self.total_js_heap_size = total_js_heap_size - self.used_js_heap_size = used_js_heap_size - - class PageEvent(Message): __id__ = 31 - def __init__(self, message_id, timestamp, url: str, referrer: str, - loaded: bool, 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, - speed_index, visually_complete, time_to_interactive): + def __init__(self, message_id, timestamp, url, referrer, loaded, 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, speed_index, visually_complete, time_to_interactive): self.message_id = message_id self.timestamp = timestamp self.url = url @@ -328,7 +318,7 @@ class PageEvent(Message): class InputEvent(Message): __id__ = 32 - def __init__(self, message_id, timestamp, value: str, value_masked: bool, label: str): + def __init__(self, message_id, timestamp, value, value_masked, label): self.message_id = message_id self.timestamp = timestamp self.value = value @@ -339,18 +329,18 @@ class InputEvent(Message): class ClickEvent(Message): __id__ = 33 - def __init__(self, message_id, timestamp, hesitation_time, label: str): + def __init__(self, message_id, timestamp, hesitation_time, label, selector): self.message_id = message_id self.timestamp = timestamp self.hesitation_time = hesitation_time self.label = label + self.selector = selector class ErrorEvent(Message): __id__ = 34 - def __init__(self, message_id, timestamp, source: str, name: str, message: str, - payload: str): + def __init__(self, message_id, timestamp, source, name, message, payload): self.message_id = message_id self.timestamp = timestamp self.source = source @@ -362,8 +352,7 @@ class ErrorEvent(Message): class ResourceEvent(Message): __id__ = 35 - def __init__(self, message_id, timestamp, duration, ttfb, header_size, encoded_body_size, - decoded_body_size, url: str, type: str, success: bool, method: str, status): + def __init__(self, message_id, timestamp, duration, ttfb, header_size, encoded_body_size, decoded_body_size, url, type, success, method, status): self.message_id = message_id self.timestamp = timestamp self.duration = duration @@ -381,7 +370,7 @@ class ResourceEvent(Message): class CustomEvent(Message): __id__ = 36 - def __init__(self, message_id, timestamp, name: str, payload: str): + def __init__(self, message_id, timestamp, name, payload): self.message_id = message_id self.timestamp = timestamp self.name = name @@ -391,7 +380,7 @@ class CustomEvent(Message): class CSSInsertRule(Message): __id__ = 37 - def __init__(self, id, rule: str, index): + def __init__(self, id, rule, index): self.id = id self.rule = rule self.index = index @@ -408,8 +397,7 @@ class CSSDeleteRule(Message): class Fetch(Message): __id__ = 39 - def __init__(self, method: str, url: str, request: str, response: str, status, - timestamp, duration): + def __init__(self, method, url, request, response, status, timestamp, duration): self.method = method self.url = url self.request = request @@ -422,7 +410,7 @@ class Fetch(Message): class Profiler(Message): __id__ = 40 - def __init__(self, name: str, duration, args: str, result: str): + def __init__(self, name, duration, args, result): self.name = name self.duration = duration self.args = args @@ -432,7 +420,7 @@ class Profiler(Message): class OTable(Message): __id__ = 41 - def __init__(self, key: str, value: str): + def __init__(self, key, value): self.key = key self.value = value @@ -440,14 +428,14 @@ class OTable(Message): class StateAction(Message): __id__ = 42 - def __init__(self, type: str): + def __init__(self, type): self.type = type class StateActionEvent(Message): __id__ = 43 - def __init__(self, message_id, timestamp, type: str): + def __init__(self, message_id, timestamp, type): self.message_id = message_id self.timestamp = timestamp self.type = type @@ -456,7 +444,7 @@ class StateActionEvent(Message): class Redux(Message): __id__ = 44 - def __init__(self, action: str, state: str, duration): + def __init__(self, action, state, duration): self.action = action self.state = state self.duration = duration @@ -465,7 +453,7 @@ class Redux(Message): class Vuex(Message): __id__ = 45 - def __init__(self, mutation: str, state: str): + def __init__(self, mutation, state): self.mutation = mutation self.state = state @@ -473,7 +461,7 @@ class Vuex(Message): class MobX(Message): __id__ = 46 - def __init__(self, type: str, payload: str): + def __init__(self, type, payload): self.type = type self.payload = payload @@ -481,7 +469,7 @@ class MobX(Message): class NgRx(Message): __id__ = 47 - def __init__(self, action: str, state: str, duration): + def __init__(self, action, state, duration): self.action = action self.state = state self.duration = duration @@ -490,8 +478,7 @@ class NgRx(Message): class GraphQL(Message): __id__ = 48 - def __init__(self, operation_kind: str, operation_name: str, - variables: str, response: str): + def __init__(self, operation_kind, operation_name, variables, response): self.operation_kind = operation_kind self.operation_name = operation_name self.variables = variables @@ -501,8 +488,7 @@ class GraphQL(Message): class PerformanceTrack(Message): __id__ = 49 - def __init__(self, frames: int, ticks: int, - total_js_heap_size, used_js_heap_size): + def __init__(self, frames, ticks, total_js_heap_size, used_js_heap_size): self.frames = frames self.ticks = ticks self.total_js_heap_size = total_js_heap_size @@ -512,13 +498,30 @@ class PerformanceTrack(Message): class GraphQLEvent(Message): __id__ = 50 - def __init__(self, message_id, timestamp, name: str): + def __init__(self, message_id, timestamp, operation_kind, operation_name, variables, response): self.message_id = message_id self.timestamp = timestamp - self.name = name + self.operation_kind = operation_kind + self.operation_name = operation_name + self.variables = variables + self.response = response -class DomDrop(Message): +class FetchEvent(Message): + __id__ = 51 + + def __init__(self, message_id, timestamp, method, url, request, response, status, duration): + self.message_id = message_id + self.timestamp = timestamp + self.method = method + self.url = url + self.request = request + self.response = response + self.status = status + self.duration = duration + + +class DOMDrop(Message): __id__ = 52 def __init__(self, timestamp): @@ -528,8 +531,7 @@ class DomDrop(Message): class ResourceTiming(Message): __id__ = 53 - def __init__(self, timestamp, duration, ttfb, header_size, encoded_body_size, - decoded_body_size, url, initiator): + def __init__(self, timestamp, duration, ttfb, header_size, encoded_body_size, decoded_body_size, url, initiator): self.timestamp = timestamp self.duration = duration self.ttfb = ttfb @@ -543,7 +545,7 @@ class ResourceTiming(Message): class ConnectionInformation(Message): __id__ = 54 - def __init__(self, downlink, type: str): + def __init__(self, downlink, type): self.downlink = downlink self.type = type @@ -551,19 +553,14 @@ class ConnectionInformation(Message): class SetPageVisibility(Message): __id__ = 55 - def __init__(self, hidden: bool): + def __init__(self, hidden): self.hidden = hidden class PerformanceTrackAggr(Message): __id__ = 56 - def __init__(self, timestamp_start, timestamp_end, 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 - ): + def __init__(self, timestamp_start, timestamp_end, 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): self.timestamp_start = timestamp_start self.timestamp_end = timestamp_end self.min_fps = min_fps @@ -583,8 +580,7 @@ class PerformanceTrackAggr(Message): class LongTask(Message): __id__ = 59 - def __init__(self, timestamp, duration, context, container_type, container_src: str, - container_id: str, container_name: str): + def __init__(self, timestamp, duration, context, container_type, container_src, container_id, container_name): self.timestamp = timestamp self.duration = duration self.context = context @@ -594,20 +590,20 @@ class LongTask(Message): self.container_name = container_name -class SetNodeURLBasedAttribute(Message): +class SetNodeAttributeURLBased(Message): __id__ = 60 - def __init__(self, id, name: str, value: str, base_url: str): + def __init__(self, id, name, value, base_url): self.id = id self.name = name self.value = value self.base_url = base_url -class SetStyleData(Message): +class SetCSSDataURLBased(Message): __id__ = 61 - def __init__(self, id, data: str, base_url: str): + def __init__(self, id, data, base_url): self.id = id self.data = data self.base_url = base_url @@ -616,8 +612,7 @@ class SetStyleData(Message): class IssueEvent(Message): __id__ = 62 - def __init__(self, message_id, timestamp, type: str, context_string: str, - context: str, payload: str): + def __init__(self, message_id, timestamp, type, context_string, context, payload): self.message_id = message_id self.timestamp = timestamp self.type = type @@ -629,7 +624,7 @@ class IssueEvent(Message): class TechnicalInfo(Message): __id__ = 63 - def __init__(self, type: str, value: str): + def __init__(self, type, value): self.type = type self.value = value @@ -637,7 +632,7 @@ class TechnicalInfo(Message): class CustomIssue(Message): __id__ = 64 - def __init__(self, name: str, payload: str): + def __init__(self, name, payload): self.name = name self.payload = payload @@ -645,13 +640,58 @@ class CustomIssue(Message): class PageClose(Message): __id__ = 65 + def __init__(self, ): + + + +class AssetCache(Message): + __id__ = 66 + + def __init__(self, url): + self.url = url + + +class CSSInsertRuleURLBased(Message): + __id__ = 67 + + def __init__(self, id, rule, index, base_url): + self.id = id + self.rule = rule + self.index = index + self.base_url = base_url + + +class MouseClick(Message): + __id__ = 69 + + def __init__(self, id, hesitation_time, label, selector): + self.id = id + self.hesitation_time = hesitation_time + self.label = label + self.selector = selector + + +class CreateIFrameDocument(Message): + __id__ = 70 + + def __init__(self, frame_id, id): + self.frame_id = frame_id + self.id = id + + +class IOSBatchMeta(Message): + __id__ = 107 + + def __init__(self, timestamp, length, first_index): + self.timestamp = timestamp + self.length = length + self.first_index = first_index + class IOSSessionStart(Message): __id__ = 90 - def __init__(self, timestamp, project_id, tracker_version: str, - rev_id: str, user_uuid: str, user_os: str, user_os_version: str, - user_device: str, user_device_type: str, user_country: str): + def __init__(self, timestamp, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country): self.timestamp = timestamp self.project_id = project_id self.tracker_version = tracker_version @@ -674,17 +714,27 @@ class IOSSessionEnd(Message): class IOSMetadata(Message): __id__ = 92 - def __init__(self, timestamp, length, key: str, value: str): + def __init__(self, timestamp, length, key, value): self.timestamp = timestamp self.length = length self.key = key self.value = value +class IOSCustomEvent(Message): + __id__ = 93 + + def __init__(self, timestamp, length, name, payload): + self.timestamp = timestamp + self.length = length + self.name = name + self.payload = payload + + class IOSUserID(Message): __id__ = 94 - def __init__(self, timestamp, length, value: str): + def __init__(self, timestamp, length, value): self.timestamp = timestamp self.length = length self.value = value @@ -693,26 +743,91 @@ class IOSUserID(Message): class IOSUserAnonymousID(Message): __id__ = 95 - def __init__(self, timestamp, length, value: str): + def __init__(self, timestamp, length, value): self.timestamp = timestamp self.length = length self.value = value -class IOSScreenLeave(Message): - __id__ = 99 +class IOSScreenChanges(Message): + __id__ = 96 - def __init__(self, timestamp, length, title: str, view_name: str): + def __init__(self, timestamp, length, x, y, width, height): + self.timestamp = timestamp + self.length = length + self.x = x + self.y = y + self.width = width + self.height = height + + +class IOSCrash(Message): + __id__ = 97 + + def __init__(self, timestamp, length, name, reason, stacktrace): + self.timestamp = timestamp + self.length = length + self.name = name + self.reason = reason + self.stacktrace = stacktrace + + +class IOSScreenEnter(Message): + __id__ = 98 + + def __init__(self, timestamp, length, title, view_name): self.timestamp = timestamp self.length = length self.title = title self.view_name = view_name +class IOSScreenLeave(Message): + __id__ = 99 + + def __init__(self, timestamp, length, title, view_name): + self.timestamp = timestamp + self.length = length + self.title = title + self.view_name = view_name + + +class IOSClickEvent(Message): + __id__ = 100 + + def __init__(self, timestamp, length, label, x, y): + self.timestamp = timestamp + self.length = length + self.label = label + self.x = x + self.y = y + + +class IOSInputEvent(Message): + __id__ = 101 + + def __init__(self, timestamp, length, value, value_masked, label): + self.timestamp = timestamp + self.length = length + self.value = value + self.value_masked = value_masked + self.label = label + + +class IOSPerformanceEvent(Message): + __id__ = 102 + + def __init__(self, timestamp, length, name, value): + self.timestamp = timestamp + self.length = length + self.name = name + self.value = value + + class IOSLog(Message): __id__ = 103 - def __init__(self, timestamp, length, severity: str, content: str): + def __init__(self, timestamp, length, severity, content): self.timestamp = timestamp self.length = length self.severity = severity @@ -722,20 +837,31 @@ class IOSLog(Message): class IOSInternalError(Message): __id__ = 104 - def __init__(self, timestamp, length, content: str): + def __init__(self, timestamp, length, content): self.timestamp = timestamp self.length = length self.content = content +class IOSNetworkCall(Message): + __id__ = 105 + + def __init__(self, timestamp, length, duration, headers, body, url, success, method, status): + self.timestamp = timestamp + self.length = length + self.duration = duration + self.headers = headers + self.body = body + self.url = url + self.success = success + self.method = method + self.status = status + + class IOSPerformanceAggregated(Message): __id__ = 110 - def __init__(self, timestamp_start, timestamp_end, min_fps, avg_fps, - max_fps, min_cpu, avg_cpu, max_cpu, - min_memory, avg_memory, max_memory, - min_battery, avg_battery, max_battery - ): + def __init__(self, timestamp_start, timestamp_end, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_memory, avg_memory, max_memory, min_battery, avg_battery, max_battery): self.timestamp_start = timestamp_start self.timestamp_end = timestamp_end self.min_fps = min_fps @@ -750,3 +876,16 @@ class IOSPerformanceAggregated(Message): self.min_battery = min_battery self.avg_battery = avg_battery self.max_battery = max_battery + + +class IOSIssueEvent(Message): + __id__ = 111 + + def __init__(self, timestamp, type, context_string, context, payload): + self.timestamp = timestamp + self.type = type + self.context_string = context_string + self.context = context + self.payload = payload + + diff --git a/ee/connectors/msgcodec/msgcodec.py b/ee/connectors/msgcodec/msgcodec.py new file mode 100644 index 000000000..3bd74499c --- /dev/null +++ b/ee/connectors/msgcodec/msgcodec.py @@ -0,0 +1,728 @@ +# Auto-generated, do not edit + +from msgcodec.codec import Codec +from msgcodec.messages import * + +class MessageCodec(Codec): + + def read_message_id(self, reader: io.BytesIO) -> int: + """ + Read and return the first byte where the message id is encoded + """ + id_ = self.read_uint(reader) + return id_ + + def encode(self, m: Message) -> bytes: + ... + + def decode(self, b: bytes) -> Message: + reader = io.BytesIO(b) + message_id = self.read_message_id(reader) + + if message_id == 80: + return BatchMeta( + page_no=self.read_uint(reader), + first_index=self.read_uint(reader), + timestamp=self.read_int(reader) + ) + + if message_id == 0: + return Timestamp( + timestamp=self.read_uint(reader) + ) + + if message_id == 1: + return SessionStart( + timestamp=self.read_uint(reader), + project_id=self.read_uint(reader), + tracker_version=self.read_string(reader), + rev_id=self.read_string(reader), + user_uuid=self.read_string(reader), + user_agent=self.read_string(reader), + user_os=self.read_string(reader), + user_os_version=self.read_string(reader), + user_browser=self.read_string(reader), + user_browser_version=self.read_string(reader), + user_device=self.read_string(reader), + user_device_type=self.read_string(reader), + user_device_memory_size=self.read_uint(reader), + user_device_heap_size=self.read_uint(reader), + user_country=self.read_string(reader), + user_id=self.read_string(reader) + ) + + if message_id == 2: + return SessionDisconnect( + timestamp=self.read_uint(reader) + ) + + if message_id == 3: + return SessionEnd( + timestamp=self.read_uint(reader) + ) + + if message_id == 4: + return SetPageLocation( + url=self.read_string(reader), + referrer=self.read_string(reader), + navigation_start=self.read_uint(reader) + ) + + if message_id == 5: + return SetViewportSize( + width=self.read_uint(reader), + height=self.read_uint(reader) + ) + + if message_id == 6: + return SetViewportScroll( + x=self.read_int(reader), + y=self.read_int(reader) + ) + + if message_id == 7: + return CreateDocument( + + ) + + if message_id == 8: + return CreateElementNode( + id=self.read_uint(reader), + parent_id=self.read_uint(reader), + index=self.read_uint(reader), + tag=self.read_string(reader), + svg=self.read_boolean(reader) + ) + + if message_id == 9: + return CreateTextNode( + id=self.read_uint(reader), + parent_id=self.read_uint(reader), + index=self.read_uint(reader) + ) + + if message_id == 10: + return MoveNode( + id=self.read_uint(reader), + parent_id=self.read_uint(reader), + index=self.read_uint(reader) + ) + + if message_id == 11: + return RemoveNode( + id=self.read_uint(reader) + ) + + if message_id == 12: + return SetNodeAttribute( + id=self.read_uint(reader), + name=self.read_string(reader), + value=self.read_string(reader) + ) + + if message_id == 13: + return RemoveNodeAttribute( + id=self.read_uint(reader), + name=self.read_string(reader) + ) + + if message_id == 14: + return SetNodeData( + id=self.read_uint(reader), + data=self.read_string(reader) + ) + + if message_id == 15: + return SetCSSData( + id=self.read_uint(reader), + data=self.read_string(reader) + ) + + if message_id == 16: + return SetNodeScroll( + id=self.read_uint(reader), + x=self.read_int(reader), + y=self.read_int(reader) + ) + + if message_id == 17: + return SetInputTarget( + id=self.read_uint(reader), + label=self.read_string(reader) + ) + + if message_id == 18: + return SetInputValue( + id=self.read_uint(reader), + value=self.read_string(reader), + mask=self.read_int(reader) + ) + + if message_id == 19: + return SetInputChecked( + id=self.read_uint(reader), + checked=self.read_boolean(reader) + ) + + if message_id == 20: + return MouseMove( + x=self.read_uint(reader), + y=self.read_uint(reader) + ) + + if message_id == 21: + return MouseClickDepricated( + id=self.read_uint(reader), + hesitation_time=self.read_uint(reader), + label=self.read_string(reader) + ) + + if message_id == 22: + return ConsoleLog( + level=self.read_string(reader), + value=self.read_string(reader) + ) + + if message_id == 23: + return PageLoadTiming( + request_start=self.read_uint(reader), + response_start=self.read_uint(reader), + response_end=self.read_uint(reader), + dom_content_loaded_event_start=self.read_uint(reader), + dom_content_loaded_event_end=self.read_uint(reader), + load_event_start=self.read_uint(reader), + load_event_end=self.read_uint(reader), + first_paint=self.read_uint(reader), + first_contentful_paint=self.read_uint(reader) + ) + + if message_id == 24: + return PageRenderTiming( + speed_index=self.read_uint(reader), + visually_complete=self.read_uint(reader), + time_to_interactive=self.read_uint(reader) + ) + + if message_id == 25: + return JSException( + name=self.read_string(reader), + message=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 26: + return IntegrationEvent( + timestamp=self.read_uint(reader), + source=self.read_string(reader), + name=self.read_string(reader), + message=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 27: + return RawCustomEvent( + name=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 28: + return UserID( + id=self.read_string(reader) + ) + + if message_id == 29: + return UserAnonymousID( + id=self.read_string(reader) + ) + + if message_id == 30: + return Metadata( + key=self.read_string(reader), + value=self.read_string(reader) + ) + + if message_id == 31: + return PageEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + url=self.read_string(reader), + referrer=self.read_string(reader), + loaded=self.read_boolean(reader), + request_start=self.read_uint(reader), + response_start=self.read_uint(reader), + response_end=self.read_uint(reader), + dom_content_loaded_event_start=self.read_uint(reader), + dom_content_loaded_event_end=self.read_uint(reader), + load_event_start=self.read_uint(reader), + load_event_end=self.read_uint(reader), + first_paint=self.read_uint(reader), + first_contentful_paint=self.read_uint(reader), + speed_index=self.read_uint(reader), + visually_complete=self.read_uint(reader), + time_to_interactive=self.read_uint(reader) + ) + + if message_id == 32: + return InputEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + value=self.read_string(reader), + value_masked=self.read_boolean(reader), + label=self.read_string(reader) + ) + + if message_id == 33: + return ClickEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + hesitation_time=self.read_uint(reader), + label=self.read_string(reader), + selector=self.read_string(reader) + ) + + if message_id == 34: + return ErrorEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + source=self.read_string(reader), + name=self.read_string(reader), + message=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 35: + return ResourceEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + duration=self.read_uint(reader), + ttfb=self.read_uint(reader), + header_size=self.read_uint(reader), + encoded_body_size=self.read_uint(reader), + decoded_body_size=self.read_uint(reader), + url=self.read_string(reader), + type=self.read_string(reader), + success=self.read_boolean(reader), + method=self.read_string(reader), + status=self.read_uint(reader) + ) + + if message_id == 36: + return CustomEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + name=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 37: + return CSSInsertRule( + id=self.read_uint(reader), + rule=self.read_string(reader), + index=self.read_uint(reader) + ) + + if message_id == 38: + return CSSDeleteRule( + id=self.read_uint(reader), + index=self.read_uint(reader) + ) + + if message_id == 39: + return Fetch( + method=self.read_string(reader), + url=self.read_string(reader), + request=self.read_string(reader), + response=self.read_string(reader), + status=self.read_uint(reader), + timestamp=self.read_uint(reader), + duration=self.read_uint(reader) + ) + + if message_id == 40: + return Profiler( + name=self.read_string(reader), + duration=self.read_uint(reader), + args=self.read_string(reader), + result=self.read_string(reader) + ) + + if message_id == 41: + return OTable( + key=self.read_string(reader), + value=self.read_string(reader) + ) + + if message_id == 42: + return StateAction( + type=self.read_string(reader) + ) + + if message_id == 43: + return StateActionEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + type=self.read_string(reader) + ) + + if message_id == 44: + return Redux( + action=self.read_string(reader), + state=self.read_string(reader), + duration=self.read_uint(reader) + ) + + if message_id == 45: + return Vuex( + mutation=self.read_string(reader), + state=self.read_string(reader) + ) + + if message_id == 46: + return MobX( + type=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 47: + return NgRx( + action=self.read_string(reader), + state=self.read_string(reader), + duration=self.read_uint(reader) + ) + + if message_id == 48: + return GraphQL( + operation_kind=self.read_string(reader), + operation_name=self.read_string(reader), + variables=self.read_string(reader), + response=self.read_string(reader) + ) + + if message_id == 49: + return PerformanceTrack( + frames=self.read_int(reader), + ticks=self.read_int(reader), + total_js_heap_size=self.read_uint(reader), + used_js_heap_size=self.read_uint(reader) + ) + + if message_id == 50: + return GraphQLEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + operation_kind=self.read_string(reader), + operation_name=self.read_string(reader), + variables=self.read_string(reader), + response=self.read_string(reader) + ) + + if message_id == 51: + return FetchEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + method=self.read_string(reader), + url=self.read_string(reader), + request=self.read_string(reader), + response=self.read_string(reader), + status=self.read_uint(reader), + duration=self.read_uint(reader) + ) + + if message_id == 52: + return DOMDrop( + timestamp=self.read_uint(reader) + ) + + if message_id == 53: + return ResourceTiming( + timestamp=self.read_uint(reader), + duration=self.read_uint(reader), + ttfb=self.read_uint(reader), + header_size=self.read_uint(reader), + encoded_body_size=self.read_uint(reader), + decoded_body_size=self.read_uint(reader), + url=self.read_string(reader), + initiator=self.read_string(reader) + ) + + if message_id == 54: + return ConnectionInformation( + downlink=self.read_uint(reader), + type=self.read_string(reader) + ) + + if message_id == 55: + return SetPageVisibility( + hidden=self.read_boolean(reader) + ) + + if message_id == 56: + return PerformanceTrackAggr( + timestamp_start=self.read_uint(reader), + timestamp_end=self.read_uint(reader), + min_fps=self.read_uint(reader), + avg_fps=self.read_uint(reader), + max_fps=self.read_uint(reader), + min_cpu=self.read_uint(reader), + avg_cpu=self.read_uint(reader), + max_cpu=self.read_uint(reader), + min_total_js_heap_size=self.read_uint(reader), + avg_total_js_heap_size=self.read_uint(reader), + max_total_js_heap_size=self.read_uint(reader), + min_used_js_heap_size=self.read_uint(reader), + avg_used_js_heap_size=self.read_uint(reader), + max_used_js_heap_size=self.read_uint(reader) + ) + + if message_id == 59: + return LongTask( + timestamp=self.read_uint(reader), + duration=self.read_uint(reader), + context=self.read_uint(reader), + container_type=self.read_uint(reader), + container_src=self.read_string(reader), + container_id=self.read_string(reader), + container_name=self.read_string(reader) + ) + + if message_id == 60: + return SetNodeAttributeURLBased( + id=self.read_uint(reader), + name=self.read_string(reader), + value=self.read_string(reader), + base_url=self.read_string(reader) + ) + + if message_id == 61: + return SetCSSDataURLBased( + id=self.read_uint(reader), + data=self.read_string(reader), + base_url=self.read_string(reader) + ) + + if message_id == 62: + return IssueEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + type=self.read_string(reader), + context_string=self.read_string(reader), + context=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 63: + return TechnicalInfo( + type=self.read_string(reader), + value=self.read_string(reader) + ) + + if message_id == 64: + return CustomIssue( + name=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 65: + return PageClose( + + ) + + if message_id == 66: + return AssetCache( + url=self.read_string(reader) + ) + + if message_id == 67: + return CSSInsertRuleURLBased( + id=self.read_uint(reader), + rule=self.read_string(reader), + index=self.read_uint(reader), + base_url=self.read_string(reader) + ) + + if message_id == 69: + return MouseClick( + id=self.read_uint(reader), + hesitation_time=self.read_uint(reader), + label=self.read_string(reader), + selector=self.read_string(reader) + ) + + if message_id == 70: + return CreateIFrameDocument( + frame_id=self.read_uint(reader), + id=self.read_uint(reader) + ) + + if message_id == 107: + return IOSBatchMeta( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + first_index=self.read_uint(reader) + ) + + if message_id == 90: + return IOSSessionStart( + timestamp=self.read_uint(reader), + project_id=self.read_uint(reader), + tracker_version=self.read_string(reader), + rev_id=self.read_string(reader), + user_uuid=self.read_string(reader), + user_os=self.read_string(reader), + user_os_version=self.read_string(reader), + user_device=self.read_string(reader), + user_device_type=self.read_string(reader), + user_country=self.read_string(reader) + ) + + if message_id == 91: + return IOSSessionEnd( + timestamp=self.read_uint(reader) + ) + + if message_id == 92: + return IOSMetadata( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + key=self.read_string(reader), + value=self.read_string(reader) + ) + + if message_id == 93: + return IOSCustomEvent( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + name=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 94: + return IOSUserID( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + value=self.read_string(reader) + ) + + if message_id == 95: + return IOSUserAnonymousID( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + value=self.read_string(reader) + ) + + if message_id == 96: + return IOSScreenChanges( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + x=self.read_uint(reader), + y=self.read_uint(reader), + width=self.read_uint(reader), + height=self.read_uint(reader) + ) + + if message_id == 97: + return IOSCrash( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + name=self.read_string(reader), + reason=self.read_string(reader), + stacktrace=self.read_string(reader) + ) + + if message_id == 98: + return IOSScreenEnter( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + title=self.read_string(reader), + view_name=self.read_string(reader) + ) + + if message_id == 99: + return IOSScreenLeave( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + title=self.read_string(reader), + view_name=self.read_string(reader) + ) + + if message_id == 100: + return IOSClickEvent( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + label=self.read_string(reader), + x=self.read_uint(reader), + y=self.read_uint(reader) + ) + + if message_id == 101: + return IOSInputEvent( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + value=self.read_string(reader), + value_masked=self.read_boolean(reader), + label=self.read_string(reader) + ) + + if message_id == 102: + return IOSPerformanceEvent( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + name=self.read_string(reader), + value=self.read_uint(reader) + ) + + if message_id == 103: + return IOSLog( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + severity=self.read_string(reader), + content=self.read_string(reader) + ) + + if message_id == 104: + return IOSInternalError( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + content=self.read_string(reader) + ) + + if message_id == 105: + return IOSNetworkCall( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + duration=self.read_uint(reader), + headers=self.read_string(reader), + body=self.read_string(reader), + url=self.read_string(reader), + success=self.read_boolean(reader), + method=self.read_string(reader), + status=self.read_uint(reader) + ) + + if message_id == 110: + return IOSPerformanceAggregated( + timestamp_start=self.read_uint(reader), + timestamp_end=self.read_uint(reader), + min_fps=self.read_uint(reader), + avg_fps=self.read_uint(reader), + max_fps=self.read_uint(reader), + min_cpu=self.read_uint(reader), + avg_cpu=self.read_uint(reader), + max_cpu=self.read_uint(reader), + min_memory=self.read_uint(reader), + avg_memory=self.read_uint(reader), + max_memory=self.read_uint(reader), + min_battery=self.read_uint(reader), + avg_battery=self.read_uint(reader), + max_battery=self.read_uint(reader) + ) + + if message_id == 111: + return IOSIssueEvent( + timestamp=self.read_uint(reader), + type=self.read_string(reader), + context_string=self.read_string(reader), + context=self.read_string(reader), + payload=self.read_string(reader) + ) + diff --git a/ee/scripts/helm/db/init_dbs/postgresql/1.7.0/1.7.0.sql b/ee/scripts/helm/db/init_dbs/postgresql/1.7.0/1.7.0.sql index 3f74ad21d..1ea5c6ab6 100644 --- a/ee/scripts/helm/db/init_dbs/postgresql/1.7.0/1.7.0.sql +++ b/ee/scripts/helm/db/init_dbs/postgresql/1.7.0/1.7.0.sql @@ -56,6 +56,7 @@ ALTER TABLE IF EXISTS events.resources PRIMARY KEY (session_id, message_id, timestamp); COMMIT; +CREATE INDEX CONCURRENTLY IF NOT EXISTS projects_tenant_id_idx ON public.projects (tenant_id); CREATE INDEX CONCURRENTLY IF NOT EXISTS projects_project_id_deleted_at_n_idx ON public.projects (project_id) WHERE deleted_at IS NULL; ALTER TYPE metric_type ADD VALUE IF NOT EXISTS 'funnel'; diff --git a/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql b/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql index 7cac6e10e..2d1c2b95f 100644 --- a/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql +++ b/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql @@ -257,6 +257,7 @@ $$ ); + CREATE INDEX IF NOT EXISTS projects_tenant_id_idx ON public.projects (tenant_id); CREATE INDEX IF NOT EXISTS projects_project_key_idx ON public.projects (project_key); CREATE INDEX IF NOT EXISTS projects_project_id_deleted_at_n_idx ON public.projects (project_id) WHERE deleted_at IS NULL; DROP TRIGGER IF EXISTS on_insert_or_update ON projects; diff --git a/ee/utilities/.gitignore b/ee/utilities/.gitignore index f54e439ba..0eaed6d80 100644 --- a/ee/utilities/.gitignore +++ b/ee/utilities/.gitignore @@ -10,7 +10,7 @@ build.sh servers/peerjs-server.js servers/sourcemaps-handler.js servers/sourcemaps-server.js -/Dockerfile /utils/geoIP.js /utils/HeapSnapshot.js /utils/helper.js +/utils/assistHelper.js diff --git a/ee/utilities/Dockerfile b/ee/utilities/Dockerfile new file mode 100644 index 000000000..2de6197a2 --- /dev/null +++ b/ee/utilities/Dockerfile @@ -0,0 +1,20 @@ +FROM node:18-alpine +LABEL Maintainer="KRAIEM Taha Yassine" +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main +RUN apk add --no-cache tini git libc6-compat && ln -s /lib/libc.musl-x86_64.so.1 /lib/ld-linux-x86-64.so.2 + +ARG envarg +ENV ENTERPRISE_BUILD=${envarg} \ + MAXMINDDB_FILE=/home/openreplay/geoip.mmdb +WORKDIR /work +COPY package.json . +COPY package-lock.json . +RUN npm install +COPY . . + +RUN adduser -u 1001 openreplay -D +USER 1001 +ADD --chown=1001 https://static.openreplay.com/geoip/GeoLite2-Country.mmdb $MAXMINDDB_FILE + +ENTRYPOINT ["/sbin/tini", "--"] +CMD npm start diff --git a/ee/utilities/clean.sh b/ee/utilities/clean.sh index 3e8ec080b..ec1aaeae4 100755 --- a/ee/utilities/clean.sh +++ b/ee/utilities/clean.sh @@ -1,6 +1,7 @@ rm -rf ./utils/geoIP.js rm -rf ./utils/HeapSnapshot.js rm -rf ./utils/helper.js +rm -rf ./utils/assistHelper.js rm -rf servers/peerjs-server.js rm -rf servers/sourcemaps-handler.js diff --git a/ee/utilities/package-lock.json b/ee/utilities/package-lock.json index 19699560a..8aa6c9196 100644 --- a/ee/utilities/package-lock.json +++ b/ee/utilities/package-lock.json @@ -29,68 +29,67 @@ "maxmind": "^4.2.0" } }, - "node_modules/@node-redis/bloom": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@node-redis/bloom/-/bloom-1.0.1.tgz", - "integrity": "sha512-mXEBvEIgF4tUzdIN89LiYsbi6//EdpFA7L8M+DHCvePXg+bfHWi+ct5VI6nHUFQE5+ohm/9wmgihCH3HSkeKsw==", + "node_modules/@redis/bloom": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz", + "integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==", "peerDependencies": { - "@node-redis/client": "^1.0.0" + "@redis/client": "^1.0.0" } }, - "node_modules/@node-redis/client": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@node-redis/client/-/client-1.0.5.tgz", - "integrity": "sha512-ESZ3bd1f+od62h4MaBLKum+klVJfA4wAeLHcVQBkoXa1l0viFesOWnakLQqKg+UyrlJhZmXJWtu0Y9v7iTMrig==", + "node_modules/@redis/client": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.2.0.tgz", + "integrity": "sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==", "dependencies": { "cluster-key-slot": "1.1.0", "generic-pool": "3.8.2", - "redis-parser": "3.0.0", "yallist": "4.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" } }, - "node_modules/@node-redis/graph": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@node-redis/graph/-/graph-1.0.0.tgz", - "integrity": "sha512-mRSo8jEGC0cf+Rm7q8mWMKKKqkn6EAnA9IA2S3JvUv/gaWW/73vil7GLNwion2ihTptAm05I9LkepzfIXUKX5g==", + "node_modules/@redis/graph": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz", + "integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==", "peerDependencies": { - "@node-redis/client": "^1.0.0" + "@redis/client": "^1.0.0" } }, - "node_modules/@node-redis/json": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@node-redis/json/-/json-1.0.2.tgz", - "integrity": "sha512-qVRgn8WfG46QQ08CghSbY4VhHFgaTY71WjpwRBGEuqGPfWwfRcIf3OqSpR7Q/45X+v3xd8mvYjywqh0wqJ8T+g==", + "node_modules/@redis/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz", + "integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==", "peerDependencies": { - "@node-redis/client": "^1.0.0" + "@redis/client": "^1.0.0" } }, - "node_modules/@node-redis/search": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@node-redis/search/-/search-1.0.5.tgz", - "integrity": "sha512-MCOL8iCKq4v+3HgEQv8zGlSkZyXSXtERgrAJ4TSryIG/eLFy84b57KmNNa/V7M1Q2Wd2hgn2nPCGNcQtk1R1OQ==", + "node_modules/@redis/search": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz", + "integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==", "peerDependencies": { - "@node-redis/client": "^1.0.0" + "@redis/client": "^1.0.0" } }, - "node_modules/@node-redis/time-series": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@node-redis/time-series/-/time-series-1.0.2.tgz", - "integrity": "sha512-HGQ8YooJ8Mx7l28tD7XjtB3ImLEjlUxG1wC1PAjxu6hPJqjPshUZxAICzDqDjtIbhDTf48WXXUcx8TQJB1XTKA==", + "node_modules/@redis/time-series": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz", + "integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==", "peerDependencies": { - "@node-redis/client": "^1.0.0" + "@redis/client": "^1.0.0" } }, "node_modules/@socket.io/redis-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-7.1.0.tgz", - "integrity": "sha512-vbsNJKUQgtVHcOqNL2ac8kSemTVNKHRzYPldqQJt0eFKvlAtAviuAMzBP0WmOp5OoRLQMjhVsVvgMzzMsVsK5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-7.2.0.tgz", + "integrity": "sha512-/r6oF6Myz0K9uatB/pfCi0BhKg/KRMh1OokrqcjlNz6aq40WiXdFLRbHJQuwGHq/KvB+D6141K+IynbVxZGvhw==", "dependencies": { "debug": "~4.3.1", "notepack.io": "~2.2.0", - "socket.io-adapter": "~2.3.0", + "socket.io-adapter": "^2.4.0", "uid2": "0.0.3" }, "engines": { @@ -113,9 +112,9 @@ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" }, "node_modules/@types/node": { - "version": "17.0.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.42.tgz", - "integrity": "sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==" + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", + "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==" }, "node_modules/accepts": { "version": "1.3.8", @@ -132,12 +131,12 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "engines": { "node": ">=0.8" } @@ -151,23 +150,26 @@ } }, "node_modules/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/body-parser/node_modules/debug": { @@ -181,7 +183,7 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/bytes": { "version": "3.1.2", @@ -191,6 +193,18 @@ "node": ">= 0.8" } }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -252,9 +266,9 @@ } }, "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "engines": { "node": ">= 0.6" } @@ -262,12 +276,12 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "node_modules/cors": { "version": "2.8.5", @@ -298,27 +312,31 @@ } }, "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "engines": { "node": ">= 0.8" } @@ -351,51 +369,60 @@ "node": ">=10.0.0" } }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { "node": ">= 0.6" } }, "node_modules/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.2", + "body-parser": "1.20.0", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.2", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.7", + "qs": "6.10.3", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -415,27 +442,27 @@ "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "engines": [ "node >=0.6.0" ] }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { @@ -453,7 +480,7 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/forwarded": { "version": "0.2.0", @@ -466,11 +493,16 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { "node": ">= 0.6" } }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "node_modules/generic-pool": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", @@ -479,19 +511,54 @@ "node": ">= 4" } }, - "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "node_modules/get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", "dependencies": { - "depd": "~1.1.2", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/iconv-lite": { @@ -549,7 +616,7 @@ "node_modules/lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" }, "node_modules/map-obj": { "version": "4.3.0", @@ -578,7 +645,7 @@ "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "engines": { "node": ">= 0.6" } @@ -586,12 +653,12 @@ "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "engines": { "node": ">= 0.6" } @@ -661,10 +728,18 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { "ee-first": "1.1.1" }, @@ -683,7 +758,7 @@ "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -698,9 +773,12 @@ } }, "node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" }, @@ -728,12 +806,12 @@ } }, "node_modules/raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dependencies": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -742,35 +820,16 @@ } }, "node_modules/redis": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.0.6.tgz", - "integrity": "sha512-IaPAxgF5dV0jx+A9l6yd6R9/PAChZIoAskDVRzUODeLDNhsMlq7OLLTmu0AwAr0xjrJ1bibW5xdpRwqIQ8Q0Xg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.2.0.tgz", + "integrity": "sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==", "dependencies": { - "@node-redis/bloom": "1.0.1", - "@node-redis/client": "1.0.5", - "@node-redis/graph": "1.0.0", - "@node-redis/json": "1.0.2", - "@node-redis/search": "1.0.5", - "@node-redis/time-series": "1.0.2" - } - }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" + "@redis/bloom": "1.0.2", + "@redis/client": "1.2.0", + "@redis/graph": "1.0.1", + "@redis/json": "1.0.3", + "@redis/search": "1.0.6", + "@redis/time-series": "1.0.3" } }, "node_modules/safe-buffer": { @@ -798,23 +857,23 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" @@ -831,7 +890,7 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/send/node_modules/ms": { "version": "2.1.3", @@ -839,14 +898,14 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" @@ -857,6 +916,19 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/socket.io": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", @@ -874,14 +946,14 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", - "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" }, "node_modules/socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.5.tgz", + "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==", "dependencies": { "@types/component-emitter": "^1.2.10", "component-emitter": "~1.3.0", @@ -891,17 +963,12 @@ "node": ">=10.0.0" } }, - "node_modules/socket.io/node_modules/socket.io-adapter": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", - "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" - }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/tiny-lru": { @@ -964,12 +1031,12 @@ "node_modules/uid2": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", - "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=" + "integrity": "sha512-5gSP1liv10Gjp8cMEnFd6shzkL/D6W1uhXSFNCxDC+YI8+L8wkCYCbJ7n77Ezb4wE/xzMogecE+DtamEe9PZjg==" }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "engines": { "node": ">= 0.8" } @@ -977,19 +1044,19 @@ "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "engines": { "node": ">= 0.4.0" } }, "node_modules/uWebSockets.js": { - "version": "20.6.0", - "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#a58e810e47a23696410f6073c8c905dc38f75da5" + "version": "20.10.0", + "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#806df48c9da86af7b3341f3e443388c7cd15c3de" }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "engines": { "node": ">= 0.8" } @@ -997,7 +1064,7 @@ "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "engines": [ "node >=0.6.0" ], @@ -1045,55 +1112,54 @@ "maxmind": "^4.2.0" } }, - "@node-redis/bloom": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@node-redis/bloom/-/bloom-1.0.1.tgz", - "integrity": "sha512-mXEBvEIgF4tUzdIN89LiYsbi6//EdpFA7L8M+DHCvePXg+bfHWi+ct5VI6nHUFQE5+ohm/9wmgihCH3HSkeKsw==", + "@redis/bloom": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz", + "integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==", "requires": {} }, - "@node-redis/client": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@node-redis/client/-/client-1.0.5.tgz", - "integrity": "sha512-ESZ3bd1f+od62h4MaBLKum+klVJfA4wAeLHcVQBkoXa1l0viFesOWnakLQqKg+UyrlJhZmXJWtu0Y9v7iTMrig==", + "@redis/client": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.2.0.tgz", + "integrity": "sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==", "requires": { "cluster-key-slot": "1.1.0", "generic-pool": "3.8.2", - "redis-parser": "3.0.0", "yallist": "4.0.0" } }, - "@node-redis/graph": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@node-redis/graph/-/graph-1.0.0.tgz", - "integrity": "sha512-mRSo8jEGC0cf+Rm7q8mWMKKKqkn6EAnA9IA2S3JvUv/gaWW/73vil7GLNwion2ihTptAm05I9LkepzfIXUKX5g==", + "@redis/graph": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz", + "integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==", "requires": {} }, - "@node-redis/json": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@node-redis/json/-/json-1.0.2.tgz", - "integrity": "sha512-qVRgn8WfG46QQ08CghSbY4VhHFgaTY71WjpwRBGEuqGPfWwfRcIf3OqSpR7Q/45X+v3xd8mvYjywqh0wqJ8T+g==", + "@redis/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz", + "integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==", "requires": {} }, - "@node-redis/search": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@node-redis/search/-/search-1.0.5.tgz", - "integrity": "sha512-MCOL8iCKq4v+3HgEQv8zGlSkZyXSXtERgrAJ4TSryIG/eLFy84b57KmNNa/V7M1Q2Wd2hgn2nPCGNcQtk1R1OQ==", + "@redis/search": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz", + "integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==", "requires": {} }, - "@node-redis/time-series": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@node-redis/time-series/-/time-series-1.0.2.tgz", - "integrity": "sha512-HGQ8YooJ8Mx7l28tD7XjtB3ImLEjlUxG1wC1PAjxu6hPJqjPshUZxAICzDqDjtIbhDTf48WXXUcx8TQJB1XTKA==", + "@redis/time-series": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz", + "integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==", "requires": {} }, "@socket.io/redis-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-7.1.0.tgz", - "integrity": "sha512-vbsNJKUQgtVHcOqNL2ac8kSemTVNKHRzYPldqQJt0eFKvlAtAviuAMzBP0WmOp5OoRLQMjhVsVvgMzzMsVsK5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-7.2.0.tgz", + "integrity": "sha512-/r6oF6Myz0K9uatB/pfCi0BhKg/KRMh1OokrqcjlNz6aq40WiXdFLRbHJQuwGHq/KvB+D6141K+IynbVxZGvhw==", "requires": { "debug": "~4.3.1", "notepack.io": "~2.2.0", - "socket.io-adapter": "~2.3.0", + "socket.io-adapter": "^2.4.0", "uid2": "0.0.3" } }, @@ -1113,9 +1179,9 @@ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" }, "@types/node": { - "version": "17.0.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.42.tgz", - "integrity": "sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==" + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", + "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==" }, "accepts": { "version": "1.3.8", @@ -1129,12 +1195,12 @@ "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" }, "base64id": { "version": "2.0.0", @@ -1142,20 +1208,22 @@ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" }, "body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { "debug": { @@ -1169,7 +1237,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -1178,6 +1246,15 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -1218,19 +1295,19 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "cors": { "version": "2.8.5", @@ -1250,24 +1327,24 @@ } }, "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, "engine.io": { "version": "6.2.0", @@ -1284,6 +1361,13 @@ "debug": "~4.3.1", "engine.io-parser": "~5.0.3", "ws": "~8.2.3" + }, + "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + } } }, "engine.io-parser": { @@ -1294,45 +1378,46 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.2", + "body-parser": "1.20.0", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.2", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.7", + "qs": "6.10.3", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -1349,26 +1434,26 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "dependencies": { @@ -1383,7 +1468,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -1395,22 +1480,50 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "generic-pool": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==" }, - "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", "requires": { - "depd": "~1.1.2", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" } }, @@ -1460,7 +1573,7 @@ "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" }, "map-obj": { "version": "4.3.0", @@ -1479,17 +1592,17 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "mime": { "version": "1.6.0", @@ -1534,10 +1647,15 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" + }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } @@ -1550,7 +1668,7 @@ "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, "proxy-addr": { "version": "2.0.7", @@ -1562,9 +1680,12 @@ } }, "qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==" + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } }, "quick-lru": { "version": "5.1.1", @@ -1577,40 +1698,27 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "requires": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "redis": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.0.6.tgz", - "integrity": "sha512-IaPAxgF5dV0jx+A9l6yd6R9/PAChZIoAskDVRzUODeLDNhsMlq7OLLTmu0AwAr0xjrJ1bibW5xdpRwqIQ8Q0Xg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.2.0.tgz", + "integrity": "sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==", "requires": { - "@node-redis/bloom": "1.0.1", - "@node-redis/client": "1.0.5", - "@node-redis/graph": "1.0.0", - "@node-redis/json": "1.0.2", - "@node-redis/search": "1.0.5", - "@node-redis/time-series": "1.0.2" - } - }, - "redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" - }, - "redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", - "requires": { - "redis-errors": "^1.0.0" + "@redis/bloom": "1.0.2", + "@redis/client": "1.2.0", + "@redis/graph": "1.0.1", + "@redis/json": "1.0.3", + "@redis/search": "1.0.6", + "@redis/time-series": "1.0.3" } }, "safe-buffer": { @@ -1624,23 +1732,23 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "debug": { @@ -1654,7 +1762,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -1666,14 +1774,14 @@ } }, "serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" } }, "setprototypeof": { @@ -1681,6 +1789,16 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "socket.io": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", @@ -1692,24 +1810,17 @@ "engine.io": "~6.2.0", "socket.io-adapter": "~2.4.0", "socket.io-parser": "~4.0.4" - }, - "dependencies": { - "socket.io-adapter": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", - "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" - } } }, "socket.io-adapter": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", - "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" }, "socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.5.tgz", + "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==", "requires": { "@types/component-emitter": "^1.2.10", "component-emitter": "~1.3.0", @@ -1717,9 +1828,9 @@ } }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "tiny-lru": { "version": "8.0.2", @@ -1753,31 +1864,31 @@ "uid2": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", - "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=" + "integrity": "sha512-5gSP1liv10Gjp8cMEnFd6shzkL/D6W1uhXSFNCxDC+YI8+L8wkCYCbJ7n77Ezb4wE/xzMogecE+DtamEe9PZjg==" }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "uWebSockets.js": { - "version": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#a58e810e47a23696410f6073c8c905dc38f75da5", + "version": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#806df48c9da86af7b3341f3e443388c7cd15c3de", "from": "uWebSockets.js@github:uNetworking/uWebSockets.js#v20.10.0" }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", diff --git a/ee/utilities/utils/helper-ee.js b/ee/utilities/utils/helper-ee.js index dc821b94a..50b414b7a 100644 --- a/ee/utilities/utils/helper-ee.js +++ b/ee/utilities/utils/helper-ee.js @@ -70,7 +70,10 @@ const extractPayloadFromRequest = async function (req, res) { filters.filter.userID = [req.getQuery("userId")]; } if (!filters.query.value) { - let body = await getBodyFromUWSResponse(res); + let body = {}; + if (req.getMethod() !== 'get') { + body = await getBodyFromUWSResponse(res); + } filters = { ...filters, "sort": { diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 198c03c7a..bfa86857d 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -14,5 +14,15 @@ COPY nginx.conf /etc/nginx/conf.d/default.conf # Default step in docker build FROM nginx:alpine LABEL maintainer=Rajesh +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main COPY --from=builder /work/public /var/www/openreplay COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 8080 +RUN chown -R nginx:nginx /var/cache/nginx && \ + chown -R nginx:nginx /var/log/nginx && \ + chown -R nginx:nginx /etc/nginx/conf.d && \ + touch /var/run/nginx.pid && \ + chown -R nginx:nginx /var/run/nginx.pid + +USER nginx diff --git a/frontend/app/Router.js b/frontend/app/Router.js index f5dc4c593..acbdd9cbb 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -31,7 +31,7 @@ const LiveSessionPure = lazy(() => import('Components/Session/LiveSession')); const OnboardingPure = lazy(() => import('Components/Onboarding/Onboarding')); const ClientPure = lazy(() => import('Components/Client/Client')); const AssistPure = lazy(() => import('Components/Assist')); -const BugFinderPure = lazy(() => import('Components/BugFinder/BugFinder')); +const BugFinderPure = lazy(() => import('Components/Overview')); const DashboardPure = lazy(() => import('Components/Dashboard/NewDashboard')); const ErrorsPure = lazy(() => import('Components/Errors/Errors')); const FunnelDetailsPure = lazy(() => import('Components/Funnels/FunnelDetails')); diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index de843e969..ba1a7cf0b 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -1,153 +1,166 @@ -import React, { useState, useEffect } from 'react' -import { Popup, Icon, IconButton } from 'UI' -import { connect } from 'react-redux' -import cn from 'classnames' +import React, { useState, useEffect } from 'react'; +import { Popup, Icon, Button, IconButton } from 'UI'; +import { connect } from 'react-redux'; +import cn from 'classnames'; import { toggleChatWindow } from 'Duck/sessions'; import { connectPlayer } from 'Player/store'; import ChatWindow from '../../ChatWindow'; -import { callPeer, requestReleaseRemoteControl, toggleAnnotation } from 'Player' +import { callPeer, requestReleaseRemoteControl, toggleAnnotation } from 'Player'; import { CallingState, ConnectionStatus, RemoteControlStatus } from 'Player/MessageDistributor/managers/AssistManager'; import RequestLocalStream from 'Player/MessageDistributor/managers/LocalStream'; import type { LocalStream } from 'Player/MessageDistributor/managers/LocalStream'; import { toast } from 'react-toastify'; import { confirm } from 'UI'; -import stl from './AassistActions.module.css' +import stl from './AassistActions.module.css'; function onClose(stream) { - stream.getTracks().forEach(t=>t.stop()); + stream.getTracks().forEach((t) => t.stop()); } function onReject() { - toast.info(`Call was rejected.`); + toast.info(`Call was rejected.`); } function onError(e) { - toast.error(typeof e === 'string' ? e : e.message); + toast.error(typeof e === 'string' ? e : e.message); } - interface Props { - userId: String, - toggleChatWindow: (state) => void, - calling: CallingState, - annotating: boolean, - peerConnectionStatus: ConnectionStatus, - remoteControlStatus: RemoteControlStatus, - hasPermission: boolean, - isEnterprise: boolean, + userId: String; + toggleChatWindow: (state) => void; + calling: CallingState; + annotating: boolean; + peerConnectionStatus: ConnectionStatus; + remoteControlStatus: RemoteControlStatus; + hasPermission: boolean; + isEnterprise: boolean; } -function AssistActions({ toggleChatWindow, userId, calling, annotating, peerConnectionStatus, remoteControlStatus, hasPermission, isEnterprise }: Props) { - const [ incomeStream, setIncomeStream ] = useState(null); - const [ localStream, setLocalStream ] = useState(null); - const [ callObject, setCallObject ] = useState<{ end: ()=>void } | null >(null); +function AssistActions({ + toggleChatWindow, + userId, + calling, + annotating, + peerConnectionStatus, + remoteControlStatus, + hasPermission, + isEnterprise, +}: Props) { + const [incomeStream, setIncomeStream] = useState(null); + const [localStream, setLocalStream] = useState(null); + const [callObject, setCallObject] = useState<{ end: () => void } | null>(null); - useEffect(() => { - return callObject?.end() - }, []) + useEffect(() => { + return callObject?.end(); + }, []); - useEffect(() => { - if (peerConnectionStatus == ConnectionStatus.Disconnected) { - toast.info(`Live session was closed.`); - } - }, [peerConnectionStatus]) - - function call() { - RequestLocalStream().then(lStream => { - setLocalStream(lStream); - setCallObject(callPeer( - lStream, - setIncomeStream, - lStream.stop.bind(lStream), - onReject, - onError - )); - }).catch(onError) - } - - const confirmCall = async () => { - if (await confirm({ - header: 'Start Call', - confirmButton: 'Call', - confirmation: `Are you sure you want to call ${userId ? userId : 'User'}?` - })) { - call() - } - } - - const onCall = calling === CallingState.OnCall || calling === CallingState.Reconnecting - const cannotCall = (peerConnectionStatus !== ConnectionStatus.Connected) || (isEnterprise && !hasPermission) - const remoteActive = remoteControlStatus === RemoteControlStatus.Enabled - - return ( -
- {(onCall || remoteActive) && ( - <> -
toggleAnnotation(!annotating) } - role="button" - > - -
-
- - )} -
{ + if (peerConnectionStatus == ConnectionStatus.Disconnected) { + toast.info(`Live session was closed.`); } - onClick={ requestReleaseRemoteControl } - role="button" - > - -
-
- - -
- -
-
+ }, [peerConnectionStatus]); -
- { onCall && callObject && } -
-
- ) + function call() { + RequestLocalStream() + .then((lStream) => { + setLocalStream(lStream); + setCallObject(callPeer(lStream, setIncomeStream, lStream.stop.bind(lStream), onReject, onError)); + }) + .catch(onError); + } + + const confirmCall = async () => { + if ( + await confirm({ + header: 'Start Call', + confirmButton: 'Call', + confirmation: `Are you sure you want to call ${userId ? userId : 'User'}?`, + }) + ) { + call(); + } + }; + + const onCall = calling === CallingState.OnCall || calling === CallingState.Reconnecting; + const cannotCall = peerConnectionStatus !== ConnectionStatus.Connected || (isEnterprise && !hasPermission); + const remoteActive = remoteControlStatus === RemoteControlStatus.Enabled; + + return ( +
+ {(onCall || remoteActive) && ( + <> +
toggleAnnotation(!annotating)} + role="button" + > + + {/* */} +
+
+ + )} +
+ + {/* */} +
+
+ + +
+ + {/* */} +
+
+ +
+ {onCall && callObject && ( + + )} +
+
+ ); } -const con = connect(state => { - const permissions = state.getIn([ 'user', 'account', 'permissions' ]) || [] - return { - hasPermission: permissions.includes('ASSIST_CALL'), - isEnterprise: state.getIn([ 'user', 'account', 'edition' ]) === 'ee', - } -}, { toggleChatWindow }) +const con = connect( + (state) => { + const permissions = state.getIn(['user', 'account', 'permissions']) || []; + return { + hasPermission: permissions.includes('ASSIST_CALL'), + isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee', + }; + }, + { toggleChatWindow } +); -export default con(connectPlayer(state => ({ - calling: state.calling, - annotating: state.annotating, - remoteControlStatus: state.remoteControl, - peerConnectionStatus: state.peerConnectionStatus, -}))(AssistActions)) +export default con( + connectPlayer((state) => ({ + calling: state.calling, + annotating: state.annotating, + remoteControlStatus: state.remoteControl, + peerConnectionStatus: state.peerConnectionStatus, + }))(AssistActions) +); diff --git a/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx b/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx index 9d965d821..93a73c236 100644 --- a/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx +++ b/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx @@ -1,3 +1,4 @@ +import { useModal } from 'App/components/Modal'; import React, { useEffect, useState } from 'react'; import { SlideModal, Avatar, TextEllipsis, Icon } from 'UI'; import SessionList from '../SessionList'; @@ -10,6 +11,7 @@ interface Props { const AssistTabs = (props: Props) => { const [showMenu, setShowMenu] = useState(false) + const { showModal } = useModal(); return (
@@ -18,19 +20,19 @@ const AssistTabs = (props: Props) => { <>
setShowMenu(!showMenu)} + onClick={() => showModal(, {})} > Active Sessions
)}
- {props.userId}'s Live Sessions
} isDisplayed={ showMenu } content={ showMenu && } onClose={ () => setShowMenu(false) } - /> + /> */}
); }; diff --git a/frontend/app/components/Assist/components/SessionList/SessionList.tsx b/frontend/app/components/Assist/components/SessionList/SessionList.tsx index 79aa4af28..ed0af5625 100644 --- a/frontend/app/components/Assist/components/SessionList/SessionList.tsx +++ b/frontend/app/components/Assist/components/SessionList/SessionList.tsx @@ -3,14 +3,17 @@ import { connect } from 'react-redux'; import { fetchLiveList } from 'Duck/sessions'; import { Loader, NoContent, Label } from 'UI'; import SessionItem from 'Shared/SessionItem'; +import { useModal } from 'App/components/Modal'; interface Props { loading: boolean; list: any; session: any; + userId: any; fetchLiveList: (params: any) => void; } function SessionList(props: Props) { + const { hideModal } = useModal(); useEffect(() => { const params: any = {}; if (props.session.userId) { @@ -20,25 +23,34 @@ function SessionList(props: Props) { }, []); return ( - - +
+
- {props.list.map((session: any) => ( -
- {session.pageTitle && session.pageTitle !== '' && ( -
- - {session.pageTitle} -
- )} - -
- ))} +
+ {props.userId}'s Live Sessions{' '} +
- - + + +
+ {props.list.map((session: any) => ( +
+ {session.pageTitle && session.pageTitle !== '' && ( +
+ + {session.pageTitle} +
+ )} + hideModal()} key={session.sessionId} session={session} /> +
+ ))} +
+
+
+
+
); } diff --git a/frontend/app/components/Client/Roles/components/RoleForm/RoleForm.tsx b/frontend/app/components/Client/Roles/components/RoleForm/RoleForm.tsx index 578cc75ad..7aed70131 100644 --- a/frontend/app/components/Client/Roles/components/RoleForm/RoleForm.tsx +++ b/frontend/app/components/Client/Roles/components/RoleForm/RoleForm.tsx @@ -49,7 +49,6 @@ const RoleForm = (props: Props) => { } const writeOption = ({ name, value }: any) => { - console.log('name', name); if (name === 'permissions') { onChangePermissions(value) } else if (name === 'projects') { diff --git a/frontend/app/components/Client/Sites/AddProjectButton/AddUserButton.tsx b/frontend/app/components/Client/Sites/AddProjectButton/AddUserButton.tsx index 078122bb2..7371056fd 100644 --- a/frontend/app/components/Client/Sites/AddProjectButton/AddUserButton.tsx +++ b/frontend/app/components/Client/Sites/AddProjectButton/AddUserButton.tsx @@ -1,29 +1,28 @@ import React from 'react'; -import { Popup, IconButton } from 'UI'; +import { Popup, Button, IconButton } from 'UI'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; const PERMISSION_WARNING = 'You don’t have the permissions to perform this action.'; const LIMIT_WARNING = 'You have reached site limit.'; -function AddProjectButton({ isAdmin = false, onClick }: any ) { +function AddProjectButton({ isAdmin = false, onClick }: any) { const { userStore } = useStore(); const limtis = useObserver(() => userStore.limits); const canAddProject = useObserver(() => isAdmin && (limtis.projects === -1 || limtis.projects > 0)); return ( - - + + {/* + /> */} ); } -export default AddProjectButton; \ No newline at end of file +export default AddProjectButton; diff --git a/frontend/app/components/Client/Sites/NewSiteForm.js b/frontend/app/components/Client/Sites/NewSiteForm.js index 57713d981..c6633b73b 100644 --- a/frontend/app/components/Client/Sites/NewSiteForm.js +++ b/frontend/app/components/Client/Sites/NewSiteForm.js @@ -12,7 +12,7 @@ import { confirm } from 'UI'; site: state.getIn([ 'site', 'instance' ]), sites: state.getIn([ 'site', 'list' ]), siteList: state.getIn([ 'site', 'list' ]), - loading: state.getIn([ 'site', 'save', 'loading' ]), + loading: state.getIn([ 'site', 'save', 'loading' ]) || state.getIn([ 'site', 'remove', 'loading' ]), }), { save, remove, @@ -103,9 +103,11 @@ export default class NewSiteForm extends React.PureComponent { > {site.exists() ? 'Update' : 'Add'} - + {site.exists() && ( + + )}
{ this.state.existsError &&
diff --git a/frontend/app/components/Client/Users/UsersView.tsx b/frontend/app/components/Client/Users/UsersView.tsx index 3b4d0c95f..a1240eb37 100644 --- a/frontend/app/components/Client/Users/UsersView.tsx +++ b/frontend/app/components/Client/Users/UsersView.tsx @@ -10,12 +10,13 @@ import { connect } from 'react-redux'; import AddUserButton from './components/AddUserButton'; interface Props { + isOnboarding?: boolean; account: any; isEnterprise: boolean; limits: any; } function UsersView(props: Props) { - const { account, limits, isEnterprise } = props; + const { account, limits, isEnterprise, isOnboarding = false } = props; const { userStore, roleStore } = useStore(); const userCount = useObserver(() => userStore.list.length); const roles = useObserver(() => roleStore.list); @@ -49,7 +50,7 @@ function UsersView(props: Props) {
- + ); } diff --git a/frontend/app/components/Client/Users/components/UserList/UserList.tsx b/frontend/app/components/Client/Users/components/UserList/UserList.tsx index cae5ffd43..a86e6d7d7 100644 --- a/frontend/app/components/Client/Users/components/UserList/UserList.tsx +++ b/frontend/app/components/Client/Users/components/UserList/UserList.tsx @@ -9,10 +9,11 @@ import UserForm from '../UserForm'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; interface Props { + isOnboarding?: boolean; isEnterprise?: boolean; } function UserList(props: Props) { - const { isEnterprise = false } = props; + const { isEnterprise = false, isOnboarding = false } = props; const { userStore } = useStore(); const loading = useObserver(() => userStore.loading); const users = useObserver(() => userStore.list); @@ -55,7 +56,7 @@ function UserList(props: Props) {
Name
Role
-
Created On
+ {!isOnboarding &&
Created On
}
@@ -67,6 +68,7 @@ function UserList(props: Props) { generateInvite={() => userStore.generateInviteCode(user.userId)} copyInviteCode={() => userStore.copyInviteCode(user.userId)} isEnterprise={isEnterprise} + isOnboarding={isOnboarding} /> ))} diff --git a/frontend/app/components/Client/Users/components/UserListItem/UserListItem.tsx b/frontend/app/components/Client/Users/components/UserListItem/UserListItem.tsx index 8628f029f..d7b7d0d55 100644 --- a/frontend/app/components/Client/Users/components/UserListItem/UserListItem.tsx +++ b/frontend/app/components/Client/Users/components/UserListItem/UserListItem.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Icon, Popup } from 'UI'; import { checkForRecent } from 'App/date'; - +import cn from 'classnames'; const AdminPrivilegeLabel = ({ user }) => { return ( @@ -13,6 +13,7 @@ const AdminPrivilegeLabel = ({ user }) => { ) } interface Props { + isOnboarding?: boolean; user: any; editHandler?: any; generateInvite?: any; @@ -26,6 +27,7 @@ function UserListItem(props: Props) { generateInvite = () => {}, copyInviteCode = () => {}, isEnterprise = false, + isOnboarding = false } = props; return (
@@ -41,11 +43,13 @@ function UserListItem(props: Props) { )}
-
- {user.createdAt && checkForRecent(user.createdAt, 'LLL dd, yyyy, hh:mm a')} -
+ {!isOnboarding && ( +
+ {user.createdAt && checkForRecent(user.createdAt, 'LLL dd, yyyy, hh:mm a')} +
+ )} -
+
{!user.isJoined && user.invitationLink && !user.isExpiredInvite && ( diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index d376e97f0..108d961a5 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -65,6 +65,9 @@ function DashboardView(props: Props) { trimQuery(); } }, []); + useEffect(() => { + dashboardStore.selectDefaultDashboard(); + }, [siteId]) const onAddWidgets = () => { dashboardStore.initDashboard(dashboard); diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index fc1aab477..9e6efa0de 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -51,6 +51,7 @@ const Header = (props) => { Promise.all([ userStore.fetchLimits(), notificationStore.fetchNotificationsCount(), + props.fetchMetadata(), ]).then(() => { userStore.updateKey('initialDataFetched', true); }); @@ -60,7 +61,6 @@ const Header = (props) => { useEffect(() => { activeSite = sites.find(s => s.id == siteId); props.initSite(activeSite); - props.fetchMetadata(); }, [siteId]) return ( diff --git a/frontend/app/components/Header/SiteDropdown.js b/frontend/app/components/Header/SiteDropdown.js index 9b3af1f8f..228190111 100644 --- a/frontend/app/components/Header/SiteDropdown.js +++ b/frontend/app/components/Header/SiteDropdown.js @@ -11,6 +11,7 @@ import styles from './siteDropdown.module.css'; import cn from 'classnames'; import NewSiteForm from '../Client/Sites/NewSiteForm'; import { clearSearch } from 'Duck/search'; +import { clearSearch as clearSearchLive } from 'Duck/liveSearch'; import { fetchList as fetchIntegrationVariables } from 'Duck/customField'; import { withStore } from 'App/mstore' import AnimatedSVG, { ICONS } from '../shared/AnimatedSVG/AnimatedSVG'; @@ -27,6 +28,7 @@ import NewProjectButton from './NewProjectButton'; pushNewSite, init, clearSearch, + clearSearchLive, fetchIntegrationVariables, }) export default class SiteDropdown extends React.PureComponent { @@ -45,8 +47,9 @@ export default class SiteDropdown extends React.PureComponent { const { mstore, location } = this.props this.props.setSiteId(siteId); - this.props.clearSearch(location.pathname.includes('/sessions')); this.props.fetchIntegrationVariables(); + this.props.clearSearch(location.pathname.includes('/sessions')); + this.props.clearSearchLive(); mstore.initClient(); } diff --git a/frontend/app/components/Onboarding/components/ManageUsersTab/ManageUsersTab.js b/frontend/app/components/Onboarding/components/ManageUsersTab/ManageUsersTab.js index c4d414395..1f4763247 100644 --- a/frontend/app/components/Onboarding/components/ManageUsersTab/ManageUsersTab.js +++ b/frontend/app/components/Onboarding/components/ManageUsersTab/ManageUsersTab.js @@ -1,5 +1,5 @@ +import UsersView from 'App/components/Client/Users/UsersView' import React from 'react' -import ManageUsers from '../../../Client/ManageUsers' export default function ManageUsersTab() { return ( @@ -9,9 +9,8 @@ export default function ManageUsersTab() { 👨‍💻
Invite Collaborators
- - - + +
diff --git a/frontend/app/components/Overview/Overview.tsx b/frontend/app/components/Overview/Overview.tsx new file mode 100644 index 000000000..78b4bfe2b --- /dev/null +++ b/frontend/app/components/Overview/Overview.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import withPageTitle from 'HOCs/withPageTitle'; +import NoSessionsMessage from 'Shared/NoSessionsMessage'; +import MainSearchBar from 'Shared/MainSearchBar'; +import SessionSearch from 'Shared/SessionSearch'; +import SessionListContainer from 'Shared/SessionListContainer/SessionListContainer'; + +function Overview() { + return ( +
+
+
+ + +
+ + + +
+ +
+
+
+
+ ); +} + +export default withPageTitle('Sessions - OpenReplay')(Overview); diff --git a/frontend/app/components/Overview/index.ts b/frontend/app/components/Overview/index.ts new file mode 100644 index 000000000..44bcc2216 --- /dev/null +++ b/frontend/app/components/Overview/index.ts @@ -0,0 +1 @@ +export { default } from './Overview'; \ No newline at end of file diff --git a/frontend/app/components/Session/EventsToggleButton/EventsToggleButton.module.css b/frontend/app/components/Session/EventsToggleButton/EventsToggleButton.module.css deleted file mode 100644 index 19eda2568..000000000 --- a/frontend/app/components/Session/EventsToggleButton/EventsToggleButton.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.wrapper { - background-color: rgba(255, 255, 255, 1); - border-top-left-radius: 20px; - border-bottom-left-radius: 20px; - padding: 5px; - box-shadow: -1px 1px 1px rgba(0, 0, 0, 0.5); -} \ No newline at end of file diff --git a/frontend/app/components/Session/EventsToggleButton/EventsToggleButton.tsx b/frontend/app/components/Session/EventsToggleButton/EventsToggleButton.tsx deleted file mode 100644 index 4d4b70895..000000000 --- a/frontend/app/components/Session/EventsToggleButton/EventsToggleButton.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react' -import { Icon, Popup } from 'UI' -import { connectPlayer, toggleEvents, scale } from 'Player'; -import cn from 'classnames' -import stl from './EventsToggleButton.module.css' - -function EventsToggleButton({ showEvents, toggleEvents }: any) { - const toggle = () => { - toggleEvents() - scale() - } - return ( - - - - ) -} - -export default connectPlayer(state => ({ - showEvents: !state.showEvents -}), { toggleEvents })(EventsToggleButton) - diff --git a/frontend/app/components/Session/EventsToggleButton/index.js b/frontend/app/components/Session/EventsToggleButton/index.js deleted file mode 100644 index b391f169d..000000000 --- a/frontend/app/components/Session/EventsToggleButton/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './EventsToggleButton' \ No newline at end of file diff --git a/frontend/app/components/Session/LiveSession.js b/frontend/app/components/Session/LiveSession.js index 673e0c701..f35997b11 100644 --- a/frontend/app/components/Session/LiveSession.js +++ b/frontend/app/components/Session/LiveSession.js @@ -4,17 +4,17 @@ import { connect } from 'react-redux'; import usePageTitle from 'App/hooks/usePageTitle'; import { fetch as fetchSession } from 'Duck/sessions'; import { fetchList as fetchSlackList } from 'Duck/integrations/slack'; -import { Link, NoContent, Loader } from 'UI'; -import { sessions as sessionsRoute } from 'App/routes'; +import { Loader } from 'UI'; +// import { sessions as sessionsRoute } from 'App/routes'; import withPermissions from 'HOCs/withPermissions' import LivePlayer from './LivePlayer'; -const SESSIONS_ROUTE = sessionsRoute(); +// const SESSIONS_ROUTE = sessionsRoute(); function LiveSession({ sessionId, loading, - hasErrors, + // hasErrors, session, fetchSession, fetchSlackList, @@ -51,7 +51,7 @@ export default withPermissions(['ASSIST_LIVE'], '', true)(connect((state, props) return { sessionId, loading: state.getIn([ 'sessions', 'loading' ]), - hasErrors: !!state.getIn([ 'sessions', 'errors' ]), + // hasErrors: !!state.getIn([ 'sessions', 'errors' ]), session: state.getIn([ 'sessions', 'current' ]), hasSessionsPath: hasSessiosPath && !isAssist, }; diff --git a/frontend/app/components/Session/Session.js b/frontend/app/components/Session/Session.js index c86d7d3e4..2d9bfa882 100644 --- a/frontend/app/components/Session/Session.js +++ b/frontend/app/components/Session/Session.js @@ -38,7 +38,7 @@ function Session({ subtext={ {'Please check your data retention plan, or try '} - {'another one'} + {'another one'} } > diff --git a/frontend/app/components/Session_/Autoscroll.js b/frontend/app/components/Session_/Autoscroll.js index 0d24f3930..02af15417 100644 --- a/frontend/app/components/Session_/Autoscroll.js +++ b/frontend/app/components/Session_/Autoscroll.js @@ -4,85 +4,82 @@ import cn from 'classnames'; import stl from './autoscroll.module.css'; export default class Autoscroll extends React.PureComponent { - static defaultProps = { - bottomOffset: 10, - } - state = { - autoScroll: true, - } + static defaultProps = { + bottomOffset: 10, + }; + state = { + autoScroll: true, + }; - componentDidMount() { - if (!this.scrollableElement) return; // is necessary ? - this.scrollableElement.addEventListener('scroll', this.scrollHandler); - this.scrollableElement.scrollTop = this.scrollableElement.scrollHeight; - } - - componentDidUpdate() { - if (!this.scrollableElement) return; // is necessary ? - if (this.state.autoScroll) { - this.scrollableElement.scrollTop = this.scrollableElement.scrollHeight; + componentDidMount() { + if (!this.scrollableElement) return; // is necessary ? + this.scrollableElement.addEventListener('scroll', this.scrollHandler); + this.scrollableElement.scrollTop = this.scrollableElement.scrollHeight; } - } - scrollHandler = (e) => { - if (!this.scrollableElement) return; - this.setState({ - autoScroll: this.scrollableElement.scrollHeight - - this.scrollableElement.clientHeight - - this.scrollableElement.scrollTop < this.props.bottomOffset, - }); - } - - onPrevClick = () => { - if (!this.scrollableElement) return; - const scEl = this.scrollableElement; - let prevItem; - for (let i = scEl.children.length - 1; i >= 0; i--) { - const child = scEl.children[ i ]; - const isScrollable = child.getAttribute("data-scroll-item") === "true"; - if (isScrollable && child.offsetTop < scEl.scrollTop) { - prevItem = child; - break; - } - } - if (!prevItem) return; - scEl.scrollTop = prevItem.offsetTop; - } - - onNextClick = () => { - if (!this.scrollableElement) return; - const scEl = this.scrollableElement; - let nextItem; - for (let i = 0; i < scEl.children.length; i++) { - const child = scEl.children[ i ]; - const isScrollable = child.getAttribute("data-scroll-item") === "true"; - if (isScrollable && child.offsetTop > scEl.scrollTop + 20) { // ? - nextItem = child; - break; - } - } - if (!nextItem) return; - scEl.scrollTop = nextItem.offsetTop; - } - - render() { - const { className, navigation=false, children, ...props } = this.props; - return ( -
-
this.scrollableElement = ref } - > - { children } -
- { navigation && -
- - -
+ componentDidUpdate() { + if (!this.scrollableElement) return; // is necessary ? + if (this.state.autoScroll) { + this.scrollableElement.scrollTop = this.scrollableElement.scrollHeight; } -
- ); - } -} \ No newline at end of file + } + + scrollHandler = (e) => { + if (!this.scrollableElement) return; + this.setState({ + autoScroll: + this.scrollableElement.scrollHeight - this.scrollableElement.clientHeight - this.scrollableElement.scrollTop < + this.props.bottomOffset, + }); + }; + + onPrevClick = () => { + if (!this.scrollableElement) return; + const scEl = this.scrollableElement; + let prevItem; + for (let i = scEl.children.length - 1; i >= 0; i--) { + const child = scEl.children[i]; + const isScrollable = child.getAttribute('data-scroll-item') === 'true'; + if (isScrollable && child.offsetTop < scEl.scrollTop) { + prevItem = child; + break; + } + } + if (!prevItem) return; + scEl.scrollTop = prevItem.offsetTop; + }; + + onNextClick = () => { + if (!this.scrollableElement) return; + const scEl = this.scrollableElement; + let nextItem; + for (let i = 0; i < scEl.children.length; i++) { + const child = scEl.children[i]; + const isScrollable = child.getAttribute('data-scroll-item') === 'true'; + if (isScrollable && child.offsetTop > scEl.scrollTop + 20) { + // ? + nextItem = child; + break; + } + } + if (!nextItem) return; + scEl.scrollTop = nextItem.offsetTop; + }; + + render() { + const { className, navigation = false, children, ...props } = this.props; + return ( +
+
(this.scrollableElement = ref)}> + {children} +
+ {navigation && ( +
+ + +
+ )} +
+ ); + } +} diff --git a/frontend/app/components/Session_/Console/Console.js b/frontend/app/components/Session_/Console/Console.js index 6c0207aa5..5534439fb 100644 --- a/frontend/app/components/Session_/Console/Console.js +++ b/frontend/app/components/Session_/Console/Console.js @@ -4,14 +4,15 @@ import ConsoleContent from './ConsoleContent'; @connectPlayer(state => ({ logs: state.logList, - time: state.time, + // time: state.time, livePlay: state.livePlay, + listNow: state.logListNow, })) export default class Console extends React.PureComponent { render() { - const { logs, time } = this.props; + const { logs, time, listNow } = this.props; return ( - + ); } } diff --git a/frontend/app/components/Session_/Console/ConsoleContent.js b/frontend/app/components/Session_/Console/ConsoleContent.js index 880f660e7..a2c084abd 100644 --- a/frontend/app/components/Session_/Console/ConsoleContent.js +++ b/frontend/app/components/Session_/Console/ConsoleContent.js @@ -3,126 +3,113 @@ import cn from 'classnames'; import { getRE } from 'App/utils'; import { Icon, NoContent, Tabs, Input } from 'UI'; import { jump } from 'Player'; -import { LEVEL } from 'Types/session/log'; +import { LEVEL } from 'Types/session/log'; import Autoscroll from '../Autoscroll'; import BottomBlock from '../BottomBlock'; import stl from './console.module.css'; - const ALL = 'ALL'; const INFO = 'INFO'; const WARNINGS = 'WARNINGS'; const ERRORS = 'ERRORS'; const LEVEL_TAB = { - [ LEVEL.INFO ]: INFO, - [ LEVEL.LOG ]: INFO, - [ LEVEL.WARNING ]: WARNINGS, - [ LEVEL.ERROR ]: ERRORS, - [ LEVEL.EXCEPTION ]: ERRORS, + [LEVEL.INFO]: INFO, + [LEVEL.LOG]: INFO, + [LEVEL.WARNING]: WARNINGS, + [LEVEL.ERROR]: ERRORS, + [LEVEL.EXCEPTION]: ERRORS, }; -const TABS = [ ALL, ERRORS, WARNINGS, INFO, ].map(tab => ({ text: tab, key: tab })); +const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ text: tab, key: tab })); // eslint-disable-next-line complexity const getIconProps = (level) => { - switch (level) { - case LEVEL.INFO: - case LEVEL.LOG: - return { - name: 'console/info', - color: 'blue2', - }; - case LEVEL.WARN: - case LEVEL.WARNING: - return { - name: 'console/warning', - color: 'red2', - }; - case LEVEL.ERROR: - return { - name: 'console/error', - color: 'red', - }; - - } - return null; + switch (level) { + case LEVEL.INFO: + case LEVEL.LOG: + return { + name: 'console/info', + color: 'blue2', + }; + case LEVEL.WARN: + case LEVEL.WARNING: + return { + name: 'console/warning', + color: 'red2', + }; + case LEVEL.ERROR: + return { + name: 'console/error', + color: 'red', + }; + } + return null; }; function renderWithNL(s = '') { - if (typeof s !== 'string') return ''; - return s.split('\n').map((line, i) =>
{ line }
) + if (typeof s !== 'string') return ''; + return s.split('\n').map((line, i) =>
{line}
); } export default class ConsoleContent extends React.PureComponent { - state = { - filter: '', - activeTab: ALL, - } - onTabClick = activeTab => this.setState({ activeTab }) - onFilterChange = (e, { value }) => this.setState({ filter: value }) + state = { + filter: '', + activeTab: ALL, + }; + onTabClick = (activeTab) => this.setState({ activeTab }); + onFilterChange = ({ target: { value } }) => this.setState({ filter: value }); - render() { - const { logs, isResult, additionalHeight, time } = this.props; - const { filter, activeTab, currentError } = this.state; - const filterRE = getRE(filter, 'i'); - const filtered = logs.filter(({ level, value }) => activeTab === ALL - ? filterRE.test(value) - : (filterRE.test(value) && LEVEL_TAB[ level ] === activeTab) - ); + render() { + const { logs, isResult, additionalHeight, lastIndex } = this.props; + const { filter, activeTab } = this.state; + const filterRE = getRE(filter, 'i'); + const filtered = logs.filter(({ level, value }) => + activeTab === ALL ? filterRE.test(value) : filterRE.test(value) && LEVEL_TAB[level] === activeTab + ); - const lastIndex = filtered.filter(item => item.time <= time).length - 1; - - return ( - <> - - -
- Console - -
- -
- - - - { filtered.map((l, index) => ( -
!isResult && jump(l.time) } - > - -
{ renderWithNL(l.value) }
-
- ))} -
-
-
-
- - ); - } + return ( + <> + + +
+ Console + +
+ +
+ + + + {filtered.map((l, index) => ( +
!isResult && jump(l.time)} + > + +
{renderWithNL(l.value)}
+
+ ))} +
+
+
+
+ + ); + } } diff --git a/frontend/app/components/Session_/EventsBlock/event.module.css b/frontend/app/components/Session_/EventsBlock/event.module.css index 2a928e3ed..8afea78f2 100644 --- a/frontend/app/components/Session_/EventsBlock/event.module.css +++ b/frontend/app/components/Session_/EventsBlock/event.module.css @@ -71,6 +71,7 @@ transition: all 0.2s; box-shadow: 0px 2px 10px 0 $gray-light; border: 1px solid $active-blue-border; + /* background-color: red; */ } &.red { diff --git a/frontend/app/components/Session_/Fetch/Fetch.js b/frontend/app/components/Session_/Fetch/Fetch.js index 46cc44d38..2dff482c9 100644 --- a/frontend/app/components/Session_/Fetch/Fetch.js +++ b/frontend/app/components/Session_/Fetch/Fetch.js @@ -10,170 +10,154 @@ import { renderName, renderDuration } from '../Network'; import { connect } from 'react-redux'; import { setTimelinePointer } from 'Duck/sessions'; -@connectPlayer(state => ({ - list: state.fetchList, - livePlay: state.livePlay, +@connectPlayer((state) => ({ + list: state.fetchList, + listNow: state.fetchListNow, + livePlay: state.livePlay, })) -@connect(state => ({ - timelinePointer: state.getIn(['sessions', 'timelinePointer']), -}), { setTimelinePointer }) +@connect( + (state) => ({ + timelinePointer: state.getIn(['sessions', 'timelinePointer']), + }), + { setTimelinePointer } +) export default class Fetch extends React.PureComponent { - state = { - filter: "", - filteredList: this.props.list, - current: null, - currentIndex: 0, - showFetchDetails: false, - hasNextError: false, - hasPreviousError: false, - } - - onFilterChange = (e, { value }) => { - const { list } = this.props; - const filterRE = getRE(value, 'i'); - const filtered = list - .filter((r) => filterRE.test(r.name) || filterRE.test(r.url) || filterRE.test(r.method) || filterRE.test(r.status)); - this.setState({ filter: value, filteredList: value ? filtered : list, currentIndex: 0 }); - } + state = { + filter: '', + filteredList: this.props.list, + current: null, + currentIndex: 0, + showFetchDetails: false, + hasNextError: false, + hasPreviousError: false, + }; - setCurrent = (item, index) => { - if (!this.props.livePlay) { - pause(); - jump(item.time) + onFilterChange = ({ target: { value } }) => { + const { list } = this.props; + const filterRE = getRE(value, 'i'); + const filtered = list.filter((r) => filterRE.test(r.name) || filterRE.test(r.url) || filterRE.test(r.method) || filterRE.test(r.status)); + this.setState({ filter: value, filteredList: value ? filtered : list, currentIndex: 0 }); + }; + + setCurrent = (item, index) => { + if (!this.props.livePlay) { + pause(); + jump(item.time); + } + this.setState({ current: item, currentIndex: index }); + }; + + onRowClick = (item, index) => { + if (!this.props.livePlay) { + pause(); + } + this.setState({ current: item, currentIndex: index, showFetchDetails: true }); + this.props.setTimelinePointer(null); + }; + + closeModal = () => this.setState({ current: null, showFetchDetails: false }); + + nextClickHander = () => { + // const { list } = this.props; + const { currentIndex, filteredList } = this.state; + + if (currentIndex === filteredList.length - 1) return; + const newIndex = currentIndex + 1; + this.setCurrent(filteredList[newIndex], newIndex); + this.setState({ showFetchDetails: true }); + }; + + prevClickHander = () => { + // const { list } = this.props; + const { currentIndex, filteredList } = this.state; + + if (currentIndex === 0) return; + const newIndex = currentIndex - 1; + this.setCurrent(filteredList[newIndex], newIndex); + this.setState({ showFetchDetails: true }); + }; + + render() { + const { listNow } = this.props; + const { current, currentIndex, showFetchDetails, filteredList } = this.state; + return ( + + +

Fetch Request

+
+
+ Status + +
+ +
+
+ } + isDisplayed={current != null && showFetchDetails} + content={ + current && + showFetchDetails && ( + + ) + } + onClose={this.closeModal} + /> + + +

Fetch

+
+ +
+
+ + + + {[ + { + label: 'Status', + dataKey: 'status', + width: 70, + }, + { + label: 'Method', + dataKey: 'method', + width: 60, + }, + { + label: 'Name', + width: 240, + render: renderName, + }, + { + label: 'Time', + width: 80, + render: renderDuration, + }, + ]} + + + +
+ + ); } - this.setState({ current: item, currentIndex: index }); - } - - onRowClick = (item, index) => { - if (!this.props.livePlay) { - pause(); - } - this.setState({ current: item, currentIndex: index, showFetchDetails: true }); - this.props.setTimelinePointer(null); - } - - closeModal = () => this.setState({ current: null, showFetchDetails: false }); - - nextClickHander = () => { - // const { list } = this.props; - const { currentIndex, filteredList } = this.state; - - if (currentIndex === filteredList.length - 1) return; - const newIndex = currentIndex + 1; - this.setCurrent(filteredList[newIndex], newIndex); - this.setState({ showFetchDetails: true }); - } - - prevClickHander = () => { - // const { list } = this.props; - const { currentIndex, filteredList } = this.state; - - if (currentIndex === 0) return; - const newIndex = currentIndex - 1; - this.setCurrent(filteredList[newIndex], newIndex); - this.setState({ showFetchDetails: true }); - } - - static getDerivedStateFromProps(nextProps, prevState) { - const { filteredList } = prevState; - if (nextProps.timelinePointer) { - let activeItem = filteredList.find((r) => r.time >= nextProps.timelinePointer.time); - activeItem = activeItem || filteredList[filteredList.length - 1]; - return { - current: activeItem, - currentIndex: filteredList.indexOf(activeItem), - }; - } - } - - render() { - // const { list } = this.props; - const { current, currentIndex, showFetchDetails, filteredList } = this.state; - return ( - - -

Fetch Request

-
-
- Status - -
- -
-
- } - isDisplayed={ current != null && showFetchDetails } - content={ current && showFetchDetails && - - } - onClose={ this.closeModal } - /> - - -

Fetch

-
- -
-
- - - - {[ - { - label: "Status", - dataKey: 'status', - width: 70, - }, { - label: "Method", - dataKey: "method", - width: 60, - }, { - label: "Name", - width: 180, - render: renderName, - }, - { - label: "Time", - width: 80, - render: renderDuration, - } - ]} - - - -
- - ); - } } diff --git a/frontend/app/components/Session_/Inspector/index.js b/frontend/app/components/Session_/Inspector/index.js index e3fda7096..f76834fee 100644 --- a/frontend/app/components/Session_/Inspector/index.js +++ b/frontend/app/components/Session_/Inspector/index.js @@ -38,9 +38,7 @@ export default function Inspector () { const onKeyPress = e => { if (e.key === 'Backspace' || e.key === 'Delete') { const elem = selectedElementRef.current; - console.log(elem) if (elem !== null && elem.parentElement !== null) { - console.log('a?') elem.parentElement.removeChild(elem); setSelectedElement(null); } diff --git a/frontend/app/components/Session_/LongTasks/LongTasks.js b/frontend/app/components/Session_/LongTasks/LongTasks.js index 61f8b41ea..55f204ea4 100644 --- a/frontend/app/components/Session_/LongTasks/LongTasks.js +++ b/frontend/app/components/Session_/LongTasks/LongTasks.js @@ -29,7 +29,7 @@ export default class GraphQL extends React.PureComponent { state = { filter: "", } - onFilterChange = (e, { value }) => this.setState({ filter: value }) + onFilterChange = ({ target: { value } }) => this.setState({ filter: value }) jump = ({ time }) => { jump(time); diff --git a/frontend/app/components/Session_/Network/Network.js b/frontend/app/components/Session_/Network/Network.js index 2834cc501..887cc1148 100644 --- a/frontend/app/components/Session_/Network/Network.js +++ b/frontend/app/components/Session_/Network/Network.js @@ -1,7 +1,7 @@ import React from 'react'; import cn from 'classnames'; import { connectPlayer, jump, pause } from 'Player'; -import { Popup } from 'UI'; +import { Popup, Button, TextEllipsis } from 'UI'; import { getRE } from 'App/utils'; import { TYPES } from 'Types/session/resource'; import stl from './network.module.css'; @@ -18,138 +18,128 @@ const MEDIA = 'media'; const OTHER = 'other'; const TAB_TO_TYPE_MAP = { - [ XHR ]: TYPES.XHR, - [ JS ]: TYPES.JS, - [ CSS ]: TYPES.CSS, - [ IMG ]: TYPES.IMG, - [ MEDIA ]: TYPES.MEDIA, - [ OTHER ]: TYPES.OTHER -} + [XHR]: TYPES.XHR, + [JS]: TYPES.JS, + [CSS]: TYPES.CSS, + [IMG]: TYPES.IMG, + [MEDIA]: TYPES.MEDIA, + [OTHER]: TYPES.OTHER, +}; -export function renderName(r) { - return ( -
- { r.url }
} > -
{ r.name }
- -
{ - e.stopPropagation(); - jump(r.time) - }} - >Jump
-
- ); +export function renderName(r) { + return ( +
+ {r.url}
}> + {r.name} + + +
+ ); } export function renderDuration(r) { - if (!r.success) return 'x'; + if (!r.success) return 'x'; - const text = `${ Math.round(r.duration) }ms`; - if (!r.isRed() && !r.isYellow()) return text; + const text = `${Math.round(r.duration)}ms`; + if (!r.isRed() && !r.isYellow()) return text; - let tooltipText; - let className = "w-full h-full flex items-center "; - if (r.isYellow()) { - tooltipText = "Slower than average"; - className += "warn color-orange"; - } else { - tooltipText = "Much slower than average"; - className += "error color-red"; - } - - return ( - -
{ text }
-
- ); -} - -@connectPlayer(state => ({ - location: state.location, - resources: state.resourceList, - domContentLoadedTime: state.domContentLoadedTime, - loadTime: state.loadTime, - // time: state.time, - playing: state.playing, - domBuildingTime: state.domBuildingTime, - fetchPresented: state.fetchList.length > 0, -})) -@connect(state => ({ - timelinePointer: state.getIn(['sessions', 'timelinePointer']), -}), { setTimelinePointer }) -export default class Network extends React.PureComponent { - state = { - filter: '', - filteredList: this.props.resources, - activeTab: ALL, - currentIndex: 0 - } - - onRowClick = (e, index) => { - pause(); - jump(e.time); - this.setState({ currentIndex: index }) - this.props.setTimelinePointer(null); - } - - onTabClick = activeTab => this.setState({ activeTab }) - - onFilterChange = (e, { value }) => { - const { resources } = this.props; - const filterRE = getRE(value, 'i'); - const filtered = resources.filter(({ type, name }) => - filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ])); - - this.setState({ filter: value, filteredList: value ? filtered : resources, currentIndex: 0 }); - } - - static getDerivedStateFromProps(nextProps, prevState) { - const { filteredList } = prevState; - if (nextProps.timelinePointer) { - const activeItem = filteredList.find((r) => r.time >= nextProps.timelinePointer.time); - return { - currentIndex: activeItem ? filteredList.indexOf(activeItem) : filteredList.length - 1, - }; - } - } - - render() { - const { - location, - resources, - domContentLoadedTime, - loadTime, - domBuildingTime, - fetchPresented, - // time, - playing - } = this.props; - const { filter, activeTab, currentIndex, filteredList } = this.state; - // const filterRE = getRE(filter, 'i'); - // let filtered = resources.filter(({ type, name }) => - // filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ])); - const resourcesSize = filteredList.reduce((sum, { decodedBodySize }) => sum + (decodedBodySize || 0), 0); - const transferredSize = filteredList - .reduce((sum, { headerSize, encodedBodySize }) => sum + (headerSize || 0) + (encodedBodySize || 0), 0); + let tooltipText; + let className = 'w-full h-full flex items-center '; + if (r.isYellow()) { + tooltipText = 'Slower than average'; + className += 'warn color-orange'; + } else { + tooltipText = 'Much slower than average'; + className += 'error color-red'; + } return ( - - - + +
{text}
+
); - } +} + +@connectPlayer((state) => ({ + location: state.location, + resources: state.resourceList, + domContentLoadedTime: state.domContentLoadedTime, + loadTime: state.loadTime, + // time: state.time, + playing: state.playing, + domBuildingTime: state.domBuildingTime, + fetchPresented: state.fetchList.length > 0, + listNow: state.resourceListNow, +})) +@connect( + (state) => ({ + timelinePointer: state.getIn(['sessions', 'timelinePointer']), + }), + { setTimelinePointer } +) +export default class Network extends React.PureComponent { + state = { + filter: '', + filteredList: this.props.resources, + activeTab: ALL, + currentIndex: 0, + }; + + onRowClick = (e, index) => { + pause(); + jump(e.time); + this.setState({ currentIndex: index }); + this.props.setTimelinePointer(null); + }; + + onTabClick = (activeTab) => this.setState({ activeTab }); + + onFilterChange = (e, { value }) => { + const { resources } = this.props; + const filterRE = getRE(value, 'i'); + const filtered = resources.filter(({ type, name }) => filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[activeTab])); + + this.setState({ filter: value, filteredList: value ? filtered : resources, currentIndex: 0 }); + }; + + render() { + const { + location, + domContentLoadedTime, + loadTime, + domBuildingTime, + fetchPresented, + listNow, + } = this.props; + const { filteredList } = this.state; + const resourcesSize = filteredList.reduce((sum, { decodedBodySize }) => sum + (decodedBodySize || 0), 0); + const transferredSize = filteredList.reduce((sum, { headerSize, encodedBodySize }) => sum + (headerSize || 0) + (encodedBodySize || 0), 0); + + return ( + + + + ); + } } diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js index 95742874c..3acdb4c11 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.js +++ b/frontend/app/components/Session_/Player/Controls/Timeline.js @@ -95,7 +95,6 @@ export default class Timeline extends React.PureComponent { const { endTime } = this.props; const p = e.nativeEvent.offsetX / e.target.offsetWidth; const time = Math.max(Math.round(p * endTime), 0); - console.log(p, time, e, endTime) this.props.jump(time); } @@ -232,7 +231,7 @@ export default class Timeline extends React.PureComponent {
} > - +
))} @@ -254,7 +253,7 @@ export default class Timeline extends React.PureComponent { } > - + } @@ -277,7 +276,7 @@ export default class Timeline extends React.PureComponent { } > - + )) @@ -330,7 +329,7 @@ export default class Timeline extends React.PureComponent { } > - + )) diff --git a/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx b/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx index f91f1d963..d5afa65a2 100644 --- a/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx +++ b/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx @@ -4,7 +4,7 @@ import type { MarkedTarget } from 'Player/MessageDistributor/StatedScreen/Stated import cn from 'classnames'; import stl from './Marker.module.css'; import { activeTarget } from 'Player'; -import { Popup } from 'UI'; +import { Tooltip } from 'react-tippy'; interface Props { target: MarkedTarget; @@ -21,17 +21,17 @@ export default function Marker({ target, active }: Props) { return (
activeTarget(target.index)}>
{target.index + 1}
- {target.count} Clicks
)} >
- + ) } \ No newline at end of file diff --git a/frontend/app/components/Session_/TimeTable/TimeTable.tsx b/frontend/app/components/Session_/TimeTable/TimeTable.tsx index 413af3f61..4a6f1140e 100644 --- a/frontend/app/components/Session_/TimeTable/TimeTable.tsx +++ b/frontend/app/components/Session_/TimeTable/TimeTable.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { List, AutoSizer } from "react-virtualized"; +import { List, AutoSizer } from 'react-virtualized'; import cn from 'classnames'; -import { NoContent, IconButton } from 'UI'; +import { NoContent, IconButton, Button } from 'UI'; import { percentOf } from 'App/utils'; import { formatMs } from 'App/date'; @@ -10,58 +10,58 @@ import stl from './timeTable.module.css'; import autoscrollStl from '../autoscroll.module.css'; //aaa - type Timed = { - time: number, -} + time: number; +}; type Durationed = { - duration: number, -} + duration: number; +}; type CanBeRed = { - //+isRed: boolean, - isRed: () => boolean, -} + //+isRed: boolean, + isRed: () => boolean; +}; -type Row = Timed & Durationed & CanBeRed +type Row = Timed & Durationed & CanBeRed; type Line = { - color: string, // Maybe use typescript? - hint?: string, - onClick?: any, -} & Timed + color: string; // Maybe use typescript? + hint?: string; + onClick?: any; +} & Timed; type Column = { - label: string, - width: number, - referenceLines?: Array, - style?: Object, -} & RenderOrKey + label: string; + width: number; + referenceLines?: Array; + style?: Object; +} & RenderOrKey; // type RenderOrKey = { // Disjoint? -// render: Row => React.Node +// render: Row => React.Node // } | { // dataKey: string, // } -type RenderOrKey = { - render?: (row: Row) => React.ReactNode, - key?: string, -} | { - dataKey: string, -} - +type RenderOrKey = + | { + render?: (row: Row) => React.ReactNode; + key?: string; + } + | { + dataKey: string; + }; type Props = { - className?: string, - rows: Array, - children: Array -} + className?: string; + rows: Array; + children: Array; +}; type TimeLineInfo = { - timestart: number, - timewidth: number, -} + timestart: number; + timewidth: number; +}; type State = TimeLineInfo & typeof initialState; @@ -73,266 +73,228 @@ const ROW_HEIGHT = 32; const TIME_SECTIONS_COUNT = 8; const ZERO_TIMEWIDTH = 1000; function formatTime(ms) { - if(ms < 0) return ""; - return formatMs(ms); + if (ms < 0) return ''; + return formatMs(ms); } function computeTimeLine(rows: Array, firstVisibleRowIndex: number, visibleCount): TimeLineInfo { - const visibleRows = rows.slice(firstVisibleRowIndex, firstVisibleRowIndex + visibleCount + _additionalHeight); - let timestart = visibleRows.length > 0 - ? Math.min(...visibleRows.map(r => r.time)) - : 0; - const timeend = visibleRows.length > 0 - ? Math.max(...visibleRows.map(r => r.time + r.duration)) - : 0; - let timewidth = timeend - timestart; - const offset = timewidth / 70; - if (timestart >= offset) { - timestart -= offset; - } - timewidth *= 1.5; // += offset; - if (timewidth === 0) { - timewidth = ZERO_TIMEWIDTH; - } - return { - timestart, - timewidth, - }; + const visibleRows = rows.slice(firstVisibleRowIndex, firstVisibleRowIndex + visibleCount + _additionalHeight); + let timestart = visibleRows.length > 0 ? Math.min(...visibleRows.map((r) => r.time)) : 0; + const timeend = visibleRows.length > 0 ? Math.max(...visibleRows.map((r) => r.time + r.duration)) : 0; + let timewidth = timeend - timestart; + const offset = timewidth / 70; + if (timestart >= offset) { + timestart -= offset; + } + timewidth *= 1.5; // += offset; + if (timewidth === 0) { + timewidth = ZERO_TIMEWIDTH; + } + return { + timestart, + timewidth, + }; } const initialState = { - firstVisibleRowIndex: 0, -} + firstVisibleRowIndex: 0, +}; export default class TimeTable extends React.PureComponent { - state = { - ...computeTimeLine(this.props.rows, initialState.firstVisibleRowIndex, this.visibleCount), - ...initialState, - } + state = { + ...computeTimeLine(this.props.rows, initialState.firstVisibleRowIndex, this.visibleCount), + ...initialState, + }; - get tableHeight() { - return this.props.tableHeight || 195; - } - - get visibleCount() { - return Math.ceil(this.tableHeight/ROW_HEIGHT); - } - - scroller = React.createRef(); - autoScroll = true; - - // componentDidMount() { - // if (this.scroller.current != null) { - // this.scroller.current.scrollToRow(this.props.rows.length - 1); - // } - // } - - componentDidUpdate(prevProps: any, prevState: any) { - // if (prevProps.rows.length !== this.props.rows.length && - // this.autoScroll && - // this.scroller.current != null) { - // this.scroller.current.scrollToRow(this.props.rows.length); - // } - if (prevState.firstVisibleRowIndex !== this.state.firstVisibleRowIndex || - (this.props.rows.length <= (this.visibleCount + _additionalHeight) && prevProps.rows.length !== this.props.rows.length)) { - this.setState({ - ...computeTimeLine(this.props.rows, this.state.firstVisibleRowIndex, this.visibleCount), - }); - } - if (this.props.activeIndex >= 0 && prevProps.activeIndex !== this.props.activeIndex && this.scroller.current != null) { - this.scroller.current.scrollToRow(this.props.activeIndex); - } - } - - onScroll = ({ scrollTop, scrollHeight, clientHeight }: - { scrollTop: number, scrollHeight: number, clientHeight: number }):void => { - const firstVisibleRowIndex = Math.floor(scrollTop / ROW_HEIGHT + 0.33); - - if (this.state.firstVisibleRowIndex !== firstVisibleRowIndex) { - this.autoScroll = (scrollHeight - clientHeight - scrollTop) < ROW_HEIGHT / 2; - this.setState({ firstVisibleRowIndex }); - } - } - - renderRow = ({ index, key, style: rowStyle }: any) => { - const { activeIndex } = this.props; - const { - children: columns, - rows, - renderPopup, - hoverable, - onRowClick, - } = this.props; - const { - timestart, - timewidth, - } = this.state; - const row = rows[ index ]; - return ( -
onRowClick(row, index) : null } - id="table-row" - > - { columns.map(({ dataKey, render, width }) => ( -
- { render ? render(row) : (row[ dataKey ] || {"empty"}) } -
- ))} -
- -
-
- ); - } - - onPrevClick = () => { - let prevRedIndex = -1; - for (let i = this.state.firstVisibleRowIndex-1; i >= 0; i--) { - if (this.props.rows[ i ].isRed()) { - prevRedIndex = i; - break; - } - } - if (this.scroller.current != null) { - this.scroller.current.scrollToRow(prevRedIndex); - } - } - - onNextClick = () => { - let prevRedIndex = -1; - for (let i = this.state.firstVisibleRowIndex+1; i < this.props.rows.length; i++) { - if (this.props.rows[ i ].isRed()) { - prevRedIndex = i; - break; - } - } - if (this.scroller.current != null) { - this.scroller.current.scrollToRow(prevRedIndex); - } - } - - render() { - const { - className, - rows, - children: columns, - navigation=false, - referenceLines = [], - additionalHeight = 0, - activeIndex, - } = this.props; - const { - timewidth, - timestart, - } = this.state; - - _additionalHeight = additionalHeight; - - const sectionDuration = Math.round(timewidth / TIME_SECTIONS_COUNT); - const timeColumns = []; - if (timewidth > 0) { - for (let i = 0; i < TIME_SECTIONS_COUNT; i++) { - timeColumns.push(timestart + i * sectionDuration); - } + get tableHeight() { + return this.props.tableHeight || 195; } - const visibleRefLines = referenceLines.filter(({ time }) => time > timestart && time < timestart + timewidth); + get visibleCount() { + return Math.ceil(this.tableHeight / ROW_HEIGHT); + } - const columnsSumWidth = columns.reduce((sum, { width }) => sum + width, 0); + scroller = React.createRef(); + autoScroll = true; - return ( -
- { navigation && -
- = 0 && prevProps.activeIndex !== this.props.activeIndex) { + this.scroller.current.scrollToRow(this.props.activeIndex); + } + } + + onScroll = ({ scrollTop, scrollHeight, clientHeight }: { scrollTop: number; scrollHeight: number; clientHeight: number }): void => { + const firstVisibleRowIndex = Math.floor(scrollTop / ROW_HEIGHT + 0.33); + + if (this.state.firstVisibleRowIndex !== firstVisibleRowIndex) { + this.autoScroll = scrollHeight - clientHeight - scrollTop < ROW_HEIGHT / 2; + this.setState({ firstVisibleRowIndex }); + } + }; + + renderRow = ({ index, key, style: rowStyle }: any) => { + const { activeIndex } = this.props; + const { children: columns, rows, renderPopup, hoverable, onRowClick } = this.props; + const { timestart, timewidth } = this.state; + const row = rows[index]; + return ( +
onRowClick(row, index) : null} + id="table-row" + > + {columns.map(({ dataKey, render, width }) => ( +
+ {render ? render(row) : row[dataKey] || {'empty'}} +
+ ))} +
+ +
+
+ ); + }; + + onPrevClick = () => { + let prevRedIndex = -1; + for (let i = this.state.firstVisibleRowIndex - 1; i >= 0; i--) { + if (this.props.rows[i].isRed()) { + prevRedIndex = i; + break; + } + } + if (this.scroller.current != null) { + this.scroller.current.scrollToRow(prevRedIndex); + } + }; + + onNextClick = () => { + let prevRedIndex = -1; + for (let i = this.state.firstVisibleRowIndex + 1; i < this.props.rows.length; i++) { + if (this.props.rows[i].isRed()) { + prevRedIndex = i; + break; + } + } + if (this.scroller.current != null) { + this.scroller.current.scrollToRow(prevRedIndex); + } + }; + + render() { + const { className, rows, children: columns, navigation = false, referenceLines = [], additionalHeight = 0, activeIndex } = this.props; + const { timewidth, timestart } = this.state; + + _additionalHeight = additionalHeight; + + const sectionDuration = Math.round(timewidth / TIME_SECTIONS_COUNT); + const timeColumns = []; + if (timewidth > 0) { + for (let i = 0; i < TIME_SECTIONS_COUNT; i++) { + timeColumns.push(timestart + i * sectionDuration); + } + } + + const visibleRefLines = referenceLines.filter(({ time }) => time > timestart && time < timestart + timewidth); + + const columnsSumWidth = columns.reduce((sum, { width }) => sum + width, 0); + + return ( +
+ {navigation && ( +
+
- } -
-
- { columns.map(({ label, width }) => ( -
- { label } -
- )) } -
-
- { timeColumns.map((time, i) => ( -
- { formatTime(time) } + /> */} +
+ )} +
+
+ {columns.map(({ label, width }) => ( +
+ {label} +
+ ))} +
+
+ {timeColumns.map((time, i) => ( +
+ {formatTime(time)} +
+ ))} +
- )) - } -
-
- -
-
- { timeColumns.map((_, index) => ( -
- )) - } - { visibleRefLines.map(({ time, color, onClick }) => ( -
- ))} + +
+
+ {timeColumns.map((_, index) => ( +
+ ))} + {visibleRefLines.map(({ time, color, onClick }) => ( +
+ ))} +
+ + {({ width }) => ( + + )} + +
+
- - {({ width }) => ( - - )} - -
-
-
- ); - } + ); + } } - diff --git a/frontend/app/components/Session_/autoscroll.module.css b/frontend/app/components/Session_/autoscroll.module.css index 546f87049..42c5d980a 100644 --- a/frontend/app/components/Session_/autoscroll.module.css +++ b/frontend/app/components/Session_/autoscroll.module.css @@ -13,7 +13,7 @@ .navButtons { position: absolute; right: 260px; - top: -34px; + top: -39px; } diff --git a/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx b/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx index 76f752f70..c9629bf7a 100644 --- a/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx +++ b/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx @@ -21,7 +21,6 @@ interface Props { } function CodeSnippet(props: Props) { const { host, projectKey, ingestPoint, defaultInputMode, obscureTextNumbers, obscureTextEmails } = props; - console.log('defaultInputMode', defaultInputMode) const codeSnippet = `