From 652ea0493a903be84a91c45f3ae6fd46f2af2f22 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 30 Mar 2022 10:58:08 +0200 Subject: [PATCH 01/32] workflow dispatch --- .github/workflows/api.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/api.yaml b/.github/workflows/api.yaml index 0d467295f..9fe8c5611 100644 --- a/.github/workflows/api.yaml +++ b/.github/workflows/api.yaml @@ -1,8 +1,9 @@ # This action will push the chalice changes to aws on: + workflow_dispatch: push: branches: - - dev + - api-v1.5.5 paths: - api/** From 9591d78bb045a73f532f30d7b5a07fbee8ca0c46 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Thu, 31 Mar 2022 18:32:27 +0200 Subject: [PATCH 02/32] fix(nginx): socketio endpoint directly from kube service nginx upstream block has some issue with forwarding the socketio connection Signed-off-by: rjshrjndrn --- .../openreplay/charts/nginx-ingress/templates/configMap.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/helmcharts/openreplay/charts/nginx-ingress/templates/configMap.yaml b/scripts/helmcharts/openreplay/charts/nginx-ingress/templates/configMap.yaml index 6780c9ee6..6322318e3 100644 --- a/scripts/helmcharts/openreplay/charts/nginx-ingress/templates/configMap.yaml +++ b/scripts/helmcharts/openreplay/charts/nginx-ingress/templates/configMap.yaml @@ -82,7 +82,7 @@ data: proxy_set_header Host $host; proxy_set_header X-Forwarded-For $origin_forwarded_ip; proxy_set_header X-Real-IP $origin_forwarded_ip; - proxy_pass http://utilities-pool; + proxy_pass http://utilities-openreplay.app.svc.cluster.local:9001; } location /assets/ { rewrite ^/assets/(.*) /sessions-assets/$1 break; From 423eb715f8f0b5a11930f19e497eac2b149c0410 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Thu, 31 Mar 2022 19:50:07 +0200 Subject: [PATCH 03/32] chore(helm): moving utils default vars Signed-off-by: rjshrjndrn --- scripts/helmcharts/openreplay/charts/utilities/values.yaml | 3 +++ scripts/helmcharts/vars.yaml | 7 ------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/scripts/helmcharts/openreplay/charts/utilities/values.yaml b/scripts/helmcharts/openreplay/charts/utilities/values.yaml index 87b3d771d..8265b5aa5 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/values.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/values.yaml @@ -83,6 +83,9 @@ autoscaling: env: REDIS_URL: "redis://redis-master.db.svc.cluster.local:6379" + debug: 0 + uws: false + redis: false nodeSelector: {} diff --git a/scripts/helmcharts/vars.yaml b/scripts/helmcharts/vars.yaml index e4ed88711..24ea9344d 100644 --- a/scripts/helmcharts/vars.yaml +++ b/scripts/helmcharts/vars.yaml @@ -95,13 +95,6 @@ chalice: # idp_name: '' # idp_tenantKey: '' -utilities: - replicaCount: 1 - env: - debug: 0 - uws: false - redis: false - # If you want to override something # chartname: # filedFrom chart/Values.yaml: From 2f9561151e98aee528b27e4904e77af411a7470b Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Thu, 7 Apr 2022 00:09:28 +0200 Subject: [PATCH 04/32] chore(minio): modularizing components Signed-off-by: rjshrjndrn --- scripts/helmcharts/openreplay/files/minio.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/scripts/helmcharts/openreplay/files/minio.sh b/scripts/helmcharts/openreplay/files/minio.sh index 1cc3e0460..903168b1b 100644 --- a/scripts/helmcharts/openreplay/files/minio.sh +++ b/scripts/helmcharts/openreplay/files/minio.sh @@ -12,9 +12,7 @@ mc alias set minio http://minio.db.svc.cluster.local:9000 $MINIO_ACCESS_KEY $MIN function init() { echo "Initializing minio" -for bucket in ${buckets[*]}; do -mc mb minio/${bucket} || true -mc ilm import minio/${bucket} < /tmp/lifecycle.json { "Rules": [ { @@ -27,13 +25,17 @@ mc ilm import minio/${bucket} < Date: Fri, 8 Apr 2022 11:39:23 +0200 Subject: [PATCH 05/32] chore(helm): update note Signed-off-by: rjshrjndrn --- .../helmcharts/openreplay/templates/NOTES.txt | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/scripts/helmcharts/openreplay/templates/NOTES.txt b/scripts/helmcharts/openreplay/templates/NOTES.txt index 6881933f1..1479a07c3 100644 --- a/scripts/helmcharts/openreplay/templates/NOTES.txt +++ b/scripts/helmcharts/openreplay/templates/NOTES.txt @@ -1,22 +1 @@ -1. Get the application URL by running these commands: -{{- if .Values.ingress.enabled }} -{{- range $host := .Values.ingress.hosts }} - {{- range .paths }} - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} - {{- end }} -{{- end }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "openreplay.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "openreplay.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "openreplay.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - echo http://$SERVICE_IP:{{ .Values.service.port }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "openreplay.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT -{{- end }} +OpenReplay Installation is complete. Follow along with the SSL configuration from [doc](https://docs.openreplay.com/deployment/deploy-aws#configuretls/ssl). From d7740ebc0b481a6da3395831ecdaab2609fe439e Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 8 Apr 2022 18:20:22 +0200 Subject: [PATCH 06/32] Update frontend.yaml --- .github/workflows/frontend.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 8c5038f5f..c0a540efb 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -1,8 +1,9 @@ name: Frontend FOSS Deployment on: + workflow_dispatch: push: branches: - - dev + - api-v1.5.5 paths: - frontend/** From 3984b2325063bcfb067ea94f481764fcd7e2bd4b Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 8 Apr 2022 18:26:59 +0200 Subject: [PATCH 07/32] feat(ui) - dashboard - wip --- frontend/app/components/Modal/ModalOverlay.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/app/components/Modal/ModalOverlay.tsx b/frontend/app/components/Modal/ModalOverlay.tsx index 8660f53a4..85b314eec 100644 --- a/frontend/app/components/Modal/ModalOverlay.tsx +++ b/frontend/app/components/Modal/ModalOverlay.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { ModalContext } from "App/components/Modal/modalContext"; import { useModal } from 'App/components/Modal'; import stl from './ModalOverlay.css' From 7f806dda9338e87fe09aa0ebabfe229aa1ae8e92 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 8 Apr 2022 18:27:58 +0200 Subject: [PATCH 08/32] feat(ui) - dashboard - wip --- .github/workflows/frontend.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 8c5038f5f..990ce3c8a 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -1,5 +1,6 @@ name: Frontend FOSS Deployment on: + workflow_dispatch: push: branches: - dev From 1b0cc63e1f56d8ff9d0fa3e1fbae43fae487a9a5 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 8 Apr 2022 19:23:59 +0200 Subject: [PATCH 09/32] fix(ui) - saved search operator --- frontend/app/duck/search.js | 2 +- frontend/app/types/filter/newFilter.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index 9106227bb..f46f5a30a 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -71,7 +71,7 @@ function reducer(state = initialState, action = {}) { return state.set("instance", action.data); case success(FETCH_LIST): const { data } = action; - return state.set("list", List(data.map(SavedFilter))); + return state.set("list", List(data.map(SavedFilter)).sortBy(i => i.searchId)); case success(FETCH_FILTER_SEARCH): const groupedList = action.data.reduce((acc, item) => { const { projectId, type, value } = item; diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index 6963643f7..7a640bbbd 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -140,8 +140,8 @@ export default Record({ } } return { - ...filter, ..._filter, + ...filter, key: _filter.key, type: _filter.type, // camelCased(filter.type.toLowerCase()), value: value.length === 0 || !value ? [""] : value, From c550a6923debefd505525fa87be5be0072d15ddb Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 8 Apr 2022 19:34:36 +0200 Subject: [PATCH 10/32] fix(ui) - filter userid value on delete --- .../app/components/shared/Filters/FilterModal/FilterModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx index cb9c6f768..04688de6b 100644 --- a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx +++ b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx @@ -73,7 +73,7 @@ function FilterModal(props: Props) {
{key}
{filters[key].map((filter: any) => ( -
onFilterClick(filter)}> +
onFilterClick({ ...filter, value: [''] })}> {filter.label}
From 3fba26180d1a4f844166e59e1059d1f245bc0e2c Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 11 Apr 2022 11:15:14 +0200 Subject: [PATCH 11/32] change(ui) - assist session data endpoint --- frontend/app/components/BugFinder/BugFinder.js | 4 ++-- frontend/app/duck/sessions.js | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/app/components/BugFinder/BugFinder.js b/frontend/app/components/BugFinder/BugFinder.js index 326a1e78e..85f899290 100644 --- a/frontend/app/components/BugFinder/BugFinder.js +++ b/frontend/app/components/BugFinder/BugFinder.js @@ -20,7 +20,7 @@ import { LAST_7_DAYS } from 'Types/app/period'; import { resetFunnel } from 'Duck/funnels'; import { resetFunnelFilters } from 'Duck/funnelFilters' import NoSessionsMessage from 'Shared/NoSessionsMessage'; -import TrackerUpdateMessage from 'Shared/TrackerUpdateMessage'; +// import TrackerUpdateMessage from 'Shared/TrackerUpdateMessage'; import SessionSearch from 'Shared/SessionSearch'; import MainSearchBar from 'Shared/MainSearchBar'; import { clearSearch, fetchSessions } from 'Duck/search'; @@ -130,7 +130,7 @@ export default class BugFinder extends React.PureComponent { />
- + {/* */}
diff --git a/frontend/app/duck/sessions.js b/frontend/app/duck/sessions.js index f3df333c7..79822bb25 100644 --- a/frontend/app/duck/sessions.js +++ b/frontend/app/duck/sessions.js @@ -285,10 +285,11 @@ export function fetchErrorStackList(sessionId, errorId) { }; } -export const fetch = (sessionId) => (dispatch, getState) => { +export const fetch = (sessionId, isLive = false) => (dispatch, getState) => { + console.log('isLive', isLive) dispatch({ types: FETCH.toArray(), - call: client => client.get(`/sessions2/${ sessionId }`), + call: client => client.get(isLive ? `/assist/sessions/${ sessionId }` : `/sessions2/${ sessionId }`), filter: getState().getIn([ 'filters', 'appliedFilter' ]) }); } From 258c4da2a4132e2e21c40ee8c370ed54a218735e Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 11 Apr 2022 12:25:55 +0200 Subject: [PATCH 12/32] fix(ui) - localstorage boolean values --- frontend/app/player/Player.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/app/player/Player.ts b/frontend/app/player/Player.ts index 67875b530..f1f305868 100644 --- a/frontend/app/player/Player.ts +++ b/frontend/app/player/Player.ts @@ -30,10 +30,10 @@ const AUTOPLAY_STORAGE_KEY = "__$player-autoplay$__"; const SHOW_EVENTS_STORAGE_KEY = "__$player-show-events$__"; const storedSpeed: number = parseInt(localStorage.getItem(SPEED_STORAGE_KEY) || "") ; const initialSpeed = [1,2,4,8,16].includes(storedSpeed) ? storedSpeed : 1; -const initialSkip = !!localStorage.getItem(SKIP_STORAGE_KEY); -const initialSkipToIssue = !!localStorage.getItem(SKIP_TO_ISSUE_STORAGE_KEY); -const initialAutoplay = !!localStorage.getItem(AUTOPLAY_STORAGE_KEY); -const initialShowEvents = !!localStorage.getItem(SHOW_EVENTS_STORAGE_KEY); +const initialSkip = localStorage.getItem(SKIP_STORAGE_KEY) === 'true'; +const initialSkipToIssue = localStorage.getItem(SKIP_TO_ISSUE_STORAGE_KEY) === 'true'; +const initialAutoplay = localStorage.getItem(AUTOPLAY_STORAGE_KEY) === 'true'; +const initialShowEvents = localStorage.getItem(SHOW_EVENTS_STORAGE_KEY) === 'true'; export const INITIAL_STATE = { ...SUPER_INITIAL_STATE, From 6ce6f72cf5095acd71e169f8931ab996e4ee1913 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 11 Apr 2022 13:15:44 +0200 Subject: [PATCH 13/32] fix(ui) - funnel daterange --- .../DomBuildingTime/DomBuildingTime.tsx | 2 +- .../ResponseTime/ResponseTime.tsx | 2 +- .../components/WidgetChart/WidgetChart.tsx | 11 +++++------ .../components/WidgetWrapper/WidgetWrapper.tsx | 2 +- .../Funnels/FunnelHeader/FunnelHeader.js | 17 +++++++++-------- frontend/app/components/Session/LiveSession.js | 2 +- .../shared/LiveSessionList/LiveSessionList.tsx | 10 +++++----- .../LiveSessionSearch/LiveSessionSearch.tsx | 10 +++++----- frontend/app/duck/funnels.js | 18 +++++++++++++----- frontend/app/duck/sessions.js | 2 +- frontend/app/mstore/dashboardStore.ts | 2 +- frontend/app/mstore/types/dashboard.ts | 2 +- frontend/app/mstore/types/widget.ts | 1 + 13 files changed, 45 insertions(+), 36 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx index 4bda65afa..c1e490422 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx @@ -47,7 +47,7 @@ function DomBuildingTime(props: Props) { />
- +
- + props.metric); const { dashboardStore } = useStore(); const period = useObserver(() => dashboardStore.period); const colors = Styles.customMetricColors; const [loading, setLoading] = useState(false) const [seriesMap, setSeriesMap] = useState([]); - const params = { density: 70 } - const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart' } + const params = { density: 28 } + const metricParams = { ...params } const prevMetricRef = useRef(); const [data, setData] = useState(metric.data); @@ -34,7 +33,7 @@ function WidgetChart(props: Props) { prevMetricRef.current = metric; setLoading(true); - const data = isWidget ? {} : { ...metricParams, ...metric.toJson() }; + const data = isWidget ? { ...params } : { ...metricParams, ...metric.toJson() }; dashboardStore.fetchMetricChartData(metric, data, isWidget).then((res: any) => { setData(res); }).finally(() => { diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index 9c723129e..b27175c1a 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -107,7 +107,7 @@ function WidgetWrapper(props: Props) { )}
- +
diff --git a/frontend/app/components/Funnels/FunnelHeader/FunnelHeader.js b/frontend/app/components/Funnels/FunnelHeader/FunnelHeader.js index 6130351e2..2297ca324 100644 --- a/frontend/app/components/Funnels/FunnelHeader/FunnelHeader.js +++ b/frontend/app/components/Funnels/FunnelHeader/FunnelHeader.js @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Icon, BackLink, IconButton, Dropdown, Popup, TextEllipsis, Button } from 'UI'; import { remove as deleteFunnel, fetch, fetchInsights, fetchIssuesFiltered, fetchSessionsFiltered } from 'Duck/funnels'; -import { editFilter, refresh, addFilter } from 'Duck/funnels'; +import { editFilter, editFunnelFilter, refresh, addFilter } from 'Duck/funnels'; import DateRange from 'Shared/DateRange'; import { connect } from 'react-redux'; import { confirm } from 'UI/Confirmation'; @@ -18,7 +18,7 @@ const Info = ({ label = '', value = '', className = 'mx-4' }) => { } const FunnelHeader = (props) => { - const { funnel, insights, funnels, onBack, funnelId, showFilters = false, renameHandler } = props; + const { funnel, insights, funnels, onBack, funnelId, showFilters = false, funnelFilters, renameHandler } = props; const [showSaveModal, setShowSaveModal] = useState(false) const writeOption = (e, { name, value }) => { @@ -40,7 +40,7 @@ const FunnelHeader = (props) => { } const onDateChange = (e) => { - props.editFilter(e, funnelId); + props.editFunnelFilter(e, funnelId); } const options = funnels.map(({ funnelId, name }) => ({ text: name, value: funnelId })).toJS(); @@ -55,7 +55,7 @@ const FunnelHeader = (props) => { show={showSaveModal} closeHandler={() => setShowSaveModal(false)} /> -
+
{ />
@@ -109,5 +109,6 @@ const FunnelHeader = (props) => { } export default connect(state => ({ + funnelFilters: state.getIn([ 'funnels', 'funnelFilters' ]).toJS(), funnel: state.getIn([ 'funnels', 'instance' ]), -}), { editFilter, deleteFunnel, fetch, fetchInsights, fetchIssuesFiltered, fetchSessionsFiltered, refresh })(FunnelHeader) +}), { editFilter, editFunnelFilter, deleteFunnel, fetch, fetchInsights, fetchIssuesFiltered, fetchSessionsFiltered, refresh })(FunnelHeader) diff --git a/frontend/app/components/Session/LiveSession.js b/frontend/app/components/Session/LiveSession.js index 46bd39cf5..0833112a2 100644 --- a/frontend/app/components/Session/LiveSession.js +++ b/frontend/app/components/Session/LiveSession.js @@ -27,7 +27,7 @@ function LiveSession({ useEffect(() => { if (sessionId != null) { - fetchSession(sessionId) + fetchSession(sessionId, true) } else { console.error("No sessionID in route.") } diff --git a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx index a1617e47d..b0dbd80ac 100644 --- a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx @@ -45,11 +45,11 @@ function LiveSessionList(props: Props) { // const displayedCount = Math.min(currentPage * PER_PAGE, sessions.size); // const addPage = () => props.updateCurrentPage(props.currentPage + 1) - useEffect(() => { - if (filters.size === 0) { - props.addFilterByKeyAndValue(FilterKey.USERID, ''); - } - }, []); + // useEffect(() => { + // if (filters.size === 0) { + // props.addFilterByKeyAndValue(FilterKey.USERID, ''); + // } + // }, []); useEffect(() => { if (metaList.size === 0 || !!sort.field) return; diff --git a/frontend/app/components/shared/LiveSessionSearch/LiveSessionSearch.tsx b/frontend/app/components/shared/LiveSessionSearch/LiveSessionSearch.tsx index ae7a60b28..5f6e5f7cc 100644 --- a/frontend/app/components/shared/LiveSessionSearch/LiveSessionSearch.tsx +++ b/frontend/app/components/shared/LiveSessionSearch/LiveSessionSearch.tsx @@ -42,9 +42,9 @@ function LiveSessionSearch(props: Props) { }); props.edit({ filters: newFilters, }); - if (newFilters.size === 0) { - props.addFilterByKeyAndValue(FilterKey.USERID, ''); - } + // if (newFilters.size === 0) { + // props.addFilterByKeyAndValue(FilterKey.USERID, ''); + // } } const onChangeEventsOrder = (e, { name, value }) => { @@ -53,7 +53,7 @@ function LiveSessionSearch(props: Props) { }); } - return (hasEvents || hasFilters) ? ( + return (
- ) : <>; + ); } export default connect(state => ({ diff --git a/frontend/app/duck/funnels.js b/frontend/app/duck/funnels.js index 1278614be..3abfcc450 100644 --- a/frontend/app/duck/funnels.js +++ b/frontend/app/duck/funnels.js @@ -24,6 +24,7 @@ const SAVE = saveType('funnel/SAVE'); const UPDATE = saveType('funnel/UPDATE'); const EDIT = editType('funnel/EDIT'); const EDIT_FILTER = `${name}/EDIT_FILTER`; +const EDIT_FUNNEL_FILTER = `${name}/EDIT_FUNNEL_FILTER`; const REMOVE = removeType('funnel/REMOVE'); const INIT = initType('funnel/INIT'); const SET_NAV_REF = 'funnels/SET_NAV_REF' @@ -51,7 +52,6 @@ const REMOVE_SUCCESS = success(REMOVE); const range = getDateRangeFromValue(LAST_7_DAYS); const defaultDateFilters = { - events: [], rangeValue: LAST_7_DAYS, startDate: range.start.unix() * 1000, endDate: range.end.unix() * 1000 @@ -67,7 +67,7 @@ const initialState = Map({ sessionsTotal: 0, sessions: List(), activeStages: List(), - funnelFilters: defaultDateFilters, + funnelFilters: Map(defaultDateFilters), sessionsSort: Map({ order: "desc", sort: "newest" }), issueFilters: Map({ filters: List(), @@ -87,6 +87,8 @@ const reducer = (state = initialState, action = {}) => { return state.mergeIn([ 'instance' ], action.instance); case EDIT_FILTER: return state.mergeIn([ 'instance', 'filter' ], action.instance); + case EDIT_FUNNEL_FILTER: + return state.mergeIn([ 'funnelFilters' ], action.instance); case INIT: return state.set('instance', Funnel(action.instance)) case FETCH_LIST_SUCCESS: @@ -171,7 +173,7 @@ const reducer = (state = initialState, action = {}) => { .set('instance', Funnel()) .set('activeStages', List()) .set('issuesSort', Map({})) - .set('funnelFilters', defaultDateFilters) + // .set('funnelFilters', Map(defaultDateFilters)) .set('insights', Funnel()) .set('issues', List()) .set('sessions', List()); @@ -200,6 +202,7 @@ export const fetch = (funnelId, params) => (dispatch, getState) => { function getParams(params, state) { const filter = state.getIn([ 'funnels', 'instance', 'filter']).toData(); filter.filters = filter.filters.map(filterMap); + const funnelFilters = state.getIn([ 'funnels', 'funnelFilters']).toJS(); // const appliedFilter = state.getIn([ 'funnels', 'instance', 'filter' ]); // const filter = appliedFilter @@ -209,7 +212,7 @@ function getParams(params, state) { // filter.filters = state.getIn([ 'funnelFilters', 'appliedFilter', 'filters' ]) // .map(filterMap).toJS(); - return filter; + return { ...filter, ...funnelFilters }; } export const fetchInsights = (funnelId, params = {}, isRefresh = false) => (dispatch, getState) => { @@ -372,7 +375,7 @@ export const resetIssue = () => { export const resetFunnel = () => { return { - type: RESET_FUNNEL, + type: RESET_FUNNEL, } } @@ -428,6 +431,11 @@ export const editFilter = reduceThenFetchList((instance) => ({ instance, })); +export const editFunnelFilter = reduceThenFetchList((instance) => ({ + type: EDIT_FUNNEL_FILTER, + instance, +})); + export const addFilter = (filter) => (dispatch, getState) => { filter.value = checkFilterValue(filter.value); const instance = getState().getIn([ 'funnels', 'instance', 'filter']); diff --git a/frontend/app/duck/sessions.js b/frontend/app/duck/sessions.js index 79822bb25..0c46cc54a 100644 --- a/frontend/app/duck/sessions.js +++ b/frontend/app/duck/sessions.js @@ -319,7 +319,7 @@ export function fetchInsights(params) { export function fetchLiveList() { return { types: FETCH_LIVE_LIST.toArray(), - call: client => client.get('/assist/sessions'), + call: client => client.get('/assist/sessions', { userId: 'test'}), }; } diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 0231bbf7c..6739d54b6 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -169,7 +169,7 @@ export default class DashboardStore implements IDashboardSotre { return dashboardService.getDashboards() .then((list: any) => { runInAction(() => { - this.dashboards = list.map(d => new Dashboard().fromJson(d)).sort((a, b) => a.position - b.position) + this.dashboards = list.map(d => new Dashboard().fromJson(d)) }) }).finally(() => { runInAction(() => { diff --git a/frontend/app/mstore/types/dashboard.ts b/frontend/app/mstore/types/dashboard.ts index e3db0a146..25cfa7a05 100644 --- a/frontend/app/mstore/types/dashboard.ts +++ b/frontend/app/mstore/types/dashboard.ts @@ -96,7 +96,7 @@ export default class Dashboard implements IDashboard { this.isPublic = json.isPublic this.isPinned = json.isPinned // this.config = json.config - this.widgets = json.widgets ? json.widgets.map(w => new Widget().fromJson(w)) : [] + this.widgets = json.widgets ? json.widgets.map(w => new Widget().fromJson(w)).sort((a, b) => a.position - b.position) : [] }) return this } diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index ee9c559b4..c32b78ed2 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -130,6 +130,7 @@ export default class Widget implements IWidget { this.position = json.config.position this.predefinedKey = json.predefinedKey }) + console.log(this.name, this.position) return this } From 410ffb14754cc40b8a49e04cccb51901724c2480 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 11 Apr 2022 13:57:09 +0200 Subject: [PATCH 14/32] change(ui) - assist filter by userid --- .../Assist/components/SessionList/SessionList.tsx | 8 ++++++-- frontend/app/duck/sessions.js | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/app/components/Assist/components/SessionList/SessionList.tsx b/frontend/app/components/Assist/components/SessionList/SessionList.tsx index 73c7a3a7f..32c267588 100644 --- a/frontend/app/components/Assist/components/SessionList/SessionList.tsx +++ b/frontend/app/components/Assist/components/SessionList/SessionList.tsx @@ -8,11 +8,15 @@ interface Props { loading: boolean, list: any, session: any, - fetchLiveList: () => void, + fetchLiveList: (params: any) => void, } function SessionList(props: Props) { useEffect(() => { - props.fetchLiveList(); + const params: any = {} + if (props.session.userId) { + params.userId = props.session.userId + } + props.fetchLiveList(params); }, []) return ( diff --git a/frontend/app/duck/sessions.js b/frontend/app/duck/sessions.js index 0c46cc54a..6fcdb15c5 100644 --- a/frontend/app/duck/sessions.js +++ b/frontend/app/duck/sessions.js @@ -316,10 +316,10 @@ export function fetchInsights(params) { }; } -export function fetchLiveList() { +export function fetchLiveList(params = {}) { return { types: FETCH_LIVE_LIST.toArray(), - call: client => client.get('/assist/sessions', { userId: 'test'}), + call: client => client.get('/assist/sessions', params), }; } From fa7ae29a6284ee31be3db0a223296c958e73147c Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 12 Apr 2022 12:38:23 +0200 Subject: [PATCH 15/32] change(ui) - player timeline slider and other fixes --- .../BugFinder/SessionList/SessionList.js | 73 ++++++-------- .../CustomMetricPercentage.tsx | 2 +- .../components/Session/Layout/PlayOverlay.js | 21 ++-- .../Session/Layout/Player/Timeline.js | 5 +- .../Session_/Player/Controls/Circle.tsx | 18 ++++ .../Session_/Player/Controls/Controls.js | 11 ++- .../Player/Controls/CustomDragLayer.tsx | 98 +++++++++++++++++++ .../Player/Controls/DraggableCircle.tsx | 67 +++++++++++++ .../Session_/Player/Controls/TimeTracker.js | 4 - .../Session_/Player/Controls/Timeline.js | 41 +++++++- .../Session_/Player/Controls/time.css | 2 + .../Session_/Player/Controls/timeline.css | 25 +++++ .../Session_/Player/Overlay/PlayIconLayer.tsx | 24 ++++- .../app/components/Session_/Player/Player.js | 2 + .../ui/TimelinePointer/timelinePointer.css | 2 +- frontend/app/duck/search.js | 11 +++ frontend/app/initialize.js | 7 -- frontend/app/svg/icons/pie-chart-fill.svg | 2 +- 18 files changed, 327 insertions(+), 88 deletions(-) create mode 100644 frontend/app/components/Session_/Player/Controls/Circle.tsx create mode 100644 frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx create mode 100644 frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx diff --git a/frontend/app/components/BugFinder/SessionList/SessionList.js b/frontend/app/components/BugFinder/SessionList/SessionList.js index 858e9cb30..27324e686 100644 --- a/frontend/app/components/BugFinder/SessionList/SessionList.js +++ b/frontend/app/components/BugFinder/SessionList/SessionList.js @@ -1,12 +1,12 @@ import { connect } from 'react-redux'; -import { Loader, NoContent, Button, LoadMoreButton, Pagination } from 'UI'; +import { Loader, NoContent, Button, Pagination } from 'UI'; import { applyFilter, addAttribute, addEvent } from 'Duck/filters'; -import { fetchSessions, addFilterByKeyAndValue, updateCurrentPage } from 'Duck/search'; +import { fetchSessions, addFilterByKeyAndValue, updateCurrentPage, setScrollPosition } from 'Duck/search'; import SessionItem from 'Shared/SessionItem'; import SessionListHeader from './SessionListHeader'; import { FilterKey } from 'Types/filter/filterType'; -const ALL = 'all'; +// const ALL = 'all'; const PER_PAGE = 10; const AUTOREFRESH_INTERVAL = 3 * 60 * 1000; var timeoutId; @@ -21,6 +21,7 @@ var timeoutId; filters: state.getIn([ 'search', 'instance', 'filters' ]), metaList: state.getIn(['customFields', 'list']).map(i => i.key), currentPage: state.getIn([ 'search', 'currentPage' ]), + scrollY: state.getIn([ 'search', 'scrollY' ]), }), { applyFilter, addAttribute, @@ -28,24 +29,15 @@ var timeoutId; fetchSessions, addFilterByKeyAndValue, updateCurrentPage, + setScrollPosition, }) export default class SessionList extends React.PureComponent { - state = { - showPages: 1, - } + constructor(props) { super(props); this.timeout(); } - componentDidUpdate(prevProps) { - if (prevProps.loading && !this.props.loading) { - this.setState({ showPages: 1 }); - } - } - - addPage = () => this.setState({ showPages: this.state.showPages + 1 }) - onUserClick = (userId, userAnonymousId) => { if (userId) { this.props.addFilterByKeyAndValue(FilterKey.USERID, userId); @@ -75,17 +67,22 @@ export default class SessionList extends React.PureComponent { } componentWillUnmount() { + this.props.setScrollPosition(window.scrollY) clearTimeout(timeoutId) } - + componentDidMount() { + const { scrollY } = this.props; + console.log('scrollY', scrollY); + window.scrollTo(0, scrollY); + } renderActiveTabContent(list) { const { loading, filters, - onMenuItemClick, - allList, + // onMenuItemClick, + // allList, activeTab, metaList, currentPage, @@ -93,8 +90,6 @@ export default class SessionList extends React.PureComponent { } = this.props; const _filterKeys = filters.map(i => i.key); const hasUserFilter = _filterKeys.includes(FilterKey.USERID) || _filterKeys.includes(FilterKey.USERANONYMOUSID); - const { showPages } = this.state; - const displayedCount = Math.min(showPages * PER_PAGE, list.size); return (
Please try changing your search parameters.
- {allList.size > 0 && ( + {/* {allList.size > 0 && (
However, we found other sessions based on your search parameters.
@@ -115,7 +110,7 @@ export default class SessionList extends React.PureComponent { >See All
- )} + )} */}
} > @@ -139,41 +134,29 @@ export default class SessionList extends React.PureComponent { debounceRequest={1000} /> - {/* - Haven't found the session in the above list?
Try being a bit more specific by setting a specific time frame or simply use different filters - - } - /> */} ); } render() { const { activeTab, allList, total } = this.props; - var filteredList; + // var filteredList; - if (activeTab.type !== ALL && activeTab.type !== 'bookmark' && activeTab.type !== 'live') { // Watchdog sessions - filteredList = allList.filter(session => activeTab.fits(session)) - } else { - filteredList = allList - } + // if (activeTab.type !== ALL && activeTab.type !== 'bookmark' && activeTab.type !== 'live') { // Watchdog sessions + // filteredList = allList.filter(session => activeTab.fits(session)) + // } else { + // filteredList = allList + // } - if (activeTab.type === 'bookmark') { - filteredList = filteredList.filter(item => item.favorite) - } - const _total = activeTab.type === 'all' ? total : filteredList.size + // if (activeTab.type === 'bookmark') { + // filteredList = filteredList.filter(item => item.favorite) + // } + // const _total = activeTab.type === 'all' ? total : allList.size return (
- - { this.renderActiveTabContent(filteredList) } + + { this.renderActiveTabContent(allList) }
); } diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx index 177dccf9a..0dfa6dbd3 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx @@ -12,7 +12,7 @@ function CustomMetriPercentage(props: Props) { return (
{numberWithCommas(data.count)}
-
{`${data.previousCount} ( ${data.countProgress}% )`}
+
{`${parseInt(data.previousCount).toFixed(1)} ( ${parseInt(data.countProgress).toFixed(1)}% )`}
from previous period.
) diff --git a/frontend/app/components/Session/Layout/PlayOverlay.js b/frontend/app/components/Session/Layout/PlayOverlay.js index 537460c26..3fb156172 100644 --- a/frontend/app/components/Session/Layout/PlayOverlay.js +++ b/frontend/app/components/Session/Layout/PlayOverlay.js @@ -1,8 +1,6 @@ import cn from 'classnames'; import { useCallback, useState } from 'react'; - import { Icon } from 'UI'; - import cls from './PlayOverlay.css'; export default function PlayOverlay({ player }) { @@ -11,20 +9,17 @@ export default function PlayOverlay({ player }) { const togglePlay = useCallback(() => { player.togglePlay(); setIconVisible(true); - setTimeout( - () => setIconVisible(false), - 800, - ); + setTimeout(() => setIconVisible(false), 800); }); return (
-
- -
-
+ className="absolute inset-0 flex items-center justify-center" + onClick={ togglePlay } + > +
+ +
+ ); } diff --git a/frontend/app/components/Session/Layout/Player/Timeline.js b/frontend/app/components/Session/Layout/Player/Timeline.js index 5f05834ee..ca1383ffa 100644 --- a/frontend/app/components/Session/Layout/Player/Timeline.js +++ b/frontend/app/components/Session/Layout/Player/Timeline.js @@ -1,12 +1,9 @@ import { useCallback } from 'react'; import cn from 'classnames'; import { Popup } from 'UI'; - import { CRASHES, EVENTS } from 'Player/ios/state'; - import TimeTracker from './TimeTracker'; import PlayerTime from './PlayerTime'; - import cls from './timeline.css'; export default function Timeline({ player }) { @@ -19,7 +16,7 @@ export default function Timeline({ player }) { const time = Math.max(Math.round(p * player.state.endTime), 0); player.jump(time); }); - const scale = 100 / player.state.endTime; + const scale = 100 / player.state.endTime; return (
diff --git a/frontend/app/components/Session_/Player/Controls/Circle.tsx b/frontend/app/components/Session_/Player/Controls/Circle.tsx new file mode 100644 index 000000000..b49d23216 --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/Circle.tsx @@ -0,0 +1,18 @@ +import React, { memo, FC } from 'react'; +import styles from './timeline.css'; + +interface Props { + preview?: boolean; +} +export const Circle: FC = memo(function Box({ preview }) { + // const backgroundColor = yellow ? 'yellow' : 'white' + return ( +
+ ) + }) + +export default Circle; \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js index 22fd3b0cf..ab1baba3e 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ b/frontend/app/components/Session_/Player/Controls/Controls.js @@ -118,6 +118,7 @@ export default class Controls extends React.Component { componentDidMount() { document.addEventListener('keydown', this.onKeyDown); } + componentWillUnmount() { document.removeEventListener('keydown', this.onKeyDown); //this.props.toggleInspectorMode(false); @@ -166,10 +167,10 @@ export default class Controls extends React.Component { return; } if (this.props.inspectorMode) return; - if (e.key === ' ') { - document.activeElement.blur(); - this.props.togglePlay(); - } + // if (e.key === ' ') { + // document.activeElement.blur(); + // this.props.togglePlay(); + // } if (e.key === 'Esc' || e.key === 'Escape') { this.props.fullscreenOff(); } @@ -262,7 +263,7 @@ export default class Controls extends React.Component { return (
- { !live && } + { !live && } { !fullscreen &&
diff --git a/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx b/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx new file mode 100644 index 000000000..c72f03ce2 --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx @@ -0,0 +1,98 @@ +import React, { memo } from 'react'; +import { useDragLayer } from "react-dnd"; +import Circle from './Circle' +import type { CSSProperties, FC } from 'react' + +const layerStyles: CSSProperties = { + position: "fixed", + pointerEvents: "none", + zIndex: 100, + left: 0, + top: 0, + width: "100%", + height: "100%" + }; + +const ItemTypes = { + BOX: 'box', +} + +function getItemStyles(initialOffset, currentOffset, maxX, minX) { + if (!initialOffset || !currentOffset) { + return { + display: "none" + }; + } + let { x, y } = currentOffset; + // if (isSnapToGrid) { + // x -= initialOffset.x; + // y -= initialOffset.y; + // [x, y] = [x, y]; + // x += initialOffset.x; + // y += initialOffset.y; + // } + if (x > maxX) { + x = maxX; + } + + if (x < minX) { + x = minX; + } + const transform = `translate(${x}px, ${initialOffset.y}px)`; + return { + transition: 'transform 0.1s ease-out', + transform, + WebkitTransform: transform + }; +} + +interface Props { + onDrag: (offset: { x: number, y: number } | null) => void; + maxX: number; + minX: number; +} + +const CustomDragLayer: FC = memo(function CustomDragLayer(props) { + const { + itemType, + isDragging, + item, + initialOffset, + currentOffset, + } = useDragLayer((monitor) => ({ + item: monitor.getItem(), + itemType: monitor.getItemType(), + initialOffset: monitor.getInitialSourceClientOffset(), + currentOffset: monitor.getSourceClientOffset(), + isDragging: monitor.isDragging(), + })); + + function renderItem() { + switch (itemType) { + case ItemTypes.BOX: + return ; + default: + return null; + } + } + + if (!isDragging) { + return null; + } + + if (isDragging) { + props.onDrag(currentOffset) + } + + return ( +
+
+ {renderItem()} +
+
+ ); +}) + +export default CustomDragLayer; \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx b/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx new file mode 100644 index 000000000..9bdf37651 --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx @@ -0,0 +1,67 @@ +import React, { memo, FC, useEffect, useRef, CSSProperties } from 'react'; +import type { DragSourceMonitor } from 'react-dnd' +import { useDrag } from 'react-dnd' +import { getEmptyImage } from 'react-dnd-html5-backend' +import Circle from './Circle' + +function getStyles( + left: number, + isDragging: boolean, + ): CSSProperties { + // const transform = `translate3d(${(left * 1161) / 100}px, -8px, 0)` + return { + position: 'absolute', + top: '-3px', + left: `${left}%`, + // transform, + // WebkitTransform: transform, + // IE fallback: hide the real node using CSS when dragging + // because IE will ignore our custom "empty image" drag preview. + opacity: isDragging ? 0 : 1, + height: isDragging ? 0 : '', + zIndex: '99999', + cursor: 'move' + } +} + +const ItemTypes = { + BOX: 'box', +} + +interface Props { + left: number; + top: number; + onDrop?: (item, monitor) => void; +} + +const DraggableCircle: FC = memo(function DraggableCircle(props) { + const { left, top } = props + const [{ isDragging, item }, dragRef, preview] = useDrag( + () => ({ + type: ItemTypes.BOX, + item: { left, top }, + end: props.onDrop, + collect: (monitor: DragSourceMonitor) => ({ + isDragging: monitor.isDragging(), + item: monitor.getItem(), + }), + }), + [left, top], + ) + + useEffect(() => { + preview(getEmptyImage(), { captureDraggingState: true }) + }, []) + + return ( +
+ +
+ ); +}) + +export default DraggableCircle \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Controls/TimeTracker.js b/frontend/app/components/Session_/Player/Controls/TimeTracker.js index be91f69fe..e3de669e5 100644 --- a/frontend/app/components/Session_/Player/Controls/TimeTracker.js +++ b/frontend/app/components/Session_/Player/Controls/TimeTracker.js @@ -4,10 +4,6 @@ import styles from './timeTracker.css'; const TimeTracker = ({ time, scale }) => ( -
{ // exception, @@ -51,6 +53,8 @@ const getPointerIcon = (type) => { } @connectPlayer(state => ({ + playing: state.playing, + time: state.time, skipIntervals: state.skipIntervals, events: state.eventList, skip: state.skip, @@ -72,6 +76,11 @@ const getPointerIcon = (type) => { state.getIn([ 'sessions', 'current', 'returningLocationTime' ]), }), { setTimelinePointer }) export default class Timeline extends React.PureComponent { + progressRef = React.createRef() + progressWidth = 0 + seekTime = 0 + wasPlaying = false + seekProgress = (e) => { const { endTime } = this.props; const p = e.nativeEvent.offsetX / e.target.offsetWidth; @@ -88,11 +97,32 @@ export default class Timeline extends React.PureComponent { componentDidMount() { const { issues, events, fetchList, skipToIssue } = this.props; const firstIssue = issues.get(0); + this.progressWidth = this.progressRef.current.offsetWidth; + if (firstIssue && skipToIssue) { this.props.jump(firstIssue.time); } } + onDragEnd = (item, monitor) => { + this.props.jump(this.seekTime); + if (this.wasPlaying) { + this.props.togglePlay(); + } + } + + onDrag = (offset) => { + const { endTime } = this.props; + + const p = (offset.x - 60) / this.progressRef.current.offsetWidth; + const time = Math.max(Math.round(p * endTime), 0); + this.seekTime = time; + if (this.props.playing) { + this.wasPlaying = true; + this.props.pause(); + } + } + render() { const { events, @@ -103,7 +133,7 @@ export default class Timeline extends React.PureComponent { live, logList, exceptionsList, - resourceList, + resourceList, clickRageTime, stackList, fetchList, @@ -111,12 +141,19 @@ export default class Timeline extends React.PureComponent { } = this.props; const scale = 100 / endTime; + return (
{ !live && } -
+
+ + { skip && skipIntervals.map(interval => (
{ + // TODO Find a better way to do this + document.addEventListener('keydown', onKeyDown); + + return () => { + document.removeEventListener('keydown', onKeyDown); + } + }, []) + + const onKeyDown = (e) => { + if (e.key === ' ') { + togglePlayAnimated() + } + } + const togglePlayAnimated = useCallback(() => { setShowPlayOverlayIcon(true); togglePlay(); - setTimeout( - () => setShowPlayOverlayIcon(false), - 800, - ); + setTimeout(() => setShowPlayOverlayIcon(false), 800); }, []); + return (
{ return { type: REFRESH_FILTER_OPTIONS } +} + +export const setScrollPosition = (scrollPosition) => { + return { + type: SET_SCROLL_POSITION, + scrollPosition, + } } \ No newline at end of file diff --git a/frontend/app/initialize.js b/frontend/app/initialize.js index dcfd01058..874bd0586 100644 --- a/frontend/app/initialize.js +++ b/frontend/app/initialize.js @@ -1,13 +1,9 @@ import './init'; - import { render } from 'react-dom'; import { Provider } from 'react-redux'; - import store from './store'; import Router from './Router'; import { StoreProvider, RootStore } from './mstore'; -import { ModalProvider } from './components/Modal'; -import ModalRoot from './components/Modal/ModalRoot'; import { HTML5Backend } from 'react-dnd-html5-backend' import { DndProvider } from 'react-dnd' @@ -17,10 +13,7 @@ document.addEventListener('DOMContentLoaded', () => { - {/* */} - {/* */} - {/* */} diff --git a/frontend/app/svg/icons/pie-chart-fill.svg b/frontend/app/svg/icons/pie-chart-fill.svg index 6aa71eb89..e3e67bb5c 100644 --- a/frontend/app/svg/icons/pie-chart-fill.svg +++ b/frontend/app/svg/icons/pie-chart-fill.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file From 43f0c50e2d37c3eb6e2fb56c0d814f787a184fd7 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 12 Apr 2022 12:45:06 +0200 Subject: [PATCH 16/32] change(ui) - css --- .../app/components/Session_/Player/Controls/timeline.css | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/app/components/Session_/Player/Controls/timeline.css b/frontend/app/components/Session_/Player/Controls/timeline.css index 4573afb23..da7a99cd8 100644 --- a/frontend/app/components/Session_/Player/Controls/timeline.css +++ b/frontend/app/components/Session_/Player/Controls/timeline.css @@ -1,9 +1,6 @@ -@import 'zindex.css'; - .positionTracker { width: 15px; height: 15px; - /* border: solid 1px $teal; */ outline: solid 1px $teal; outline-style: inset; margin-left: -7px; @@ -11,7 +8,7 @@ background-color: $active-blue; position: absolute; left: 0; - z-index: $positionTracker; + z-index: 98; top: 0; transition: all 0.2s ease-out; &:hover, From fc2336bc91244dc3886746170278ef45da5a0d35 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 12 Apr 2022 12:55:29 +0200 Subject: [PATCH 17/32] fix(ui) - metric selection height --- .../DashboardMetricSelection/DashboardMetricSelection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx b/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx index e0b4778c4..371815135 100644 --- a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx +++ b/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx @@ -89,7 +89,7 @@ function DashboardMetricSelection(props) {
{activeCategory && activeCategory.widgets.map((widget: any) => ( Date: Tue, 12 Apr 2022 16:25:31 +0200 Subject: [PATCH 18/32] feat(ui) - dashboard - wip --- .../app/components/Dashboard/NewDashboard.tsx | 18 +++++----- .../CustomMetricOverviewChart.tsx | 2 +- .../DashboardRouter/DashboardRouter.tsx | 16 ++++++--- .../DashboardSideMenu/DashboardSideMenu.tsx | 2 +- .../DashboardView/DashboardView.tsx | 22 +++++++++--- .../DashboardWidgetGrid.tsx | 2 +- .../app/components/Header/SiteDropdown.js | 7 ++++ .../app/components/ui/NoContent/NoContent.js | 4 ++- frontend/app/mstore/dashboardStore.ts | 36 +++++++++---------- frontend/app/mstore/types/widget.ts | 1 - frontend/app/svg/icons/no-metrics-chart.svg | 7 ++++ 11 files changed, 76 insertions(+), 41 deletions(-) create mode 100644 frontend/app/svg/icons/no-metrics-chart.svg diff --git a/frontend/app/components/Dashboard/NewDashboard.tsx b/frontend/app/components/Dashboard/NewDashboard.tsx index f66dbe4de..ca62377f3 100644 --- a/frontend/app/components/Dashboard/NewDashboard.tsx +++ b/frontend/app/components/Dashboard/NewDashboard.tsx @@ -20,15 +20,17 @@ function NewDashboard(props) { dashboardStore.fetchList().then((resp) => { if (parseInt(dashboardId) > 0) { dashboardStore.selectDashboardById(dashboardId); - } else { - dashboardStore.selectDefaultDashboard().then(({ dashboardId }) => { - if (!history.location.pathname.includes('/metrics')) { - history.push(withSiteId(dashboardSelected(dashboardId), siteId)); - } - }); - } + } + // else { + // dashboardStore.selectDefaultDashboard().then(({ dashboardId }) => { + // console.log('dashboardId', dashboardId) + // // if (!history.location.pathname.includes('/metrics')) { + // // history.push(withSiteId(dashboardSelected(dashboardId), siteId)); + // // } + // }); + // } }); - }, []); + }, [siteId]); return ( diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx index 93e0fbf6d..769e63fb4 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx @@ -12,8 +12,8 @@ interface Props { } function CustomMetricOverviewChart(props: Props) { const { data } = props; - console.log('data', data) const gradientDef = Styles.gradientDef(); + return (
diff --git a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx index 9621f1c71..6004202a2 100644 --- a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx +++ b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx @@ -15,6 +15,12 @@ import DashboardView from '../DashboardView'; import MetricsView from '../MetricsView'; import WidgetView from '../WidgetView'; +function DashboardViewSelected({ siteId, dashboardId}) { + return ( + + ) +} + interface Props { history: any match: any @@ -32,6 +38,10 @@ function DashboardRouter(props: Props) { + + + + @@ -40,12 +50,8 @@ function DashboardRouter(props: Props) { - - <>Nothing... - - - +
diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx index 3d1901be2..838d0930d 100644 --- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx @@ -18,7 +18,7 @@ function DashboardSideMenu(props: Props) { const { history, siteId } = props; const { hideModal, showModal } = useModal(); const { dashboardStore } = useStore(); - const dashboardId = dashboardStore.selectedDashboard?.dashboardId; + const dashboardId = useObserver(() => dashboardStore.selectedDashboard?.dashboardId); const dashboardsPicked = useObserver(() => dashboardStore.dashboards.slice(0, SHOW_COUNT)); const remainingDashboardsCount = dashboardStore.dashboards.length - SHOW_COUNT; diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index 5d698f704..b741c009b 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -23,12 +23,19 @@ function DashboardView(props: Props) { const { dashboardStore } = useStore(); const { hideModal, showModal } = useModal(); const loading = useObserver(() => dashboardStore.fetchingDashboard); - const dashboard: any = dashboardStore.selectedDashboard + const dashboards = useObserver(() => dashboardStore.dashboards); + const dashboard: any = useObserver(() => dashboardStore.selectedDashboard); const period = useObserver(() => dashboardStore.period); const [showEditModal, setShowEditModal] = React.useState(false); useEffect(() => { - dashboardStore.fetch(dashboardId) + if (!dashboard || !dashboard.dashboardId) return; + dashboardStore.fetch(dashboard.dashboardId) + }, [dashboard]); + + useEffect(() => { + if (dashboardId) return; + dashboardStore.selectDefaultDashboard(); }, []); const onAddWidgets = () => { @@ -58,9 +65,14 @@ function DashboardView(props: Props) { return useObserver(() => ( Create Dashboard + } >
diff --git a/frontend/app/components/Header/SiteDropdown.js b/frontend/app/components/Header/SiteDropdown.js index 74b650055..4f8b9ca60 100644 --- a/frontend/app/components/Header/SiteDropdown.js +++ b/frontend/app/components/Header/SiteDropdown.js @@ -13,7 +13,9 @@ import { clearSearch } from 'Duck/search'; import { fetchList as fetchIntegrationVariables } from 'Duck/customField'; import { fetchList as fetchAlerts } from 'Duck/alerts'; import { fetchWatchdogStatus } from 'Duck/watchdogs'; +import { withStore } from 'App/mstore' +@withStore @withRouter @connect(state => ({ sites: state.getIn([ 'site', 'list' ]), @@ -45,11 +47,16 @@ export default class SiteDropdown extends React.PureComponent { } switchSite = (siteId) => { + const { mstore } = this.props + + this.props.setSiteId(siteId); this.props.clearSearch(); this.props.fetchIntegrationVariables(); this.props.fetchAlerts(); this.props.fetchWatchdogStatus(); + + mstore.initClient(); } render() { diff --git a/frontend/app/components/ui/NoContent/NoContent.js b/frontend/app/components/ui/NoContent/NoContent.js index 83b4f53f3..846627260 100644 --- a/frontend/app/components/ui/NoContent/NoContent.js +++ b/frontend/app/components/ui/NoContent/NoContent.js @@ -5,6 +5,7 @@ export default ({ title = "No data available.", subtext, icon, + iconSize = 100, size, show = true, children = null, @@ -14,7 +15,8 @@ export default ({ }) => (!show ? children :
{ - icon &&
+ // icon &&
+ icon && } { title &&
{ title }
} { diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 6739d54b6..ced5de480 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -181,7 +181,8 @@ export default class DashboardStore implements IDashboardSotre { fetch(dashboardId: string): Promise { this.fetchingDashboard = true return dashboardService.getDashboard(dashboardId).then(response => { - this.selectedDashboard = new Dashboard().fromJson(response) + // const widgets = new Dashboard().fromJson(response).widgets + this.selectedDashboard?.update({ 'widgets' : new Dashboard().fromJson(response).widgets}) }).finally(() => { this.fetchingDashboard = false }) @@ -300,9 +301,9 @@ export default class DashboardStore implements IDashboardSotre { selectDashboardById = (dashboardId: any) => { this.selectedDashboard = this.dashboards.find(d => d.dashboardId == dashboardId) || new Dashboard(); - if (this.selectedDashboard.dashboardId) { - this.fetch(this.selectedDashboard.dashboardId) - } + // if (this.selectedDashboard.dashboardId) { + // this.fetch(this.selectedDashboard.dashboardId) + // } } setSiteId = (siteId: any) => { @@ -418,16 +419,16 @@ export default class DashboardStore implements IDashboardSotre { // } else { // data.chart = getChartFormatter(this.period)(Array.isArray(data) ? data : data.chart) // } - data.namesMap = Array.isArray(data) ? data - .map(i => Object.keys(i)) - .flat() - .filter(i => i !== 'time' && i !== 'timestamp') - .reduce((unique: any, item: any) => { - if (!unique.includes(item)) { - unique.push(item); - } - return unique; - }, []) : data.chart; + // data.namesMap = Array.isArray(data) ? data + // .map(i => Object.keys(i)) + // .flat() + // .filter(i => i !== 'time' && i !== 'timestamp') + // .reduce((unique: any, item: any) => { + // if (!unique.includes(item)) { + // unique.push(item); + // } + // return unique; + // }, []) : data.chart; // console.log('map', data.namesMap) // const _data = { ...data, namesMap: data.namesMap, chart: data.chart } // metric.setData(_data) @@ -447,9 +448,8 @@ export default class DashboardStore implements IDashboardSotre { return unique; }, []) } else { - _data['chart'] = data - _data['namesMap'] = data - .map(i => Object.keys(i)) + _data['chart'] = Array.isArray(data) ? data : [] + _data['namesMap'] = Array.isArray(data) ? data.map(i => Object.keys(i)) .flat() .filter(i => i !== 'time' && i !== 'timestamp') .reduce((unique: any, item: any) => { @@ -457,7 +457,7 @@ export default class DashboardStore implements IDashboardSotre { unique.push(item); } return unique; - }, []) + }, []) : [] } metric.setData(_data) diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index c32b78ed2..ee9c559b4 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -130,7 +130,6 @@ export default class Widget implements IWidget { this.position = json.config.position this.predefinedKey = json.predefinedKey }) - console.log(this.name, this.position) return this } diff --git a/frontend/app/svg/icons/no-metrics-chart.svg b/frontend/app/svg/icons/no-metrics-chart.svg new file mode 100644 index 000000000..74c3b7b2d --- /dev/null +++ b/frontend/app/svg/icons/no-metrics-chart.svg @@ -0,0 +1,7 @@ + + + + + + + From 53063f78470169f6c830461656dffcb1d08eea6d Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 12 Apr 2022 16:49:45 +0200 Subject: [PATCH 19/32] change(ui) - no content icon --- .../app/components/Alerts/Notifications/Notifications.js | 2 +- frontend/app/components/Announcements/Announcements.js | 2 +- .../BugFinder/SessionFlowList/SessionFlowList.js | 2 +- .../app/components/BugFinder/SessionList/SessionList.js | 2 +- frontend/app/components/Dashboard/Dashboard.js | 3 +-- .../Dashboard/components/MetricsList/MetricsList.tsx | 2 +- .../Dashboard/components/WidgetSessions/WidgetSessions.tsx | 2 +- .../Dashboard/components/WidgetWrapper/WidgetWrapper.tsx | 7 +------ frontend/app/components/Errors/Error/ErrorInfo.js | 2 +- .../app/components/Funnels/FunnelIssues/FunnelIssues.js | 2 +- .../Funnels/FunnelSessionList/FunnelSessionList.js | 2 +- frontend/app/components/Session_/Fetch/FetchDetails.js | 4 ++-- .../Session_/Fetch/components/Headers/Headers.tsx | 2 +- .../CustomMetrics/SessionListModal/SessionListModal.tsx | 2 +- .../app/components/shared/ResultTimings/ResultTimings.js | 2 +- frontend/app/components/ui/NoContent/NoContent.js | 3 ++- frontend/app/components/ui/NoContent/noContent.css | 4 ++-- 17 files changed, 20 insertions(+), 25 deletions(-) diff --git a/frontend/app/components/Alerts/Notifications/Notifications.js b/frontend/app/components/Alerts/Notifications/Notifications.js index a30ad824f..b4dd055a0 100644 --- a/frontend/app/components/Alerts/Notifications/Notifications.js +++ b/frontend/app/components/Alerts/Notifications/Notifications.js @@ -113,7 +113,7 @@ class Notifications extends React.Component { diff --git a/frontend/app/components/Announcements/Announcements.js b/frontend/app/components/Announcements/Announcements.js index 733b4ce37..e0d09f024 100644 --- a/frontend/app/components/Announcements/Announcements.js +++ b/frontend/app/components/Announcements/Announcements.js @@ -71,7 +71,7 @@ class Announcements extends React.Component { diff --git a/frontend/app/components/BugFinder/SessionFlowList/SessionFlowList.js b/frontend/app/components/BugFinder/SessionFlowList/SessionFlowList.js index c7dca4cf8..f4962573a 100644 --- a/frontend/app/components/BugFinder/SessionFlowList/SessionFlowList.js +++ b/frontend/app/components/BugFinder/SessionFlowList/SessionFlowList.js @@ -11,7 +11,7 @@ function SessionFlowList({ activeTab, savedFilters, loading }) { diff --git a/frontend/app/components/BugFinder/SessionList/SessionList.js b/frontend/app/components/BugFinder/SessionList/SessionList.js index 8ec60a884..938f15b30 100644 --- a/frontend/app/components/BugFinder/SessionList/SessionList.js +++ b/frontend/app/components/BugFinder/SessionList/SessionList.js @@ -97,7 +97,7 @@ export default class SessionList extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Dashboard.js b/frontend/app/components/Dashboard/Dashboard.js index 2b71ef253..9d84a8779 100644 --- a/frontend/app/components/Dashboard/Dashboard.js +++ b/frontend/app/components/Dashboard/Dashboard.js @@ -212,8 +212,7 @@ export default class Dashboard extends React.PureComponent { show={ noWidgets } title="You haven't added any insights widgets!" subtext="Add new to keep track of Processed Sessions, Application Activity, Errors and lot more." - icon - empty + animatedIcon="empty-state" > ( - +
Title
diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index cb39f7d3d..978fad1fb 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -23,7 +23,7 @@ function WidgetSessions(props: Props) { {widget.sessions.map((session: any) => ( diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index b27175c1a..bb47dbc98 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -61,10 +61,6 @@ function WidgetWrapper(props: Props) { } } - const editHandler = () => { - console.log('clicked', widget.metricId); - } - const onChartClick = () => { if (!isWidget || widget.metricType === 'predefined') return; props.history.push(withSiteId(dashboardMetricDetails(dashboardId, widget.metricId),siteId)); @@ -88,14 +84,13 @@ function WidgetWrapper(props: Props) {
-

{widget.name}

{isWidget && (
diff --git a/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js b/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js index 82d39037c..b46ef824d 100644 --- a/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js +++ b/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js @@ -44,7 +44,7 @@ function FunnelIssues(props) { { filteredList.take(displayedCount).map(issue => ( diff --git a/frontend/app/components/Funnels/FunnelSessionList/FunnelSessionList.js b/frontend/app/components/Funnels/FunnelSessionList/FunnelSessionList.js index 707049faa..f1b3ec67a 100644 --- a/frontend/app/components/Funnels/FunnelSessionList/FunnelSessionList.js +++ b/frontend/app/components/Funnels/FunnelSessionList/FunnelSessionList.js @@ -30,7 +30,7 @@ function FunnelSessionList(props) { { list.take(displayedCount).map(session => ( diff --git a/frontend/app/components/Session_/Fetch/FetchDetails.js b/frontend/app/components/Session_/Fetch/FetchDetails.js index b7a5386a1..6893c7194 100644 --- a/frontend/app/components/Session_/Fetch/FetchDetails.js +++ b/frontend/app/components/Session_/Fetch/FetchDetails.js @@ -44,7 +44,7 @@ export default class FetchDetails extends React.PureComponent { title="Body is Empty." size="small" show={ !payload } - icon="exclamation-circle" + animatedIcon="no-results" >
@@ -63,7 +63,7 @@ export default class FetchDetails extends React.PureComponent { title="Body is Empty." size="small" show={ !response } - icon="exclamation-circle" + animatedIcon="no-results" >
diff --git a/frontend/app/components/Session_/Fetch/components/Headers/Headers.tsx b/frontend/app/components/Session_/Fetch/components/Headers/Headers.tsx index fa941e36f..47ebff217 100644 --- a/frontend/app/components/Session_/Fetch/components/Headers/Headers.tsx +++ b/frontend/app/components/Session_/Fetch/components/Headers/Headers.tsx @@ -9,7 +9,7 @@ function Headers(props) { title="No data available." size="small" show={ !props.requestHeaders && !props.responseHeaders } - icon="exclamation-circle" + animatedIcon="no-results" > { props.requestHeaders && ( <> diff --git a/frontend/app/components/shared/CustomMetrics/SessionListModal/SessionListModal.tsx b/frontend/app/components/shared/CustomMetrics/SessionListModal/SessionListModal.tsx index 7da36c6a9..921795f97 100644 --- a/frontend/app/components/shared/CustomMetrics/SessionListModal/SessionListModal.tsx +++ b/frontend/app/components/shared/CustomMetrics/SessionListModal/SessionListModal.tsx @@ -101,7 +101,7 @@ function SessionListModal(props: Props) { { filteredSessions.map(session => ) } diff --git a/frontend/app/components/shared/ResultTimings/ResultTimings.js b/frontend/app/components/shared/ResultTimings/ResultTimings.js index 2cd82ee1e..120233074 100644 --- a/frontend/app/components/shared/ResultTimings/ResultTimings.js +++ b/frontend/app/components/shared/ResultTimings/ResultTimings.js @@ -24,7 +24,7 @@ function ResultTimings({ duration, timing }) { return ( diff --git a/frontend/app/components/ui/NoContent/NoContent.js b/frontend/app/components/ui/NoContent/NoContent.js index 846627260..ed5d63828 100644 --- a/frontend/app/components/ui/NoContent/NoContent.js +++ b/frontend/app/components/ui/NoContent/NoContent.js @@ -4,6 +4,7 @@ import styles from './noContent.css'; export default ({ title = "No data available.", subtext, + animatedIcon = false, icon, iconSize = 100, size, @@ -16,7 +17,7 @@ export default ({
{ // icon &&
- icon && + animatedIcon ?
: (icon && ) } { title &&
{ title }
} { diff --git a/frontend/app/components/ui/NoContent/noContent.css b/frontend/app/components/ui/NoContent/noContent.css index f4296757c..5cf7a0d24 100644 --- a/frontend/app/components/ui/NoContent/noContent.css +++ b/frontend/app/components/ui/NoContent/noContent.css @@ -34,7 +34,7 @@ } -.icon { +.no-results { display: block; margin: auto; background-image: svg-load(no-results.svg, fill=#CCC); @@ -46,7 +46,7 @@ margin-bottom: 20px; } -.emptyIcon { +.empty-state { display: block; margin: auto; background-image: svg-load(empty-state.svg, fill=#CCC); From d50d63ba7e331c7fd7eab50ff6e41f02ebc24966 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 12 Apr 2022 17:37:06 +0200 Subject: [PATCH 20/32] change(ui) - assist filter box --- .../BugFinder/SessionList/SessionList.js | 1 - .../WidgetPreview/WidgetPreview.tsx | 16 +++++------- .../WidgetSessions/WidgetSessions.tsx | 16 ++++++++---- .../LiveSessionSearch/LiveSessionSearch.tsx | 25 +++++++++++-------- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/frontend/app/components/BugFinder/SessionList/SessionList.js b/frontend/app/components/BugFinder/SessionList/SessionList.js index 938f15b30..64bf722f4 100644 --- a/frontend/app/components/BugFinder/SessionList/SessionList.js +++ b/frontend/app/components/BugFinder/SessionList/SessionList.js @@ -74,7 +74,6 @@ export default class SessionList extends React.PureComponent { componentDidMount() { const { scrollY } = this.props; - console.log('scrollY', scrollY); window.scrollTo(0, scrollY); } diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx index 4ec8a63e2..24bfdcbd8 100644 --- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx @@ -11,7 +11,8 @@ interface Props { } function WidgetPreview(props: Props) { const { className = '' } = props; - const { metricStore } = useStore(); + const { metricStore, dashboardStore } = useStore(); + const period = useObserver(() => dashboardStore.period); const metric: any = useObserver(() => metricStore.instance); const isTimeSeries = metric.metricType === 'timeseries'; const isTable = metric.metricType === 'table'; @@ -20,11 +21,6 @@ function WidgetPreview(props: Props) { metric.update({ [ name ]: value }); } - const onDateChange = (changedDates) => { - // setPeriod({ ...changedDates, rangeName: changedDates.rangeValue }) - metric.update({ ...changedDates, rangeName: changedDates.rangeValue }); - } - return useObserver(() => (
@@ -68,10 +64,10 @@ function WidgetPreview(props: Props) {
Time Range dashboardStore.setPeriod(period)} customRangeRight direction="left" /> diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index 978fad1fb..0ceace3c2 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -3,20 +3,26 @@ import { NoContent } from 'UI'; import cn from 'classnames'; import { useStore } from 'App/mstore'; import SessionItem from 'Shared/SessionItem'; - +import { useObserver } from 'mobx-react-lite'; +import { DateTime } from 'luxon'; interface Props { className?: string; } function WidgetSessions(props: Props) { const { className = '' } = props; const { dashboardStore } = useStore(); + const period = useObserver(() => dashboardStore.period); const widget = dashboardStore.currentWidget; - return ( + const range = period.toTimestamps() + const startTime = DateTime.fromMillis(range.startTimestamp).toFormat('LLL dd, yyyy HH:mm a'); + const endTime = DateTime.fromMillis(range.endTimestamp).toFormat('LLL dd, yyyy HH:mm a'); + + return useObserver(() => (
-
+

Sessions

- {/*
Showing all sessions between {startTime} and {endTime}
*/} +
between {startTime} and {endTime}
@@ -31,7 +37,7 @@ function WidgetSessions(props: Props) {
- ); + )); } export default WidgetSessions; \ No newline at end of file diff --git a/frontend/app/components/shared/LiveSessionSearch/LiveSessionSearch.tsx b/frontend/app/components/shared/LiveSessionSearch/LiveSessionSearch.tsx index 5f6e5f7cc..cc3fef487 100644 --- a/frontend/app/components/shared/LiveSessionSearch/LiveSessionSearch.tsx +++ b/frontend/app/components/shared/LiveSessionSearch/LiveSessionSearch.tsx @@ -4,9 +4,9 @@ import { connect } from 'react-redux'; import { edit, addFilter, addFilterByKeyAndValue } from 'Duck/liveSearch'; import FilterSelection from 'Shared/Filters/FilterSelection'; import { IconButton } from 'UI'; -import { FilterKey } from 'App/types/filter/filterType'; interface Props { + list: any, appliedFilter: any; edit: typeof edit; addFilter: typeof addFilter; @@ -53,16 +53,18 @@ function LiveSessionSearch(props: Props) { }); } - return ( + return props.list.size > 0 ? (
-
- -
+ { hasEvents || hasFilters && ( +
+ +
+ )}
@@ -75,9 +77,10 @@ function LiveSessionSearch(props: Props) {
- ); + ) : <>; } export default connect(state => ({ appliedFilter: state.getIn([ 'liveSearch', 'instance' ]), + list: state.getIn(['sessions', 'liveSessions']), }), { edit, addFilter, addFilterByKeyAndValue })(LiveSessionSearch); \ No newline at end of file From bb6401b675a11162d58fa49084b9129b21f524c9 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 13 Apr 2022 16:37:21 +0200 Subject: [PATCH 21/32] change(ui) - remove projects from client, and other fixes --- frontend/app/Router.js | 45 +++++------- frontend/app/api_client.js | 2 +- .../components/Announcements/Announcements.js | 2 +- .../app/components/BugFinder/BugFinder.js | 2 +- .../SessionCaptureRate/SessionCaptureRate.js | 2 +- .../Client/CustomFields/CustomFields.js | 2 +- .../Client/Integrations/IntegrationForm.js | 4 +- .../components/Client/Sites/NewSiteForm.js | 3 +- .../components/Client/Webhooks/Webhooks.js | 2 +- frontend/app/components/Client/client.css | 2 +- .../DashboardHeader/DashboardHeader.js | 2 +- .../app/components/Dashboard/NewDashboard.tsx | 18 ++--- .../Dashboard/SideMenu/SideMenuSection.js | 2 +- .../CustomMetriLineChart.tsx | 29 ++++---- .../CustomMetricOverviewChart.tsx | 18 ++--- .../CustomMetricPercentage.tsx | 2 +- .../CustomMetricWidget/CustomMetricWidget.tsx | 2 +- .../CustomMetricWidgetPreview.tsx | 2 +- .../PredefinedWidgets/CPULoad/CPULoad.tsx | 2 +- .../PredefinedWidgets/Crashes/Crashes.tsx | 5 +- .../DomBuildingTime/DomBuildingTime.tsx | 6 +- .../Widgets/PredefinedWidgets/FPS/FPS.tsx | 4 +- .../MemoryConsumption/MemoryConsumption.tsx | 2 +- .../DashboardView/DashboardView.tsx | 29 ++++---- .../components/WidgetChart/WidgetChart.tsx | 44 +++++++++--- .../WidgetSessions/WidgetSessions.tsx | 8 +-- .../components/WidgetView/WidgetView.tsx | 2 +- .../WidgetWrapper/WidgetWrapper.tsx | 1 + .../Funnels/FunnelDetails/FunnelDetails.js | 2 +- .../Funnels/FunnelHeader/FunnelDropdown.js | 2 +- .../FunnelIssueDetails/FunnelIssueDetails.js | 2 +- .../Funnels/FunnelIssues/FunnelIssues.js | 2 +- .../Funnels/FunnelList/FunnelList.js | 2 +- frontend/app/components/Header/Header.js | 2 +- .../OnboardingExplore/OnboardingExplore.js | 2 +- .../app/components/Header/SiteDropdown.js | 4 +- .../Session/Layout/ToolPanel/StackEvents.js | 2 +- .../Session_/Player/Overlay/AutoplayTimer.tsx | 2 +- .../components/Session_/PlayerBlockHeader.js | 2 +- .../Session_/StackEvents/StackEvents.js | 10 ++- .../StackEvents/UserEvent/UserEvent.js | 72 ++++++++++--------- .../app/components/hocs/withSiteIdRouter.js | 4 +- .../app/components/hocs/withSiteIdUpdater.js | 4 +- .../shared/SessionItem/SessionItem.js | 2 +- .../shared/SiteDropdown/SiteDropdown.js | 4 +- frontend/app/components/ui/Link/Link.js | 2 +- .../ui/SavedSearchList/SavedSearchList.js | 2 +- frontend/app/duck/site.js | 39 ++++++++-- frontend/app/duck/user.js | 29 ++------ frontend/app/mstore/dashboardStore.ts | 14 +++- frontend/app/mstore/types/filter.ts | 17 +++-- frontend/app/types/client/client.js | 10 +-- 52 files changed, 265 insertions(+), 212 deletions(-) diff --git a/frontend/app/Router.js b/frontend/app/Router.js index f42f3c456..ab41173bd 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -22,7 +22,7 @@ const FunnelIssueDetails = lazy(() => import('Components/Funnels/FunnelIssueDeta import WidgetViewPure from 'Components/Dashboard/components/WidgetView'; import Header from 'Components/Header/Header'; // import ResultsModal from 'Shared/Results/ResultsModal'; -import { fetchList as fetchIntegrationVariables } from 'Duck/customField'; +import { fetchList as fetchMetadata } from 'Duck/customField'; import { fetchList as fetchSiteList } from 'Duck/site'; import { fetchList as fetchAnnouncements } from 'Duck/announcements'; import { fetchList as fetchAlerts } from 'Duck/alerts'; @@ -80,7 +80,7 @@ const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB); @withStore @withRouter @connect((state) => { - const siteId = state.getIn([ 'user', 'siteId' ]); + const siteId = state.getIn([ 'site', 'siteId' ]); const jwt = state.get('jwt'); const changePassword = state.getIn([ 'user', 'account', 'changePassword' ]); const userInfoLoading = state.getIn([ 'user', 'fetchUserInfoRequest', 'loading' ]); @@ -88,7 +88,7 @@ const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB); jwt, siteId, changePassword, - sites: state.getIn([ 'user', 'client', 'sites' ]), + sites: state.getIn([ 'site', 'list' ]), isLoggedIn: jwt !== null && !changePassword, loading: siteId === null || userInfoLoading, email: state.getIn([ 'user', 'account', 'email' ]), @@ -103,7 +103,7 @@ const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB); fetchUserInfo, fetchTenants, setSessionPath, - fetchIntegrationVariables, + fetchMetadata, fetchSiteList, fetchAnnouncements, fetchAlerts, @@ -124,17 +124,18 @@ class Router extends React.Component { fetchInitialData = () => { Promise.all([ this.props.fetchUserInfo().then(() => { - const { mstore } = this.props - mstore.initClient(); - this.props.fetchIntegrationVariables() - }), - this.props.fetchSiteList().then(() => { - setTimeout(() => { - this.props.fetchAnnouncements(); - this.props.fetchAlerts(); - this.props.fetchWatchdogStatus(); - }, 100); - }), + this.props.fetchSiteList().then(() => { + const { mstore } = this.props + mstore.initClient(); + + setTimeout(() => { + this.props.fetchMetadata() + this.props.fetchAnnouncements(); + this.props.fetchAlerts(); + this.props.fetchWatchdogStatus(); + }, 100); + }) + }) ]) } @@ -197,25 +198,17 @@ class Router extends React.Component { { onboarding && } - { siteIdList.length === 0 && + {/* { siteIdList.length === 0 && - } + } */} + {/* DASHBOARD and Metrics */} - - - - - {/* - - - - */} diff --git a/frontend/app/api_client.js b/frontend/app/api_client.js index 626f033ea..98a1f4dfd 100644 --- a/frontend/app/api_client.js +++ b/frontend/app/api_client.js @@ -56,7 +56,7 @@ export const clean = (obj, forbidenValues = [ undefined, '' ]) => { export default class APIClient { constructor() { const jwt = store.getState().get('jwt'); - const siteId = store.getState().getIn([ 'user', 'siteId' ]); + const siteId = store.getState().getIn([ 'site', 'siteId' ]); this.init = { headers: { Accept: 'application/json', diff --git a/frontend/app/components/Announcements/Announcements.js b/frontend/app/components/Announcements/Announcements.js index e0d09f024..6252e4d79 100644 --- a/frontend/app/components/Announcements/Announcements.js +++ b/frontend/app/components/Announcements/Announcements.js @@ -96,6 +96,6 @@ class Announcements extends React.Component { export default connect(state => ({ announcements: state.getIn(['announcements', 'list']), loading: state.getIn(['announcements', 'fetchList', 'loading']), - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), sites: state.getIn([ 'site', 'list' ]), }), { fetchList, setLastRead })(Announcements); \ No newline at end of file diff --git a/frontend/app/components/BugFinder/BugFinder.js b/frontend/app/components/BugFinder/BugFinder.js index 85f899290..3ce09d23b 100644 --- a/frontend/app/components/BugFinder/BugFinder.js +++ b/frontend/app/components/BugFinder/BugFinder.js @@ -53,7 +53,7 @@ const allowedQueryKeys = [ sources: state.getIn([ 'customFields', 'sources' ]), filterValues: state.get('filterValues'), favoriteList: state.getIn([ 'sessions', 'favoriteList' ]), - currentProjectId: state.getIn([ 'user', 'siteId' ]), + currentProjectId: state.getIn([ 'site', 'siteId' ]), sites: state.getIn([ 'site', 'list' ]), watchdogs: state.getIn(['watchdogs', 'list']), activeFlow: state.getIn([ 'filters', 'activeFlow' ]), diff --git a/frontend/app/components/BugFinder/SessionCaptureRate/SessionCaptureRate.js b/frontend/app/components/BugFinder/SessionCaptureRate/SessionCaptureRate.js index 4c2b41218..f545bcacd 100644 --- a/frontend/app/components/BugFinder/SessionCaptureRate/SessionCaptureRate.js +++ b/frontend/app/components/BugFinder/SessionCaptureRate/SessionCaptureRate.js @@ -72,7 +72,7 @@ const SessionCaptureRate = props => { } export default connect(state => ({ - currentProjectId: state.getIn([ 'user', 'siteId' ]), + currentProjectId: state.getIn([ 'site', 'siteId' ]), captureRate: state.getIn(['watchdogs', 'captureRate']), loading: state.getIn(['watchdogs', 'savingCaptureRate', 'loading']), }), { diff --git a/frontend/app/components/Client/CustomFields/CustomFields.js b/frontend/app/components/Client/CustomFields/CustomFields.js index 081f11a58..b46994f0b 100644 --- a/frontend/app/components/Client/CustomFields/CustomFields.js +++ b/frontend/app/components/Client/CustomFields/CustomFields.js @@ -13,7 +13,7 @@ import { confirm } from 'UI/Confirmation'; fields: state.getIn(['customFields', 'list']).sortBy(i => i.index), field: state.getIn(['customFields', 'instance']), loading: state.getIn(['customFields', 'fetchRequest', 'loading']), - sites: state.getIn([ 'user', 'client', 'sites' ]), + sites: state.getIn([ 'site', 'list' ]), errors: state.getIn([ 'customFields', 'saveRequest', 'errors' ]), }), { init, diff --git a/frontend/app/components/Client/Integrations/IntegrationForm.js b/frontend/app/components/Client/Integrations/IntegrationForm.js index 3481068de..239958233 100644 --- a/frontend/app/components/Client/Integrations/IntegrationForm.js +++ b/frontend/app/components/Client/Integrations/IntegrationForm.js @@ -4,8 +4,8 @@ import SiteDropdown from 'Shared/SiteDropdown'; import { save, init, edit, remove, fetchList } from 'Duck/integrations/actions'; @connect((state, { name, customPath }) => ({ - sites: state.getIn([ 'user', 'client', 'sites' ]), - initialSiteId: state.getIn([ 'user', 'siteId' ]), + sites: state.getIn([ 'site', 'list' ]), + initialSiteId: state.getIn([ 'site', 'siteId' ]), list: state.getIn([ name, 'list' ]), config: state.getIn([ name, 'instance']), saving: state.getIn([ customPath || name, 'saveRequest', 'loading']), diff --git a/frontend/app/components/Client/Sites/NewSiteForm.js b/frontend/app/components/Client/Sites/NewSiteForm.js index 011ed8b01..b98a77a38 100644 --- a/frontend/app/components/Client/Sites/NewSiteForm.js +++ b/frontend/app/components/Client/Sites/NewSiteForm.js @@ -1,7 +1,8 @@ import { connect } from 'react-redux'; import { Input, Button, Label } from 'UI'; import { save, edit, update , fetchList } from 'Duck/site'; -import { pushNewSite, setSiteId } from 'Duck/user'; +import { pushNewSite } from 'Duck/user'; +import { setSiteId } from 'Duck/site'; import { withRouter } from 'react-router-dom'; import styles from './siteForm.css'; diff --git a/frontend/app/components/Client/Webhooks/Webhooks.js b/frontend/app/components/Client/Webhooks/Webhooks.js index 100887285..150ed05a6 100644 --- a/frontend/app/components/Client/Webhooks/Webhooks.js +++ b/frontend/app/components/Client/Webhooks/Webhooks.js @@ -59,7 +59,7 @@ class Webhooks extends React.PureComponent { title="No webhooks available." size="small" show={ noSlackWebhooks.size === 0 } - icon + animatedIcon="no-results" >
{ noSlackWebhooks.map(webhook => ( diff --git a/frontend/app/components/Client/client.css b/frontend/app/components/Client/client.css index d9406e4d8..8e69458ef 100644 --- a/frontend/app/components/Client/client.css +++ b/frontend/app/components/Client/client.css @@ -18,7 +18,7 @@ & .tabContent { background-color: white; padding: 25px; - margin-top: -30px; + /* margin-top: -30px; */ margin-right: -20px; width: 100%; } diff --git a/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.js b/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.js index dd2c35c76..c4160b598 100644 --- a/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.js +++ b/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.js @@ -35,6 +35,6 @@ const DashboardHeader = props => { export default connect(state => ({ period: state.getIn([ 'dashboard', 'period' ]), platform: state.getIn([ 'dashboard', 'platform' ]), - currentProjectId: state.getIn([ 'user', 'siteId' ]), + currentProjectId: state.getIn([ 'site', 'siteId' ]), sites: state.getIn([ 'site', 'list' ]), }), { setPeriod, setPlatform })(DashboardHeader) diff --git a/frontend/app/components/Dashboard/NewDashboard.tsx b/frontend/app/components/Dashboard/NewDashboard.tsx index ca62377f3..6b77ff830 100644 --- a/frontend/app/components/Dashboard/NewDashboard.tsx +++ b/frontend/app/components/Dashboard/NewDashboard.tsx @@ -3,18 +3,16 @@ import withPageTitle from 'HOCs/withPageTitle'; import { observer, useObserver } from "mobx-react-lite"; import { useStore } from 'App/mstore'; import { withRouter } from 'react-router-dom'; -import { - dashboardSelected, - withSiteId, -} from 'App/routes'; import DashboardSideMenu from './components/DashboardSideMenu'; import { Loader } from 'UI'; import DashboardRouter from './components/DashboardRouter'; +import cn from 'classnames'; function NewDashboard(props) { const { history, match: { params: { siteId, dashboardId, metricId } } } = props; const { dashboardStore } = useStore(); const loading = useObserver(() => dashboardStore.isLoading); + const isMetricDetails = history.location.pathname.includes('/metrics/') || history.location.pathname.includes('/metric/'); useEffect(() => { dashboardStore.fetchList().then((resp) => { @@ -32,20 +30,18 @@ function NewDashboard(props) { }); }, [siteId]); - return ( + return useObserver(() => (
-
+
-
+
- ); + )); } -export default withPageTitle('New Dashboard')( - withRouter(observer(NewDashboard)) -); \ No newline at end of file +export default withPageTitle('New Dashboard')(withRouter(NewDashboard)); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/SideMenu/SideMenuSection.js b/frontend/app/components/Dashboard/SideMenu/SideMenuSection.js index 801c02415..494ce7128 100644 --- a/frontend/app/components/Dashboard/SideMenu/SideMenuSection.js +++ b/frontend/app/components/Dashboard/SideMenu/SideMenuSection.js @@ -41,5 +41,5 @@ function SideMenuSection({ title, items, onItemClick, setShowAlerts, siteId }) { SideMenuSection.displayName = "SideMenuSection"; export default connect(state => ({ - siteId: state.getIn([ 'user', 'siteId' ]) + siteId: state.getIn([ 'site', 'siteId' ]) }), { setShowAlerts })(SideMenuSection); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx index 59417bd49..957f44deb 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx @@ -6,16 +6,17 @@ import { LineChart, Line, Legend } from 'recharts'; interface Props { data: any; params: any; - seriesMap: any; + // seriesMap: any; colors: any; onClick?: (event, index) => void; } function CustomMetriLineChart(props: Props) { - const { data, params, seriesMap = [], colors, onClick = () => null } = props; + const { data = { chart: [], namesMap: [] }, params, colors, onClick = () => null } = props; + return ( - { seriesMap.map((key, index) => ( + { Array.isArray(data.namesMap) && data.namesMap.map((key, index) => ( ))} diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx index 769e63fb4..1f65e1c81 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx @@ -16,24 +16,24 @@ function CustomMetricOverviewChart(props: Props) { return (
-
+
- +
{gradientDef} diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx index 0dfa6dbd3..7e3681f45 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx @@ -12,7 +12,7 @@ function CustomMetriPercentage(props: Props) { return (
{numberWithCommas(data.count)}
-
{`${parseInt(data.previousCount).toFixed(1)} ( ${parseInt(data.countProgress).toFixed(1)}% )`}
+
{`${parseInt(data.previousCount)} ( ${parseInt(data.countProgress).toFixed(1)}% )`}
from previous period.
) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx index 32f800e1f..15acd21bb 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx @@ -136,7 +136,7 @@ function CustomMetricWidget(props: Props) { diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx index 73321405c..a1a2534f9 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx @@ -168,7 +168,7 @@ function CustomMetricWidget(props: Props) { { metric.viewType === 'lineChart' && ( diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx index 5e5853251..d64747585 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx @@ -40,7 +40,7 @@ function CPULoad(props: Props) { name="Avg" type="monotone" unit="%" - dataKey="avgCpu" + dataKey="value" stroke={Styles.colors[0]} fillOpacity={ 1 } strokeWidth={ 2 } diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx index a73537d69..5dffda63d 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx @@ -32,14 +32,13 @@ function Crashes(props: Props) { {...Styles.yaxis} allowDecimals={false} tickFormatter={val => Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }} + label={{ ...Styles.axisLabelLeft, value: "Number of Crashes" }} /> Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }} + label={{ ...Styles.axisLabelLeft, value: "DOM Build Time (ms)" }} /> Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }} + label={{ ...Styles.axisLabelLeft, value: "Frames Per Second" }} />
- Time Range + {/* Time Range */}
- +
+ More + +
void; } function WidgetChart(props: Props) { const { isWidget = false, metric } = props; @@ -19,12 +21,32 @@ function WidgetChart(props: Props) { const period = useObserver(() => dashboardStore.period); const colors = Styles.customMetricColors; const [loading, setLoading] = useState(false) - const [seriesMap, setSeriesMap] = useState([]); - const params = { density: 28 } + const isOverviewWidget = metric.metricType === 'predefined' && metric.viewType === 'overview'; + const params = { density: isOverviewWidget ? 7 : 70 } const metricParams = { ...params } const prevMetricRef = useRef(); const [data, setData] = useState(metric.data); + const onChartClick = (event: any) => { + if (event) { + const payload = event.activePayload[0].payload; + const timestamp = payload.timestamp; + 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); + } + } + useEffect(() => { if (prevMetricRef.current && prevMetricRef.current.name !== metric.name) { prevMetricRef.current = metric; @@ -33,8 +55,8 @@ function WidgetChart(props: Props) { prevMetricRef.current = metric; setLoading(true); - const data = isWidget ? { ...params } : { ...metricParams, ...metric.toJson() }; - dashboardStore.fetchMetricChartData(metric, data, isWidget).then((res: any) => { + const payload = isWidget ? { ...params } : { ...metricParams, ...metric.toJson() }; + dashboardStore.fetchMetricChartData(metric, payload, isWidget).then((res: any) => { setData(res); }).finally(() => { setLoading(false); @@ -42,10 +64,10 @@ function WidgetChart(props: Props) { }, [period]); const renderChart = () => { - const { metricType, viewType, predefinedKey } = metric; + const { metricType, viewType } = metric; if (metricType === 'predefined') { - if (viewType === 'overview') { + if (isOverviewWidget) { return } return @@ -55,16 +77,16 @@ function WidgetChart(props: Props) { if (viewType === 'lineChart') { return ( ) } else if (viewType === 'progress') { return ( @@ -74,12 +96,12 @@ function WidgetChart(props: Props) { if (metricType === 'table') { if (viewType === 'table') { - return ; + return ; } else if (viewType === 'pieChart') { return ( diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index 0ceace3c2..ccfb0fbe7 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -11,12 +11,12 @@ interface Props { function WidgetSessions(props: Props) { const { className = '' } = props; const { dashboardStore } = useStore(); - const period = useObserver(() => dashboardStore.period); + const filter = useObserver(() => dashboardStore.drillDownFilter); const widget = dashboardStore.currentWidget; - const range = period.toTimestamps() - const startTime = DateTime.fromMillis(range.startTimestamp).toFormat('LLL dd, yyyy HH:mm a'); - const endTime = DateTime.fromMillis(range.endTimestamp).toFormat('LLL dd, yyyy HH:mm a'); + // const range = period.toTimestamps() + const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat('LLL dd, yyyy HH:mm a'); + const endTime = DateTime.fromMillis(filter.endTimestamp).toFormat('LLL dd, yyyy HH:mm a'); return useObserver(() => (
diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx index 8a5391948..d388386e6 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -38,7 +38,7 @@ function WidgetView(props: Props) { return useObserver(() => ( -
+
diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index bb47dbc98..6bb9ca875 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -63,6 +63,7 @@ function WidgetWrapper(props: Props) { const onChartClick = () => { if (!isWidget || widget.metricType === 'predefined') return; + props.history.push(withSiteId(dashboardMetricDetails(dashboardId, widget.metricId),siteId)); } diff --git a/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.js b/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.js index 48142ef13..17078976c 100644 --- a/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.js +++ b/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.js @@ -143,7 +143,7 @@ export default connect((state, props) => { funnelId: props.match.params.funnelId, activeStages: state.getIn(['funnels', 'activeStages']), funnelFilters: state.getIn(['funnels', 'funnelFilters']), - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), liveFilters: state.getIn(['funnelFilters', 'appliedFilter']), } }, { diff --git a/frontend/app/components/Funnels/FunnelHeader/FunnelDropdown.js b/frontend/app/components/Funnels/FunnelHeader/FunnelDropdown.js index 87f7983b7..a5b3bf445 100644 --- a/frontend/app/components/Funnels/FunnelHeader/FunnelDropdown.js +++ b/frontend/app/components/Funnels/FunnelHeader/FunnelDropdown.js @@ -33,5 +33,5 @@ function FunnelDropdown(props) { export default connect((state, props) => ({ funnels: state.getIn(['funnels', 'list']), funnel: state.getIn(['funnels', 'instance']), - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), }), { })(withRouter(FunnelDropdown)) diff --git a/frontend/app/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.js b/frontend/app/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.js index 6672bc580..77017e4ac 100644 --- a/frontend/app/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.js +++ b/frontend/app/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.js @@ -39,5 +39,5 @@ export default connect((state, props) => ({ issue: state.getIn(['funnels', 'issue']), issueId: props.match.params.issueId, funnelId: props.match.params.funnelId, - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), }), { fetchIssue, setNavRef, resetIssue })(withRouter(FunnelIssueDetails)) diff --git a/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js b/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js index b46ef824d..036565d60 100644 --- a/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js +++ b/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js @@ -73,7 +73,7 @@ export default connect(state => ({ list: state.getIn(['funnels', 'issues']), criticalIssuesCount: state.getIn(['funnels', 'criticalIssuesCount']), loading: state.getIn(['funnels', 'fetchIssuesRequest', 'loading']), - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), funnel: state.getIn(['funnels', 'instance']), activeStages: state.getIn(['funnels', 'activeStages']), funnelFilters: state.getIn(['funnels', 'funnelFilters']), diff --git a/frontend/app/components/Funnels/FunnelList/FunnelList.js b/frontend/app/components/Funnels/FunnelList/FunnelList.js index f7e3c70de..c96ef2e74 100644 --- a/frontend/app/components/Funnels/FunnelList/FunnelList.js +++ b/frontend/app/components/Funnels/FunnelList/FunnelList.js @@ -28,5 +28,5 @@ function FunnelList(props) { export default connect(state => ({ list: state.getIn(['funnels', 'list']), - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), }))(withRouter(FunnelList)) diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 9b8819cdc..f3aa726d1 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -151,7 +151,7 @@ export default withRouter(connect( state => ({ account: state.getIn([ 'user', 'account' ]), appearance: state.getIn([ 'user', 'account', 'appearance' ]), - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), sites: state.getIn([ 'site', 'list' ]), showAlerts: state.getIn([ 'dashboard', 'showAlerts' ]), boardingCompletion: state.getIn([ 'dashboard', 'boardingCompletion' ]) diff --git a/frontend/app/components/Header/OnboardingExplore/OnboardingExplore.js b/frontend/app/components/Header/OnboardingExplore/OnboardingExplore.js index c6d7aa179..d143a6bbd 100644 --- a/frontend/app/components/Header/OnboardingExplore/OnboardingExplore.js +++ b/frontend/app/components/Header/OnboardingExplore/OnboardingExplore.js @@ -37,7 +37,7 @@ const styles = { }; @connect(state => ({ - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), boarding: state.getIn([ 'dashboard', 'boarding' ]), boardingCompletion: state.getIn([ 'dashboard', 'boardingCompletion' ]), }), { diff --git a/frontend/app/components/Header/SiteDropdown.js b/frontend/app/components/Header/SiteDropdown.js index 4f8b9ca60..38fe6ec57 100644 --- a/frontend/app/components/Header/SiteDropdown.js +++ b/frontend/app/components/Header/SiteDropdown.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { setSiteId } from 'Duck/user'; +import { setSiteId } from 'Duck/site'; import { withRouter } from 'react-router-dom'; import { hasSiteId, siteChangeAvaliable } from 'App/routes'; import { STATUS_COLOR_MAP, GREEN } from 'Types/site'; @@ -19,7 +19,7 @@ import { withStore } from 'App/mstore' @withRouter @connect(state => ({ sites: state.getIn([ 'site', 'list' ]), - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), account: state.getIn([ 'user', 'account' ]), }), { setSiteId, diff --git a/frontend/app/components/Session/Layout/ToolPanel/StackEvents.js b/frontend/app/components/Session/Layout/ToolPanel/StackEvents.js index 0c10a38d1..6298a8a2e 100644 --- a/frontend/app/components/Session/Layout/ToolPanel/StackEvents.js +++ b/frontend/app/components/Session/Layout/ToolPanel/StackEvents.js @@ -66,7 +66,7 @@ function StackEvents({ export default connect(state => ({ hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'stack']) || - !state.getIn([ 'user', 'client', 'sites' ]).some(s => s.stackIntegrations), + !state.getIn([ 'site', 'list' ]).some(s => s.stackIntegrations), }), { hideHint })(StackEvents); \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.tsx b/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.tsx index 91ce53722..7bb72c475 100644 --- a/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.tsx +++ b/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.tsx @@ -52,6 +52,6 @@ function AutoplayTimer({ nextId, siteId, history }) { export default withRouter(connect(state => ({ - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), nextId: parseInt(state.getIn([ 'sessions', 'nextId' ])), }))(AutoplayTimer)) diff --git a/frontend/app/components/Session_/PlayerBlockHeader.js b/frontend/app/components/Session_/PlayerBlockHeader.js index abf94e11c..58a524022 100644 --- a/frontend/app/components/Session_/PlayerBlockHeader.js +++ b/frontend/app/components/Session_/PlayerBlockHeader.js @@ -41,7 +41,7 @@ const ASSIST_ROUTE = assistRoute(); issuesFetched: state.getIn([ 'issues', 'issuesFetched' ]), local: state.getIn(['sessions', 'timezone']), funnelRef: state.getIn(['funnels', 'navRef']), - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), metaList: state.getIn(['customFields', 'list']).map(i => i.key), closedLive: !!state.getIn([ 'sessions', 'errors' ]) || (isAssist && !session.live), } diff --git a/frontend/app/components/Session_/StackEvents/StackEvents.js b/frontend/app/components/Session_/StackEvents/StackEvents.js index 7145bf7fd..79c1e9c72 100644 --- a/frontend/app/components/Session_/StackEvents/StackEvents.js +++ b/frontend/app/components/Session_/StackEvents/StackEvents.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { connectPlayer } from 'Player'; +import { connectPlayer, jump } from 'Player'; import { NoContent, Tabs } from 'UI'; import withEnumToggle from 'HOCs/withEnumToggle'; import { hideHint } from 'Duck/components/player'; @@ -18,7 +18,7 @@ const TABS = [ ALL, ...typeList ].map(tab =>({ text: tab, key: tab })); })) @connect(state => ({ hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'stack']) || - !state.getIn([ 'user', 'client', 'sites' ]).some(s => s.stackIntegrations), + !state.getIn([ 'site', 'list' ]).some(s => s.stackIntegrations), }), { hideHint }) @@ -66,7 +66,11 @@ export default class StackEvents extends React.PureComponent { > { filteredStackEvents.map(userEvent => ( - + jump(userEvent.time) } + /> ))} diff --git a/frontend/app/components/Session_/StackEvents/UserEvent/UserEvent.js b/frontend/app/components/Session_/StackEvents/UserEvent/UserEvent.js index 93a901f0a..9c0e66816 100644 --- a/frontend/app/components/Session_/StackEvents/UserEvent/UserEvent.js +++ b/frontend/app/components/Session_/StackEvents/UserEvent/UserEvent.js @@ -1,6 +1,6 @@ import cn from 'classnames'; import { OPENREPLAY, SENTRY, DATADOG, STACKDRIVER } from 'Types/session/stackEvent'; -import { Modal, Icon, SlideModal } from 'UI'; +import { Modal, Icon, SlideModal, IconButton } from 'UI'; import withToggle from 'HOCs/withToggle'; import Sentry from './Sentry'; import JsonViewer from './JsonViewer'; @@ -54,34 +54,42 @@ export default class UserEvent extends React.PureComponent { return !!this.props.userEvent.payload; } + onClickDetails = (e) => { + e.stopPropagation(); + this.props.switchOpen(); + } + renderContent(modalTrigger) { const { userEvent } = this.props; //const message = this.getEventMessage(); return (
-
-
- - { userEvent.name } -
- { /* message && -
- { message } -
*/ - } -
+ // onClick={ this.props.switchOpen } // + onClick={ this.props.onJump } // + className={ + cn( + "group", + stl.userEvent, + this.getLevelClassname(), + { [ stl.modalTrigger ]: modalTrigger } + ) + } + > +
+
+ + { userEvent.name } +
+ { /* message && +
+ { message } +
*/ + } +
+ +
+
); } @@ -91,15 +99,15 @@ export default class UserEvent extends React.PureComponent { if (this.ifNeedModal()) { return ( - - { this.renderContent(true) } - + + { this.renderContent(true) } + // @withRouter @connect((state, props) => ({ urlSiteId: props.match.params.siteId, - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), }), { setSiteId, }) diff --git a/frontend/app/components/hocs/withSiteIdUpdater.js b/frontend/app/components/hocs/withSiteIdUpdater.js index 67a7dbc60..9319474ca 100644 --- a/frontend/app/components/hocs/withSiteIdUpdater.js +++ b/frontend/app/components/hocs/withSiteIdUpdater.js @@ -1,11 +1,11 @@ import { connect } from 'react-redux'; import { withSiteId } from 'App/routes'; -import { setSiteId } from 'Duck/user'; +import { setSiteId } from 'Duck/site'; export default BaseComponent => @connect((state, props) => ({ urlSiteId: props.match.params.siteId, - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), }), { setSiteId, }) diff --git a/frontend/app/components/shared/SessionItem/SessionItem.js b/frontend/app/components/shared/SessionItem/SessionItem.js index 64e4199ba..35434cf76 100644 --- a/frontend/app/components/shared/SessionItem/SessionItem.js +++ b/frontend/app/components/shared/SessionItem/SessionItem.js @@ -28,7 +28,7 @@ const SESSIONS_ROUTE = sessionsRoute(); // ) @connect(state => ({ timezone: state.getIn(['sessions', 'timezone']), - siteId: state.getIn([ 'user', 'siteId' ]), + siteId: state.getIn([ 'site', 'siteId' ]), }), { toggleFavorite, setSessionPath }) @withRouter export default class SessionItem extends React.PureComponent { diff --git a/frontend/app/components/shared/SiteDropdown/SiteDropdown.js b/frontend/app/components/shared/SiteDropdown/SiteDropdown.js index b83178512..5fc482389 100644 --- a/frontend/app/components/shared/SiteDropdown/SiteDropdown.js +++ b/frontend/app/components/shared/SiteDropdown/SiteDropdown.js @@ -5,7 +5,7 @@ const SiteDropdown = ({ contextName="", sites, onChange, value }) => { const options = sites.map(site => ({ value: site.id, text: site.host })).toJS(); return ( +
+ + + + ); +} + +export default CallWithErrors; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/Chart.js b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/Chart.js new file mode 100644 index 000000000..2f406622d --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/Chart.js @@ -0,0 +1,16 @@ +import { AreaChart, Area } from 'recharts'; +import { Styles } from '../../common'; + +const Chart = ({ data, compare }) => { + const colors = compare ? Styles.compareColors : Styles.colors; + + return ( + + + + ); +} + +Chart.displayName = 'Chart'; + +export default Chart; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/ImageInfo.js b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/ImageInfo.js new file mode 100644 index 000000000..8251bec60 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/ImageInfo.js @@ -0,0 +1,12 @@ +import { Popup, Icon, TextEllipsis } from 'UI'; +import styles from './imageInfo.css'; + +const ImageInfo = ({ data }) => ( +
+ +
+); + +ImageInfo.displayName = 'ImageInfo'; + +export default ImageInfo; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/MethodType.js b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/MethodType.js new file mode 100644 index 000000000..ba370b481 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/MethodType.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Label } from 'UI'; + +const MethodType = ({ data }) => { + return ( + + ) +} + +export default MethodType diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/callWithErrors.css b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/callWithErrors.css new file mode 100644 index 000000000..bc37a3991 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/callWithErrors.css @@ -0,0 +1,22 @@ +.topActions { + position: absolute; + top: -4px; + right: 50px; + display: flex; + justify-content: flex-end; +} + +.searchField { + padding: 4px 5px; + border-bottom: dotted thin $gray-light; + border-radius: 3px; + &:focus, + &:active { + border: solid thin transparent !important; + box-shadow: none; + background-color: $gray-light; + } + &:hover { + border: solid thin $gray-light !important; + } +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/imageInfo.css b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/imageInfo.css new file mode 100644 index 000000000..69030a582 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/imageInfo.css @@ -0,0 +1,39 @@ +.name { + display: flex; + align-items: center; + + & > span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 60%; + } +} + +.imagePreview { + max-width: 200px; + max-height: 200px; +} + +.imageWrapper { + display: flex; + flex-flow: column; + align-items: center; + width: 40px; + text-align: center; + margin-right: 10px; + & > span { + height: 16px; + } + & .label { + font-size: 9px; + color: $gray-light; + } +} + +.popup { + background-color: #f5f5f5 !important; + &:before { + background-color: #f5f5f5 !important; + } +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/index.ts new file mode 100644 index 000000000..4d3ba4df8 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/index.ts @@ -0,0 +1 @@ +export { default } from './CallWithErrors' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx index bd54bc5e3..f1e192587 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx @@ -9,26 +9,26 @@ import { interface Props { data: any + metric?: any } function CallsErrors4xx(props: Props) { - const { data } = props; + const { data, metric } = props; + console.log('asd', metric.data.namesMap) return ( - {/* { data.namesMap.map((key, index) => ( + { Array.isArray(metric.data.namesMap) && metric.data.namesMap.map((key, index) => ( - ))} */} + ))} diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx index e55bbb0cb..3dec42162 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx @@ -9,26 +9,25 @@ import { interface Props { data: any + metric?: any } function CallsErrors5xx(props: Props) { - const { data } = props; + const { data, metric } = props; return ( - {/* { data.namesMap.map((key, index) => ( + { Array.isArray(metric.data.namesMap) && metric.data.namesMap.map((key, index) => ( - ))} */} + ))} diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx index 5dffda63d..b27cf319b 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx @@ -10,24 +10,24 @@ import { interface Props { data: any + metric?: any } function Crashes(props: Props) { - const { data } = props; + const { data, metric } = props; const gradientDef = Styles.gradientDef(); - const params = { density: 70 } return ( {gradientDef} - + { - const _params = { density: 70 } + // const _params = { density: 70 } console.log('params', params) // TODO reload the data with new params; // this.props.fetchWidget(WIDGET_KEY, dashbaordStore.period, props.platform, { ..._params, url: params.value }) } @@ -34,7 +33,7 @@ function DomBuildingTime(props: Props) { return ( <>
@@ -45,7 +44,7 @@ function DomBuildingTime(props: Props) { onSelect={onSelect} placeholder="Search for Page" /> - +
{gradientDef} - +
- {data.chart.map((item, i) => + {metric.data.chart.map((item, i) => <>
@@ -27,12 +27,12 @@ function FPS(props: Props) {
{gradientDef} - + <>
@@ -27,12 +27,12 @@ function MemoryConsumption(props: Props) {
{gradientDef} - +
diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.tsx index 378a3abdc..0423a0007 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.tsx @@ -8,19 +8,19 @@ import { interface Props { data: any + metric?: any } function ResourceLoadedVsResponseEnd(props: Props) { - const { data } = props; - const params = { density: 70 } + const { data, metric } = props; return ( @@ -28,7 +28,7 @@ function ResourceLoadedVsResponseEnd(props: Props) { {...Styles.xaxis} dataKey="time" // interval={3} - interval={(params.density / 7)} + interval={(metric.params.density / 7)} /> { - const _params = { density: 70 } + // const _params = { density: 70 } setSutoCompleteSelected(params.value); console.log('params', params) // TODO reload the data with new params; // this.props.fetchWidget(WIDGET_KEY, dashbaordStore.period, props.platform, { ..._params, url: params.value }) @@ -52,7 +52,7 @@ function ResourceLoadingTime(props: Props) { return ( <>
@@ -80,17 +80,17 @@ function ResourceLoadingTime(props: Props) {
{gradientDef} - + Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }} + label={{ ...Styles.axisLabelLeft, value: "Resource Fetch Time (ms)" }} /> { - const _params = { density: 70 } + // const _params = { density: 70 } console.log('params', params) // TODO reload the data with new params; // this.props.fetchWidget(WIDGET_KEY, dashbaordStore.period, props.platform, { ..._params, url: params.value }) } @@ -34,7 +34,7 @@ function ResponseTime(props: Props) { return ( <>
@@ -45,7 +45,7 @@ function ResponseTime(props: Props) { onSelect={onSelect} placeholder="Search for Page" /> - +
{gradientDef} - + {gradientDef} - + Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }} + label={{ ...Styles.axisLabelLeft, value: "Number of Requests" }} /> { + return Object.keys(item) + .filter(i => i !== 'browser' && i !== 'count') + .map(i => ({ key: 'v' +i, value: item[i]})) + } + return ( + +
+ {metric.data.chart.map((item, i) => + + )} +
+
+ ); +} + +export default SessionsPerBrowser; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/index.ts new file mode 100644 index 000000000..06f0656a1 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/index.ts @@ -0,0 +1 @@ +export { default } from './SessionsPerBrowser' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx index a7334e650..9e2563752 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx @@ -6,17 +6,18 @@ import Bar from 'App/components/Dashboard/Widgets/SlowestDomains/Bar'; interface Props { data: any + metric?: any } function SlowestDomains(props: Props) { - const { data } = props; - const firstAvg = data.chart[0] && data.chart[0].errorsCount; + const { data, metric } = props; + const firstAvg = metric.data.chart[0] && metric.data.chart[0].errorsCount; return (
- {data.chart.map((item, i) => + {metric.data.chart.map((item, i) => { - const _params = { density: 70 } + // const _params = { density: 70 } console.log('params', params) // TODO reload the data with new params; // this.props.fetchWidget(WIDGET_KEY, dashbaordStore.period, props.platform, { ..._params, url: params.value }) } @@ -34,7 +34,7 @@ function TimeToRender(props: Props) { return ( <>
@@ -49,12 +49,12 @@ function TimeToRender(props: Props) {
{gradientDef} - + } - return + return } if (metricType === 'timeseries') { diff --git a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx index 7ae8a581f..151825b80 100644 --- a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx @@ -22,64 +22,69 @@ import ResourceLoadingTime from 'App/components/Dashboard/Widgets/PredefinedWidg import BreakdownOfLoadedResources from 'App/components/Dashboard/Widgets/PredefinedWidgets/BreakdownOfLoadedResources'; import MissingResources from 'App/components/Dashboard/Widgets/PredefinedWidgets/MissingResources'; import ResourceLoadedVsResponseEnd from 'App/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd'; +import SessionsPerBrowser from 'App/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser'; +import CallWithErrors from '../../Widgets/PredefinedWidgets/CallWithErrors'; interface Props { data: any; predefinedKey: string + metric?: any; } function WidgetPredefinedChart(props: Props) { - const { data, predefinedKey } = props; + const { data, predefinedKey, metric } = props; const renderWidget = () => { switch (predefinedKey) { // ERRORS case 'errors_per_type': - return + return case 'errors_per_domains': - return + return case 'resources_by_party': - return + return case 'impacted_sessions_by_js_errors': - return + return case 'domains_errors_4xx': - return + return case 'domains_errors_5xx': - return + return + case 'calls_errors': + return // PERFORMANCE // case 'impacted_sessions_by_slow_pages': // case 'pages_response_time_distribution': // case 'speed_location': case 'cpu': - return + return case 'crashes': - return + return case 'pages_dom_buildtime': - return + return case 'fps': - return + return case 'memory_consumption': - return + return case 'pages_response_time': - return + return case 'resources_vs_visually_complete': - return + return case 'sessions_per_browser': - return + return case 'slowest_domains': - return + return case 'time_to_render': - return + return // Resources case 'resources_count_by_type': - return + return case 'missing_resources': - return + return case 'resource_type_vs_response_end': - return + return case 'resources_loading_time': - return + return // case 'slowest_resources': default: diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 1166b245f..73a41b379 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -420,27 +420,6 @@ export default class DashboardStore implements IDashboardSotre { metric.setData(_data) resolve(_data); } else { - // if (metric.predefinedKey === 'errors_per_domains') { - // console.log('errors_per_domains', data) - // data.chart = data - // } else { - // data.chart = getChartFormatter(this.period)(Array.isArray(data) ? data : data.chart) - // } - // data.namesMap = Array.isArray(data) ? data - // .map(i => Object.keys(i)) - // .flat() - // .filter(i => i !== 'time' && i !== 'timestamp') - // .reduce((unique: any, item: any) => { - // if (!unique.includes(item)) { - // unique.push(item); - // } - // return unique; - // }, []) : data.chart; - // console.log('map', data.namesMap) - // const _data = { ...data, namesMap: data.namesMap, chart: data.chart } - // metric.setData(_data) - // resolve(_data); - const _data = { ...data, } @@ -457,7 +436,7 @@ export default class DashboardStore implements IDashboardSotre { return unique; }, []) } else { - _data['chart'] = Array.isArray(data) ? data : [] + _data['chart'] = getChartFormatter(this.period)(Array.isArray(data) ? data : []); _data['namesMap'] = Array.isArray(data) ? data.map(i => Object.keys(i)) .flat() .filter(i => i !== 'time' && i !== 'timestamp') diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index ee9c559b4..2c74ce26b 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -28,6 +28,8 @@ export interface IWidget { colSpan: number predefinedKey: string + params: any + udpateKey(key: string, value: any): void removeSeries(index: number): void addSeries(): void @@ -57,6 +59,7 @@ export default class Widget implements IWidget { dashboards: any[] = [] dashboardIds: any[] = [] config: any = {} + params: any = { density: 70 } position: number = 0 data: any = { From f9df0d2b91da4e4dbaca95e1660578c312ed2629 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 14 Apr 2022 15:02:19 +0200 Subject: [PATCH 23/32] feat(ui) - dashboard - widget drilldown --- .../CallsErrors4xx/CallsErrors4xx.tsx | 9 +- .../CallsErrors5xx/CallsErrors5xx.tsx | 4 +- .../ErrorsPerDomain/ErrorsPerDomain.tsx | 1 + .../components/WidgetChart/WidgetChart.tsx | 8 +- .../components/WidgetForm/WidgetForm.tsx | 25 +++-- .../WidgetSessions/WidgetSessions.tsx | 103 ++++++++++++++---- frontend/app/components/Errors/List/List.js | 2 +- frontend/app/mstore/dashboardStore.ts | 7 +- frontend/app/mstore/types/filter.ts | 32 +++++- frontend/app/mstore/types/session.ts | 79 ++++++++++++++ frontend/app/mstore/types/widget.ts | 35 +++++- frontend/app/services/MetricService.ts | 13 +++ 12 files changed, 274 insertions(+), 44 deletions(-) create mode 100644 frontend/app/mstore/types/session.ts diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx index f1e192587..e0f721078 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { NoContent } from 'UI'; import { Styles } from '../../common'; import { - BarChart, Bar, CartesianGrid, Tooltip, + CartesianGrid, Tooltip, LineChart, Line, Legend, ResponsiveContainer, XAxis, YAxis } from 'recharts'; @@ -12,15 +12,14 @@ interface Props { metric?: any } function CallsErrors4xx(props: Props) { - const { data, metric } = props; - console.log('asd', metric.data.namesMap) + const { data, metric } = props; return ( - @@ -40,7 +39,7 @@ function CallsErrors4xx(props: Props) { { Array.isArray(metric.data.namesMap) && metric.data.namesMap.map((key, index) => ( ))} - + ); diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx index 3dec42162..cc62baf45 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx @@ -19,7 +19,7 @@ function CallsErrors5xx(props: Props) { show={ metric.data.chart.length === 0 } > - @@ -39,7 +39,7 @@ function CallsErrors5xx(props: Props) { { Array.isArray(metric.data.namesMap) && metric.data.namesMap.map((key, index) => ( ))} - +
); diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx index aaae19efa..fab8ced65 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx @@ -16,6 +16,7 @@ function ErrorsPerDomain(props: Props) {
{metric.data.chart.map((item, i) => diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 24cbe0cc4..c3f8d10c2 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -19,6 +19,7 @@ function WidgetChart(props: Props) { const { isWidget = false, metric } = props; const { dashboardStore } = useStore(); const period = useObserver(() => dashboardStore.period); + const drillDownFilter = useObserver(() => dashboardStore.drillDownFilter); const colors = Styles.customMetricColors; const [loading, setLoading] = useState(false) const isOverviewWidget = metric.metricType === 'predefined' && metric.viewType === 'overview'; @@ -34,6 +35,11 @@ function WidgetChart(props: Props) { const periodTimestamps = metric.metricType === 'timeseries' ? getStartAndEndTimestampsByDensity(timestamp, period.start, period.end, params.density) : period.toTimestamps(); + + drillDownFilter.merge({ + startTimestamp: periodTimestamps.startTimestamp, + endTimestamp: periodTimestamps.endTimestamp, + }); // const activeWidget = { // widget: metric, @@ -42,8 +48,6 @@ function WidgetChart(props: Props) { // timestamp: payload.timestamp, // index, // } - - // props.setActiveWidget(activeWidget); } } diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index 6813a07aa..81d2ce430 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -20,7 +20,8 @@ interface Props { function WidgetForm(props: Props) { const [showDashboardSelectionModal, setShowDashboardSelectionModal] = useState(false); const { history, match: { params: { siteId, dashboardId, metricId } } } = props; - const { metricStore } = useStore(); + const { metricStore, dashboardStore } = useStore(); + const dashboards = dashboardStore.dashboards; const isSaving = useObserver(() => metricStore.isSaving); const metric: any = useObserver(() => metricStore.instance); @@ -29,6 +30,7 @@ function WidgetForm(props: Props) { const isTable = metric.metricType === 'table'; const isTimeSeries = metric.metricType === 'timeseries'; const _issueOptions = [{ text: 'All', value: 'all' }].concat(issueOptions); + const canAddToDashboard = metric.exists() && dashboards.length > 0; const write = ({ target: { value, name } }) => metricStore.merge({ [ name ]: value }); const writeOption = (e, { value, name }) => { @@ -193,7 +195,12 @@ function WidgetForm(props: Props) { Delete - @@ -201,13 +208,13 @@ function WidgetForm(props: Props) { )}
- - - setShowDashboardSelectionModal(false)} - /> + { canAddToDashboard && ( + setShowDashboardSelectionModal(false)} + /> + )} )); } diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index ccfb0fbe7..e6c9cb808 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -1,43 +1,106 @@ -import React from 'react'; -import { NoContent } from 'UI'; +import React, { useEffect, useState } from 'react'; +import { NoContent, Dropdown, Icon, Loader } from 'UI'; import cn from 'classnames'; import { useStore } from 'App/mstore'; import SessionItem from 'Shared/SessionItem'; -import { useObserver } from 'mobx-react-lite'; +import { observer, useObserver } from 'mobx-react-lite'; import { DateTime } from 'luxon'; interface Props { className?: string; } function WidgetSessions(props: Props) { const { className = '' } = props; - const { dashboardStore } = useStore(); - const filter = useObserver(() => dashboardStore.drillDownFilter); - const widget = dashboardStore.currentWidget; + const [data, setData] = useState([]); + const [seriesOptions, setSeriesOptions] = useState([ + { text: 'All', value: 'all' }, + ]); - // const range = period.toTimestamps() + const [activeSeries, setActiveSeries] = useState('all'); + + const writeOption = (e, { name, value }) => setActiveSeries(value); + useEffect(() => { + if (!data) return; + const seriesOptions = data.map(item => ({ + text: item.seriesName, + value: item.seriesId, + })); + setSeriesOptions([ + { text: 'All', value: 'all' }, + ...seriesOptions, + ]); + }, [data]); + + const filteredSessions = getListSessionsBySeries(data, activeSeries); + const { dashboardStore, metricStore } = useStore(); + const filter = useObserver(() => dashboardStore.drillDownFilter); + const widget: any = metricStore.instance; const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat('LLL dd, yyyy HH:mm a'); const endTime = DateTime.fromMillis(filter.endTimestamp).toFormat('LLL dd, yyyy HH:mm a'); + useEffect(() => { + widget.fetchSessions({ ...filter, filter: widget.toJsonDrilldown()}).then(res => { + console.log('res', res) + setData(res); + }); + }, [filter.startTimestamp, filter.endTimestamp, widget.filter]); + return useObserver(() => (
-
-

Sessions

-
between {startTime} and {endTime}
+
+
+

Sessions

+
between {startTime} and {endTime}
+
+ + { widget.metricType !== 'table' && ( +
+ Series + } + /> +
+ )}
- - {widget.sessions.map((session: any) => ( - - ))} - + + + {filteredSessions.map((session: any) => ( + + ))} + +
)); } -export default WidgetSessions; \ No newline at end of file +const getListSessionsBySeries = (data, seriesId) => { + const arr: any = [] + data.forEach(element => { + if (seriesId === 'all') { + const sessionIds = arr.map(i => i.sessionId); + arr.push(...element.sessions.filter(i => !sessionIds.includes(i.sessionId))); + } else { + if (element.seriesId === seriesId) { + arr.push(...element.sessions) + } + } + }); + return arr; +} + +export default observer(WidgetSessions); \ No newline at end of file diff --git a/frontend/app/components/Errors/List/List.js b/frontend/app/components/Errors/List/List.js index 2fa91c5e5..82ecce40c 100644 --- a/frontend/app/components/Errors/List/List.js +++ b/frontend/app/components/Errors/List/List.js @@ -219,7 +219,7 @@ export default class List extends React.PureComponent { diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 73a41b379..dc8b4b999 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -5,7 +5,7 @@ import { dashboardService, metricService } from "App/services"; import { toast } from 'react-toastify'; import Period, { LAST_24_HOURS, LAST_7_DAYS } from 'Types/app/period'; import { getChartFormatter } from 'Types/dashboard/helper'; -import Filter from "./types/filter"; +import Filter, { IFilter } from "./types/filter"; export interface IDashboardSotre { dashboards: IDashboard[] @@ -15,7 +15,7 @@ export interface IDashboardSotre { startTimestamp: number endTimestamp: number period: Period - drillDownFilter: Filter + drillDownFilter: IFilter siteId: any currentWidget: Widget @@ -29,6 +29,7 @@ export interface IDashboardSotre { isSaving: boolean isDeleting: boolean fetchingDashboard: boolean + sessionsLoading: boolean toggleAllSelectedWidgets: (isSelected: boolean) => void removeSelectedWidgetByCategory(category: string): void @@ -89,9 +90,11 @@ export default class DashboardStore implements IDashboardSotre { isSaving: boolean = false isDeleting: boolean = false fetchingDashboard: boolean = false + sessionsLoading: boolean = false; constructor() { makeAutoObservable(this, { + drillDownFilter: observable.ref, widgetCategories: observable.ref, resetCurrentWidget: action, addDashboard: action, diff --git a/frontend/app/mstore/types/filter.ts b/frontend/app/mstore/types/filter.ts index edd11f5fe..3c4e0aa1a 100644 --- a/frontend/app/mstore/types/filter.ts +++ b/frontend/app/mstore/types/filter.ts @@ -2,8 +2,27 @@ import { makeAutoObservable, runInAction, observable, action, reaction } from "m import { FilterKey, FilterType } from 'Types/filter/filterType' import { filtersMap } from 'Types/filter/newFilter' import FilterItem from "./filterItem" -export default class Filter { + +export interface IFilter { + filterId: string + name: string + filters: FilterItem[] + eventsOrder: string + startTimestamp: number + endTimestamp: number + + merge: (filter: any) => void + addFilter: (filter: FilterItem) => void + updateFilter: (index:number, filter: any) => void + updateKey: (key: any, value: any) => void + removeFilter: (index: number) => void + fromJson: (json: any) => void + toJson: () => any + toJsonDrilldown: () => any +} +export default class Filter implements IFilter { public static get ID_KEY():string { return "filterId" } + filterId: string = '' name: string = '' filters: FilterItem[] = [] eventsOrder: string = 'then' @@ -14,10 +33,19 @@ export default class Filter { makeAutoObservable(this, { filters: observable, eventsOrder: observable, + startTimestamp: observable, + endTimestamp: observable, addFilter: action, removeFilter: action, updateKey: action, + merge: action, + }) + } + + merge(filter: any) { + runInAction(() => { + Object.assign(this, filter) }) } @@ -36,7 +64,7 @@ export default class Filter { this.filters[index] = new FilterItem(filter) } - updateKey(key, value) { + updateKey(key: string, value) { this[key] = value } diff --git a/frontend/app/mstore/types/session.ts b/frontend/app/mstore/types/session.ts new file mode 100644 index 000000000..337e7009b --- /dev/null +++ b/frontend/app/mstore/types/session.ts @@ -0,0 +1,79 @@ +import { runInAction, makeAutoObservable, observable } from 'mobx' +import { List, Map } from 'immutable'; +import { DateTime, Duration } from 'luxon'; + +const HASH_MOD = 1610612741; +const HASH_P = 53; +function hashString(s: string): number { + let mul = 1; + let hash = 0; + for (let i = 0; i < s.length; i++) { + hash = (hash + s.charCodeAt(i) * mul) % HASH_MOD; + mul = (mul*HASH_P) % HASH_MOD; + } + return hash; +} + +export interface ISession { + sessionId: string + viewed: boolean + duration: number + metadata: any, + startedAt: number + userBrowser: string + userOs: string + userId: string + userDeviceType: string + userCountry: string + eventsCount: number + userNumericHash: number + userDisplayName: string +} + +export default class Session implements ISession { + sessionId: string = ""; + viewed: boolean = false + duration: number = 0 + metadata: any = Map() + startedAt: number = 0 + userBrowser: string = "" + userOs: string = "" + userId: string = "" + userDeviceType: string = "" + userCountry: string = "" + eventsCount: number = 0 + userNumericHash: number = 0 + userDisplayName: string = "" + + constructor() { + makeAutoObservable(this, { + sessionId: observable, + }) + } + + fromJson(session: any) { + runInAction(() => { + Object.keys(session).forEach(key => { + this[key] = session[key] + }) + + const { startTs, timestamp } = session; + const startedAt = +startTs || +timestamp; + + this.sessionId = session.sessionId + this.viewed = session.viewed + this.duration = Duration.fromMillis(session.duration < 1000 ? 1000 : session.duration); + this.metadata = Map(session.metadata) + this.startedAt = startedAt + this.userBrowser = session.userBrowser + this.userOs = session.userOs + this.userId = session.userId + this.userDeviceType = session.userDeviceType + this.eventsCount = session.eventsCount + this.userCountry = session.userCountry + this.userNumericHash = hashString(session.userId || session.userAnonymousId || session.userUuid || session.userID || session.userUUID || "") + this.userDisplayName = session.userId || session.userAnonymousId || session.userID || 'Anonymous User' + }) + return this + } +} \ No newline at end of file diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index 2c74ce26b..4d20ca0f1 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -1,7 +1,9 @@ import { makeAutoObservable, runInAction, observable, action, reaction, computed } from "mobx" import FilterSeries from "./filterSeries"; import { DateTime } from 'luxon'; - +import { IFilter } from "./filter"; +import { metricService } from "App/services"; +import Session, { ISession } from "App/mstore/types/session"; export interface IWidget { metricId: any widgetId: any @@ -20,6 +22,8 @@ export interface IWidget { dashboardIds: any[] config: any + sessionsLoading: boolean + position: number data: any isLoading: boolean @@ -34,12 +38,14 @@ export interface IWidget { removeSeries(index: number): void addSeries(): void fromJson(json: any): void + toJsonDrilldown(json: any): void toJson(): any validate(): void update(data: any): void exists(): boolean toWidget(): any setData(data: any): void + fetchSessions(filter: any): Promise } export default class Widget implements IWidget { public static get ID_KEY():string { return "metricId" } @@ -61,6 +67,8 @@ export default class Widget implements IWidget { config: any = {} params: any = { density: 70 } + sessionsLoading: boolean = false + position: number = 0 data: any = { chart: [], @@ -74,7 +82,9 @@ export default class Widget implements IWidget { constructor() { makeAutoObservable(this, { + sessionsLoading: observable, data: observable.ref, + metricId: observable, widgetId: observable, name: observable, metricType: observable, @@ -146,6 +156,12 @@ export default class Widget implements IWidget { } } + toJsonDrilldown() { + return { + series: this.series.map((series: any) => series.toJson()), + } + } + toJson() { return { metricId: this.metricId, @@ -179,4 +195,21 @@ export default class Widget implements IWidget { Object.assign(this.data, data) }) } + + fetchSessions(filter: any): Promise { + this.sessionsLoading = true + return new Promise((resolve, reject) => { + console.log('fetching sessions', filter) + metricService.fetchSessions(this.metricId, filter).then(response => { + resolve(response.map(cat => { + return { + ...cat, + sessions: cat.sessions.map(s => new Session().fromJson(s)) + } + })) + }).finally(() => { + this.sessionsLoading = false + }) + }) + } } \ No newline at end of file diff --git a/frontend/app/services/MetricService.ts b/frontend/app/services/MetricService.ts index a62520d52..0007bc8d1 100644 --- a/frontend/app/services/MetricService.ts +++ b/frontend/app/services/MetricService.ts @@ -1,5 +1,6 @@ import Widget, { IWidget } from "App/mstore/types/widget"; import APIClient from 'App/api_client'; +import { IFilter } from "App/mstore/types/filter"; export interface IMetricService { initClient(client?: APIClient): void; @@ -11,6 +12,7 @@ export interface IMetricService { getTemplates(): Promise; getMetricChartData(metric: IWidget, data: any, isWidget: boolean): Promise; + fetchSessions(metricId: string, filter: any): Promise } export default class MetricService implements IMetricService { @@ -88,4 +90,15 @@ export default class MetricService implements IMetricService { .then(response => response.json()) .then(response => response.data || {}); } + + /** + * Fetch sessions from the server. + * @param filter + * @returns + */ + fetchSessions(metricId: string, filter: any): Promise { + return this.client.post(`/metrics/${metricId}/sessions`, filter) + .then(response => response.json()) + .then(response => response.data || []); + } } \ No newline at end of file From af39480e09642d1bf23f729a4ee08e888b4ecdda Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 14 Apr 2022 17:00:02 +0200 Subject: [PATCH 24/32] feat(ui) - dashboard - ui review and fixes --- .../CustomMetricPercentage.tsx | 2 +- .../SlowestDomains/SlowestDomains.tsx | 1 + .../DashboardMetricSelection.tsx | 22 ++++++--- .../DashboardModal/DashboardModal.tsx | 8 ++-- .../DashboardSelectionModal.tsx | 2 +- .../DashboardSideMenu/DashboardSideMenu.tsx | 2 + .../DashboardView/DashboardView.tsx | 9 ++-- .../components/WidgetView/WidgetView.tsx | 2 +- .../WidgetWrapper/TemplateOverlay.tsx | 23 +++++++++ .../WidgetWrapper/WidgetWrapper.tsx | 29 +++++++----- .../app/components/Modal/ModalOverlay.tsx | 2 +- .../app/components/ui/ItemMenu/ItemMenu.js | 47 ++++++++++++------- frontend/app/mstore/dashboardStore.ts | 8 ++++ 13 files changed, 109 insertions(+), 48 deletions(-) create mode 100644 frontend/app/components/Dashboard/components/WidgetWrapper/TemplateOverlay.tsx diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx index 7e3681f45..ffce73783 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx @@ -12,7 +12,7 @@ function CustomMetriPercentage(props: Props) { return (
{numberWithCommas(data.count)}
-
{`${parseInt(data.previousCount)} ( ${parseInt(data.countProgress).toFixed(1)}% )`}
+
{`${parseInt(data.previousCount || 0)} ( ${parseInt(data.countProgress || 0).toFixed(1)}% )`}
from previous period.
) diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx index 9e2563752..2d74e2b39 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx @@ -15,6 +15,7 @@ function SlowestDomains(props: Props) {
{metric.data.chart.map((item, i) => diff --git a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx b/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx index 371815135..a999e0358 100644 --- a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx +++ b/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx @@ -17,8 +17,8 @@ function WidgetCategoryItem({ category, isSelected, onClick, selectedWidgetIds,
{category.description}
{selectedCategoryWidgetsCount > 0 && (
- unSelectCategory(category)} /> - {`Selected ${selectedCategoryWidgetsCount} of ${category.widgets.length}`} + {/* unSelectCategory(category)} /> */} + {`Selected ${selectedCategoryWidgetsCount} of ${category.widgets.length}`}
)}
@@ -29,6 +29,7 @@ function DashboardMetricSelection(props) { const { dashboardStore } = useStore(); let widgetCategories: any[] = useObserver(() => dashboardStore.widgetCategories); const [activeCategory, setActiveCategory] = React.useState(); + const [selectAllCheck, setSelectAllCheck] = React.useState(false); const selectedWidgetIds = useObserver(() => dashboardStore.selectedWidgets.map((widget: any) => widget.metricId)); useEffect(() => { @@ -39,10 +40,17 @@ function DashboardMetricSelection(props) { const handleWidgetCategoryClick = (category: any) => { setActiveCategory(category); + setSelectAllCheck(false); }; const toggleAllWidgets = ({ target: { checked }}) => { - dashboardStore.toggleAllSelectedWidgets(checked); + // dashboardStore.toggleAllSelectedWidgets(checked); + setSelectAllCheck(checked); + if (checked) { + dashboardStore.selectWidgetsByCategory(activeCategory.name); + } else { + dashboardStore.removeSelectedWidgetByCategory(activeCategory); + } } return useObserver(() => ( @@ -62,10 +70,10 @@ function DashboardMetricSelection(props) {
Showing past 7 days data for visual clue -
- - Select All -
+
)} diff --git a/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx b/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx index 4814ada0d..35277dc74 100644 --- a/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx +++ b/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx @@ -16,6 +16,7 @@ interface Props { function DashboardModal(props) { const { history, siteId, dashboardId } = props; const { dashboardStore } = useStore(); + const selectedWidgetsCount = useObserver(() => dashboardStore.selectedWidgets.length); const { hideModal } = useModal(); const dashboard = useObserver(() => dashboardStore.dashboardInstance); const loading = useObserver(() => dashboardStore.isSaving); @@ -33,7 +34,7 @@ function DashboardModal(props) { return useObserver(() => (
@@ -53,15 +54,16 @@ function DashboardModal(props) { )} -
+
+ {selectedWidgetsCount} Widgets
)); diff --git a/frontend/app/components/Dashboard/components/DashboardSelectionModal/DashboardSelectionModal.tsx b/frontend/app/components/Dashboard/components/DashboardSelectionModal/DashboardSelectionModal.tsx index f7a2c46ac..830730d7b 100644 --- a/frontend/app/components/Dashboard/components/DashboardSelectionModal/DashboardSelectionModal.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSelectionModal/DashboardSelectionModal.tsx @@ -29,7 +29,7 @@ function DashboardSelectionModal(props: Props) { return useObserver(() => ( -
{ 'Add to selected Dashboard' }
+
{ 'Add to selected dashboard' }
dashboardStore.selectedDashboard?.dashboardId); const dashboardsPicked = useObserver(() => dashboardStore.dashboards.slice(0, SHOW_COUNT)); const remainingDashboardsCount = dashboardStore.dashboards.length - SHOW_COUNT; + const isMetric = history.location.pathname.includes('metrics'); const redirect = (path) => { history.push(path); @@ -82,6 +83,7 @@ function DashboardSideMenu(props: Props) {
Create Dashboard } > -
+
setShowEditModal(false)} @@ -98,15 +98,16 @@ function DashboardView(props: Props) {
- More + {/* Options */} setExpanded(!expanded)} className="flex items-center cursor-pointer select-none" > - {expanded ? 'Collapse' : 'Expand'} + {expanded ? 'Close' : 'Edit'}
diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/TemplateOverlay.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/TemplateOverlay.tsx new file mode 100644 index 000000000..3b76f36de --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/TemplateOverlay.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Tooltip } from 'react-tippy'; + +function TemplateOverlay() { + return ( +
+ +
+ +
+ ); +} + +export default TemplateOverlay; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index 6bb9ca875..2aadc8733 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -9,6 +9,7 @@ import { useStore } from 'App/mstore'; import LazyLoad from 'react-lazyload'; import { withRouter } from 'react-router-dom'; import { withSiteId, dashboardMetricDetails } from 'App/routes'; +import TemplateOverlay from './TemplateOverlay'; interface Props { className?: string; @@ -27,7 +28,7 @@ interface Props { function WidgetWrapper(props: Props) { const { dashboardStore } = useStore(); const { isWidget = false, active = false, index = 0, moveListItem = null, isPreview = false, isTemplate = false, dashboardId, siteId } = props; - const widget = useObserver(() => props.widget); + const widget: any = useObserver(() => props.widget); const [{ opacity, isDragging }, dragRef] = useDrag({ type: 'item', @@ -40,7 +41,6 @@ function WidgetWrapper(props: Props) { const [{ isOver, canDrop }, dropRef] = useDrop({ accept: 'item', - drop: (item: any) => { if (item.index === index) return; moveListItem(item.index, index); @@ -52,13 +52,14 @@ function WidgetWrapper(props: Props) { }) const onDelete = async () => { - if (await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to permanently delete the widget from this dashboard?` - })) { - dashboardStore.deleteDashboardWidget(dashboardId!, widget.widgetId); - } + dashboardStore.deleteDashboardWidget(dashboardId!, widget.widgetId); + // if (await confirm({ + // header: 'Confirm', + // confirmButton: 'Yes, delete', + // confirmation: `Are you sure you want to permanently delete the widget from this dashboard?` + // })) { + // dashboardStore.deleteDashboardWidget(dashboardId!, widget.widgetId); + // } } const onChartClick = () => { @@ -72,9 +73,8 @@ function WidgetWrapper(props: Props) { return useObserver(() => (
{}} > + {isTemplate && }

{widget.name}

{isWidget && ( @@ -92,9 +93,11 @@ function WidgetWrapper(props: Props) { items={[ { text: 'Edit', onClick: onChartClick, + disabled: widget.metricType === 'predefined', + disabledMessage: 'Cannot edit system generated metrics' }, { - text: 'Hide from view', + text: 'Remove from view', onClick: onDelete }, ]} diff --git a/frontend/app/components/Modal/ModalOverlay.tsx b/frontend/app/components/Modal/ModalOverlay.tsx index 85b314eec..4dad0ec61 100644 --- a/frontend/app/components/Modal/ModalOverlay.tsx +++ b/frontend/app/components/Modal/ModalOverlay.tsx @@ -6,7 +6,7 @@ function ModalOverlay({ children }) { let modal = useModal(); return ( -
+
modal.hideModal()} className={stl.overlay} diff --git a/frontend/app/components/ui/ItemMenu/ItemMenu.js b/frontend/app/components/ui/ItemMenu/ItemMenu.js index 2663a7b2a..841d9e4c9 100644 --- a/frontend/app/components/ui/ItemMenu/ItemMenu.js +++ b/frontend/app/components/ui/ItemMenu/ItemMenu.js @@ -2,7 +2,7 @@ import { Icon } from 'UI'; import styles from './itemMenu.css'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; import cn from 'classnames'; - +import { Tooltip } from 'react-tippy'; export default class ItemMenu extends React.PureComponent { state = { displayed: false, @@ -20,7 +20,7 @@ export default class ItemMenu extends React.PureComponent { closeMenu = () => this.setState({ displayed: false }) render() { - const { items } = this.props; + const { items, label = "" } = this.props; const { displayed } = this.state; return ( @@ -28,33 +28,46 @@ export default class ItemMenu extends React.PureComponent { -
{ this.menuBtnRef = ref; } } - className={cn("w-10 h-10 cursor-pointer rounded-full flex items-center justify-center hover:bg-gray-light", { 'bg-gray-light' : displayed })} - onClick={ this.toggleMenu } - role="button" - > - +
+ {label && {label}} +
{ this.menuBtnRef = ref; } } + className={cn("w-10 h-10 rounded-full flex items-center justify-center hover:bg-gray-light", { 'bg-gray-light' : displayed })} + role="button" + > + +
- { items.filter(({ hidden }) => !hidden).map(({ onClick, text, icon }) => ( + { items.filter(({ hidden }) => !hidden).map(({ onClick, text, icon, disabled = false, disabledMessage = '' }) => (
{} } role="menuitem" tabIndex="-1" > - { icon && ( -
- + +
+ { icon && ( +
+ +
+ )} +
{ text }
- )} -
{ text }
+
))}
diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index dc8b4b999..fb33ac89e 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -31,6 +31,7 @@ export interface IDashboardSotre { fetchingDashboard: boolean sessionsLoading: boolean + selectWidgetsByCategory: (category: string) => void toggleAllSelectedWidgets: (isSelected: boolean) => void removeSelectedWidgetByCategory(category: string): void toggleWidgetSelection(widget: IWidget): void @@ -111,6 +112,7 @@ export default class DashboardStore implements IDashboardSotre { editWidget: action, updateKey: action, + selectWidgetsByCategory: action, toggleAllSelectedWidgets: action, removeSelectedWidgetByCategory: action, toggleWidgetSelection: action, @@ -138,6 +140,12 @@ export default class DashboardStore implements IDashboardSotre { } } + selectWidgetsByCategory(category: string) { + const selectedWidgetIds = this.selectedWidgets.map((widget: any) => widget.metricId); + const widgets = this.widgetCategories.find(cat => cat.name === category)?.widgets.filter(widget => !selectedWidgetIds.includes(widget.metricId)) + this.selectedWidgets = this.selectedWidgets.concat(widgets) || [] + } + removeSelectedWidgetByCategory = (category: any) => { const categoryWidgetIds = category.widgets.map(w => w.metricId) this.selectedWidgets = this.selectedWidgets.filter((widget: any) => !categoryWidgetIds.includes(widget.metricId)); From 2c3c0c30c085ab08720a65cbe56733d257ae1ec6 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 14 Apr 2022 18:29:33 +0200 Subject: [PATCH 25/32] feat(ui) - dashboard - ui review and fixes --- .../PredefinedWidgets/CPULoad/CPULoad.tsx | 1 + .../PredefinedWidgets/Crashes/Crashes.tsx | 1 + .../DashboardSideMenu/DashboardSideMenu.tsx | 27 ++++++++++++++---- .../DashboardView/DashboardView.tsx | 17 ++++++----- .../components/WidgetWrapper/AlertButton.tsx | 28 +++++++++++++++++++ .../components/WidgetWrapper/WidgetIcon.tsx | 27 ++++++++++++++++++ .../WidgetWrapper/WidgetWrapper.tsx | 16 +++++++++-- frontend/app/components/Modal/index.tsx | 12 +++++++- frontend/app/mstore/dashboardStore.ts | 4 +++ 9 files changed, 114 insertions(+), 19 deletions(-) create mode 100644 frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx create mode 100644 frontend/app/components/Dashboard/components/WidgetWrapper/WidgetIcon.tsx diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx index 3ae354168..53356bf0d 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx @@ -20,6 +20,7 @@ function CPULoad(props: Props) { void } function DashboardSideMenu(props: Props) { - const { history, siteId } = props; + const { history, siteId, setShowAlerts } = props; const { hideModal, showModal } = useModal(); const { dashboardStore } = useStore(); const dashboardId = useObserver(() => dashboardStore.selectedDashboard?.dashboardId); @@ -54,9 +58,19 @@ function DashboardSideMenu(props: Props) { onClick={() => onItemClick(item)} className="group" leading = {( -
+
{item.isPublic &&
} - {
togglePinned(item)}>
} + {item.isPinned &&
} + {!item.isPinned && ( + +
togglePinned(item)} + > + +
+
+ )}
)} /> @@ -96,11 +110,12 @@ function DashboardSideMenu(props: Props) { id="menu-manage-alerts" title="Alerts" iconName="bell-plus" - // onClick={() => setShowAlerts(true)} + onClick={() => setShowAlerts(true)} />
)); } -export default withRouter(DashboardSideMenu); \ No newline at end of file +export default connect((state) => { +}, { setShowAlerts })(withRouter(DashboardSideMenu)); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index 000a3c59c..b412dc1f2 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -11,6 +11,7 @@ import { useModal } from 'App/components/Modal'; import DashboardModal from '../DashboardModal'; import DashboardEditModal from '../DashboardEditModal'; import DateRange from 'Shared/DateRange'; +import AlertFormModal from 'App/components/Alerts/AlertFormModal'; interface Props { siteId: number; @@ -22,6 +23,7 @@ function DashboardView(props: Props) { const { siteId, dashboardId } = props; const { dashboardStore } = useStore(); const { hideModal, showModal } = useModal(); + const showAlertModal = useObserver(() => dashboardStore.showAlertModal); const loading = useObserver(() => dashboardStore.fetchingDashboard); const dashboards = useObserver(() => dashboardStore.dashboards); const dashboard: any = useObserver(() => dashboardStore.selectedDashboard); @@ -98,18 +100,11 @@ function DashboardView(props: Props) {
- {/* Options */}
@@ -120,6 +115,10 @@ function DashboardView(props: Props) { dashboardId={dashboardId} onEditHandler={onAddWidgets} /> + dashboardStore.updateKey('showAlertModal', false)} + />
diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx new file mode 100644 index 000000000..78d858b3e --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import WidgetIcon from './WidgetIcon'; +import { init as initAlert } from 'Duck/alerts'; +import { useStore } from 'App/mstore'; + +interface Props { + seriesId: string; + initAlert: Function; +} +function AlertButton(props: Props) { + const { seriesId, initAlert } = props; + const { dashboardStore } = useStore(); + const onClick = () => { + initAlert({ query: { left: seriesId }}) + dashboardStore.updateKey('showAlertModal', true); + } + return ( + + ); +} + +export default connect(null, { initAlert })(AlertButton); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetIcon.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetIcon.tsx new file mode 100644 index 000000000..5ed7c2a45 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetIcon.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Icon } from 'UI'; +import { Tooltip } from 'react-tippy'; + +interface Props { + className: string + onClick: () => void + icon: string + tooltip: string +} +function WidgetIcon(props: Props) { + const { className, onClick, icon, tooltip } = props; + return ( + +
+ +
+
+ ); +} + +export default WidgetIcon; diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index 2aadc8733..8f58e6691 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -4,12 +4,14 @@ import { ItemMenu } from 'UI'; import { useDrag, useDrop } from 'react-dnd'; import WidgetChart from '../WidgetChart'; import { useObserver } from 'mobx-react-lite'; -import { confirm } from 'UI/Confirmation'; +// import { confirm } from 'UI/Confirmation'; import { useStore } from 'App/mstore'; import LazyLoad from 'react-lazyload'; import { withRouter } from 'react-router-dom'; import { withSiteId, dashboardMetricDetails } from 'App/routes'; import TemplateOverlay from './TemplateOverlay'; +import WidgetIcon from './WidgetIcon'; +import AlertButton from './AlertButton'; interface Props { className?: string; @@ -29,6 +31,7 @@ function WidgetWrapper(props: Props) { const { dashboardStore } = useStore(); const { isWidget = false, active = false, index = 0, moveListItem = null, isPreview = false, isTemplate = false, dashboardId, siteId } = props; const widget: any = useObserver(() => props.widget); + const isPredefined = widget.metricType === 'predefined'; const [{ opacity, isDragging }, dragRef] = useDrag({ type: 'item', @@ -63,7 +66,7 @@ function WidgetWrapper(props: Props) { } const onChartClick = () => { - if (!isWidget || widget.metricType === 'predefined') return; + if (!isWidget || isPredefined) return; props.history.push(withSiteId(dashboardMetricDetails(dashboardId, widget.metricId),siteId)); } @@ -88,7 +91,14 @@ function WidgetWrapper(props: Props) { >

{widget.name}

{isWidget && ( -
+
+ {!isPredefined && ( + <> + +
+ + )} + { + if (e.keyCode === 27) { + this.hideModal(); + } + } + showModal = (component, props = {}) => { this.setState({ component, props }); + document.addEventListener('keydown', this.handleKeyDown); }; - hideModal = () => + hideModal = () => { this.setState({ component: null, props: {} }); + document.removeEventListener('keydown', this.handleKeyDown); + } state = { component: null, diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index fb33ac89e..ba8c23e58 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -31,6 +31,8 @@ export interface IDashboardSotre { fetchingDashboard: boolean sessionsLoading: boolean + showAlertModal: boolean + selectWidgetsByCategory: (category: string) => void toggleAllSelectedWidgets: (isSelected: boolean) => void removeSelectedWidgetByCategory(category: string): void @@ -93,6 +95,8 @@ export default class DashboardStore implements IDashboardSotre { fetchingDashboard: boolean = false sessionsLoading: boolean = false; + showAlertModal: boolean = false; + constructor() { makeAutoObservable(this, { drillDownFilter: observable.ref, From dc18805699ba614ce81ec8cddffcc36f4c621b58 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 14 Apr 2022 19:23:08 +0200 Subject: [PATCH 26/32] feat(ui) - dashboard - other widgets --- .../CallsErrors4xx/CallsErrors4xx.tsx | 1 + .../CallsErrors5xx/CallsErrors5xx.tsx | 1 + .../DomBuildingTime/DomBuildingTime.tsx | 4 +- .../ErrorsByOrigin/ErrorsByOrigin.tsx | 1 + .../ErrorsByType/ErrorsByType.tsx | 1 + .../ResourceLoadingTime.tsx | 4 +- .../ResponseTime/ResponseTime.tsx | 4 +- .../SessionsAffectedByJSErrors.tsx | 1 + .../SlowestResources/Chart.js | 15 +++ .../SlowestResources/CopyPath.js | 23 ++++ .../SlowestResources/ImageInfo.js | 27 +++++ .../SlowestResources/ResourceType.js | 12 ++ .../SlowestResources/SlowestResources.tsx | 81 +++++++++++++ .../SlowestResources/imageInfo.css | 52 ++++++++ .../SlowestResources/index.ts | 1 + .../SpeedIndexByLocation/Scale.js | 24 ++++ .../SpeedIndexByLocation.js | 112 ++++++++++++++++++ .../SpeedIndexByLocation/index.ts | 1 + .../SpeedIndexByLocation/scale.css | 11 ++ .../TimeToRender/TimeToRender.tsx | 4 +- .../DashboardSideMenu/DashboardSideMenu.tsx | 3 +- .../WidgetPredefinedChart.tsx | 8 +- 22 files changed, 379 insertions(+), 12 deletions(-) create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/Chart.js create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/CopyPath.js create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/ImageInfo.js create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/ResourceType.js create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/imageInfo.css create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/Scale.js create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.js create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/scale.css diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx index e0f721078..afaaeb37d 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx @@ -17,6 +17,7 @@ function CallsErrors4xx(props: Props) { <>
- + /> */}
diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx index 8e1a125f0..87fd42eb3 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx @@ -17,6 +17,7 @@ function ErrorsByOrigin(props: Props) { <>
- + /> */}
diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx index d1af7d2b4..a9ef6dcac 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx @@ -38,13 +38,13 @@ function ResponseTime(props: Props) { > <>
- + /> */}
diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx index a2206f2f9..0c077e747 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx @@ -17,6 +17,7 @@ function SessionsAffectedByJSErrors(props: Props) { { + const colors = compare ? Styles.compareColors : Styles.colors; + return ( + + + + ); +} + +Chart.displayName = 'Chart'; + +export default Chart; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/CopyPath.js b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/CopyPath.js new file mode 100644 index 000000000..6b7e709e7 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/CopyPath.js @@ -0,0 +1,23 @@ +import React from 'react' +import copy from 'copy-to-clipboard' +import { useState } from 'react' + +const CopyPath = ({ data }) => { + const [copied, setCopied] = useState(false) + + const copyHandler = () => { + copy(data.url); + setCopied(true); + setTimeout(function() { + setCopied(false) + }, 500); + } + + return ( +
+ { copied ? 'Copied' : 'Copy Path'} +
+ ) +} + +export default CopyPath diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/ImageInfo.js b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/ImageInfo.js new file mode 100644 index 000000000..fed6b71b6 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/ImageInfo.js @@ -0,0 +1,27 @@ +import { Popup } from 'UI'; +import cn from 'classnames'; +import styles from './imageInfo.css'; + +const supportedTypes = ['png', 'jpg', 'jpeg', 'svg']; + +const ImageInfo = ({ data }) => { + const canPreview = supportedTypes.includes(data.type); + return ( +
+ +
{data.name}
+
+ } + disabled={!canPreview} + content={ One of the slowest images } + /> +
+ ) +}; + +ImageInfo.displayName = 'ImageInfo'; + +export default ImageInfo; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/ResourceType.js b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/ResourceType.js new file mode 100644 index 000000000..9803a050f --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/ResourceType.js @@ -0,0 +1,12 @@ +import React from 'react' +import cn from 'classnames' + +const ResourceType = ({ data : { type = 'js' }, compare }) => { + return ( +
+ { type.toUpperCase() } +
+ ) +} + +export default ResourceType diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx new file mode 100644 index 000000000..ca62855b0 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles, Table } from '../../common'; +import { List } from 'immutable'; +import { numberWithCommas } from 'App/utils'; + +import Chart from './Chart'; +import ImageInfo from './ImageInfo'; +import ResourceType from './ResourceType'; +import CopyPath from './CopyPath'; + +export const RESOURCE_OPTIONS = [ + { text: 'All', value: 'ALL', }, + { text: 'CSS', value: 'STYLESHEET', }, + { text: 'JS', value: 'SCRIPT', }, +]; + +const cols = [ + { + key: 'type', + title: 'Type', + Component: ResourceType, + className: 'text-center justify-center', + cellClass: 'ml-2', + width: '8%', + }, + { + key: 'name', + title: 'File Name', + Component: ImageInfo, + cellClass: '-ml-2', + width: '40%', + }, + { + key: 'avg', + title: 'Load Time', + toText: avg => `${ avg ? numberWithCommas(Math.trunc(avg)) : 0} ms`, + className: 'justify-center', + width: '15%', + }, + { + key: 'trend', + title: 'Trend', + Component: Chart, + width: '15%', + }, + { + key: 'copy-path', + title: '', + Component: CopyPath, + cellClass: 'invisible group-hover:visible text-right', + width: '15%', + } +]; + +interface Props { + data: any + metric?: any +} +function MissingResources(props: Props) { + const { data, metric } = props; + + return ( + +
+
+ + + ); +} + +export default MissingResources; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/imageInfo.css b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/imageInfo.css new file mode 100644 index 000000000..1de36b529 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/imageInfo.css @@ -0,0 +1,52 @@ +.name { + display: flex; + align-items: center; + + & > span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 60%; + } + + & .label { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +.hasPreview { + /* text-decoration: underline; */ + border-bottom: 1px dotted; + cursor: pointer; +} + +.imagePreview { + max-width: 200px; + max-height: 200px; +} + +.imageWrapper { + display: flex; + flex-flow: column; + align-items: center; + width: 40px; + text-align: center; + margin-right: 10px; + & > span { + height: 16px; + } + & .label { + font-size: 9px; + color: $gray-light; + } +} + +.popup { + background-color: #f5f5f5 !important; + &:before { + background-color: #f5f5f5 !important; + } +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/index.ts new file mode 100644 index 000000000..ca907e9f0 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/index.ts @@ -0,0 +1 @@ +export { default } from './SlowestResources' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/Scale.js b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/Scale.js new file mode 100644 index 000000000..2171c432e --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/Scale.js @@ -0,0 +1,24 @@ +import React from 'react' +import { Styles } from '../../common'; +import cn from 'classnames'; +import stl from './scale.css'; + +function Scale({ colors }) { + const lastIndex = (Styles.colors.length - 1) + return ( +
+ {colors.map((c, i) => ( +
+ { i === 0 &&
Slow
} + { i === lastIndex &&
Fast
} +
+ ))} +
+ ) +} + +export default Scale diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.js b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.js new file mode 100644 index 000000000..411e47030 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.js @@ -0,0 +1,112 @@ +import React, { useEffect } from 'react'; +import { NoContent } from 'UI'; +import { Styles, AvgLabel } from '../../common'; +import Scale from './Scale'; +import { threeLetter } from 'App/constants/countries'; +import { colorScale } from 'App/utils'; +import { observer } from 'mobx-react-lite'; +import * as DataMap from "datamaps"; +import { numberWithCommas } from 'App/utils'; + +// interface Props { +// metric?: any +// } +function SpeedIndexByLocation(props) { + const { metric } = props; + const wrapper: any = React.useRef(null); + let map: any = null; + + const getSeries = data => { + const series: any[] = []; + data.forEach(item => { + const d = [threeLetter[item.userCountry], Math.round(item.avg)] + series.push(d) + }) + + return series; + } + + useEffect(() => { + if (wrapper.current && !map && metric.data.chart.length > 0) { + const dataset = getDataset(); + map = new DataMap({ + element: wrapper.current, + fills: { defaultFill: '#E8E8E8' }, + data: dataset, + // responsive: true, + // height: null, //if not null, datamaps will grab the height of 'element' + // width: null, //if not null, datamaps will grab the width of 'element' + geographyConfig: { + borderColor: '#FFFFFF', + borderWidth: 0.5, + highlightBorderWidth: 1, + popupOnHover: true, + // don't change color on mouse hover + highlightFillColor: function(geo) { + return '#999999'; + // return geo['fillColor'] || '#F5F5F5'; + }, + // only change border + highlightBorderColor: '#B7B7B7', + // show desired information in tooltip + popupTemplate: function(geo, data) { + // don't show tooltip if country don't present in dataset + if (!data) { return ; } + // tooltip content + return ['
', + '', geo.properties.name, '', + 'Avg: ', numberWithCommas(data.numberOfThings), '', + '
'].join(''); + } + } + }); + } + }, []) + + // useEffect(() => { + // if (map) { + // map.updateChoropleth(getSeries(metric.data.chart), { reset: true}); + // } + // }, []) + + const getDataset = () => { + const { metric } = props; + const colors = Styles.colors; + + var dataset = {}; + const series = getSeries(metric.data.chart); + var onlyValues = series.map(function(obj){ return obj[1]; }); + const paletteScale = colorScale(onlyValues, [...colors].reverse()); + + // fill dataset in appropriate format + series.forEach(function(item){ + var iso = item[0], value = item[1]; + dataset[iso] = { numberOfThings: value, fillColor: paletteScale(value) }; + }); + return dataset; + } + + return ( + +
+ +
+ +
+ + ); +} + +export default observer(SpeedIndexByLocation); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/index.ts new file mode 100644 index 000000000..1cbdfe2f8 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/index.ts @@ -0,0 +1 @@ +export { default } from './SpeedIndexByLocation' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/scale.css b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/scale.css new file mode 100644 index 000000000..5aa34f966 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/scale.css @@ -0,0 +1,11 @@ +.bars { + & div:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; + } + + & div:last-child { + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + } +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx index 7e3b928bb..e0da69b8b 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx @@ -38,13 +38,13 @@ function TimeToRender(props: Props) { > <>
- + /> */}
diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx index 0d04ad363..6cd0c19e1 100644 --- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx @@ -117,5 +117,4 @@ function DashboardSideMenu(props: Props) { )); } -export default connect((state) => { -}, { setShowAlerts })(withRouter(DashboardSideMenu)); \ No newline at end of file +export default connect(null, { setShowAlerts })(withRouter(DashboardSideMenu)); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx index 151825b80..e61510760 100644 --- a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx @@ -24,6 +24,8 @@ import MissingResources from 'App/components/Dashboard/Widgets/PredefinedWidgets import ResourceLoadedVsResponseEnd from 'App/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd'; import SessionsPerBrowser from 'App/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser'; import CallWithErrors from '../../Widgets/PredefinedWidgets/CallWithErrors'; +import SpeedIndexByLocation from '../../Widgets/PredefinedWidgets/SpeedIndexByLocation'; +import SlowestResources from '../../Widgets/PredefinedWidgets/SlowestResources'; interface Props { data: any; @@ -54,7 +56,8 @@ function WidgetPredefinedChart(props: Props) { // PERFORMANCE // case 'impacted_sessions_by_slow_pages': // case 'pages_response_time_distribution': - // case 'speed_location': + case 'speed_location': + return case 'cpu': return case 'crashes': @@ -85,7 +88,8 @@ function WidgetPredefinedChart(props: Props) { return case 'resources_loading_time': return - // case 'slowest_resources': + case 'slowest_resources': + return default: return
Widget not supported
From 9cc4cebc5e34bf84993a9709678fb5d4f84dfed3 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 15 Apr 2022 11:50:17 +0200 Subject: [PATCH 27/32] feat(ui) - dashboard - other widgets --- .../ResponseTimeDistribution.tsx | 128 ++++++++++++++++++ .../ResponseTimeDistribution/index.ts | 1 + .../SessionsImpactedBySlowRequests.tsx | 2 +- .../SpeedIndexByLocation.js | 12 +- .../DashboardSideMenu/DashboardSideMenu.tsx | 7 +- .../WidgetPredefinedChart.tsx | 7 +- .../WidgetWrapper/TemplateOverlay.tsx | 4 - .../app/components/ui/ItemMenu/ItemMenu.js | 35 +++-- frontend/app/components/ui/Loader/Loader.js | 2 +- frontend/app/constants/countries.js | 2 +- frontend/app/mstore/types/widget.ts | 4 +- 11 files changed, 170 insertions(+), 34 deletions(-) create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/index.ts diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx new file mode 100644 index 000000000..4cfb032e2 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import { Loader, NoContent } from 'UI'; +import { Styles, AvgLabel } from '../../common'; +import { + ComposedChart, Bar, BarChart, CartesianGrid, ResponsiveContainer, + XAxis, YAxis, ReferenceLine, Tooltip, Legend +} from 'recharts'; + + +const PercentileLine = props => { + const { + viewBox: { x, y }, + xoffset, + yheight, + height, + label + } = props; + return ( + + + + {label} + + + ); +}; + +interface Props { + data: any + metric?: any +} +function ResponseTimeDistribution(props: Props) { + const { data, metric } = props; + const colors = Styles.colors; + + return ( + +
+ +
+
+ + + + + + + 'Page Response Time: ' + val} /> + { metric.data.percentiles.map((item, i) => ( + + } + allowDecimals={false} + x={item.responseTime} + strokeWidth={0} + strokeOpacity={1} + /> + ))} + + + + + + + + + + + +
+
+ ); +} + +export default ResponseTimeDistribution; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/index.ts new file mode 100644 index 000000000..163efa255 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/index.ts @@ -0,0 +1 @@ +export { default } from './ResponseTimeDistribution' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.tsx index 0ba6ec5e8..ee227854b 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.tsx @@ -35,7 +35,7 @@ function SessionsImpactedBySlowRequests(props: Props) { {...Styles.yaxis} allowDecimals={false} tickFormatter={val => Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "Number of Requests" }} + label={{ ...Styles.axisLabelLeft, value: "Number of Sessions" }} /> { - // if (map) { - // map.updateChoropleth(getSeries(metric.data.chart), { reset: true}); - // } - // }, []) + useEffect(() => { + if (map && map.updateChoropleth) { + const series = getSeries(metric.data.chart); + // console.log('series', series) + // map.updateChoropleth(series, {reset: true}); + } + }, []) const getDataset = () => { const { metric } = props; diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx index 6cd0c19e1..72de0ea3d 100644 --- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx @@ -62,7 +62,12 @@ function DashboardSideMenu(props: Props) { {item.isPublic &&
} {item.isPinned &&
} {!item.isPinned && ( - +
togglePinned(item)} diff --git a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx index e61510760..c4aa215d7 100644 --- a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx @@ -26,6 +26,7 @@ import SessionsPerBrowser from 'App/components/Dashboard/Widgets/PredefinedWidge import CallWithErrors from '../../Widgets/PredefinedWidgets/CallWithErrors'; import SpeedIndexByLocation from '../../Widgets/PredefinedWidgets/SpeedIndexByLocation'; import SlowestResources from '../../Widgets/PredefinedWidgets/SlowestResources'; +import ResponseTimeDistribution from '../../Widgets/PredefinedWidgets/ResponseTimeDistribution'; interface Props { data: any; @@ -54,8 +55,10 @@ function WidgetPredefinedChart(props: Props) { return // PERFORMANCE - // case 'impacted_sessions_by_slow_pages': - // case 'pages_response_time_distribution': + case 'impacted_sessions_by_slow_pages': + return + case 'pages_response_time_distribution': + return case 'speed_location': return case 'cpu': diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/TemplateOverlay.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/TemplateOverlay.tsx index 3b76f36de..4cb9e17cf 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/TemplateOverlay.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/TemplateOverlay.tsx @@ -5,13 +5,9 @@ function TemplateOverlay() { return (
diff --git a/frontend/app/components/ui/ItemMenu/ItemMenu.js b/frontend/app/components/ui/ItemMenu/ItemMenu.js index 841d9e4c9..e97f6999b 100644 --- a/frontend/app/components/ui/ItemMenu/ItemMenu.js +++ b/frontend/app/components/ui/ItemMenu/ItemMenu.js @@ -46,28 +46,27 @@ export default class ItemMenu extends React.PureComponent { { items.filter(({ hidden }) => !hidden).map(({ onClick, text, icon, disabled = false, disabledMessage = '' }) => (
{} } role="menuitem" tabIndex="-1" > - -
- { icon && ( -
- -
- )} -
{ text }
-
-
+ +
+ { icon && ( +
+ +
+ )} +
{ text }
+
+
))}
diff --git a/frontend/app/components/ui/Loader/Loader.js b/frontend/app/components/ui/Loader/Loader.js index 5977af141..e286cd04a 100644 --- a/frontend/app/components/ui/Loader/Loader.js +++ b/frontend/app/components/ui/Loader/Loader.js @@ -1,7 +1,7 @@ import cn from 'classnames'; import styles from './loader.css'; -const Loader = React.memo(({ className, loading = true, children = null, size, style = { minHeight: '150px' } }) => (!loading ? children : +const Loader = React.memo(({ className = '', loading = true, children = null, size, style = { minHeight: '150px' } }) => (!loading ? children :
diff --git a/frontend/app/constants/countries.js b/frontend/app/constants/countries.js index bf3fbaea5..bab3836ae 100644 --- a/frontend/app/constants/countries.js +++ b/frontend/app/constants/countries.js @@ -1,4 +1,4 @@ -export const threeLetter = {'BD':'BGD', 'BE':'BEL', 'BF':'BFA', 'BG':'BGR', 'BA':'BIH', 'BB':'BRB', 'WF':'WLF', 'BL':'BLM', 'BM':'BMU', 'BN':'BRN', 'BO':'BOL', 'BH':'BHR', 'BI':'BDI', 'BJ':'BEN', 'BT':'BTN', 'JM':'JAM', 'BV':'BVT', 'BW':'BWA', 'WS':'WSM', 'BQ':'BES', 'BR':'BRA', 'BS':'BHS', 'JE':'JEY', 'BY':'BLR', 'BZ':'BLZ', 'RU':'RUS', 'RW':'RWA', 'RS':'SRB', 'TL':'TLS', 'RE':'REU', 'TM':'TKM', 'TJ':'TJK', 'RO':'ROU', 'TK':'TKL', 'GW':'GNB', 'GU':'GUM', 'GT':'GTM', 'GS':'SGS', 'GR':'GRC', 'GQ':'GNQ', 'GP':'GLP', 'JP':'JPN', 'GY':'GUY', 'GG':'GGY', 'GF':'GUF', 'GE':'GEO', 'GD':'GRD', 'GB':'GBR', 'GA':'GAB', 'SV':'SLV', 'GN':'GIN', 'GM':'GMB', 'GL':'GRL', 'GI':'GIB', 'GH':'GHA', 'OM':'OMN', 'TN':'TUN', 'JO':'JOR', 'HR':'HRV', 'HT':'HTI', 'HU':'HUN', 'HK':'HKG', 'HN':'HND', 'HM':'HMD', 'VE':'VEN', 'PR':'PRI', 'PS':'PSE', 'PW':'PLW', 'PT':'PRT', 'SJ':'SJM', 'PY':'PRY', 'IQ':'IRQ', 'PA':'PAN', 'PF':'PYF', 'PG':'PNG', 'PE':'PER', 'PK':'PAK', 'PH':'PHL', 'PN':'PCN', 'PL':'POL', 'PM':'SPM', 'ZM':'ZMB', 'EH':'ESH', 'EE':'EST', 'EG':'EGY', 'ZA':'ZAF', 'EC':'ECU', 'IT':'ITA', 'VN':'VNM', 'SB':'SLB', 'ET':'ETH', 'SO':'SOM', 'ZW':'ZWE', 'SA':'SAU', 'ES':'ESP', 'ER':'ERI', 'ME':'MNE', 'MD':'MDA', 'MG':'MDG', 'MF':'MAF', 'MA':'MAR', 'MC':'MCO', 'UZ':'UZB', 'MM':'MMR', 'ML':'MLI', 'MO':'MAC', 'MN':'MNG', 'MH':'MHL', 'MK':'MKD', 'MU':'MUS', 'MT':'MLT', 'MW':'MWI', 'MV':'MDV', 'MQ':'MTQ', 'MP':'MNP', 'MS':'MSR', 'OR':'SEÑMRT', 'IM':'IMN', 'UG':'UGA', 'TZ':'TZA', 'MIS':'MYS', 'MX':'MEX', 'IL':'ISR', 'FR':'FRA', 'IO':'IOT', 'SH':'SHN', 'FI':'FIN', 'FJ':'FJI', 'FK':'FLK', 'FM':'FSM', 'FO':'FRO', 'NI':'NIC', 'NL':'NLD', 'NO':'NOR', 'NA':'NAM', 'VU':'VUT', 'NC':'NCL', 'NE':'NER', 'NF':'NFK', 'NG':'NGA', 'NZ':'NZL', 'NP':'NPL', 'NR':'NRU', 'NU':'NIU', 'CK':'COK', 'XK':'XKX', 'CI':'CIV', 'CH':'CHE', 'CO':'COL', 'CN':'CHN', 'CM':'CMR', 'CL':'CHL', 'CC':'CCK', 'CA':'CAN', 'CG':'COG', 'CF':'CAF', 'CD':'COD', 'CZ':'CZE', 'CY':'CYP', 'CX':'CXR', 'CR':'CRI', 'CW':'CUW', 'CV':'CPV', 'CU':'CUB', 'SZ':'SWZ', 'SY':'SYR', 'SX':'SXM', 'KG':'KGZ', 'KE':'KEN', 'SS':'SSD', 'SR':'SUR', 'KI':'KIR', 'KH':'KHM', 'KN':'KNA', 'KM':'COM', 'ST':'STP', 'SK':'SVK', 'KR':'KOR', 'SI':'SVN', 'KP':'PRK', 'KW':'KWT', 'SN':'SEN', 'SM':'SMR', 'SL':'SLE', 'SC':'SYC', 'KZ':'KAZ', 'KY':'CYM', 'SG':'SGP', 'SE':'SWE', 'SD':'SDN', 'DO':'DOM', 'DM':'DMA', 'DJ':'DJI', 'DK':'DNK', 'VG':'VGB', 'DE':'DEU', 'YE':'YEM', 'DZ':'DZA', 'US':'USA', 'UY':'URY', 'YT':'MYT', 'UM':'UMI', 'LB':'LBN', 'LC':'LCA', 'LA':'LAO', 'TV':'TUV', 'TW':'TWN', 'TT':'TTO', 'TR':'TUR', 'LK':'LKA', 'LI':'LIE', 'LV':'LVA', 'TO':'TON', 'LT':'LTU', 'LU':'LUX', 'LR':'LBR', 'LS':'LSO', 'TH':'THA', 'TF':'ATF', 'TG':'TGO', 'TD':'TCD', 'TC':'TCA', 'LY':'LBY', 'VA':'VAT', 'VC':'VCT', 'AE':'ARE', 'AD':'AND', 'AG':'ATG', 'AF':'AFG', 'AI':'AIA', 'VI':'VIR', 'IS':'ISL', 'IR':'IRN', 'AM':'ARM', 'AL':'ALB', 'AO':'AGO', 'AQ':'ATA', 'AS':'ASM', 'AR':'ARG', 'AU':'AUS', 'AT':'AUT', 'AW':'ABW', 'IN':'IND', 'AX':'ALA', 'AZ':'AZE', 'IE':'IRL', 'ID':'IDN', 'UA':'UKR', 'QA':'QAT', 'MZ':'MOZ'} +export const threeLetter = {UN: 'BGD', BD: 'BGD', BE: 'BEL', BF: 'BFA', BG: 'BGR', BA: 'BIH', BB: 'BRB', WF: 'WLF', BL: 'BLM', BM: 'BMU', BN: 'BRN', BO: 'BOL', BH: 'BHR', BI: 'BDI', BJ: 'BEN', BT: 'BTN', JM: 'JAM', BV: 'BVT', BW: 'BWA', WS: 'WSM', BQ: 'BES', BR: 'BRA', BS: 'BHS', JE: 'JEY', BY: 'BLR', BZ: 'BLZ', RU: 'RUS', RW: 'RWA', RS: 'SRB', TL: 'TLS', RE: 'REU', TM: 'TKM', TJ: 'TJK', RO: 'ROU', TK: 'TKL', GW: 'GNB', GU: 'GUM', GT: 'GTM', GS: 'SGS', GR: 'GRC', GQ: 'GNQ', GP: 'GLP', JP: 'JPN', GY: 'GUY', GG: 'GGY', GF: 'GUF', GE: 'GEO', GD: 'GRD', GB: 'GBR', GA: 'GAB', SV: 'SLV', GN: 'GIN', GM: 'GMB', GL: 'GRL', GI: 'GIB', GH: 'GHA', OM: 'OMN', TN: 'TUN', JO: 'JOR', HR: 'HRV', HT: 'HTI', HU: 'HUN', HK: 'HKG', HN: 'HND', HM: 'HMD', VE: 'VEN', PR: 'PRI', PS: 'PSE', PW: 'PLW', PT: 'PRT', SJ: 'SJM', PY: 'PRY', IQ: 'IRQ', PA: 'PAN', PF: 'PYF', PG: 'PNG', PE: 'PER', PK: 'PAK', PH: 'PHL', PN: 'PCN', PL: 'POL', PM: 'SPM', ZM: 'ZMB', EH: 'ESH', EE: 'EST', EG: 'EGY', ZA: 'ZAF', EC: 'ECU', IT: 'ITA', VN: 'VNM', SB: 'SLB', ET: 'ETH', SO: 'SOM', ZW: 'ZWE', SA: 'SAU', ES: 'ESP', ER: 'ERI', ME: 'MNE', MD: 'MDA', MG: 'MDG', MF: 'MAF', MA: 'MAR', MC: 'MCO', UZ: 'UZB', MM: 'MMR', ML: 'MLI', MO: 'MAC', MN: 'MNG', MH: 'MHL', MK: 'MKD', MU: 'MUS', MT: 'MLT', MW: 'MWI', MV: 'MDV', MQ: 'MTQ', MP: 'MNP', MS: 'MSR', OR: 'SEÑ', IM: 'IMN', UG: 'UGA', TZ: 'TZA', IS: 'MYS', MX: 'MEX', IL: 'ISR', FR: 'FRA', IO: 'IOT', SH: 'SHN', FI: 'FIN', FJ: 'FJI', FK: 'FLK', FM: 'FSM', FO: 'FRO', NI: 'NIC', NL: 'NLD', NO: 'NOR', NA: 'NAM', VU: 'VUT', NC: 'NCL', NE: 'NER', NF: 'NFK', NG: 'NGA', NZ: 'NZL', NP: 'NPL', NR: 'NRU', NU: 'NIU', CK: 'COK', XK: 'XKX', CI: 'CIV', CH: 'CHE', CO: 'COL', CN: 'CHN', CM: 'CMR', CL: 'CHL', CC: 'CCK', CA: 'CAN', CG: 'COG', CF: 'CAF', CD: 'COD', CZ: 'CZE', CY: 'CYP', CX: 'CXR', CR: 'CRI', CW: 'CUW', CV: 'CPV', CU: 'CUB', SZ: 'SWZ', SY: 'SYR', SX: 'SXM', KG: 'KGZ', KE: 'KEN', SS: 'SSD', SR: 'SUR', KI: 'KIR', KH: 'KHM', KN: 'KNA', KM: 'COM', ST: 'STP', SK: 'SVK', KR: 'KOR', SI: 'SVN', KP: 'PRK', KW: 'KWT', SN: 'SEN', SM: 'SMR', SL: 'SLE', SC: 'SYC', KZ: 'KAZ', KY: 'CYM', SG: 'SGP', SE: 'SWE', SD: 'SDN', DO: 'DOM', DM: 'DMA', DJ: 'DJI', DK: 'DNK', VG: 'VGB', DE: 'DEU', YE: 'YEM', DZ: 'DZA', US: 'USA', UY: 'URY', YT: 'MYT', UM: 'UMI', LB: 'LBN', LC: 'LCA', LA: 'LAO', TV: 'TUV', TW: 'TWN', TT: 'TTO', TR: 'TUR', LK: 'LKA', LI: 'LIE', LV: 'LVA', TO: 'TON', LT: 'LTU', LU: 'LUX', LR: 'LBR', LS: 'LSO', TH: 'THA', TF: 'ATF', TG: 'TGO', TD: 'TCD', TC: 'TCA', LY: 'LBY', VA: 'VAT', VC: 'VCT', AE: 'ARE', AD: 'AND', AG: 'ATG', AF: 'AFG', AI: 'AIA', VI: 'VIR', IS: 'ISL', IR: 'IRN', AM: 'ARM', AL: 'ALB', AO: 'AGO', AQ: 'ATA', AS: 'ASM', AR: 'ARG', AU: 'AUS', AT: 'AUT', AW: 'ABW', IN: 'IND', AX: 'ALA', AZ: 'AZE', IE: 'IRL', ID: 'IDN', UA: 'UKR', QA: 'QAT', MZ: 'MOZ'} export default { AC: 'Ascension Island', AD: 'Andorra', diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index 4d20ca0f1..0a465f5cf 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -72,7 +72,9 @@ export default class Widget implements IWidget { position: number = 0 data: any = { chart: [], - namesMap: {} + namesMap: {}, + avg: 0, + percentiles: [], } isLoading: boolean = false isValid: boolean = false From eb4aa50211e2fcc3976f0538d9249a8228f6b59c Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 15 Apr 2022 12:35:29 +0200 Subject: [PATCH 28/32] feat(ui) - dashboard - other widgets --- .../SlowestResources/SlowestResources.tsx | 4 +- ...tion.js => SpeedIndexByLocation copy.js__} | 0 .../SpeedIndexByLocation.css | 21 ++++ .../SpeedIndexByLocation.tsx | 102 ++++++++++++++++++ frontend/app/constants/countries.js | 2 +- frontend/app/styles/main.css | 7 ++ 6 files changed, 133 insertions(+), 3 deletions(-) rename frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/{SpeedIndexByLocation.js => SpeedIndexByLocation copy.js__} (100%) create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.css create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx index ca62855b0..c4bbb1ed9 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx @@ -57,7 +57,7 @@ interface Props { data: any metric?: any } -function MissingResources(props: Props) { +function SlowestResources(props: Props) { const { data, metric } = props; return ( @@ -78,4 +78,4 @@ function MissingResources(props: Props) { ); } -export default MissingResources; \ No newline at end of file +export default SlowestResources; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.js b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation copy.js__ similarity index 100% rename from frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.js rename to frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation copy.js__ diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.css b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.css new file mode 100644 index 000000000..ab474e37b --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.css @@ -0,0 +1,21 @@ +.maps { + height: auto; + width: 110%; + stroke: $gray-medium; + stroke-width: 1; + stroke-linecap: round; + stroke-linejoin: round; + margin-top: -20px; + +} + +.location { + fill: $gray-light !important; + cursor: pointer; + + &:focus, + &:hover { + fill: #b8e2b3; + outline: 0; + } +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx new file mode 100644 index 000000000..788e4f020 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx @@ -0,0 +1,102 @@ +import React, { useEffect } from 'react'; +import { NoContent } from 'UI'; +import { Styles, AvgLabel } from '../../common'; +import Scale from './Scale'; +import { threeLetter } from 'App/constants/countries'; +import { colorScale } from 'App/utils'; +import { observer } from 'mobx-react-lite'; +import { numberWithCommas } from 'App/utils'; +import WorldMap from "@svg-maps/world"; +import { SVGMap } from "react-svg-map"; +import "react-svg-map/lib/index.css"; +import stl from './SpeedIndexByLocation.css'; + +interface Props { + metric?: any +} +function SpeedIndexByLocation(props: Props) { + const { metric } = props; + const wrapper: any = React.useRef(null); + let map: any = null; + + const getSeries = data => { + const series: any[] = []; + data.forEach(item => { + const d = [threeLetter[item.userCountry], Math.round(item.avg)] + series.push(d) + }) + + return series; + } + + useEffect(() => { + // if (!wrapper && !wrapper.current) return + + }, []) + + // useEffect(() => { + // if (map && map.updateChoropleth) { + // const series = getSeries(metric.data.chart); + // // console.log('series', series) + // // map.updateChoropleth(series, {reset: true}); + // } + // }, []) + + const getDataset = () => { + const { metric } = props; + const colors = Styles.colors; + + var dataset = {}; + const series = getSeries(metric.data.chart); + var onlyValues = series.map(function(obj){ return obj[1]; }); + const paletteScale = colorScale(onlyValues, [...colors].reverse()); + + // fill dataset in appropriate format + series.forEach(function(item){ + var iso = item[0], value = item[1]; + dataset[iso] = { numberOfThings: value, fillColor: paletteScale(value) }; + }); + return dataset; + } + + const getLocationClassName = (location, index) => { + // Generate random heat map + return `svg-map__location svg-map__location--heat${index % 4}`; + } + + return ( + +
+ +
+ +
+
+ console.log(e.target)} + /> +
+ {/*
+ {this.state.pointedLocation} +
*/} +
+ ); +} + +export default observer(SpeedIndexByLocation); \ No newline at end of file diff --git a/frontend/app/constants/countries.js b/frontend/app/constants/countries.js index bab3836ae..68a65b000 100644 --- a/frontend/app/constants/countries.js +++ b/frontend/app/constants/countries.js @@ -1,4 +1,4 @@ -export const threeLetter = {UN: 'BGD', BD: 'BGD', BE: 'BEL', BF: 'BFA', BG: 'BGR', BA: 'BIH', BB: 'BRB', WF: 'WLF', BL: 'BLM', BM: 'BMU', BN: 'BRN', BO: 'BOL', BH: 'BHR', BI: 'BDI', BJ: 'BEN', BT: 'BTN', JM: 'JAM', BV: 'BVT', BW: 'BWA', WS: 'WSM', BQ: 'BES', BR: 'BRA', BS: 'BHS', JE: 'JEY', BY: 'BLR', BZ: 'BLZ', RU: 'RUS', RW: 'RWA', RS: 'SRB', TL: 'TLS', RE: 'REU', TM: 'TKM', TJ: 'TJK', RO: 'ROU', TK: 'TKL', GW: 'GNB', GU: 'GUM', GT: 'GTM', GS: 'SGS', GR: 'GRC', GQ: 'GNQ', GP: 'GLP', JP: 'JPN', GY: 'GUY', GG: 'GGY', GF: 'GUF', GE: 'GEO', GD: 'GRD', GB: 'GBR', GA: 'GAB', SV: 'SLV', GN: 'GIN', GM: 'GMB', GL: 'GRL', GI: 'GIB', GH: 'GHA', OM: 'OMN', TN: 'TUN', JO: 'JOR', HR: 'HRV', HT: 'HTI', HU: 'HUN', HK: 'HKG', HN: 'HND', HM: 'HMD', VE: 'VEN', PR: 'PRI', PS: 'PSE', PW: 'PLW', PT: 'PRT', SJ: 'SJM', PY: 'PRY', IQ: 'IRQ', PA: 'PAN', PF: 'PYF', PG: 'PNG', PE: 'PER', PK: 'PAK', PH: 'PHL', PN: 'PCN', PL: 'POL', PM: 'SPM', ZM: 'ZMB', EH: 'ESH', EE: 'EST', EG: 'EGY', ZA: 'ZAF', EC: 'ECU', IT: 'ITA', VN: 'VNM', SB: 'SLB', ET: 'ETH', SO: 'SOM', ZW: 'ZWE', SA: 'SAU', ES: 'ESP', ER: 'ERI', ME: 'MNE', MD: 'MDA', MG: 'MDG', MF: 'MAF', MA: 'MAR', MC: 'MCO', UZ: 'UZB', MM: 'MMR', ML: 'MLI', MO: 'MAC', MN: 'MNG', MH: 'MHL', MK: 'MKD', MU: 'MUS', MT: 'MLT', MW: 'MWI', MV: 'MDV', MQ: 'MTQ', MP: 'MNP', MS: 'MSR', OR: 'SEÑ', IM: 'IMN', UG: 'UGA', TZ: 'TZA', IS: 'MYS', MX: 'MEX', IL: 'ISR', FR: 'FRA', IO: 'IOT', SH: 'SHN', FI: 'FIN', FJ: 'FJI', FK: 'FLK', FM: 'FSM', FO: 'FRO', NI: 'NIC', NL: 'NLD', NO: 'NOR', NA: 'NAM', VU: 'VUT', NC: 'NCL', NE: 'NER', NF: 'NFK', NG: 'NGA', NZ: 'NZL', NP: 'NPL', NR: 'NRU', NU: 'NIU', CK: 'COK', XK: 'XKX', CI: 'CIV', CH: 'CHE', CO: 'COL', CN: 'CHN', CM: 'CMR', CL: 'CHL', CC: 'CCK', CA: 'CAN', CG: 'COG', CF: 'CAF', CD: 'COD', CZ: 'CZE', CY: 'CYP', CX: 'CXR', CR: 'CRI', CW: 'CUW', CV: 'CPV', CU: 'CUB', SZ: 'SWZ', SY: 'SYR', SX: 'SXM', KG: 'KGZ', KE: 'KEN', SS: 'SSD', SR: 'SUR', KI: 'KIR', KH: 'KHM', KN: 'KNA', KM: 'COM', ST: 'STP', SK: 'SVK', KR: 'KOR', SI: 'SVN', KP: 'PRK', KW: 'KWT', SN: 'SEN', SM: 'SMR', SL: 'SLE', SC: 'SYC', KZ: 'KAZ', KY: 'CYM', SG: 'SGP', SE: 'SWE', SD: 'SDN', DO: 'DOM', DM: 'DMA', DJ: 'DJI', DK: 'DNK', VG: 'VGB', DE: 'DEU', YE: 'YEM', DZ: 'DZA', US: 'USA', UY: 'URY', YT: 'MYT', UM: 'UMI', LB: 'LBN', LC: 'LCA', LA: 'LAO', TV: 'TUV', TW: 'TWN', TT: 'TTO', TR: 'TUR', LK: 'LKA', LI: 'LIE', LV: 'LVA', TO: 'TON', LT: 'LTU', LU: 'LUX', LR: 'LBR', LS: 'LSO', TH: 'THA', TF: 'ATF', TG: 'TGO', TD: 'TCD', TC: 'TCA', LY: 'LBY', VA: 'VAT', VC: 'VCT', AE: 'ARE', AD: 'AND', AG: 'ATG', AF: 'AFG', AI: 'AIA', VI: 'VIR', IS: 'ISL', IR: 'IRN', AM: 'ARM', AL: 'ALB', AO: 'AGO', AQ: 'ATA', AS: 'ASM', AR: 'ARG', AU: 'AUS', AT: 'AUT', AW: 'ABW', IN: 'IND', AX: 'ALA', AZ: 'AZE', IE: 'IRL', ID: 'IDN', UA: 'UKR', QA: 'QAT', MZ: 'MOZ'} +export const threeLetter = {BD: 'BGD', BE: 'BEL', BF: 'BFA', BG: 'BGR', BA: 'BIH', BB: 'BRB', WF: 'WLF', BL: 'BLM', BM: 'BMU', BN: 'BRN', BO: 'BOL', BH: 'BHR', BI: 'BDI', BJ: 'BEN', BT: 'BTN', JM: 'JAM', BV: 'BVT', BW: 'BWA', WS: 'WSM', BQ: 'BES', BR: 'BRA', BS: 'BHS', JE: 'JEY', BY: 'BLR', BZ: 'BLZ', RU: 'RUS', RW: 'RWA', RS: 'SRB', TL: 'TLS', RE: 'REU', TM: 'TKM', TJ: 'TJK', RO: 'ROU', TK: 'TKL', GW: 'GNB', GU: 'GUM', GT: 'GTM', GS: 'SGS', GR: 'GRC', GQ: 'GNQ', GP: 'GLP', JP: 'JPN', GY: 'GUY', GG: 'GGY', GF: 'GUF', GE: 'GEO', GD: 'GRD', GB: 'GBR', GA: 'GAB', SV: 'SLV', GN: 'GIN', GM: 'GMB', GL: 'GRL', GI: 'GIB', GH: 'GHA', OM: 'OMN', TN: 'TUN', JO: 'JOR', HR: 'HRV', HT: 'HTI', HU: 'HUN', HK: 'HKG', HN: 'HND', HM: 'HMD', VE: 'VEN', PR: 'PRI', PS: 'PSE', PW: 'PLW', PT: 'PRT', SJ: 'SJM', PY: 'PRY', IQ: 'IRQ', PA: 'PAN', PF: 'PYF', PG: 'PNG', PE: 'PER', PK: 'PAK', PH: 'PHL', PN: 'PCN', PL: 'POL', PM: 'SPM', ZM: 'ZMB', EH: 'ESH', EE: 'EST', EG: 'EGY', ZA: 'ZAF', EC: 'ECU', IT: 'ITA', VN: 'VNM', SB: 'SLB', ET: 'ETH', SO: 'SOM', ZW: 'ZWE', SA: 'SAU', ES: 'ESP', ER: 'ERI', ME: 'MNE', MD: 'MDA', MG: 'MDG', MF: 'MAF', MA: 'MAR', MC: 'MCO', UZ: 'UZB', MM: 'MMR', ML: 'MLI', MO: 'MAC', MN: 'MNG', MH: 'MHL', MK: 'MKD', MU: 'MUS', MT: 'MLT', MW: 'MWI', MV: 'MDV', MQ: 'MTQ', MP: 'MNP', MS: 'MSR', OR: 'SEÑ', IM: 'IMN', UG: 'UGA', TZ: 'TZA', IS: 'MYS', MX: 'MEX', IL: 'ISR', FR: 'FRA', IO: 'IOT', SH: 'SHN', FI: 'FIN', FJ: 'FJI', FK: 'FLK', FM: 'FSM', FO: 'FRO', NI: 'NIC', NL: 'NLD', NO: 'NOR', NA: 'NAM', VU: 'VUT', NC: 'NCL', NE: 'NER', NF: 'NFK', NG: 'NGA', NZ: 'NZL', NP: 'NPL', NR: 'NRU', NU: 'NIU', CK: 'COK', XK: 'XKX', CI: 'CIV', CH: 'CHE', CO: 'COL', CN: 'CHN', CM: 'CMR', CL: 'CHL', CC: 'CCK', CA: 'CAN', CG: 'COG', CF: 'CAF', CD: 'COD', CZ: 'CZE', CY: 'CYP', CX: 'CXR', CR: 'CRI', CW: 'CUW', CV: 'CPV', CU: 'CUB', SZ: 'SWZ', SY: 'SYR', SX: 'SXM', KG: 'KGZ', KE: 'KEN', SS: 'SSD', SR: 'SUR', KI: 'KIR', KH: 'KHM', KN: 'KNA', KM: 'COM', ST: 'STP', SK: 'SVK', KR: 'KOR', SI: 'SVN', KP: 'PRK', KW: 'KWT', SN: 'SEN', SM: 'SMR', SL: 'SLE', SC: 'SYC', KZ: 'KAZ', KY: 'CYM', SG: 'SGP', SE: 'SWE', SD: 'SDN', DO: 'DOM', DM: 'DMA', DJ: 'DJI', DK: 'DNK', VG: 'VGB', DE: 'DEU', YE: 'YEM', DZ: 'DZA', US: 'USA', UY: 'URY', YT: 'MYT', UM: 'UMI', LB: 'LBN', LC: 'LCA', LA: 'LAO', TV: 'TUV', TW: 'TWN', TT: 'TTO', TR: 'TUR', LK: 'LKA', LI: 'LIE', LV: 'LVA', TO: 'TON', LT: 'LTU', LU: 'LUX', LR: 'LBR', LS: 'LSO', TH: 'THA', TF: 'ATF', TG: 'TGO', TD: 'TCD', TC: 'TCA', LY: 'LBY', VA: 'VAT', VC: 'VCT', AE: 'ARE', AD: 'AND', AG: 'ATG', AF: 'AFG', AI: 'AIA', VI: 'VIR', IS: 'ISL', IR: 'IRN', AM: 'ARM', AL: 'ALB', AO: 'AGO', AQ: 'ATA', AS: 'ASM', AR: 'ARG', AU: 'AUS', AT: 'AUT', AW: 'ABW', IN: 'IND', AX: 'ALA', AZ: 'AZE', IE: 'IRL', ID: 'IDN', UA: 'UKR', QA: 'QAT', MZ: 'MOZ'} export default { AC: 'Ascension Island', AD: 'Andorra', diff --git a/frontend/app/styles/main.css b/frontend/app/styles/main.css index 3b7f5fe4b..d59334bd2 100644 --- a/frontend/app/styles/main.css +++ b/frontend/app/styles/main.css @@ -147,4 +147,11 @@ height: 100vh; overflow-y: hidden; padding-right: 15px; +} + +.svg-map { + &__location { + fill: #ddd !important; + cursor: pointer; + } } \ No newline at end of file From f7c06ff12536eb7ae270c5868f7974b490f5f8ef Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 15 Apr 2022 13:03:06 +0200 Subject: [PATCH 29/32] feat(ui) - dashboard - filter fix duration --- .../FilterAutoCompleteLocal.tsx | 27 ++++++++++++------- .../Filters/FilterValue/FilterValue.tsx | 1 + frontend/app/types/filter/newFilter.js | 2 +- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/FilterAutoCompleteLocal.tsx b/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/FilterAutoCompleteLocal.tsx index 542dfce1c..2030d422a 100644 --- a/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/FilterAutoCompleteLocal.tsx +++ b/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/FilterAutoCompleteLocal.tsx @@ -1,9 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { Icon, Loader } from 'UI'; -// import { debounce } from 'App/utils'; +import { Icon } from 'UI'; import stl from './FilterAutoCompleteLocal.css'; -// import cn from 'classnames'; - interface Props { showOrButton?: boolean; showCloseButton?: boolean; @@ -15,6 +12,7 @@ interface Props { icon?: string; type?: string; isMultilple?: boolean; + allowDecimals?: boolean; } function FilterAutoCompleteLocal(props: Props) { @@ -28,15 +26,24 @@ function FilterAutoCompleteLocal(props: Props) { icon = null, type = "text", isMultilple = true, + allowDecimals = true, } = props; const [showModal, setShowModal] = useState(true) const [query, setQuery] = useState(value); - // const debounceOnSelect = debounce(props.onSelect, 500); - const onInputChange = ({ target: { value } }) => { - setQuery(value); - props.onSelect(null, { value }); - } + const onInputChange = (e) => { + if(allowDecimals) { + const value = e.target.value; + setQuery(value); + props.onSelect(null, { value }); + } else { + const value = e.target.value.replace(/[^\d]/, ""); + if (+value !== 0) { + setQuery(value); + props.onSelect(null, { value }); + } + } + }; useEffect(() => { setQuery(value); @@ -58,7 +65,7 @@ function FilterAutoCompleteLocal(props: Props) {
setShowModal(true)} value={ query } diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx index 2fd2fc132..4e8d4001f 100644 --- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx +++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx @@ -137,6 +137,7 @@ function FilterValue(props: Props) { onSelect={(e, item) => debounceOnSelect(e, item, valueIndex)} icon={filter.icon} type="number" + allowDecimals={false} isMultilple={false} /> // Date: Fri, 15 Apr 2022 13:03:25 +0200 Subject: [PATCH 30/32] feat(ui) - dashboard - filter fix duration --- frontend/package-lock.json | 133 ++++++++++++++++++++++++++----------- frontend/package.json | 2 + 2 files changed, 98 insertions(+), 37 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5393d1fcc..5fa927e01 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "1.3.6", "dependencies": { "@sentry/browser": "^5.21.1", + "@svg-maps/world": "^1.0.1", "classnames": "^2.2.6", "codemirror": "^5.62.3", "copy-to-clipboard": "^3.3.1", @@ -41,6 +42,7 @@ "react-redux": "^5.1.2", "react-router": "^4.3.1", "react-router-dom": "^4.3.1", + "react-svg-map": "^2.2.0", "react-tippy": "^1.4.0", "react-toastify": "^5.5.0", "react-virtualized": "^9.22.2", @@ -4544,6 +4546,11 @@ "react-dom": "^16.8.0 || ^17.0.0" } }, + "node_modules/@svg-maps/world": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@svg-maps/world/-/world-1.0.1.tgz", + "integrity": "sha512-Mawh/jEYBBHnug9S17PyePLYKJ+Xd0Bbh96mCePebpbvcbJu5YKpfKhpyMeLFmmdWPrSFxl0f0MTsJfXU0gSaQ==" + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -4650,7 +4657,7 @@ "version": "14.18.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==", - "dev": true + "devOptional": true }, "node_modules/@types/node-fetch": { "version": "2.6.1", @@ -4702,7 +4709,7 @@ "version": "15.7.4", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", - "dev": true + "devOptional": true }, "node_modules/@types/q": { "version": "1.5.5", @@ -4720,7 +4727,7 @@ "version": "17.0.43", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.43.tgz", "integrity": "sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4740,13 +4747,13 @@ "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "dev": true + "devOptional": true }, "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "dev": true + "devOptional": true }, "node_modules/@types/source-list-map": { "version": "0.1.2", @@ -19275,6 +19282,18 @@ "react-dom": ">=15.0.0" } }, + "node_modules/react-svg-map": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-svg-map/-/react-svg-map-2.2.0.tgz", + "integrity": "sha512-slZTt4ffXgOb3ND/WEHOc0ojX5lBEv9FKbTU3tWnTLZPQ9L0e686SBqgVxmsuTD+o52Aor87InMzAzDRTQedpA==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^16.0.0", + "react-dom": "^16.0.0" + } + }, "node_modules/react-syntax-highlighter": { "version": "13.5.3", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-13.5.3.tgz", @@ -29303,6 +29322,11 @@ "store2": "^2.12.0" } }, + "@svg-maps/world": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@svg-maps/world/-/world-1.0.1.tgz", + "integrity": "sha512-Mawh/jEYBBHnug9S17PyePLYKJ+Xd0Bbh96mCePebpbvcbJu5YKpfKhpyMeLFmmdWPrSFxl0f0MTsJfXU0gSaQ==" + }, "@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -29406,7 +29430,7 @@ "version": "14.18.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==", - "dev": true + "devOptional": true }, "@types/node-fetch": { "version": "2.6.1", @@ -29458,7 +29482,7 @@ "version": "15.7.4", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", - "dev": true + "devOptional": true }, "@types/q": { "version": "1.5.5", @@ -29476,7 +29500,7 @@ "version": "17.0.43", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.43.tgz", "integrity": "sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A==", - "dev": true, + "devOptional": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -29487,7 +29511,7 @@ "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "dev": true + "devOptional": true } } }, @@ -29504,7 +29528,7 @@ "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "dev": true + "devOptional": true }, "@types/source-list-map": { "version": "0.1.2", @@ -29869,13 +29893,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-align": { "version": "3.0.1", @@ -30503,7 +30529,8 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", - "dev": true + "dev": true, + "requires": {} }, "babel-plugin-polyfill-corejs2": { "version": "0.3.1", @@ -31380,7 +31407,8 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz", "integrity": "sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ==", - "dev": true + "dev": true, + "requires": {} }, "class-utils": { "version": "0.3.6", @@ -32442,7 +32470,8 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz", "integrity": "sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg==", - "dev": true + "dev": true, + "requires": {} }, "css-loader": { "version": "3.6.0", @@ -32753,7 +32782,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true + "dev": true, + "requires": {} }, "csso": { "version": "4.2.0", @@ -33626,7 +33656,8 @@ "ws": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "requires": {} } } }, @@ -37422,7 +37453,8 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz", "integrity": "sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w==", - "dev": true + "dev": true, + "requires": {} }, "math-expression-evaluator": { "version": "1.3.14", @@ -37898,7 +37930,8 @@ "mobx-react-lite": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.3.0.tgz", - "integrity": "sha512-U/kMSFtV/bNVgY01FuiGWpRkaQVHozBq5CEBZltFvPt4FcV111hEWkgwqVg9GPPZSEuEdV438PEz8mk8mKpYlA==" + "integrity": "sha512-U/kMSFtV/bNVgY01FuiGWpRkaQVHozBq5CEBZltFvPt4FcV111hEWkgwqVg9GPPZSEuEdV438PEz8mk8mKpYlA==", + "requires": {} }, "moment": { "version": "2.29.1", @@ -39211,25 +39244,29 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz", "integrity": "sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-duplicates": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-empty": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-overridden": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-flexbugs-fixes": { "version": "4.2.1", @@ -39903,7 +39940,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true + "dev": true, + "requires": {} }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -40799,23 +40837,27 @@ "react-circular-progressbar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-circular-progressbar/-/react-circular-progressbar-2.0.4.tgz", - "integrity": "sha512-OfX0ThSxRYEVGaQSt0DlXfyl5w4DbXHsXetyeivmoQrh9xA9bzVPHNf8aAhOIiwiaxX2WYWpLDB3gcpsDJ9oww==" + "integrity": "sha512-OfX0ThSxRYEVGaQSt0DlXfyl5w4DbXHsXetyeivmoQrh9xA9bzVPHNf8aAhOIiwiaxX2WYWpLDB3gcpsDJ9oww==", + "requires": {} }, "react-codemirror2": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-5.1.0.tgz", - "integrity": "sha512-Cksbgbviuf2mJfMyrKmcu7ycK6zX/ukuQO8dvRZdFWqATf5joalhjFc6etnBdGCcPA2LbhIwz+OPnQxLN/j1Fw==" + "integrity": "sha512-Cksbgbviuf2mJfMyrKmcu7ycK6zX/ukuQO8dvRZdFWqATf5joalhjFc6etnBdGCcPA2LbhIwz+OPnQxLN/j1Fw==", + "requires": {} }, "react-colorful": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.5.1.tgz", "integrity": "sha512-M1TJH2X3RXEt12sWkpa6hLc/bbYS0H6F4rIqjQZ+RxNBstpY67d9TrFXtqdZwhpmBXcCwEi7stKqFue3ZRkiOg==", - "dev": true + "dev": true, + "requires": {} }, "react-confirm": { "version": "0.1.24", "resolved": "https://registry.npmjs.org/react-confirm/-/react-confirm-0.1.24.tgz", - "integrity": "sha512-96qA+mbZyBRmh/3Y5aDgrYLwLndbaRjkP3GlXQtPEQbIH0P66xGcHJ7ui6y/MN85AZWq/V3drA1fJOiEcVkAVA==" + "integrity": "sha512-96qA+mbZyBRmh/3Y5aDgrYLwLndbaRjkP3GlXQtPEQbIH0P66xGcHJ7ui6y/MN85AZWq/V3drA1fJOiEcVkAVA==", + "requires": {} }, "react-datepicker": { "version": "2.16.0", @@ -40908,7 +40950,8 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz", "integrity": "sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==", - "dev": true + "dev": true, + "requires": {} }, "react-dom": { "version": "16.14.0", @@ -40986,7 +41029,8 @@ "react-lazyload": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/react-lazyload/-/react-lazyload-3.2.0.tgz", - "integrity": "sha512-zJlrG8QyVZz4+xkYZH5v1w3YaP5wEFaYSUWC4CT9UXfK75IfRAIEdnyIUF+dXr3kX2MOtL1lUaZmaQZqrETwgw==" + "integrity": "sha512-zJlrG8QyVZz4+xkYZH5v1w3YaP5wEFaYSUWC4CT9UXfK75IfRAIEdnyIUF+dXr3kX2MOtL1lUaZmaQZqrETwgw==", + "requires": {} }, "react-lifecycles-compat": { "version": "3.0.4", @@ -40996,7 +41040,8 @@ "react-onclickoutside": { "version": "6.12.1", "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.1.tgz", - "integrity": "sha512-a5Q7CkWznBRUWPmocCvE8b6lEYw1s6+opp/60dCunhO+G6E4tDTO2Sd2jKE+leEnnrLAE2Wj5DlDHNqj5wPv1Q==" + "integrity": "sha512-a5Q7CkWznBRUWPmocCvE8b6lEYw1s6+opp/60dCunhO+G6E4tDTO2Sd2jKE+leEnnrLAE2Wj5DlDHNqj5wPv1Q==", + "requires": {} }, "react-popper": { "version": "1.3.11", @@ -41192,6 +41237,14 @@ } } }, + "react-svg-map": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-svg-map/-/react-svg-map-2.2.0.tgz", + "integrity": "sha512-slZTt4ffXgOb3ND/WEHOc0ojX5lBEv9FKbTU3tWnTLZPQ9L0e686SBqgVxmsuTD+o52Aor87InMzAzDRTQedpA==", + "requires": { + "prop-types": "^15.7.2" + } + }, "react-syntax-highlighter": { "version": "13.5.3", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-13.5.3.tgz", @@ -41438,12 +41491,14 @@ "redux-immutable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", - "integrity": "sha1-Ohoy32Y2ZGK2NpHw4dw15HK7yfM=" + "integrity": "sha1-Ohoy32Y2ZGK2NpHw4dw15HK7yfM=", + "requires": {} }, "redux-thunk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", - "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==" + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "requires": {} }, "refractor": { "version": "3.6.0", @@ -44624,12 +44679,14 @@ "use-composed-ref": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.2.1.tgz", - "integrity": "sha512-6+X1FLlIcjvFMAeAD/hcxDT8tmyrWnbSPMU0EnxQuDLIxokuFzWliXBiYZuGIx+mrAMLBw0WFfCkaPw8ebzAhw==" + "integrity": "sha512-6+X1FLlIcjvFMAeAD/hcxDT8tmyrWnbSPMU0EnxQuDLIxokuFzWliXBiYZuGIx+mrAMLBw0WFfCkaPw8ebzAhw==", + "requires": {} }, "use-isomorphic-layout-effect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz", - "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==" + "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==", + "requires": {} }, "use-latest": { "version": "1.2.0", @@ -46199,7 +46256,8 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/webpack-filter-warnings-plugin/-/webpack-filter-warnings-plugin-1.2.1.tgz", "integrity": "sha512-Ez6ytc9IseDMLPo0qCuNNYzgtUl8NovOqjIq4uAU8LTD4uoa1w1KpZyyzFtLTEMZpkkOkLfL9eN+KGYdk1Qtwg==", - "dev": true + "dev": true, + "requires": {} }, "webpack-hot-middleware": { "version": "2.25.1", @@ -46515,7 +46573,8 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true + "dev": true, + "requires": {} }, "xml": { "version": "1.0.1", diff --git a/frontend/package.json b/frontend/package.json index 5806baf98..a613d5919 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "@sentry/browser": "^5.21.1", + "@svg-maps/world": "^1.0.1", "classnames": "^2.2.6", "codemirror": "^5.62.3", "copy-to-clipboard": "^3.3.1", @@ -48,6 +49,7 @@ "react-redux": "^5.1.2", "react-router": "^4.3.1", "react-router-dom": "^4.3.1", + "react-svg-map": "^2.2.0", "react-tippy": "^1.4.0", "react-toastify": "^5.5.0", "react-virtualized": "^9.22.2", From 8756fd16dac571b643fab8e655c7d6b89dbf513a Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 15 Apr 2022 13:32:38 +0200 Subject: [PATCH 31/32] feat(ui) - widget drilldown on table nad pie chart --- .../CustomMetricTable/CustomMetricTable.tsx | 1 + .../components/WidgetChart/WidgetChart.tsx | 42 +++++++++++-------- .../WidgetSessions/WidgetSessions.tsx | 2 +- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx index a43a35fc8..f83f48466 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx @@ -33,6 +33,7 @@ function CustomMetriTable(props: Props) { const rows = List(data.values); const onClickHandler = (event, data) => { + console.log('onClickHandler', data); const filters = Array(); let filter = { ...filtersMap[metric.metricOf] } filter.value = [data.name] diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index c3f8d10c2..0f073e8e3 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -28,26 +28,28 @@ function WidgetChart(props: Props) { const prevMetricRef = useRef(); const [data, setData] = useState(metric.data); + const isTableWidget = metric.metricType === 'table' && metric.viewType === 'table'; + const isPieChart = metric.metricType === 'table' && metric.viewType === 'pieChart'; + const onChartClick = (event: any) => { if (event) { - const payload = event.activePayload[0].payload; - const timestamp = payload.timestamp; - const periodTimestamps = metric.metricType === 'timeseries' ? - getStartAndEndTimestampsByDensity(timestamp, period.start, period.end, params.density) : - period.toTimestamps(); + if (isTableWidget || isPieChart) { + const periodTimestamps = period.toTimestamps() + drillDownFilter.merge({ + filters: event, + startTimestamp: periodTimestamps.startTimestamp, + endTimestamp: periodTimestamps.endTimestamp, + }); + } else { + const payload = event.activePayload[0].payload; + const timestamp = payload.timestamp; + const periodTimestamps = getStartAndEndTimestampsByDensity(timestamp, period.start, period.end, params.density); - drillDownFilter.merge({ - startTimestamp: periodTimestamps.startTimestamp, - endTimestamp: periodTimestamps.endTimestamp, - }); - - // const activeWidget = { - // widget: metric, - // period: period, - // ...periodTimestamps, - // timestamp: payload.timestamp, - // index, - // } + drillDownFilter.merge({ + startTimestamp: periodTimestamps.startTimestamp, + endTimestamp: periodTimestamps.endTimestamp, + }); + } } } @@ -100,7 +102,10 @@ function WidgetChart(props: Props) { if (metricType === 'table') { if (viewType === 'table') { - return ; + return ; } else if (viewType === 'pieChart') { return ( ) } diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index e6c9cb808..54982ba9c 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -42,7 +42,7 @@ function WidgetSessions(props: Props) { console.log('res', res) setData(res); }); - }, [filter.startTimestamp, filter.endTimestamp, widget.filter]); + }, [filter.startTimestamp, filter.endTimestamp, filter.filters]); return useObserver(() => (
From b54f8d1de35545a3320b6aae19facec81aed259a Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 15 Apr 2022 13:41:59 +0200 Subject: [PATCH 32/32] feat(ui) - check for key --- frontend/app/mstore/dashboardStore.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index ba8c23e58..503ea285f 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -358,7 +358,8 @@ export default class DashboardStore implements IDashboardSotre { const categories: any[] = [] response.forEach(category => { const widgets: any[] = [] - category.widgets.forEach(widget => { + // TODO speed_location is not supported yet + category.widgets.filter(w => w.predefinedKey !== 'speed_location').forEach(widget => { const w = new Widget().fromJson(widget) widgets.push(w) })