diff --git a/.github/workflows/api-ee.yaml b/.github/workflows/api-ee.yaml index 9de1c53d3..ccf6108cf 100644 --- a/.github/workflows/api-ee.yaml +++ b/.github/workflows/api-ee.yaml @@ -82,11 +82,11 @@ jobs: sed -i "s/enterpriseEditionLicense: \"\"/enterpriseEditionLicense: \"${{ secrets.EE_LICENSE_KEY }}\"/g" vars.yaml # Update changed image tag - sed -i "/chalice/{n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml + sed -i "/chalice/{n;n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml cat /tmp/image_override.yaml # Deploy command - helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml + helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set skipMigration=true env: DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} # We're not passing -ee flag, because helm will add that. diff --git a/.github/workflows/api.yaml b/.github/workflows/api.yaml index 3d04a02c1..ad3f848c2 100644 --- a/.github/workflows/api.yaml +++ b/.github/workflows/api.yaml @@ -84,7 +84,7 @@ jobs: cat /tmp/image_override.yaml # Deploy command - helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml + helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set skipMigration=true env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} IMAGE_TAG: ${{ github.sha }} diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index dba71939f..3cbca5ced 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -36,8 +36,6 @@ jobs: method: kubeconfig kubeconfig: ${{ secrets.OSS_KUBECONFIG }} # Use content of kubeconfig in secret. id: setcontext -# - name: Install -# run: npm install - name: Building and Pushing frontend image id: build-image @@ -46,14 +44,19 @@ jobs: IMAGE_TAG: ${{ github.sha }} ENVIRONMENT: staging run: | + set -x cd frontend mv .env.sample .env docker run --rm -v /etc/passwd:/etc/passwd -u `id -u`:`id -g` -v $(pwd):/home/${USER} -w /home/${USER} --name node_build node:14-stretch-slim /bin/bash -c "yarn && yarn build" # https://github.com/docker/cli/issues/1134#issuecomment-613516912 DOCKER_BUILDKIT=1 docker build --target=cicd -t $DOCKER_REPO/frontend:${IMAGE_TAG} . + docker tag $DOCKER_REPO/frontend:${IMAGE_TAG} $DOCKER_REPO/frontend:${IMAGE_TAG}-ee docker push $DOCKER_REPO/frontend:${IMAGE_TAG} + docker push $DOCKER_REPO/frontend:${IMAGE_TAG}-ee + - name: Creating old image input run: | + set -x # # Create yaml with existing image tags # @@ -72,7 +75,7 @@ jobs: EOF done - - name: Deploy to kubernetes + - name: Deploy to kubernetes foss run: | cd scripts/helmcharts/ @@ -88,12 +91,74 @@ jobs: cat /tmp/image_override.yaml # Deploy command - helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --atomic + helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --atomic --set skipMigration=true env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} IMAGE_TAG: ${{ github.sha }} ENVIRONMENT: staging + +### Enterprise code deployment + + - name: cleaning old assets + run: | + rm -rf /tmp/image_* + - uses: azure/k8s-set-context@v1 + with: + method: kubeconfig + kubeconfig: ${{ secrets.EE_KUBECONFIG }} # Use content of kubeconfig in secret. + id: setcontextee + + - name: Creating old image input + env: + IMAGE_TAG: ${{ github.sha }} + run: | + # + # Create yaml with existing image tags + # + kubectl get pods -n app -o jsonpath="{.items[*].spec.containers[*].image}" |\ + tr -s '[[:space:]]' '\n' | sort | uniq -c | grep '/foss/' | cut -d '/' -f3 > /tmp/image_tag.txt + + echo > /tmp/image_override.yaml + + for line in `cat /tmp/image_tag.txt`; + do + image_array=($(echo "$line" | tr ':' '\n')) + cat <> /tmp/image_override.yaml + ${image_array[0]}: + image: + # We've to strip off the -ee, as helm will append it. + tag: `echo ${image_array[1]} | cut -d '-' -f 1` + EOF + done + + - name: Resetting vars file + run: | + git checkout -- scripts/helmcharts/vars.yaml + - name: Deploy to kubernetes ee + run: | + cd scripts/helmcharts/ + + ## Update secerts + sed -i "s/postgresqlPassword: \"changeMePassword\"/postgresqlPassword: \"${{ secrets.EE_PG_PASSWORD }}\"/g" vars.yaml + sed -i "s/accessKey: \"changeMeMinioAccessKey\"/accessKey: \"${{ secrets.EE_MINIO_ACCESS_KEY }}\"/g" vars.yaml + sed -i "s/secretKey: \"changeMeMinioPassword\"/secretKey: \"${{ secrets.EE_MINIO_SECRET_KEY }}\"/g" vars.yaml + sed -i "s/jwt_secret: \"SetARandomStringHere\"/jwt_secret: \"${{ secrets.EE_JWT_SECRET }}\"/g" vars.yaml + sed -i "s/domainName: \"\"/domainName: \"${{ secrets.EE_DOMAIN_NAME }}\"/g" vars.yaml + sed -i "s/enterpriseEditionLicense: \"\"/enterpriseEditionLicense: \"${{ secrets.EE_LICENSE_KEY }}\"/g" vars.yaml + + # Update changed image tag + sed -i "/frontend/{n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml + + cat /tmp/image_override.yaml + # Deploy command + helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set skipMigration=true + env: + DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} + # We're not passing -ee flag, because helm will add that. + IMAGE_TAG: ${{ github.sha }} + ENVIRONMENT: staging + # - name: Debug Job # if: ${{ failure() }} # uses: mxschmitt/action-tmate@v3 diff --git a/.github/workflows/workers-ee.yaml b/.github/workflows/workers-ee.yaml index e42089602..d7bd551ed 100644 --- a/.github/workflows/workers-ee.yaml +++ b/.github/workflows/workers-ee.yaml @@ -35,10 +35,10 @@ jobs: kubeconfig: ${{ secrets.EE_KUBECONFIG }} # Use content of kubeconfig in secret. id: setcontext - # Caching docker images - - uses: satackey/action-docker-layer-caching@v0.0.11 - # Ignore the failure of a step and avoid terminating the job. - continue-on-error: true + # # Caching docker images + # - uses: satackey/action-docker-layer-caching@v0.0.11 + # # Ignore the failure of a step and avoid terminating the job. + # continue-on-error: true - name: Build, tag id: build-image @@ -125,7 +125,7 @@ jobs: cat /tmp/image_override.yaml # Deploy command - helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml + helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set skipMigration=true # - name: Debug Job # if: ${{ failure() }} diff --git a/.github/workflows/workers.yaml b/.github/workflows/workers.yaml index f84909a39..871415e45 100644 --- a/.github/workflows/workers.yaml +++ b/.github/workflows/workers.yaml @@ -119,7 +119,7 @@ jobs: done # Deploy command - helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml + helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set skipMigration=true # - name: Debug Job # if: ${{ failure() }} diff --git a/backend/internal/config/integrations/config.go b/backend/internal/config/integrations/config.go index 290acde1d..86ba7698d 100644 --- a/backend/internal/config/integrations/config.go +++ b/backend/internal/config/integrations/config.go @@ -3,9 +3,9 @@ package integrations import "openreplay/backend/pkg/env" type Config struct { - TopicRawWeb string - PostgresURI string - TokenSecret string + TopicAnalytics string + PostgresURI string + TokenSecret string } func New() *Config { diff --git a/backend/internal/http/router/handlers-web.go b/backend/internal/http/router/handlers-web.go index 69cbe4675..a8b47c559 100644 --- a/backend/internal/http/router/handlers-web.go +++ b/backend/internal/http/router/handlers-web.go @@ -118,10 +118,14 @@ func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) } // Save sessionStart to db - e.services.Database.InsertWebSessionStart(sessionID, sessionStart) + if err := e.services.Database.InsertWebSessionStart(sessionID, sessionStart); err != nil { + log.Printf("can't insert session start: %s", err) + } // Send sessionStart message to kafka - e.services.Producer.Produce(e.cfg.TopicRawWeb, tokenData.ID, Encode(sessionStart)) + if err := e.services.Producer.Produce(e.cfg.TopicRawWeb, tokenData.ID, Encode(sessionStart)); err != nil { + log.Printf("can't send session start: %s", err) + } } ResponseWithJSON(w, &StartSessionResponse{ diff --git a/backend/pkg/db/postgres/messages-web-stats.go b/backend/pkg/db/postgres/messages-web-stats.go index 2a5a11750..396f2e74d 100644 --- a/backend/pkg/db/postgres/messages-web-stats.go +++ b/backend/pkg/db/postgres/messages-web-stats.go @@ -1,7 +1,6 @@ package postgres import ( - "log" . "openreplay/backend/pkg/messages" "openreplay/backend/pkg/url" ) @@ -28,25 +27,16 @@ func (conn *Conn) InsertWebStatsPerformance(sessionID uint64, p *PerformanceTrac $10, $11, $12, $13, $14, $15 )` - //conn.batchQueue(sessionID, sqlRequest, - // sessionID, timestamp, timestamp, // ??? TODO: primary key by timestamp+session_id - // p.MinFPS, p.AvgFPS, p.MaxFPS, - // p.MinCPU, p.AvgCPU, p.MinCPU, - // p.MinTotalJSHeapSize, p.AvgTotalJSHeapSize, p.MaxTotalJSHeapSize, - // p.MinUsedJSHeapSize, p.AvgUsedJSHeapSize, p.MaxUsedJSHeapSize, - //) - if err := conn.exec(sqlRequest, + conn.batchQueue(sessionID, sqlRequest, sessionID, timestamp, timestamp, // ??? TODO: primary key by timestamp+session_id p.MinFPS, p.AvgFPS, p.MaxFPS, p.MinCPU, p.AvgCPU, p.MinCPU, p.MinTotalJSHeapSize, p.AvgTotalJSHeapSize, p.MaxTotalJSHeapSize, p.MinUsedJSHeapSize, p.AvgUsedJSHeapSize, p.MaxUsedJSHeapSize, - ); err != nil { - log.Printf("can't insert perf: %s", err) - } + ) // Record approximate message size - //conn.updateBatchSize(sessionID, len(sqlRequest)+8*15) + conn.updateBatchSize(sessionID, len(sqlRequest)+8*15) return nil } diff --git a/backend/pkg/messages/batch.go b/backend/pkg/messages/batch.go index 2d531c429..84f6cde99 100644 --- a/backend/pkg/messages/batch.go +++ b/backend/pkg/messages/batch.go @@ -40,7 +40,8 @@ func ReadBatchReader(reader io.Reader, messageHandler func(Message)) error { // No skipping here for making it easy to encode back the same sequence of message // continue readLoop case *SessionStart: - // Save session start timestamp for collecting "empty" sessions + timestamp = int64(m.Timestamp) + case *SessionEnd: timestamp = int64(m.Timestamp) } msg.Meta().Index = index diff --git a/backend/pkg/sessions/builder.go b/backend/pkg/sessions/builder.go index eed3d8229..c9cb0b6dd 100644 --- a/backend/pkg/sessions/builder.go +++ b/backend/pkg/sessions/builder.go @@ -50,7 +50,7 @@ func (b *builder) handleMessage(message Message, messageID uint64) { timestamp := GetTimestamp(message) if timestamp == 0 { switch message.(type) { - case *SessionEnd, *IssueEvent, *PerformanceTrackAggr: + case *IssueEvent, *PerformanceTrackAggr: break default: log.Printf("skip message with empty timestamp, sessID: %d, msgID: %d, msgType: %d", b.sessionID, messageID, message.TypeID()) diff --git a/ee/backend/internal/datasaver/stats.go b/ee/backend/internal/db/datasaver/stats.go similarity index 100% rename from ee/backend/internal/datasaver/stats.go rename to ee/backend/internal/db/datasaver/stats.go diff --git a/ee/backend/pkg/db/clickhouse/connector.go b/ee/backend/pkg/db/clickhouse/connector.go index 532389425..cc0d20497 100644 --- a/ee/backend/pkg/db/clickhouse/connector.go +++ b/ee/backend/pkg/db/clickhouse/connector.go @@ -9,23 +9,16 @@ import ( ) type Connector struct { - sessionsIOS *bulk - //viewsIOS *bulk - clicksIOS *bulk - inputsIOS *bulk - crashesIOS *bulk - performanceIOS *bulk - resourcesIOS *bulk - sessions *bulk - metadata *bulk // TODO: join sessions, sessions_metadata & sessions_ios - resources *bulk - pages *bulk - clicks *bulk - inputs *bulk - errors *bulk - performance *bulk - longtasks *bulk - db *sql.DB + sessions *bulk + metadata *bulk // TODO: join sessions, sessions_metadata & sessions_ios + resources *bulk + pages *bulk + clicks *bulk + inputs *bulk + errors *bulk + performance *bulk + longtasks *bulk + db *sql.DB } func NewConnector(url string) *Connector { @@ -37,35 +30,6 @@ func NewConnector(url string) *Connector { } return &Connector{ db: db, - // sessionsIOS: newBulk(db, ` - // INSERT INTO sessions_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, duration, views_count, events_count, crashes_count, metadata_1, metadata_2, metadata_3, metadata_4, metadata_5, metadata_6, metadata_7, metadata_8, metadata_9, metadata_10) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - // `), - // viewsIOS: newBulk(db, ` - // INSERT INTO views_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, url, request_start, response_start, response_end, dom_content_loaded_event_start, dom_content_loaded_event_end, load_event_start, load_event_end, first_paint, first_contentful_paint, speed_index, visually_complete, time_to_interactive) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - // `), - // clicksIOS: newBulk(db, ` - // INSERT INTO clicks_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, label) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - // `), - // inputsIOS: newBulk(db, ` - // INSERT INTO inputs_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, label) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - // `), - // crashesIOS: newBulk(db, ` - // INSERT INTO crashes_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, name, reason, crash_id) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - // `), - // performanceIOS: newBulk(db, ` - // INSERT INTO performance_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_memory, avg_memory, max_memory) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - // `), - // resourcesIOS: newBulk(db, ` - // INSERT INTO resources_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, url, duration, body_size, success, method, status) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, nullIf(?, ''), ?) - // `), - sessions: newBulk(db, ` INSERT INTO sessions (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, duration, pages_count, events_count, errors_count, user_browser, user_browser_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) @@ -107,27 +71,6 @@ func NewConnector(url string) *Connector { } func (conn *Connector) Prepare() error { - // if err := conn.sessionsIOS.prepare(); err != nil { - // return err - // } - // // if err := conn.viewsIOS.prepare(); err != nil { - // // return err - // // } - // if err := conn.clicksIOS.prepare(); err != nil { - // return err - // } - // if err := conn.inputsIOS.prepare(); err != nil { - // return err - // } - // if err := conn.crashesIOS.prepare(); err != nil { - // return err - // } - // if err := conn.performanceIOS.prepare(); err != nil { - // return err - // } - // if err := conn.resourcesIOS.prepare(); err != nil { - // return err - // } if err := conn.sessions.prepare(); err != nil { return err } @@ -159,27 +102,6 @@ func (conn *Connector) Prepare() error { } func (conn *Connector) Commit() error { - // if err := conn.sessionsIOS.commit(); err != nil { - // return err - // } - // // if err := conn.viewsIOS.commit(); err != nil { - // // return err - // // } - // if err := conn.clicksIOS.commit(); err != nil { - // return err - // } - // if err := conn.inputsIOS.commit(); err != nil { - // return err - // } - // if err := conn.crashesIOS.commit(); err != nil { - // return err - // } - // if err := conn.performanceIOS.commit(); err != nil { - // return err - // } - // if err := conn.resourcesIOS.commit(); err != nil { - // return err - // } if err := conn.sessions.commit(); err != nil { return err } diff --git a/ee/backend/pkg/db/clickhouse/messages-ios.go b/ee/backend/pkg/db/clickhouse/messages-ios.go index f5ca30495..1c7723b2b 100644 --- a/ee/backend/pkg/db/clickhouse/messages-ios.go +++ b/ee/backend/pkg/db/clickhouse/messages-ios.go @@ -2,46 +2,41 @@ package clickhouse import ( "errors" - - "openreplay/backend/pkg/hashid" - "openreplay/backend/pkg/url" - . "openreplay/backend/pkg/db/types" - . "openreplay/backend/pkg/messages" ) - // TODO: join sessions & sessions_ios clcikhouse tables func (conn *Connector) InsertIOSSession(session *Session) error { - if (session.Duration == nil) { + if session.Duration == nil { return errors.New("Clickhouse: trying to insert session with ") } - return conn.sessionsIOS.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(session.Timestamp), - uint32(*session.Duration), - session.PagesCount, - session.EventsCount, - session.ErrorsCount, - session.Metadata1, - session.Metadata2, - session.Metadata3, - session.Metadata4, - session.Metadata5, - session.Metadata6, - session.Metadata7, - session.Metadata8, - session.Metadata9, - session.Metadata10, - ) + //return conn.sessionsIOS.exec( + // session.SessionID, + // session.ProjectID, + // session.TrackerVersion, + // nullableString(session.RevID), + // session.UserUUID, + // session.UserOS, + // nullableString(session.UserOSVersion), + // nullableString(session.UserDevice), + // session.UserDeviceType, + // session.UserCountry, + // datetime(session.Timestamp), + // uint32(*session.Duration), + // session.PagesCount, + // session.EventsCount, + // session.ErrorsCount, + // session.Metadata1, + // session.Metadata2, + // session.Metadata3, + // session.Metadata4, + // session.Metadata5, + // session.Metadata6, + // session.Metadata7, + // session.Metadata8, + // session.Metadata9, + // session.Metadata10, + //) + return nil } // func (conn *Connector) IOSScreenEnter(session *Session, msg *PageEvent) error { @@ -76,105 +71,110 @@ func (conn *Connector) InsertIOSClickEvent(session *Session, msg *IOSClickEvent) if msg.Label == "" { return nil } - return conn.clicksIOS.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - msg.Label, - ) + //return conn.clicksIOS.exec( + // session.SessionID, + // session.ProjectID, + // session.TrackerVersion, + // nullableString(session.RevID), + // session.UserUUID, + // session.UserOS, + // nullableString(session.UserOSVersion), + // nullableString(session.UserDevice), + // session.UserDeviceType, + // session.UserCountry, + // datetime(msg.Timestamp), + // msg.Label, + //) + return nil } func (conn *Connector) InsertIOSInputEvent(session *Session, msg *IOSInputEvent) error { if msg.Label == "" { return nil } - return conn.inputsIOS.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - msg.Label, - ) + //return conn.inputsIOS.exec( + // session.SessionID, + // session.ProjectID, + // session.TrackerVersion, + // nullableString(session.RevID), + // session.UserUUID, + // session.UserOS, + // nullableString(session.UserOSVersion), + // nullableString(session.UserDevice), + // session.UserDeviceType, + // session.UserCountry, + // datetime(msg.Timestamp), + // msg.Label, + //) + return nil } func (conn *Connector) InsertIOSCrash(session *Session, msg *IOSCrash) error { - return conn.crashesIOS.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - msg.Name, - msg.Reason, - hashid.IOSCrashID(session.ProjectID, msg), - ) + //return conn.crashesIOS.exec( + // session.SessionID, + // session.ProjectID, + // session.TrackerVersion, + // nullableString(session.RevID), + // session.UserUUID, + // session.UserOS, + // nullableString(session.UserOSVersion), + // nullableString(session.UserDevice), + // session.UserDeviceType, + // session.UserCountry, + // datetime(msg.Timestamp), + // msg.Name, + // msg.Reason, + // hashid.IOSCrashID(session.ProjectID, msg), + //) + return nil } func (conn *Connector) InsertIOSNetworkCall(session *Session, msg *IOSNetworkCall) error { - return conn.resourcesIOS.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - url.DiscardURLQuery(msg.URL), - nullableUint16(uint16(msg.Duration)), - nullableUint32(uint32(len(msg.Body))), - msg.Success, - url.EnsureMethod(msg.Method), // nullableString causes error "unexpected type *string" - nullableUint16(uint16(msg.Status)), - ) + //return conn.resourcesIOS.exec( + // session.SessionID, + // session.ProjectID, + // session.TrackerVersion, + // nullableString(session.RevID), + // session.UserUUID, + // session.UserOS, + // nullableString(session.UserOSVersion), + // nullableString(session.UserDevice), + // session.UserDeviceType, + // session.UserCountry, + // datetime(msg.Timestamp), + // url.DiscardURLQuery(msg.URL), + // nullableUint16(uint16(msg.Duration)), + // nullableUint32(uint32(len(msg.Body))), + // msg.Success, + // url.EnsureMethod(msg.Method), // nullableString causes error "unexpected type *string" + // nullableUint16(uint16(msg.Status)), + //) + return nil } func (conn *Connector) InsertIOSPerformanceAggregated(session *Session, msg *IOSPerformanceAggregated) error { - var timestamp uint64 = (msg.TimestampStart + msg.TimestampEnd) / 2 - return conn.performanceIOS.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(timestamp), - uint8(msg.MinFPS), - uint8(msg.AvgFPS), - uint8(msg.MaxFPS), - uint8(msg.MinCPU), - uint8(msg.AvgCPU), - uint8(msg.MaxCPU), - msg.MinMemory, - msg.AvgMemory, - msg.MaxMemory, - ) + //var timestamp uint64 = (msg.TimestampStart + msg.TimestampEnd) / 2 + //return conn.performanceIOS.exec( + // session.SessionID, + // session.ProjectID, + // session.TrackerVersion, + // nullableString(session.RevID), + // session.UserUUID, + // session.UserOS, + // nullableString(session.UserOSVersion), + // nullableString(session.UserDevice), + // session.UserDeviceType, + // session.UserCountry, + // datetime(timestamp), + // uint8(msg.MinFPS), + // uint8(msg.AvgFPS), + // uint8(msg.MaxFPS), + // uint8(msg.MinCPU), + // uint8(msg.AvgCPU), + // uint8(msg.MaxCPU), + // msg.MinMemory, + // msg.AvgMemory, + // msg.MaxMemory, + //) + return nil } diff --git a/frontend/.env.sample b/frontend/.env.sample index 85d169876..acfc72ad6 100644 --- a/frontend/.env.sample +++ b/frontend/.env.sample @@ -12,7 +12,7 @@ SENTRY_URL = '' # CAPTCHA CAPTCHA_ENABLED = false -CAPTCHA_SITE_KEY = 'asdad' +CAPTCHA_SITE_KEY = '' # MINIO MINIO_ENDPOINT = '' @@ -22,5 +22,5 @@ MINIO_ACCESS_KEY = '' MINIO_SECRET_KEY = '' # APP and TRACKER VERSIONS -VERSION = '1.6.0' -TRACKER_VERSION = '3.5.10' +VERSION = '1.7.0' +TRACKER_VERSION = '3.5.12' diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 000000000..5a938ce18 --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "useTabs": false +} diff --git a/frontend/app/api_client.js b/frontend/app/api_client.js index 918fe57e2..1f85d5af9 100644 --- a/frontend/app/api_client.js +++ b/frontend/app/api_client.js @@ -25,7 +25,6 @@ const siteIdRequiredPaths = [ '/custom_metrics', '/dashboards', '/metrics', - '/trails', // '/custom_metrics/sessions', ]; diff --git a/frontend/app/components/Client/Integrations/Integrations.js b/frontend/app/components/Client/Integrations/Integrations.js index 2f4ed073f..b4a421980 100644 --- a/frontend/app/components/Client/Integrations/Integrations.js +++ b/frontend/app/components/Client/Integrations/Integrations.js @@ -1,36 +1,36 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import withPageTitle from 'HOCs/withPageTitle'; -import { Loader, IconButton, Button, Icon, SlideModal } from 'UI'; -import { fetchList as fetchListSlack } from 'Duck/integrations/slack'; -import { remove as removeIntegrationConfig } from 'Duck/integrations/actions'; -import { fetchList, init } from 'Duck/integrations/actions'; -import cn from 'classnames'; +import React from "react"; +import { connect } from "react-redux"; +import withPageTitle from "HOCs/withPageTitle"; +import { Loader, IconButton, SlideModal } from "UI"; +import { fetchList as fetchListSlack } from "Duck/integrations/slack"; +import { remove as removeIntegrationConfig } from "Duck/integrations/actions"; +import { fetchList, init } from "Duck/integrations/actions"; +import cn from "classnames"; -import IntegrationItem from './IntegrationItem'; -import SentryForm from './SentryForm'; -import GithubForm from './GithubForm'; -import SlackForm from './SlackForm'; -import DatadogForm from './DatadogForm'; -import StackdriverForm from './StackdriverForm'; -import RollbarForm from './RollbarForm'; -import NewrelicForm from './NewrelicForm'; -import BugsnagForm from './BugsnagForm'; -import CloudwatchForm from './CloudwatchForm'; -import ElasticsearchForm from './ElasticsearchForm'; -import SumoLogicForm from './SumoLogicForm'; -import JiraForm from './JiraForm'; -import styles from './integrations.module.css'; -import ReduxDoc from './ReduxDoc'; -import VueDoc from './VueDoc'; -import GraphQLDoc from './GraphQLDoc'; -import NgRxDoc from './NgRxDoc/NgRxDoc'; -import SlackAddForm from './SlackAddForm'; -import FetchDoc from './FetchDoc'; -import MobxDoc from './MobxDoc'; -import ProfilerDoc from './ProfilerDoc'; -import AssistDoc from './AssistDoc'; -import AxiosDoc from './AxiosDoc/AxiosDoc'; +import IntegrationItem from "./IntegrationItem"; +import SentryForm from "./SentryForm"; +import GithubForm from "./GithubForm"; +import SlackForm from "./SlackForm"; +import DatadogForm from "./DatadogForm"; +import StackdriverForm from "./StackdriverForm"; +import RollbarForm from "./RollbarForm"; +import NewrelicForm from "./NewrelicForm"; +import BugsnagForm from "./BugsnagForm"; +import CloudwatchForm from "./CloudwatchForm"; +import ElasticsearchForm from "./ElasticsearchForm"; +import SumoLogicForm from "./SumoLogicForm"; +import JiraForm from "./JiraForm"; +import styles from "./integrations.module.css"; +import ReduxDoc from "./ReduxDoc"; +import VueDoc from "./VueDoc"; +import GraphQLDoc from "./GraphQLDoc"; +import NgRxDoc from "./NgRxDoc/NgRxDoc"; +import SlackAddForm from "./SlackAddForm"; +import FetchDoc from "./FetchDoc"; +import MobxDoc from "./MobxDoc"; +import ProfilerDoc from "./ProfilerDoc"; +import AssistDoc from "./AssistDoc"; +import AxiosDoc from "./AxiosDoc/AxiosDoc"; const NONE = -1; const SENTRY = 0; @@ -56,468 +56,578 @@ const ASSIST = 19; const AXIOS = 20; const TITLE = { - [ SENTRY ]: 'Sentry', - [ SLACK ]: 'Slack', - [ DATADOG ]: 'Datadog', - [ STACKDRIVER ]: 'Stackdriver', - [ ROLLBAR ]: 'Rollbar', - [ NEWRELIC ]: 'New Relic', - [ BUGSNAG ]: 'Bugsnag', - [ CLOUDWATCH ]: 'CloudWatch', - [ ELASTICSEARCH ]: 'Elastic Search', - [ SUMOLOGIC ]: 'Sumo Logic', - [ JIRA ]: 'Jira', - [ GITHUB ]: 'Github', - [ REDUX ] : 'Redux', - [ VUE ] : 'VueX', - [ GRAPHQL ] : 'GraphQL', - [ NGRX ] : 'NgRx', - [ FETCH ] : 'Fetch', - [ MOBX ] : 'MobX', - [ PROFILER ] : 'Profiler', - [ ASSIST ] : 'Assist', - [ AXIOS ] : 'Axios', -} + [SENTRY]: "Sentry", + [SLACK]: "Slack", + [DATADOG]: "Datadog", + [STACKDRIVER]: "Stackdriver", + [ROLLBAR]: "Rollbar", + [NEWRELIC]: "New Relic", + [BUGSNAG]: "Bugsnag", + [CLOUDWATCH]: "CloudWatch", + [ELASTICSEARCH]: "Elastic Search", + [SUMOLOGIC]: "Sumo Logic", + [JIRA]: "Jira", + [GITHUB]: "Github", + [REDUX]: "Redux", + [VUE]: "VueX", + [GRAPHQL]: "GraphQL", + [NGRX]: "NgRx", + [FETCH]: "Fetch", + [MOBX]: "MobX", + [PROFILER]: "Profiler", + [ASSIST]: "Assist", + [AXIOS]: "Axios", +}; -const DOCS = [REDUX, VUE, GRAPHQL, NGRX, FETCH, MOBX, PROFILER, ASSIST] +const DOCS = [REDUX, VUE, GRAPHQL, NGRX, FETCH, MOBX, PROFILER, ASSIST]; -const integrations = [ 'sentry', 'datadog', 'stackdriver', 'rollbar', 'newrelic', 'bugsnag', 'cloudwatch', 'elasticsearch', 'sumologic', 'issues' ]; +const integrations = [ + "sentry", + "datadog", + "stackdriver", + "rollbar", + "newrelic", + "bugsnag", + "cloudwatch", + "elasticsearch", + "sumologic", + "issues", +]; -@connect(state => { - const props = {}; - integrations.forEach(name => { - props[ `${ name }Integrated`] = name === 'issues' ? - !!(state.getIn([ name, 'list' ]).first() && state.getIn([ name, 'list' ]).first().token) : - state.getIn([ name, 'list' ]).size > 0; - props.loading = props.loading || state.getIn([ name, 'fetchRequest', 'loading']); - }) - const site = state.getIn([ 'site', 'instance' ]); - return { - ...props, - issues: state.getIn([ 'issues', 'list']).first() || {}, - slackChannelListExists: state.getIn([ 'slack', 'list' ]).size > 0, - tenantId: state.getIn([ 'user', 'account', 'tenantId' ]), - jwt: state.get('jwt'), - projectKey: site ? site.projectKey : '' - }; -}, { - fetchList, - init, - fetchListSlack, - removeIntegrationConfig -}) -@withPageTitle('Integrations - OpenReplay Preferences') -export default class Integrations extends React.PureComponent { - state = { - modalContent: NONE, - showDetailContent: false, - }; - - componentWillMount() { - integrations.forEach(name => - this.props.fetchList(name) - ); - this.props.fetchListSlack(); - } - - onClickIntegrationItem = (e, url) => { - e.preventDefault(); - window.open(url); - } - - closeModal = () => this.setState({ modalContent: NONE, showDetailContent: false }); - - onOauthClick = (source) => { - if (source === GITHUB) { - const githubUrl = `https://auth.openreplay.com/oauth/login?provider=github&back_url=${document.location.href}`; - const options = { - method: 'GET', - credentials: 'include', - headers: new Headers({ - 'Authorization': 'Bearer ' + this.props.jwt.toString() - }) - }; - fetch(githubUrl, options).then((resp) => resp.text().then((txt) => window.open(txt, '_self'))) - } - } - - renderDetailContent() { - switch (this.state.modalContent) { - case SLACK: - return this.setState({ showDetailContent: false }) } />; - } - } - - renderModalContent() { - const { projectKey } = this.props; - - switch (this.state.modalContent) { - case SENTRY: - return ; - case GITHUB: - return ; - case SLACK: - return this.setState({ showDetailContent: true })} - /> - case DATADOG: - return ; - case STACKDRIVER: - return ; - case ROLLBAR: - return ; - case NEWRELIC: - return ; - case BUGSNAG: - return ; - case CLOUDWATCH: - return ; - case ELASTICSEARCH: - return ; - case SUMOLOGIC: - return ; - case JIRA: - return ; - case REDUX: - return - case VUE: - return - case GRAPHQL: - return - case NGRX: - return - case FETCH: - return - case MOBX: - return - case PROFILER: - return - case ASSIST: - return - case AXIOS: - return - default: - return null; +@connect( + (state) => { + const props = {}; + integrations.forEach((name) => { + props[`${name}Integrated`] = + name === "issues" + ? !!( + state.getIn([name, "list"]).first() && + state.getIn([name, "list"]).first().token + ) + : state.getIn([name, "list"]).size > 0; + props.loading = + props.loading || state.getIn([name, "fetchRequest", "loading"]); + }); + const site = state.getIn(["site", "instance"]); + return { + ...props, + issues: state.getIn(["issues", "list"]).first() || {}, + slackChannelListExists: state.getIn(["slack", "list"]).size > 0, + tenantId: state.getIn(["user", "account", "tenantId"]), + jwt: state.get("jwt"), + projectKey: site ? site.projectKey : "", + }; + }, + { + fetchList, + init, + fetchListSlack, + removeIntegrationConfig, } - } +) +@withPageTitle("Integrations - OpenReplay Preferences") +export default class Integrations extends React.PureComponent { + state = { + modalContent: NONE, + showDetailContent: false, + }; - deleteHandler = name => { - this.props.removeIntegrationConfig(name, null).then(function() { - this.props.fetchList(name) - }.bind(this)); - } - - showIntegrationConfig = (type) => { - this.setState({ modalContent: type }); - } + componentWillMount() { + integrations.forEach((name) => this.props.fetchList(name)); + this.props.fetchListSlack(); + } - render() { - const { - loading, - sentryIntegrated, - stackdriverIntegrated, - datadogIntegrated, - rollbarIntegrated, - newrelicIntegrated, - bugsnagIntegrated, - cloudwatchIntegrated, - elasticsearchIntegrated, - sumologicIntegrated, - hideHeader=false, - plugins=false, - jiraIntegrated, - issuesIntegrated, - tenantId, - slackChannelListExists, - issues, - } = this.props; - const { modalContent, showDetailContent } = this.state; - return ( -
- -
{TITLE[ modalContent ]}
- { modalContent === SLACK && ( - this.setState({ showDetailContent: true })} - /> - )} -
- } - isDisplayed={ modalContent !== NONE } - onClose={ this.closeModal } - size={ DOCS.includes(this.state.modalContent) ? 'middle' : 'small' } - content={ this.renderModalContent() } - detailContent={ showDetailContent && this.renderDetailContent() } - /> + onClickIntegrationItem = (e, url) => { + e.preventDefault(); + window.open(url); + }; - {!hideHeader && ( -
-

{ 'Integrations' }

-

Power your workflow with your favourite tools.

-
-
- )} + closeModal = () => + this.setState({ modalContent: NONE, showDetailContent: false }); - {plugins && ( -
-
Use plugins to better debug your application's store, monitor queries and track performance issues.
-
- this.showIntegrationConfig(REDUX) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(VUE) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(GRAPHQL) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(NGRX) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(MOBX) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(FETCH) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(PROFILER) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(AXIOS) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(ASSIST) } - // integrated={ sentryIntegrated } - /> -
-
- )} + onOauthClick = (source) => { + if (source === GITHUB) { + const githubUrl = `https://auth.openreplay.com/oauth/login?provider=github&back_url=${document.location.href}`; + const options = { + method: "GET", + credentials: "include", + headers: new Headers({ + Authorization: "Bearer " + this.props.jwt.toString(), + }), + }; + fetch(githubUrl, options).then((resp) => + resp.text().then((txt) => window.open(txt, "_self")) + ); + } + }; - {!plugins && ( - -
-
-
How are you monitoring errors and crash reporting?
-
- { - (!issues.token || issues.provider !== 'github') && - this.showIntegrationConfig(JIRA) } - integrated={ issuesIntegrated } - /> - } - { (!issues.token || issues.provider !== 'jira') && - this.showIntegrationConfig(GITHUB) } - integrated={ issuesIntegrated } - deleteHandler={issuesIntegrated ? () => this.deleteHandler('issues') : null} - /> - } - {/* this.showIntegrationConfig(GITHUB) } - integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(JIRA) } - integrated={ sentryIntegrated } - /> */} - this.showIntegrationConfig(SLACK) } - integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(SENTRY) } - integrated={ sentryIntegrated } - /> + renderDetailContent() { + switch (this.state.modalContent) { + case SLACK: + return ( + + this.setState({ showDetailContent: false }) + } + /> + ); + } + } - this.showIntegrationConfig(BUGSNAG) } - integrated={ bugsnagIntegrated } - /> + renderModalContent() { + const { projectKey } = this.props; - this.showIntegrationConfig(ROLLBAR) } - integrated={ rollbarIntegrated } - /> + switch (this.state.modalContent) { + case SENTRY: + return ; + case GITHUB: + return ; + case SLACK: + return ( + + this.setState({ showDetailContent: true }) + } + /> + ); + case DATADOG: + return ; + case STACKDRIVER: + return ; + case ROLLBAR: + return ; + case NEWRELIC: + return ; + case BUGSNAG: + return ; + case CLOUDWATCH: + return ; + case ELASTICSEARCH: + return ; + case SUMOLOGIC: + return ; + case JIRA: + return ; + case REDUX: + return ( + + ); + case VUE: + return ( + + ); + case GRAPHQL: + return ( + + ); + case NGRX: + return ( + + ); + case FETCH: + return ( + + ); + case MOBX: + return ( + + ); + case PROFILER: + return ( + + ); + case ASSIST: + return ( + + ); + case AXIOS: + return ( + + ); + default: + return null; + } + } - this.showIntegrationConfig(ELASTICSEARCH) } - integrated={ elasticsearchIntegrated } - /> + deleteHandler = (name) => { + this.props.removeIntegrationConfig(name, null).then( + function () { + this.props.fetchList(name); + }.bind(this) + ); + }; - this.showIntegrationConfig(DATADOG) } - integrated={ datadogIntegrated } - /> - this.showIntegrationConfig(SUMOLOGIC) } - integrated={ sumologicIntegrated } - /> - this.showIntegrationConfig(STACKDRIVER) } - integrated={ stackdriverIntegrated } - /> + showIntegrationConfig = (type) => { + this.setState({ modalContent: type }); + }; - this.showIntegrationConfig(CLOUDWATCH) } - integrated={ cloudwatchIntegrated } - /> + render() { + const { + loading, + sentryIntegrated, + stackdriverIntegrated, + datadogIntegrated, + rollbarIntegrated, + newrelicIntegrated, + bugsnagIntegrated, + cloudwatchIntegrated, + elasticsearchIntegrated, + sumologicIntegrated, + hideHeader = false, + plugins = false, + jiraIntegrated, + issuesIntegrated, + tenantId, + slackChannelListExists, + issues, + } = this.props; + const { modalContent, showDetailContent } = this.state; + return ( +
+ +
{TITLE[modalContent]}
+ {modalContent === SLACK && ( + + this.setState({ + showDetailContent: true, + }) + } + /> + )} +
+ } + isDisplayed={modalContent !== NONE} + onClose={this.closeModal} + size={ + DOCS.includes(this.state.modalContent) + ? "middle" + : "small" + } + content={this.renderModalContent()} + detailContent={ + showDetailContent && this.renderDetailContent() + } + /> - this.showIntegrationConfig(NEWRELIC) } - integrated={ newrelicIntegrated } - /> -
-
- - {/*
-
How are you logging backend errors?
-
- -
-
*/} - {/* - - */} -
-
- )} -
- ); - } -} \ No newline at end of file + {!hideHeader && ( +
+

+ {"Integrations"} +

+

+ Power your workflow with your favourite tools. +

+
+
+ )} + + {plugins && ( +
+
+ Use plugins to better debug your application's + store, monitor queries and track performance issues. +
+
+ + this.showIntegrationConfig(REDUX) + } + // integrated={ sentryIntegrated } + /> + this.showIntegrationConfig(VUE)} + // integrated={ sentryIntegrated } + /> + + this.showIntegrationConfig(GRAPHQL) + } + // integrated={ sentryIntegrated } + /> + this.showIntegrationConfig(NGRX)} + // integrated={ sentryIntegrated } + /> + this.showIntegrationConfig(MOBX)} + // integrated={ sentryIntegrated } + /> + + this.showIntegrationConfig(FETCH) + } + // integrated={ sentryIntegrated } + /> + + this.showIntegrationConfig(PROFILER) + } + // integrated={ sentryIntegrated } + /> + + this.showIntegrationConfig(AXIOS) + } + // integrated={ sentryIntegrated } + /> + + this.showIntegrationConfig(ASSIST) + } + // integrated={ sentryIntegrated } + /> +
+
+ )} + + {!plugins && ( + +
+
+
+ How are you monitoring errors and crash + reporting? +
+
+ {(!issues.token || + issues.provider !== "github") && ( + + this.showIntegrationConfig(JIRA) + } + integrated={issuesIntegrated} + /> + )} + {(!issues.token || + issues.provider !== "jira") && ( + + this.showIntegrationConfig( + GITHUB + ) + } + integrated={issuesIntegrated} + deleteHandler={ + issuesIntegrated + ? () => + this.deleteHandler( + "issues" + ) + : null + } + /> + )} + + + this.showIntegrationConfig(SLACK) + } + integrated={sentryIntegrated} + /> + + this.showIntegrationConfig(SENTRY) + } + integrated={sentryIntegrated} + /> + + + this.showIntegrationConfig(BUGSNAG) + } + integrated={bugsnagIntegrated} + /> + + + this.showIntegrationConfig(ROLLBAR) + } + integrated={rollbarIntegrated} + /> + + + this.showIntegrationConfig( + ELASTICSEARCH + ) + } + integrated={elasticsearchIntegrated} + /> + + + this.showIntegrationConfig(DATADOG) + } + integrated={datadogIntegrated} + /> + + this.showIntegrationConfig( + SUMOLOGIC + ) + } + integrated={sumologicIntegrated} + /> + + this.showIntegrationConfig( + STACKDRIVER + ) + } + integrated={stackdriverIntegrated} + /> + + + this.showIntegrationConfig( + CLOUDWATCH + ) + } + integrated={cloudwatchIntegrated} + /> + + + this.showIntegrationConfig(NEWRELIC) + } + integrated={newrelicIntegrated} + /> +
+
+
+
+ )} +
+ ); + } +} diff --git a/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js b/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js index e854dfce2..f78527204 100644 --- a/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js +++ b/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js @@ -16,9 +16,10 @@ function SlackChannelList(props) {
-
Integrate Slack with OpenReplay and share insights with the rest of the team, directly from the recording page.
- +
+
Integrate Slack with OpenReplay and share insights with the rest of the team, directly from the recording page.
+ {/* */} +
} size="small" diff --git a/frontend/app/components/Client/ManageUsers/ManageUsers.js b/frontend/app/components/Client/ManageUsers/ManageUsers.js index 010b8cdea..dd703813e 100644 --- a/frontend/app/components/Client/ManageUsers/ManageUsers.js +++ b/frontend/app/components/Client/ManageUsers/ManageUsers.js @@ -24,7 +24,7 @@ const LIMIT_WARNING = 'You have reached users limit.'; errors: state.getIn([ 'members', 'saveRequest', 'errors' ]), loading: state.getIn([ 'members', 'loading' ]), saving: state.getIn([ 'members', 'saveRequest', 'loading' ]), - roles: state.getIn(['roles', 'list']).filter(r => !r.protected).map(r => ({ text: r.name, value: r.roleId })).toJS(), + roles: state.getIn(['roles', 'list']).filter(r => !r.protected).map(r => ({ label: r.name, value: r.roleId })).toJS(), isEnterprise: state.getIn([ 'user', 'account', 'edition' ]) === 'ee', }), { init, diff --git a/frontend/app/components/Client/Roles/Roles.tsx b/frontend/app/components/Client/Roles/Roles.tsx index 831477b90..f9b9ef072 100644 --- a/frontend/app/components/Client/Roles/Roles.tsx +++ b/frontend/app/components/Client/Roles/Roles.tsx @@ -109,10 +109,11 @@ function Roles(props: Props) { icon >
-
-
Title
-
Project Access
-
Feature Access
+
+
Title
+
Project Access
+
Feature Access
+
{roles.map(role => ( { isSearchable name="projects" options={ projectOptions } - onChange={ ({ value }: any) => writeOption({ name: 'projects', value }) } + onChange={ ({ value }: any) => writeOption({ name: 'projects', value: value.value }) } value={null} /> { role.projects.size > 0 && ( @@ -181,10 +181,10 @@ export default connect((state: any) => { key: p.get('id'), value: p.get('id'), label: p.get('name'), - isDisabled: role.projects.includes(p.get('id')), - })).toJS(), - permissions: state.getIn(['roles', 'permissions']) - .map(({ text, value }: any) => ({ label: text, value, isDisabled: role.permissions.includes(value) })).toJS(), + // isDisabled: role.projects.includes(p.get('id')), + })).filter(({ value }: any) => !role.projects.includes(value)).toJS(), + permissions: state.getIn(['roles', 'permissions']).filter(({ value }: any) => !role.permissions.includes(value)) + .map(({ text, value }: any) => ({ label: text, value })).toJS(), saving: state.getIn([ 'roles', 'saveRequest', 'loading' ]), projectsMap: projects.reduce((acc: any, p: any) => { acc[ p.get('id') ] = p.get('name') diff --git a/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx b/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx index c07919e8e..02058f337 100644 --- a/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx +++ b/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx @@ -42,20 +42,21 @@ function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions, proj )}
- {role.permissions.map((permission: any) => ( - - ))} -
+
+ {role.permissions.map((permission: any) => ( + + ))} +
- { isAdmin && (
- { !!editHandler && + {isAdmin && !!editHandler &&
editHandler(role) }>
}
- )} +
+
); } diff --git a/frontend/app/components/Client/Users/components/AddUserButton/AddUserButton.tsx b/frontend/app/components/Client/Users/components/AddUserButton/AddUserButton.tsx index 6907a7b12..5632ed6fa 100644 --- a/frontend/app/components/Client/Users/components/AddUserButton/AddUserButton.tsx +++ b/frontend/app/components/Client/Users/components/AddUserButton/AddUserButton.tsx @@ -9,6 +9,7 @@ const LIMIT_WARNING = 'You have reached users limit.'; function AddUserButton({ isAdmin = false, onClick }: any ) { const { userStore } = useStore(); const limtis = useObserver(() => userStore.limits); + console.log('limtis', limtis) const cannAddUser = useObserver(() => isAdmin && (limtis.teamMember === -1 || limtis.teamMember > 0)); return ( user.updateKey('roleId', value)} + onChange={({ value }) => user.updateKey('roleId', value.value)} className="block" isDisabled={user.isSuperAdmin} /> diff --git a/frontend/app/components/Dashboard/NewDashboard.tsx b/frontend/app/components/Dashboard/NewDashboard.tsx index 106852923..89af30897 100644 --- a/frontend/app/components/Dashboard/NewDashboard.tsx +++ b/frontend/app/components/Dashboard/NewDashboard.tsx @@ -7,6 +7,7 @@ import { Loader } from 'UI'; import DashboardRouter from './components/DashboardRouter'; import cn from 'classnames'; import { withSiteId } from 'App/routes'; +import withPermissions from 'HOCs/withPermissions' function NewDashboard(props: RouteComponentProps<{}>) { const { history, match: { params: { siteId, dashboardId, metricId } } } = props; @@ -27,7 +28,6 @@ function NewDashboard(props: RouteComponentProps<{}>) { props.history.push(withSiteId('/dashboard', siteId)); }) } - }, [siteId]); return useObserver(() => ( @@ -49,4 +49,4 @@ function NewDashboard(props: RouteComponentProps<{}>) { )); } -export default withRouter(NewDashboard); +export default withRouter(withPermissions(['METRICS'])(NewDashboard)); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx index b8e982b62..9e42f9ea6 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx @@ -1,58 +1,75 @@ -import React, { useEffect } from 'react'; -import { Pagination, NoContent } from 'UI'; -import ErrorListItem from '../../../components/Errors/ErrorListItem'; -import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { useModal } from 'App/components/Modal'; -import ErrorDetailsModal from '../../../components/Errors/ErrorDetailsModal'; - -const PER_PAGE = 5; +import React, { useEffect } from "react"; +import { Pagination, NoContent } from "UI"; +import ErrorListItem from "App/components/Dashboard/components/Errors/ErrorListItem"; +import { withRouter, RouteComponentProps } from "react-router-dom"; +import { useModal } from "App/components/Modal"; +import ErrorDetailsModal from "App/components/Dashboard/components/Errors/ErrorDetailsModal"; +import { useStore } from "App/mstore"; +import { overPastString } from "App/dateRange"; interface Props { metric: any; - isTemplate?: boolean; - isEdit?: boolean; - history: any, - location: any, + isEdit: any; + history: any; + location: any; } function CustomMetricTableErrors(props: RouteComponentProps) { const { metric, isEdit = false } = props; const errorId = new URLSearchParams(props.location.search).get("errorId"); const { showModal, hideModal } = useModal(); + const { dashboardStore } = useStore(); + const period = dashboardStore.period; const onErrorClick = (e: any, error: any) => { e.stopPropagation(); - props.history.replace({search: (new URLSearchParams({errorId : error.errorId})).toString()}); - } + props.history.replace({ + search: new URLSearchParams({ errorId: error.errorId }).toString(), + }); + }; useEffect(() => { if (!errorId) return; - showModal(, { right: true, onClose: () => { - if (props.history.location.pathname.includes("/dashboard")) { - props.history.replace({search: ""}); - } - }}); + showModal(, { + right: true, + onClose: () => { + if (props.history.location.pathname.includes("/dashboard")) { + props.history.replace({ search: "" }); + } + }, + }); return () => { hideModal(); - } - }, [errorId]) + }; + }, [errorId]); return (
- {metric.data.errors && metric.data.errors.map((error: any, index: any) => ( - onErrorClick(e, error)} /> - ))} + {metric.data.errors && + metric.data.errors.map((error: any, index: any) => ( +
+ onErrorClick(e, error)} + /> +
+ ))} {isEdit && (
metric.updateKey('page', page)} + totalPages={Math.ceil( + metric.data.total / metric.limit + )} + onPageChange={(page: any) => + metric.updateKey("page", page) + } limit={metric.limit} debounceRequest={500} /> @@ -67,14 +84,17 @@ function CustomMetricTableErrors(props: RouteComponentProps) { ); } -export default withRouter(CustomMetricTableErrors) as React.FunctionComponent>; +export default withRouter(CustomMetricTableErrors) as React.FunctionComponent< + RouteComponentProps +>; -const ViewMore = ({ total, limit }: any) => total > limit && ( -
-
-
- All {total} errors +const ViewMore = ({ total, limit }: any) => + total > limit && ( +
+
+
+ All {total} errors +
-
-); \ No newline at end of file + ); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx index 4c06664c7..4ca953dcc 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx @@ -1,8 +1,9 @@ -import { useObserver } from 'mobx-react-lite'; -import React, { useEffect } from 'react'; -import SessionItem from 'Shared/SessionItem'; -import { Pagination, NoContent } from 'UI'; -import { useModal } from 'App/components/Modal'; +import { useObserver } from "mobx-react-lite"; +import React from "react"; +import SessionItem from "Shared/SessionItem"; +import { Pagination, NoContent } from "UI"; +import { useStore } from "App/mstore"; +import { overPastString } from "App/dateRange"; interface Props { metric: any; @@ -12,25 +13,41 @@ interface Props { function CustomMetricTableSessions(props: Props) { const { isEdit = false, metric } = props; - + const { dashboardStore } = useStore(); + const period = dashboardStore.period; + return useObserver(() => (
- {metric.data.sessions && metric.data.sessions.map((session: any, index: any) => ( -
- -
- ))} - + {metric.data.sessions && + metric.data.sessions.map((session: any, index: any) => ( +
+ +
+ ))} + {isEdit && (
metric.updateKey('page', page)} + totalPages={Math.ceil( + metric.data.total / metric.limit + )} + onPageChange={(page: any) => + metric.updateKey("page", page) + } limit={metric.data.total} debounceRequest={500} /> @@ -47,12 +64,13 @@ function CustomMetricTableSessions(props: Props) { export default CustomMetricTableSessions; -const ViewMore = ({ total, limit }: any) => total > limit && ( -
-
-
- All {total} sessions +const ViewMore = ({ total, limit }: any) => + total > limit && ( +
+
+
+ All {total} sessions +
-
-); \ No newline at end of file + ); diff --git a/frontend/app/components/Dashboard/components/DashboardEditModal/DashboardEditModal.tsx b/frontend/app/components/Dashboard/components/DashboardEditModal/DashboardEditModal.tsx index bf92d6ea1..da0e49935 100644 --- a/frontend/app/components/Dashboard/components/DashboardEditModal/DashboardEditModal.tsx +++ b/frontend/app/components/Dashboard/components/DashboardEditModal/DashboardEditModal.tsx @@ -29,7 +29,7 @@ function DashboardEditModal(props: Props) { const write = ({ target: { value, name } }) => dashboard.update({ [ name ]: value }) return useObserver(() => ( - +
{ 'Edit Dashboard' }
( - +
{ 'Add to selected dashboard' }
{ if (!dashboard || !dashboard.dashboardId) return; - dashboardStore.fetch(dashboard.dashboardId) + dashboardStore.fetch(dashboard.dashboardId); }, [dashboard]); const trimQuery = () => { - if (!queryParams.has('modal')) return; - queryParams.delete('modal') + if (!queryParams.has("modal")) return; + queryParams.delete("modal"); props.history.replace({ search: queryParams.toString(), - }) - } + }); + }; const pushQuery = () => { - if (!queryParams.has('modal')) props.history.push('?modal=addMetric') - } + if (!queryParams.has("modal")) props.history.push("?modal=addMetric"); + }; useEffect(() => { if (!dashboardId) dashboardStore.selectDefaultDashboard(); - if (queryParams.has('modal')) { + if (queryParams.has("modal")) { onAddWidgets(); trimQuery(); } }, []); const onAddWidgets = () => { - dashboardStore.initDashboard(dashboard) - showModal(, { right: true }) - } + dashboardStore.initDashboard(dashboard); + showModal( + , + { right: true } + ); + }; const onEdit = (isTitle: boolean) => { - dashboardStore.initDashboard(dashboard) + dashboardStore.initDashboard(dashboard); setFocusedInput(isTitle); - setShowEditModal(true) - } + setShowEditModal(true); + }; const onDelete = async () => { - if (await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to permanently delete this Dashboard?` - })) { + if ( + await confirm({ + header: "Confirm", + confirmButton: "Yes, delete", + confirmation: `Are you sure you want to permanently delete this Dashboard?`, + }) + ) { dashboardStore.deleteDashboard(dashboard).then(() => { - dashboardStore.selectDefaultDashboard().then(({ dashboardId }) => { - props.history.push(withSiteId(`/dashboard/${dashboardId}`, siteId)); - }, () => { - props.history.push(withSiteId('/dashboard', siteId)); - }) + dashboardStore.selectDefaultDashboard().then( + ({ dashboardId }) => { + props.history.push( + withSiteId(`/dashboard/${dashboardId}`, siteId) + ); + }, + () => { + props.history.push(withSiteId("/dashboard", siteId)); + } + ); }); } - } + }; return ( - - Gather and analyze
important metrics in one place.
+ + + Gather and analyze
important metrics in one + place. +
} size="small" subtext={ - + } > -
+
setShowEditModal(false)} @@ -118,23 +150,41 @@ function DashboardView(props: Props) {
{dashboard?.name}} + title={ + + {dashboard?.name} + + } onDoubleClick={() => onEdit(true)} className="mr-3 select-none hover:border-dotted hover:border-b border-gray-medium cursor-pointer" actionButton={ - + } /> -
-
-
+
+
dashboardStore.setPeriod(period)} + onChange={(period: any) => + dashboardStore.setPeriod(period) + } right={true} />
@@ -150,7 +200,9 @@ function DashboardView(props: Props) {
-

{dashboard?.description}

+

+ {dashboard?.description} +

dashboardStore.updateKey('showAlertModal', false)} + onClose={() => + dashboardStore.updateKey("showAlertModal", false) + } />
@@ -168,6 +222,6 @@ function DashboardView(props: Props) { ); } -export default withPageTitle('Dashboards - OpenReplay')( +export default withPageTitle("Dashboards - OpenReplay")( withReport(withRouter(withModal(observer(DashboardView)))) ); diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx index d6e52638f..2d76241b3 100644 --- a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx +++ b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx @@ -27,7 +27,7 @@ function ErrorListItem(props: Props) { // } return (
diff --git a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx index 50bb99164..492a41bd5 100644 --- a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx +++ b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon, NoContent, Label, Link, Pagination } from 'UI'; +import { Icon, NoContent, Label, Link, Pagination, Popup } from 'UI'; import { checkForRecent, formatDateTimeDefault, convertTimestampToUtcTimestamp } from 'App/date'; import { getIcon } from 'react-toastify/dist/components'; @@ -24,11 +24,22 @@ function DashboardLink({ dashboards}: any) { ); } -function MetricListItem(props: Props) { - const { metric } = props; +function MetricTypeIcon({ type }: any) { + const PopupWrapper = (props: any) => { + return ( + {type}
} + position="top center" + on="hover" + hideOnScroll={true} + > + {props.children} + + ); + } - const getIcon = (metricType: string) => { - switch (metricType) { + const getIcon = () => { + switch (type) { case 'funnel': return 'filter'; case 'table': @@ -37,13 +48,28 @@ function MetricListItem(props: Props) { return 'bar-chart-line'; } } + + return ( + +
+ +
+
+ ) +} + +function MetricListItem(props: Props) { + const { metric } = props; + + return (
-
+ {/*
-
+
*/} + {metric.name} diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index e6553dcc8..0cccc7a58 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -146,7 +146,7 @@ function WidgetChart(props: Props) { return ( ) diff --git a/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx b/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx index 7c9c62439..52beded51 100644 --- a/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx +++ b/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx @@ -43,10 +43,10 @@ function WidgetName(props: Props) { setEditing(false) } } - document.addEventListener('keypress', handler, false) + document.addEventListener('keydown', handler, false) return () => { - document.removeEventListener('keypress', handler, false) + document.removeEventListener('keydown', handler, false) } }, []) diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index 06a722cfd..66a4654e3 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -1,21 +1,21 @@ -import React, { useEffect, useState } from 'react'; -import { NoContent, Loader, Pagination } from 'UI'; -import Select from 'Shared/Select'; -import cn from 'classnames'; -import { useStore } from 'App/mstore'; -import SessionItem from 'Shared/SessionItem'; -import { observer, useObserver } from 'mobx-react-lite'; -import { DateTime } from 'luxon'; -import { debounce } from 'App/utils'; -import useIsMounted from 'App/hooks/useIsMounted' -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import React, { useEffect, useState } from "react"; +import { NoContent, Loader, Pagination } from "UI"; +import Select from "Shared/Select"; +import cn from "classnames"; +import { useStore } from "App/mstore"; +import SessionItem from "Shared/SessionItem"; +import { observer, useObserver } from "mobx-react-lite"; +import { DateTime } from "luxon"; +import { debounce } from "App/utils"; +import useIsMounted from "App/hooks/useIsMounted"; +import AnimatedSVG, { ICONS } from "Shared/AnimatedSVG/AnimatedSVG"; interface Props { className?: string; } function WidgetSessions(props: Props) { - const { className = '' } = props; - const [activeSeries, setActiveSeries] = useState('all'); + const { className = "" } = props; + const [activeSeries, setActiveSeries] = useState("all"); const [data, setData] = useState([]); const isMounted = useIsMounted(); const [loading, setLoading] = useState(false); @@ -23,15 +23,14 @@ function WidgetSessions(props: Props) { const { dashboardStore, metricStore } = useStore(); const filter = useObserver(() => dashboardStore.drillDownFilter); const widget: any = useObserver(() => metricStore.instance); - // const drillDownPeriod = useObserver(() => dashboardStore.drillDownPeriod); - const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat('LLL dd, yyyy HH:mm'); - const endTime = DateTime.fromMillis(filter.endTimestamp).toFormat('LLL dd, yyyy HH:mm'); - // const [timestamps, setTimestamps] = useState({ - // startTimestamp: 0, - // endTimestamp: 0, - // }); + const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat( + "LLL dd, yyyy HH:mm" + ); + const endTime = DateTime.fromMillis(filter.endTimestamp).toFormat( + "LLL dd, yyyy HH:mm" + ); const [seriesOptions, setSeriesOptions] = useState([ - { label: 'All', value: 'all' }, + { label: "All", value: "all" }, ]); const writeOption = ({ value }: any) => setActiveSeries(value.value); @@ -41,49 +40,68 @@ function WidgetSessions(props: Props) { label: item.seriesName, value: item.seriesId, })); - setSeriesOptions([ - { label: 'All', value: 'all' }, - ...seriesOptions, - ]); + setSeriesOptions([{ label: "All", value: "all" }, ...seriesOptions]); }, [data]); const fetchSessions = (metricId: any, filter: any) => { if (!isMounted()) return; - setLoading(true) - widget.fetchSessions(metricId, filter).then((res: any) => { - setData(res) - }).finally(() => { - setLoading(false) - }); - } - const debounceRequest: any = React.useCallback(debounce(fetchSessions, 1000), []); + setLoading(true); + widget + .fetchSessions(metricId, filter) + .then((res: any) => { + setData(res); + }) + .finally(() => { + setLoading(false); + }); + }; + const debounceRequest: any = React.useCallback( + debounce(fetchSessions, 1000), + [] + ); const depsString = JSON.stringify(widget.series); useEffect(() => { - debounceRequest(widget.metricId, { ...filter, series: widget.toJsonDrilldown(), page: metricStore.sessionsPage, limit: metricStore.sessionsPageSize }); - }, [filter.startTimestamp, filter.endTimestamp, filter.filters, depsString, metricStore.sessionsPage]); - - // useEffect(() => { - // const timestamps = drillDownPeriod.toTimestamps(); - // // console.log('timestamps', timestamps); - // debounceRequest(widget.metricId, { startTime: timestamps.startTimestamp, endTime: timestamps.endTimestamp, series: widget.toJsonDrilldown(), page: metricStore.sessionsPage, limit: metricStore.sessionsPageSize }); - // }, [drillDownPeriod]); + debounceRequest(widget.metricId, { + ...filter, + series: widget.toJsonDrilldown(), + page: metricStore.sessionsPage, + limit: metricStore.sessionsPageSize, + }); + }, [ + filter.startTimestamp, + filter.endTimestamp, + filter.filters, + depsString, + metricStore.sessionsPage, + ]); return useObserver(() => (

Sessions

-
between {startTime} and {endTime}
+
+ between{" "} + + {startTime} + {" "} + and{" "} + + {endTime} + {" "} +
- { widget.metricType !== 'table' && ( + {widget.metricType !== "table" && (
- Filter by Series + + Filter by Series +