From 6ffa00cfe49e353e7df60a15592603da333381e3 Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Mon, 14 Feb 2022 16:54:53 +0100 Subject: [PATCH 01/25] chore(http): check for custom endpoint for caching Signed-off-by: --global <--global> --- .../openreplay/charts/assets/templates/deployment.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml index 1af5164ca..1c8216468 100644 --- a/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml @@ -59,7 +59,11 @@ spec: # Ref: https://stackoverflow.com/questions/53634583/go-template-split-string-by-delimiter # We need https://bucketname.s3endpoint - name: ASSETS_ORIGIN + {{- if eq .Values.global.s3.endpoint "http://minio.db.svc.cluster.local:9000" }} + value: 'https://{{ .Values.global.domainName }}/{{.Values.global.s3.assetsBucket}}' + {{- else }} value: {{ (split "://" .Values.global.s3.endpoint)._0 }}://{{.Values.global.s3.assetsBucket}}.{{ (split "://" .Values.global.s3.endpoint)._1 }} + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' From c1f0e79c8dd1dc55758df45e3e7cc51dfe106f94 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 14 Feb 2022 17:51:44 +0100 Subject: [PATCH 02/25] fix(ui) - typo --- .../app/components/shared/SaveSearchModal/SaveSearchModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx b/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx index b579652e9..1ba6b3d56 100644 --- a/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx +++ b/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx @@ -39,7 +39,7 @@ function SaveSearchModal(props: Props) { if (await confirm({ header: 'Confirm', confirmButton: 'Yes, Delete', - confirmation: `Are you sure you want to permanently delete this Saved serch?`, + confirmation: `Are you sure you want to permanently delete this Saved search?`, })) { props.remove(savedSearch.searchId).then(() => { closeHandler(); From e705c8566c3a430fab3a46291f3566eb10a65147 Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Tue, 15 Feb 2022 05:57:55 +0100 Subject: [PATCH 03/25] fix(migration): template file variable values --- scripts/helmcharts/vars_template.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/helmcharts/vars_template.yaml b/scripts/helmcharts/vars_template.yaml index 2230e0a75..1e008250d 100644 --- a/scripts/helmcharts/vars_template.yaml +++ b/scripts/helmcharts/vars_template.yaml @@ -48,11 +48,11 @@ global: kafka: *kafka redis: *redis s3: - region: "us-east-1" - endpoint: "http://minio.db.svc.cluster.local:9000" - assetsBucket: "sessions-assets" - recordingsBucket: "mobs" - sourcemapsBucket: "sourcemaps" + region: "{{ aws_region }}" + endpoint: "{{ s3_endpoint }}" + assetsBucket: "{{ assets_bucket }}" + recordingsBucket: "{{ recordings_bucket }}" + sourcemapsBucket: "{{ sourcemaps_bucket }}" # if you're using one node installation, where # you're using local s3, make sure these variables # are same as minio.global.minio.accesskey and secretKey From 5ecf2d9b0363c4032036028865a08809a7f696d9 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Tue, 15 Feb 2022 06:30:54 +0100 Subject: [PATCH 04/25] fix(install): minio download path Signed-off-by: rjshrjndrn --- scripts/helmcharts/openreplay/files/minio.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/helmcharts/openreplay/files/minio.sh b/scripts/helmcharts/openreplay/files/minio.sh index dc8fbdbb0..1cc3e0460 100644 --- a/scripts/helmcharts/openreplay/files/minio.sh +++ b/scripts/helmcharts/openreplay/files/minio.sh @@ -35,7 +35,7 @@ mc policy set download minio/frontend mc policy set download minio/sessions-assets mc policy set download minio/static -curl -L https://github.com/openreplay/openreplay/releases/download/${CHART_APP_VERSION}/frontend.tar.gz -O +curl -L https://github.com/openreplay/openreplay/releases/download/v${CHART_APP_VERSION}/frontend.tar.gz -O tar -xf frontend.tar.gz mc cp --recursive frontend/ minio/frontend/ } From f2cfd37a37264c91def748ac8183be62d6cc86f5 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Tue, 15 Feb 2022 18:19:54 +0100 Subject: [PATCH 05/25] chore(nginx): precedence x-forward-for ip for geo location tagging Signed-off-by: rjshrjndrn --- .../nginx-ingress/templates/configMap.yaml | 12 +++++++---- .../charts/nginx-ingress/values.yaml | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/scripts/helmcharts/openreplay/charts/nginx-ingress/templates/configMap.yaml b/scripts/helmcharts/openreplay/charts/nginx-ingress/templates/configMap.yaml index baba2f5e0..f5b7699cd 100644 --- a/scripts/helmcharts/openreplay/charts/nginx-ingress/templates/configMap.yaml +++ b/scripts/helmcharts/openreplay/charts/nginx-ingress/templates/configMap.yaml @@ -13,7 +13,7 @@ data: } location ~ ^/(mobs|sessions-assets|frontend|static|sourcemaps|ios-images)/ { proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-For $origin_forwarded_ip; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; @@ -38,7 +38,7 @@ data: proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-For $origin_forwarded_ip; proxy_set_header X-Forwarded-Host $real_ip; proxy_set_header X-Real-IP $real_ip; proxy_set_header Host $host; @@ -71,7 +71,7 @@ data: proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-For $origin_forwarded_ip; proxy_pass http://utilities-openreplay.app.svc.cluster.local:9000; } location /ws-assist/ { @@ -80,7 +80,7 @@ data: proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-For $origin_forwarded_ip; proxy_set_header X-Real-IP $real_ip; proxy_pass http://utilities-openreplay.app.svc.cluster.local:9001; } @@ -151,6 +151,10 @@ data: default $http_x_forwarded_proto; '' $scheme; } + map $http_x_forwarded_for $origin_forwarded_ip { + default $http_x_forwarded_for; + '' $remote_addr; + } # Default server for helath check server { listen 80 default_server; diff --git a/scripts/helmcharts/openreplay/charts/nginx-ingress/values.yaml b/scripts/helmcharts/openreplay/charts/nginx-ingress/values.yaml index 821ad9e3c..43d6d3eae 100644 --- a/scripts/helmcharts/openreplay/charts/nginx-ingress/values.yaml +++ b/scripts/helmcharts/openreplay/charts/nginx-ingress/values.yaml @@ -84,3 +84,23 @@ nodeSelector: {} tolerations: [] affinity: {} + +healthProbes: + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: http + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: http + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 From cb41d9134d6add1a7b7e028cb29218c85e9091f4 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Wed, 16 Feb 2022 10:08:07 +0100 Subject: [PATCH 06/25] chore(kafka): change retention to 4 days Signed-off-by: rjshrjndrn --- scripts/helmcharts/openreplay/files/kafka.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/helmcharts/openreplay/files/kafka.sh b/scripts/helmcharts/openreplay/files/kafka.sh index 1c811eb5d..61c039601 100644 --- a/scripts/helmcharts/openreplay/files/kafka.sh +++ b/scripts/helmcharts/openreplay/files/kafka.sh @@ -22,7 +22,7 @@ function init() { echo "Creating topic: $topic" # TODO: Have to check an idempotent way of creating topics. kafka-topics.sh --create --bootstrap-server ${KAFKA_HOST}:${KAFKA_PORT} --replication-factor 2 --partitions 16 --topic ${topic} --command-config /tmp/config.txt || true - kafka-configs.sh --bootstrap-server ${KAFKA_HOST}:${KAFKA_PORT} --entity-type topics --alter --add-config retention.ms=3456000000 --entity-name=${topic} --command-config /tmp/config.txt || true + kafka-configs.sh --bootstrap-server ${KAFKA_HOST}:${KAFKA_PORT} --entity-type topics --alter --add-config retention.ms=345600000 --entity-name=${topic} --command-config /tmp/config.txt || true done } From 6a9035e26047319d4ba7f227119b267f54373bd6 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Thu, 17 Feb 2022 11:58:26 +0100 Subject: [PATCH 07/25] chore(helm): commit We need to check what is the object store endpoint. There can be 4 options 1. Using minio inside kube clster 2. Using minio managed external cluster, like aws minio offering 3. Using GCP or other object stores compatible with s3 apis 4. Using AWS itself. AWS uses bucketname.endpoint/object while others use endpoint/bucketname/object Signed-off-by: rjshrjndrn --- .../charts/assets/templates/deployment.yaml | 18 +++++++++++++++--- .../charts/http/templates/deployment.yaml | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml index 1c8216468..d34883b5a 100644 --- a/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml @@ -56,13 +56,25 @@ spec: value: '{{ .Values.global.kafka.kafkaHost }}:{{ .Values.global.kafka.kafkaPort }}' - name: KAFKA_USE_SSL value: '{{ .Values.global.kafka.kafkaUseSsl }}' - # Ref: https://stackoverflow.com/questions/53634583/go-template-split-string-by-delimiter - # We need https://bucketname.s3endpoint + # We need to check what is the object store endpoint. + # There can be 4 options + # 1. Using minio inside kube clster + # 2. Using minio managed external cluster, like aws minio offering + # 3. Using GCP or other object stores compatible with s3 apis + # 4. Using AWS itself. + # AWS uses bucketname.endpoint/object while others use endpoint/bucketname/object - name: ASSETS_ORIGIN {{- if eq .Values.global.s3.endpoint "http://minio.db.svc.cluster.local:9000" }} + # Local minio Installation value: 'https://{{ .Values.global.domainName }}/{{.Values.global.s3.assetsBucket}}' - {{- else }} + {{- else if contains "amazonaws.com" .Values.global.s3.endpoint }} + # AWS S3 + # Ref: https://stackoverflow.com/questions/53634583/go-template-split-string-by-delimiter + # We need https://bucketname.s3endpoint value: {{ (split "://" .Values.global.s3.endpoint)._0 }}://{{.Values.global.s3.assetsBucket}}.{{ (split "://" .Values.global.s3.endpoint)._1 }} + {{- else }} + # S3 compatible storage + value: '{{ .Values.global.s3.endpoint }}/{{.Values.global.s3.assetsBucket}}' {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} diff --git a/scripts/helmcharts/openreplay/charts/http/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/http/templates/deployment.yaml index 875cc3630..0c4399518 100644 --- a/scripts/helmcharts/openreplay/charts/http/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/http/templates/deployment.yaml @@ -54,11 +54,25 @@ spec: value: '{{ .Values.global.kafka.kafkaUseSsl }}' - name: POSTGRES_STRING value: 'postgres://{{ .Values.global.postgresql.postgresqlUser }}:{{ .Values.global.postgresql.postgresqlPassword }}@{{ .Values.global.postgresql.postgresqlHost }}:{{ .Values.global.postgresql.postgresqlPort }}/{{ .Values.global.postgresql.postgresqlDatabase }}' + # We need to check what is the object store endpoint. + # There can be 4 options + # 1. Using minio inside kube clster + # 2. Using minio managed external cluster, like aws minio offering + # 3. Using GCP or other object stores compatible with s3 apis + # 4. Using AWS itself. + # AWS uses bucketname.endpoint/object while others use endpoint/bucketname/object - name: ASSETS_ORIGIN {{- if eq .Values.global.s3.endpoint "http://minio.db.svc.cluster.local:9000" }} + # Local minio Installation value: 'https://{{ .Values.global.domainName }}/{{.Values.global.s3.assetsBucket}}' - {{- else }} + {{- else if contains "amazonaws.com" .Values.global.s3.endpoint }} + # AWS S3 + # Ref: https://stackoverflow.com/questions/53634583/go-template-split-string-by-delimiter + # We need https://bucketname.s3endpoint value: {{ (split "://" .Values.global.s3.endpoint)._0 }}://{{.Values.global.s3.assetsBucket}}.{{ (split "://" .Values.global.s3.endpoint)._1 }} + {{- else }} + # S3 compatible storage + value: '{{ .Values.global.s3.endpoint }}/{{.Values.global.s3.assetsBucket}}' {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} From 3466ba9750e55767eb2e22a6a31b99da64836314 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 22 Feb 2022 17:43:55 +0100 Subject: [PATCH 08/25] feat(utilities): WS changed maxHttpBufferSize --- utilities/servers/websocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/servers/websocket.js b/utilities/servers/websocket.js index ab6a2c4d5..dfce5b4d7 100644 --- a/utilities/servers/websocket.js +++ b/utilities/servers/websocket.js @@ -161,7 +161,7 @@ module.exports = { wsRouter, start: (server) => { io = _io(server, { - maxHttpBufferSize: 1e6, + maxHttpBufferSize: 5e6, cors: { origin: "*", methods: ["GET", "POST", "PUT"] From da63085c94ebe2a4d79c1c0e50b9dbf39f33c263 Mon Sep 17 00:00:00 2001 From: estradino Date: Thu, 24 Feb 2022 18:10:20 +0000 Subject: [PATCH 09/25] chore(release): change version --- scripts/helmcharts/init.sh | 2 +- scripts/helmcharts/openreplay/Chart.yaml | 2 +- scripts/helmcharts/openreplay/charts/alerts/Chart.yaml | 2 +- scripts/helmcharts/openreplay/charts/assets/Chart.yaml | 2 +- scripts/helmcharts/openreplay/charts/chalice/Chart.yaml | 2 +- scripts/helmcharts/openreplay/charts/db/Chart.yaml | 2 +- scripts/helmcharts/openreplay/charts/ender/Chart.yaml | 2 +- scripts/helmcharts/openreplay/charts/http/Chart.yaml | 2 +- scripts/helmcharts/openreplay/charts/integrations/Chart.yaml | 2 +- scripts/helmcharts/openreplay/charts/nginx-ingress/Chart.yaml | 2 +- scripts/helmcharts/openreplay/charts/sink/Chart.yaml | 2 +- scripts/helmcharts/openreplay/charts/storage/Chart.yaml | 2 +- scripts/helmcharts/openreplay/charts/utilities/Chart.yaml | 2 +- scripts/helmcharts/vars.yaml | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/scripts/helmcharts/init.sh b/scripts/helmcharts/init.sh index 428ed82eb..2df3b8450 100644 --- a/scripts/helmcharts/init.sh +++ b/scripts/helmcharts/init.sh @@ -15,7 +15,7 @@ fatal() exit 1 } -version="v1.5.1" +version="v1.5.2" usr=`whoami` # Installing k3s diff --git a/scripts/helmcharts/openreplay/Chart.yaml b/scripts/helmcharts/openreplay/Chart.yaml index 8057ec335..e621e543d 100644 --- a/scripts/helmcharts/openreplay/Chart.yaml +++ b/scripts/helmcharts/openreplay/Chart.yaml @@ -22,4 +22,4 @@ version: 0.1.0 # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. # Ref: https://github.com/helm/helm/issues/7858#issuecomment-608114589 -AppVersion: "v1.5.1" +AppVersion: "v1.5.2" diff --git a/scripts/helmcharts/openreplay/charts/alerts/Chart.yaml b/scripts/helmcharts/openreplay/charts/alerts/Chart.yaml index 885ad5503..0bfe8b4bc 100644 --- a/scripts/helmcharts/openreplay/charts/alerts/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/alerts/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.5.1" +AppVersion: "v1.5.2" diff --git a/scripts/helmcharts/openreplay/charts/assets/Chart.yaml b/scripts/helmcharts/openreplay/charts/assets/Chart.yaml index 75657b1df..9b8c32623 100644 --- a/scripts/helmcharts/openreplay/charts/assets/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/assets/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.5.1" +AppVersion: "v1.5.2" diff --git a/scripts/helmcharts/openreplay/charts/chalice/Chart.yaml b/scripts/helmcharts/openreplay/charts/chalice/Chart.yaml index 8de5927eb..1d0441f7e 100644 --- a/scripts/helmcharts/openreplay/charts/chalice/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/chalice/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.5.1" +AppVersion: "v1.5.2" diff --git a/scripts/helmcharts/openreplay/charts/db/Chart.yaml b/scripts/helmcharts/openreplay/charts/db/Chart.yaml index b0cdbebd4..0aa5f39e3 100644 --- a/scripts/helmcharts/openreplay/charts/db/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/db/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.5.1" +AppVersion: "v1.5.2" diff --git a/scripts/helmcharts/openreplay/charts/ender/Chart.yaml b/scripts/helmcharts/openreplay/charts/ender/Chart.yaml index ec411fad1..8a7ce610e 100644 --- a/scripts/helmcharts/openreplay/charts/ender/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/ender/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.5.1" +AppVersion: "v1.5.2" diff --git a/scripts/helmcharts/openreplay/charts/http/Chart.yaml b/scripts/helmcharts/openreplay/charts/http/Chart.yaml index 5b65ded45..5422d1f3d 100644 --- a/scripts/helmcharts/openreplay/charts/http/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/http/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.5.1" +AppVersion: "v1.5.2" diff --git a/scripts/helmcharts/openreplay/charts/integrations/Chart.yaml b/scripts/helmcharts/openreplay/charts/integrations/Chart.yaml index f98a99254..e8fc92887 100644 --- a/scripts/helmcharts/openreplay/charts/integrations/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/integrations/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.5.1" +AppVersion: "v1.5.2" diff --git a/scripts/helmcharts/openreplay/charts/nginx-ingress/Chart.yaml b/scripts/helmcharts/openreplay/charts/nginx-ingress/Chart.yaml index 75cf9a946..2c473b4b4 100644 --- a/scripts/helmcharts/openreplay/charts/nginx-ingress/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/nginx-ingress/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.5.1" +AppVersion: "v1.5.2" diff --git a/scripts/helmcharts/openreplay/charts/sink/Chart.yaml b/scripts/helmcharts/openreplay/charts/sink/Chart.yaml index 51c85b6f3..7ece0a5bf 100644 --- a/scripts/helmcharts/openreplay/charts/sink/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/sink/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.5.1" +AppVersion: "v1.5.2" diff --git a/scripts/helmcharts/openreplay/charts/storage/Chart.yaml b/scripts/helmcharts/openreplay/charts/storage/Chart.yaml index 5b535bc5a..9e1c06d8c 100644 --- a/scripts/helmcharts/openreplay/charts/storage/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/storage/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.5.1" +AppVersion: "v1.5.2" diff --git a/scripts/helmcharts/openreplay/charts/utilities/Chart.yaml b/scripts/helmcharts/openreplay/charts/utilities/Chart.yaml index 895405214..acc73d941 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.5.1" +AppVersion: "v1.5.2" diff --git a/scripts/helmcharts/vars.yaml b/scripts/helmcharts/vars.yaml index e7f59ab13..ed0ab4c05 100644 --- a/scripts/helmcharts/vars.yaml +++ b/scripts/helmcharts/vars.yaml @@ -1,4 +1,4 @@ -fromVersion: "v1.5.1" +fromVersion: "v1.5.2" # Databases specific variables postgresql: &postgres # For generating passwords From 87438fff14417691d551a780f2438018b2751163 Mon Sep 17 00:00:00 2001 From: estradino Date: Thu, 24 Feb 2022 18:12:14 +0000 Subject: [PATCH 10/25] chore(release): change version --- frontend/env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/env.js b/frontend/env.js index 1b1beb4bc..7d3ab7e6e 100644 --- a/frontend/env.js +++ b/frontend/env.js @@ -13,7 +13,7 @@ const oss = { ORIGIN: () => 'window.location.origin', API_EDP: () => 'window.location.origin + "/api"', ASSETS_HOST: () => 'window.location.origin + "/assets"', - VERSION: '1.5.1', + VERSION: '1.5.2', SOURCEMAP: true, MINIO_ENDPOINT: process.env.MINIO_ENDPOINT, MINIO_PORT: process.env.MINIO_PORT, From 1a55f9a897e0b075e8656933a3ed1702b091e414 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 25 Feb 2022 15:19:24 +0100 Subject: [PATCH 11/25] feat(ui) - subfilters - wip --- .../FilterAutoComplete/FilterAutoComplete.tsx | 20 ++++--- .../shared/Filters/FilterItem/FilterItem.tsx | 55 ++++++++++++++++--- .../Filters/SubFilterItem/SubFilterItem.tsx | 34 ++++++++++++ .../shared/Filters/SubFilterItem/index.ts | 1 + .../shared/FunnelSearch/FunnelSearch.tsx | 6 -- .../shared/SessionSearch/SessionSearch.tsx | 6 -- frontend/app/duck/filters.js | 14 +---- frontend/app/duck/search.js | 6 +- frontend/app/types/filter/filterType.ts | 5 ++ frontend/app/types/filter/index.js | 20 +------ frontend/app/types/filter/newFilter.js | 35 ++++++------ 11 files changed, 123 insertions(+), 79 deletions(-) create mode 100644 frontend/app/components/shared/Filters/SubFilterItem/SubFilterItem.tsx create mode 100644 frontend/app/components/shared/Filters/SubFilterItem/index.ts diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx index 333188b3e..92baa3d51 100644 --- a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx +++ b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx @@ -47,15 +47,17 @@ function FilterAutoComplete(props: Props) { const requestValues = (q) => { setLoading(true); - return new APIClient()[method?.toLowerCase()](endpoint, { ...params, q }) - .then(response => response.json()) - .then(({ errors, data }) => { - if (errors) { - // this.setError(); - } else { - setOptions(data); - } - }).finally(() => setLoading(false)); + return new APIClient()[method?.toLocaleLowerCase()](endpoint, { ...params, q }) + .then(response => { + if (response.ok) { + return response.json(); + } + throw new Error(response.statusText); + }) + .then(({ data }) => { + setOptions(data); + }) + .finally(() => setLoading(false)); } const debouncedRequestValues = React.useCallback(debounce(requestValues, 300), []); diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index db0bedf32..f01fefcd8 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -4,6 +4,8 @@ import FilterSelection from '../FilterSelection'; import FilterValue from '../FilterValue'; import { Icon } from 'UI'; import FilterSource from '../FilterSource'; +import { FilterType } from 'App/types/filter/filterType'; +import SubFilterItem from '../SubFilterItem'; interface Props { filterIndex: number; @@ -15,9 +17,14 @@ interface Props { function FilterItem(props: Props) { const { isFilter = false, filterIndex, filter } = props; const canShowValues = !(filter.operator === "isAny" || filter.operator === "onAny" || filter.operator === "isUndefined"); + const isSubFilter = filter.type === FilterType.SUB_FILTERS; const replaceFilter = (filter) => { - props.onUpdate({ ...filter, value: [""]}); + props.onUpdate({ + ...filter, + value: [""], + subFilters: filter.subFilters ? filter.subFilters.map(i => ({ ...i, value: [""] })) : [] + }); }; const onOperatorChange = (e, { name, value }) => { @@ -28,6 +35,19 @@ function FilterItem(props: Props) { props.onUpdate({ ...filter, sourceOperator: value }) } + const onUpdateSubFilter = (subFilter, subFilterIndex) => { + props.onUpdate({ + ...filter, + subFilters: filter.subFilters.map((i, index) => { + if (index === subFilterIndex) { + return subFilter; + } + return i; + }) + }); + }; + + return (
@@ -48,14 +68,31 @@ function FilterItem(props: Props) { )} {/* Filter values */} - - { canShowValues && () } - + { !isSubFilter && ( + <> + + { canShowValues && () } + + )} + + {/* SubFilters */} + {isSubFilter && ( +
+ {filter.subFilters.map((subFilter, subFilterIndex) => ( + onUpdateSubFilter(f, subFilterIndex)} + onRemoveFilter={props.onRemoveFilter} + /> + ))} +
+ )}
void; + onRemoveFilter: () => void; + isFilter?: boolean; +} +export default function SubFilterItem(props: Props) { + const { isFilter = false, filterIndex, filter } = props; + const canShowValues = !(filter.operator === "isAny" || filter.operator === "onAny" || filter.operator === "isUndefined"); + + const onOperatorChange = (e, { name, value }) => { + props.onUpdate({ ...filter, operator: value }) + } + + return ( +
+
{filter.label}
+ + + { canShowValues && () } +
+ ) +} diff --git a/frontend/app/components/shared/Filters/SubFilterItem/index.ts b/frontend/app/components/shared/Filters/SubFilterItem/index.ts new file mode 100644 index 000000000..0877700cc --- /dev/null +++ b/frontend/app/components/shared/Filters/SubFilterItem/index.ts @@ -0,0 +1 @@ +export { default } from './SubFilterItem'; \ No newline at end of file diff --git a/frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx b/frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx index 809eb739f..19a3b7ceb 100644 --- a/frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx +++ b/frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx @@ -18,12 +18,6 @@ function FunnelSearch(props: Props) { const onAddFilter = (filter) => { props.addFilter(filter); - // filter.value = [""] - // const newFilters = appliedFilter.filters.concat(filter); - // props.edit({ - // ...appliedFilter.filter, - // filters: newFilters, - // }); } const onUpdateFilter = (filterIndex, filter) => { diff --git a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx index 264786fff..17904c1ba 100644 --- a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx +++ b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx @@ -19,12 +19,6 @@ function SessionSearch(props: Props) { const onAddFilter = (filter) => { props.addFilter(filter); - // filter.value = [""] - // const newFilters = appliedFilter.filters.concat(filter); - // props.edit({ - // ...appliedFilter.filter, - // filters: newFilters, - // }); } const onUpdateFilter = (filterIndex, filter) => { diff --git a/frontend/app/duck/filters.js b/frontend/app/duck/filters.js index 132996797..16c16aa5e 100644 --- a/frontend/app/duck/filters.js +++ b/frontend/app/duck/filters.js @@ -1,4 +1,4 @@ -import { fromJS, List, Map, Set } from 'immutable'; +import { List, Map, Set } from 'immutable'; import { errors as errorsRoute, isRoute } from "App/routes"; import Filter from 'Types/filter'; import SavedFilter from 'Types/filter/savedFilter'; @@ -8,15 +8,6 @@ import withRequestState, { RequestTypes } from './requestStateCreator'; import { fetchList as fetchSessionList } from './sessions'; import { fetchList as fetchErrorsList } from './errors'; import { fetchListType, fetchType, saveType, editType, initType, removeType } from './funcTools/crud/types'; -import logger from 'App/logger'; - -import { newFiltersList } from 'Types/filter' -import NewFilter, { filtersMap } from 'Types/filter/newFilter'; - - -// for (var i = 0; i < newFiltersList.length; i++) { -// filterOptions[newFiltersList[i].category] = newFiltersList.filter(filter => filter.category === newFiltersList[i].category) -// } const ERRORS_ROUTE = errorsRoute(); @@ -44,11 +35,8 @@ const ADD_ATTRIBUTE = 'filters/ADD_ATTRIBUTE'; const EDIT_ATTRIBUTE = 'filters/EDIT_ATTRIBUTE'; const REMOVE_ATTRIBUTE = 'filters/REMOVE_ATTRIBUTE'; const SET_ACTIVE_FLOW = 'filters/SET_ACTIVE_FLOW'; - const UPDATE_VALUE = 'filters/UPDATE_VALUE'; -const REFRESH_FILTER_OPTIONS = 'filters/REFRESH_FILTER_OPTIONS'; - const initialState = Map({ instance: Filter(), activeFilter: null, diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index ad4ea944c..00799e5d7 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -161,7 +161,7 @@ export const applySavedSearch = (filter) => (dispatch, getState) => { export const fetchSessions = (filter) => (dispatch, getState) => { const _filter = filter ? filter : getState().getIn([ 'search', 'instance']); - return dispatch(applyFilter(_filter)); + // return dispatch(applyFilter(_filter)); // TODO uncomment this line }; export const updateSeries = (index, series) => ({ @@ -233,6 +233,10 @@ export const hasFilterApplied = (filters, filter) => { export const addFilter = (filter) => (dispatch, getState) => { filter.value = checkFilterValue(filter.value); + filter.subFilters = filter.subFilters ? filter.subFilters.map(subFilter => ({ + ...subFilter, + value: checkFilterValue(subFilter.value), + })) : null; const instance = getState().getIn([ 'search', 'instance']); if (hasFilterApplied(instance.filters, filter)) { diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts index 16f128975..655681796 100644 --- a/frontend/app/types/filter/filterType.ts +++ b/frontend/app/types/filter/filterType.ts @@ -15,6 +15,7 @@ export enum FilterType { NUMBER = "NUMBER", DURATION = "DURATION", MULTIPLE = "MULTIPLE", + SUB_FILTERS = "SUB_FILTERS", COUNTRY = "COUNTRY", DROPDOWN = "DROPDOWN", MULTIPLE_DROPDOWN = "MULTIPLE_DROPDOWN", @@ -61,4 +62,8 @@ export enum FilterKey { AVG_CPU_LOAD = "AVG_CPU_LOAD", AVG_MEMORY_USAGE = "AVG_MEMORY_USAGE", FETCH_FAILED = "FETCH_FAILED", + FETCH = "FETCH", + FETCH_URL = "FETCH_URL", + FETCH_STATUS = "FETCH_STATUS", + FETCH_METHOD = "FETCH_METHOD", } \ No newline at end of file diff --git a/frontend/app/types/filter/index.js b/frontend/app/types/filter/index.js index 957e1dfb8..386ea96a0 100644 --- a/frontend/app/types/filter/index.js +++ b/frontend/app/types/filter/index.js @@ -236,22 +236,4 @@ export const operatorOptions = (filter) => { case KEYS.CLICK_RAGE: return [{ key: 'onAnything', text: 'on anything', value: 'true' }] } -} - -const NewFilterType = (key, category, label, icon, isEvent = false) => { - return { - key: key, - category: category, - label: label, - icon: icon, - isEvent: isEvent, - operators: operatorOptions({ key }), - value: [""] - } -} - -export const newFiltersList = [ - NewFilterType(TYPES.CLICK, 'Gear', 'Click', 'filters/click', true), - NewFilterType(TYPES.CLICK, 'Gear', 'Input', 'filters/click', true), - NewFilterType(TYPES.CONSOLE, 'Other', 'Console', 'filters/click', true), -]; \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index d4cb905a1..99726b4a6 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -48,6 +48,11 @@ export const filtersMap = { [FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' }, // PERFORMANCE + [FilterKey.FETCH]: { key: FilterKey.FETCH, type: FilterType.SUB_FILTERS, category: FilterCategory.PERFORMANCE, label: 'Fetch Request', subFilters: [ + { key: FilterKey.FETCH_URL, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with URL', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, + { key: FilterKey.FETCH_STATUS, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with status code', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, + { key: FilterKey.FETCH_METHOD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, + ], icon: 'filters/fetch-failed', isEvent: true }, [FilterKey.DOM_COMPLETE]: { key: FilterKey.DOM_COMPLETE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'DOM Complete', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/dom-complete', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, [FilterKey.LARGEST_CONTENTFUL_PAINT_TIME]: { key: FilterKey.LARGEST_CONTENTFUL_PAINT_TIME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Largest Contentful Paint', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/lcpt', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, [FilterKey.TTFB]: { key: FilterKey.TTFB, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Time to First Byte', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/ttfb', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, @@ -121,17 +126,19 @@ export default Record({ isEvent: false, index: 0, options: [], + + subFilters: [], }, { keyKey: "_key", fromJS: ({ value, key, type, ...filter }) => { - // const _filter = filtersMap[key] || filtersMap[type] || {}; const _filter = filtersMap[type]; return { ...filter, ..._filter, key: _filter.key, type: _filter.type, // camelCased(filter.type.toLowerCase()), - value: value.length === 0 ? [""] : value, // make sure there an empty value + value: value.length === 0 ? [""] : value, + // subFilters: filter.subFilters.map(this), } }, }) @@ -142,33 +149,29 @@ export default Record({ * @returns */ export const generateFilterOptions = (map) => { - const _options = {}; + const filterSection = {}; Object.keys(map).forEach(key => { const filter = map[key]; - if (_options.hasOwnProperty(filter.category)) { - _options[filter.category].push(filter); + if (filterSection.hasOwnProperty(filter.category)) { + filterSection[filter.category].push(filter); } else { - _options[filter.category] = [filter]; + filterSection[filter.category] = [filter]; } }); - return _options; + return filterSection; } export const generateLiveFilterOptions = (map) => { - const _options = {}; + const filterSection = {}; Object.keys(map).filter(i => map[i].isLive).forEach(key => { const filter = map[key]; filter.operator = 'contains'; - // filter.type = FilterType.STRING; - // filter.type = FilterType.AUTOCOMPLETE_LOCAL; - // filter.options = countryOptions; - // filter.operatorOptions = [{ key: 'contains', text: 'contains', value: 'contains' }] - if (_options.hasOwnProperty(filter.category)) { - _options[filter.category].push(filter); + if (filterSection.hasOwnProperty(filter.category)) { + filterSection[filter.category].push(filter); } else { - _options[filter.category] = [filter]; + filterSection[filter.category] = [filter]; } }); - return _options; + return filterSection; } \ No newline at end of file From 0791744de770cd6256d20fa1b70a2603a6036c76 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Sun, 27 Feb 2022 12:15:51 +0100 Subject: [PATCH 12/25] feat(ui) - custommetrics - wip --- .../CustomMetricForm/CustomMetricForm.tsx | 32 +++++++++++++++++-- .../shared/DropdownPlain/DropdownPlain.tsx | 7 ++-- frontend/app/constants/filterOptions.js | 16 ++++++++++ frontend/app/types/customMetric.js | 4 ++- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx b/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx index 7700a7a29..fd95cd5c6 100644 --- a/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx +++ b/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx @@ -7,6 +7,8 @@ import CustomMetricWidgetPreview from 'App/components/Dashboard/Widgets/CustomMe import { confirm } from 'UI/Confirmation'; import { toast } from 'react-toastify'; import cn from 'classnames'; +import DropdownPlain from '../../DropdownPlain'; +import { metricTypes, metricOf } from 'App/constants/filterOptions'; interface Props { metric: any; @@ -21,6 +23,7 @@ interface Props { function CustomMetricForm(props: Props) { const { metric, loading } = props; + const metricOfOptions = metricOf.filter(i => i.key === metric.metricType); const addSeries = () => { props.addSeries(); @@ -30,7 +33,8 @@ function CustomMetricForm(props: Props) { props.removeSeries(index); } - const write = ({ target: { value, name } }) => props.editMetric({ ...metric, [ name ]: value }, false); + const write = ({ target: { value, name } }) => props.editMetric({ [ name ]: value }, false); + const writeOption = (e, { value, name }) => props.editMetric({ [ name ]: value }, false); const changeConditionTab = (e, { name, value }) => { props.editMetric({[ 'viewType' ]: value }); @@ -79,6 +83,30 @@ function CustomMetricForm(props: Props) {
+ + of + + showing + +
+ {/*
Timeseries of
@@ -95,7 +123,7 @@ function CustomMetricForm(props: Props) { ]} />
-
+
*/}
diff --git a/frontend/app/components/shared/DropdownPlain/DropdownPlain.tsx b/frontend/app/components/shared/DropdownPlain/DropdownPlain.tsx index f4f21b1c8..d3313ac3e 100644 --- a/frontend/app/components/shared/DropdownPlain/DropdownPlain.tsx +++ b/frontend/app/components/shared/DropdownPlain/DropdownPlain.tsx @@ -3,6 +3,7 @@ import stl from './DropdownPlain.css'; import { Dropdown, Icon } from 'UI'; interface Props { + name?: string; options: any[]; onChange: (e, { name, value }) => void; icon?: string; @@ -11,17 +12,19 @@ interface Props { } export default function DropdownPlain(props: Props) { - const { value, options, icon = "chevron-down", direction = "left" } = props; + const { name = "sort", value, options, icon = "chevron-down", direction = "left" } = props; return (
: null } /> diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index 868379e86..215832c1a 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -54,6 +54,20 @@ export const customOperators = [ { key: '>=', text: '>=', value: '>=' }, ] +export const metricTypes = [ + { text: 'Timeseries', value: 'timeseries' }, + { text: 'Table', value: 'table' }, +]; + +export const metricOf = [ + { text: 'Session Count', value: 'sessionCount', key: 'timeseries' }, + { text: 'Users', value: 'users', key: 'table' }, + { text: 'Rage Click', value: 'rageClick', key: 'table' }, + { text: 'Dead Click', value: 'deadClick', key: 'table' }, + { text: 'Browser', value: 'browser', key: 'table' }, + { text: 'Device', value: 'device', key: 'table' }, +] + export default { options, baseOperators, @@ -62,4 +76,6 @@ export default { booleanOperators, customOperators, getOperatorsByKeys, + metricTypes, + metricOf, } \ No newline at end of file diff --git a/frontend/app/types/customMetric.js b/frontend/app/types/customMetric.js index d5238a0aa..902c10a23 100644 --- a/frontend/app/types/customMetric.js +++ b/frontend/app/types/customMetric.js @@ -27,7 +27,9 @@ export const FilterSeries = Record({ export default Record({ metricId: undefined, name: 'Series', - viewType: 'lineChart', + metricType: 'timeseries', + metricOf: 'sessionCount', + viewType: 'sessionCount', series: List(), isPublic: true, startDate: '', From 47846e264171bbe4b8dc2e9607e149a105b49142 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Sun, 27 Feb 2022 17:17:05 +0100 Subject: [PATCH 13/25] fix(ui) - metadata in offline session --- .../Session_/EventsBlock/Metadata/Metadata.js | 16 +++++++++------- .../EventsBlock/Metadata/MetadataItem.js | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js b/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js index b0dca0956..0992c0f88 100644 --- a/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js +++ b/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js @@ -10,13 +10,15 @@ export default connect(state => ({ metadata: state.getIn([ 'sessions', 'current', 'metadata' ]), }))(function Metadata ({ metadata }) { const [ visible, setVisible ] = useState(false); - const toggle = useCallback(() => metadata.length > 0 && setVisible(v => !v), []); + const metaLenth = Object.keys(metadata).length; + const toggle = useCallback(() => metaLenth > 0 && setVisible(v => !v), []); + return ( <> ({
} on="click" - disabled={metadata.length > 0} + disabled={metaLenth > 0} size="tiny" inverted position="top center" /> { visible &&
- - { metadata.map((i) => { - const key = Object.keys(i)[0] - const value = i[key] + + { Object.keys(metadata).map((key) => { + // const key = Object.keys(i)[0] + const value = metadata[key] return }) } diff --git a/frontend/app/components/Session_/EventsBlock/Metadata/MetadataItem.js b/frontend/app/components/Session_/EventsBlock/Metadata/MetadataItem.js index 8abd1913c..76cf459b0 100644 --- a/frontend/app/components/Session_/EventsBlock/Metadata/MetadataItem.js +++ b/frontend/app/components/Session_/EventsBlock/Metadata/MetadataItem.js @@ -49,7 +49,7 @@ export default class extends React.PureComponent { content={ open && } onClose={ open ? this.switchOpen : () => null } /> -
+
{ item.key }
Date: Sun, 27 Feb 2022 18:03:03 +0100 Subject: [PATCH 14/25] fix(ui) - performance var check --- frontend/app/components/Session_/Performance/Performance.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Session_/Performance/Performance.js b/frontend/app/components/Session_/Performance/Performance.js index b31d1ed85..78aea13ce 100644 --- a/frontend/app/components/Session_/Performance/Performance.js +++ b/frontend/app/components/Session_/Performance/Performance.js @@ -115,7 +115,7 @@ const HeapTooltip = ({ active, payload}) => { } const NodesCountTooltip = ({ active, payload} ) => { - if (!active || payload.length === 0) return null; + if (!active || !payload || payload.length === 0) return null; return (

From ffc2f78045c40403f73d9092dc051fa1fa3c19be Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Sun, 27 Feb 2022 19:53:36 +0100 Subject: [PATCH 15/25] fix(ui) - player live check --- frontend/app/components/Session/Session.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/app/components/Session/Session.js b/frontend/app/components/Session/Session.js index 0138e7e50..210a24bff 100644 --- a/frontend/app/components/Session/Session.js +++ b/frontend/app/components/Session/Session.js @@ -16,11 +16,11 @@ const SESSIONS_ROUTE = sessionsRoute(); function Session({ sessionId, loading, - hasErrors, + hasErrors, session, fetchSession, - fetchSlackList, - hasSessionsPath + fetchSlackList, + hasSessionsPath }) { usePageTitle("OpenReplay Session Player"); useEffect(() => { @@ -51,7 +51,7 @@ function Session({ { session.isIOS ? - : (session.live && !hasSessionsPath ? : ) + : } From d8e4f78235ac1bc6b96daeeafb2cb712283b451e Mon Sep 17 00:00:00 2001 From: Mehdi Osman Date: Sun, 27 Feb 2022 19:03:13 +0000 Subject: [PATCH 16/25] Increase ctx timeout to 30s --- backend/pkg/db/postgres/connector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/pkg/db/postgres/connector.go b/backend/pkg/db/postgres/connector.go index 9e4e82633..1eb29e04c 100644 --- a/backend/pkg/db/postgres/connector.go +++ b/backend/pkg/db/postgres/connector.go @@ -10,7 +10,7 @@ import ( ) func getTimeoutContext() context.Context { - ctx, _ := context.WithTimeout(context.Background(), time.Duration(time.Second*10)) + ctx, _ := context.WithTimeout(context.Background(), time.Duration(time.Second*30)) return ctx } From b74e9844b1ec3ec59f719c8d0e84c7ebbfccda9c Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Sun, 27 Feb 2022 22:14:47 +0100 Subject: [PATCH 17/25] feat(frontend-assist): 30s for reconnection --- .../app/player/MessageDistributor/managers/AssistManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/player/MessageDistributor/managers/AssistManager.ts b/frontend/app/player/MessageDistributor/managers/AssistManager.ts index 908add195..595cf3b3e 100644 --- a/frontend/app/player/MessageDistributor/managers/AssistManager.ts +++ b/frontend/app/player/MessageDistributor/managers/AssistManager.ts @@ -103,7 +103,7 @@ export default class AssistManager { if (document.hidden && getState().calling === CallingState.NoCall) { this.socket?.close() } - }, 15000) + }, 30000) } else { inactiveTimeout && clearTimeout(inactiveTimeout) this.socket?.open() From 985ab6117bce42a44bfcfa60192166ac0c8d0764 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Mon, 28 Feb 2022 14:58:20 +0100 Subject: [PATCH 18/25] feat(api): fixed update user-role casing --- ee/api/chalicelib/core/users.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/api/chalicelib/core/users.py b/ee/api/chalicelib/core/users.py index c8c1c7669..9ca77c1ea 100644 --- a/ee/api/chalicelib/core/users.py +++ b/ee/api/chalicelib/core/users.py @@ -156,7 +156,7 @@ def update(tenant_id, user_id, changes): (SELECT role_id FROM roles WHERE tenant_id = %(tenant_id)s AND name != 'Owner' LIMIT 1)))""") else: sub_query_users.append(f"{helper.key_to_snake_case(key)} = %({key})s") - + changes["role_id"] = changes.get("roleId", changes.get("role_id")) with pg_client.PostgresClient() as cur: if len(sub_query_users) > 0: cur.execute( From 82e572b9d1337cf8a8b5b13db245e3cbd6d5d4f1 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 1 Mar 2022 13:29:20 +0100 Subject: [PATCH 19/25] feat(ui) - custom metrics - wip --- .../CustomMetriLineChart.tsx | 56 +++++++++++++ .../CustomMetriLineChart/index.ts | 1 + .../CustomMetriPercentage.tsx | 16 ++++ .../CustomMetriPercentage/index.ts | 1 + .../CustomMetricPieChart.css | 6 ++ .../CustomMetricPieChart.tsx | 16 ++++ .../CustomMetricPieChart/index.ts | 1 + .../CustomMetricTable/CustomMetricTable.css | 6 ++ .../CustomMetricTable/CustomMetricTable.tsx | 44 ++++++++++ .../CustomMetricTable/index.ts | 1 + .../CustomMetricWidgetPreview.tsx | 81 ++++++++++--------- .../Dashboard/Widgets/common/Table.js | 12 +-- .../CustomMetricForm/CustomMetricForm.tsx | 68 ++++++++++++---- .../shared/DropdownPlain/DropdownPlain.css | 2 + .../shared/DropdownPlain/DropdownPlain.tsx | 4 +- .../ui/SegmentSelection/SegmentSelection.js | 4 +- .../ui/SegmentSelection/segmentSelection.css | 5 +- frontend/app/constants/filterOptions.js | 2 +- frontend/app/svg/icons/graph-up-arrow.svg | 3 + frontend/app/svg/icons/hash.svg | 3 + frontend/app/types/customMetric.js | 6 +- 21 files changed, 266 insertions(+), 72 deletions(-) create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriPercentage/CustomMetriPercentage.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriPercentage/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.css create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.css create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/index.ts create mode 100644 frontend/app/svg/icons/graph-up-arrow.svg create mode 100644 frontend/app/svg/icons/hash.svg diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx new file mode 100644 index 000000000..9f68abf3a --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx @@ -0,0 +1,56 @@ +import React from 'react' +import { Styles } from '../../common'; +import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; +import { LineChart, Line, Legend } from 'recharts'; + +interface Props { + data: any; + params: any; + seriesMap: any; + colors: any; +} +function CustomMetriLineChart(props: Props) { + const { data, params, seriesMap, colors } = props; + return ( + + + + + + + + { seriesMap.map((key, index) => ( + + ))} + + + ) +} + +export default CustomMetriLineChart diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/index.ts new file mode 100644 index 000000000..f05a16274 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/index.ts @@ -0,0 +1 @@ +export { default } from './CustomMetriLineChart'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriPercentage/CustomMetriPercentage.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriPercentage/CustomMetriPercentage.tsx new file mode 100644 index 000000000..7138f2b1c --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriPercentage/CustomMetriPercentage.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +interface Props { + data: any; +} +function CustomMetriPercentage(props: Props) { + const { data } = props; + return ( +

+
0%
+
0 ( 0.0% ) from previous hour
+
+ ) +} + +export default CustomMetriPercentage; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriPercentage/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriPercentage/index.ts new file mode 100644 index 000000000..a36f4ae23 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriPercentage/index.ts @@ -0,0 +1 @@ +export { default } from './CustomMetriPercentage'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.css b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.css new file mode 100644 index 000000000..1d1ef3ee4 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.css @@ -0,0 +1,6 @@ +.wrapper { + background-color: white; + /* border: solid thin $gray-medium; */ + border-radius: 3px; + padding: 10px; +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx new file mode 100644 index 000000000..a771a0f45 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +interface Props { + data: any; +} +function CustomMetricPieChart(props: Props) { + const { data } = props; + return ( +
+
0%
+
0 ( 0.0% ) from previous hour
+
+ ) +} + +export default CustomMetricPieChart; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/index.ts new file mode 100644 index 000000000..6bdaf2270 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/index.ts @@ -0,0 +1 @@ +export { default } from './CustomMetricPieChart'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.css b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.css new file mode 100644 index 000000000..1d1ef3ee4 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.css @@ -0,0 +1,6 @@ +.wrapper { + background-color: white; + /* border: solid thin $gray-medium; */ + border-radius: 3px; + padding: 10px; +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx new file mode 100644 index 000000000..9ab0d72eb --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx @@ -0,0 +1,44 @@ +import React from 'react' +import { Table } from '../../common'; +import { List } from 'immutable'; + +const cols = [ + { + key: 'name', + title: 'Resource', + toText: name => name, + width: '70%', + }, + { + key: 'sessions', + title: 'Sessions', + toText: sessions => sessions, + width: '30%', + }, +]; + +interface Props { + data: any; +} +function CustomMetriTable(props: Props) { + const { data } = props; + const rows = List([ + { name: 'one', sessions: 2 }, + { name: 'two', sessions: 3 }, + { name: 'three', sessions: 4 }, + { name: 'four', sessions: 1 }, + { name: 'five', sessions: 6 }, + ]) + return ( +
+ + + ) +} + +export default CustomMetriTable; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/index.ts new file mode 100644 index 000000000..dc43c93b4 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/index.ts @@ -0,0 +1 @@ +export { default } from './CustomMetricTable'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx index 8af9784d5..f0ca8aa80 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, useRef } from 'react'; import { connect } from 'react-redux'; -import { Loader, NoContent, Icon } from 'UI'; +import { Loader, NoContent, SegmentSelection, Icon } from 'UI'; import { Styles } from '../../common'; import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip, LineChart, Line, Legend } from 'recharts'; import Period, { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; @@ -9,6 +9,9 @@ import { getChartFormatter } from 'Types/dashboard/helper'; import { remove } from 'Duck/customMetrics'; import DateRange from 'Shared/DateRange'; import { edit } from 'Duck/customMetrics'; +import CustomMetriLineChart from '../CustomMetriLineChart'; +import CustomMetriPercentage from '../CustomMetriPercentage'; +import CustomMetricTable from '../CustomMetricTable'; import APIClient from 'App/api_client'; @@ -83,11 +86,28 @@ function CustomMetricWidget(props: Props) { props.edit({ ...changedDates, rangeName: changedDates.rangeValue }); } + const chagneViewType = (e, { name, value }) => { + props.edit({ [ name ]: value }); + } + return (
Preview
-
+
+ +
+ Time Range - - - - - - - - { seriesMap.map((key, index) => ( - + { metric.viewType === 'percent' && ( + + )} + { metric.viewType === 'chart' && ( + - ))} - - + )} + + )} + + { metric.metricType === 'table' && ( +
+ +
+ )}
diff --git a/frontend/app/components/Dashboard/Widgets/common/Table.js b/frontend/app/components/Dashboard/Widgets/common/Table.js index 0aecca5ea..73192a501 100644 --- a/frontend/app/components/Dashboard/Widgets/common/Table.js +++ b/frontend/app/components/Dashboard/Widgets/common/Table.js @@ -16,7 +16,8 @@ export default class Table extends React.PureComponent { rowProps, rowClass = '', small = false, - compare = false + compare = false, + maxHeight = 200, } = this.props; const { showAll } = this.state; @@ -30,7 +31,7 @@ export default class Table extends React.PureComponent {
{ title }
) }
-
+
{ rows.take(showAll ? 10 : (small ? 3 : 5)).map(row => (
{ cols.map(({ cellClass = '', className = '', Component, key, toText = t => t, width }) => ( @@ -41,9 +42,9 @@ export default class Table extends React.PureComponent {
)) }
- )) } - - { rows.size > (small ? 3 : 5) && !showAll && + )) } +
+ { rows.size > (small ? 3 : 5) && !showAll &&
} -
); } diff --git a/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx b/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx index fd95cd5c6..754af89e2 100644 --- a/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx +++ b/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx @@ -24,6 +24,8 @@ interface Props { function CustomMetricForm(props: Props) { const { metric, loading } = props; const metricOfOptions = metricOf.filter(i => i.key === metric.metricType); + const timeseriesOptions = metricOf.filter(i => i.key === 'timeseries'); + const tableOptions = metricOf.filter(i => i.key === 'table'); const addSeries = () => { props.addSeries(); @@ -34,7 +36,17 @@ function CustomMetricForm(props: Props) { } const write = ({ target: { value, name } }) => props.editMetric({ [ name ]: value }, false); - const writeOption = (e, { value, name }) => props.editMetric({ [ name ]: value }, false); + const writeOption = (e, { value, name }) => { + props.editMetric({ [ name ]: value }, false); + + if (name === 'metricType') { + if (value === 'timeseries') { + props.editMetric({ metricOf: timeseriesOptions[0].value }, false); + } else if (value === 'table') { + props.editMetric({ metricOf: tableOptions[0].value }, false); + } + } + }; const changeConditionTab = (e, { name, value }) => { props.editMetric({[ 'viewType' ]: value }); @@ -89,22 +101,44 @@ function CustomMetricForm(props: Props) { value={ metric.metricType } onChange={ writeOption } /> - of - - showing - + + {metric.metricType === 'timeseries' && ( + <> + of + + + )} + + {metric.metricType === 'table' && ( + <> + of + + + )} + + {metric.metricType === 'table' && ( + <> + showing + + + )}
{/*
Timeseries diff --git a/frontend/app/components/shared/DropdownPlain/DropdownPlain.css b/frontend/app/components/shared/DropdownPlain/DropdownPlain.css index 87e26bc68..1bf3e305c 100644 --- a/frontend/app/components/shared/DropdownPlain/DropdownPlain.css +++ b/frontend/app/components/shared/DropdownPlain/DropdownPlain.css @@ -4,6 +4,8 @@ border-radius: 3px; color: $gray-darkest; font-weight: 500; + background-color: white; + border: solid thin $gray-light; &:hover { background-color: $gray-light; } diff --git a/frontend/app/components/shared/DropdownPlain/DropdownPlain.tsx b/frontend/app/components/shared/DropdownPlain/DropdownPlain.tsx index d3313ac3e..07d64aa5d 100644 --- a/frontend/app/components/shared/DropdownPlain/DropdownPlain.tsx +++ b/frontend/app/components/shared/DropdownPlain/DropdownPlain.tsx @@ -12,7 +12,7 @@ interface Props { } export default function DropdownPlain(props: Props) { - const { name = "sort", value, options, icon = "chevron-down", direction = "left" } = props; + const { name = "sort", value, options, icon = "chevron-down", direction = "right" } = props; return (
{ list.map(item => ( @@ -27,7 +27,7 @@ class SegmentSelection extends React.Component { data-active={ this.props.value && this.props.value.value === item.value } onClick={ () => !item.disabled && this.setActiveItem(item) } > - { item.icon && } + { item.icon && }
{ item.name }
} diff --git a/frontend/app/components/ui/SegmentSelection/segmentSelection.css b/frontend/app/components/ui/SegmentSelection/segmentSelection.css index 20007b010..197627c9f 100644 --- a/frontend/app/components/ui/SegmentSelection/segmentSelection.css +++ b/frontend/app/components/ui/SegmentSelection/segmentSelection.css @@ -12,13 +12,13 @@ padding: 10px; flex: 1; text-align: center; - border-right: solid thin $teal; cursor: pointer; background-color: $gray-lightest; display: flex; align-items: center; justify-content: center; white-space: nowrap; + border-right: solid thin $gray-light; & span svg { fill: $gray-medium; @@ -53,6 +53,7 @@ & .item { color: $teal; background-color: white; + border-right: solid thin $teal; &[data-active=true] { background-color: $teal; color: white; @@ -65,6 +66,6 @@ } .extraSmall .item { - padding: 0 4px; + padding: 6px !important; font-size: 12px; } \ No newline at end of file diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index 215832c1a..9884d7851 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -61,7 +61,7 @@ export const metricTypes = [ export const metricOf = [ { text: 'Session Count', value: 'sessionCount', key: 'timeseries' }, - { text: 'Users', value: 'users', key: 'table' }, + { text: 'Users', value: 'USERID', key: 'table' }, { text: 'Rage Click', value: 'rageClick', key: 'table' }, { text: 'Dead Click', value: 'deadClick', key: 'table' }, { text: 'Browser', value: 'browser', key: 'table' }, diff --git a/frontend/app/svg/icons/graph-up-arrow.svg b/frontend/app/svg/icons/graph-up-arrow.svg new file mode 100644 index 000000000..fd582e467 --- /dev/null +++ b/frontend/app/svg/icons/graph-up-arrow.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/hash.svg b/frontend/app/svg/icons/hash.svg new file mode 100644 index 000000000..4621b1dac --- /dev/null +++ b/frontend/app/svg/icons/hash.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/types/customMetric.js b/frontend/app/types/customMetric.js index 902c10a23..9b3585be1 100644 --- a/frontend/app/types/customMetric.js +++ b/frontend/app/types/customMetric.js @@ -27,9 +27,9 @@ export const FilterSeries = Record({ export default Record({ metricId: undefined, name: 'Series', - metricType: 'timeseries', - metricOf: 'sessionCount', - viewType: 'sessionCount', + metricType: 'table', + metricOf: 'USERID', + viewType: 'lineChart', series: List(), isPublic: true, startDate: '', From 01cfb41de10927acac5529d1dcb258da2a113a95 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 1 Mar 2022 14:18:35 +0100 Subject: [PATCH 20/25] feat(ui) - check for alphanumeric in version compare --- .../TrackerUpdateMessage.js | 21 ++++++++++++------- frontend/app/utils.js | 6 ++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/frontend/app/components/shared/TrackerUpdateMessage/TrackerUpdateMessage.js b/frontend/app/components/shared/TrackerUpdateMessage/TrackerUpdateMessage.js index a5fe4411c..787751d79 100644 --- a/frontend/app/components/shared/TrackerUpdateMessage/TrackerUpdateMessage.js +++ b/frontend/app/components/shared/TrackerUpdateMessage/TrackerUpdateMessage.js @@ -1,18 +1,25 @@ -import React from 'react' +import React, { useEffect } from 'react' import { Icon } from 'UI' import { connect } from 'react-redux' import { withRouter } from 'react-router-dom'; import { onboarding as onboardingRoute } from 'App/routes' import { withSiteId } from 'App/routes'; +import { isGreaterOrEqualVersion } from 'App/utils' const TrackerUpdateMessage= (props) => { - // const { site } = props; - const { site, sites, match: { params: { siteId } } } = props; + const [needUpdate, setNeedUpdate] = React.useState(false) + const { sites, match: { params: { siteId } } } = props; const activeSite = sites.find(s => s.id == siteId); - const hasSessions = !!activeSite && !activeSite.recorded; - const appVersionInt = parseInt(window.ENV.TRACKER_VERSION.split(".").join("")) - const trackerVersionInt = site.trackerVersion ? parseInt(site.trackerVersion.split(".").join("")) : 0 - const needUpdate = !hasSessions && appVersionInt > trackerVersionInt; + + useEffect(() => { + if (!activeSite || !activeSite.trackerVersion) return; + + const isLatest = isGreaterOrEqualVersion(activeSite.trackerVersion, window.ENV.TRACKER_VERSION); + if (!isLatest && activeSite.recorded) { + setNeedUpdate(true) + } + }, [activeSite]) + return needUpdate ? ( <> {( diff --git a/frontend/app/utils.js b/frontend/app/utils.js index 52b2a9d6a..4c1a0c607 100644 --- a/frontend/app/utils.js +++ b/frontend/app/utils.js @@ -226,4 +226,10 @@ export const iceServerConfigFromString = (str) => { return server } }) +} + +export const isGreaterOrEqualVersion = (version, compareTo) => { + const [major, minor, patch] = version.split("-")[0].split('.'); + const [majorC, minorC, patchC] = compareTo.split("-")[0].split('.'); + return (major > majorC) || (major === majorC && minor > minorC) || (major === majorC && minor === minorC && patch >= patchC); } \ No newline at end of file From 6e7f0c33a7bdd83007c257bc757d35f8df9d5461 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 1 Mar 2022 16:03:41 +0100 Subject: [PATCH 21/25] feat(ui) - api recalls --- frontend/app/Router.js | 21 +++++++++++++- .../Alerts/Notifications/Notifications.js | 6 ++-- .../components/Announcements/Announcements.js | 6 +--- .../app/components/BugFinder/BugFinder.js | 28 ------------------- .../app/components/BugFinder/DateRange.js | 2 +- .../BugFinder/SessionsMenu/SessionsMenu.js | 6 ++-- frontend/app/components/Client/Client.js | 2 -- frontend/app/components/Header/Header.js | 4 --- .../app/components/Header/SiteDropdown.js | 12 ++++++-- 9 files changed, 37 insertions(+), 50 deletions(-) diff --git a/frontend/app/Router.js b/frontend/app/Router.js index 86f07c4f6..782f47e74 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -21,6 +21,10 @@ import Header from 'Components/Header/Header'; import FunnelDetails from 'Components/Funnels/FunnelDetails'; import FunnelIssueDetails from 'Components/Funnels/FunnelIssueDetails'; import { fetchList as fetchIntegrationVariables } from 'Duck/customField'; +import { fetchList as fetchSiteList } from 'Duck/site'; +import { fetchList as fetchAnnouncements } from 'Duck/announcements'; +import { fetchList as fetchAlerts } from 'Duck/alerts'; +import { fetchWatchdogStatus } from 'Duck/watchdogs'; import APIClient from './api_client'; import * as routes from './routes'; @@ -80,7 +84,14 @@ const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB); onboarding: state.getIn([ 'user', 'onboarding' ]) }; }, { - fetchUserInfo, fetchTenants, setSessionPath, fetchIntegrationVariables + fetchUserInfo, + fetchTenants, + setSessionPath, + fetchIntegrationVariables, + fetchSiteList, + fetchAnnouncements, + fetchAlerts, + fetchWatchdogStatus, }) class Router extends React.Component { state = { @@ -93,6 +104,14 @@ class Router extends React.Component { props.fetchUserInfo().then(() => { props.fetchIntegrationVariables() }), + props.fetchSiteList().then(() => { + setTimeout(() => { + props.fetchAnnouncements(); + props.fetchAlerts(); + props.fetchWatchdogStatus(); + }, 100); + }), + // props.fetchAnnouncements(), ]) // .then(() => this.onLoginLogout()); } diff --git a/frontend/app/components/Alerts/Notifications/Notifications.js b/frontend/app/components/Alerts/Notifications/Notifications.js index deb14b640..a30ad824f 100644 --- a/frontend/app/components/Alerts/Notifications/Notifications.js +++ b/frontend/app/components/Alerts/Notifications/Notifications.js @@ -18,9 +18,9 @@ class Notifications extends React.Component { constructor(props) { super(props); - setTimeout(() => { - props.fetchList(); - }, 1000); + // setTimeout(() => { + // props.fetchList(); + // }, 1000); setInterval(() => { props.fetchList(); diff --git a/frontend/app/components/Announcements/Announcements.js b/frontend/app/components/Announcements/Announcements.js index ff392e2e7..733b4ce37 100644 --- a/frontend/app/components/Announcements/Announcements.js +++ b/frontend/app/components/Announcements/Announcements.js @@ -10,11 +10,7 @@ import { withRouter } from 'react-router-dom'; @withToggle('visible', 'toggleVisisble') @withRouter class Announcements extends React.Component { - constructor(props) { - super(props); - props.fetchList(); - } - + navigateToUrl = url => { if (url) { if (url.startsWith(window.ENV.ORIGIN)) { diff --git a/frontend/app/components/BugFinder/BugFinder.js b/frontend/app/components/BugFinder/BugFinder.js index f0024c20a..6d30359f1 100644 --- a/frontend/app/components/BugFinder/BugFinder.js +++ b/frontend/app/components/BugFinder/BugFinder.js @@ -4,14 +4,11 @@ import withPageTitle from 'HOCs/withPageTitle'; import { fetchFavoriteList as fetchFavoriteSessionList } from 'Duck/sessions'; -import { countries } from 'App/constants'; import { applyFilter, clearEvents, addAttribute } from 'Duck/filters'; import { fetchList as fetchFunnelsList } from 'Duck/funnels'; -import { defaultFilters, preloadedFilters } from 'Types/filter'; import { KEYS } from 'Types/filter/customFilter'; import SessionList from './SessionList'; import stl from './bugFinder.css'; -import { fetchList as fetchSiteList } from 'Duck/site'; import withLocationHandlers from "HOCs/withLocationHandlers"; import { fetch as fetchFilterVariables } from 'Duck/sources'; import { fetchSources } from 'Duck/customField'; @@ -68,7 +65,6 @@ const allowedQueryKeys = [ fetchSources, clearEvents, setActiveTab, - fetchSiteList, fetchFunnelsList, resetFunnel, resetFunnelFilters, @@ -81,7 +77,6 @@ export default class BugFinder extends React.PureComponent { state = {showRehydratePanel: false} constructor(props) { super(props); - // props.fetchFavoriteSessionList(); // TODO should cache the response // props.fetchSources().then(() => { @@ -115,29 +110,6 @@ export default class BugFinder extends React.PureComponent { this.setState({ showRehydratePanel: !this.state.showRehydratePanel }) } - // fetchPreloadedFilters = () => { - // this.props.fetchFilterVariables('filterValues').then(function() { - // const { filterValues } = this.props; - // const keys = [ - // {key: KEYS.USER_OS, label: 'OS'}, - // {key: KEYS.USER_BROWSER, label: 'Browser'}, - // {key: KEYS.USER_DEVICE, label: 'Device'}, - // {key: KEYS.REFERRER, label: 'Referrer'}, - // {key: KEYS.USER_COUNTRY, label: 'Country'}, - // ] - // if (filterValues && filterValues.size != 0) { - // keys.forEach(({key, label}) => { - // const _keyFilters = filterValues.get(key) - // if (key === KEYS.USER_COUNTRY) { - // preloadedFilters.push(_keyFilters.map(item => ({label, type: key, key, value: item, actualValue: countries[item], isFilter: true}))); - // } else { - // preloadedFilters.push(_keyFilters.map(item => ({label, type: key, key, value: item, isFilter: true}))); - // } - // }) - // } - // }.bind(this)); - // } - setActiveTab = tab => { this.props.setActiveTab(tab); } diff --git a/frontend/app/components/BugFinder/DateRange.js b/frontend/app/components/BugFinder/DateRange.js index ed8c76c98..c0758b426 100644 --- a/frontend/app/components/BugFinder/DateRange.js +++ b/frontend/app/components/BugFinder/DateRange.js @@ -10,7 +10,7 @@ import DateRangeDropdown from 'Shared/DateRangeDropdown'; }) export default class DateRange extends React.PureComponent { onDateChange = (e) => { - this.props.fetchFunnelsList(e.rangeValue) + // this.props.fetchFunnelsList(e.rangeValue) this.props.applyFilter(e) } render() { diff --git a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js index 2436d4be6..be98a28cd 100644 --- a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js +++ b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js @@ -22,9 +22,9 @@ function SessionsMenu(props) { } } - useEffect(() => { - fetchWatchdogStatus() - }, []) + // useEffect(() => { + // fetchWatchdogStatus() + // }, []) const capturingAll = props.captureRate && props.captureRate.get('captureAll'); diff --git a/frontend/app/components/Client/Client.js b/frontend/app/components/Client/Client.js index 6cae36710..cb55d933d 100644 --- a/frontend/app/components/Client/Client.js +++ b/frontend/app/components/Client/Client.js @@ -3,7 +3,6 @@ import { withRouter } from 'react-router-dom'; import { Switch, Route, Redirect } from 'react-router'; import { CLIENT_TABS, client as clientRoute } from 'App/routes'; import { fetchList as fetchMemberList } from 'Duck/member'; -import { fetchList as fetchSiteList } from 'Duck/site'; import ProfileSettings from './ProfileSettings'; import Integrations from './Integrations'; @@ -21,7 +20,6 @@ import Roles from './Roles'; appearance: state.getIn([ 'user', 'account', 'appearance' ]), }), { fetchMemberList, - fetchSiteList, }) @withRouter export default class Client extends React.PureComponent { diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 38d958c17..6d541fca7 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -66,10 +66,6 @@ const Header = (props) => { } }, [showTrackingModal]) - useEffect(() => { - fetchSiteList() - }, []) - return (
diff --git a/frontend/app/components/Header/SiteDropdown.js b/frontend/app/components/Header/SiteDropdown.js index 55259c088..74b650055 100644 --- a/frontend/app/components/Header/SiteDropdown.js +++ b/frontend/app/components/Header/SiteDropdown.js @@ -11,6 +11,8 @@ import cn from 'classnames'; import NewSiteForm from '../Client/Sites/NewSiteForm'; import { clearSearch } from 'Duck/search'; import { fetchList as fetchIntegrationVariables } from 'Duck/customField'; +import { fetchList as fetchAlerts } from 'Duck/alerts'; +import { fetchWatchdogStatus } from 'Duck/watchdogs'; @withRouter @connect(state => ({ @@ -23,13 +25,15 @@ import { fetchList as fetchIntegrationVariables } from 'Duck/customField'; init, clearSearch, fetchIntegrationVariables, + fetchAlerts, + fetchWatchdogStatus, }) export default class SiteDropdown extends React.PureComponent { state = { showProductModal: false } - componentDidMount() { - this.props.fetchIntegrationVariables(); - } + // componentDidMount() { + // this.props.fetchIntegrationVariables(); + // } closeModal = (e, newSite) => { this.setState({ showProductModal: false }) @@ -44,6 +48,8 @@ export default class SiteDropdown extends React.PureComponent { this.props.setSiteId(siteId); this.props.clearSearch(); this.props.fetchIntegrationVariables(); + this.props.fetchAlerts(); + this.props.fetchWatchdogStatus(); } render() { From 009cf7b1acaa1537765c5fc3a02a79cad49c8162 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 1 Mar 2022 23:30:03 +0100 Subject: [PATCH 22/25] feat(api): flag live session as viewed --- api/routers/core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/routers/core.py b/api/routers/core.py index 9af9fec63..59bede0ae 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -36,8 +36,7 @@ def get_session2(projectId: int, sessionId: Union[int, str], context: schemas.Cu include_fav_viewed=True, group_metadata=True) if data is None: return {"errors": ["session not found"]} - if not data.get("live"): - sessions_favorite_viewed.view_session(project_id=projectId, user_id=context.user_id, session_id=sessionId) + sessions_favorite_viewed.view_session(project_id=projectId, user_id=context.user_id, session_id=sessionId) return { 'data': data } From 09e3b44914c142af8ab4a120f90018ca3b4ff155 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 2 Mar 2022 10:28:14 +0100 Subject: [PATCH 23/25] feat(ui) - slide modal disable body scroll on open --- .../shared/CustomMetrics/CustomMetrics.tsx | 5 ++++- .../components/ui/SlideModal/SlideModal.js | 22 ++++++++++++++----- frontend/app/constants/filterOptions.js | 9 ++++---- frontend/app/styles/main.css | 6 +++++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx b/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx index aedd4a097..ae0718aea 100644 --- a/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx +++ b/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { IconButton } from 'UI'; import { connect } from 'react-redux'; import { edit, init } from 'Duck/customMetrics'; @@ -7,6 +7,9 @@ interface Props { init: (instance?, setDefault?) => void; } function CustomMetrics(props: Props) { + useEffect(() => { // TODO remove this block + props.init() + }, []) return (
props.init()} /> diff --git a/frontend/app/components/ui/SlideModal/SlideModal.js b/frontend/app/components/ui/SlideModal/SlideModal.js index cc329463c..92f8ba710 100644 --- a/frontend/app/components/ui/SlideModal/SlideModal.js +++ b/frontend/app/components/ui/SlideModal/SlideModal.js @@ -1,12 +1,24 @@ import styles from './slideModal.css'; import cn from 'classnames'; export default class SlideModal extends React.PureComponent { - componentDidMount() { - document.addEventListener('keydown', this.keyPressHandler); - } + // componentDidMount() { + // document.addEventListener('keydown', this.keyPressHandler); + // } - componentWillUnmount() { - document.removeEventListener('keydown', this.keyPressHandler); + // componentWillUnmount() { + // document.removeEventListener('keydown', this.keyPressHandler); + // } + + componentDidUpdate(prevProps) { + if (prevProps.isDisplayed !== this.props.isDisplayed) { + if (this.props.isDisplayed) { + document.addEventListener('keydown', this.keyPressHandler); + document.body.classList.add('no-scroll'); + } else { + document.removeEventListener('keydown', this.keyPressHandler); + document.body.classList.remove('no-scroll'); + } + } } keyPressHandler = (e) => { diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index 9884d7851..3a497e8cf 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -62,10 +62,11 @@ export const metricTypes = [ export const metricOf = [ { text: 'Session Count', value: 'sessionCount', key: 'timeseries' }, { text: 'Users', value: 'USERID', key: 'table' }, - { text: 'Rage Click', value: 'rageClick', key: 'table' }, - { text: 'Dead Click', value: 'deadClick', key: 'table' }, - { text: 'Browser', value: 'browser', key: 'table' }, - { text: 'Device', value: 'device', key: 'table' }, + { text: 'Issues', value: 'ISSUES', key: 'table' }, + { text: 'Browser', value: 'USERBROWSER', key: 'table' }, + { text: 'Device', value: 'USERDEVICE', key: 'table' }, + { text: 'Country', value: 'USERCOUNTRY', key: 'table' }, + { text: 'URL', value: 'VISITED_URL', key: 'table' }, ] export default { diff --git a/frontend/app/styles/main.css b/frontend/app/styles/main.css index 81e5ab814..3b7f5fe4b 100644 --- a/frontend/app/styles/main.css +++ b/frontend/app/styles/main.css @@ -141,4 +141,10 @@ margin: 25px 0; background-color: $gray-light; +} + +.no-scroll { + height: 100vh; + overflow-y: hidden; + padding-right: 15px; } \ No newline at end of file From ed56d206be8e0e3b8b92845dd2a593f11f723485 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 3 Mar 2022 13:24:39 +0100 Subject: [PATCH 24/25] feat(ui) - custom metrics - wip --- .../app/components/Dashboard/Dashboard.js | 1 - .../CustomMetriLineChart.tsx | 4 +- .../CustomMetriPercentage/index.ts | 1 - .../CustomMetricPercentage.tsx} | 7 +- .../CustomMetricPercentage/index.ts | 1 + .../CustomMetricPieChart.tsx | 43 ++++++-- .../CustomMetricWidget/CustomMetricWidget.tsx | 102 +++++++++--------- .../CustomMetricWidgetPreview.tsx | 72 +++++++++---- .../CustomMetricsWidgets.tsx | 12 ++- .../CustomMetricForm/CustomMetricForm.tsx | 61 ++++++----- .../FilterSeries/FilterSeries.tsx | 2 +- .../SessionListModal/SessionListModal.tsx | 3 +- .../shared/DropdownPlain/DropdownPlain.tsx | 4 +- .../shared/FilterDropdown/FilterDropdown.js | 26 ++--- .../shared/Filters/FilterList/FilterList.tsx | 2 +- .../ui/SegmentSelection/SegmentSelection.js | 5 +- .../ui/SegmentSelection/segmentSelection.css | 13 ++- frontend/app/constants/filterOptions.js | 16 +++ frontend/app/svg/icons/table.svg | 3 + frontend/app/types/app/period.js | 6 ++ frontend/app/types/customMetric.js | 4 +- frontend/app/types/filter/newFilter.js | 17 +-- 22 files changed, 254 insertions(+), 151 deletions(-) delete mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriPercentage/index.ts rename frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/{CustomMetriPercentage/CustomMetriPercentage.tsx => CustomMetricPercentage/CustomMetricPercentage.tsx} (55%) create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/index.ts create mode 100644 frontend/app/svg/icons/table.svg diff --git a/frontend/app/components/Dashboard/Dashboard.js b/frontend/app/components/Dashboard/Dashboard.js index aacda8b07..8ad8ecdf8 100644 --- a/frontend/app/components/Dashboard/Dashboard.js +++ b/frontend/app/components/Dashboard/Dashboard.js @@ -240,7 +240,6 @@ export default class Dashboard extends React.PureComponent { Custom Metrics are not supported for comparison.
)} - {/* */}
} > diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx index 9f68abf3a..c9287c4ed 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx @@ -8,15 +8,17 @@ interface Props { params: any; seriesMap: any; colors: any; + onClick?: (event, index) => void; } function CustomMetriLineChart(props: Props) { - const { data, params, seriesMap, colors } = props; + const { data, params, seriesMap, colors, onClick = () => null } = props; return ( void; } function CustomMetriPercentage(props: Props) { const { data } = props; return (
-
0%
-
0 ( 0.0% ) from previous hour
+
{data.count}
+
{`${data.previousCount} ( ${data.countProgress}% ) from previous hour`}
) } diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/index.ts new file mode 100644 index 000000000..0e1f80170 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/index.ts @@ -0,0 +1 @@ +export { default } from './CustomMetricPercentage'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx index a771a0f45..b0f7dc36f 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx @@ -1,15 +1,46 @@ import React from 'react' - +import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; +import { LineChart, Line, Legend, PieChart, Pie } from 'recharts'; +import { Styles } from '../../common'; interface Props { data: any; + params: any; + // seriesMap: any; + colors: any; + onClick?: (event, index) => void; } function CustomMetricPieChart(props: Props) { - const { data } = props; + const { data, params, colors, onClick = () => null } = props; + const data01 = [ + { "name": "Group A", "value": 400 }, + { "name": "Group B", "value": 300 }, + { "name": "Group C", "value": 300 }, + { "name": "Group D", "value": 200 }, + { "name": "Group E", "value": 278 }, + { "name": "Group F", "value": 189 } + ]; return ( -
-
0%
-
0 ( 0.0% ) from previous hour
-
+ //
+ //
0%
+ //
0 ( 0.0% ) from previous hour
+ //
+ + + + + + ) } diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx index bbf37408a..7898b980d 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx @@ -2,22 +2,24 @@ import React, { useEffect, useState } from 'react'; import { connect } from 'react-redux'; import { Loader, NoContent, Icon, Popup } from 'UI'; import { Styles } from '../../common'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import { LineChart, Line, Legend } from 'recharts'; +import { ResponsiveContainer } from 'recharts'; import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; import stl from './CustomMetricWidget.css'; import { getChartFormatter, getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper'; import { init, edit, remove, setAlertMetricId, setActiveWidget, updateActiveState } from 'Duck/customMetrics'; import APIClient from 'App/api_client'; import { setShowAlerts } from 'Duck/dashboard'; +import CustomMetriLineChart from '../CustomMetriLineChart'; +import CustomMetricPieChart from '../CustomMetricPieChart'; +import CustomMetricPercentage from '../CustomMetricPercentage'; const customParams = rangeName => { const params = { density: 70 } - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 + // if (rangeName === LAST_24_HOURS) params.density = 70 + // if (rangeName === LAST_30_MINUTES) params.density = 70 + // if (rangeName === YESTERDAY) params.density = 70 + // if (rangeName === LAST_7_DAYS) params.density = 70 return params } @@ -47,11 +49,10 @@ function CustomMetricWidget(props: Props) { const colors = Styles.customMetricColors; const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart', startDate: period.start, endDate: period.end } useEffect(() => { - new APIClient()['post']('/custom_metrics/chart', { ...metricParams, q: metric.name }) + new APIClient()['post'](`/custom_metrics/${metricParams.metricId}/chart`, { ...metricParams, q: metric.name }) .then(response => response.json()) .then(({ errors, data }) => { if (errors) { @@ -78,8 +79,19 @@ function CustomMetricWidget(props: Props) { if (event) { const payload = event.activePayload[0].payload; const timestamp = payload.timestamp; - const { startTimestamp, endTimestamp } = getStartAndEndTimestampsByDensity(timestamp, period.start, period.end, params.density); - props.setActiveWidget({ widget: metric, startTimestamp, endTimestamp, timestamp: payload.timestamp, index }) + const periodTimestamps = metric.metricType === 'timeseries' ? + getStartAndEndTimestampsByDensity(timestamp, period.start, period.end, params.density) : + period.toTimestamps(); + + const activeWidget = { + widget: metric, + period: period, + ...periodTimestamps, + timestamp: payload.timestamp, + index, + } + + props.setActiveWidget(activeWidget); } } @@ -104,49 +116,35 @@ function CustomMetricWidget(props: Props) { show={ data.length === 0 } > - - - - - - - - - - - - - { seriesMap.map((key, index) => ( - + {metric.viewType === 'lineChart' && ( + - ))} - + )} + + {metric.viewType === 'pieChart' && ( + + )} + + {metric.viewType === 'progress' && ( + + )} + diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx index f0ca8aa80..7aa43f94b 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react'; import { connect } from 'react-redux'; import { Loader, NoContent, SegmentSelection, Icon } from 'UI'; import { Styles } from '../../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip, LineChart, Line, Legend } from 'recharts'; +// import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip, LineChart, Line, Legend } from 'recharts'; import Period, { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; import stl from './CustomMetricWidgetPreview.css'; import { getChartFormatter } from 'Types/dashboard/helper'; @@ -10,10 +10,11 @@ import { remove } from 'Duck/customMetrics'; import DateRange from 'Shared/DateRange'; import { edit } from 'Duck/customMetrics'; import CustomMetriLineChart from '../CustomMetriLineChart'; -import CustomMetriPercentage from '../CustomMetriPercentage'; +import CustomMetricPercentage from '../CustomMetricPercentage'; import CustomMetricTable from '../CustomMetricTable'; import APIClient from 'App/api_client'; +import CustomMetricPieChart from '../CustomMetricPieChart'; const customParams = rangeName => { const params = { density: 70 } @@ -46,8 +47,9 @@ function CustomMetricWidget(props: Props) { const params = customParams(period.rangeName) const gradientDef = Styles.gradientDef(); const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart' } - const prevMetricRef = useRef(); + const isTimeSeries = metric.metricType === 'timeseries'; + const isTable = metric.metricType === 'table'; useEffect(() => { // Check for title change @@ -95,17 +97,35 @@ function CustomMetricWidget(props: Props) {
Preview
- + {isTimeSeries && ( + + )} + + {isTable && ( + + )}
Time Range - { metric.metricType === 'timeseries' && ( + { isTimeSeries && ( <> - { metric.viewType === 'percent' && ( - + { metric.viewType === 'progress' && ( + )} - { metric.viewType === 'chart' && ( + { metric.viewType === 'lineChart' && ( )} - { metric.metricType === 'table' && ( + { isTable && (
- + { metric.viewType === 'table' ? ( + + ) : ( + + )}
)} diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricsWidgets.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricsWidgets.tsx index 6cf633518..57c181904 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricsWidgets.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricsWidgets.tsx @@ -5,6 +5,7 @@ import CustomMetricWidget from './CustomMetricWidget'; import AlertFormModal from 'App/components/Alerts/AlertFormModal'; import { init as initAlert } from 'Duck/alerts'; import LazyLoad from 'react-lazyload'; +import CustomMetrics from 'App/components/shared/CustomMetrics'; interface Props { fetchList: Function; @@ -22,7 +23,7 @@ function CustomMetricsWidgets(props: Props) { return ( <> - {list.filter(item => item.active).map((item: any) => ( + {list.map((item: any) => ( ))} + {list.size === 0 && ( +
+
Be proactive by monitoring the metrics you care about the most.
+ +
+ )} + ({ - list: state.getIn(['customMetrics', 'list']), + list: state.getIn(['customMetrics', 'list']).filter(item => item.active), }), { fetchList, initAlert })(CustomMetricsWidgets); \ No newline at end of file diff --git a/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx b/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx index 754af89e2..b10b1dfe4 100644 --- a/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx +++ b/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Form, SegmentSelection, Button, IconButton } from 'UI'; +import { Form, Button, IconButton } from 'UI'; import FilterSeries from '../FilterSeries'; import { connect } from 'react-redux'; import { edit as editMetric, save, addSeries, removeSeries, remove } from 'Duck/customMetrics'; @@ -8,8 +8,7 @@ import { confirm } from 'UI/Confirmation'; import { toast } from 'react-toastify'; import cn from 'classnames'; import DropdownPlain from '../../DropdownPlain'; -import { metricTypes, metricOf } from 'App/constants/filterOptions'; - +import { metricTypes, metricOf, issueOptions } from 'App/constants/filterOptions'; interface Props { metric: any; editMetric: (metric, shouldFetch?) => void; @@ -23,7 +22,7 @@ interface Props { function CustomMetricForm(props: Props) { const { metric, loading } = props; - const metricOfOptions = metricOf.filter(i => i.key === metric.metricType); + // const metricOfOptions = metricOf.filter(i => i.key === metric.metricType); const timeseriesOptions = metricOf.filter(i => i.key === 'timeseries'); const tableOptions = metricOf.filter(i => i.key === 'table'); @@ -39,18 +38,28 @@ function CustomMetricForm(props: Props) { const writeOption = (e, { value, name }) => { props.editMetric({ [ name ]: value }, false); + if (name === 'metricValue') { + props.editMetric({ metricValue: [value] }, false); + } + + if (name === 'metricOf') { + if (value === 'ISSUES') { + props.editMetric({ metricValue: [issueOptions[0].value] }, false); + } + } + if (name === 'metricType') { if (value === 'timeseries') { - props.editMetric({ metricOf: timeseriesOptions[0].value }, false); + props.editMetric({ metricOf: timeseriesOptions[0].value, viewType: 'lineChart' }, false); } else if (value === 'table') { - props.editMetric({ metricOf: tableOptions[0].value }, false); + props.editMetric({ metricOf: tableOptions[0].value, viewType: 'table' }, false); } } }; - const changeConditionTab = (e, { name, value }) => { - props.editMetric({[ 'viewType' ]: value }); - }; + // const changeConditionTab = (e, { name, value }) => { + // props.editMetric({[ 'viewType' ]: value }); + // }; const save = () => { props.save(metric).then(() => { @@ -126,38 +135,32 @@ function CustomMetricForm(props: Props) { )} + {metric.metricOf === 'ISSUES' && ( + <> + issue type + + + )} + {metric.metricType === 'table' && ( <> showing )}
- {/*
- Timeseries - of -
- -
-
*/}
diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx index 0f5df220b..7054e4516 100644 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx @@ -81,7 +81,7 @@ function FilterSeries(props: Props) {
Add user event or filter to define the series by clicking Add Step.
)}
-
+
setActiveSeries(value); const filteredSessions = getListSessionsBySeries(activeSeries); - const startTime = DateTime.fromMillis(activeWidget.startTimestamp).toFormat('LLL dd, yyyy HH:mm a'); const endTime = DateTime.fromMillis(activeWidget.endTimestamp).toFormat('LLL dd, yyyy HH:mm a'); + return ( : null } diff --git a/frontend/app/components/shared/FilterDropdown/FilterDropdown.js b/frontend/app/components/shared/FilterDropdown/FilterDropdown.js index 66da1f586..5b8a4ac76 100644 --- a/frontend/app/components/shared/FilterDropdown/FilterDropdown.js +++ b/frontend/app/components/shared/FilterDropdown/FilterDropdown.js @@ -100,18 +100,20 @@ const FilterDropdown = props => {
)} {showDropdown && ( -
-
SELECT FILTER
- {filterKeys.filter(f => !filterKeyMaps.includes(f.key)).map(f => ( -
onFilterKeySelect(f.key)} - className={cn(stl.filterItem, 'py-3 -mx-3 px-3 flex items-center cursor-pointer')} - > - - {f.name} -
- ))} +
+
SELECT FILTER
+
+ {filterKeys.filter(f => !filterKeyMaps.includes(f.key)).map(f => ( +
onFilterKeySelect(f.key)} + className={cn(stl.filterItem, 'py-3 -mx-3 px-3 flex items-center cursor-pointer')} + > + + {f.name} +
+ ))} +
)} {filterKey && ( diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx index 0e62d20e1..8eba891bb 100644 --- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx +++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx @@ -32,7 +32,7 @@ function FilterList(props: Props) {
Events Order
} - content={ `Events Order` } + content={ `Select the operator to be applied between events in your search.` } size="tiny" inverted position="top center" diff --git a/frontend/app/components/ui/SegmentSelection/SegmentSelection.js b/frontend/app/components/ui/SegmentSelection/SegmentSelection.js index f564131cb..e18faf096 100644 --- a/frontend/app/components/ui/SegmentSelection/SegmentSelection.js +++ b/frontend/app/components/ui/SegmentSelection/SegmentSelection.js @@ -9,13 +9,14 @@ class SegmentSelection extends React.Component { } render() { - const { className, list, small = false, extraSmall = false, primary = false, size = "normal" } = this.props; + const { className, list, small = false, extraSmall = false, primary = false, size = "normal", icons = false } = this.props; return (
{ list.map(item => ( @@ -27,7 +28,7 @@ class SegmentSelection extends React.Component { data-active={ this.props.value && this.props.value.value === item.value } onClick={ () => !item.disabled && this.setActiveItem(item) } > - { item.icon && } + { item.icon && }
{ item.name }
} diff --git a/frontend/app/components/ui/SegmentSelection/segmentSelection.css b/frontend/app/components/ui/SegmentSelection/segmentSelection.css index 197627c9f..f63559c0c 100644 --- a/frontend/app/components/ui/SegmentSelection/segmentSelection.css +++ b/frontend/app/components/ui/SegmentSelection/segmentSelection.css @@ -54,9 +54,15 @@ color: $teal; background-color: white; border-right: solid thin $teal; + & svg { + fill: $teal !important; + } &[data-active=true] { background-color: $teal; color: white; + & svg { + fill: white !important; + } } } } @@ -66,6 +72,11 @@ } .extraSmall .item { - padding: 6px !important; + padding: 0 4px !important; + font-size: 12px; +} + +.icons .item { + padding: 4px !important; font-size: 12px; } \ No newline at end of file diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index 3a497e8cf..6b9b5b70d 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -69,6 +69,21 @@ export const metricOf = [ { text: 'URL', value: 'VISITED_URL', key: 'table' }, ] +export const issueOptions = [ + { text: 'Click Rage', value: 'click_rage' }, + { text: 'Dead Click', value: 'dead_click' }, + { text: 'Excessive Scrolling', value: 'excessive_scrolling' }, + { text: 'Bad Request', value: 'bad_request' }, + { text: 'Missing Resource', value: 'missing_resource' }, + { text: 'Memory', value: 'memory' }, + { text: 'CPU', value: 'cpu' }, + { text: 'Slow Resource', value: 'slow_resource' }, + { text: 'Slow Page Load', value: 'slow_page_load' }, + { text: 'Crash', value: 'crash' }, + { text: 'Custom', value: 'custom' }, + { text: 'JS Exception', value: 'js_exception' }, +] + export default { options, baseOperators, @@ -79,4 +94,5 @@ export default { getOperatorsByKeys, metricTypes, metricOf, + issueOptions, } \ No newline at end of file diff --git a/frontend/app/svg/icons/table.svg b/frontend/app/svg/icons/table.svg new file mode 100644 index 000000000..5e70d22c4 --- /dev/null +++ b/frontend/app/svg/icons/table.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/types/app/period.js b/frontend/app/types/app/period.js index 5e5e9aba8..deebf9cad 100644 --- a/frontend/app/types/app/period.js +++ b/frontend/app/types/app/period.js @@ -103,5 +103,11 @@ export default Record({ endTimestamp: this.end, }; }, + toTimestampstwo() { + return { + startTimestamp: this.start / 1000, + endTimestamp: this.end / 1000, + }; + }, } }); \ No newline at end of file diff --git a/frontend/app/types/customMetric.js b/frontend/app/types/customMetric.js index 9b3585be1..9ce5841f0 100644 --- a/frontend/app/types/customMetric.js +++ b/frontend/app/types/customMetric.js @@ -29,7 +29,9 @@ export default Record({ name: 'Series', metricType: 'table', metricOf: 'USERID', - viewType: 'lineChart', + metricValue: ['sessionCount'], + metricFormat: 'sessionCount', + viewType: 'table', series: List(), isPublic: true, startDate: '', diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index 99726b4a6..db0248458 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -6,21 +6,6 @@ import { capitalize } from 'App/utils'; const countryOptions = Object.keys(countries).map(i => ({ text: countries[i], value: i })); const containsFilters = [{ key: 'contains', text: 'contains', value: 'contains' }] -const ISSUE_OPTIONS = [ - { text: 'Click Rage', value: 'click_rage' }, - { text: 'Dead Click', value: 'dead_click' }, - { text: 'Excessive Scrolling', value: 'excessive_scrolling' }, - { text: 'Bad Request', value: 'bad_request' }, - { text: 'Missing Resource', value: 'missing_resource' }, - { text: 'Memory', value: 'memory' }, - { text: 'CPU', value: 'cpu' }, - { text: 'Slow Resource', value: 'slow_resource' }, - { text: 'Slow Page Load', value: 'slow_page_load' }, - { text: 'Crash', value: 'crash' }, - { text: 'Custom', value: 'custom' }, - { text: 'JS Exception', value: 'js_exception' }, -] - export const filtersMap = { // EVENTS [FilterKey.CLICK]: { key: FilterKey.CLICK, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Click', operator: 'on', operatorOptions: filterOptions.targetOperators, icon: 'filters/click', isEvent: true }, @@ -59,7 +44,7 @@ export const filtersMap = { [FilterKey.AVG_CPU_LOAD]: { key: FilterKey.AVG_CPU_LOAD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg CPU Load', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/cpu-load', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, [FilterKey.AVG_MEMORY_USAGE]: { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg Memory Usage', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/memory-load', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, [FilterKey.FETCH_FAILED]: { key: FilterKey.FETCH_FAILED, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Failed Request', operator: 'isAny', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch-failed', isEvent: true }, - [FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/click', options: ISSUE_OPTIONS }, + [FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/click', options: filterOptions.issueOptions }, } export const liveFiltersMap = { From 984b29f451bf994ab1b1895e1bd329598295a108 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 4 Mar 2022 11:32:09 +0100 Subject: [PATCH 25/25] feat(ui) - custom metrics - wip --- .../CustomMetriLineChart.tsx | 1 + .../CustomMetricPercentage.tsx | 4 +- .../CustomMetricPieChart.tsx | 150 +++++++++++++++--- .../CustomMetricTable/CustomMetricTable.tsx | 17 +- .../CustomMetricWidget/CustomMetricWidget.tsx | 18 ++- .../CustomMetricWidgetPreview.css | 11 +- .../CustomMetricWidgetPreview.tsx | 73 +++++---- .../Dashboard/Widgets/common/Styles.js | 2 + .../Dashboard/Widgets/common/Table.js | 4 +- .../CustomMetricForm/CustomMetricForm.tsx | 19 ++- .../FilterSeries/FilterSeries.tsx | 8 +- .../ui/SegmentSelection/SegmentSelection.js | 4 +- .../ui/SegmentSelection/segmentSelection.css | 2 +- frontend/app/types/customMetric.js | 2 +- frontend/app/types/filter/newFilter.js | 2 +- 15 files changed, 227 insertions(+), 90 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx index c9287c4ed..ffbbc6b88 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx @@ -19,6 +19,7 @@ function CustomMetriLineChart(props: Props) { margin={Styles.chartMargins} // syncId={ showSync ? "domainsErrors_4xx" : undefined } onClick={onClick} + isAnimationActive={ false } > void; } function CustomMetriPercentage(props: Props) { - const { data } = props; + const { data = {} } = props; return (
{data.count}
-
{`${data.previousCount} ( ${data.countProgress}% ) from previous hour`}
+
{`${data.previousCount} ( ${data.countProgress}% ) from previous period.`}
) } diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx index b0f7dc36f..99c054fae 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx @@ -1,7 +1,36 @@ import React from 'react' import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import { LineChart, Line, Legend, PieChart, Pie } from 'recharts'; +import { LineChart, Line, Legend, PieChart, Pie, Cell } from 'recharts'; import { Styles } from '../../common'; + + +function renderCustomizedLabel({ + cx, cy, midAngle, innerRadius, outerRadius, value, color, startAngle, endAngle}) { + const RADIAN = Math.PI / 180; + const diffAngle = endAngle - startAngle; + const delta = ((360-diffAngle)/15)-1; + const radius = innerRadius + (outerRadius - innerRadius); + const x = cx + (radius+delta) * Math.cos(-midAngle * RADIAN); + const y = cy + (radius+(delta*delta)) * Math.sin(-midAngle * RADIAN); + return ( + cx ? 'start' : 'end'} dominantBaseline="central" fontSize={12} fontWeight="normal"> + {value} + + ); +}; +function renderCustomizedLabelLine(props){ + let { cx, cy, midAngle, innerRadius, outerRadius, color, startAngle, endAngle } = props; + const RADIAN = Math.PI / 180; + const diffAngle = endAngle - startAngle; + const radius = 10 + innerRadius + (outerRadius - innerRadius); + let path=''; + for(let i=0;i<((360-diffAngle)/15);i++){ + path += `${(cx + (radius+i) * Math.cos(-midAngle * RADIAN))},${(cy + (radius+i*i) * Math.sin(-midAngle * RADIAN))} ` + } + return ( + + ); +} interface Props { data: any; params: any; @@ -9,35 +38,114 @@ interface Props { colors: any; onClick?: (event, index) => void; } + function CustomMetricPieChart(props: Props) { - const { data, params, colors, onClick = () => null } = props; - const data01 = [ - { "name": "Group A", "value": 400 }, - { "name": "Group B", "value": 300 }, - { "name": "Group C", "value": 300 }, - { "name": "Group D", "value": 200 }, - { "name": "Group E", "value": 278 }, - { "name": "Group F", "value": 189 } - ]; + const { data = { values: [] }, params, colors, onClick = () => null } = props; return ( - //
- //
0%
- //
0 ( 0.0% ) from previous hour
- //
- + + labelLine={({ + cx, + cy, + midAngle, + innerRadius, + outerRadius, + value, + index + }) => { + const RADIAN = Math.PI / 180; + let radius1 = 15 + innerRadius + (outerRadius - innerRadius); + let radius2 = innerRadius + (outerRadius - innerRadius); + let x2 = cx + radius1 * Math.cos(-midAngle * RADIAN); + let y2 = cy + radius1 * Math.sin(-midAngle * RADIAN); + let x1 = cx + radius2 * Math.cos(-midAngle * RADIAN); + let y1 = cy + radius2 * Math.sin(-midAngle * RADIAN); + + const percentage = value * 100 / data.values.reduce((a, b) => a + b.sessionCount, 0); + + if (percentage<3){ + return null; + } + + return( + + ) + }} + label={({ + cx, + cy, + midAngle, + innerRadius, + outerRadius, + value, + index + }) => { + const RADIAN = Math.PI / 180; + let radius = 20 + innerRadius + (outerRadius - innerRadius); + let x = cx + radius * Math.cos(-midAngle * RADIAN); + let y = cy + radius * Math.sin(-midAngle * RADIAN); + const percentage = (value / data.values.reduce((a, b) => a + b.sessionCount, 0)) * 100; + if (percentage<3){ + return null; + } + return ( + cx ? "start" : "end"} + dominantBaseline="central" + fill='#3EAAAF' + > + {data.values[index].name} - ({value}) + + ); + }} + // label={({ + // cx, + // cy, + // midAngle, + // innerRadius, + // outerRadius, + // value, + // index + // }) => { + // const RADIAN = Math.PI / 180; + // const radius = 30 + innerRadius + (outerRadius - innerRadius); + // const x = cx + radius * Math.cos(-midAngle * RADIAN); + // const y = cy + radius * Math.sin(-midAngle * RADIAN); + + // return ( + // cx ? "start" : "end"} + // dominantBaseline="top" + // fontSize={10} + // > + // {data.values[index].name} ({value}) + // + // ); + // }} + > + {data.values.map((entry, index) => ( + + ))} + diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx index 9ab0d72eb..be94acc57 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx @@ -6,11 +6,11 @@ const cols = [ { key: 'name', title: 'Resource', - toText: name => name, + toText: name => name || 'Unidentified', width: '70%', }, { - key: 'sessions', + key: 'sessionCount', title: 'Sessions', toText: sessions => sessions, width: '30%', @@ -19,18 +19,13 @@ const cols = [ interface Props { data: any; + onClick?: (event, index) => void; } function CustomMetriTable(props: Props) { - const { data } = props; - const rows = List([ - { name: 'one', sessions: 2 }, - { name: 'two', sessions: 3 }, - { name: 'three', sessions: 4 }, - { name: 'four', sessions: 1 }, - { name: 'five', sessions: 6 }, - ]) + const { data = { values: [] }, onClick = () => null } = props; + const rows = List(data.values); return ( -
+
{ const params = { density: 70 } @@ -101,7 +102,7 @@ function CustomMetricWidget(props: Props) { return (
-
+
{metric.name}
@@ -109,7 +110,7 @@ function CustomMetricWidget(props: Props) { updateActiveState(metric.metricId, false)} />
-
+
)} + + {metric.viewType === 'table' && ( + + )} diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.css b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.css index 1d1ef3ee4..42444d934 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.css +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.css @@ -1,6 +1,13 @@ .wrapper { - background-color: white; + background-color: $gray-light; /* border: solid thin $gray-medium; */ border-radius: 3px; - padding: 10px; + padding: 20px; +} + +.innerWapper { + border-radius: 3px; + width: 70%; + margin: 0 auto; + background-color: white; } \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx index 7aa43f94b..eb37268fc 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx @@ -106,8 +106,8 @@ function CustomMetricWidget(props: Props) { onSelect={ chagneViewType } value={{ value: metric.viewType }} list={ [ - { value: 'lineChart', icon: 'graph-up-arrow' }, - { value: 'progress', icon: 'hash' }, + { value: 'lineChart', name: 'Chart', icon: 'graph-up-arrow' }, + { value: 'progress', name: 'Progress', icon: 'hash' }, ]} /> )} @@ -121,8 +121,8 @@ function CustomMetricWidget(props: Props) { onSelect={ chagneViewType } value={{ value: metric.viewType }} list={[ - { value: 'table', icon: 'table' }, - { value: 'pieChart', icon: 'graph-up-arrow' }, + { value: 'table', name: 'Table', icon: 'table' }, + { value: 'pieChart', name: 'Chart', icon: 'graph-up-arrow' }, ]} /> )} @@ -139,45 +139,50 @@ function CustomMetricWidget(props: Props) {
-
+
+
+ {metric.name} +
+
{ isTimeSeries && ( - <> - { metric.viewType === 'progress' && ( - - )} - { metric.viewType === 'lineChart' && ( - - )} - - )} - - { isTable && ( -
- { metric.viewType === 'table' ? ( - - ) : ( - + { metric.viewType === 'progress' && ( + - )} -
- )} + )} + { metric.viewType === 'lineChart' && ( + + )} + + )} + + { isTable && ( + <> + { metric.viewType === 'table' ? ( + + ) : ( + + )} + + )} +
diff --git a/frontend/app/components/Dashboard/Widgets/common/Styles.js b/frontend/app/components/Dashboard/Widgets/common/Styles.js index 7b763f698..f071127dd 100644 --- a/frontend/app/components/Dashboard/Widgets/common/Styles.js +++ b/frontend/app/components/Dashboard/Widgets/common/Styles.js @@ -5,6 +5,7 @@ const colorsx = ['#256669', '#38999e', '#3eaaaf', '#51b3b7', '#78c4c7', '#9fd5d7 const compareColors = ['#394EFF', '#4D5FFF', '#808DFF', '#B3BBFF', '#E5E8FF']; const compareColorsx = ["#222F99", "#2E3ECC", "#394EFF", "#6171FF", "#8895FF", "#B0B8FF", "#D7DCFF"].reverse(); const customMetricColors = ['#3EAAAF', '#394EFF', '#666666']; +const colorsPie = colors.concat(["#DDDDDD"]); const countView = count => { const isMoreThanK = count >= 1000; @@ -14,6 +15,7 @@ const countView = count => { export default { customMetricColors, colors, + colorsPie, colorsx, compareColors, compareColorsx, diff --git a/frontend/app/components/Dashboard/Widgets/common/Table.js b/frontend/app/components/Dashboard/Widgets/common/Table.js index 73192a501..8fcef4315 100644 --- a/frontend/app/components/Dashboard/Widgets/common/Table.js +++ b/frontend/app/components/Dashboard/Widgets/common/Table.js @@ -45,14 +45,14 @@ export default class Table extends React.PureComponent { )) }
{ rows.size > (small ? 3 : 5) && !showAll && -
+
} diff --git a/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx b/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx index b10b1dfe4..ea923a6e1 100644 --- a/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx +++ b/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx @@ -25,6 +25,10 @@ function CustomMetricForm(props: Props) { // const metricOfOptions = metricOf.filter(i => i.key === metric.metricType); const timeseriesOptions = metricOf.filter(i => i.key === 'timeseries'); const tableOptions = metricOf.filter(i => i.key === 'table'); + const isTable = metric.metricType === 'table'; + const isTimeSeries = metric.metricType === 'timeseries'; + const _issueOptions = [{ text: 'All', value: '' }].concat(issueOptions); + const addSeries = () => { props.addSeries(); @@ -44,7 +48,7 @@ function CustomMetricForm(props: Props) { if (name === 'metricOf') { if (value === 'ISSUES') { - props.editMetric({ metricValue: [issueOptions[0].value] }, false); + props.editMetric({ metricValue: [''] }, false); } } @@ -140,7 +144,7 @@ function CustomMetricForm(props: Props) { issue type @@ -165,9 +169,10 @@ function CustomMetricForm(props: Props) {
- {metric.series && metric.series.size > 0 && metric.series.map((series: any, index: number) => ( + {metric.series && metric.series.size > 0 && metric.series.take(isTable ? 1 : metric.series.size).map((series: any, index: number) => (
removeSeries(index)} @@ -177,9 +182,11 @@ function CustomMetricForm(props: Props) { ))}
-
2})}> - -
+ { isTimeSeries && ( +
2})}> + +
+ )}
diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx index 7054e4516..aea20aea1 100644 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx @@ -25,10 +25,12 @@ interface Props { editSeriesFilterFilter: typeof editSeriesFilterFilter; editSeriesFilter: typeof editSeriesFilter; removeSeriesFilterFilter: typeof removeSeriesFilterFilter; + hideHeader?: boolean; + emptyMessage?: any; } function FilterSeries(props: Props) { - const { canDelete } = props; + const { canDelete, hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' } = props; const [expanded, setExpanded] = useState(true) const { series, seriesIndex } = props; @@ -51,7 +53,7 @@ function FilterSeries(props: Props) { return (
-
+
props.updateSeries(seriesIndex, { name }) } />
@@ -78,7 +80,7 @@ function FilterSeries(props: Props) { onChangeEventsOrder={onChangeEventsOrder} /> ): ( -
Add user event or filter to define the series by clicking Add Step.
+
{emptyMessage}
)}
diff --git a/frontend/app/components/ui/SegmentSelection/SegmentSelection.js b/frontend/app/components/ui/SegmentSelection/SegmentSelection.js index e18faf096..74335fddd 100644 --- a/frontend/app/components/ui/SegmentSelection/SegmentSelection.js +++ b/frontend/app/components/ui/SegmentSelection/SegmentSelection.js @@ -28,8 +28,8 @@ class SegmentSelection extends React.Component { data-active={ this.props.value && this.props.value.value === item.value } onClick={ () => !item.disabled && this.setActiveItem(item) } > - { item.icon && } -
{ item.name }
+ { item.icon && } +
{ item.name }
} disabled={!item.disabled} diff --git a/frontend/app/components/ui/SegmentSelection/segmentSelection.css b/frontend/app/components/ui/SegmentSelection/segmentSelection.css index f63559c0c..907f81e37 100644 --- a/frontend/app/components/ui/SegmentSelection/segmentSelection.css +++ b/frontend/app/components/ui/SegmentSelection/segmentSelection.css @@ -72,7 +72,7 @@ } .extraSmall .item { - padding: 0 4px !important; + padding: 2px 4px !important; font-size: 12px; } diff --git a/frontend/app/types/customMetric.js b/frontend/app/types/customMetric.js index 9ce5841f0..0686af87d 100644 --- a/frontend/app/types/customMetric.js +++ b/frontend/app/types/customMetric.js @@ -31,7 +31,7 @@ export default Record({ metricOf: 'USERID', metricValue: ['sessionCount'], metricFormat: 'sessionCount', - viewType: 'table', + viewType: 'pieChart', series: List(), isPublic: true, startDate: '', diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index db0248458..154db3f23 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -44,7 +44,7 @@ export const filtersMap = { [FilterKey.AVG_CPU_LOAD]: { key: FilterKey.AVG_CPU_LOAD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg CPU Load', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/cpu-load', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, [FilterKey.AVG_MEMORY_USAGE]: { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg Memory Usage', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/memory-load', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, [FilterKey.FETCH_FAILED]: { key: FilterKey.FETCH_FAILED, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Failed Request', operator: 'isAny', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch-failed', isEvent: true }, - [FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/click', options: filterOptions.issueOptions }, + [FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/click', options: filterOptions.issueOptions }, } export const liveFiltersMap = {