From 9dd8a351d9ea417c6fe69c29305b22d1660913ca Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 6 Jan 2023 11:56:12 +0100 Subject: [PATCH 01/65] change(ui) - filter series to check for empty flag --- .../components/FilterSeries/FilterSeries.tsx | 6 ++++-- .../Dashboard/components/WidgetForm/WidgetForm.tsx | 1 + .../shared/Filters/FilterItem/FilterItem.tsx | 13 ++++++------- .../shared/Filters/FilterList/FilterList.tsx | 5 ++++- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx index 12fbab009..c0de60d2d 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx @@ -11,7 +11,7 @@ interface Props { series: any; onRemoveSeries: (seriesIndex: any) => void; canDelete?: boolean; - + supportsEmpty?: boolean; hideHeader?: boolean; emptyMessage?: any; observeChanges?: () => void; @@ -23,7 +23,8 @@ function FilterSeries(props: Props) { }, canDelete, hideHeader = false, - emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' + emptyMessage = 'Add user event or filter to define the series by clicking Add Step.', + supportsEmpty = true, } = props; const [expanded, setExpanded] = useState(true) const { series, seriesIndex } = props; @@ -74,6 +75,7 @@ function FilterSeries(props: Props) { onUpdateFilter={onUpdateFilter} onRemoveFilter={onRemoveFilter} onChangeEventsOrder={onChangeEventsOrder} + supportsEmpty={supportsEmpty} /> ) : (
{emptyMessage}
diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index bc8429d87..a24db7391 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -201,6 +201,7 @@ function WidgetForm(props: Props) { .map((series: any, index: number) => (
metric.updateKey('hasChanged', true)} hideHeader={isTable || isClickmap} seriesIndex={index} diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index fae8d9f1d..c76cc7938 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -2,7 +2,7 @@ import React from 'react'; import FilterOperator from '../FilterOperator'; import FilterSelection from '../FilterSelection'; import FilterValue from '../FilterValue'; -import { Icon } from 'UI'; +import { Icon, Button } from 'UI'; import FilterSource from '../FilterSource'; import { FilterKey, FilterType } from 'App/types/filter/filterType'; import SubFilterItem from '../SubFilterItem'; @@ -14,9 +14,10 @@ interface Props { onRemoveFilter: () => void; isFilter?: boolean; saveRequestPayloads?: boolean; + disableDelete?: boolean } function FilterItem(props: Props) { - const { isFilter = false, filterIndex, filter, saveRequestPayloads } = props; + const { isFilter = false, filterIndex, filter, saveRequestPayloads, disableDelete = false } = props; const canShowValues = !(filter.operator === 'isAny' || filter.operator === 'onAny' || filter.operator === 'isUndefined'); const isSubFilter = filter.type === FilterType.SUB_FILTERS; @@ -49,7 +50,7 @@ function FilterItem(props: Props) { }; return ( -
+
{!isFilter && (
@@ -102,10 +103,8 @@ function FilterItem(props: Props) {
)}
-
-
- -
+
+
); diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx index f6cbbec94..afdd02823 100644 --- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx +++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx @@ -12,13 +12,15 @@ interface Props { hideEventsOrder?: boolean; observeChanges?: () => void; saveRequestPayloads?: boolean; + supportsEmpty?: boolean } function FilterList(props: Props) { - const { observeChanges = () => {}, filter, hideEventsOrder = false, saveRequestPayloads } = props; + const { observeChanges = () => {}, filter, hideEventsOrder = false, saveRequestPayloads, supportsEmpty = true } = props; const filters = List(filter.filters); const hasEvents = filters.filter((i: any) => i.isEvent).size > 0; const hasFilters = filters.filter((i: any) => !i.isEvent).size > 0; let rowIndex = 0; + const cannotDeleteFilter = filters.size === 1 && !supportsEmpty; useEffect(observeChanges, [filters]); @@ -69,6 +71,7 @@ function FilterList(props: Props) { onUpdate={(filter) => props.onUpdateFilter(filterIndex, filter)} onRemoveFilter={() => onRemoveFilter(filterIndex)} saveRequestPayloads={saveRequestPayloads} + disableDelete={cannotDeleteFilter} /> ) : null )} From 221e605cc476af8768d6a000211182c5c3c80b4a Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 6 Jan 2023 13:15:53 +0100 Subject: [PATCH 02/65] fix(ui): fix default player data --- frontend/app/player/web/WebPlayer.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index 089ad94c5..e27432daf 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -26,19 +26,18 @@ export default class WebPlayer extends Player { private targetMarker: TargetMarker constructor(protected wpState: Store, session: any, live: boolean) { - console.log(session.events, session.stackEvents, session.resources, session.errors) let initialLists = live ? {} : { - event: session.events, + event: session.events || [], stack: session.stackEvents || [], resource: session.resources || [], // MBTODO: put ResourceTiming in file - exceptions: session.errors.map(({ time, errorId, name }: any) => + exceptions: session.errors?.map(({ time, errorId, name }: any) => Log({ level: LogLevel.ERROR, value: name, time, errorId, }) - ), + ) || [], } const screen = new Screen(session.isMobile) From a30da5ef77f96a77ad149c9c6b57fdbb220983a7 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 6 Jan 2023 14:55:57 +0100 Subject: [PATCH 03/65] fix(ui): fix alert modal --- .../components/shared/AlertTriggersModal/AlertTriggersModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx b/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx index b76666962..594fa0f8b 100644 --- a/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx +++ b/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx @@ -31,7 +31,7 @@ function AlertTriggersModal(props: Props) { }, []) return useObserver(() => ( -
+
Alerts
{ count > 0 && ( From a94d5f49046efca9ae8eae0a82bcba23e6649fb7 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 6 Jan 2023 17:48:39 +0100 Subject: [PATCH 04/65] fix(ui): fix method context --- frontend/app/api_middleware.js | 4 ++-- frontend/app/store.js | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/app/api_middleware.js b/frontend/app/api_middleware.js index 2b4fd4b4a..cf866f298 100644 --- a/frontend/app/api_middleware.js +++ b/frontend/app/api_middleware.js @@ -1,6 +1,6 @@ import logger from 'App/logger'; import APIClient from './api_client'; -import { UPDATE_JWT } from './duck/user'; +import { LOGIN, UPDATE_JWT } from './duck/user'; export default () => (next) => (action) => { const { types, call, ...rest } = action; @@ -14,7 +14,7 @@ export default () => (next) => (action) => { return call(client) .then(async (response) => { if (response.status === 403) { - next({ type: UPDATE_JWT, data: null }); + next({ type: LOGIN.FAILURE, data: null }); } if (!response.ok) { const text = await response.text(); diff --git a/frontend/app/store.js b/frontend/app/store.js index a2379496c..54b429ada 100644 --- a/frontend/app/store.js +++ b/frontend/app/store.js @@ -6,6 +6,9 @@ import apiMiddleware from './api_middleware'; import LocalStorage from './local_storage'; import { initialState as initUserState, UPDATE_JWT } from './duck/user' +// TODO @remove after few days +localStorage.removeItem('jwt') + const storage = new LocalStorage({ user: Object, }); From 9e7486c6b7309b7bc689253601cc39cb8f4ae846 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 6 Jan 2023 18:04:26 +0100 Subject: [PATCH 05/65] change(ui) - clickmaps disable first event selection and delete btn, also restrict the filters --- .../components/FilterSeries/FilterSeries.tsx | 4 ++ .../components/WidgetForm/WidgetForm.tsx | 6 +- .../shared/Filters/FilterItem/FilterItem.tsx | 7 ++- .../shared/Filters/FilterList/FilterList.tsx | 7 ++- .../Filters/FilterModal/FilterModal.tsx | 25 ++++++++- .../FilterSelection/FilterSelection.tsx | 56 ++++++++++++------- frontend/app/types/filter/newFilter.js | 2 + 7 files changed, 78 insertions(+), 29 deletions(-) diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx index c0de60d2d..49f3659da 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx @@ -15,6 +15,7 @@ interface Props { hideHeader?: boolean; emptyMessage?: any; observeChanges?: () => void; + excludeFilterKeys?: Array } function FilterSeries(props: Props) { @@ -25,6 +26,7 @@ function FilterSeries(props: Props) { hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.', supportsEmpty = true, + excludeFilterKeys = [] } = props; const [expanded, setExpanded] = useState(true) const { series, seriesIndex } = props; @@ -76,6 +78,7 @@ function FilterSeries(props: Props) { onRemoveFilter={onRemoveFilter} onChangeEventsOrder={onChangeEventsOrder} supportsEmpty={supportsEmpty} + excludeFilterKeys={excludeFilterKeys} /> ) : (
{emptyMessage}
@@ -86,6 +89,7 @@ function FilterSeries(props: Props) { diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index a24db7391..d9eaf0e65 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -19,9 +19,8 @@ import { PERFORMANCE, WEB_VITALS, } from 'App/constants/card'; -import { clickmapFilter } from 'App/types/filter/newFilter'; +import { clickmapFilter, eventKeys } from 'App/types/filter/newFilter'; import { renderClickmapThumbnail } from './renderMap'; - interface Props { history: any; match: any; @@ -51,6 +50,8 @@ function WidgetForm(props: Props) { metric.metricType ); + const excludeFilterKeys = isClickmap ? eventKeys : [] + const writeOption = ({ value, name }: { value: any; name: any }) => { value = Array.isArray(value) ? value : value.value; const obj: any = { [name]: value }; @@ -202,6 +203,7 @@ function WidgetForm(props: Props) {
metric.updateKey('hasChanged', true)} hideHeader={isTable || isClickmap} seriesIndex={index} diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index c76cc7938..854a15b75 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -14,10 +14,11 @@ interface Props { onRemoveFilter: () => void; isFilter?: boolean; saveRequestPayloads?: boolean; - disableDelete?: boolean + disableDelete?: boolean; + excludeFilterKeys?: Array; } function FilterItem(props: Props) { - const { isFilter = false, filterIndex, filter, saveRequestPayloads, disableDelete = false } = props; + const { isFilter = false, filterIndex, filter, saveRequestPayloads, disableDelete = false, excludeFilterKeys = [] } = props; const canShowValues = !(filter.operator === 'isAny' || filter.operator === 'onAny' || filter.operator === 'isUndefined'); const isSubFilter = filter.type === FilterType.SUB_FILTERS; @@ -57,7 +58,7 @@ function FilterItem(props: Props) { {filterIndex + 1}
)} - + {/* Filter with Source */} {filter.hasSource && ( diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx index afdd02823..521a70eeb 100644 --- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx +++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx @@ -13,14 +13,15 @@ interface Props { observeChanges?: () => void; saveRequestPayloads?: boolean; supportsEmpty?: boolean + excludeFilterKeys?: Array } function FilterList(props: Props) { - const { observeChanges = () => {}, filter, hideEventsOrder = false, saveRequestPayloads, supportsEmpty = true } = props; + const { observeChanges = () => {}, filter, hideEventsOrder = false, saveRequestPayloads, supportsEmpty = true, excludeFilterKeys = [] } = props; const filters = List(filter.filters); const hasEvents = filters.filter((i: any) => i.isEvent).size > 0; const hasFilters = filters.filter((i: any) => !i.isEvent).size > 0; let rowIndex = 0; - const cannotDeleteFilter = filters.size === 1 && !supportsEmpty; + const cannotDeleteFilter = hasEvents && !supportsEmpty; useEffect(observeChanges, [filters]); @@ -72,6 +73,7 @@ function FilterList(props: Props) { onRemoveFilter={() => onRemoveFilter(filterIndex)} saveRequestPayloads={saveRequestPayloads} disableDelete={cannotDeleteFilter} + excludeFilterKeys={excludeFilterKeys} /> ) : null )} @@ -92,6 +94,7 @@ function FilterList(props: Props) { filter={filter} onUpdate={(filter) => props.onUpdateFilter(filterIndex, filter)} onRemoveFilter={() => onRemoveFilter(filterIndex)} + excludeFilterKeys={excludeFilterKeys} /> ) : null )} diff --git a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx index 5106dd8c6..fa259b9f6 100644 --- a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx +++ b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Icon, Loader } from 'UI'; import { connect } from 'react-redux'; import cn from 'classnames'; @@ -6,10 +6,27 @@ import stl from './FilterModal.module.css'; import { filtersMap } from 'Types/filter/newFilter'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +function filterJson( + jsonObj: Record, + excludeKeys: string[] = [] +): Record { + let filtered: Record = {}; + + for (const key in jsonObj) { + const arr = jsonObj[key].filter((i: any) => !excludeKeys.includes(i.key)); + if (arr.length) { + filtered[key] = arr; + } + } + + return filtered; +} + export const getMatchingEntries = (searchQuery: string, filters: Record) => { const matchingCategories: string[] = []; const matchingFilters: Record = {}; const lowerCaseQuery = searchQuery.toLowerCase(); + if (lowerCaseQuery.length === 0) return { matchingCategories: Object.keys(filters), matchingFilters: filters, @@ -33,12 +50,13 @@ export const getMatchingEntries = (searchQuery: string, filters: Record void, + onFilterClick?: (filter: any) => void, filterSearchList: any, // metaOptions: any, isMainSearch?: boolean, fetchingFilterSearchList: boolean, searchQuery?: string, + excludeFilterKeys?: Array } function FilterModal(props: Props) { const { @@ -48,6 +66,7 @@ function FilterModal(props: Props) { isMainSearch = false, fetchingFilterSearchList, searchQuery = '', + excludeFilterKeys = [] } = props; const showSearchList = isMainSearch && searchQuery.length > 0; @@ -57,7 +76,7 @@ function FilterModal(props: Props) { onFilterClick(_filter); } - const { matchingCategories, matchingFilters } = getMatchingEntries(searchQuery, filters); + const { matchingCategories, matchingFilters } = getMatchingEntries(searchQuery, filterJson(filters, excludeFilterKeys)); const isResultEmpty = (!filterSearchList || Object.keys(filterSearchList).length === 0) && matchingCategories.length === 0 && Object.keys(matchingFilters).length === 0 diff --git a/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx b/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx index d4e20d322..e78904936 100644 --- a/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx +++ b/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx @@ -3,7 +3,8 @@ import FilterModal from '../FilterModal'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; import { Icon } from 'UI'; import { connect } from 'react-redux'; -import { assist as assistRoute, isRoute } from "App/routes"; +import { assist as assistRoute, isRoute } from 'App/routes'; +import cn from 'classnames'; const ASSIST_ROUTE = assistRoute(); @@ -14,40 +15,54 @@ interface Props { onFilterClick: (filter: any) => void; children?: any; isLive?: boolean; + excludeFilterKeys?: Array + disabled?: boolean } function FilterSelection(props: Props) { - const { filter, onFilterClick, children } = props; + const { filter, onFilterClick, children, excludeFilterKeys = [], disabled = false } = props; const [showModal, setShowModal] = useState(false); return (
setTimeout(function() { - setShowModal(false) - }, 200)} + onClickOutside={() => + setTimeout(function () { + setShowModal(false); + }, 200) + } > - { children ? React.cloneElement(children, { onClick: (e) => { - e.stopPropagation(); - e.preventDefault(); - setShowModal(true); - }}) : ( + {children ? ( + React.cloneElement(children, { + onClick: (e) => { + e.stopPropagation(); + e.preventDefault(); + setShowModal(true); + }, + disabled: disabled + }) + ) : (
setShowModal(true)} > -
{filter.label}
+
+ {filter.label} +
- ) } + )}
{showModal && (
)} @@ -55,8 +70,11 @@ function FilterSelection(props: Props) { ); } -export default connect((state: any) => ({ - filterList: state.getIn([ 'search', 'filterList' ]), - filterListLive: state.getIn([ 'search', 'filterListLive' ]), - isLive: state.getIn([ 'sessions', 'activeTab' ]).type === 'live', -}), { })(FilterSelection); \ No newline at end of file +export default connect( + (state: any) => ({ + filterList: state.getIn(['search', 'filterList']), + filterListLive: state.getIn(['search', 'filterListLive']), + isLive: state.getIn(['sessions', 'activeTab']).type === 'live', + }), + {} +)(FilterSelection); diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index 488eb0331..2f4ffe127 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -53,6 +53,8 @@ export const filters = [ { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', placeholder: 'Select an issue', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/click', options: filterOptions.issueOptions }, ]; +export const eventKeys = filters.filter((i) => i.isEvent).map(i => i.key); + export const clickmapFilter = { key: FilterKey.LOCATION, type: FilterType.MULTIPLE, From 6be1e7b6620b222af187733401836bcd4df7c79f Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 6 Jan 2023 18:05:42 +0100 Subject: [PATCH 06/65] fix(ui): fix jwt --- frontend/app/api_middleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/api_middleware.js b/frontend/app/api_middleware.js index cf866f298..9c71c6d19 100644 --- a/frontend/app/api_middleware.js +++ b/frontend/app/api_middleware.js @@ -14,7 +14,7 @@ export default () => (next) => (action) => { return call(client) .then(async (response) => { if (response.status === 403) { - next({ type: LOGIN.FAILURE, data: null }); + next({ type: LOGIN.FAILURE }); } if (!response.ok) { const text = await response.text(); From 8c00dfa1b6c6d06908a7a8738b431af1c44037de Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Thu, 5 Jan 2023 00:17:45 +0100 Subject: [PATCH 07/65] chore(helm): Disable linkerd injection for migration job Signed-off-by: rjshrjndrn --- scripts/helmcharts/openreplay/templates/job.yaml | 5 +++++ scripts/helmcharts/openreplay/values.yaml | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/scripts/helmcharts/openreplay/templates/job.yaml b/scripts/helmcharts/openreplay/templates/job.yaml index aaadc2eb7..a30cca848 100644 --- a/scripts/helmcharts/openreplay/templates/job.yaml +++ b/scripts/helmcharts/openreplay/templates/job.yaml @@ -26,6 +26,11 @@ spec: template: metadata: name: postgresqlMigrate + {{- with .Values.migrationJob.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + annotations: spec: initContainers: - name: git diff --git a/scripts/helmcharts/openreplay/values.yaml b/scripts/helmcharts/openreplay/values.yaml index 67cf00405..cf76cb2d3 100644 --- a/scripts/helmcharts/openreplay/values.yaml +++ b/scripts/helmcharts/openreplay/values.yaml @@ -1,3 +1,7 @@ +migrationJob: + podAnnotations: + linkerd.io/inject: disabled + redis: &redis tls: enabled: false @@ -5,6 +9,10 @@ redis: &redis ingress-nginx: enabled: true controller: + admissionWebhooks: + patch: + podAnnotations: + linkerd.io/inject: disabled name: controller image: registry: k8s.gcr.io From cfce1eb84eda3635aa6efdca1af3418a2cb249d0 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Thu, 5 Jan 2023 00:29:31 +0100 Subject: [PATCH 08/65] chore(helm): Disabling linkerd for cronjobs --- .../openreplay/charts/utilities/templates/efs-cron.yaml | 5 +++++ .../openreplay/charts/utilities/templates/report-cron.yaml | 5 +++++ .../charts/utilities/templates/sessions-cleaner-cron.yaml | 5 +++++ .../charts/utilities/templates/telemetry-cron.yaml | 5 +++++ scripts/helmcharts/openreplay/charts/utilities/values.yaml | 3 +++ scripts/helmcharts/openreplay/templates/job.yaml | 1 - 6 files changed, 23 insertions(+), 1 deletion(-) diff --git a/scripts/helmcharts/openreplay/charts/utilities/templates/efs-cron.yaml b/scripts/helmcharts/openreplay/charts/utilities/templates/efs-cron.yaml index e88aabe80..ea9413538 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/templates/efs-cron.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/templates/efs-cron.yaml @@ -11,6 +11,11 @@ spec: spec: backoffLimit: 0 # Don't restart the failed jobs template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 12 }} + {{- end }} spec: restartPolicy: Never containers: diff --git a/scripts/helmcharts/openreplay/charts/utilities/templates/report-cron.yaml b/scripts/helmcharts/openreplay/charts/utilities/templates/report-cron.yaml index 17fa52720..79b88f520 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/templates/report-cron.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/templates/report-cron.yaml @@ -12,6 +12,11 @@ spec: spec: backoffLimit: 0 # Don't restart the failed jobs template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 12 }} + {{- end }} spec: restartPolicy: Never containers: diff --git a/scripts/helmcharts/openreplay/charts/utilities/templates/sessions-cleaner-cron.yaml b/scripts/helmcharts/openreplay/charts/utilities/templates/sessions-cleaner-cron.yaml index 49bc8cfed..c6fcea0e6 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/templates/sessions-cleaner-cron.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/templates/sessions-cleaner-cron.yaml @@ -12,6 +12,11 @@ spec: spec: backoffLimit: 0 # Don't restart the failed jobs template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 12 }} + {{- end }} spec: restartPolicy: Never containers: diff --git a/scripts/helmcharts/openreplay/charts/utilities/templates/telemetry-cron.yaml b/scripts/helmcharts/openreplay/charts/utilities/templates/telemetry-cron.yaml index b9044664f..890bedf9a 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/templates/telemetry-cron.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/templates/telemetry-cron.yaml @@ -12,6 +12,11 @@ spec: spec: backoffLimit: 0 # Don't restart the failed jobs template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 12 }} + {{- end }} spec: restartPolicy: Never containers: diff --git a/scripts/helmcharts/openreplay/charts/utilities/values.yaml b/scripts/helmcharts/openreplay/charts/utilities/values.yaml index 2076f8349..feba1be7c 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/values.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/values.yaml @@ -85,6 +85,9 @@ cron: "5 3 */3 * *" # Pod configurations +podAnnotations: + linkerd.io/inject: disabled + securityContext: runAsUser: 1001 runAsGroup: 1001 diff --git a/scripts/helmcharts/openreplay/templates/job.yaml b/scripts/helmcharts/openreplay/templates/job.yaml index a30cca848..ba406fa7a 100644 --- a/scripts/helmcharts/openreplay/templates/job.yaml +++ b/scripts/helmcharts/openreplay/templates/job.yaml @@ -30,7 +30,6 @@ spec: annotations: {{- toYaml . | nindent 8 }} {{- end }} - annotations: spec: initContainers: - name: git From 3e50efd6737d0639de40bb2eb0f63e8d8016d5f1 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 10 Jan 2023 16:06:43 +0100 Subject: [PATCH 09/65] change(ui) - fadeout inactive rows --- frontend/app/components/Session_/TimeTable/timeTable.module.css | 2 +- frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/components/Session_/TimeTable/timeTable.module.css b/frontend/app/components/Session_/TimeTable/timeTable.module.css index c2412ff8d..175958f21 100644 --- a/frontend/app/components/Session_/TimeTable/timeTable.module.css +++ b/frontend/app/components/Session_/TimeTable/timeTable.module.css @@ -108,5 +108,5 @@ $offset: 10px; } .inactiveRow { - opacity: 0.5; + opacity: 0.4; } \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx index bbb2b204e..06cfa2d5c 100644 --- a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx +++ b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx @@ -214,7 +214,7 @@ export default class TimeTable extends React.PureComponent { 'error color-red': !!row.isRed && row.isRed(), 'cursor-pointer': typeof onRowClick === 'function', [stl.activeRow]: activeIndex === index, - // [stl.inactiveRow]: !activeIndex || index > activeIndex, + [stl.inactiveRow]: !activeIndex || index > activeIndex, } )} onClick={typeof onRowClick === 'function' ? () => onRowClick(row, index) : undefined} From 2ec63b159194e2e58514c42bf06d02607d809b2f Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Tue, 10 Jan 2023 16:55:50 +0100 Subject: [PATCH 10/65] fix(ui): fix incorrect login error --- frontend/app/components/Login/Login.js | 6 +++--- frontend/app/duck/user.js | 15 +++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/frontend/app/components/Login/Login.js b/frontend/app/components/Login/Login.js index 70ee0f74e..06a2a8dd7 100644 --- a/frontend/app/components/Login/Login.js +++ b/frontend/app/components/Login/Login.js @@ -122,15 +122,15 @@ export default class Login extends React.Component {
- { errors && -
+ { errors.length ? + (
{ errors.map(error => (
{ error }
)) } -
+
) : null } {/*
*/} diff --git a/frontend/app/duck/user.js b/frontend/app/duck/user.js index f28fb7ed0..5814f082c 100644 --- a/frontend/app/duck/user.js +++ b/frontend/app/duck/user.js @@ -28,7 +28,11 @@ export const initialState = Map({ authDetails: {}, onboarding: false, sites: List(), - jwt: null + jwt: null, + loginRequest: { + loading: false, + errors: [] + }, }); const setClient = (state, data) => { @@ -50,10 +54,12 @@ const reducer = (state = initialState, action = {}) => { switch (action.type) { case UPDATE_JWT: return state.set('jwt', action.data); + case LOGIN.REQUEST: + return state.set('loginRequest', { loading: true, errors: [] }) case RESET_PASSWORD.SUCCESS: case UPDATE_PASSWORD.SUCCESS: case LOGIN.SUCCESS: - state.set('account', Account({...action.data.user })) + state.set('account', Account({...action.data.user })).set('loginRequest', { loading: false, errors: [] }) case SIGNUP.SUCCESS: state.set('account', Account(action.data.user)).set('onboarding', true); case REQUEST_RESET_PASSWORD.SUCCESS: @@ -67,6 +73,8 @@ const reducer = (state = initialState, action = {}) => { return state.set('passwordErrors', List(action.errors)) case FETCH_ACCOUNT.FAILURE: case LOGIN.FAILURE: + deleteCookie('jwt', '/', 'openreplay.com') + return state.set('loginRequest', { loading: false, errors: ['Invalid username or password'] }); case DELETE.SUCCESS: case DELETE.FAILURE: deleteCookie('jwt', '/', 'openreplay.com') @@ -86,7 +94,6 @@ const reducer = (state = initialState, action = {}) => { export default withRequestState({ - loginRequest: LOGIN, signupRequest: SIGNUP, updatePasswordRequest: UPDATE_PASSWORD, requestResetPassowrd: REQUEST_RESET_PASSWORD, @@ -96,7 +103,7 @@ export default withRequestState({ updateAccountRequest: UPDATE_ACCOUNT, }, reducer); -export const login = params => dispatch => dispatch({ +export const login = params => ({ types: LOGIN.toArray(), call: client => client.post('/login', params), }); From 269ea69206d302646eb0fc5c09e0cee256bd0c9f Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Tue, 10 Jan 2023 17:28:37 +0100 Subject: [PATCH 11/65] fix(ui): fix incorrect login error --- frontend/app/duck/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/duck/user.js b/frontend/app/duck/user.js index 5814f082c..fc627e214 100644 --- a/frontend/app/duck/user.js +++ b/frontend/app/duck/user.js @@ -71,10 +71,10 @@ const reducer = (state = initialState, action = {}) => { return state.set('authDetails', action.data); case UPDATE_PASSWORD.FAILURE: return state.set('passwordErrors', List(action.errors)) - case FETCH_ACCOUNT.FAILURE: case LOGIN.FAILURE: deleteCookie('jwt', '/', 'openreplay.com') return state.set('loginRequest', { loading: false, errors: ['Invalid username or password'] }); + case FETCH_ACCOUNT.FAILURE: case DELETE.SUCCESS: case DELETE.FAILURE: deleteCookie('jwt', '/', 'openreplay.com') From 44f9e4c1203055524a8b56e72a2acf181dd4d7e6 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 11 Jan 2023 16:23:10 +0100 Subject: [PATCH 12/65] [Storage] added sort operation for session messages (#921) * feat(backend): added sort operation for session messages --- backend/internal/config/storage/config.go | 1 + backend/internal/storage/storage.go | 45 +++++++++++++- backend/pkg/messages/bytes.go | 11 ++++ backend/pkg/messages/session-iterator.go | 71 +++++++++++++++++++++++ 4 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 backend/pkg/messages/session-iterator.go diff --git a/backend/internal/config/storage/config.go b/backend/internal/config/storage/config.go index 63c595f62..ca4ff8028 100644 --- a/backend/internal/config/storage/config.go +++ b/backend/internal/config/storage/config.go @@ -21,6 +21,7 @@ type Config struct { ProducerCloseTimeout int `env:"PRODUCER_CLOSE_TIMEOUT,default=15000"` UseFailover bool `env:"USE_FAILOVER,default=false"` MaxFileSize int64 `env:"MAX_FILE_SIZE,default=524288000"` + UseSort bool `env:"USE_SESSION_SORT,default=true"` UseProfiler bool `env:"PROFILER_ENABLED,default=false"` } diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go index 3b315561d..fbe9e2228 100644 --- a/backend/internal/storage/storage.go +++ b/backend/internal/storage/storage.go @@ -42,6 +42,8 @@ type Storage struct { sessionDEVSize syncfloat64.Histogram readingDOMTime syncfloat64.Histogram readingDEVTime syncfloat64.Histogram + sortingDOMTime syncfloat64.Histogram + sortingDEVTime syncfloat64.Histogram archivingDOMTime syncfloat64.Histogram archivingDEVTime syncfloat64.Histogram uploadingDOMTime syncfloat64.Histogram @@ -79,6 +81,14 @@ func New(cfg *config.Config, s3 *storage.S3, metrics *monitoring.Metrics) (*Stor if err != nil { log.Printf("can't create reading_duration metric: %s", err) } + sortingDOMTime, err := metrics.RegisterHistogram("sorting_duration") + if err != nil { + log.Printf("can't create reading_duration metric: %s", err) + } + sortingDEVTime, err := metrics.RegisterHistogram("sorting_dt_duration") + if err != nil { + log.Printf("can't create reading_duration metric: %s", err) + } archivingDOMTime, err := metrics.RegisterHistogram("archiving_duration") if err != nil { log.Printf("can't create archiving_duration metric: %s", err) @@ -104,6 +114,8 @@ func New(cfg *config.Config, s3 *storage.S3, metrics *monitoring.Metrics) (*Stor sessionDEVSize: sessionDevtoolsSize, readingDOMTime: readingDOMTime, readingDEVTime: readingDEVTime, + sortingDOMTime: sortingDOMTime, + sortingDEVTime: sortingDEVTime, archivingDOMTime: archivingDOMTime, archivingDEVTime: archivingDEVTime, uploadingDOMTime: uploadingDOMTime, @@ -156,14 +168,41 @@ func (s *Storage) Upload(msg *messages.SessionEnd) (err error) { return nil } -func (s *Storage) openSession(filePath string) ([]byte, error) { +func (s *Storage) openSession(filePath string, tp FileType) ([]byte, error) { // Check file size before download into memory info, err := os.Stat(filePath) if err == nil && info.Size() > s.cfg.MaxFileSize { return nil, fmt.Errorf("big file, size: %d", info.Size()) } // Read file into memory - return os.ReadFile(filePath) + raw, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + if !s.cfg.UseSort { + return raw, nil + } + start := time.Now() + res, err := s.sortSessionMessages(raw) + if err != nil { + return nil, fmt.Errorf("can't sort session, err: %s", err) + } + if tp == DOM { + s.sortingDOMTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds())) + } else { + s.sortingDEVTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds())) + } + return res, nil +} + +func (s *Storage) sortSessionMessages(raw []byte) ([]byte, error) { + // Parse messages, sort by index and save result into slice of bytes + unsortedMessages, err := messages.SplitMessages(raw) + if err != nil { + log.Printf("can't sort session, err: %s", err) + return raw, nil + } + return messages.MergeMessages(raw, messages.SortMessages(unsortedMessages)), nil } func (s *Storage) prepareSession(path string, tp FileType, task *Task) error { @@ -172,7 +211,7 @@ func (s *Storage) prepareSession(path string, tp FileType, task *Task) error { path += "devtools" } startRead := time.Now() - mob, err := s.openSession(path) + mob, err := s.openSession(path, tp) if err != nil { return err } diff --git a/backend/pkg/messages/bytes.go b/backend/pkg/messages/bytes.go index 0576201ea..00d161d97 100644 --- a/backend/pkg/messages/bytes.go +++ b/backend/pkg/messages/bytes.go @@ -1,6 +1,7 @@ package messages import ( + "encoding/binary" "errors" "fmt" "io" @@ -13,6 +14,7 @@ type BytesReader interface { ReadInt() (int64, error) ReadBoolean() (bool, error) ReadString() (string, error) + ReadIndex() (uint64, error) Data() []byte Pointer() int64 SetPointer(p int64) @@ -106,6 +108,15 @@ func (m *bytesReaderImpl) ReadString() (string, error) { return str, nil } +func (m *bytesReaderImpl) ReadIndex() (uint64, error) { + if len(m.data)-int(m.curr) < 8 { + return 0, fmt.Errorf("out of range") + } + size := binary.LittleEndian.Uint64(m.data[m.curr : m.curr+8]) + m.curr += 8 + return size, nil +} + func (m *bytesReaderImpl) Data() []byte { return m.data } diff --git a/backend/pkg/messages/session-iterator.go b/backend/pkg/messages/session-iterator.go new file mode 100644 index 000000000..45daae4b8 --- /dev/null +++ b/backend/pkg/messages/session-iterator.go @@ -0,0 +1,71 @@ +package messages + +import ( + "bytes" + "fmt" + "io" + "log" + "sort" +) + +type msgInfo struct { + index uint64 + start int64 + end int64 +} + +func SplitMessages(data []byte) ([]*msgInfo, error) { + messages := make([]*msgInfo, 0) + reader := NewBytesReader(data) + for { + // Get message start + msgStart := reader.Pointer() + if int(msgStart) >= len(data) { + return messages, nil + } + + // Read message index + msgIndex, err := reader.ReadIndex() + if err != nil { + if err != io.EOF { + log.Println(reader.Pointer(), msgStart) + return nil, fmt.Errorf("read message index err: %s", err) + } + return messages, nil + } + + // Read message type + msgType, err := reader.ReadUint() + if err != nil { + return nil, fmt.Errorf("read message type err: %s", err) + } + + // Read message body + _, err = ReadMessage(msgType, reader) + if err != nil { + return nil, fmt.Errorf("read message body err: %s", err) + } + + // Add new message info to messages slice + messages = append(messages, &msgInfo{ + index: msgIndex, + start: msgStart, + end: reader.Pointer(), + }) + } +} + +func SortMessages(messages []*msgInfo) []*msgInfo { + sort.SliceStable(messages, func(i, j int) bool { + return messages[i].index < messages[j].index + }) + return messages +} + +func MergeMessages(data []byte, messages []*msgInfo) []byte { + sortedSession := bytes.NewBuffer(make([]byte, 0, len(data))) + for _, info := range messages { + sortedSession.Write(data[info.start:info.end]) + } + return sortedSession.Bytes() +} From 6972f842756012f699159aab0c765468d9eca00c Mon Sep 17 00:00:00 2001 From: Alexander Zavorotynskiy Date: Wed, 11 Jan 2023 16:52:32 +0100 Subject: [PATCH 13/65] feat(backend): added batch and compression configuration for kafka producer --- backend/Dockerfile | 4 ++-- ee/backend/pkg/kafka/producer.go | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 043de51cd..3705600ff 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -76,8 +76,8 @@ ENV TZ=UTC \ USE_FAILOVER=false \ GROUP_STORAGE_FAILOVER=failover \ TOPIC_STORAGE_FAILOVER=storage-failover \ - PROFILER_ENABLED=false - + PROFILER_ENABLED=false \ + COMPRESSION_TYPE=zstd ARG SERVICE_NAME diff --git a/ee/backend/pkg/kafka/producer.go b/ee/backend/pkg/kafka/producer.go index 1ec241b8a..d3bc2cf51 100644 --- a/ee/backend/pkg/kafka/producer.go +++ b/ee/backend/pkg/kafka/producer.go @@ -15,13 +15,20 @@ type Producer struct { func NewProducer(messageSizeLimit int, useBatch bool) *Producer { kafkaConfig := &kafka.ConfigMap{ - "enable.idempotence": true, - "bootstrap.servers": env.String("KAFKA_SERVERS"), - "go.delivery.reports": true, - "security.protocol": "plaintext", - "go.batch.producer": useBatch, - "queue.buffering.max.ms": 100, - "message.max.bytes": messageSizeLimit, + "enable.idempotence": true, + "bootstrap.servers": env.String("KAFKA_SERVERS"), + "go.delivery.reports": true, + "security.protocol": "plaintext", + "go.batch.producer": useBatch, + "message.max.bytes": messageSizeLimit, // should be synced with broker config + "linger.ms": 1000, + "queue.buffering.max.ms": 1000, + "batch.num.messages": 1000, + "queue.buffering.max.messages": 1000, + "retries": 3, + "retry.backoff.ms": 100, + "max.in.flight.requests.per.connection": 1, + "compression.type": env.String("COMPRESSION_TYPE"), } // Apply ssl configuration if env.Bool("KAFKA_USE_SSL") { From d88896d649de2b0ceef11540784a52a108e48c5c Mon Sep 17 00:00:00 2001 From: Alexander Zavorotynskiy Date: Wed, 11 Jan 2023 17:53:36 +0100 Subject: [PATCH 14/65] feat(backend): added 503 error for assets retrier --- backend/internal/assets/cacher/cacher.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/internal/assets/cacher/cacher.go b/backend/internal/assets/cacher/cacher.go index b56c97d74..8bbee092f 100644 --- a/backend/internal/assets/cacher/cacher.go +++ b/backend/internal/assets/cacher/cacher.go @@ -90,8 +90,8 @@ func (c *cacher) cacheURL(t *Task) { defer res.Body.Close() if res.StatusCode >= 400 { printErr := true - // Retry 403 error - if res.StatusCode == 403 && t.retries > 0 { + // Retry 403/503 errors + if (res.StatusCode == 403 || res.StatusCode == 503) && t.retries > 0 { c.workers.AddTask(t) printErr = false } From 572971b7fced36303dc739c8488ed66c5290827f Mon Sep 17 00:00:00 2001 From: Mehdi Osman Date: Thu, 12 Jan 2023 12:09:20 +0100 Subject: [PATCH 15/65] Update Dockerfile --- backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 3705600ff..ad60c1b75 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -27,7 +27,7 @@ RUN adduser -u 1001 openreplay -D ENV TZ=UTC \ GIT_SHA=$GIT_SHA \ - FS_ULIMIT=1000 \ + FS_ULIMIT=10000 \ FS_DIR=/mnt/efs \ MAXMINDDB_FILE=/home/openreplay/geoip.mmdb \ UAPARSER_FILE=/home/openreplay/regexes.yaml \ From a30c278611cdcc94f1b79667bac978ad10fd5b1b Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Thu, 12 Jan 2023 16:03:42 +0100 Subject: [PATCH 16/65] ci(helm): setting app namespace as default Signed-off-by: rjshrjndrn --- .github/workflows/api-ee.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/api-ee.yaml b/.github/workflows/api-ee.yaml index f30c1b111..c014f34c5 100644 --- a/.github/workflows/api-ee.yaml +++ b/.github/workflows/api-ee.yaml @@ -110,7 +110,9 @@ jobs: cat /tmp/image_override.yaml # Deploy command - helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true --no-hooks | kubectl apply -n app -f - + kubectl config set-context --namespace=app --current + kubectl config get-contexts + helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true --no-hooks | kubectl apply -f - env: DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} # We're not passing -ee flag, because helm will add that. From 50b2bd17f4381db218d490ba9fc07bcc7a0d0060 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Fri, 13 Jan 2023 12:58:52 +0100 Subject: [PATCH 17/65] fix(backend/assets):add asp.NET extension for caching; strip #fragment suffix on baseURL when resolving --- backend/pkg/url/assets/url.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/pkg/url/assets/url.go b/backend/pkg/url/assets/url.go index 83cdd5ac1..6fdc7cb92 100644 --- a/backend/pkg/url/assets/url.go +++ b/backend/pkg/url/assets/url.go @@ -24,8 +24,9 @@ func ResolveURL(baseurl string, rawurl string) string { if !isRelativeCachable(rawurl) { return rawurl } - base, _ := url.ParseRequestURI(baseurl) // fn Only for base urls - u, _ := url.Parse(rawurl) // TODO: handle errors ? + baseurl = strings.Split(baseurl, "#")[0] // remove #fragment suffix if present + base, _ := url.ParseRequestURI(baseurl) // fn Only for base urls + u, _ := url.Parse(rawurl) // TODO: handle errors ? if base == nil || u == nil { return rawurl } @@ -48,6 +49,7 @@ func isCachable(rawurl string) bool { } ext := filepath.Ext(u.Path) return ext == ".css" || + ext == ".ashx" || // ASP .NET ext == ".woff" || ext == ".woff2" || ext == ".ttf" || From 1b310a28bc9dfc845267c9536732907e77927b09 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Fri, 13 Jan 2023 16:10:29 +0100 Subject: [PATCH 18/65] fix(ui): bugfix for rage click --- frontend/app/components/Session/WebPlayer.tsx | 8 +++++--- frontend/app/types/session/event.ts | 9 ++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index f6f3cfb71..40b5674dc 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -13,6 +13,7 @@ import { fetchList as fetchMembers } from 'Duck/member'; import PlayerContent from './PlayerContent'; import { IPlayerContext, PlayerContext, defaultContextValue } from './playerContext'; import { observer } from 'mobx-react-lite'; +import { Note } from "App/services/NotesService"; const TABS = { EVENTS: 'User Steps', @@ -36,7 +37,7 @@ function WebPlayer(props: any) { const { notesStore } = useStore(); const [activeTab, setActiveTab] = useState(''); const [showNoteModal, setShowNote] = useState(false); - const [noteItem, setNoteItem] = useState(null); + const [noteItem, setNoteItem] = useState(); const [contextValue, setContextValue] = useState(defaultContextValue); useEffect(() => { @@ -54,10 +55,11 @@ function WebPlayer(props: any) { if (!isClickmap) { notesStore.fetchSessionNotes(session.sessionId).then((r) => { - const note = props.query.get('note'); + const noteId = props.query.get('note'); + const note = notesStore.getNoteById(parseInt(noteId, 10), r) if (note) { WebPlayerInst.pause(); - setNoteItem(notesStore.getNoteById(parseInt(note, 10), r)); + setNoteItem(note); setShowNote(true); } }); diff --git a/frontend/app/types/session/event.ts b/frontend/app/types/session/event.ts index 575183b93..569a1903e 100644 --- a/frontend/app/types/session/event.ts +++ b/frontend/app/types/session/event.ts @@ -87,15 +87,18 @@ class Console extends Event { } class Click extends Event { - readonly type = CLICK; + readonly type: typeof CLICKRAGE | typeof CLICK = CLICK; readonly name = 'Click' targetContent = ''; count: number - constructor(evt: ClickEvent) { + constructor(evt: ClickEvent, isClickRage: boolean) { super(evt); this.targetContent = evt.targetContent this.count = evt.count + if (isClickRage) { + this.type = CLICKRAGE + } } } @@ -151,7 +154,7 @@ export default function(event: EventData) { return new Location(event as LocationEvent) } if (event.type && event.type === CLICKRAGE) { - return new Click(event as ClickEvent) + return new Click(event as ClickEvent, true) } // not used right now? // if (event.type === CUSTOM || !event.type) { From e6d46c3ea1be90da8fd0b7838b428bad7f27bccf Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 3 Jan 2023 12:57:09 +0100 Subject: [PATCH 19/65] change(ui): start session mobx store --- frontend/app/duck/sessions.ts | 3 +- frontend/app/mstore/sessionStore.ts | 119 ++++++++++++++++++++++- frontend/app/services/SessionService.ts | 27 ++++- frontend/app/types/session/errorStack.ts | 2 +- 4 files changed, 143 insertions(+), 8 deletions(-) diff --git a/frontend/app/duck/sessions.ts b/frontend/app/duck/sessions.ts index f434faccc..3fd035974 100644 --- a/frontend/app/duck/sessions.ts +++ b/frontend/app/duck/sessions.ts @@ -93,14 +93,13 @@ const reducer = (state = initialState, action: IAction) => { const { sessions, total } = action.data; const list = sessions.map(s => new Session(s)); - console.log(sessions, list, action) return state .set('list', list) .set('sessionIds', list.map(({ sessionId }) => sessionId)) .set('favoriteList', list.filter(({ favorite }) => favorite)) .set('total', total); case FETCH_AUTOPLAY_LIST.SUCCESS: - let sessionIds = state.get('sessionIds'); + let sessionIds = state.get('sessionIds') as []; sessionIds = sessionIds.concat(action.data.map(i => i.sessionId + '')) return state.set('sessionIds', sessionIds.filter((i, index) => sessionIds.indexOf(i) === index )) case SET_AUTOPLAY_VALUES: { diff --git a/frontend/app/mstore/sessionStore.ts b/frontend/app/mstore/sessionStore.ts index 553f742b2..ef8d1312e 100644 --- a/frontend/app/mstore/sessionStore.ts +++ b/frontend/app/mstore/sessionStore.ts @@ -1,8 +1,13 @@ import { makeAutoObservable, observable, action } from 'mobx'; import { sessionService } from 'App/services'; import { filterMap } from 'Duck/search'; -import Session from './types/session'; +import Session from 'Types/session'; import Record, { LAST_7_DAYS } from 'Types/app/period'; +import Watchdog from "Types/watchdog"; +import ErrorStack from 'Types/session/errorStack'; +import { Location, InjectedEvent } from 'Types/session/event' +import { getDateRangeFromValue } from "App/dateRange"; +import { getRE, setSessionFilter, getSessionFilter, compareJsonObjects, cleanSessionFilters } from 'App/utils'; class UserFilter { endDate: number = new Date().getTime(); @@ -76,10 +81,47 @@ class DevTools { } } +const range = getDateRangeFromValue(LAST_7_DAYS); +const defaultDateFilters = { + url: '', + rangeValue: LAST_7_DAYS, + startDate: range.start.unix() * 1000, + endDate: range.end.unix() * 1000, +}; + export default class SessionStore { userFilter: UserFilter = new UserFilter(); devTools: DevTools = new DevTools(); + list: Session[] = [] + sessionIds: string[] = [] + current = new Session() + total = 0 + keyMap = {} + wdTypeCount = {} + favoriteList: Session[] = [] + activeTab = Watchdog({ name: 'All', type: 'all' }) + timezone = 'local' + errorStack: ErrorStack[] = [] + eventsIndex = [] + sourcemapUploaded = true + filteredEvents: InjectedEvent[] | null = null + eventsQuery = '' + showChatWindow = false + liveSessions: Session[] = [] + visitedEvents = [] + insights = [] + insightFilters = defaultDateFilters + host = '' + funnelPage = {} + timelinePointer = null + sessionPath = {} + lastPlayedSessionId: string + timeLineTooltip = { time: 0, offset: 0, isVisible: false, timeStr: '' } + createNoteTooltip = { time: 0, isVisible: false, isEdit: false, note: null } + previousId = '' + nextId = '' + constructor() { makeAutoObservable(this, { userFilter: observable, @@ -106,4 +148,79 @@ export default class SessionStore { }); }); } + + async fetchLiveSessions(params = {}) { + try { + const data = await sessionService.getLiveSessions(params); + this.liveSessions = data.map(session => new Session({ ...session, live: true })); + } catch (e) { + console.error(e) + } + } + + async fetchSessions(params = {}, force = false) { + try { + if (!force) { + const oldFilters = getSessionFilter(); + if (compareJsonObjects(oldFilters, cleanSessionFilters(params))) { + return; + } + } + setSessionFilter(cleanSessionFilters(params)); + const data = await sessionService.getSessions(params); + const list = data.sessions.map(s => new Session(s)) + + this.list = list; + this.total = data.total; + this.sessionIds = data.sessions.map(s => s.sessionId); + this.favoriteList = list.filter(s => s.favorite); + } catch(e) { + console.error(e) + } + } + + async fetchErrorStack(sessionId: string, errorId: string) { + try { + const data = await sessionService.getErrorStack(sessionId, errorId); + this.errorStack = data.trace.map(es => new ErrorStack(es)) + } catch (e) { + console.error(e) + } + } + + async fetchAutoplayList(params = {}) { + try { + setSessionFilter(cleanSessionFilters(params)); + const data = await sessionService.getAutoplayList(params); + const list = [...this.sessionIds, ...data.map(s => s.sessionId)] + this.sessionIds = list.filter((id, ind) => list.indexOf(id) === ind); + } catch(e) { + console.error(e) + } + } + + setAutoplayValues() { + const currentId = this.current.sessionId + const currentIndex = this.sessionIds.indexOf(currentId) + + this.previousId = this.sessionIds[currentIndex - 1] + this.nextId = this.sessionIds[currentIndex + 1] + } + + setEventQuery(filter: { query: string }) { + const events = this.current.events + const query = filter.query; + const searchRe = getRE(query, 'i') + + const filteredEvents = query ? events.filter( + (e) => searchRe.test(e.url) + || searchRe.test(e.value) + || searchRe.test(e.label) + || searchRe.test(e.type) + || (e.type === 'LOCATION' && searchRe.test('visited')) + ) : null; + + this.filteredEvents = filteredEvents + this.eventsQuery = query + } } diff --git a/frontend/app/services/SessionService.ts b/frontend/app/services/SessionService.ts index 6f59b3805..60e2a1958 100644 --- a/frontend/app/services/SessionService.ts +++ b/frontend/app/services/SessionService.ts @@ -1,4 +1,7 @@ import APIClient from 'App/api_client'; +import { ISession } from 'Types/session/session'; +import { IErrorStack } from 'Types/session/errorStack'; +import { clean as cleanParams } from 'App/api_client'; export default class SettingsService { private client: APIClient; @@ -22,7 +25,7 @@ export default class SettingsService { .then((response) => response.data || 0); } - getSessions(filter: any) { + getSessions(filter: any): Promise<{ sessions: ISession[], total: number }> { return this.client .post('/sessions/search', filter) .then(r => r.json()) @@ -30,7 +33,7 @@ export default class SettingsService { .catch(e => Promise.reject(e)) } - getSessionInfo(sessionId: string, isLive?: boolean): Promise> { + getSessionInfo(sessionId: string, isLive?: boolean): Promise { return this.client .get(isLive ? `/assist/sessions/${sessionId}` : `/sessions/${sessionId}`) .then((r) => r.json()) @@ -38,11 +41,27 @@ export default class SettingsService { .catch(console.error); } - getLiveSessions(filter: any) { + getLiveSessions(filter: any): Promise<{ sessions: ISession[] }> { return this.client - .post('/assist/sessions', filter) + .post('/assist/sessions', cleanParams(filter)) .then(r => r.json()) .then((response) => response.data || []) .catch(e => Promise.reject(e)) } + + getErrorStack(sessionId: string, errorId: string): Promise<{ trace: IErrorStack[] }> { + return this.client + .get(`/sessions/${sessionId}/errors/${errorId}/sourcemaps`) + .then(r => r.json()) + .then(j => j.data || {}) + .catch(e => Promise.reject(e)) + } + + getAutoplayList(params = {}): Promise<{ sessionId: string}[]> { + return this.client + .post('/sessions/search/ids', cleanParams(params)) + .then(r => r.json()) + .then(j => j.data || []) + .catch(e => Promise.reject(e)) + } } diff --git a/frontend/app/types/session/errorStack.ts b/frontend/app/types/session/errorStack.ts index 59e63d16f..912c102c7 100644 --- a/frontend/app/types/session/errorStack.ts +++ b/frontend/app/types/session/errorStack.ts @@ -1,4 +1,4 @@ -interface IErrorStack { +export interface IErrorStack { absPath?: string, filename?: string, function?: string, From c2d9a9768ae1f4ab006e1bcdebea3e7d9cab8570 Mon Sep 17 00:00:00 2001 From: sylenien Date: Thu, 5 Jan 2023 11:20:35 +0100 Subject: [PATCH 20/65] change(ui): widget types --- frontend/app/duck/sessions.ts | 11 +- frontend/app/mstore/sessionStore.ts | 192 +++++++++++++++++- frontend/app/services/SessionService.ts | 16 +- .../app/types/dashboard/callWithErrors.js | 13 -- .../types/dashboard/domBuildingTime copy.js | 14 -- .../types/dashboard/{helper.js => helper.ts} | 31 +-- .../types/dashboard/{index.js => index.ts} | 22 +- frontend/app/types/dashboard/resource.js | 16 -- frontend/app/types/dashboard/resource.ts | 24 +++ .../types/dashboard/resourceLoadingTime.js | 13 -- .../types/dashboard/resourceLoadingTime.ts | 21 ++ frontend/app/types/dashboard/responseTime.js | 14 -- frontend/app/types/dashboard/responseTime.ts | 20 ++ .../dashboard/responseTimeDistribution.js | 17 -- .../dashboard/responseTimeDistribution.ts | 27 +++ .../dashboard/sessionsImpactedByJSErrors.js | 14 -- .../dashboard/sessionsImpactedByJSErrors.ts | 20 ++ .../sessionsImpactedBySlowRequests.js | 14 -- .../sessionsImpactedBySlowRequests.ts | 20 ++ .../app/types/dashboard/sessionsPerBrowser.js | 13 -- .../app/types/dashboard/sessionsPerBrowser.ts | 22 ++ .../{slowestDomains.js => slowestDomains.ts} | 17 +- .../app/types/dashboard/slowestResources.js | 21 -- .../app/types/dashboard/slowestResources.ts | 19 ++ frontend/app/types/dashboard/speedLocation.js | 14 -- frontend/app/types/dashboard/speedLocation.ts | 20 ++ frontend/app/types/dashboard/timeToRender.js | 13 -- frontend/app/types/dashboard/timeToRender.ts | 20 ++ .../{topDomains.js => topDomains.ts} | 14 +- frontend/app/types/dashboard/topMetrics.js | 19 -- frontend/app/types/dashboard/topMetrics.ts | 17 ++ frontend/app/types/dashboard/userActivity.js | 10 - frontend/app/types/dashboard/userActivity.ts | 17 ++ frontend/app/types/session/session.ts | 2 +- 34 files changed, 483 insertions(+), 274 deletions(-) delete mode 100644 frontend/app/types/dashboard/callWithErrors.js delete mode 100644 frontend/app/types/dashboard/domBuildingTime copy.js rename frontend/app/types/dashboard/{helper.js => helper.ts} (54%) rename frontend/app/types/dashboard/{index.js => index.ts} (96%) delete mode 100644 frontend/app/types/dashboard/resource.js create mode 100644 frontend/app/types/dashboard/resource.ts delete mode 100644 frontend/app/types/dashboard/resourceLoadingTime.js create mode 100644 frontend/app/types/dashboard/resourceLoadingTime.ts delete mode 100644 frontend/app/types/dashboard/responseTime.js create mode 100644 frontend/app/types/dashboard/responseTime.ts delete mode 100644 frontend/app/types/dashboard/responseTimeDistribution.js create mode 100644 frontend/app/types/dashboard/responseTimeDistribution.ts delete mode 100644 frontend/app/types/dashboard/sessionsImpactedByJSErrors.js create mode 100644 frontend/app/types/dashboard/sessionsImpactedByJSErrors.ts delete mode 100644 frontend/app/types/dashboard/sessionsImpactedBySlowRequests.js create mode 100644 frontend/app/types/dashboard/sessionsImpactedBySlowRequests.ts delete mode 100644 frontend/app/types/dashboard/sessionsPerBrowser.js create mode 100644 frontend/app/types/dashboard/sessionsPerBrowser.ts rename frontend/app/types/dashboard/{slowestDomains.js => slowestDomains.ts} (51%) delete mode 100644 frontend/app/types/dashboard/slowestResources.js create mode 100644 frontend/app/types/dashboard/slowestResources.ts delete mode 100644 frontend/app/types/dashboard/speedLocation.js create mode 100644 frontend/app/types/dashboard/speedLocation.ts delete mode 100644 frontend/app/types/dashboard/timeToRender.js create mode 100644 frontend/app/types/dashboard/timeToRender.ts rename frontend/app/types/dashboard/{topDomains.js => topDomains.ts} (50%) delete mode 100644 frontend/app/types/dashboard/topMetrics.js create mode 100644 frontend/app/types/dashboard/topMetrics.ts delete mode 100644 frontend/app/types/dashboard/userActivity.js create mode 100644 frontend/app/types/dashboard/userActivity.ts diff --git a/frontend/app/duck/sessions.ts b/frontend/app/duck/sessions.ts index 3fd035974..79298de87 100644 --- a/frontend/app/duck/sessions.ts +++ b/frontend/app/duck/sessions.ts @@ -124,7 +124,7 @@ const reducer = (state = initialState, action: IAction) => { return state.set('filteredEvents', filteredEvents).set('eventsQuery', query); } case FETCH.SUCCESS: { - // TODO: more common.. or TEMP + // TODO: more common.. or TEMP filters', 'appliedFilter const events = action.filter.events; const session = new Session(action.data); @@ -191,17 +191,10 @@ const reducer = (state = initialState, action: IAction) => { }; return state.update('list', (list: Session[]) => list.sort(comparator)).update('favoriteList', (list: Session[]) => list.sort(comparator)); } - case REDEFINE_TARGET: { - // TODO: update for list - const { label, path } = action.target; - return state.updateIn(['current', 'events'], (list) => - list.map((event) => (event.target && event.target.path === path ? event.setIn(['target', 'label'], label) : event)) - ); - } case SET_ACTIVE_TAB: const allList = action.tab.type === 'all' ? state.get('list') : state.get('list').filter((s) => s.issueTypes.includes(action.tab.type)); - return state.set('activeTab', action.tab).set('sessionIds', allList.map(({ sessionId }) => sessionId).toJS()); + return state.set('activeTab', action.tab).set('sessionIds', allList.map(({ sessionId }) => sessionId)); case SET_TIMEZONE: return state.set('timezone', action.timezone); case TOGGLE_CHAT_WINDOW: diff --git a/frontend/app/mstore/sessionStore.ts b/frontend/app/mstore/sessionStore.ts index ef8d1312e..21c54de22 100644 --- a/frontend/app/mstore/sessionStore.ts +++ b/frontend/app/mstore/sessionStore.ts @@ -8,6 +8,8 @@ import ErrorStack from 'Types/session/errorStack'; import { Location, InjectedEvent } from 'Types/session/event' import { getDateRangeFromValue } from "App/dateRange"; import { getRE, setSessionFilter, getSessionFilter, compareJsonObjects, cleanSessionFilters } from 'App/utils'; +import store from 'App/store' +import { Note } from "App/services/NotesService"; class UserFilter { endDate: number = new Date().getTime(); @@ -110,12 +112,13 @@ export default class SessionStore { showChatWindow = false liveSessions: Session[] = [] visitedEvents = [] - insights = [] + insights: any[] = [] insightFilters = defaultDateFilters host = '' funnelPage = {} - timelinePointer = null - sessionPath = {} + /** @Deprecated */ + timelinePointer = {} + sessionPath = {} lastPlayedSessionId: string timeLineTooltip = { time: 0, offset: 0, isVisible: false, timeStr: '' } createNoteTooltip = { time: 0, isVisible: false, isEdit: false, note: null } @@ -174,7 +177,43 @@ export default class SessionStore { this.total = data.total; this.sessionIds = data.sessions.map(s => s.sessionId); this.favoriteList = list.filter(s => s.favorite); - } catch(e) { + } catch (e) { + console.error(e) + } + } + + async fetchSessionInfo(sessionId: string, isLive = false) { + try { + const { events } = store.getState().getIn(['filters', 'appliedFilter']); + const data = await sessionService.getSessionInfo(sessionId, isLive) + const session = new Session(data) + + const matching: number[] = []; + const visitedEvents: Location[] = []; + const tmpMap: Set = new Set(); + + session.events.forEach((event) => { + if (event.type === 'LOCATION' && !tmpMap.has(event.url)) { + tmpMap.add(event.url); + visitedEvents.push(event); + } + }); + + + (events as {}[]).forEach(({ key, operator, value }: any) => { + session.events.forEach((e, index) => { + if (key === e.type) { + const val = e.type === 'LOCATION' ? e.url : e.value; + if (operator === 'is' && value === val) { + matching.push(index); + } + if (operator === 'contains' && val.includes(value)) { + matching.push(index); + } + } + }); + }); + } catch (e) { console.error(e) } } @@ -194,7 +233,7 @@ export default class SessionStore { const data = await sessionService.getAutoplayList(params); const list = [...this.sessionIds, ...data.map(s => s.sessionId)] this.sessionIds = list.filter((id, ind) => list.indexOf(id) === ind); - } catch(e) { + } catch (e) { console.error(e) } } @@ -221,6 +260,147 @@ export default class SessionStore { ) : null; this.filteredEvents = filteredEvents - this.eventsQuery = query + this.eventsQuery = query } + + async toggleFavorite(id: string) { + try { + const r = await sessionService.toggleFavorite(id) + if (r.success) { + const list = this.list; + const current = this.current; + const sessionIdx = list.findIndex(({ sessionId }) => sessionId === id); + const session = list[sessionIdx] + const wasInFavorite = this.favoriteList.findIndex(({ sessionId }) => sessionId === id) > -1; + + if (session && !wasInFavorite) { + session.favorite = true + this.list[sessionIdx] = session + } + if (current.sessionId === id) { + this.current.favorite = !wasInFavorite + } + + if (session) { + if (wasInFavorite) { + this.favoriteList = this.favoriteList.filter(({ sessionId }) => sessionId !== id) + } else { + this.favoriteList.push(session) + } + } + } else { + console.error(r) + } + } catch (e) { + console.error(e) + } + } + + sortSessions(sortKey: string, sign: number) { + const comparator = (s1: Session, s2: Session) => { + // @ts-ignore + let diff = s1[sortKey] - s2[sortKey]; + diff = diff === 0 ? s1.startedAt - s2.startedAt : diff; + return sign * diff; + }; + + this.list = this.list.sort(comparator) + this.favoriteList = this.favoriteList.sort(comparator) + return; + } + + setActiveTab(tab: { type: string }) { + const list = tab.type === 'all' + ? this.list : this.list.filter(s => s.issueTypes.includes(tab.type)) + + // @ts-ignore + this.activeTab = tab + this.sessionIds = list.map(s => s.sessionId) + } + + setTimezone(tz: string) { + this.timezone = tz; + } + + toggleChatWindow(isActive: boolean) { + this.showChatWindow = isActive + } + + async fetchInsights(filters = {}) { + try { + const data = await sessionService.getClickMap(filters) + + this.insights = data + } catch (e) { + console.error(e) + } + } + + setFunnelPage(page = {}) { + this.funnelPage = page || false + } + + /* @deprecated */ + setTimelinePointer(pointer: {}) { + this.timelinePointer = pointer + } + + setTimelineTooltip(tp: { time: number, offset: number, isVisible: boolean, timeStr: string }) { + this.timeLineTooltip = tp + } + + setEditNoteTooltip(tp: { time: number, isVisible: boolean, isEdit: boolean, note: any }) { + this.createNoteTooltip = tp + } + + filterOutNote(noteId: string) { + const current = this.current + + current.notesWithEvents = current.notesWithEvents.filter(n => { + if ('noteId' in item) { + return item.noteId !== noteId + } + return true + }) + + this.current = current + } + + addNote(note: Note) { + const current = this.current + + current.notesWithEvents.push(note) + current.notesWithEvents.sort((a,b) => { + const aTs = a.time || a.timestamp + const bTs = b.time || b.timestamp + + return aTs - bTs + }) + + this.current = current + } + + updateNote(note: Note) { + const noteIndex = this.current.notesWithEvents.findIndex(item => { + if ('noteId' in item) { + return item.noteId === note.noteId + } + return false + }) + + this.current.notesWithEvents[noteIndex] = note + } + + setSessionPath(path = {}) { + this.sessionPath = path + } + + setLastPlayed(sessionId: string) { + const list = this.list + const sIndex = list.findIndex((s) => s.sessionId === sessionId) + if (sIndex !== -1) { + this.list[sIndex].viewed = true + } + } + } diff --git a/frontend/app/services/SessionService.ts b/frontend/app/services/SessionService.ts index 60e2a1958..6537a2dbb 100644 --- a/frontend/app/services/SessionService.ts +++ b/frontend/app/services/SessionService.ts @@ -57,11 +57,25 @@ export default class SettingsService { .catch(e => Promise.reject(e)) } - getAutoplayList(params = {}): Promise<{ sessionId: string}[]> { + getAutoplayList(params = {}): Promise<{ sessionId: string }[]> { return this.client .post('/sessions/search/ids', cleanParams(params)) .then(r => r.json()) .then(j => j.data || []) .catch(e => Promise.reject(e)) } + + toggleFavorite(sessionId: string): Promise { + return this.client + .get(`/sessions/${sessionId}/favorite`) + .catch(Promise.reject) + } + + getClickMap(params = {}): Promise { + return this.client + .post('/heatmaps/url', params) + .then(r => r.json()) + .then(j => j.data || []) + .catch(Promise.reject) + } } diff --git a/frontend/app/types/dashboard/callWithErrors.js b/frontend/app/types/dashboard/callWithErrors.js deleted file mode 100644 index 3c3aefd7c..000000000 --- a/frontend/app/types/dashboard/callWithErrors.js +++ /dev/null @@ -1,13 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - method: '', - urlHostpath: '', - allRequests: '', - '4xx': '', - '5xx': '' -}, { - // fromJS: pm => ({ - // ...pm, - // }), -}); \ No newline at end of file diff --git a/frontend/app/types/dashboard/domBuildingTime copy.js b/frontend/app/types/dashboard/domBuildingTime copy.js deleted file mode 100644 index 258902f60..000000000 --- a/frontend/app/types/dashboard/domBuildingTime copy.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const DomBuildingTime = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof DomBuildingTime) return data; - return new DomBuildingTime(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/helper.js b/frontend/app/types/dashboard/helper.ts similarity index 54% rename from frontend/app/types/dashboard/helper.js rename to frontend/app/types/dashboard/helper.ts index f6e819da8..97a7c17e8 100644 --- a/frontend/app/types/dashboard/helper.js +++ b/frontend/app/types/dashboard/helper.ts @@ -1,30 +1,9 @@ -export const getDayStartAndEndTimestamps = (date) => { - const start = moment(date).startOf('day').valueOf(); - const end = moment(date).endOf('day').valueOf(); - return { start, end }; -}; - -// const getPerformanceDensity = (period) => { -// switch (period) { -// case HALF_AN_HOUR: -// return 30; -// case WEEK: -// return 84; -// case MONTH: -// return 90; -// case DAY: -// return 48; -// default: -// return 48; -// } -// }; - const DAY = 1000 * 60 * 60 * 24; const WEEK = DAY * 8; -const startWithZero = num => (num < 10 ? `0${ num }` : `${ num }`); +const startWithZero = (num: number) => (num < 10 ? `0${ num }` : `${ num }`); const weekdays = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]; -// const months = [ "January", "February" ]; + export const getTimeString = (ts, period) => { const date = new Date(ts); const diff = period.endTimestamp - period.startTimestamp; @@ -41,11 +20,11 @@ export const getTimeString = (ts, period) => { export const getChartFormatter = period => (data = []) => data.map(({ timestamp, ...rest }) => ({ time: getTimeString(timestamp, period), ...rest, timestamp })); -export const getStartAndEndTimestampsByDensity = (current, start, end, density) => { +export const getStartAndEndTimestampsByDensity = (current: number, start: number, end: number, density: number) => { const diff = end - start; const step = Math.floor(diff / density); const currentIndex = Math.floor((current - start) / step); - const startTimestamp = parseInt(start + currentIndex * step); - const endTimestamp = parseInt(startTimestamp + step); + const startTimestamp = start + currentIndex * step; + const endTimestamp = startTimestamp + step; return { startTimestamp, endTimestamp }; }; \ No newline at end of file diff --git a/frontend/app/types/dashboard/index.js b/frontend/app/types/dashboard/index.ts similarity index 96% rename from frontend/app/types/dashboard/index.js rename to frontend/app/types/dashboard/index.ts index 670f44a2c..3e2ff0e74 100644 --- a/frontend/app/types/dashboard/index.js +++ b/frontend/app/types/dashboard/index.ts @@ -1,4 +1,4 @@ -import { Map, List } from 'immutable'; +import { List } from 'immutable'; import Session from 'Types/session'; import { camelCased } from 'App/utils'; @@ -29,7 +29,6 @@ import SlowestDomains from './slowestDomains'; import ResourceLoadingTime from './resourceLoadingTime'; import Image from './image'; -import Resource from './resource'; import Err from './err'; import MissingResource from './missingResource'; @@ -58,7 +57,7 @@ export const WIDGET_LIST = [{ name: "User Activity", description: 'The average user feedback score, average number of visited pages per session and average session duration.', thumb: 'user_activity.png', - dataWrapper: UserActivity, + dataWrapper: data => new UserActivity(data), }, { key: "pageMetrics", name: "Page Metrics", @@ -85,13 +84,6 @@ export const WIDGET_LIST = [{ return i2.avgDuration - i1.avgDuration; }), }, - // { - // key: "errorsTrend", - // name: "Most Impactful Errors", - // description: 'List of errors and exceptions, sorted by the number of impacted sessions.', - // thumb: 'most_Impactful_errors.png', - // dataWrapper: list => List(list).map(Err), - // }, { key: "sessionsFrustration", name: "Recent Frustrations", @@ -114,19 +106,13 @@ export const WIDGET_LIST = [{ type: 'resources', dataWrapper: list => List(list).map(MissingResource), }, - // { - // key: "sessionsPerformance", - // name: "Recent Performance Issues", - // description: "", - // dataWrapper: list => List(list).map(Session), - // } { key: "slowestResources", name: "Slowest Resources", description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', thumb: 'na.png', type: 'resources', - dataWrapper: list => List(list).map(SlowestResources) + dataWrapper: list => list.map(res => new SlowestResources(res)) }, { key: "overview", @@ -138,8 +124,6 @@ export const WIDGET_LIST = [{ .map(item => OverviewWidget({ key: camelCased(item.key), ...item.data})) .map(widget => widget.update("chart", getChartFormatter(period))) } - // dataWrapper: (p, period) => List(p) - // .update("chart", getChartFormatter(period)) }, { key: "speedLocation", diff --git a/frontend/app/types/dashboard/resource.js b/frontend/app/types/dashboard/resource.js deleted file mode 100644 index 341ef8e47..000000000 --- a/frontend/app/types/dashboard/resource.js +++ /dev/null @@ -1,16 +0,0 @@ -import Record from 'Types/Record'; - -const getName = (url = '') => url.split('/').filter(part => !!part).pop(); - -export default Record({ - avgDuration: undefined, - sessions: undefined, - chart: [], - url: '', - name: '', -}, { - fromJS: (resource) => ({ - ...resource, - name: getName(resource.url), - }) -}); diff --git a/frontend/app/types/dashboard/resource.ts b/frontend/app/types/dashboard/resource.ts new file mode 100644 index 000000000..c35b00765 --- /dev/null +++ b/frontend/app/types/dashboard/resource.ts @@ -0,0 +1,24 @@ +const getName = (url = '') => url.split('/').filter(part => !!part).pop(); + +interface IResource { + avgDuration: number; + sessions: any[]; + chart: any[]; + url: string; + name: string; +} + +export default class Resource { + avgDuration: IResource["avgDuration"]; + sessions: IResource["sessions"]; + chart: IResource["chart"] = []; + url: IResource["url"] = ''; + name: IResource["name"] = ''; + + constructor(data: IResource) { + Object.assign(this, { + ...data, + name: getName(data.url), + }) + } +} \ No newline at end of file diff --git a/frontend/app/types/dashboard/resourceLoadingTime.js b/frontend/app/types/dashboard/resourceLoadingTime.js deleted file mode 100644 index cc23bdae9..000000000 --- a/frontend/app/types/dashboard/resourceLoadingTime.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const ResourceLoadingTime = Record({ - avg: undefined, - timestamp: undefined -}); - -function fromJS(resourceLoadingTime = {}) { - if (resourceLoadingTime instanceof ResourceLoadingTime) return resourceLoadingTime; - return new ResourceLoadingTime(resourceLoadingTime); -} - -export default fromJS; diff --git a/frontend/app/types/dashboard/resourceLoadingTime.ts b/frontend/app/types/dashboard/resourceLoadingTime.ts new file mode 100644 index 000000000..65a9114a9 --- /dev/null +++ b/frontend/app/types/dashboard/resourceLoadingTime.ts @@ -0,0 +1,21 @@ + +interface IResourceLoadingTime { + avg: number; + timestamp: number; +} + +class ResourceLoadingTime { + avg: IResourceLoadingTime["avg"]; + timestamp: IResourceLoadingTime["timestamp"]; + + constructor(data: IResourceLoadingTime) { + Object.assign(this, data) + } +} + +function fromJS(resourceLoadingTime = {}) { + if (resourceLoadingTime instanceof ResourceLoadingTime) return resourceLoadingTime; + return new ResourceLoadingTime(resourceLoadingTime); +} + +export default fromJS; diff --git a/frontend/app/types/dashboard/responseTime.js b/frontend/app/types/dashboard/responseTime.js deleted file mode 100644 index 961eda42b..000000000 --- a/frontend/app/types/dashboard/responseTime.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const ResponseTime = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof ResponseTime) return data; - return new ResponseTime(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/responseTime.ts b/frontend/app/types/dashboard/responseTime.ts new file mode 100644 index 000000000..774440008 --- /dev/null +++ b/frontend/app/types/dashboard/responseTime.ts @@ -0,0 +1,20 @@ +interface IResponseTime { + avg: number; + chart: any[]; +} + +class ResponseTime { + avg: IResponseTime["avg"] + chart: IResponseTime["chart"] = [] + + constructor(data: IResponseTime) { + Object.assign(this, data) + } +} + +function fromJS(data = {}) { + if (data instanceof ResponseTime) return data; + return new ResponseTime(data); +} + +export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/responseTimeDistribution.js b/frontend/app/types/dashboard/responseTimeDistribution.js deleted file mode 100644 index a53f3555f..000000000 --- a/frontend/app/types/dashboard/responseTimeDistribution.js +++ /dev/null @@ -1,17 +0,0 @@ -import { Record } from 'immutable'; - -const ResponseTimeDistribution = Record({ - chart: [], - avg: undefined, - percentiles: [], - extremeValues: [], - total: undefined -}); - - -function fromJS(data = {}) { - if (data instanceof ResponseTimeDistribution) return data; - return new ResponseTimeDistribution(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/responseTimeDistribution.ts b/frontend/app/types/dashboard/responseTimeDistribution.ts new file mode 100644 index 000000000..76a96a1b2 --- /dev/null +++ b/frontend/app/types/dashboard/responseTimeDistribution.ts @@ -0,0 +1,27 @@ +interface IResponseTimeDistribution { + chart: any[], + avg: number, + percentiles: number[], + extremeValues: number[], + total: number +} + +class ResponseTimeDistribution { + chart: IResponseTimeDistribution["chart"] = [] + avg: IResponseTimeDistribution["avg"] + percentiles: IResponseTimeDistribution["percentiles"] = [] + extremeValues: IResponseTimeDistribution["extremeValues"] = [] + total: IResponseTimeDistribution["total"] + + constructor(data: IResponseTimeDistribution) { + Object.assign(this, data) + } +} + + +function fromJS(data = {}) { + if (data instanceof ResponseTimeDistribution) return data; + return new ResponseTimeDistribution(data); +} + +export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/sessionsImpactedByJSErrors.js b/frontend/app/types/dashboard/sessionsImpactedByJSErrors.js deleted file mode 100644 index c0368744d..000000000 --- a/frontend/app/types/dashboard/sessionsImpactedByJSErrors.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const SessionsImpactedByJSErrors = Record({ - errorsCount: undefined, - chart: [] -}); - - -function fromJS(data = {}) { - if (data instanceof SessionsImpactedByJSErrors) return data; - return new SessionsImpactedByJSErrors(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/sessionsImpactedByJSErrors.ts b/frontend/app/types/dashboard/sessionsImpactedByJSErrors.ts new file mode 100644 index 000000000..843ed6b9a --- /dev/null +++ b/frontend/app/types/dashboard/sessionsImpactedByJSErrors.ts @@ -0,0 +1,20 @@ +interface ISessionsImpactedByJSErrors { + errorsCount: number; + chart: any[]; +} + +class SessionsImpactedByJSErrors { + errorsCount: ISessionsImpactedByJSErrors["errorsCount"]; + chart: ISessionsImpactedByJSErrors["chart"]; + + constructor(data: ISessionsImpactedByJSErrors) { + Object.assign(this, data) + } +} + +function fromJS(data = {}) { + if (data instanceof SessionsImpactedByJSErrors) return data; + return new SessionsImpactedByJSErrors(data); +} + +export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/sessionsImpactedBySlowRequests.js b/frontend/app/types/dashboard/sessionsImpactedBySlowRequests.js deleted file mode 100644 index 4d5d5fc61..000000000 --- a/frontend/app/types/dashboard/sessionsImpactedBySlowRequests.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const SessionsImpactedBySlowRequests = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof SessionsImpactedBySlowRequests) return data; - return new SessionsImpactedBySlowRequests(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/sessionsImpactedBySlowRequests.ts b/frontend/app/types/dashboard/sessionsImpactedBySlowRequests.ts new file mode 100644 index 000000000..6b905fc26 --- /dev/null +++ b/frontend/app/types/dashboard/sessionsImpactedBySlowRequests.ts @@ -0,0 +1,20 @@ +interface ISessionsSlowRequests { + avg: number; + chart: any[]; +} + +class SessionsImpactedBySlowRequests { + avg: ISessionsSlowRequests["avg"]; + chart: ISessionsSlowRequests["chart"] = []; + + constructor(data: ISessionsSlowRequests) { + Object.assign(this, data) + } +} + +function fromJS(data = {}) { + if (data instanceof SessionsImpactedBySlowRequests) return data; + return new SessionsImpactedBySlowRequests(data); +} + +export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/sessionsPerBrowser.js b/frontend/app/types/dashboard/sessionsPerBrowser.js deleted file mode 100644 index cd8662b13..000000000 --- a/frontend/app/types/dashboard/sessionsPerBrowser.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const SessionsPerBrowser = Record({ - count: undefined, - chart: [], -}); - -function fromJS(sessionsPerBrowser = {}) { - if (sessionsPerBrowser instanceof SessionsPerBrowser) return sessionsPerBrowser; - return new SessionsPerBrowser({...sessionsPerBrowser, avg: Math.round(sessionsPerBrowser.avg)}); -} - -export default fromJS; diff --git a/frontend/app/types/dashboard/sessionsPerBrowser.ts b/frontend/app/types/dashboard/sessionsPerBrowser.ts new file mode 100644 index 000000000..ec7a08c76 --- /dev/null +++ b/frontend/app/types/dashboard/sessionsPerBrowser.ts @@ -0,0 +1,22 @@ +interface ISessionsPerBrowser { + count?: number, + chart?: any[], + avg: number, +} + +class SessionsPerBrowser { + count: ISessionsPerBrowser["count"] + chart: ISessionsPerBrowser["chart"] = [] + avg: ISessionsPerBrowser["avg"] + + constructor(data: ISessionsPerBrowser) { + Object.assign(this, data) + } +} + +function fromJS(sessionsPerBrowser = {}) { + if (sessionsPerBrowser instanceof SessionsPerBrowser) return sessionsPerBrowser; + return new SessionsPerBrowser({...sessionsPerBrowser, avg: Math.round(sessionsPerBrowser.avg)}); +} + +export default fromJS; diff --git a/frontend/app/types/dashboard/slowestDomains.js b/frontend/app/types/dashboard/slowestDomains.ts similarity index 51% rename from frontend/app/types/dashboard/slowestDomains.js rename to frontend/app/types/dashboard/slowestDomains.ts index fc15ed831..9dbe09870 100644 --- a/frontend/app/types/dashboard/slowestDomains.js +++ b/frontend/app/types/dashboard/slowestDomains.ts @@ -1,9 +1,18 @@ import { Record } from 'immutable'; -const SlowestDomains = Record({ - partition: [], - avg: undefined, -}); +interface ISlowestDomains { + partition?: string[]; + avg: number; +} + +class SlowestDomains { + partition: ISlowestDomains["partition"] = []; + avg: ISlowestDomains["avg"]; + + constructor(data: ISlowestDomains) { + Object.assign(this, data) + } +} function fromJS(slowestDomains = {}) { if (slowestDomains instanceof SlowestDomains) return slowestDomains; diff --git a/frontend/app/types/dashboard/slowestResources.js b/frontend/app/types/dashboard/slowestResources.js deleted file mode 100644 index 54e00984b..000000000 --- a/frontend/app/types/dashboard/slowestResources.js +++ /dev/null @@ -1,21 +0,0 @@ -import Record from 'Types/Record'; -import { fileType, fileName } from 'App/utils'; - -const validTypes = ['jpg', 'jpeg', 'js', 'css', 'woff', 'css', 'png', 'gif', 'svg'] - -export default Record({ - avg: 0, - url: '', - type: '', - name: '', - chart: [], -}, { - fromJS: pm => { - const type = fileType(pm.url).toLowerCase(); - return { - ...pm, - // type: validTypes.includes(type) ? type : 'n/a', - // fileName: fileName(pm.url) - } - }, -}); \ No newline at end of file diff --git a/frontend/app/types/dashboard/slowestResources.ts b/frontend/app/types/dashboard/slowestResources.ts new file mode 100644 index 000000000..57a802ac6 --- /dev/null +++ b/frontend/app/types/dashboard/slowestResources.ts @@ -0,0 +1,19 @@ +interface ISlowestResources { + avg: number; + url: string; + type: string; + name: string; + chart: any[] +} + +export default class SlowestResources { + avg: ISlowestResources["avg"]; + url: ISlowestResources["url"]; + type: ISlowestResources["type"]; + name: ISlowestResources["name"]; + chart: ISlowestResources["chart"]; + + constructor(data: ISlowestResources) { + Object.assign(this, data); + } +} \ No newline at end of file diff --git a/frontend/app/types/dashboard/speedLocation.js b/frontend/app/types/dashboard/speedLocation.js deleted file mode 100644 index f1774eba0..000000000 --- a/frontend/app/types/dashboard/speedLocation.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const SpeedLocation = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof SpeedLocation) return data; - return new SpeedLocation(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/speedLocation.ts b/frontend/app/types/dashboard/speedLocation.ts new file mode 100644 index 000000000..ee9ff7cd3 --- /dev/null +++ b/frontend/app/types/dashboard/speedLocation.ts @@ -0,0 +1,20 @@ +interface ISpeedLocation { + avg?: number + chart?: any[] +} + +class SpeedLocation { + avg?: ISpeedLocation["avg"] + chart?: ISpeedLocation["chart"] + + constructor(data: ISpeedLocation) { + Object.assign(this, data) + } +} + +function fromJS(data = {}) { + if (data instanceof SpeedLocation) return data; + return new SpeedLocation(data); +} + +export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/timeToRender.js b/frontend/app/types/dashboard/timeToRender.js deleted file mode 100644 index e07521498..000000000 --- a/frontend/app/types/dashboard/timeToRender.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const TimeToRender = Record({ - avg: undefined, - chart: [], -}); - -function fromJS(data = {}) { - if (data instanceof TimeToRender) return data; - return new TimeToRender(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/timeToRender.ts b/frontend/app/types/dashboard/timeToRender.ts new file mode 100644 index 000000000..d04b40986 --- /dev/null +++ b/frontend/app/types/dashboard/timeToRender.ts @@ -0,0 +1,20 @@ +interface ITimeToRender { + avg?: number + chart?: any[] +} + +class TimeToRender { + avg: ITimeToRender["avg"] + chart: ITimeToRender["chart"] = [] + + constructor(data: ITimeToRender) { + Object.assign(this, data) + } +} + +function fromJS(data = {}) { + if (data instanceof TimeToRender) return data; + return new TimeToRender(data); +} + +export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/topDomains.js b/frontend/app/types/dashboard/topDomains.ts similarity index 50% rename from frontend/app/types/dashboard/topDomains.js rename to frontend/app/types/dashboard/topDomains.ts index fa92db397..dfbb2af7e 100644 --- a/frontend/app/types/dashboard/topDomains.js +++ b/frontend/app/types/dashboard/topDomains.ts @@ -1,8 +1,16 @@ import { Record } from 'immutable'; -const TopDomains = Record({ - chart: [] -}); +interface ITopDomains { + chart?: any[] +} + +class TopDomains { + chart: ITopDomains["chart"] = [] + + constructor(data: ITopDomains) { + this.chart = data.chart + } +} function fromJS(data = {}) { if (data instanceof TopDomains) return data; diff --git a/frontend/app/types/dashboard/topMetrics.js b/frontend/app/types/dashboard/topMetrics.js deleted file mode 100644 index 221f65d6c..000000000 --- a/frontend/app/types/dashboard/topMetrics.js +++ /dev/null @@ -1,19 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - avgResponseTime: 0, - requestsCount: 0, - avgTimeTilFirstBite: 0, - avgDomCompleteTime: 0 -}, { - // fromJS: aa => ({ - // avgPageLoad: aa.avgDom, - // avgPageLoadProgress: aa.avgDomProgress, - // avgImgLoad: aa.avgLoad, - // avgImgLoadProgress: aa.avgLoadProgress, - // avgReqLoad: aa.avgFirstPixel, - // avgReqLoadProgress: aa.avgFirstPixelProgress, - // ...aa, - // }), -}); - diff --git a/frontend/app/types/dashboard/topMetrics.ts b/frontend/app/types/dashboard/topMetrics.ts new file mode 100644 index 000000000..59d7c89b7 --- /dev/null +++ b/frontend/app/types/dashboard/topMetrics.ts @@ -0,0 +1,17 @@ +interface ITopMetrics { + avgResponseTime: number + requestsCount: number + avgTimeTilFirstBite: number + avgDomCompleteTime: number +} + +export default class TopMetrics { + avgResponseTime: ITopMetrics["avgResponseTime"] = 0 + requestsCount: ITopMetrics["requestsCount"] = 0 + avgTimeTilFirstBite: ITopMetrics["avgTimeTilFirstBite"] = 0 + avgDomCompleteTime: ITopMetrics["avgDomCompleteTime"] = 0 + + constructor(data: ITopMetrics) { + Object.assign(this, data) + } +} \ No newline at end of file diff --git a/frontend/app/types/dashboard/userActivity.js b/frontend/app/types/dashboard/userActivity.js deleted file mode 100644 index 788349337..000000000 --- a/frontend/app/types/dashboard/userActivity.js +++ /dev/null @@ -1,10 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - avgVisitedPages: undefined, - avgVisitedPagesProgress: undefined, - avgDuration: undefined, - avgDurationProgress: undefined, - avgEmotionalRating: undefined, - avgEmotionalRatingProgress: undefined, -}); \ No newline at end of file diff --git a/frontend/app/types/dashboard/userActivity.ts b/frontend/app/types/dashboard/userActivity.ts new file mode 100644 index 000000000..835fa4b8a --- /dev/null +++ b/frontend/app/types/dashboard/userActivity.ts @@ -0,0 +1,17 @@ +interface IUserActivity { + avgVisitedPages: number; + avgVisitedPagesProgress: number; + avgDuration: number; + avgDurationProgress: number; +} + +export default class UserActivity { + avgVisitedPages: IUserActivity["avgVisitedPages"] + avgVisitedPagesProgress: IUserActivity["avgDurationProgress"] + avgDuration: IUserActivity["avgDuration"] + avgDurationProgress: IUserActivity["avgDurationProgress"] + + constructor(activity: IUserActivity) { + Object.assign(this, activity) + } +} \ No newline at end of file diff --git a/frontend/app/types/session/session.ts b/frontend/app/types/session/session.ts index d0bf42936..4dcef8d49 100644 --- a/frontend/app/types/session/session.ts +++ b/frontend/app/types/session/session.ts @@ -60,7 +60,7 @@ export interface ISession { eventsCount: number, pagesCount: number, errorsCount: number, - issueTypes: [], + issueTypes: string[], issues: [], referrer: string | null, userDeviceHeapSize: number, From 0ad417d0dcd713dcc4bbb29a029dc8ddfdd50c93 Mon Sep 17 00:00:00 2001 From: sylenien Date: Thu, 5 Jan 2023 17:34:40 +0100 Subject: [PATCH 21/65] change(ui): remove unused files --- .../DashboardHeader/DashboardHeader.js | 40 -- .../DashboardHeader.module.css | 27 - .../Dashboard/DashboardHeader/index.js | 1 - .../Dashboard/Widgets/ApplicationActivity.js | 43 -- .../BreakdownOfLoadedResources.js | 60 -- .../BreakdownOfLoadedResources/index.js | 1 - .../BusiestTimeOfTheDay.js | 43 -- .../Widgets/BusiestTimeOfTheDay/Chart.js | 14 - .../Widgets/BusiestTimeOfTheDay/ImageInfo.js | 29 - .../BusiestTimeOfTheDay/imageInfo.module.css | 39 -- .../Widgets/BusiestTimeOfTheDay/index.js | 1 - .../Widgets/CallWithErrors/CallWithErrors.js | 79 --- .../Dashboard/Widgets/CallWithErrors/Chart.js | 14 - .../Widgets/CallWithErrors/ImageInfo.js | 13 - .../Widgets/CallWithErrors/MethodType.js | 10 - .../CallWithErrors/callWithErrors.module.css | 22 - .../CallWithErrors/imageInfo.module.css | 39 -- .../Dashboard/Widgets/CallWithErrors/index.js | 1 - .../Widgets/CallsErrors4xx/CallsErrors4xx.js | 80 --- .../Dashboard/Widgets/CallsErrors4xx/index.js | 1 - .../Widgets/CallsErrors5xx/CallsErrors5xx.js | 85 --- .../Dashboard/Widgets/CallsErrors5xx/index.js | 1 - .../Dashboard/Widgets/CpuLoad/CpuLoad.js | 69 --- .../Dashboard/Widgets/CpuLoad/index.js | 1 - .../Dashboard/Widgets/Crashes/Crashes.js | 68 --- .../Dashboard/Widgets/Crashes/index.js | 1 - .../CustomMetricWidget.module.css | 6 - .../CustomMetricWidget/CustomMetricWidget.tsx | 207 ------- .../CustomMetricWidget/index.ts | 1 - .../DomBuildingTime/DomBuildingTime.js | 103 ---- .../Widgets/DomBuildingTime/index.js | 1 - .../Dashboard/Widgets/Errors/Errors.js | 54 -- .../Widgets/Errors/errors.module.css | 4 - .../Dashboard/Widgets/Errors/index.js | 1 - .../Widgets/ErrorsByOrigin/ErrorsByOrigin.js | 58 -- .../Dashboard/Widgets/ErrorsByOrigin/index.js | 1 - .../Widgets/ErrorsByType/ErrorsByType.js | 63 -- .../Dashboard/Widgets/ErrorsByType/index.js | 1 - .../Dashboard/Widgets/ErrorsPerDomain/Bar.js | 18 - .../Widgets/ErrorsPerDomain/Bar.module.css | 6 - .../ErrorsPerDomain/ErrorsPerDomain.js | 37 -- .../Widgets/ErrorsPerDomain/index.js | 1 - .../components/Dashboard/Widgets/FPS/FPS.js | 68 --- .../components/Dashboard/Widgets/FPS/index.js | 1 - .../LastFrustrations/LastFrustrations.js | 44 -- .../Widgets/LastFrustrations/index.js | 1 - .../MemoryConsumption/MemoryConsumption.js | 71 --- .../Widgets/MemoryConsumption/index.js | 1 - .../Widgets/MissingResources/Chart.js | 17 - .../Widgets/MissingResources/CopyPath.js | 23 - .../MissingResources/MissingResources.js | 59 -- .../Widgets/MissingResources/ResourceInfo.js | 19 - .../Widgets/MissingResources/index.js | 1 - .../MissingResources/resourceInfo.module.css | 10 - .../Widgets/MostImpactfulErrors/Chart.js | 11 - .../Widgets/MostImpactfulErrors/ErrorInfo.js | 31 - .../MostImpactfulErrors.js | 64 -- .../MostImpactfulErrors/errorInfo.module.css | 11 - .../Widgets/MostImpactfulErrors/index.js | 1 - .../Dashboard/Widgets/PageMetrics.js | 33 - .../Widgets/Performance/Performance.js | 198 ------ .../Dashboard/Widgets/Performance/index.js | 1 - .../Performance/performance.module.css | 3 - .../Dashboard/Widgets/ProcessedSessions.js | 45 -- .../ResourceLoadedVsResponseEnd.js | 77 --- .../ResourceLoadedVsResponseEnd/index.js | 1 - .../ResourceLoadedVsVisuallyComplete.js | 87 --- .../ResourceLoadedVsVisuallyComplete/index.js | 1 - .../ResourceLoadingTime.js | 136 ----- .../Widgets/ResourceLoadingTime/index.js | 1 - .../Widgets/ResponseTime/ResponseTime.js | 98 --- .../Dashboard/Widgets/ResponseTime/index.js | 1 - .../ResponseTimeDistribution.js | 140 ----- .../Widgets/ResponseTimeDistribution/index.js | 1 - .../SessionsAffectedByJSErrors.js | 66 -- .../SessionsAffectedByJSErrors/index.js | 1 - .../SessionsImpactedBySlowRequests.js | 64 -- .../SessionsImpactedBySlowRequests/index.js | 1 - .../Widgets/SessionsPerBrowser/Bar.js | 34 -- .../Widgets/SessionsPerBrowser/Bar.module.css | 20 - .../SessionsPerBrowser/SessionsPerBrowser.js | 44 -- .../Widgets/SessionsPerBrowser/index.js | 1 - .../Dashboard/Widgets/SlowestDomains/Bar.js | 19 - .../Widgets/SlowestDomains/Bar.module.css | 6 - .../Widgets/SlowestDomains/SlowestDomains.js | 35 -- .../Dashboard/Widgets/SlowestDomains/index.js | 1 - .../Dashboard/Widgets/SlowestImages/Chart.js | 14 - .../Widgets/SlowestImages/ImageInfo.js | 30 - .../Widgets/SlowestImages/SlowestImages.js | 54 -- .../SlowestImages/imageInfo.module.css | 39 -- .../Dashboard/Widgets/SlowestImages/index.js | 1 - .../Widgets/SlowestResources/Chart.js | 16 - .../Widgets/SlowestResources/CopyPath.js | 23 - .../Widgets/SlowestResources/ImageInfo.js | 33 - .../Widgets/SlowestResources/ResourceType.js | 12 - .../SlowestResources/SlowestResources.js | 98 --- .../SlowestResources.module.css | 7 - .../SlowestResources/imageInfo.module.css | 52 -- .../Widgets/SlowestResources/index.js | 1 - .../Widgets/SpeedIndexLocation/Bar.js | 16 - .../Widgets/SpeedIndexLocation/Bar.module.css | 6 - .../Widgets/SpeedIndexLocation/Scale.js | 24 - .../SpeedIndexLocation/SpeedIndexLocation.js | 97 --- .../Widgets/SpeedIndexLocation/index.js | 1 - .../SpeedIndexLocation/scale.module.css | 11 - .../speedIndexLocation.module.css | 6 - .../Widgets/TimeToRender/TimeToRender.js | 97 --- .../Dashboard/Widgets/TimeToRender/index.js | 1 - .../Widgets/TopDomains/TopDomains.js | 63 -- .../Dashboard/Widgets/TopDomains/index.js | 1 - .../Dashboard/Widgets/TopMetrics.js | 56 -- .../Widgets/TrendChart/TrendChart.js | 107 ---- .../Dashboard/Widgets/TrendChart/index.js | 1 - .../Widgets/TrendChart/trendChart.module.css | 6 - .../Dashboard/Widgets/UserActivity.js | 31 - .../CustomMetricWidgetHoc.module.css | 6 - .../CustomMetricWidgetHoc.tsx | 23 - .../common/CustomMetricWidgetHoc/index.ts | 1 - .../Dashboard/Widgets/common/index.js | 4 - .../Dashboard/Widgets/common/widgetHOC.js | 149 ----- .../app/components/Dashboard/Widgets/index.js | 39 -- .../WidgetPredefinedChart.tsx | 2 - frontend/app/duck/dashboard.js | 188 +----- .../types/dashboard/applicationActivity.js | 21 - frontend/app/types/dashboard/crashes.js | 14 - frontend/app/types/dashboard/customMetric.js | 14 - .../app/types/dashboard/domBuildingTime.js | 14 - frontend/app/types/dashboard/err.js | 23 - frontend/app/types/dashboard/errors.js | 16 - .../app/types/dashboard/errorsByOrigin.js | 13 - frontend/app/types/dashboard/errorsByType.js | 13 - frontend/app/types/dashboard/image.js | 16 - frontend/app/types/dashboard/index.ts | 571 ------------------ .../app/types/dashboard/memoryConsumption.js | 16 - .../app/types/dashboard/missingResource.js | 26 - .../app/types/dashboard/overviewWidget.js | 20 - frontend/app/types/dashboard/pageMetrics.js | 14 - frontend/app/types/dashboard/performance.js | 13 - .../app/types/dashboard/processedSessions.js | 14 - frontend/app/types/dashboard/resource.ts | 24 - .../types/dashboard/resourceLoadingTime.ts | 21 - frontend/app/types/dashboard/responseTime.ts | 20 - .../dashboard/responseTimeDistribution.ts | 27 - .../dashboard/sessionsImpactedByJSErrors.ts | 20 - .../sessionsImpactedBySlowRequests.ts | 20 - .../app/types/dashboard/sessionsPerBrowser.ts | 22 - .../app/types/dashboard/slowestDomains.ts | 22 - .../app/types/dashboard/slowestResources.ts | 19 - frontend/app/types/dashboard/speedLocation.ts | 20 - frontend/app/types/dashboard/timeToRender.ts | 20 - frontend/app/types/dashboard/topDomains.ts | 20 - frontend/app/types/dashboard/topMetrics.ts | 17 - frontend/app/types/dashboard/userActivity.ts | 17 - 153 files changed, 4 insertions(+), 5311 deletions(-) delete mode 100644 frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.js delete mode 100644 frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.module.css delete mode 100644 frontend/app/components/Dashboard/DashboardHeader/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ApplicationActivity.js delete mode 100644 frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js delete mode 100644 frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/BusiestTimeOfTheDay.js delete mode 100644 frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/Chart.js delete mode 100644 frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/ImageInfo.js delete mode 100644 frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/imageInfo.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js delete mode 100644 frontend/app/components/Dashboard/Widgets/CallWithErrors/Chart.js delete mode 100644 frontend/app/components/Dashboard/Widgets/CallWithErrors/ImageInfo.js delete mode 100644 frontend/app/components/Dashboard/Widgets/CallWithErrors/MethodType.js delete mode 100644 frontend/app/components/Dashboard/Widgets/CallWithErrors/callWithErrors.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/CallWithErrors/imageInfo.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/CallWithErrors/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/CallsErrors4xx/CallsErrors4xx.js delete mode 100644 frontend/app/components/Dashboard/Widgets/CallsErrors4xx/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js delete mode 100644 frontend/app/components/Dashboard/Widgets/CallsErrors5xx/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js delete mode 100644 frontend/app/components/Dashboard/Widgets/CpuLoad/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js delete mode 100644 frontend/app/components/Dashboard/Widgets/Crashes/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx delete mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/index.ts delete mode 100644 frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js delete mode 100644 frontend/app/components/Dashboard/Widgets/DomBuildingTime/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/Errors/Errors.js delete mode 100644 frontend/app/components/Dashboard/Widgets/Errors/errors.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/Errors/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ErrorsByType/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/FPS/FPS.js delete mode 100644 frontend/app/components/Dashboard/Widgets/FPS/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js delete mode 100644 frontend/app/components/Dashboard/Widgets/LastFrustrations/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js delete mode 100644 frontend/app/components/Dashboard/Widgets/MemoryConsumption/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/MissingResources/Chart.js delete mode 100644 frontend/app/components/Dashboard/Widgets/MissingResources/CopyPath.js delete mode 100644 frontend/app/components/Dashboard/Widgets/MissingResources/MissingResources.js delete mode 100644 frontend/app/components/Dashboard/Widgets/MissingResources/ResourceInfo.js delete mode 100644 frontend/app/components/Dashboard/Widgets/MissingResources/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/MissingResources/resourceInfo.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/Chart.js delete mode 100644 frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/ErrorInfo.js delete mode 100644 frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js delete mode 100644 frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/errorInfo.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/PageMetrics.js delete mode 100644 frontend/app/components/Dashboard/Widgets/Performance/Performance.js delete mode 100644 frontend/app/components/Dashboard/Widgets/Performance/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/Performance/performance.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/ProcessedSessions.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/ResourceLoadedVsVisuallyComplete.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ResponseTime/ResponseTime.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ResponseTime/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/ResponseTimeDistribution.js delete mode 100644 frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/SessionsPerBrowser.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestDomains/Bar.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestDomains/Bar.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestDomains/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestImages/Chart.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestImages/ImageInfo.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestImages/imageInfo.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestImages/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestResources/Chart.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestResources/CopyPath.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestResources/ImageInfo.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestResources/ResourceType.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestResources/imageInfo.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/SlowestResources/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Scale.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/SpeedIndexLocation.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/scale.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/speedIndexLocation.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js delete mode 100644 frontend/app/components/Dashboard/Widgets/TimeToRender/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/TopDomains/TopDomains.js delete mode 100644 frontend/app/components/Dashboard/Widgets/TopDomains/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/TopMetrics.js delete mode 100644 frontend/app/components/Dashboard/Widgets/TrendChart/TrendChart.js delete mode 100644 frontend/app/components/Dashboard/Widgets/TrendChart/index.js delete mode 100644 frontend/app/components/Dashboard/Widgets/TrendChart/trendChart.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/UserActivity.js delete mode 100644 frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.module.css delete mode 100644 frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.tsx delete mode 100644 frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/index.ts delete mode 100644 frontend/app/components/Dashboard/Widgets/common/widgetHOC.js delete mode 100644 frontend/app/components/Dashboard/Widgets/index.js delete mode 100644 frontend/app/types/dashboard/applicationActivity.js delete mode 100644 frontend/app/types/dashboard/crashes.js delete mode 100644 frontend/app/types/dashboard/customMetric.js delete mode 100644 frontend/app/types/dashboard/domBuildingTime.js delete mode 100644 frontend/app/types/dashboard/err.js delete mode 100644 frontend/app/types/dashboard/errors.js delete mode 100644 frontend/app/types/dashboard/errorsByOrigin.js delete mode 100644 frontend/app/types/dashboard/errorsByType.js delete mode 100644 frontend/app/types/dashboard/image.js delete mode 100644 frontend/app/types/dashboard/index.ts delete mode 100644 frontend/app/types/dashboard/memoryConsumption.js delete mode 100644 frontend/app/types/dashboard/missingResource.js delete mode 100644 frontend/app/types/dashboard/overviewWidget.js delete mode 100644 frontend/app/types/dashboard/pageMetrics.js delete mode 100644 frontend/app/types/dashboard/performance.js delete mode 100644 frontend/app/types/dashboard/processedSessions.js delete mode 100644 frontend/app/types/dashboard/resource.ts delete mode 100644 frontend/app/types/dashboard/resourceLoadingTime.ts delete mode 100644 frontend/app/types/dashboard/responseTime.ts delete mode 100644 frontend/app/types/dashboard/responseTimeDistribution.ts delete mode 100644 frontend/app/types/dashboard/sessionsImpactedByJSErrors.ts delete mode 100644 frontend/app/types/dashboard/sessionsImpactedBySlowRequests.ts delete mode 100644 frontend/app/types/dashboard/sessionsPerBrowser.ts delete mode 100644 frontend/app/types/dashboard/slowestDomains.ts delete mode 100644 frontend/app/types/dashboard/slowestResources.ts delete mode 100644 frontend/app/types/dashboard/speedLocation.ts delete mode 100644 frontend/app/types/dashboard/timeToRender.ts delete mode 100644 frontend/app/types/dashboard/topDomains.ts delete mode 100644 frontend/app/types/dashboard/topMetrics.ts delete mode 100644 frontend/app/types/dashboard/userActivity.ts diff --git a/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.js b/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.js deleted file mode 100644 index cc8792848..000000000 --- a/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react' -import { LAST_24_HOURS, LAST_30_MINUTES, LAST_7_DAYS, LAST_30_DAYS, CUSTOM_RANGE } from 'Types/app/period'; -import { ALL, DESKTOP, MOBILE } from 'Types/app/platform'; -import { connect } from 'react-redux'; -import { setPeriod, setPlatform } from 'Duck/dashboard'; -import cn from 'classnames'; -import styles from './DashboardHeader.module.css'; -import Filters from '../Filters/Filters'; - -export const PERIOD_OPTIONS = [ - { text: 'Past 30 Min', value: LAST_30_MINUTES }, - { text: 'Past 24 Hours', value: LAST_24_HOURS }, - { text: 'Past 7 Days', value: LAST_7_DAYS }, - { text: 'Past 30 Days', value: LAST_30_DAYS }, - { text: 'Choose Date', value: CUSTOM_RANGE }, -]; - -const PLATFORM_OPTIONS = [ - { text: 'All Platforms', value: ALL }, - { text: 'Desktop', value: DESKTOP }, - { text: 'Mobile', value: MOBILE } -]; - -const DashboardHeader = props => { - return ( -
- - -
-
-
- ) -} - -export default connect(state => ({ - period: state.getIn([ 'dashboard', 'period' ]), - platform: state.getIn([ 'dashboard', 'platform' ]), - currentProjectId: state.getIn([ 'site', 'siteId' ]), - sites: state.getIn([ 'site', 'list' ]), -}), { setPeriod, setPlatform })(DashboardHeader) diff --git a/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.module.css b/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.module.css deleted file mode 100644 index bef1bd7f1..000000000 --- a/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.module.css +++ /dev/null @@ -1,27 +0,0 @@ -.dropdown { - display: 'flex' !important; - align-items: 'center'; - padding: 5px 8px; - border-radius: 3px; - transition: all 0.3s; - font-weight: 500; - - &:hover { - background-color: #DDDDDD; - transition: all 0.2s; - } -} - -.dateInput { - display: flex; - align-items: center; - padding: 4px; - font-weight: 500; - font-size: 14px; - color: $gray-darkest; - - &:hover { - background-color: lightgray; - border-radius: 3px; - } -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/DashboardHeader/index.js b/frontend/app/components/Dashboard/DashboardHeader/index.js deleted file mode 100644 index 924f0ec2c..000000000 --- a/frontend/app/components/Dashboard/DashboardHeader/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as DashboardHeader } from './DashboardHeader'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/ApplicationActivity.js b/frontend/app/components/Dashboard/Widgets/ApplicationActivity.js deleted file mode 100644 index d091e0a66..000000000 --- a/frontend/app/components/Dashboard/Widgets/ApplicationActivity.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { Loader } from 'UI'; -import { msToSec } from 'App/date'; -import { CountBadge, Divider, widgetHOC } from './common'; - -@widgetHOC('applicationActivity') -export default class ApplicationActivity extends React.PureComponent { - render() { - const { data, loading } = this.props; - return ( -
- - - - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js b/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js deleted file mode 100644 index 99ffc931b..000000000 --- a/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { - BarChart, Bar, CartesianGrid, Legend, ResponsiveContainer, - XAxis, YAxis, Tooltip -} from 'recharts'; - -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 28 } - - if (rangeName === LAST_24_HOURS) params.density = 21 - if (rangeName === LAST_30_MINUTES) params.density = 28 - if (rangeName === YESTERDAY) params.density = 28 - if (rangeName === LAST_7_DAYS) params.density = 28 - - return params -} - -@widgetHOC('resourcesCountByType', { customParams }) -export default class BreakdownOfLoadedResources extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - return ( - - - - - - - - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/index.js b/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/index.js deleted file mode 100644 index 6e2e706fb..000000000 --- a/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './BreakdownOfLoadedResources'; diff --git a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/BusiestTimeOfTheDay.js b/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/BusiestTimeOfTheDay.js deleted file mode 100644 index 31deb2cba..000000000 --- a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/BusiestTimeOfTheDay.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { Table, widgetHOC, domain } from '../common'; -import { - Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, - PieChart, Pie, Cell, Tooltip, ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area } from 'recharts'; - -const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042']; -const RADIAN = Math.PI / 180; - -@widgetHOC('busiestTimeOfDay', { fitContent: true }) -export default class BusiestTimeOfTheDay extends React.PureComponent { - renderCustomizedLabel = ({ - cx, cy, midAngle, innerRadius, outerRadius, percent, index, - }) => { - const radius = innerRadius + (outerRadius - innerRadius) * 0.5; - const x = cx + radius * Math.cos(-midAngle * RADIAN); - const y = cy + radius * Math.sin(-midAngle * RADIAN); - - return ( - cx ? 'start' : 'end'} dominantBaseline="central"> - {`${(percent * 100).toFixed(0)}%`} - - ); - }; - - render() { - const { data, loading } = this.props; - return ( - - - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/Chart.js b/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/Chart.js deleted file mode 100644 index a66c2801d..000000000 --- a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/Chart.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { AreaChart, Area } from 'recharts'; - -const Chart = ({ data }) => { - return ( - - - - ); -} - -Chart.displayName = 'Chart'; - -export default Chart; diff --git a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/ImageInfo.js b/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/ImageInfo.js deleted file mode 100644 index 28fd52a75..000000000 --- a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/ImageInfo.js +++ /dev/null @@ -1,29 +0,0 @@ -import { Tooltip, Icon } from 'UI'; -import styles from './imageInfo.module.css'; - -const ImageInfo = ({ data }) => ( -
- - } - > -
- -
{'Preview'}
-
-
- - {data.name} - -
-); - -ImageInfo.displayName = 'ImageInfo'; - -export default ImageInfo; diff --git a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/imageInfo.module.css b/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/imageInfo.module.css deleted file mode 100644 index 69030a582..000000000 --- a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/imageInfo.module.css +++ /dev/null @@ -1,39 +0,0 @@ -.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/BusiestTimeOfTheDay/index.js b/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/index.js deleted file mode 100644 index ce82e31a0..000000000 --- a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './BusiestTimeOfTheDay'; diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js b/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js deleted file mode 100644 index 4b3cadef6..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { Table, widgetHOC } from '../common'; -import { getRE } from 'App/utils'; -import ImageInfo from './ImageInfo'; -import MethodType from './MethodType'; -import cn from 'classnames'; -import stl from './callWithErrors.module.css'; - -const cols = [ - { - key: 'method', - title: 'Method', - className: 'text-left', - Component: MethodType, - cellClass: 'ml-2', - width: '8%', - }, - { - key: 'urlHostpath', - title: 'Path', - Component: ImageInfo, - width: '40%', - }, - { - key: 'allRequests', - title: 'Requests', - className: 'text-left', - width: '15%', - }, - { - key: '4xx', - title: '4xx', - className: 'text-left', - width: '15%', - }, - { - key: '5xx', - title: '5xx', - className: 'text-left', - width: '15%', - } -]; - -@widgetHOC('callsErrors', { fitContent: true }) -export default class CallWithErrors extends React.PureComponent { - state = { search: ''} - - test = (value = '', serach) => getRE(serach, 'i').test(value); - - write = ({ target: { name, value } }) => { - this.setState({ [ name ]: value }) - }; - - render() { - const { data: images, loading } = this.props; - const { search } = this.state; - const _data = search ? images.filter(i => this.test(i.urlHostpath, search)) : images; - - return ( - -
- -
- - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/Chart.js b/frontend/app/components/Dashboard/Widgets/CallWithErrors/Chart.js deleted file mode 100644 index a66c2801d..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/Chart.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { AreaChart, Area } from 'recharts'; - -const Chart = ({ data }) => { - return ( - - - - ); -} - -Chart.displayName = 'Chart'; - -export default Chart; diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/ImageInfo.js b/frontend/app/components/Dashboard/Widgets/CallWithErrors/ImageInfo.js deleted file mode 100644 index 80a2010b9..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/ImageInfo.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { TextEllipsis } from 'UI'; -import styles from './imageInfo.module.css'; - -const ImageInfo = ({ data }) => ( -
- -
-); - -ImageInfo.displayName = 'ImageInfo'; - -export default ImageInfo; diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/MethodType.js b/frontend/app/components/Dashboard/Widgets/CallWithErrors/MethodType.js deleted file mode 100644 index ba370b481..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/MethodType.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { Label } from 'UI'; - -const MethodType = ({ data }) => { - return ( - - ) -} - -export default MethodType diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/callWithErrors.module.css b/frontend/app/components/Dashboard/Widgets/CallWithErrors/callWithErrors.module.css deleted file mode 100644 index bc37a3991..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/callWithErrors.module.css +++ /dev/null @@ -1,22 +0,0 @@ -.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/CallWithErrors/imageInfo.module.css b/frontend/app/components/Dashboard/Widgets/CallWithErrors/imageInfo.module.css deleted file mode 100644 index 69030a582..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/imageInfo.module.css +++ /dev/null @@ -1,39 +0,0 @@ -.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/CallWithErrors/index.js b/frontend/app/components/Dashboard/Widgets/CallWithErrors/index.js deleted file mode 100644 index 6b0db1bd0..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CallWithErrors'; diff --git a/frontend/app/components/Dashboard/Widgets/CallsErrors4xx/CallsErrors4xx.js b/frontend/app/components/Dashboard/Widgets/CallsErrors4xx/CallsErrors4xx.js deleted file mode 100644 index d387b53cc..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallsErrors4xx/CallsErrors4xx.js +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, - LineChart, Line, Legend, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@widgetHOC('domainsErrors_4xx', { customParams }) -export default class CallsErrors4xx extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - const namesMap = data.chart - .map(i => Object.keys(i)) - .flat() - .filter(i => i !== 'time' && i !== 'timestamp') - .reduce( - (unique, item) => (unique.includes(item) ? unique : [...unique, item]), - [] - ); - - return ( - - - - - - - - - - - - - - - - { namesMap.map((key, index) => ( - - ))} - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/CallsErrors4xx/index.js b/frontend/app/components/Dashboard/Widgets/CallsErrors4xx/index.js deleted file mode 100644 index 78fa75b53..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallsErrors4xx/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CallsErrors4xx'; diff --git a/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js b/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js deleted file mode 100644 index 3c655da5f..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js +++ /dev/null @@ -1,85 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, - LineChart, Line, Legend, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@widgetHOC('domainsErrors_5xx', { customParams }) -export default class CallsErrors5xx extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - const namesMap = data.chart - .map(i => Object.keys(i)) - .flat() - .filter(i => i !== 'time' && i !== 'timestamp') - .reduce( - (unique, item) => (unique.includes(item) ? unique : [...unique, item]), - [] - ); - - return ( - - - - - - - - - - { namesMap.map((key, index) => ( - - ))} - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/index.js b/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/index.js deleted file mode 100644 index 61b9900d7..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CallsErrors5xx'; diff --git a/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js b/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js deleted file mode 100644 index ee448dac2..000000000 --- a/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, AreaChart, Area, Tooltip } from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@widgetHOC('cpu', { customParams }) -export default class CpuLoad extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - -
- -
- - - {gradientDef} - - - Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }} - /> - - - - -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/CpuLoad/index.js b/frontend/app/components/Dashboard/Widgets/CpuLoad/index.js deleted file mode 100644 index 84bd7bc61..000000000 --- a/frontend/app/components/Dashboard/Widgets/CpuLoad/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CpuLoad'; diff --git a/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js b/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js deleted file mode 100644 index 576c9c13f..000000000 --- a/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { - AreaChart, Area, ResponsiveContainer, - XAxis, YAxis, CartesianGrid, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@widgetHOC('crashes', { customParams }) -export default class Crashes extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - - - - {gradientDef} - - - Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "Number of Crashes" }} - /> - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/Crashes/index.js b/frontend/app/components/Dashboard/Widgets/Crashes/index.js deleted file mode 100644 index cb869ef09..000000000 --- a/frontend/app/components/Dashboard/Widgets/Crashes/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Crashes'; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.module.css b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.module.css deleted file mode 100644 index 1d1ef3ee4..000000000 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.wrapper { - background-color: white; - /* border: solid thin $gray-medium; */ - border-radius: 3px; - padding: 10px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx deleted file mode 100644 index 71820c502..000000000 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import React, { useState } from 'react'; -import { connect } from 'react-redux'; -import { Loader, NoContent, Icon, Tooltip } from 'UI'; -import { Styles } from '../../common'; -import { ResponsiveContainer } from 'recharts'; -import stl from './CustomMetricWidget.module.css'; -import { getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper'; -import { - init, - edit, - remove, - setActiveWidget, - updateActiveState, -} from 'Duck/customMetrics'; -import { setShowAlerts } from 'Duck/dashboard'; -import CustomMetriLineChart from '../CustomMetriLineChart'; -import CustomMetricPieChart from '../CustomMetricPieChart'; -import CustomMetricPercentage from '../CustomMetricPercentage'; -import CustomMetricTable from '../CustomMetricTable'; -import { NO_METRIC_DATA } from 'App/constants/messages'; - -const customParams = (rangeName) => { - const params = { density: 70 }; - - // if (rangeName === LAST_24_HOURS) params.density = 70 - // if (rangeName === LAST_30_MINUTES) params.density = 70 - // if (rangeName === YESTERDAY) params.density = 70 - // if (rangeName === LAST_7_DAYS) params.density = 70 - - return params; -}; - -interface Props { - metric: any; - // loading?: boolean; - data?: any; - compare?: boolean; - period?: any; - onClickEdit: (e) => void; - remove: (id) => void; - setShowAlerts: (showAlerts) => void; - setAlertMetricId: (id) => void; - onAlertClick: (e) => void; - init: (metric: any) => void; - edit: (setDefault?) => void; - setActiveWidget: (widget) => void; - updateActiveState: (metricId, state) => void; - isTemplate?: boolean; -} -function CustomMetricWidget(props: Props) { - const { metric, period, isTemplate } = props; - const [loading, setLoading] = useState(false); - const [data, setData] = useState([]); - // const [seriesMap, setSeriesMap] = useState([]); - - const colors = Styles.customMetricColors; - const params = customParams(period.rangeName); - // const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart', startDate: period.start, endDate: period.end } - const isLineChart = metric.viewType === 'lineChart'; - const isProgress = metric.viewType === 'progress'; - const isTable = metric.viewType === 'table'; - const isPieChart = metric.viewType === 'pieChart'; - - const clickHandlerTable = (filters) => { - const activeWidget = { - widget: metric, - period: period, - ...period.toTimestamps(), - filters, - }; - props.setActiveWidget(activeWidget); - }; - - const clickHandler = (event, index) => { - 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); - } - }; - - const updateActiveState = (metricId, state) => { - props.updateActiveState(metricId, state); - }; - - return ( -
-
-
{metric.name}
-
- {!isTable && !isPieChart && ( - - )} - props.init(metric)} - /> - updateActiveState(metric.metricId, false)} - /> -
-
-
- - - - <> - {isLineChart && ( - - )} - - {isPieChart && ( - - )} - - {isProgress && ( - - )} - - {isTable && ( - - )} - - - - -
-
- ); -} - -export default connect( - (state) => ({ - period: state.getIn(['dashboard', 'period']), - }), - { - remove, - setShowAlerts, - edit, - setActiveWidget, - updateActiveState, - init, - } -)(CustomMetricWidget); - -const WidgetIcon = ({ - className = '', - tooltip = '', - icon, - onClick, -}: { - className: string; - tooltip: string; - icon: string; - onClick: any; -}) => ( - -
- {/* @ts-ignore */} - -
-
-); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/index.ts deleted file mode 100644 index 4a6d9b653..000000000 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CustomMetricWidget'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js b/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js deleted file mode 100644 index 970bfdbad..000000000 --- a/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js +++ /dev/null @@ -1,103 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { withRequest } from 'HOCs'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import WidgetAutoComplete from 'Shared/WidgetAutoComplete'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const WIDGET_KEY = 'pagesDomBuildtime'; -const toUnderscore = s => s.split(/(?=[A-Z])/).join('_').toLowerCase(); - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} -@withRequest({ - dataName: "options", - initialData: [], - dataWrapper: data => data, - loadingName: 'optionsLoading', - requestName: "fetchOptions", - endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', - method: 'GET' -}) -@widgetHOC(WIDGET_KEY, { customParams }) -export default class DomBuildingTime extends React.PureComponent { - onSelect = (params) => { - const _params = customParams(this.props.period.rangeName) - this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { ..._params, url: params.value }) - } - - render() { - const { data, loading, period, optionsLoading, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - -
- - -
- - - - - {gradientDef} - - - Styles.tickFormatter(val)} - /> - - - - - - -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/DomBuildingTime/index.js b/frontend/app/components/Dashboard/Widgets/DomBuildingTime/index.js deleted file mode 100644 index b5d6c5682..000000000 --- a/frontend/app/components/Dashboard/Widgets/DomBuildingTime/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './DomBuildingTime'; diff --git a/frontend/app/components/Dashboard/Widgets/Errors/Errors.js b/frontend/app/components/Dashboard/Widgets/Errors/Errors.js deleted file mode 100644 index ad2313c7f..000000000 --- a/frontend/app/components/Dashboard/Widgets/Errors/Errors.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area } from 'recharts'; -import { Loader, NoContent } from 'UI'; -import { CountBadge, domain, widgetHOC } from '../common'; -import styles from './errors.module.css'; - -@widgetHOC('errors') -export default class Errors extends React.PureComponent { - render() { - const { data, loading } = this.props; - - const isMoreThanKSessions = data.impactedSessions > 1000; - const impactedSessionsView = isMoreThanKSessions ? Math.trunc(data.impactedSessions / 1000) : data.impactedSessions; - return ( -
- - - { 'Events' }
} - count={ data.count } - change={ data.progress } - oppositeColors - /> - { 'Sessions' } } - count={ impactedSessionsView } - change={ data.impactedSessionsProgress } - unit={ isMoreThanKSessions ? 'k' : '' } - oppositeColors - /> - - - - - - - - - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/Errors/errors.module.css b/frontend/app/components/Dashboard/Widgets/Errors/errors.module.css deleted file mode 100644 index 1de075dc2..000000000 --- a/frontend/app/components/Dashboard/Widgets/Errors/errors.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.label { - max-width: 65px; - line-height: 14px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/Errors/index.js b/frontend/app/components/Dashboard/Widgets/Errors/index.js deleted file mode 100644 index 87b132550..000000000 --- a/frontend/app/components/Dashboard/Widgets/Errors/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Errors'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js b/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js deleted file mode 100644 index d77bac5f4..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { - BarChart, Bar, CartesianGrid, Tooltip, Legend, ResponsiveContainer, - XAxis, YAxis -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 28 } - - if (rangeName === LAST_24_HOURS) params.density = 28 - if (rangeName === LAST_30_MINUTES) params.density = 28 - if (rangeName === YESTERDAY) params.density = 28 - if (rangeName === LAST_7_DAYS) params.density = 28 - - return params -} - -@widgetHOC('resourcesByParty', { fullwidth: true, customParams }) -export default class ErrorsByOrigin extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - return ( - - - - - - - - - - 1st Party} dataKey="firstParty" stackId="a" fill={colors[0]} /> - 3rd Party} dataKey="thirdParty" stackId="a" fill={colors[2]} /> - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/index.js b/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/index.js deleted file mode 100644 index 9ef5b0117..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ErrorsByOrigin'; diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js b/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js deleted file mode 100644 index 4421a3fbb..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, domain, Styles } from '../common'; -import { numberWithCommas} from 'App/utils'; -import { - BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, - XAxis, YAxis -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 28 } - - if (rangeName === LAST_24_HOURS) params.density = 21 - if (rangeName === LAST_30_MINUTES) params.density = 28 - if (rangeName === YESTERDAY) params.density = 28 - if (rangeName === LAST_7_DAYS) params.density = 28 - - return params -} - -@widgetHOC('errorsPerType', { fullwidth: true, customParams }) -export default class ErrorsByType extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - return ( - - - - - - - - - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByType/index.js b/frontend/app/components/Dashboard/Widgets/ErrorsByType/index.js deleted file mode 100644 index 53f976df3..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsByType/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ErrorsByType'; diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.js b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.js deleted file mode 100644 index 14f52349d..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import stl from './Bar.module.css' - -const Bar = ({ className = '', width = 0, avg, domain, color }) => { - return ( -
-
-
0 ? width : 5 }%`, backgroundColor: color }}>
-
- {`${avg}`} -
-
-
{domain}
-
- ) -} - -export default Bar diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.module.css b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.module.css deleted file mode 100644 index 529aa15eb..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.bar { - height: 5px; - background-color: red; - width: 100%; - border-radius: 3px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js deleted file mode 100644 index 11af3f7d7..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { Table, widgetHOC, domain, AvgLabel, Styles } from '../common'; -import Bar from './Bar'; -import { numberWithCommas } from 'App/utils'; - -@widgetHOC('errorsPerDomains') -export default class ErrorsPerDomain extends React.PureComponent { - render() { - const { data, loading, compare = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const firstAvg = data.first() && data.first().errorsCount; - - return ( - - -
- {data.map((item, i) => - - )} -
-
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/index.js b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/index.js deleted file mode 100644 index 3a60d15c0..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ErrorsPerDomain'; diff --git a/frontend/app/components/Dashboard/Widgets/FPS/FPS.js b/frontend/app/components/Dashboard/Widgets/FPS/FPS.js deleted file mode 100644 index 843cea3db..000000000 --- a/frontend/app/components/Dashboard/Widgets/FPS/FPS.js +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid, AreaChart, Area } from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@widgetHOC('fps', { customParams }) -export default class FPS extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - -
- -
- - - {gradientDef} - - - Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "Frames Per Second" }} - /> - - - - -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/FPS/index.js b/frontend/app/components/Dashboard/Widgets/FPS/index.js deleted file mode 100644 index 55394ee20..000000000 --- a/frontend/app/components/Dashboard/Widgets/FPS/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './FPS'; diff --git a/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js b/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js deleted file mode 100644 index fcd36d98e..000000000 --- a/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { Loader, NoContent, BrowserIcon, OsIcon } from 'UI'; -import { countries } from 'App/constants'; -import { diffFromNowString } from 'App/date'; -import { widgetHOC, SessionLine } from '../common'; - -@widgetHOC('sessionsFrustration', { fitContent: true }) -export default class LastFeedbacks extends React.PureComponent { - render() { - const { data: sessions, loading } = this.props; - return ( - - - { sessions.map(({ - startedAt, - sessionId, - clickRage, - returningLocation, - userBrowser, - userOs, - userCountry, - }) => ( - - { clickRage ? "Click Rage" : "Returning Location" } - - - - } - subInfo={ `${ diffFromNowString(startedAt) } ago - ${ countries[ userCountry ] || '' }` } - /> - ))} - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/LastFrustrations/index.js b/frontend/app/components/Dashboard/Widgets/LastFrustrations/index.js deleted file mode 100644 index caaafd7e5..000000000 --- a/frontend/app/components/Dashboard/Widgets/LastFrustrations/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './LastFrustrations'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js b/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js deleted file mode 100644 index 14ed08d93..000000000 --- a/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid, AreaChart, Area } from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} -@widgetHOC('memoryConsumption', { customParams }) -export default class MemoryConsumption extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - -
- -
- - - {gradientDef} - - - Styles.tickFormatterBytes(val)} - label={{ ...Styles.axisLabelLeft, value: "JS Heap Size (mb)" }} - /> - - - - -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/MemoryConsumption/index.js b/frontend/app/components/Dashboard/Widgets/MemoryConsumption/index.js deleted file mode 100644 index 9e6644855..000000000 --- a/frontend/app/components/Dashboard/Widgets/MemoryConsumption/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './MemoryConsumption'; diff --git a/frontend/app/components/Dashboard/Widgets/MissingResources/Chart.js b/frontend/app/components/Dashboard/Widgets/MissingResources/Chart.js deleted file mode 100644 index cf2e5758e..000000000 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/Chart.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -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/MissingResources/CopyPath.js b/frontend/app/components/Dashboard/Widgets/MissingResources/CopyPath.js deleted file mode 100644 index 6b7e709e7..000000000 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/CopyPath.js +++ /dev/null @@ -1,23 +0,0 @@ -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/MissingResources/MissingResources.js b/frontend/app/components/Dashboard/Widgets/MissingResources/MissingResources.js deleted file mode 100644 index af2444418..000000000 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/MissingResources.js +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { Table, widgetHOC } from '../common'; -import Chart from './Chart'; -import ResourceInfo from './ResourceInfo'; -import CopyPath from './CopyPath'; - -const cols = [ - { - key: 'resource', - title: 'Resource', - Component: ResourceInfo, - width: '40%', - }, - { - key: 'sessions', - title: 'Sessions', - toText: count => `${ count > 1000 ? Math.trunc(count / 1000) : count }${ count > 1000 ? 'k' : '' }`, - width: '20%', - }, - { - key: 'trend', - title: 'Trend', - Component: Chart, - width: '20%', - }, - { - key: 'copy-path', - title: '', - Component: CopyPath, - cellClass: 'invisible group-hover:visible text-right', - width: '20%', - } -]; - -@widgetHOC('missingResources', { }) -export default class MissingResources extends React.PureComponent { - render() { - const { data: resources, loading, compare } = this.props; - return ( - - -
- - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/MissingResources/ResourceInfo.js b/frontend/app/components/Dashboard/Widgets/MissingResources/ResourceInfo.js deleted file mode 100644 index 0a3ba485e..000000000 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/ResourceInfo.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { diffFromNowString } from 'App/date'; -import { TextEllipsis } from 'UI'; - -import styles from './resourceInfo.module.css'; - -export default class ResourceInfo extends React.PureComponent { - render() { - const { data } = this.props; - return ( -
- -
- { data.endedAt && data.startedAt && `${ diffFromNowString(data.endedAt) } ago - ${ diffFromNowString(data.startedAt) } old` } -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/MissingResources/index.js b/frontend/app/components/Dashboard/Widgets/MissingResources/index.js deleted file mode 100644 index 27229bb1c..000000000 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './MissingResources'; diff --git a/frontend/app/components/Dashboard/Widgets/MissingResources/resourceInfo.module.css b/frontend/app/components/Dashboard/Widgets/MissingResources/resourceInfo.module.css deleted file mode 100644 index d73d23530..000000000 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/resourceInfo.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.name { - letter-spacing: -.04em; - font-size: .9rem; - cursor: pointer; -} - -.timings { - color: $gray-medium; - font-size: 12px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/Chart.js b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/Chart.js deleted file mode 100644 index 1f97c6605..000000000 --- a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/Chart.js +++ /dev/null @@ -1,11 +0,0 @@ -import { BarChart, Bar } from 'recharts'; - -const Chart = ({ data }) => ( - - - -); - -Chart.displaName = 'Chart'; - -export default Chart; diff --git a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/ErrorInfo.js b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/ErrorInfo.js deleted file mode 100644 index 4ac5e5f67..000000000 --- a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/ErrorInfo.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { diffFromNowString } from 'App/date'; -import { TextEllipsis } from 'UI'; -import styles from './errorInfo.module.css'; - -export default class ErrorInfo extends React.PureComponent { - findJourneys = () => this.props.findJourneys(this.props.data.error) - - render() { - const { data } = this.props; - return ( -
- -
- { `${ diffFromNowString(data.lastOccurrenceAt) } ago - ${ diffFromNowString(data.firstOccurrenceAt) } old` } -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js deleted file mode 100644 index a86e23220..000000000 --- a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import withSiteIdRouter from 'HOCs/withSiteIdRouter'; -import { Loader, NoContent } from 'UI'; -import { addEvent } from 'Duck/filters'; -import { TYPES } from 'Types/filter/event'; -import { sessions } from 'App/routes'; -import { Table, widgetHOC } from '../common'; -import Chart from './Chart'; -import ErrorInfo from './ErrorInfo'; - -const cols = [ - { - key: 'error', - title: 'Error Info', - Component: ErrorInfo, - width: '80%', - }, - { - key: 'trend', - title: 'Trend', - Component: Chart, - width: '10%', - }, - { - key: 'sessions', - title: 'Sessions', - toText: count => `${ count > 1000 ? Math.trunc(count / 1000) : count }${ count > 1000 ? 'k' : '' }`, - width: '10%', - }, -]; - -@withSiteIdRouter -@widgetHOC('errorsTrend', { fullwidth: true }) -@connect(null, { addEvent }) -export default class MostImpactfulErrors extends React.PureComponent { - findJourneys = (error) => { - this.props.addEvent({ - type: TYPES.CONSOLE, - value: error, - }, true); - this.props.history.push(sessions()); - } - - render() { - const { data: errors, loading } = this.props; - return ( - - -
- - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/errorInfo.module.css b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/errorInfo.module.css deleted file mode 100644 index 7aa0df799..000000000 --- a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/errorInfo.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.errorText { - font-family: 'menlo', 'monaco', 'consolas', monospace; - letter-spacing: -.04em; - font-size: .9rem; - cursor: pointer; -} - -.timings { - color: $gray-medium; - font-size: 12px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/index.js b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/index.js deleted file mode 100644 index c072ed32c..000000000 --- a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './MostImpactfulErrors'; diff --git a/frontend/app/components/Dashboard/Widgets/PageMetrics.js b/frontend/app/components/Dashboard/Widgets/PageMetrics.js deleted file mode 100644 index 0df75ae99..000000000 --- a/frontend/app/components/Dashboard/Widgets/PageMetrics.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { Loader } from 'UI'; -import { CountBadge, Divider, widgetHOC } from './common'; - -@widgetHOC('pageMetrics') -export default class PageMetrics extends React.PureComponent { - render() { - const { data, loading } = this.props; - return ( -
- - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/Performance/Performance.js b/frontend/app/components/Dashboard/Widgets/Performance/Performance.js deleted file mode 100644 index 6c585305f..000000000 --- a/frontend/app/components/Dashboard/Widgets/Performance/Performance.js +++ /dev/null @@ -1,198 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { Map } from 'immutable'; -import cn from 'classnames'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Legend } from 'recharts'; -import { Loader, TextEllipsis, Tooltip } from 'UI'; -import { TYPES } from 'Types/resource'; -import { LAST_24_HOURS, LAST_30_MINUTES, LAST_7_DAYS, LAST_30_DAYS } from 'Types/app/period'; -import { fetchPerformanseSearch } from 'Duck/dashboard'; -import { widgetHOC } from '../common'; - -import styles from './performance.module.css'; - -const BASE_KEY = 'resource'; - -const pagesColor = '#7FCC33'; -const imagesColor = '#40C4FF'; -const requestsColor = '#DAB72F'; - -@widgetHOC('performance', { fullwidth: true }) -@connect((state, props) => ({ - performanceChartSpecified: state.getIn([ 'dashboard', 'performanceChart' ]), - period: state.getIn([ 'dashboard', 'period' ]), - loading: state.getIn([ 'dashboard', 'performanceSearchRequest', 'loading' ]) || - props.loading, -}), { - fetchPerformanseSearch, -}) -export default class Performance extends React.PureComponent { - state = { - comparing: false, - resources: Map(), - opacity: {}, - } - - onResourceSelect = (resource) => { - if (!resource || this.state.resources.size > 1) return; - - resource.fillColor = this.getFillColor(resource); - resource.strokeColor = this.getStrokeColor(resource); - this.setResources(this.state.resources.set(this.state.resources.size, resource)); - } - - onResourceSelect0 = resource => this.onResourceSelect(0, resource) - onResourceSelect1 = resource => this.onResourceSelect(1, resource) - - getInterval = () => { - switch (this.props.period.rangeName) { - case LAST_30_MINUTES: - return 0; - case LAST_24_HOURS: - return 2; - case LAST_7_DAYS: - return 3; - case LAST_30_DAYS: - return 2; - default: - return 0; - } - } - - setResources = (resources) => { - this.setState({ - resources, - }); - this.props.fetchPerformanseSearch({ - ...this.props.period.toTimestamps(), - resources: resources.valueSeq().toJS(), - }); - } - - getFillColor = (resource) => { - switch (resource.type) { - case TYPES.IMAGE: - return 'url(#colorAvgImageLoadTime)'; - case TYPES.PAGE: - return 'url(#colorAvgPageLoadTime)'; - case TYPES.REQUEST: - return 'url(#colorAvgRequestLoadTime)'; - default: - return 'blue'; - } - } - - getStrokeColor = (resource) => { - switch (resource.type) { - case TYPES.IMAGE: - return imagesColor; - case TYPES.PAGE: - return pagesColor; - case TYPES.REQUEST: - return requestsColor; - default: - return 'blue'; - } - } - - removeResource = (index) => { - this.setResources(this.state.resources.remove(index)); - } - - compare = () => this.setState({ comparing: true }) - - legendPopup = (component, trigger) => {trigger} - - legendFormatter = (value, entry, index) => { - const { opacity } = this.state; - - if (value === 'avgPageLoadTime') return (this.legendPopup(opacity.avgPageLoadTime === 0 ? 'Show' : 'Hide', {'Pages'})); - if (value === 'avgRequestLoadTime') return (this.legendPopup(opacity.avgRequestLoadTime === 0 ? 'Show' : 'Hide', {'Requests'})); - if (value === 'avgImageLoadTime') return (this.legendPopup(opacity.avgImageLoadTime === 0 ? 'Show' : 'Hide', {'Images'})); - // if (value === 'avgImageLoadTime') return ({'Images'}); - if (value.includes(BASE_KEY)) { - const resourceIndex = Number.parseInt(value.substr(BASE_KEY.length)); - return ( - - - - ); - } - } - - handleLegendClick = (legend) => { - const { dataKey } = legend; - const { opacity } = this.state; - - if (dataKey === 'resource0') { - this.removeResource(0); - } else if (dataKey === 'resource1') { - this.removeResource(1); - } else { - this.setState({ - opacity: { ...opacity, [ dataKey ]: opacity[ dataKey ] === 0 ? 1 : 0 }, - }); - } - } - - // eslint-disable-next-line complexity - render() { - const { comparing, resources, opacity } = this.state; - const { data, performanceChartSpecified, loading } = this.props; - const r0Presented = !!resources.get(0); - const r1Presented = !!resources.get(1); - const resourcesPresented = r0Presented || r1Presented; - const defaultData = !resourcesPresented || performanceChartSpecified.length === 0; // TODO: more safe? - const interval = this.getInterval(); - - return ( - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - { defaultData && } - { defaultData && } - { defaultData && } - { !defaultData && r0Presented && } - { !defaultData && r1Presented && } - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/Performance/index.js b/frontend/app/components/Dashboard/Widgets/Performance/index.js deleted file mode 100644 index 63b30d682..000000000 --- a/frontend/app/components/Dashboard/Widgets/Performance/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Performance'; diff --git a/frontend/app/components/Dashboard/Widgets/Performance/performance.module.css b/frontend/app/components/Dashboard/Widgets/Performance/performance.module.css deleted file mode 100644 index ab974bdc3..000000000 --- a/frontend/app/components/Dashboard/Widgets/Performance/performance.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.muted { - color: rgba(0, 0, 0, 0.3); -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/ProcessedSessions.js b/frontend/app/components/Dashboard/Widgets/ProcessedSessions.js deleted file mode 100644 index 2e6d3e743..000000000 --- a/frontend/app/components/Dashboard/Widgets/ProcessedSessions.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area } from 'recharts'; -import { Loader } from 'UI'; -import { CountBadge, domain, widgetHOC, Styles } from './common'; - -@widgetHOC('sessions', { trendChart: true, fitContent: true }) -export default class ProcessedSessions extends React.PureComponent { - render() { - const { data, loading } = this.props; - const isMoreThanK = data.count > 1000; - const countView = isMoreThanK ? Math.trunc(data.count / 1000) : data.count; - - return ( -
- - - - - - - - - - - - - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js deleted file mode 100644 index d04a5cef5..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js +++ /dev/null @@ -1,77 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { ComposedChart, Bar, CartesianGrid, Line, Legend, ResponsiveContainer, - XAxis, YAxis, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 30 } - - if (rangeName === LAST_24_HOURS) params.density = 24 - if (rangeName === LAST_30_MINUTES) params.density = 18 - if (rangeName === YESTERDAY) params.density = 24 - if (rangeName === LAST_7_DAYS) params.density = 30 - - return params -} - -@widgetHOC('resourceTypeVsResponseEnd', { customParams }) -export default class ResourceLoadedVsResponseEnd extends React.PureComponent { - render() { - const { data, loading, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - - return ( - - - - - - - Styles.tickFormatter(val, 'ms')} - yAxisId="left" - /> - Styles.tickFormatter(val, 'ms')} - label={{ - ...Styles.axisLabelLeft, - offset: 70, - value: "Response End (ms)" - }} - yAxisId="right" - orientation="right" - /> - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/index.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/index.js deleted file mode 100644 index e56d66bcd..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ResourceLoadedVsResponseEnd'; diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/ResourceLoadedVsVisuallyComplete.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/ResourceLoadedVsVisuallyComplete.js deleted file mode 100644 index b56d6fed3..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/ResourceLoadedVsVisuallyComplete.js +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { - ComposedChart, Bar, CartesianGrid, Line, Legend, ResponsiveContainer, - XAxis, YAxis, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 21 } - - if (rangeName === LAST_24_HOURS) params.density = 21 - if (rangeName === LAST_30_MINUTES) params.density = 21 - if (rangeName === YESTERDAY) params.density = 21 - if (rangeName === LAST_7_DAYS) params.density = 21 - - return params -} - -@widgetHOC('resourcesVsVisuallyComplete', { customParams }) -export default class ResourceLoadedVsVisuallyComplete extends React.PureComponent { - render() { - const {className, data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - return ( - - - - - - - Styles.tickFormatter(val, 'ms')} - /> - Styles.tickFormatter(val)} - /> - - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/index.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/index.js deleted file mode 100644 index 6f3621b2e..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ResourceLoadedVsVisuallyComplete'; diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js deleted file mode 100644 index 8f95a3479..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js +++ /dev/null @@ -1,136 +0,0 @@ -import React from 'react'; -import { Loader, NoContent, DropdownPlain } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { withRequest } from 'HOCs'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import WidgetAutoComplete from 'Shared/WidgetAutoComplete'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const WIDGET_KEY = 'resourcesLoadingTime'; -const toUnderscore = s => s.split(/(?=[A-Z])/).join('_').toLowerCase(); - -// other' = -1, 'script' = 0, 'stylesheet' = 1, 'fetch' = 2, 'img' = 3, 'media' = 4 -export const RESOURCE_OPTIONS = [ - { text: 'All', value: 'all', }, - { text: 'JS', value: "SCRIPT", }, - { text: 'CSS', value: "STYLESHEET", }, - { text: 'Fetch', value: "REQUEST", }, - { text: 'Image', value: "IMG", }, - { text: 'Media', value: "MEDIA", }, - { text: 'Other', value: "OTHER", }, -]; - -const customParams = rangeName => { - const params = {density: 70, type: null } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@withRequest({ - dataName: "options", - initialData: [], - dataWrapper: data => data, - loadingName: 'optionsLoading', - requestName: "fetchOptions", - endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', - method: 'GET' -}) -@widgetHOC(WIDGET_KEY, { customParams }) -export default class ResourceLoadingTime extends React.PureComponent { - state = { autoCompleteSelected: null, type: null } - onSelect = (params) => { - const _params = customParams(this.props.period.rangeName) - this.setState({ autoCompleteSelected: params.value }); - this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { ..._params, url: params.value }) - } - - writeOption = (e, { name, value }) => { - this.setState({ [name]: value }) - const _params = customParams(this.props.period.rangeName) - this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { ..._params, [ name ]: value === 'all' ? null : value }) - } - - render() { - const { data, loading, period, optionsLoading, compare = false, showSync = false } = this.props; - const { autoCompleteSelected, type } = this.state; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - -
- - - -
- - - - - {gradientDef} - - - Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "Resource Fetch Time (ms)" }} - /> - - - - - - -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/index.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/index.js deleted file mode 100644 index 91072b5ec..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ResourceLoadingTime'; diff --git a/frontend/app/components/Dashboard/Widgets/ResponseTime/ResponseTime.js b/frontend/app/components/Dashboard/Widgets/ResponseTime/ResponseTime.js deleted file mode 100644 index 5bd400d37..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResponseTime/ResponseTime.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, domain, Styles, AvgLabel } from '../common'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; -import WidgetAutoComplete from 'Shared/WidgetAutoComplete'; -import { withRequest } from 'HOCs'; -import { toUnderscore } from 'App/utils'; - -const WIDGET_KEY = 'pagesResponseTime'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@withRequest({ - dataName: "options", - initialData: [], - dataWrapper: data => data, - // resetBeforeRequest: true, - loadingName: "optionsLoading", - requestName: "fetchOptions", - endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', - method: 'GET' -}) -@widgetHOC(WIDGET_KEY, { customParams }) -export default class ResponseTime extends React.PureComponent { - onSelect = (params) => { - const _params = customParams(this.props.period.rangeName) - this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, {..._params, url: params.value }, this.props.filters) - } - - render() { - const { data, loading, optionsLoading, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const gradientDef = Styles.gradientDef(); - - return ( - -
- -
- -
-
- - - - - {gradientDef} - - - `${val}` } - label={{ ...Styles.axisLabelLeft, value: "Page Response Time (ms)" }} - /> - - - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ResponseTime/index.js b/frontend/app/components/Dashboard/Widgets/ResponseTime/index.js deleted file mode 100644 index c4228f56e..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResponseTime/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ResponseTime'; diff --git a/frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/ResponseTimeDistribution.js b/frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/ResponseTimeDistribution.js deleted file mode 100644 index ea4762fb1..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/ResponseTimeDistribution.js +++ /dev/null @@ -1,140 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { - ComposedChart, Bar, BarChart, CartesianGrid, ResponsiveContainer, - XAxis, YAxis, ReferenceLine, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 40 } - - if (rangeName === LAST_24_HOURS) params.density = 40 - if (rangeName === LAST_30_MINUTES) params.density = 40 - if (rangeName === YESTERDAY) params.density = 40 - if (rangeName === LAST_7_DAYS) params.density = 40 - - return params -} - -const PercentileLine = props => { - const { - viewBox: { x, y }, - xoffset, - yheight, - height, - label - } = props; - return ( - - - - {label} - - - ); -}; - -@widgetHOC('pagesResponseTimeDistribution', { customParams }) -export default class ResponseTimeDistribution extends React.PureComponent { - render() { - const { data, loading, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - - return ( - - -
- -
-
- - - - - - - 'Page Response Time: ' + val} /> - { data.percentiles.map((item, i) => ( - - } - allowDecimals={false} - x={item.responseTime} - strokeWidth={0} - strokeOpacity={1} - /> - ))} - - - - - - - - - - - -
-
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/index.js b/frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/index.js deleted file mode 100644 index 2ddc12a3c..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ResponseTimeDistribution'; diff --git a/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js b/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js deleted file mode 100644 index 057122195..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { - ComposedChart, Bar, CartesianGrid, Legend, ResponsiveContainer, - XAxis, YAxis, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 28 } - - if (rangeName === LAST_24_HOURS) params.density = 28 - if (rangeName === LAST_30_MINUTES) params.density = 28 - if (rangeName === YESTERDAY) params.density = 28 - if (rangeName === LAST_7_DAYS) params.density = 28 - - return params -} - -@widgetHOC('impactedSessionsByJsErrors', { customParams }) -export default class SessionsAffectedByJSErrors extends React.PureComponent { - render() { - const { data, loading, period, compare, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - return ( - -
-
- -
-
- - - - - - - - - - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/index.js b/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/index.js deleted file mode 100644 index 2af7f10db..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SessionsAffectedByJSErrors'; diff --git a/frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.js b/frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.js deleted file mode 100644 index af321c1c0..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.js +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@widgetHOC('impactedSessionsBySlowPages', { customParams }) -export default class SessionsImpactedBySlowRequests extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - - - - {gradientDef} - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/index.js b/frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/index.js deleted file mode 100644 index 7e4adcc74..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SessionsImpactedBySlowRequests'; diff --git a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.js b/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.js deleted file mode 100644 index ffcace6ba..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -import stl from './Bar.module.css' -import { Styles } from '../common' -import { TextEllipsis } from 'UI'; - -const Bar = ({ className = '', versions = [], width = 0, avg, domain, colors }) => { - return ( -
-
-
- {versions.map((v, i) => { - const w = (v.value * 100)/ avg; - return ( -
- -
Version: {v.key}
-
Sessions: {v.value}
-
- } /> -
- ) - })} -
-
- {`${avg}`} -
-
-
{domain}
- - ) -} - -export default Bar \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.module.css b/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.module.css deleted file mode 100644 index dde6009e4..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.module.css +++ /dev/null @@ -1,20 +0,0 @@ -.bar { - height: 5px; - width: 100%; - border-radius: 3px; - display: flex; - align-items: center; - & div { - padding: 0 5px; - height: 20px; - color: #FFF; - } - & div:first-child { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; - } - & div:last-child { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - } -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/SessionsPerBrowser.js b/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/SessionsPerBrowser.js deleted file mode 100644 index 3179f64da..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/SessionsPerBrowser.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, AvgLabel, Styles } from '../common'; -import Bar from './Bar'; - -@widgetHOC('sessionsPerBrowser') -export default class SessionsPerBrowser extends React.PureComponent { - - getVersions = item => { - return Object.keys(item) - .filter(i => i !== 'browser' && i !== 'count') - .map(i => ({ key: 'v' +i, value: item[i]})) - } - - render() { - const { data, loading, compare = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const firstAvg = data.chart[0] && data.chart[0].count; - - return ( - - -
- {data.chart.map((item, i) => - - )} -
-
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/index.js b/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/index.js deleted file mode 100644 index facd495bd..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SessionsPerBrowser'; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestDomains/Bar.js b/frontend/app/components/Dashboard/Widgets/SlowestDomains/Bar.js deleted file mode 100644 index 99ed6af05..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestDomains/Bar.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -import stl from './Bar.module.css' - -const Bar = ({ className = '', width = 0, avg, domain, color }) => { - return ( -
-
-
-
- {avg} - ms -
-
-
{domain}
-
- ) -} - -export default Bar diff --git a/frontend/app/components/Dashboard/Widgets/SlowestDomains/Bar.module.css b/frontend/app/components/Dashboard/Widgets/SlowestDomains/Bar.module.css deleted file mode 100644 index d3d399918..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestDomains/Bar.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.bar { - height: 10px; - background-color: red; - width: 100%; - border-radius: 3px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js b/frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js deleted file mode 100644 index 9f85ae412..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import Bar from './Bar'; -import { numberWithCommas } from 'App/utils'; - -@widgetHOC('slowestDomains') -export default class ResponseTime extends React.PureComponent { - render() { - const { data, loading, compare = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const firstAvg = data.partition.first() && data.partition.first().avg; - - return ( - - -
- {data.partition && data.partition.map((item, i) => - - )} -
-
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SlowestDomains/index.js b/frontend/app/components/Dashboard/Widgets/SlowestDomains/index.js deleted file mode 100644 index 87f1e002a..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestDomains/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SlowestDomains'; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestImages/Chart.js b/frontend/app/components/Dashboard/Widgets/SlowestImages/Chart.js deleted file mode 100644 index a66c2801d..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestImages/Chart.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { AreaChart, Area } from 'recharts'; - -const Chart = ({ data }) => { - return ( - - - - ); -} - -Chart.displayName = 'Chart'; - -export default Chart; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestImages/ImageInfo.js b/frontend/app/components/Dashboard/Widgets/SlowestImages/ImageInfo.js deleted file mode 100644 index cf7402a80..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestImages/ImageInfo.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { Tooltip, Icon } from 'UI'; -import styles from './imageInfo.module.css'; - -const ImageInfo = ({ data }) => ( -
- - } - > -
- -
{'Preview'}
-
-
- - {data.name} - -
-); - -ImageInfo.displayName = 'ImageInfo'; - -export default ImageInfo; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js b/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js deleted file mode 100644 index 7bfc0cfd9..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { Table, widgetHOC } from '../common'; -import Chart from './Chart'; -import ImageInfo from './ImageInfo'; - -const cols = [ - { - key: 'image', - title: 'Image', - Component: ImageInfo, - width: '40%', - }, - { - key: 'avgDuration', - title: 'Load Time', - toText: time => `${ Math.trunc(time) }ms`, - width: '25%', - }, - { - key: 'trend', - title: 'Trend', - Component: Chart, - width: '20%', - }, - { - key: 'sessions', - title: 'Sessions', - width: '15%', - toText: count => `${ count > 1000 ? Math.trunc(count / 1000) : count }${ count > 1000 ? 'k' : '' }`, - className: 'text-left' - }, -]; - -@widgetHOC('slowestImages', { fitContent: true }) -export default class SlowestImages extends React.PureComponent { - render() { - const { data: images, loading } = this.props; - return ( - - -
- - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SlowestImages/imageInfo.module.css b/frontend/app/components/Dashboard/Widgets/SlowestImages/imageInfo.module.css deleted file mode 100644 index 69030a582..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestImages/imageInfo.module.css +++ /dev/null @@ -1,39 +0,0 @@ -.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/SlowestImages/index.js b/frontend/app/components/Dashboard/Widgets/SlowestImages/index.js deleted file mode 100644 index 54bcac137..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestImages/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SlowestImages'; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestResources/Chart.js b/frontend/app/components/Dashboard/Widgets/SlowestResources/Chart.js deleted file mode 100644 index 1990733fb..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/Chart.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -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/SlowestResources/CopyPath.js b/frontend/app/components/Dashboard/Widgets/SlowestResources/CopyPath.js deleted file mode 100644 index 6b7e709e7..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/CopyPath.js +++ /dev/null @@ -1,23 +0,0 @@ -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/SlowestResources/ImageInfo.js b/frontend/app/components/Dashboard/Widgets/SlowestResources/ImageInfo.js deleted file mode 100644 index c8b3890e3..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/ImageInfo.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { Tooltip } from 'UI'; -import cn from 'classnames'; -import styles from './imageInfo.module.css'; - -const supportedTypes = ['png', 'jpg', 'jpeg', 'svg']; - -const ImageInfo = ({ data }) => { - const canPreview = supportedTypes.includes(data.type); - return ( -
- - } - > -
-
{data.name}
-
-
-
- ); -}; - -ImageInfo.displayName = 'ImageInfo'; - -export default ImageInfo; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestResources/ResourceType.js b/frontend/app/components/Dashboard/Widgets/SlowestResources/ResourceType.js deleted file mode 100644 index 9803a050f..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/ResourceType.js +++ /dev/null @@ -1,12 +0,0 @@ -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/SlowestResources/SlowestResources.js b/frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.js deleted file mode 100644 index 4de486e49..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react'; -import { Loader, NoContent, DropdownPlain } from 'UI'; -import { Table, widgetHOC } from '../common'; -import Chart from './Chart'; -import ImageInfo from './ImageInfo'; -import { getRE } from 'App/utils'; -import cn from 'classnames'; -import stl from './SlowestResources.module.css'; -import ResourceType from './ResourceType'; -import CopyPath from './CopyPath'; -import { numberWithCommas } from 'App/utils'; - -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%', - } -]; -const WIDGET_KEY = 'slowestResources' - -@widgetHOC(WIDGET_KEY, { fitContent: true }) -export default class SlowestResources extends React.PureComponent { - state = { resource: 'all', search: ''} - - test = (key, value = '') => getRE(key, 'i').test(value); - - write = ({ target: { name, value } }) => { - this.setState({ [ name ]: value }) - }; - - writeOption = (e, { name, value }) => { - this.setState({ [ name ]: value }) - this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { [ name ]: value === 'all' ? null : value }) - } - - render() { - const { data, loading, compare, isTemplate } = this.props; - - return ( -
-
- -
- - -
- - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.module.css b/frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.module.css deleted file mode 100644 index 42a6c55a1..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.topActions { - position: absolute; - top: 0px; - right: 50px; - display: flex; - justify-content: flex-end; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SlowestResources/imageInfo.module.css b/frontend/app/components/Dashboard/Widgets/SlowestResources/imageInfo.module.css deleted file mode 100644 index 1de36b529..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/imageInfo.module.css +++ /dev/null @@ -1,52 +0,0 @@ -.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/SlowestResources/index.js b/frontend/app/components/Dashboard/Widgets/SlowestResources/index.js deleted file mode 100644 index 22fd02391..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SlowestResources'; diff --git a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.js b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.js deleted file mode 100644 index 8702869e9..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import stl from './Bar.module.css' - -const Bar = ({ className = '', width = 0, avg, domain, color }) => { - return ( -
-
-
-
{avg}
-
-
{domain}
-
- ) -} - -export default Bar diff --git a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.module.css b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.module.css deleted file mode 100644 index d3d399918..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.bar { - height: 10px; - background-color: red; - width: 100%; - border-radius: 3px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Scale.js b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Scale.js deleted file mode 100644 index 6ee56f2b3..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Scale.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react' -import { Styles } from '../common'; -import cn from 'classnames'; -import stl from './scale.module.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/SpeedIndexLocation/SpeedIndexLocation.js b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/SpeedIndexLocation.js deleted file mode 100644 index 9e1a23213..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/SpeedIndexLocation.js +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { widgetHOC, AvgLabel, Styles } from '../common'; -import * as DataMap from "datamaps"; -import { threeLetter } from 'App/constants/countries'; -import Scale from './Scale'; -import { numberWithCommas } from 'App/utils'; -import stl from './speedIndexLocation.module.css'; -import { colorScale } from 'App/utils'; - -@widgetHOC('speedLocation', { fitContent: false }) -export default class SpeedIndexLocation extends React.PureComponent { - wrapper = React.createRef() - map = null; - - getSeries = data => { - const series = []; - data.chart.forEach(item => { - const d = [threeLetter[item.userCountry], Math.round(item.avg)] - series.push(d) - }) - - return series; - } - - componentDidUpdate(prevProps) { - if (this.map) { - this.map.updateChoropleth(this.getDataset(), { reset: true}); - } - } - - getDataset = () => { - const { data, compare } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - - var dataset = {}; - const series = this.getSeries(data); - 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; - } - - render() { - const { data, loading, compare = false } = this.props; - - - if (this.wrapper.current && !this.map && data.chart.length > 0) { - const dataset = this.getDataset(); - this.map = new DataMap({ - element: this.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(''); - } - } - }); - } - - return ( - <> -
- -
- -
- - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/index.js b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/index.js deleted file mode 100644 index d62ce8efc..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SpeedIndexLocation'; diff --git a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/scale.module.css b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/scale.module.css deleted file mode 100644 index 5aa34f966..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/scale.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.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/SpeedIndexLocation/speedIndexLocation.module.css b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/speedIndexLocation.module.css deleted file mode 100644 index 1a433dc85..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/speedIndexLocation.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.mapWrapper { - height: 220px; - width: 90%; - margin: 0 auto; - display: flex; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js b/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js deleted file mode 100644 index 1ac489588..000000000 --- a/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { withRequest } from 'HOCs'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import WidgetAutoComplete from 'Shared/WidgetAutoComplete'; -import { toUnderscore } from 'App/utils'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const WIDGET_KEY = 'timeToRender'; -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@withRequest({ - dataName: "options", - initialData: [], - dataWrapper: data => data, - loadingName: "optionsLoading", - requestName: "fetchOptions", - endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', - method: 'GET' -}) -@widgetHOC(WIDGET_KEY, { customParams }) -export default class TimeToRender extends React.PureComponent { - onSelect = (params) => { - const _params = customParams(this.props.period.rangeName) - this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { ..._params, url: params.value }, this.props.filters) - } - - render() { - const { data, loading, optionsLoading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - -
- -
- -
-
- - - - - {gradientDef} - - - Styles.tickFormatter(val)} - /> - - - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/TimeToRender/index.js b/frontend/app/components/Dashboard/Widgets/TimeToRender/index.js deleted file mode 100644 index 9c4c77c12..000000000 --- a/frontend/app/components/Dashboard/Widgets/TimeToRender/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './TimeToRender'; diff --git a/frontend/app/components/Dashboard/Widgets/TopDomains/TopDomains.js b/frontend/app/components/Dashboard/Widgets/TopDomains/TopDomains.js deleted file mode 100644 index 23537e0f7..000000000 --- a/frontend/app/components/Dashboard/Widgets/TopDomains/TopDomains.js +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, - LineChart, Line, Legend, Tooltip -} from 'recharts'; - -@widgetHOC('domainsErrors', { fitContent: true }) -export default class TopDomains extends React.PureComponent { - render() { - const { data, loading, key = '4xx' } = this.props; - - const namesMap = data.chart[key] - .map(i => Object.keys(i)) - .flat() - .filter(i => i !== 'time' && i !== 'timestamp') - .reduce( - (unique, item) => (unique.includes(item) ? unique : [...unique, item]), - [] - ); - - return ( - - - - - - - - - - - - - - - - { namesMap.map((key, index) => ( - - ))} - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/TopDomains/index.js b/frontend/app/components/Dashboard/Widgets/TopDomains/index.js deleted file mode 100644 index 33cefa725..000000000 --- a/frontend/app/components/Dashboard/Widgets/TopDomains/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './TopDomains'; diff --git a/frontend/app/components/Dashboard/Widgets/TopMetrics.js b/frontend/app/components/Dashboard/Widgets/TopMetrics.js deleted file mode 100644 index bac8d5d41..000000000 --- a/frontend/app/components/Dashboard/Widgets/TopMetrics.js +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react'; -import { Loader } from 'UI'; -import { msToSec } from 'App/date'; -import { CountBadge, Divider, widgetHOC } from './common'; - -@widgetHOC('topMetrics') -export default class TopMetrics extends React.PureComponent { - render() { - const { data, loading } = this.props; - return ( -
- -
- - - - {/* */} -
-
- - - -
-
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/TrendChart/TrendChart.js b/frontend/app/components/Dashboard/Widgets/TrendChart/TrendChart.js deleted file mode 100644 index b32f4171a..000000000 --- a/frontend/app/components/Dashboard/Widgets/TrendChart/TrendChart.js +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react'; -import { Styles, CountBadge } from '../common'; -import { CloseButton, Loader } from 'UI'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, Area, Tooltip } from 'recharts'; -import { numberWithCommas } from 'App/utils'; -import cn from 'classnames'; -import stl from './trendChart.module.css'; - -const loadChart = (data, loading, unit, syncId, compare, tooltipLael) => { - const gradientDef = Styles.gradientDef(); - return ( -
- - - - {gradientDef} - - - - - - - -
- ) -} - -const countView = (avg, unit) => { - if (unit === 'mb') { - if (!avg) return 0; - const count = Math.trunc(avg / 1024 / 1024); - return numberWithCommas(count); - } - if (unit === 'min') { - if (!avg) return 0; - const count = Math.trunc(avg); - return numberWithCommas(count > 1000 ? count +'k' : count); - } - return avg ? numberWithCommas(avg): 0; -} - -function TrendChart({ - loading = true, - title, - avg, - progress, - unit = false, - subtext, - data, - handleRemove, - compare = false, - comparing = false, - syncId = '', - tooltipLael = '', - textClass ='', - prefix = '', - canRemove = true -}) { - return ( -
- { canRemove && ( - - )} -
-
- {comparing &&
} -
{ title }
-
-
- {prefix} - {/*
*/} - {/*
*/} - {/*
*/} - -
-
- { loadChart(data, loading, unit, syncId, compare, tooltipLael) } -
- ) -} - -export default TrendChart diff --git a/frontend/app/components/Dashboard/Widgets/TrendChart/index.js b/frontend/app/components/Dashboard/Widgets/TrendChart/index.js deleted file mode 100644 index bdf7f32b2..000000000 --- a/frontend/app/components/Dashboard/Widgets/TrendChart/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as TrendChart } from './TrendChart'; diff --git a/frontend/app/components/Dashboard/Widgets/TrendChart/trendChart.module.css b/frontend/app/components/Dashboard/Widgets/TrendChart/trendChart.module.css deleted file mode 100644 index e69fb4878..000000000 --- a/frontend/app/components/Dashboard/Widgets/TrendChart/trendChart.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.circle { - width: 8px; - height: 8px; - border-radius: 50%; - margin-right: 5px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/UserActivity.js b/frontend/app/components/Dashboard/Widgets/UserActivity.js deleted file mode 100644 index 282e305d9..000000000 --- a/frontend/app/components/Dashboard/Widgets/UserActivity.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { msToMin } from 'App/date'; -import { Loader } from 'UI'; -import { CountBadge, Divider, widgetHOC } from './common'; - -@widgetHOC('userActivity') -export default class UserActivity extends React.PureComponent { - render() { - const { data, loading } = this.props; - return ( -
- - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.module.css b/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.module.css deleted file mode 100644 index 1d1ef3ee4..000000000 --- a/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.wrapper { - background-color: white; - /* border: solid thin $gray-medium; */ - border-radius: 3px; - padding: 10px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.tsx b/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.tsx deleted file mode 100644 index 4df0d6fbb..000000000 --- a/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import stl from './CustomMetricWidgetHoc.module.css'; -import { Icon } from 'UI'; - -interface Props { -} -const CustomMetricWidgetHoc = ({ ...rest }: Props) => BaseComponent => { - return ( -
-
-
Widget Name
-
-
- -
-
-
- {/* */} -
- ); -} - -export default CustomMetricWidgetHoc; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/index.ts b/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/index.ts deleted file mode 100644 index 0be8a5be5..000000000 --- a/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CustomMetricWidgetHoc'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/common/index.js b/frontend/app/components/Dashboard/Widgets/common/index.js index 52ca30580..21ef76ea0 100644 --- a/frontend/app/components/Dashboard/Widgets/common/index.js +++ b/frontend/app/components/Dashboard/Widgets/common/index.js @@ -1,9 +1,5 @@ export { default as Title } from './Title'; -export { default as CountBadge } from './CountBadge'; export { default as Table } from './Table'; -export { default as Divider } from './Divider'; export { default as domain } from './domain'; -export { default as widgetHOC } from './widgetHOC'; -export { default as SessionLine } from './SessionLine'; export { default as Styles } from './Styles'; export { default as AvgLabel } from './AvgLabel'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/common/widgetHOC.js b/frontend/app/components/Dashboard/Widgets/common/widgetHOC.js deleted file mode 100644 index 341d52245..000000000 --- a/frontend/app/components/Dashboard/Widgets/common/widgetHOC.js +++ /dev/null @@ -1,149 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import cn from 'classnames'; -import { CloseButton } from 'UI'; -import { fetchWidget } from 'Duck/dashboard'; -// import { updateAppearance } from 'Duck/user'; -import { WIDGET_MAP } from 'Types/dashboard'; -import Title from './Title'; -import stl from './widgetHOC.module.css'; - -export default (widgetKey, panelProps = {}, wrapped = true, allowedFilters = []) => - (BaseComponent) => { - @connect( - (state, props) => { - const compare = props && props.compare; - const key = compare ? '_' + widgetKey : widgetKey; - - return { - loading: state.getIn(['dashboard', 'fetchWidget', key, 'loading']), - data: state.getIn(['dashboard', key]), - comparing: state.getIn(['dashboard', 'comparing']), - filtersSize: state.getIn(['dashboard', 'filters']).size, - filters: state.getIn(['dashboard', compare ? 'filtersCompare' : 'filters']), - period: state.getIn(['dashboard', compare ? 'periodCompare' : 'period']), //TODO: filters - platform: state.getIn(['dashboard', 'platform']), - // appearance: state.getIn([ 'user', 'account', 'appearance' ]), - - dataCompare: state.getIn(['dashboard', '_' + widgetKey]), // only for overview - loadingCompare: state.getIn(['dashboard', 'fetchWidget', '_' + widgetKey, 'loading']), - filtersCompare: state.getIn(['dashboard', 'filtersCompare']), - periodCompare: state.getIn(['dashboard', 'periodCompare']), //TODO: filters - }; - }, - { - fetchWidget, - // updateAppearance, - } - ) - class WidgetWrapper extends React.PureComponent { - constructor(props) { - super(props); - const params = panelProps.customParams - ? panelProps.customParams(this.props.period.rangeName) - : {}; - if (props.testId) { - params.testId = parseInt(props.testId); - } - params.compare = this.props.compare; - const filters = - allowedFilters.length > 0 - ? props.filters.filter((f) => allowedFilters.includes(f.key)) - : props.filters; - props.fetchWidget(widgetKey, props.period, props.platform, params, filters); - } - - componentDidUpdate(prevProps) { - if ( - prevProps.period !== this.props.period || - prevProps.platform !== this.props.platform || - prevProps.filters.size !== this.props.filters.size - ) { - const params = panelProps.customParams - ? panelProps.customParams(this.props.period.rangeName) - : {}; - if (this.props.testId) { - params.testId = parseInt(this.props.testId); - } - params.compare = this.props.compare; - const filters = - allowedFilters.length > 0 - ? this.props.filters.filter((f) => allowedFilters.includes(f.key)) - : this.props.filters; - this.props.fetchWidget( - widgetKey, - this.props.period, - this.props.platform, - params, - filters - ); - } - - // handling overview widgets - if ( - (!prevProps.comparing || - prevProps.periodCompare !== this.props.periodCompare || - prevProps.filtersCompare.size !== this.props.filtersCompare.size) && - this.props.comparing && - this.props.isOverview - ) { - const params = panelProps.customParams - ? panelProps.customParams(this.props.period.rangeName) - : {}; - params.compare = true; - const filtersCompare = - allowedFilters.length > 0 - ? this.props.filtersCompare.filter((f) => allowedFilters.includes(f.key)) - : this.props.filtersCompare; - this.props.fetchWidget( - widgetKey, - this.props.periodCompare, - this.props.platform, - params, - filtersCompare - ); - } - } - - handleRemove = () => { - // const { appearance } = this.props; - // this.props.updateAppearance(appearance.setIn([ 'dashboard', widgetKey ], false)); - }; - - render() { - const { comparing, compare } = this.props; - - return wrapped ? ( -
-
-
- {comparing && ( -
- )} - - { - <CloseButton - className={cn(stl.closeButton, 'ml-auto')} - onClick={this.handleRemove} - size="17" - /> - } - </div> - <div className="flex-1 flex flex-col"> - <BaseComponent {...this.props} /> - </div> - </div> - </div> - ) : ( - <BaseComponent {...this.props} /> - ); - } - } - return WidgetWrapper; - }; diff --git a/frontend/app/components/Dashboard/Widgets/index.js b/frontend/app/components/Dashboard/Widgets/index.js deleted file mode 100644 index e5dbd3758..000000000 --- a/frontend/app/components/Dashboard/Widgets/index.js +++ /dev/null @@ -1,39 +0,0 @@ -// export { default as ApplicationActivity } from './ApplicationActivity'; -// export { default as ProcessedSessions } from './ProcessedSessions'; -// export { default as Errors } from './Errors'; -// export { default as UserActivity } from './UserActivity'; -// export { default as Performance } from './Performance'; -// export { default as SlowestImages } from './SlowestImages'; -// export { default as PageMetrics } from './PageMetrics'; -// export { default as LastFrustrations } from './LastFrustrations'; -// export { default as MissingResources } from './MissingResources'; -// export { default as ResourceLoadingTime } from './ResourceLoadingTime'; -// export { default as SlowestResources } from './SlowestResources'; -// export { default as DomBuildingTime } from './DomBuildingTime'; -// export { default as BusiestTimeOfTheDay } from './BusiestTimeOfTheDay'; -// export { default as ResponseTime } from './ResponseTime'; -// export { default as ResponseTimeDistribution } from './ResponseTimeDistribution'; -// export { default as TimeToRender } from './TimeToRender'; -// export { default as SessionsImpactedBySlowRequests } from './SessionsImpactedBySlowRequests'; -// export { default as MemoryConsumption } from './MemoryConsumption'; -// export { default as FPS } from './FPS'; -// export { default as CpuLoad } from './CpuLoad'; -// export { default as Crashes } from './Crashes'; -// export { default as TopDomains } from './TopDomains'; -// export { default as SlowestDomains } from './SlowestDomains'; -// export { default as ErrorsPerDomain } from './ErrorsPerDomain'; -// export { default as CallWithErrors } from './CallWithErrors'; -// export { default as ErrorsByType } from './ErrorsByType'; -// export { default as ErrorsByOrigin } from './ErrorsByOrigin'; -// export { default as ResourceLoadedVsResponseEnd } from './ResourceLoadedVsResponseEnd'; -// export { default as ResourceLoadedVsVisuallyComplete } from './ResourceLoadedVsVisuallyComplete'; -// export { default as SessionsAffectedByJSErrors } from './SessionsAffectedByJSErrors'; -// export { default as BreakdownOfLoadedResources } from './BreakdownOfLoadedResources'; -// export { default as TopMetrics } from './TopMetrics'; -// export { default as SpeedIndexLocation } from './SpeedIndexLocation'; -// export { default as SessionsPerBrowser } from './SessionsPerBrowser'; -// export { default as CallsErrors5xx } from './CallsErrors5xx'; -// export { default as CallsErrors4xx } from './CallsErrors4xx'; -// export { default as TrendChart } from './TrendChart'; - -// TODO remove all the references to the old widgets \ 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 c411ee25c..88da5d888 100644 --- a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx @@ -1,6 +1,4 @@ import React from 'react'; -import { Styles } from 'App/components/Dashboard/Widgets/common'; -import CustomMetricOverviewChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart'; import ErrorsByType from 'App/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType'; import ErrorsByOrigin from 'App/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin'; import ErrorsPerDomain from 'App/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain'; diff --git a/frontend/app/duck/dashboard.js b/frontend/app/duck/dashboard.js index a99f985e3..9e40505c6 100644 --- a/frontend/app/duck/dashboard.js +++ b/frontend/app/duck/dashboard.js @@ -1,123 +1,22 @@ -import { List, Map, getIn } from 'immutable'; -import { - WIDGET_LIST, - WIDGET_MAP, - WIDGET_KEYS, -} from 'Types/dashboard'; -import Period, { LAST_24_HOURS, LAST_7_DAYS } from 'Types/app/period'; -import { ALL } from 'Types/app/platform'; +import { List, Map } from 'immutable'; import { createRequestReducer } from './funcTools/request'; -import { mergeReducers, success, array } from './funcTools/tools'; +import { mergeReducers, } from './funcTools/tools'; import { RequestTypes } from 'Duck/requestStateCreator'; -const SET_PERIOD = 'dashboard/SET_PERIOD'; -const SET_PLATFORM = 'dashboard/SET_PLATFORM'; const SET_SHOW_ALERTS = 'dashboard/SET_SHOW_ALERTS'; -const SET_COMPARING = 'dashboard/SET_COMPARING'; -const SET_FILTERS = 'dashboard/SET_FILTERS'; -const REMOVE_FILTER = 'dashboard/REMOVE_FILTER'; -const CLEAR_FILTERS = 'dashboard/CLEAR_FILTERS'; const FETCH_PERFORMANCE_SEARCH = 'dashboard/FETCH_PERFORMANCE_SEARCH'; -const FETCH_PERFORMANCE_SEARCH_SUCCESS = success(FETCH_PERFORMANCE_SEARCH); const ON_BOARD = new RequestTypes('plan/ON_BOARD'); -const FETCH_META_OPTIONS = 'dashboard/FETCH_META_OPTIONS'; -const FETCH_META_OPTIONS_SUCCESS = success(FETCH_META_OPTIONS); - -export const FETCH_WIDGET_TYPES = {}; -WIDGET_KEYS.forEach(key => { - FETCH_WIDGET_TYPES[ key ] = `dashboard/FETCH_WIDGET-${ key }-`; //workaround TODO - FETCH_WIDGET_TYPES[ '_' + key ] = `dashboard/FETCH_WIDGET-${ '_' + key }-`; //workaround TODO -}); -const FETCH_WIDGET_SUCCESS_LIST = WIDGET_KEYS.map(key => success(FETCH_WIDGET_TYPES[ key ])).concat(WIDGET_KEYS.map(key => success(FETCH_WIDGET_TYPES[ '_' + key ]))); - -const widgetInitialStates = {}; -WIDGET_LIST.forEach(({ key, dataWrapper }) => { - widgetInitialStates[ key ] = dataWrapper(); - widgetInitialStates[ '_' + key ] = dataWrapper(); -}); - const initialState = Map({ - ...widgetInitialStates, - period: Period({ rangeName: LAST_7_DAYS }), - periodCompare: Period({ rangeName: LAST_7_DAYS }), - filters: List(), - filtersCompare: List(), - platform: ALL, - performanceChart: [], showAlerts: false, - comparing: false, - metaOptions: [], - boarding: List(), - boardingCompletion: 0, + boarding: undefined, + boardingCompletion: undefined, }); -const getValue = ({ avgPageLoadTime, avgRequestLoadTime, avgImageLoadTime }) => avgPageLoadTime || avgRequestLoadTime || avgImageLoadTime; - -const getCountry = item => { - switch(item.location) { - case 'us-east-2': - case 'us-east-1': - return { - userCountry: 'US', - avg: Math.round(item.avg) - }; - case 'europe-west1-d': - return { - userCountry: 'EU', - avg: Math.round(item.avg) - }; - default: - return ''; - } -} - const reducer = (state = initialState, action = {}) => { - let isCompare; - if (FETCH_WIDGET_SUCCESS_LIST.includes(action.type)) { - const key = action.type.split('-')[ 1 ]; - const _key = key.startsWith('_') ? key.replace('_', '') : key; - const dataWrapper = WIDGET_LIST.find(w => w.key === _key).dataWrapper; - return state.set(action.compare ? key : key, dataWrapper(action.data, action.period)); - } switch (action.type) { - case SET_PERIOD: - return state.set(action.compare ? 'periodCompare' : 'period', Period(action.period)); - case SET_PLATFORM: - return state.set("platform", action.platform); - case FETCH_PERFORMANCE_SEARCH_SUCCESS: - const timestamps = List(getIn(action.data, [ 0, "chart" ])).map(({ timestamp }) => ({ timestamp })); - const chart = List(action.data) - .reduce((zippedChartData, resource, index) => zippedChartData - .zipWith((chartPoint, resourcePoint) => ({ - ...chartPoint, - [ `resource${ index }` ]: getValue(resourcePoint), - }), List(resource.chart)), - timestamps - ) - .toJS(); - return state.set('performanceChart', formatChartTime(chart, state.get("period"))); case SET_SHOW_ALERTS: return state.set('showAlerts', action.state); - case SET_COMPARING: - return state.set('comparing', action.status) - .set('filtersCompare', List()).set('periodCompare', state.get("period")); - case SET_FILTERS: - isCompare = action.key === 'compare'; - return state.update(isCompare ? 'filtersCompare' : 'filters', list => list.push(action.filter)) - case REMOVE_FILTER: - isCompare = action.key === 'compare'; - return state.update( - isCompare ? 'filtersCompare' : 'filters', - list => list.filter(filter => filter.key !== action.filterKey) - ); - case CLEAR_FILTERS: - isCompare = action.key === 'compare'; - return state.set(isCompare ? 'filtersCompare' : 'filters', List()); - - case FETCH_META_OPTIONS_SUCCESS: - return state.set('metaOptions', action.data.map(i => ({ ...i, icon: 'id-card', placeholder: 'Search for ' + i.name}))); - case ON_BOARD.SUCCESS: const tasks = List(action.data); const completion = tasks.filter(task => task.done).size * 100 / tasks.size; @@ -129,82 +28,10 @@ const reducer = (state = initialState, action = {}) => { export default mergeReducers( reducer, createRequestReducer({ - fetchWidget: FETCH_WIDGET_TYPES, performanceSearchRequest: FETCH_PERFORMANCE_SEARCH, }), ); -export function setPeriod(compare, period) { - return { - type: SET_PERIOD, - compare, - period, - } -} - -export function setPlatform(platform) { - return { - type: SET_PLATFORM, - platform, - }; -} - -export function setComparing(status) { - return { - type: SET_COMPARING, - status, - }; -} - -export function setFilters(key, filter) { - return { - type: SET_FILTERS, - key, - filter - }; -} - -export function removeFilter(key, filterKey) { - return { - type: REMOVE_FILTER, - key, - filterKey - }; -} - -export function clearFilters(key) { - return { - type: CLEAR_FILTERS, - key - }; -} - - -const toUnderscore = s => s.split(/(?=[A-Z])/).join('_').toLowerCase(); -export function fetchWidget(widgetKey, period, platform, _params, filters) { - let path = `/dashboard/${ toUnderscore(widgetKey) }`; - const widget = WIDGET_MAP[widgetKey]; - const params = period.toTimestamps(); - params.filters = filters ? - filters.map(f => ({key: f.key, value: f.value ? f.value : f.text})).toJS() : []; - // if (platform !== ALL) { - // params.platform = platform; - // } - - return { - types: array(FETCH_WIDGET_TYPES[ _params.compare ? '_' + widgetKey : widgetKey ]), - call: client => client.post(path, {...params, ..._params}), - period, - compare: _params && _params.compare - }; -} - -export function fetchPerformanseSearch(params) { - return { - types: array(FETCH_PERFORMANCE_SEARCH), - call: client => client.post('/dashboard/performance/search', params), - }; -} export function setShowAlerts(state) { return { @@ -213,13 +40,6 @@ export function setShowAlerts(state) { } } -export function fetchMetadataOptions() { - return { - types: array(FETCH_META_OPTIONS), - call: client => client.get('/dashboard/metadata'), - }; -} - export function getOnboard() { return { types: ON_BOARD.toArray(), diff --git a/frontend/app/types/dashboard/applicationActivity.js b/frontend/app/types/dashboard/applicationActivity.js deleted file mode 100644 index f24b063b8..000000000 --- a/frontend/app/types/dashboard/applicationActivity.js +++ /dev/null @@ -1,21 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - avgPageLoad: 0, - avgPageLoadProgress: 0, - avgImgLoad: 0, - avgImgLoadProgress: 0, - avgReqLoad: 0, - avgReqLoadProgress: 0, -}, { - // fromJS: aa => ({ - // avgPageLoad: aa.avgDom, - // avgPageLoadProgress: aa.avgDomProgress, - // avgImgLoad: aa.avgLoad, - // avgImgLoadProgress: aa.avgLoadProgress, - // avgReqLoad: aa.avgFirstPixel, - // avgReqLoadProgress: aa.avgFirstPixelProgress, - // ...aa, - // }), -}); - diff --git a/frontend/app/types/dashboard/crashes.js b/frontend/app/types/dashboard/crashes.js deleted file mode 100644 index 0986dddeb..000000000 --- a/frontend/app/types/dashboard/crashes.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const Crashes = Record({ - chart: [], - browsers: [] -}); - - -function fromJS(data = {}) { - if (data instanceof Crashes) return data; - return new Crashes(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/customMetric.js b/frontend/app/types/dashboard/customMetric.js deleted file mode 100644 index 9dd374b56..000000000 --- a/frontend/app/types/dashboard/customMetric.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const CustomMetric = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof CustomMetric) return data; - return new CustomMetric(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/domBuildingTime.js b/frontend/app/types/dashboard/domBuildingTime.js deleted file mode 100644 index 258902f60..000000000 --- a/frontend/app/types/dashboard/domBuildingTime.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const DomBuildingTime = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof DomBuildingTime) return data; - return new DomBuildingTime(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/err.js b/frontend/app/types/dashboard/err.js deleted file mode 100644 index cd869b60a..000000000 --- a/frontend/app/types/dashboard/err.js +++ /dev/null @@ -1,23 +0,0 @@ -import Record from 'Types/Record'; -import { convertNumberRange } from 'App/utils'; - -export default Record({ - error: '', - count: undefined, - sessions: undefined, - firstOccurrenceAt: undefined, - lastOccurrenceAt: undefined, - startTimestamp: undefined, - endTimestamp: undefined, - chart: [], -}, { - fromJS: ({ chart = [], ...err }) => { - const oldMax = [ ...chart ].sort((a, b) => b.count - a.count)[ 0 ].count; - const formattedChart = chart.map(({ count, ...rest }) => - ({ count: convertNumberRange(oldMax, 0, 2, 20, count), ...rest })); - return { - ...err, - chart: formattedChart, - } - } -}); \ No newline at end of file diff --git a/frontend/app/types/dashboard/errors.js b/frontend/app/types/dashboard/errors.js deleted file mode 100644 index 8d56e4c33..000000000 --- a/frontend/app/types/dashboard/errors.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Record } from 'immutable'; - -const Errors = Record({ - count: undefined, - progress: undefined, - impactedSessions: undefined, - impactedSessionsProgress: undefined, - chart: [], -}); - -function fromJS(errors = {}) { - if (errors instanceof Errors) return errors; - return new Errors(errors); -} - -export default fromJS; diff --git a/frontend/app/types/dashboard/errorsByOrigin.js b/frontend/app/types/dashboard/errorsByOrigin.js deleted file mode 100644 index bdbceab60..000000000 --- a/frontend/app/types/dashboard/errorsByOrigin.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const ErrorsByOrigin = Record({ - chart: [] -}); - - -function fromJS(data = {}) { - if (data instanceof ErrorsByOrigin) return data; - return new ErrorsByOrigin(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/errorsByType.js b/frontend/app/types/dashboard/errorsByType.js deleted file mode 100644 index ccedc566e..000000000 --- a/frontend/app/types/dashboard/errorsByType.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const ErrorsByType = Record({ - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof ErrorsByType) return data; - return new ErrorsByType(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/image.js b/frontend/app/types/dashboard/image.js deleted file mode 100644 index 57f5f7507..000000000 --- a/frontend/app/types/dashboard/image.js +++ /dev/null @@ -1,16 +0,0 @@ -import Record from 'Types/Record'; - -const getName = (url = '') => url.split('/').filter(part => !!part).pop(); - -export default Record({ - avgDuration: undefined, - sessions: undefined, - chart: [], - url: '', - name: '', -}, { - fromJS: (image) => ({ - ...image, - name: getName(image.url), - }) -}); diff --git a/frontend/app/types/dashboard/index.ts b/frontend/app/types/dashboard/index.ts deleted file mode 100644 index 3e2ff0e74..000000000 --- a/frontend/app/types/dashboard/index.ts +++ /dev/null @@ -1,571 +0,0 @@ -import { List } from 'immutable'; -import Session from 'Types/session'; -import { camelCased } from 'App/utils'; - -import { getChartFormatter } from './helper'; -import ProcessedSessions from './processedSessions'; -import DomBuildingTime from './domBuildingTime'; -import MemoryConsumption from './memoryConsumption'; -import ResponseTime from './responseTime'; -import ErrorsByType from './errorsByType'; -import OverviewWidget from './overviewWidget'; -import TopDomains from './topDomains'; -import SpeedLocation from './speedLocation'; -import SessionsPerBrowser from './sessionsPerBrowser'; -import ErrorsByOrigin from './errorsByOrigin'; -import SlowestResources from './slowestResources'; -import ResponseTimeDistribution from './responseTimeDistribution'; -import SessionsImpactedBySlowRequests from './sessionsImpactedBySlowRequests'; -import TimeToRender from './timeToRender'; -import SessionsImpactedByJSErrors from './sessionsImpactedByJSErrors'; -import ApplicationActivity from './applicationActivity'; -import TopMetrics from './topMetrics'; -import Errors from './errors'; -import UserActivity from './userActivity'; -import Performance from './performance'; -import Crashes from './crashes'; -import PageMetrics from './pageMetrics'; -import SlowestDomains from './slowestDomains'; -import ResourceLoadingTime from './resourceLoadingTime'; - -import Image from './image'; -import Err from './err'; -import MissingResource from './missingResource'; - -export const WIDGET_LIST = [{ - key: "sessions", - name: "Processed Sessions", - description: 'Number of recorded user sessions.', - thumb: 'processed_sessions.png', - dataWrapper: (ps, period) => ProcessedSessions(ps) - .update("chart", getChartFormatter(period)), - }, { - key: "applicationActivity", - name: "Application Activity", - description: 'Average loading time of pages, images and browser requests.', - thumb: 'application_activity.png', - dataWrapper: ApplicationActivity, - }, { - key: "errors", - name: "Exceptions", - description: 'Number of errors and impacted user sessions.', - thumb: 'errors.png', - dataWrapper: (e, period) => Errors(e) - .update("chart", getChartFormatter(period)), - }, { - key: "userActivity", - name: "User Activity", - description: 'The average user feedback score, average number of visited pages per session and average session duration.', - thumb: 'user_activity.png', - dataWrapper: data => new UserActivity(data), - }, { - key: "pageMetrics", - name: "Page Metrics", - description: "Average speed metrics across all pages: First Meaningful Pain and DOM Content Loaded.", - thumb: 'page_metrics.png', - dataWrapper: PageMetrics, - }, { - key: "performance", - name: "Performance", - description: "Compare the average loading times of your web app's resources (pages, images and browser requests)", - thumb: 'performance.png', - dataWrapper: (p, period) => Performance(p) - .update("chart", getChartFormatter(period)), - }, { - key: "slowestImages", - name: "Slowest Images", - description: 'List of images that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - dataWrapper: list => List(list).map(Image).sort((i1, i2) => { - if (i1.sessions < 1000) return i2.sessions -i1.sessions; - const sessionK1 = Math.trunc(i1.sessions/1000); - const sessionK2 = Math.trunc(i2.sessions/1000); - if (sessionK1 !== sessionK2) return sessionK2 - sessionK1; - return i2.avgDuration - i1.avgDuration; - }), - }, - { - key: "sessionsFrustration", - name: "Recent Frustrations", - description: "List of recent sessions where users experienced some kind of frustrations, such as click rage.", - thumb: 'recent_frustrations.png', - dataWrapper: list => List(list).map(s => new Session(s)), - }, - { - key: "sessionsFeedback", - name: "Recent Negative Feedback", - description: "List of recent sessions where users reported an issue or a bad experience.", - thumb: 'negative_feedback.png', - dataWrapper: list => List(list).map(s => new Session(s)), - }, - { - key: "missingResources", - name: "Missing Resources", - description: "List of resources, such as images, that couldn't be loaded.", - thumb: 'missing_resources.png', - type: 'resources', - dataWrapper: list => List(list).map(MissingResource), - }, - { - key: "slowestResources", - name: "Slowest Resources", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'resources', - dataWrapper: list => list.map(res => new SlowestResources(res)) - }, - { - key: "overview", - name: "Overview Metrics", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - dataWrapper: (p, period) => { - return List(p) - .map(item => OverviewWidget({ key: camelCased(item.key), ...item.data})) - .map(widget => widget.update("chart", getChartFormatter(period))) - } - }, - { - key: "speedLocation", - name: "Speed Index by Location", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: list => SpeedLocation(list) - }, - { - key: "slowestDomains", - name: "Slowest Domains", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - // dataWrapper: list => List(list).sort((a, b) => a.avg >= b.avg).map(SlowestDomains) - dataWrapper: list => SlowestDomains(list) - .update("partition", (partition) => List(partition).sort((a, b) => b.avg - a.avg)) - }, - { - key: "sessionsPerBrowser", - name: "Sessions per Browser", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: list => SessionsPerBrowser(list) - // .update("chart", (list) => List(list).sort((a, b) => a.count >= b.count)) - }, - { - key: "resourcesLoadingTime", - name: "Resource Fetch Time", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'resources', - dataWrapper: (list, period) => DomBuildingTime(list) - .update("chart", getChartFormatter(period)) - }, - { - key: "timeToRender", - name: "Time To Render", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => TimeToRender(p) - .update("chart", getChartFormatter(period)) - }, - { - key: "impactedSessionsBySlowPages", - name: "Sessions Impacted by Slow Requests", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => SessionsImpactedBySlowRequests({ chart: p}) - .update("chart", getChartFormatter(period)) - }, - { - key: "memoryConsumption", - name: "Memory Consumption", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => MemoryConsumption(p) - // .update("chart", list => list.map((i) => ({...i, avgUsedJsHeapSize: i.avgUsedJsHeapSize/1024/1024 }))) - .update("chart", getChartFormatter(period)) - }, - { - key: "cpu", - name: "CPU Load", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => MemoryConsumption(p) - .update("chart", getChartFormatter(period)), - }, - { - key: "fps", - name: "Framerate", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => MemoryConsumption(p) - .update("chart", getChartFormatter(period)), - }, - { - key: "crashes", - name: "Crashes", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (e, period) => Crashes(e) - .update("chart", getChartFormatter(period)) - }, - { - key: "resourceTypeVsResponseEnd", - name: "Resource Loaded vs Response End", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'resources', - dataWrapper: (p, period) => ErrorsByType({chart: p}) - .update("chart", getChartFormatter(period)) - }, - { - key: "resourcesCountByType", - name: "Breakdown of Loaded Resources", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'resources', - dataWrapper: (p, period) => ErrorsByType({chart: p}) - .update("chart", getChartFormatter(period)) - }, - { - key: "resourcesVsVisuallyComplete", - name: "Resource Loaded vs Visually Complete", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => ErrorsByType({chart: p}) - .update("chart", getChartFormatter(period)) - // .update('chart', (data) => { - // return data.map(i => ({...i, avgTimeToRender: i.avgTimeToRender / 100})); - // }) - }, - { - key: "pagesDomBuildtime", - name: "DOM Build Time", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => DomBuildingTime(p) - .update("chart", getChartFormatter(period)) - }, - { - key: "pagesResponseTime", - name: "Page Response Time", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => ResponseTime(p) - .update("chart", getChartFormatter(period)) - }, - { - key: "pagesResponseTimeDistribution", - name: "Page Response Time Distribution", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => ResponseTimeDistribution(p) - .update("chart", getChartFormatter(period)) - .update("extremeValues", list => list.map(i => ({...i, time: 'Extreme Values'}))) - .update("percentiles", list => list.map(i => ({ ...i, responseTime: Math.round(i.responseTime)}))) - }, - { - key: "domainsErrors_4xx", - name: "Top Domains with 4xx Fetch Errors", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - dataWrapper: (p, period) => TopDomains({ chart: p}) - .update("chart", getChartFormatter(period)) - // .updateIn(["chart", "5xx"], getChartFormatter(period)) - }, - { - key: "domainsErrors_5xx", - name: "Top Domains with 5xx Fetch Errors", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - dataWrapper: (p, period) => TopDomains({ chart: p}) - .update("chart", getChartFormatter(period)) - // .updateIn(["chart", "5xx"], getChartFormatter(period)) - }, - { - key: "errorsPerDomains", - name: "Errors per Domain", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - // dataWrapper: list => List(list) - dataWrapper: list => List(list).sort((a, b) => b.errorsCount - a.errorsCount).take(5) - }, - { - key: "callsErrors", - name: "Fetch Calls with Errors", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - dataWrapper: list => List(list).sort((a, b) => b.allRequests - a.allRequests) - }, - { - key: "errorsPerType", - name: "Errors by Type", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - dataWrapper: (p, period) => ErrorsByType({chart: p}) - .update("chart", getChartFormatter(period)) - }, - { - key: "resourcesByParty", - name: "Errors by Origin", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - dataWrapper: (p, period) => ErrorsByOrigin({chart: p}) - .update("chart", getChartFormatter(period)) - }, - { - key: "impactedSessionsByJsErrors", - name: "Sessions Affected by JS Errors", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - dataWrapper: (p, period) => SessionsImpactedByJSErrors(p) - .update("chart", getChartFormatter(period)) - }, - { - key: "busiestTimeOfDay", - name: "Busiest Time of the Day", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - dataWrapper: list => List(list) - }, - { - key: "topMetrics", - name: "Top Metrics", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - dataWrapper: TopMetrics - }, - - // Overview Widgets - { - key: 'countSessions', - name: 'Captured Sessions', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'New vs Returning', - type: 'overview', - tooltipLabel: "Count", - dataWrapper: list => List(list) - }, - { - key: 'avgTimeToRender', - name: 'Time To Render', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => TimeToRender(list) - }, - { - key: 'avgTimeToInteractive', - name: 'Time To Interactive', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgPageLoadTime', - name: 'Page Load Time', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgImageLoadTime', - name: 'Image Load Time', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgRequestLoadTime', - name: 'Request Load Time', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgDomContentLoadStart', - name: 'DOM Content Loaded', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgPagesDomBuildtime', - name: 'DOM Build Time', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: (list, period) => DomBuildingTime(list) - .update("chart", getChartFormatter(period)) - }, - { - key: 'avgSessionDuration', - name: 'Session Duration', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'min', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgVisitedPages', - name: 'No. of Visited Pages', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgPagesResponseTime', - name: 'Page Response Time', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => ResponseTime(list) - }, - { - key: 'avgTillFirstBit', - name: 'Time Till First Byte', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => ResponseTime(list) - }, - // { - // key: 'avgResponseTime', - // name: 'Response Time', - // description: 'Lorem ipsum...', - // thumb: 'na.png', - // subtext: 'test', - // unit: 'ms', - // type: 'overview', - // dataWrapper: list => List(list) - // }, - // { - // key: 'requestsCount', - // name: 'Request Count', - // description: 'Lorem ipsum...', - // thumb: 'na.png', - // subtext: 'test', - // // unit: 'ms', - // type: 'overview', - // dataWrapper: list => List(list) - // }, - { - key: 'avgFirstContentfulPixel', - name: 'First Meaningful Paint', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgFirstPaint', - name: 'First Paint', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgUsedJsHeapSize', - name: 'Memory Consumption', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'mb', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgCpu', - name: 'CPU Load', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - type: 'overview', - unit: '%', - dataWrapper: list => List(list) - }, - { - key: 'avgFps', - name: 'Framerate', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - type: 'overview', - dataWrapper: list => List(list) - }, - -]; - -export const WIDGET_KEYS = WIDGET_LIST.map(({ key }) => key); - -const WIDGET_MAP = {}; -WIDGET_LIST.forEach(w => { WIDGET_MAP[ w.key ] = w; }); - -const OVERVIEW_WIDGET_MAP = {}; -WIDGET_LIST.filter(w => w.type === 'overview').forEach(w => { OVERVIEW_WIDGET_MAP[ w.key ] = w; }); - -export { - WIDGET_MAP, - OVERVIEW_WIDGET_MAP, - ProcessedSessions, - ApplicationActivity, - Errors, - UserActivity, - Performance, - PageMetrics, - Image, - Err, - SlowestDomains, - ResourceLoadingTime -}; diff --git a/frontend/app/types/dashboard/memoryConsumption.js b/frontend/app/types/dashboard/memoryConsumption.js deleted file mode 100644 index f585ac4fd..000000000 --- a/frontend/app/types/dashboard/memoryConsumption.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Record } from 'immutable'; - -const MemoryConsumption = Record({ - avgFps: undefined, - avgUsedJsHeapSize: undefined, - avgCpu: undefined, - chart: [], -}); - -function fromJS(data = {}) { - const size = data.avgUsedJsHeapSize && data.avgUsedJsHeapSize / 1024 / 1024 - if (data instanceof MemoryConsumption) return data; - return new MemoryConsumption({...data, avgUsedJsHeapSize: size}); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/missingResource.js b/frontend/app/types/dashboard/missingResource.js deleted file mode 100644 index 8ca975973..000000000 --- a/frontend/app/types/dashboard/missingResource.js +++ /dev/null @@ -1,26 +0,0 @@ -import Record from 'Types/Record'; -import { convertNumberRange, getResourceName } from 'App/utils'; - - -export default Record({ - url: '', - name: '', - count: undefined, - sessions: undefined, - startedAt: undefined, - endedAt: undefined, - startTimestamp: undefined, - endTimestamp: undefined, - chart: [], -}, { - fromJS: ({ chart = [], ...missingResource }) => { - const oldMax = [ ...chart ].sort((a, b) => b.count - a.count)[ 0 ].count; - const formattedChart = chart.map(({ count, ...rest }) => - ({ count: convertNumberRange(oldMax, 0, 2, 20, count), ...rest })); - return { - ...missingResource, - chart: formattedChart, - name: getResourceName(missingResource.url), - } - } -}); \ No newline at end of file diff --git a/frontend/app/types/dashboard/overviewWidget.js b/frontend/app/types/dashboard/overviewWidget.js deleted file mode 100644 index 6133c5946..000000000 --- a/frontend/app/types/dashboard/overviewWidget.js +++ /dev/null @@ -1,20 +0,0 @@ -import { Record } from 'immutable'; - -const OverviewWidget = Record({ - key: undefined, - value: undefined, - progress: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof OverviewWidget) return data; - - if (data.key === "avgSessionDuration") { - data.value = data.value / 100000 - } - return new OverviewWidget(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/pageMetrics.js b/frontend/app/types/dashboard/pageMetrics.js deleted file mode 100644 index 9c92d9486..000000000 --- a/frontend/app/types/dashboard/pageMetrics.js +++ /dev/null @@ -1,14 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - avgLoad: 0, - avgLoadProgress: 0, - avgFirstContentfulPixel: 0, - avgFirstContentfulPixelProgress: 0, -}, { - fromJS: pm => ({ - ...pm, - avgFirstContentfulPixel: pm.avgFirstContentfulPixel || pm.avgFirstPixel, - avgFirstContentfulPixelProgress: pm.avgFirstContentfulPixelProgress || pm.avgFirstPixelProgress, - }), -}); \ No newline at end of file diff --git a/frontend/app/types/dashboard/performance.js b/frontend/app/types/dashboard/performance.js deleted file mode 100644 index 88932f22b..000000000 --- a/frontend/app/types/dashboard/performance.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const Performance = Record({ - chart: [], -}); - - -function fromJS(performance = {}) { - if (performance instanceof Performance) return performance; - return new Performance(performance); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/processedSessions.js b/frontend/app/types/dashboard/processedSessions.js deleted file mode 100644 index 2f719648f..000000000 --- a/frontend/app/types/dashboard/processedSessions.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const ProcessedSessions = Record({ - count: undefined, - progress: undefined, - chart: [], -}); - -function fromJS(processedSessions = {}) { - if (processedSessions instanceof ProcessedSessions) return processedSessions; - return new ProcessedSessions(processedSessions); -} - -export default fromJS; diff --git a/frontend/app/types/dashboard/resource.ts b/frontend/app/types/dashboard/resource.ts deleted file mode 100644 index c35b00765..000000000 --- a/frontend/app/types/dashboard/resource.ts +++ /dev/null @@ -1,24 +0,0 @@ -const getName = (url = '') => url.split('/').filter(part => !!part).pop(); - -interface IResource { - avgDuration: number; - sessions: any[]; - chart: any[]; - url: string; - name: string; -} - -export default class Resource { - avgDuration: IResource["avgDuration"]; - sessions: IResource["sessions"]; - chart: IResource["chart"] = []; - url: IResource["url"] = ''; - name: IResource["name"] = ''; - - constructor(data: IResource) { - Object.assign(this, { - ...data, - name: getName(data.url), - }) - } -} \ No newline at end of file diff --git a/frontend/app/types/dashboard/resourceLoadingTime.ts b/frontend/app/types/dashboard/resourceLoadingTime.ts deleted file mode 100644 index 65a9114a9..000000000 --- a/frontend/app/types/dashboard/resourceLoadingTime.ts +++ /dev/null @@ -1,21 +0,0 @@ - -interface IResourceLoadingTime { - avg: number; - timestamp: number; -} - -class ResourceLoadingTime { - avg: IResourceLoadingTime["avg"]; - timestamp: IResourceLoadingTime["timestamp"]; - - constructor(data: IResourceLoadingTime) { - Object.assign(this, data) - } -} - -function fromJS(resourceLoadingTime = {}) { - if (resourceLoadingTime instanceof ResourceLoadingTime) return resourceLoadingTime; - return new ResourceLoadingTime(resourceLoadingTime); -} - -export default fromJS; diff --git a/frontend/app/types/dashboard/responseTime.ts b/frontend/app/types/dashboard/responseTime.ts deleted file mode 100644 index 774440008..000000000 --- a/frontend/app/types/dashboard/responseTime.ts +++ /dev/null @@ -1,20 +0,0 @@ -interface IResponseTime { - avg: number; - chart: any[]; -} - -class ResponseTime { - avg: IResponseTime["avg"] - chart: IResponseTime["chart"] = [] - - constructor(data: IResponseTime) { - Object.assign(this, data) - } -} - -function fromJS(data = {}) { - if (data instanceof ResponseTime) return data; - return new ResponseTime(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/responseTimeDistribution.ts b/frontend/app/types/dashboard/responseTimeDistribution.ts deleted file mode 100644 index 76a96a1b2..000000000 --- a/frontend/app/types/dashboard/responseTimeDistribution.ts +++ /dev/null @@ -1,27 +0,0 @@ -interface IResponseTimeDistribution { - chart: any[], - avg: number, - percentiles: number[], - extremeValues: number[], - total: number -} - -class ResponseTimeDistribution { - chart: IResponseTimeDistribution["chart"] = [] - avg: IResponseTimeDistribution["avg"] - percentiles: IResponseTimeDistribution["percentiles"] = [] - extremeValues: IResponseTimeDistribution["extremeValues"] = [] - total: IResponseTimeDistribution["total"] - - constructor(data: IResponseTimeDistribution) { - Object.assign(this, data) - } -} - - -function fromJS(data = {}) { - if (data instanceof ResponseTimeDistribution) return data; - return new ResponseTimeDistribution(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/sessionsImpactedByJSErrors.ts b/frontend/app/types/dashboard/sessionsImpactedByJSErrors.ts deleted file mode 100644 index 843ed6b9a..000000000 --- a/frontend/app/types/dashboard/sessionsImpactedByJSErrors.ts +++ /dev/null @@ -1,20 +0,0 @@ -interface ISessionsImpactedByJSErrors { - errorsCount: number; - chart: any[]; -} - -class SessionsImpactedByJSErrors { - errorsCount: ISessionsImpactedByJSErrors["errorsCount"]; - chart: ISessionsImpactedByJSErrors["chart"]; - - constructor(data: ISessionsImpactedByJSErrors) { - Object.assign(this, data) - } -} - -function fromJS(data = {}) { - if (data instanceof SessionsImpactedByJSErrors) return data; - return new SessionsImpactedByJSErrors(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/sessionsImpactedBySlowRequests.ts b/frontend/app/types/dashboard/sessionsImpactedBySlowRequests.ts deleted file mode 100644 index 6b905fc26..000000000 --- a/frontend/app/types/dashboard/sessionsImpactedBySlowRequests.ts +++ /dev/null @@ -1,20 +0,0 @@ -interface ISessionsSlowRequests { - avg: number; - chart: any[]; -} - -class SessionsImpactedBySlowRequests { - avg: ISessionsSlowRequests["avg"]; - chart: ISessionsSlowRequests["chart"] = []; - - constructor(data: ISessionsSlowRequests) { - Object.assign(this, data) - } -} - -function fromJS(data = {}) { - if (data instanceof SessionsImpactedBySlowRequests) return data; - return new SessionsImpactedBySlowRequests(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/sessionsPerBrowser.ts b/frontend/app/types/dashboard/sessionsPerBrowser.ts deleted file mode 100644 index ec7a08c76..000000000 --- a/frontend/app/types/dashboard/sessionsPerBrowser.ts +++ /dev/null @@ -1,22 +0,0 @@ -interface ISessionsPerBrowser { - count?: number, - chart?: any[], - avg: number, -} - -class SessionsPerBrowser { - count: ISessionsPerBrowser["count"] - chart: ISessionsPerBrowser["chart"] = [] - avg: ISessionsPerBrowser["avg"] - - constructor(data: ISessionsPerBrowser) { - Object.assign(this, data) - } -} - -function fromJS(sessionsPerBrowser = {}) { - if (sessionsPerBrowser instanceof SessionsPerBrowser) return sessionsPerBrowser; - return new SessionsPerBrowser({...sessionsPerBrowser, avg: Math.round(sessionsPerBrowser.avg)}); -} - -export default fromJS; diff --git a/frontend/app/types/dashboard/slowestDomains.ts b/frontend/app/types/dashboard/slowestDomains.ts deleted file mode 100644 index 9dbe09870..000000000 --- a/frontend/app/types/dashboard/slowestDomains.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Record } from 'immutable'; - -interface ISlowestDomains { - partition?: string[]; - avg: number; -} - -class SlowestDomains { - partition: ISlowestDomains["partition"] = []; - avg: ISlowestDomains["avg"]; - - constructor(data: ISlowestDomains) { - Object.assign(this, data) - } -} - -function fromJS(slowestDomains = {}) { - if (slowestDomains instanceof SlowestDomains) return slowestDomains; - return new SlowestDomains({...slowestDomains, avg: Math.round(slowestDomains.avg)}); -} - -export default fromJS; diff --git a/frontend/app/types/dashboard/slowestResources.ts b/frontend/app/types/dashboard/slowestResources.ts deleted file mode 100644 index 57a802ac6..000000000 --- a/frontend/app/types/dashboard/slowestResources.ts +++ /dev/null @@ -1,19 +0,0 @@ -interface ISlowestResources { - avg: number; - url: string; - type: string; - name: string; - chart: any[] -} - -export default class SlowestResources { - avg: ISlowestResources["avg"]; - url: ISlowestResources["url"]; - type: ISlowestResources["type"]; - name: ISlowestResources["name"]; - chart: ISlowestResources["chart"]; - - constructor(data: ISlowestResources) { - Object.assign(this, data); - } -} \ No newline at end of file diff --git a/frontend/app/types/dashboard/speedLocation.ts b/frontend/app/types/dashboard/speedLocation.ts deleted file mode 100644 index ee9ff7cd3..000000000 --- a/frontend/app/types/dashboard/speedLocation.ts +++ /dev/null @@ -1,20 +0,0 @@ -interface ISpeedLocation { - avg?: number - chart?: any[] -} - -class SpeedLocation { - avg?: ISpeedLocation["avg"] - chart?: ISpeedLocation["chart"] - - constructor(data: ISpeedLocation) { - Object.assign(this, data) - } -} - -function fromJS(data = {}) { - if (data instanceof SpeedLocation) return data; - return new SpeedLocation(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/timeToRender.ts b/frontend/app/types/dashboard/timeToRender.ts deleted file mode 100644 index d04b40986..000000000 --- a/frontend/app/types/dashboard/timeToRender.ts +++ /dev/null @@ -1,20 +0,0 @@ -interface ITimeToRender { - avg?: number - chart?: any[] -} - -class TimeToRender { - avg: ITimeToRender["avg"] - chart: ITimeToRender["chart"] = [] - - constructor(data: ITimeToRender) { - Object.assign(this, data) - } -} - -function fromJS(data = {}) { - if (data instanceof TimeToRender) return data; - return new TimeToRender(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/topDomains.ts b/frontend/app/types/dashboard/topDomains.ts deleted file mode 100644 index dfbb2af7e..000000000 --- a/frontend/app/types/dashboard/topDomains.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Record } from 'immutable'; - -interface ITopDomains { - chart?: any[] -} - -class TopDomains { - chart: ITopDomains["chart"] = [] - - constructor(data: ITopDomains) { - this.chart = data.chart - } -} - -function fromJS(data = {}) { - if (data instanceof TopDomains) return data; - return new TopDomains(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/topMetrics.ts b/frontend/app/types/dashboard/topMetrics.ts deleted file mode 100644 index 59d7c89b7..000000000 --- a/frontend/app/types/dashboard/topMetrics.ts +++ /dev/null @@ -1,17 +0,0 @@ -interface ITopMetrics { - avgResponseTime: number - requestsCount: number - avgTimeTilFirstBite: number - avgDomCompleteTime: number -} - -export default class TopMetrics { - avgResponseTime: ITopMetrics["avgResponseTime"] = 0 - requestsCount: ITopMetrics["requestsCount"] = 0 - avgTimeTilFirstBite: ITopMetrics["avgTimeTilFirstBite"] = 0 - avgDomCompleteTime: ITopMetrics["avgDomCompleteTime"] = 0 - - constructor(data: ITopMetrics) { - Object.assign(this, data) - } -} \ No newline at end of file diff --git a/frontend/app/types/dashboard/userActivity.ts b/frontend/app/types/dashboard/userActivity.ts deleted file mode 100644 index 835fa4b8a..000000000 --- a/frontend/app/types/dashboard/userActivity.ts +++ /dev/null @@ -1,17 +0,0 @@ -interface IUserActivity { - avgVisitedPages: number; - avgVisitedPagesProgress: number; - avgDuration: number; - avgDurationProgress: number; -} - -export default class UserActivity { - avgVisitedPages: IUserActivity["avgVisitedPages"] - avgVisitedPagesProgress: IUserActivity["avgDurationProgress"] - avgDuration: IUserActivity["avgDuration"] - avgDurationProgress: IUserActivity["avgDurationProgress"] - - constructor(activity: IUserActivity) { - Object.assign(this, activity) - } -} \ No newline at end of file From bf1fb4f6805c1b9765f1a2f2505aff95e81d919a Mon Sep 17 00:00:00 2001 From: sylenien <nikita@openreplay.com> Date: Fri, 6 Jan 2023 13:14:39 +0100 Subject: [PATCH 22/65] change(ui): refactor alerts? --- frontend/app/Router.js | 2 - frontend/app/components/Alerts/AlertForm.js | 31 ++- .../Alerts/AlertFormModal/AlertFormModal.tsx | 25 +-- frontend/app/components/Alerts/AlertsList.js | 4 +- .../PredefinedWidgets/ErrorsPerDomain/Bar.tsx | 18 ++ .../ErrorsPerDomain/ErrorsPerDomain.tsx | 2 +- .../ErrorsPerDomain/bar.module.css | 6 + .../SlowestDomains/Bar.module.css | 6 + .../PredefinedWidgets/SlowestDomains/Bar.tsx | 19 ++ .../SlowestDomains/SlowestDomains.tsx | 2 +- .../components/Alerts/AlertListItem.tsx | 17 +- .../components/Alerts/AlertsList.tsx | 29 ++- .../components/Alerts/AlertsSearch.tsx | 23 +- .../components/Alerts/AlertsView.tsx | 14 +- .../Dashboard/components/Alerts/NewAlert.tsx | 74 +++---- .../Dashboard/components/Alerts/type.d.ts | 2 - ...{alertConditions.js => alertConditions.ts} | 2 +- .../{alertMetrics.js => alertMetrics.ts} | 2 +- frontend/app/duck/alerts.js | 6 +- frontend/app/mstore/alertsStore.ts | 75 +++++++ frontend/app/mstore/index.tsx | 5 + frontend/app/mstore/types/dashboard.ts | 4 +- frontend/app/mstore/types/widget.ts | 24 --- frontend/app/services/AlertsService.ts | 49 +++++ frontend/app/services/index.ts | 5 +- frontend/app/types/alert.js | 119 ----------- frontend/app/types/alert.ts | 197 ++++++++++++++++++ 27 files changed, 475 insertions(+), 287 deletions(-) create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/Bar.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/bar.module.css create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/Bar.module.css create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/Bar.tsx delete mode 100644 frontend/app/components/Dashboard/components/Alerts/type.d.ts rename frontend/app/constants/{alertConditions.js => alertConditions.ts} (93%) rename frontend/app/constants/{alertMetrics.js => alertMetrics.ts} (99%) create mode 100644 frontend/app/mstore/alertsStore.ts create mode 100644 frontend/app/services/AlertsService.ts delete mode 100644 frontend/app/types/alert.js create mode 100644 frontend/app/types/alert.ts diff --git a/frontend/app/Router.js b/frontend/app/Router.js index 584fb59e5..662a7a7a7 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -8,7 +8,6 @@ import { fetchUserInfo } from 'Duck/user'; import withSiteIdUpdater from 'HOCs/withSiteIdUpdater'; import Header from 'Components/Header/Header'; import { fetchList as fetchSiteList } from 'Duck/site'; -import { fetchList as fetchAlerts } from 'Duck/alerts'; import { withStore } from 'App/mstore'; import APIClient from './api_client'; @@ -114,7 +113,6 @@ const MULTIVIEW_INDEX_PATH = routes.multiviewIndex(); fetchTenants, setSessionPath, fetchSiteList, - fetchAlerts, } ) class Router extends React.Component { diff --git a/frontend/app/components/Alerts/AlertForm.js b/frontend/app/components/Alerts/AlertForm.js index fd6fe3d77..18ae1ac01 100644 --- a/frontend/app/components/Alerts/AlertForm.js +++ b/frontend/app/components/Alerts/AlertForm.js @@ -1,13 +1,12 @@ import React, { useEffect } from 'react'; import { Button, Form, Input, SegmentSelection, Checkbox, Icon } from 'UI'; import { alertConditions as conditions } from 'App/constants'; -import { client, CLIENT_TABS } from 'App/routes'; -import { connect } from 'react-redux'; import stl from './alertForm.module.css'; import DropdownChips from './DropdownChips'; import { validateEmail } from 'App/validate'; import cn from 'classnames'; -import { fetchTriggerOptions } from 'Duck/alerts'; +import { useStore } from 'App/mstore' +import { observer } from 'mobx-react-lite' import Select from 'Shared/Select'; const thresholdOptions = [ @@ -44,26 +43,28 @@ const Section = ({ index, title, description, content }) => ( </div> ); -const integrationsRoute = client(CLIENT_TABS.INTEGRATIONS); - const AlertForm = (props) => { const { - instance, slackChannels, msTeamsChannels, webhooks, - loading, onDelete, - deleting, - triggerOptions, style = { width: '580px', height: '100vh' }, } = props; + const { alertsStore } = useStore() + const { + instance, + triggerOptions, + loading, + } = alertsStore + const deleting = loading + const write = ({ target: { value, name } }) => props.edit({ [name]: value }); const writeOption = (e, { name, value }) => props.edit({ [name]: value.value }); const onChangeCheck = ({ target: { checked, name } }) => props.edit({ [name]: checked }); useEffect(() => { - props.fetchTriggerOptions(); + alertsStore.fetchTriggerOptions(); }, []); const writeQueryOption = (e, { name, value }) => { @@ -378,12 +379,4 @@ const AlertForm = (props) => { ); }; -export default connect( - (state) => ({ - instance: state.getIn(['alerts', 'instance']), - triggerOptions: state.getIn(['alerts', 'triggerOptions']), - loading: state.getIn(['alerts', 'saveRequest', 'loading']), - deleting: state.getIn(['alerts', 'removeRequest', 'loading']), - }), - { fetchTriggerOptions } -)(AlertForm); +export default observer(AlertForm); diff --git a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx index dc4c9db15..f03c479be 100644 --- a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx +++ b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx @@ -1,11 +1,12 @@ import React, { useEffect, useState } from 'react'; -import { SlideModal, IconButton } from 'UI'; -import { init, edit, save, remove } from 'Duck/alerts'; +import { SlideModal } from 'UI'; +import { useStore } from 'App/mstore' +import { observer } from 'mobx-react-lite' import { fetchList as fetchWebhooks } from 'Duck/webhook'; import AlertForm from '../AlertForm'; import { connect } from 'react-redux'; import { setShowAlerts } from 'Duck/dashboard'; -import { EMAIL, SLACK, WEBHOOK } from 'App/constants/schedule'; +import { SLACK, WEBHOOK } from 'App/constants/schedule'; import { confirm } from 'UI'; interface Props { @@ -14,12 +15,9 @@ interface Props { onClose?: () => void; webhooks: any; fetchWebhooks: Function; - save: Function; - remove: Function; - init: Function; - edit: Function; } function AlertFormModal(props: Props) { + const { alertsStore } = useStore() const { metricId = null, showModal = false, webhooks } = props; const [showForm, setShowForm] = useState(false); @@ -38,7 +36,7 @@ function AlertFormModal(props: Props) { const saveAlert = (instance) => { const wasUpdating = instance.exists(); - props.save(instance).then(() => { + alertsStore.save(instance).then(() => { if (!wasUpdating) { toggleForm(null, false); } @@ -56,7 +54,7 @@ function AlertFormModal(props: Props) { confirmation: `Are you sure you want to permanently delete this alert?`, }) ) { - props.remove(instance.alertId).then(() => { + alertsStore.remove(instance.alertId).then(() => { toggleForm(null, false); }); } @@ -64,7 +62,7 @@ function AlertFormModal(props: Props) { const toggleForm = (instance, state) => { if (instance) { - props.init(instance); + alertsStore.init(instance); } return setShowForm(state ? state : !showForm); }; @@ -83,7 +81,7 @@ function AlertFormModal(props: Props) { showModal && ( <AlertForm metricId={metricId} - edit={props.edit} + edit={alertsStore.edit} slackChannels={slackChannels} webhooks={hooks} onSubmit={saveAlert} @@ -100,7 +98,6 @@ function AlertFormModal(props: Props) { export default connect( (state) => ({ webhooks: state.getIn(['webhooks', 'list']), - instance: state.getIn(['alerts', 'instance']), }), - { init, edit, save, remove, fetchWebhooks, setShowAlerts } -)(AlertFormModal); + { fetchWebhooks, setShowAlerts } +)(observer(AlertFormModal)); diff --git a/frontend/app/components/Alerts/AlertsList.js b/frontend/app/components/Alerts/AlertsList.js index 5a874e0fa..a64094a0d 100644 --- a/frontend/app/components/Alerts/AlertsList.js +++ b/frontend/app/components/Alerts/AlertsList.js @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Loader, NoContent, Input, Button } from 'UI'; import AlertItem from './AlertItem'; -import { fetchList, init } from 'Duck/alerts'; +import { fetchList } from 'Duck/alerts'; import { connect } from 'react-redux'; import { getRE } from 'App/utils'; @@ -54,5 +54,5 @@ export default connect( instance: state.getIn(['alerts', 'instance']), loading: state.getIn(['alerts', 'loading']), }), - { fetchList, init } + { fetchList } )(AlertsList); diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/Bar.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/Bar.tsx new file mode 100644 index 000000000..a6ca35923 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/Bar.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import stl from './Bar.module.css' + +const Bar = ({ className = '', width = 0, avg, domain, color }) => { + return ( + <div className={className}> + <div className="flex items-center"> + <div className={stl.bar} style={{ width: `${width > 0 ? width : 5 }%`, backgroundColor: color }}></div> + <div className="ml-2"> + <span className="font-medium">{`${avg}`}</span> + </div> + </div> + <div className="text-sm leading-3 color-gray-medium">{domain}</div> + </div> + ) +} + +export default Bar \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx index 13643c769..d9e773948 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { NoContent } from 'UI'; import { Styles } from '../../common'; import { numberWithCommas } from 'App/utils'; -import Bar from 'App/components/Dashboard/Widgets/ErrorsPerDomain/Bar'; +import Bar from './Bar'; import { NO_METRIC_DATA } from 'App/constants/messages' interface Props { diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/bar.module.css b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/bar.module.css new file mode 100644 index 000000000..6dfde11a5 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/bar.module.css @@ -0,0 +1,6 @@ +.bar { + height: 5px; + background-color: red; + width: 100%; + border-radius: 3px; +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/Bar.module.css b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/Bar.module.css new file mode 100644 index 000000000..8037424f2 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/Bar.module.css @@ -0,0 +1,6 @@ +.bar { + height: 10px; + background-color: red; + width: 100%; + border-radius: 3px; +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/Bar.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/Bar.tsx new file mode 100644 index 000000000..179bca846 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/Bar.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import stl from './Bar.module.css' + +const Bar = ({ className = '', width = 0, avg, domain, color }) => { + return ( + <div className={className}> + <div className="flex items-center"> + <div className={stl.bar} style={{ width: `${width < 5 ? 5 : width }%`, backgroundColor: color }}></div> + <div className="ml-2 shrink-0"> + <span className="font-medium">{avg}</span> + <span> ms</span> + </div> + </div> + <div className="text-sm leading-3">{domain}</div> + </div> + ) +} + +export default Bar \ 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 fa4b703f2..758a6575a 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { NoContent } from 'UI'; import { Styles } from '../../common'; import { numberWithCommas } from 'App/utils'; -import Bar from 'App/components/Dashboard/Widgets/SlowestDomains/Bar'; +import Bar from './Bar'; import { NO_METRIC_DATA } from 'App/constants/messages' interface Props { diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx index 202bb7c21..1022842c9 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx @@ -7,6 +7,7 @@ import { numberWithCommas } from 'App/utils'; import { DateTime } from 'luxon'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import cn from 'classnames'; +import Alert from 'Types/alert'; const getThreshold = (threshold: number) => { if (threshold === 15) return '15 Minutes'; @@ -75,7 +76,7 @@ const getNotifyChannel = (alert: Record<string, any>, webhooks: Array<any>) => { interface Props extends RouteComponentProps { alert: Alert; siteId: string; - init: (alert?: Alert) => void; + init: (alert: Alert) => void; demo?: boolean; webhooks: Array<any>; } @@ -90,7 +91,7 @@ function AlertListItem(props: Props) { const onItemClick = () => { if (demo) return; const path = withSiteId(alertEdit(alert.alertId), siteId); - init(alert); + init(alert || {}); history.push(path); }; @@ -117,9 +118,9 @@ function AlertListItem(props: Props) { {demo ? DateTime.fromMillis(+new Date()).toFormat('LLL dd, yyyy, hh:mm a') : checkForRecent( - DateTime.fromMillis(alert.createdAt || +new Date()), - 'LLL dd, yyyy, hh:mm a' - )} + DateTime.fromMillis(alert.createdAt || +new Date()), + 'LLL dd, yyyy, hh:mm a' + )} </div> </div> <div className="color-gray-medium px-2 pb-2"> @@ -133,11 +134,13 @@ function AlertListItem(props: Props) { {numberWithCommas(alert.query.right)} {alert.metric.unit} </span> {' over the past '} - <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}>{getThreshold(alert.currentPeriod)}</span> + <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}>{getThreshold( + alert.currentPeriod)}</span> {alert.detectionMethod === 'change' ? ( <> {' compared to the previous '} - <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas ' }}>{getThreshold(alert.previousPeriod)}</span> + <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas ' }}>{getThreshold( + alert.previousPeriod)}</span> </> ) : null} {', notify me on '} diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx index 57af6efc2..a6f20b449 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx @@ -2,37 +2,36 @@ import React from 'react'; import { NoContent, Pagination, Icon } from 'UI'; import { filterList } from 'App/utils'; import { sliceListPerPage } from 'App/utils'; -import { fetchList } from 'Duck/alerts'; import { connect } from 'react-redux'; import { fetchList as fetchWebhooks } from 'Duck/webhook'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import AlertListItem from './AlertListItem' +import { useStore } from 'App/mstore' +import { observer } from 'mobx-react-lite' +import Alert from 'Types/alert' const pageSize = 10; interface Props { - fetchList: () => void; - list: any; - alertsSearch: any; siteId: string; webhooks: Array<any>; - init: (instance?: Alert) => void fetchWebhooks: () => void; } -function AlertsList({ fetchList, list: alertsList, alertsSearch, siteId, init, fetchWebhooks, webhooks }: Props) { - React.useEffect(() => { fetchList(); fetchWebhooks() }, []); +function AlertsList({ siteId, fetchWebhooks, webhooks }: Props) { + const { alertsStore } = useStore(); + const { alerts: alertsList, alertsSearch, fetchList, init } = alertsStore - const alertsArray = alertsList.toJS(); + React.useEffect(() => { fetchList(); fetchWebhooks() }, []); + const alertsArray = alertsList const [page, setPage] = React.useState(1); const filteredAlerts = filterList(alertsArray, alertsSearch, ['name'], (item, query) => query.test(item.query.left)) const list = alertsSearch !== '' ? filteredAlerts : alertsArray; - const lenth = list.length; return ( <NoContent - show={lenth === 0} + show={list.length === 0} title={ <div className="flex flex-col items-center justify-center"> <AnimatedSVG name={ICONS.NO_ALERTS} size={180} /> @@ -63,7 +62,7 @@ function AlertsList({ fetchList, list: alertsList, alertsSearch, siteId, init, f </div> <Pagination page={page} - totalPages={Math.ceil(lenth / pageSize)} + totalPages={Math.ceil(list.length / pageSize)} onPageChange={(page) => setPage(page)} limit={pageSize} debounceRequest={100} @@ -75,12 +74,8 @@ function AlertsList({ fetchList, list: alertsList, alertsSearch, siteId, init, f export default connect( (state) => ({ - // @ts-ignore - list: state.getIn(['alerts', 'list']).sort((a, b) => b.createdAt - a.createdAt), - // @ts-ignore - alertsSearch: state.getIn(['alerts', 'alertsSearch']), // @ts-ignore webhooks: state.getIn(['webhooks', 'list']), }), - { fetchList, fetchWebhooks } -)(AlertsList); + { fetchWebhooks } +)(observer(AlertsList)); diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertsSearch.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertsSearch.tsx index 0e4ffc5ef..0928f3a46 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertsSearch.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertsSearch.tsx @@ -1,20 +1,17 @@ import React, { useEffect, useState } from 'react'; import { Icon } from 'UI'; import { debounce } from 'App/utils'; -import { changeSearch } from 'Duck/alerts'; -import { connect } from 'react-redux'; +import { useStore } from 'App/mstore' +import { observer } from 'mobx-react-lite' let debounceUpdate: any = () => {}; -interface Props { - changeSearch: (value: string) => void; -} - -function AlertsSearch({ changeSearch }: Props) { - const [inputValue, setInputValue] = useState(''); +function AlertsSearch() { + const { alertsStore } = useStore(); + const [inputValue, setInputValue] = useState(alertsStore.alertsSearch); useEffect(() => { - debounceUpdate = debounce((value: string) => changeSearch(value), 500); + debounceUpdate = debounce((value: string) => alertsStore.changeSearch(value), 500); }, []); const write = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => { @@ -36,10 +33,4 @@ function AlertsSearch({ changeSearch }: Props) { ); } -export default connect( - (state) => ({ - // @ts-ignore - alertsSearch: state.getIn(['alerts', 'alertsSearch']), - }), - { changeSearch } -)(AlertsSearch); +export default observer(AlertsSearch); diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx index 07b77961a..631df8e43 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { Button, PageTitle, Icon, Link } from 'UI'; import withPageTitle from 'HOCs/withPageTitle'; -import { connect } from 'react-redux'; -import { init } from 'Duck/alerts'; import { withSiteId, alertCreate } from 'App/routes'; import AlertsList from './AlertsList'; @@ -10,10 +8,9 @@ import AlertsSearch from './AlertsSearch'; interface IAlertsView { siteId: string; - init: (instance?: Alert) => any; } -function AlertsView({ siteId, init }: IAlertsView) { +function AlertsView({ siteId }: IAlertsView) { return ( <div style={{ maxWidth: '1300px', margin: 'auto'}} className="bg-white rounded py-4 border"> <div className="flex items-center mb-4 justify-between px-6"> @@ -21,7 +18,7 @@ function AlertsView({ siteId, init }: IAlertsView) { <PageTitle title="Alerts" /> </div> <div className="ml-auto flex items-center"> - <Link to={withSiteId(alertCreate(), siteId)}><Button variant="primary" onClick={null}>Create Alert</Button></Link> + <Link to={withSiteId(alertCreate(), siteId)}><Button variant="primary">Create Alert</Button></Link> <div className="ml-4 w-1/4" style={{ minWidth: 300 }}> <AlertsSearch /> </div> @@ -31,12 +28,9 @@ function AlertsView({ siteId, init }: IAlertsView) { <Icon name="info-circle-fill" className="mr-2" size={16} /> Alerts helps your team stay up to date with the activity on your app. </div> - <AlertsList siteId={siteId} init={init} /> + <AlertsList siteId={siteId} /> </div> ); } -// @ts-ignore -const Container = connect(null, { init })(AlertsView); - -export default withPageTitle('Alerts - OpenReplay')(Container); +export default withPageTitle('Alerts - OpenReplay')(AlertsView); diff --git a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx index 335bf8295..4ad94e8a0 100644 --- a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx @@ -1,8 +1,7 @@ import React, { useEffect } from 'react'; -import { Form, SegmentSelection, Icon } from 'UI'; +import { Form, SegmentSelection } from 'UI'; import { connect } from 'react-redux'; import { validateEmail } from 'App/validate'; -import { fetchTriggerOptions, init, edit, save, remove, fetchList } from 'Duck/alerts'; import { confirm } from 'UI'; import { toast } from 'react-toastify'; import { SLACK, WEBHOOK, TEAMS } from 'App/constants/schedule'; @@ -10,7 +9,9 @@ import { fetchList as fetchWebhooks } from 'Duck/webhook'; import Breadcrumb from 'Shared/Breadcrumb'; import { withSiteId, alerts } from 'App/routes'; import { withRouter, RouteComponentProps } from 'react-router-dom'; - +import { useStore } from 'App/mstore' +import { observer } from 'mobx-react-lite' +import Alert from 'Types/alert' import cn from 'classnames'; import WidgetName from '../WidgetName'; import BottomButtons from './AlertForm/BottomButtons'; @@ -55,67 +56,63 @@ interface Select { interface IProps extends RouteComponentProps { siteId: string; - instance: Alert; slackChannels: any[]; webhooks: any[]; loading: boolean; deleting: boolean; triggerOptions: any[]; list: any; - fetchTriggerOptions: () => void; - edit: (query: any) => void; - init: (alert?: Alert) => any; - save: (alert: Alert) => Promise<any>; - remove: (alertId: string) => Promise<any>; onSubmit: (instance: Alert) => void; fetchWebhooks: () => void; - fetchList: () => void; } const NewAlert = (props: IProps) => { + const { alertsStore } = useStore(); const { - instance, - siteId, - webhooks, - loading, - deleting, - triggerOptions, + fetchTriggerOptions, init, edit, save, remove, - fetchWebhooks, fetchList, - list, + instance, + alerts: list, + triggerOptions, + loading, + } = alertsStore + const deleting = loading + + const { + siteId, + webhooks, + fetchWebhooks, } = props; useEffect(() => { init({}); - if (list.size === 0) fetchList(); - props.fetchTriggerOptions(); + if (list.length === 0) fetchList(); + fetchTriggerOptions(); fetchWebhooks(); }, []); useEffect(() => { - if (list.size > 0) { + if (list.length > 0) { const alertId = location.pathname.split('/').pop(); const currentAlert = list - .toJS() - .find((alert: Alert) => alert.alertId === parseInt(alertId, 10)); - init(currentAlert); + .find((alert: Alert) => alert.alertId === String(alertId)); + init(currentAlert || {}); } }, [list]); const write = ({ target: { value, name } }: React.ChangeEvent<HTMLInputElement>) => - props.edit({ [name]: value }); + edit({ [name]: value }); const writeOption = ( _: React.ChangeEvent, { name, value }: { name: string; value: Record<string, any> } - ) => props.edit({ [name]: value.value }); + ) => edit({ [name]: value.value }); - const onChangeCheck = ({ target: { checked, name } }: React.ChangeEvent<HTMLInputElement>) => - props.edit({ [name]: checked }); + const onChangeCheck = ({ target: { checked, name } }: React.ChangeEvent<HTMLInputElement>) => edit({ [name]: checked }); const onDelete = async (instance: Alert) => { if ( @@ -170,12 +167,12 @@ const NewAlert = (props: IProps) => { { name, value }: { name: string; value: string } ) => { const { query } = instance; - props.edit({ query: { ...query, [name]: value } }); + edit({ query: { ...query, [name]: value } }); }; const writeQuery = ({ target: { value, name } }: React.ChangeEvent<HTMLInputElement>) => { const { query } = instance; - props.edit({ query: { ...query, [name]: value } }); + edit({ query: { ...query, [name]: value } }); }; const metric = @@ -222,7 +219,7 @@ const NewAlert = (props: IProps) => { outline name="detectionMethod" className="my-3 w-1/4" - onSelect={(e: any, { name, value }: any) => props.edit({ [name]: value })} + onSelect={(e: any, { name, value }: any) => edit({ [name]: value })} value={{ value: instance.detectionMethod }} list={[ { name: 'Threshold', value: 'threshold' }, @@ -294,20 +291,9 @@ const NewAlert = (props: IProps) => { export default withRouter( connect( (state) => ({ - // @ts-ignore - instance: state.getIn(['alerts', 'instance']), - //@ts-ignore - list: state.getIn(['alerts', 'list']), - // @ts-ignore - triggerOptions: state.getIn(['alerts', 'triggerOptions']), - // @ts-ignore - loading: state.getIn(['alerts', 'saveRequest', 'loading']), - // @ts-ignore - deleting: state.getIn(['alerts', 'removeRequest', 'loading']), // @ts-ignore webhooks: state.getIn(['webhooks', 'list']), }), - { fetchTriggerOptions, init, edit, save, remove, fetchWebhooks, fetchList } - // @ts-ignore - )(NewAlert) + { fetchWebhooks } + )(observer(NewAlert)) ); diff --git a/frontend/app/components/Dashboard/components/Alerts/type.d.ts b/frontend/app/components/Dashboard/components/Alerts/type.d.ts deleted file mode 100644 index 6ac1a8f34..000000000 --- a/frontend/app/components/Dashboard/components/Alerts/type.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -// TODO burn the immutable and make typing this possible -type Alert = Record<string, any> diff --git a/frontend/app/constants/alertConditions.js b/frontend/app/constants/alertConditions.ts similarity index 93% rename from frontend/app/constants/alertConditions.js rename to frontend/app/constants/alertConditions.ts index 09dfefc58..0c19fd51e 100644 --- a/frontend/app/constants/alertConditions.js +++ b/frontend/app/constants/alertConditions.ts @@ -3,4 +3,4 @@ export default [ { value: '>=', label: 'above or equal to' }, { value: '<', label: 'below' }, { value: '<=', label: 'below or equal to' }, -]; +] as const; diff --git a/frontend/app/constants/alertMetrics.js b/frontend/app/constants/alertMetrics.ts similarity index 99% rename from frontend/app/constants/alertMetrics.js rename to frontend/app/constants/alertMetrics.ts index 01fc24acb..b7ee3ce4f 100644 --- a/frontend/app/constants/alertMetrics.js +++ b/frontend/app/constants/alertMetrics.ts @@ -18,4 +18,4 @@ export default [ { value: 'performance.crashes.count', label: 'performance.crashes.count', unit: '' }, { value: 'errors.javascript.count', label: 'errors.javascript.count', unit: '' }, { value: 'errors.backend.count', label: 'errors.backend.count', unit: '' }, -]; +] as const; diff --git a/frontend/app/duck/alerts.js b/frontend/app/duck/alerts.js index 6591ae7e7..31721ff45 100644 --- a/frontend/app/duck/alerts.js +++ b/frontend/app/duck/alerts.js @@ -1,16 +1,16 @@ import Alert from 'Types/alert'; import { Map } from 'immutable'; import crudDuckGenerator from './tools/crudDuck'; -import withRequestState, { RequestTypes } from 'Duck/requestStateCreator'; +import { RequestTypes } from 'Duck/requestStateCreator'; import { reduceDucks } from 'Duck/tools'; const name = 'alert' const idKey = 'alertId'; -const crudDuck = crudDuckGenerator(name, Alert, { idKey: idKey }); +const crudDuck = crudDuckGenerator(name, (d) => new Alert(d), { idKey: idKey }); export const { fetchList, init, edit, remove } = crudDuck.actions; const FETCH_TRIGGER_OPTIONS = new RequestTypes(`${name}/FETCH_TRIGGER_OPTIONS`); const CHANGE_SEARCH = `${name}/CHANGE_SEARCH` - +console.log(fetchList(), init(), edit(), remove()) const initialState = Map({ definedPercent: 0, triggerOptions: [], diff --git a/frontend/app/mstore/alertsStore.ts b/frontend/app/mstore/alertsStore.ts new file mode 100644 index 000000000..25f90644d --- /dev/null +++ b/frontend/app/mstore/alertsStore.ts @@ -0,0 +1,75 @@ +import { makeAutoObservable } from 'mobx' +import Alert, { IAlert } from 'Types/alert' +import { alertsService } from 'App/services' + +export default class AlertsStore { + alerts: Alert[] = []; + triggerOptions: { label: string, value: string | number, unit?: string }[] = []; + alertsSearch = ''; + // @ts-ignore + instance: Alert = new Alert({}, false); + loading = false + + constructor() { + makeAutoObservable(this); + } + + changeSearch(value: string) { + this.alertsSearch = value; + } + + async fetchList() { + this.loading = true + try { + const list = await alertsService.fetchList(); + this.alerts = list.map(alert => new Alert(alert, true)); + } catch (e) { + console.error(e) + } finally { + this.loading = false + } + } + + async save(inst: Alert) { + this.loading = true + try { + await alertsService.save(inst ? inst : this.instance) + this.instance.isExists = true + } catch (e) { + console.error(e) + } finally { + this.loading = false + } + } + + async remove(id: string) { + this.loading = true + try { + await alertsService.remove(id) + } catch (e) { + console.error(e) + } finally { + this.loading = false + } + } + + async fetchTriggerOptions() { + this.loading = true + try { + const options = await alertsService.fetchTriggerOptions(); + this.triggerOptions = options.map(({ name, value }) => ({ label: name, value })) + } catch (e) { + console.error(e) + } finally { + this.loading = false + } + } + + init(inst: Partial<IAlert> | Alert) { + this.instance = inst instanceof Alert ? inst : new Alert(inst, false) + } + + edit(diff: Partial<Alert>) { + Object.assign(this.instance, diff) + } +} \ No newline at end of file diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx index 707fb175a..b9567c817 100644 --- a/frontend/app/mstore/index.tsx +++ b/frontend/app/mstore/index.tsx @@ -16,6 +16,7 @@ import { notesService, recordingsService, configService, + alertsService, } from 'App/services'; import SettingsStore from './settingsStore'; import AuditStore from './auditStore'; @@ -27,6 +28,7 @@ import BugReportStore from './bugReportStore' import RecordingsStore from './recordingsStore' import AssistMultiviewStore from './assistMultiviewStore'; import WeeklyReportStore from './weeklyReportConfigStore' +import AlertStore from './alertsStore' export class RootStore { dashboardStore: DashboardStore; @@ -44,6 +46,7 @@ export class RootStore { recordingsStore: RecordingsStore; assistMultiviewStore: AssistMultiviewStore; weeklyReportStore: WeeklyReportStore + alertsStore: AlertStore constructor() { this.dashboardStore = new DashboardStore(); @@ -61,6 +64,7 @@ export class RootStore { this.recordingsStore = new RecordingsStore(); this.assistMultiviewStore = new AssistMultiviewStore(); this.weeklyReportStore = new WeeklyReportStore(); + this.alertsStore = new AlertStore(); } initClient() { @@ -75,6 +79,7 @@ export class RootStore { notesService.initClient(client) recordingsService.initClient(client); configService.initClient(client); + alertsService.initClient(client) } } diff --git a/frontend/app/mstore/types/dashboard.ts b/frontend/app/mstore/types/dashboard.ts index 0213f9b0d..19faffe7b 100644 --- a/frontend/app/mstore/types/dashboard.ts +++ b/frontend/app/mstore/types/dashboard.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, observable, action, runInAction } from "mobx" +import { makeAutoObservable, runInAction } from "mobx" import Widget from "./widget" import { dashboardService } from "App/services" import { toast } from 'react-toastify'; @@ -6,7 +6,7 @@ import { DateTime } from 'luxon'; export default class Dashboard { public static get ID_KEY():string { return "dashboardId" } - dashboardId: any = undefined + dashboardId?: string = undefined name: string = "Untitled Dashboard" description: string = "" isPublic: boolean = true diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index 4b6760bc1..9d8f5a151 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -15,7 +15,6 @@ export default class Widget { widgetId: any = undefined category?: string = undefined name: string = "Untitled Card" - // metricType: string = "timeseries" metricType: string = "timeseries" metricOf: string = "sessionCount" metricValue: string = "" @@ -37,8 +36,6 @@ export default class Widget { period: Record<string, any> = Period({ rangeName: LAST_24_HOURS }) // temp value in detail view hasChanged: boolean = false - sessionsLoading: boolean = false - position: number = 0 data: any = { sessions: [], @@ -51,7 +48,6 @@ export default class Widget { isLoading: boolean = false isValid: boolean = false dashboardId: any = undefined - colSpan: number = 2 predefinedKey: string = '' constructor() { @@ -103,10 +99,6 @@ export default class Widget { return this } - setPeriod(period: any) { - this.period = new Period({ start: period.startDate, end: period.endDate, rangeName: period.rangeName }) - } - toWidget(): any { return { config: { @@ -117,10 +109,6 @@ export default class Widget { } } - toJsonDrilldown() { - return this.series.map((series: any) => series.toJson()) - } - toJson() { return { metricId: this.metricId, @@ -171,18 +159,6 @@ export default class Widget { }) } - fetchIssues(filter: any): Promise<any> { - return new Promise((resolve) => { - metricService.fetchIssues(filter).then((response: any) => { - const significantIssues = response.issues.significant ? response.issues.significant.map((issue: any) => new Funnelissue().fromJSON(issue)) : [] - const insignificantIssues = response.issues.insignificant ? response.issues.insignificant.map((issue: any) => new Funnelissue().fromJSON(issue)) : [] - resolve({ - issues: significantIssues.length > 0 ? significantIssues : insignificantIssues, - }) - }) - }) - } - fetchIssue(funnelId: any, issueId: any, params: any): Promise<any> { return new Promise((resolve, reject) => { metricService.fetchIssue(funnelId, issueId, params).then((response: any) => { diff --git a/frontend/app/services/AlertsService.ts b/frontend/app/services/AlertsService.ts new file mode 100644 index 000000000..7ba1702e9 --- /dev/null +++ b/frontend/app/services/AlertsService.ts @@ -0,0 +1,49 @@ +import APIClient from 'App/api_client'; +import Alert, { IAlert } from "Types/alert"; + +export default class AlertsService { + private client: APIClient; + + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } + + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } + + save(instance: Alert): Promise<IAlert> { + return this.client.post(instance['alertId'] ? `/alerts/${instance['alertId']}` : '/alerts', instance.toData()) + .then(response => response.json()) + .then(response => response.data || {}) + .catch(Promise.reject) + } + + fetchTriggerOptions(): Promise<{ name: string, value: string | number }[]> { + return this.client.get('/alerts/triggers') + .then(r => r.json()) + .then(j => j.data || []) + .catch(Promise.reject) + } + + fetchList(): Promise<IAlert[]> { + return this.client.get('/alerts') + .then(r => r.json()) + .then(j => j.data || []) + .catch(Promise.reject) + } + + fetch(id: string): Promise<IAlert> { + return this.client.get(`/alerts/${id}`) + .then(r => r.json()) + .then(j => j.data || {}) + .catch(Promise.reject) + } + + remove(id: string): Promise<IAlert> { + return this.client.delete(`/alerts/${id}`) + .then(r => r.json()) + .then(j => j.data || {}) + .catch(Promise.reject) + } +} \ No newline at end of file diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts index 5033c4ed3..72d0f4d92 100644 --- a/frontend/app/services/index.ts +++ b/frontend/app/services/index.ts @@ -8,7 +8,7 @@ import ErrorService from "./ErrorService"; import NotesService from "./NotesService"; import RecordingsService from "./RecordingsService"; import ConfigService from './ConfigService' - +import AlertsService from './AlertsService' export const dashboardService = new DashboardService(); export const metricService = new MetricService(); export const sessionService = new SessionSerivce(); @@ -18,4 +18,5 @@ export const auditService = new AuditService(); export const errorService = new ErrorService(); export const notesService = new NotesService(); export const recordingsService = new RecordingsService(); -export const configService = new ConfigService(); \ No newline at end of file +export const configService = new ConfigService(); +export const alertsService = new AlertsService(); \ No newline at end of file diff --git a/frontend/app/types/alert.js b/frontend/app/types/alert.js deleted file mode 100644 index 3e46a2e06..000000000 --- a/frontend/app/types/alert.js +++ /dev/null @@ -1,119 +0,0 @@ -import Record from 'Types/Record'; -import { notEmptyString, validateName, validateNumber, validateEmail } from 'App/validate'; -import { List, Map } from 'immutable'; -import { alertMetrics as metrics, alertConditions as conditions } from 'App/constants'; -// import Filter from './filter'; - -const metricsMap = {} -const conditionsMap = {} -metrics.forEach(m => { metricsMap[m.value] = m }); -conditions.forEach(c => { conditionsMap[c.value] = c }); - -export default Record({ - alertId: '', - projectId: undefined, - name: 'Untitled Alert', - description: '', - active: true, - currentPeriod: 15, - previousPeriod: 15, - detectionMethod: 'threshold', - change: 'change', - query: Map({ left: '', operator: '', right: ''}), - options: Map({ currentPeriod: 15, previousPeriod: 15 }), - createdAt: undefined, - - slack: false, - slackInput: [], - webhook: false, - webhookInput: [], - email: false, - emailInput: [], - msteams: false, - msteamsInput: [], - hasNotification: false, - metric: '', - condition: '', -}, { - idKey: 'alertId', - methods: { - validate() { - return notEmptyString(this.name) && - this.query.left && this.query.right && validateNumber(this.query.right) && this.query.right > 0 && this.query.operator && - (this.slack ? this.slackInput.length > 0 : true) && - (this.email ? this.emailInput.length > 0 : true) && - (this.msteams ? this.msteamsInput.length > 0 : true) && - (this.webhook ? this.webhookInput.length > 0 : true); - }, - toData() { - const js = this.toJS(); - - const options = { message: [] } - if (js.slack && js.slackInput) - options.message = options.message.concat(js.slackInput.map(i => ({ type: 'slack', value: i }))) - // options.message.push({ type: 'slack', value: js.slackInput }) - if (js.email && js.emailInput) - options.message = options.message.concat(js.emailInput.map(i => ({ type: 'email', value: i }))) - // options.message.push({ type: 'email', value: js.emailInput }) - if (js.webhook && js.webhookInput) - options.message = options.message.concat(js.webhookInput.map(i => ({ type: 'webhook', value: i }))) - // options.message.push({ type: 'webhook', value: js.webhookInput }) - if (js.msteams && js.msteamsInput) - options.message = options.message.concat(js.msteamsInput.map(i => ({ type: 'msteams', value: i }))) - - options.previousPeriod = js.previousPeriod - options.currentPeriod = js.currentPeriod - - js.detection_method = js.detectionMethod; - delete js.slack; - delete js.webhook; - delete js.email; - delete js.slackInput; - delete js.webhookInput; - delete js.emailInput; - delete js.msteams; - delete js.msteamsInput; - delete js.hasNotification; - delete js.metric; - delete js.condition; - delete js.currentPeriod; - delete js.previousPeriod; - - return { ...js, options: options }; - }, - }, - fromJS: (item) => { - const options = item.options || { currentPeriod: 15, previousPeriod: 15, message: [] }; - const query = item.query || { left: '', operator: '', right: ''}; - - const slack = List(options.message).filter(i => i.type === 'slack'); - const email = List(options.message).filter(i => i.type === 'email'); - const webhook = List(options.message).filter(i => i.type === 'webhook'); - const msteams = List(options.message).filter(i => i.type === 'msteams'); - - return { - ...item, - metric: metricsMap[query.left], - condition: item.query ? conditionsMap[item.query.operator] : {}, - detectionMethod: item.detectionMethod || item.detection_method, - query: query, - options: options, - previousPeriod: options.previousPeriod, - currentPeriod: options.currentPeriod, - - slack: slack.size > 0, - slackInput: slack.map(i => parseInt(i.value)).toJS(), - - msteams: msteams.size > 0, - msteamsInput: msteams.map(i => parseInt(i.value)).toJS(), - - email: email.size > 0, - emailInput: email.map(i => i.value).toJS(), - - webhook: webhook.size > 0, - webhookInput: webhook.map(i => parseInt(i.value)).toJS(), - - hasNotification: !!slack || !!email || !!webhook - } - }, -}); diff --git a/frontend/app/types/alert.ts b/frontend/app/types/alert.ts new file mode 100644 index 000000000..ff127a240 --- /dev/null +++ b/frontend/app/types/alert.ts @@ -0,0 +1,197 @@ +import { notEmptyString, validateNumber } from 'App/validate'; +import { alertMetrics as metrics, alertConditions as conditions } from 'App/constants'; + +const metricsMap = {} +const conditionsMap = {} +// @ts-ignore +metrics.forEach(m => { metricsMap[m.value] = m }); +// @ts-ignore +conditions.forEach(c => { conditionsMap[c.value] = c }); + +export interface IAlert { + alertId: string; + projectId?: string; + name: string; + description: string; + active: boolean; + currentPeriod: number; + previousPeriod: number; + detectionMethod: string; + detection_method?: string; + change: string; + query: { left: string, operator: string, right: string }; + options: { currentPeriod: number, previousPeriod: number, message: {type: string, value: string}[] }; + createdAt?: number; + slack: boolean; + slackInput: string[]; + webhook: boolean; + webhookInput: string[]; + email: boolean; + emailInput: string[]; + msteams: boolean; + msteamsInput: string[]; + hasNotification: boolean; + metric: { unit: any }; + condition: string; +} + +const defaults = { + alertId: '', + projectId: undefined, + name: 'Untitled Alert', + description: '', + active: true, + currentPeriod: 15, + previousPeriod: 15, + detectionMethod: 'threshold', + change: 'change', + query: { left: '', operator: '', right: '' }, + options: { currentPeriod: 15, previousPeriod: 15 }, + createdAt: undefined, + + slack: false, + slackInput: [], + webhook: false, + webhookInput: [], + email: false, + emailInput: [], + msteams: false, + msteamsInput: [], + hasNotification: false, + metric: '', + condition: '', +} as unknown as IAlert + +export default class Alert { + alertId: IAlert["alertId"] + projectId?: IAlert["projectId"] + name: IAlert["name"] + description: IAlert["description"] + active: IAlert["active"] + currentPeriod: IAlert["currentPeriod"] + previousPeriod: IAlert["previousPeriod"] + detectionMethod: IAlert["detectionMethod"] + detection_method: IAlert["detection_method"] + change: IAlert["change"] + query:IAlert["query"] + options: IAlert["options"] + createdAt?: IAlert["createdAt"] + slack: IAlert["slack"] + slackInput: IAlert["slackInput"] + webhook: IAlert["webhook"] + webhookInput: IAlert["webhookInput"] + email: IAlert["email"] + emailInput: IAlert["emailInput"] + msteams: IAlert["msteams"] + msteamsInput: IAlert["msteamsInput"] + hasNotification: IAlert["hasNotification"] + metric: IAlert["metric"] + condition: IAlert["condition"] + isExists = false + + constructor(item: Partial<IAlert> = defaults, isExists: boolean) { + Object.assign(defaults, item) + + const options = defaults.options || { currentPeriod: 15, previousPeriod: 15, message: [] }; + const query = defaults.query || { left: '', operator: '', right: ''}; + + const slack = options.message?.filter(i => i.type === 'slack') || []; + const email = options.message?.filter(i => i.type === 'email') || []; + const webhook = options.message?.filter(i => i.type === 'webhook') || []; + const msteams = options.message?.filter(i => i.type === 'msteams') || []; + + Object.assign(this, { + ...defaults, + // @ts-ignore + metric: metricsMap[query.left], + alertId: String(defaults.alertId), + // @ts-ignore TODO + condition: defaults.query ? conditionsMap[defaults.query.operator] : {}, + detectionMethod: defaults.detectionMethod || defaults.detection_method, + query: query, + options: options, + previousPeriod: options.previousPeriod, + currentPeriod: options.currentPeriod, + + slack: slack.length > 0, + slackInput: slack.map(i => parseInt(i.value)), + + msteams: msteams.length > 0, + msteamsInput: msteams.map(i => parseInt(i.value)), + + email: email.length > 0, + emailInput: email.map(i => i.value), + + webhook: webhook.length > 0, + webhookInput: webhook.map(i => parseInt(i.value)), + + hasNotification: !!slack || !!email || !!webhook, + isExists, + }) + } + + validate() { + return notEmptyString(this.name) && + this.query.left && this.query.right && validateNumber(this.query.right) && parseInt(this.query.right, 10) > 0 && this.query.operator && + (this.slack ? this.slackInput.length > 0 : true) && + (this.email ? this.emailInput.length > 0 : true) && + (this.msteams ? this.msteamsInput.length > 0 : true) && + (this.webhook ? this.webhookInput.length > 0 : true); + } + + + toData() { + const js = { ...this }; + + const options = { message: [], previousPeriod: 0, currentPeriod: 0 } + if (js.slack && js.slackInput) + // @ts-ignore + options.message = options.message.concat(js.slackInput.map(i => ({ type: 'slack', value: i }))) + if (js.email && js.emailInput) + // @ts-ignore + options.message = options.message.concat(js.emailInput.map(i => ({ type: 'email', value: i }))) + if (js.webhook && js.webhookInput) + // @ts-ignore + options.message = options.message.concat(js.webhookInput.map(i => ({ type: 'webhook', value: i }))) + if (js.msteams && js.msteamsInput) + // @ts-ignore + options.message = options.message.concat(js.msteamsInput.map(i => ({ type: 'msteams', value: i }))) + + options.previousPeriod = js.previousPeriod + options.currentPeriod = js.currentPeriod + + js.detection_method = js.detectionMethod; + // @ts-ignore + delete js.slack; + // @ts-ignore + delete js.webhook; + // @ts-ignore + delete js.email; + // @ts-ignore + delete js.slackInput; + // @ts-ignore + delete js.webhookInput; + // @ts-ignore + delete js.emailInput; + // @ts-ignore + delete js.msteams; + // @ts-ignore + delete js.msteamsInput; + // @ts-ignore + delete js.hasNotification; + // @ts-ignore + delete js.metric; + // @ts-ignore + delete js.condition; + // @ts-ignore + delete js.currentPeriod; + // @ts-ignore + delete js.previousPeriod; + + return { ...js, options: options }; + } + + exists() { + return this.isExists + } +} From f2333e10e17f1227d195a750e4754fe29dc0cd7a Mon Sep 17 00:00:00 2001 From: sylenien <nikita@openreplay.com> Date: Fri, 6 Jan 2023 16:26:13 +0100 Subject: [PATCH 23/65] fix(ui): fix method context --- frontend/app/mstore/alertsStore.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/app/mstore/alertsStore.ts b/frontend/app/mstore/alertsStore.ts index 25f90644d..744370671 100644 --- a/frontend/app/mstore/alertsStore.ts +++ b/frontend/app/mstore/alertsStore.ts @@ -14,11 +14,11 @@ export default class AlertsStore { makeAutoObservable(this); } - changeSearch(value: string) { + changeSearch = (value: string) => { this.alertsSearch = value; } - async fetchList() { + fetchList = async () => { this.loading = true try { const list = await alertsService.fetchList(); @@ -30,7 +30,7 @@ export default class AlertsStore { } } - async save(inst: Alert) { + save = async (inst: Alert) => { this.loading = true try { await alertsService.save(inst ? inst : this.instance) @@ -42,7 +42,7 @@ export default class AlertsStore { } } - async remove(id: string) { + remove = async (id: string) => { this.loading = true try { await alertsService.remove(id) @@ -53,7 +53,7 @@ export default class AlertsStore { } } - async fetchTriggerOptions() { + fetchTriggerOptions = async () => { this.loading = true try { const options = await alertsService.fetchTriggerOptions(); @@ -65,11 +65,11 @@ export default class AlertsStore { } } - init(inst: Partial<IAlert> | Alert) { + init = (inst: Partial<IAlert> | Alert) => { this.instance = inst instanceof Alert ? inst : new Alert(inst, false) } - edit(diff: Partial<Alert>) { + edit = (diff: Partial<Alert>) => { Object.assign(this.instance, diff) } } \ No newline at end of file From ef090aa696020684e0b257acbe195a76a9c5586c Mon Sep 17 00:00:00 2001 From: sylenien <nikita@openreplay.com> Date: Mon, 9 Jan 2023 14:52:45 +0100 Subject: [PATCH 24/65] fix(ui): remove old alerts --- frontend/app/components/Alerts/AlertForm.js | 26 ++-- .../Alerts/AlertFormModal/AlertFormModal.tsx | 39 ++++-- frontend/app/components/Alerts/AlertItem.js | 57 -------- .../app/components/Alerts/AlertTypeLabel.js | 13 -- frontend/app/components/Alerts/Alerts.js | 109 --------------- frontend/app/components/Alerts/AlertsList.js | 58 -------- .../Alerts/Notifications/ListItem/ListItem.js | 33 ----- .../Alerts/Notifications/ListItem/index.js | 1 - .../ListItem/listItem.module.css | 7 - .../components/Alerts/alertItem.module.css | 9 -- .../Alerts/alertTypeLabel.module.css | 11 -- .../app/components/Alerts/alerts.stories.js | 130 ------------------ frontend/app/components/Alerts/index.js | 1 - .../components/Alerts/AlertListItem.tsx | 2 +- .../DashboardView/DashboardView.tsx | 2 +- .../FunnelIssueDetails/FunnelIssueDetails.tsx | 2 +- .../Funnels/FunnelIssues/FunnelIssues.tsx | 2 +- .../WidgetSessions/WidgetSessions.tsx | 3 +- .../components/WidgetWrapper/AlertButton.tsx | 15 +- .../components/WidgetWrapper/WidgetIcon.tsx | 5 +- frontend/app/components/Header/Header.js | 4 - frontend/app/duck/alerts.js | 63 --------- frontend/app/duck/index.ts | 2 - frontend/app/mstore/alertsStore.ts | 8 +- frontend/app/mstore/dashboardStore.ts | 4 + frontend/app/types/alert.ts | 5 +- 26 files changed, 68 insertions(+), 543 deletions(-) delete mode 100644 frontend/app/components/Alerts/AlertItem.js delete mode 100644 frontend/app/components/Alerts/AlertTypeLabel.js delete mode 100644 frontend/app/components/Alerts/Alerts.js delete mode 100644 frontend/app/components/Alerts/AlertsList.js delete mode 100644 frontend/app/components/Alerts/Notifications/ListItem/ListItem.js delete mode 100644 frontend/app/components/Alerts/Notifications/ListItem/index.js delete mode 100644 frontend/app/components/Alerts/Notifications/ListItem/listItem.module.css delete mode 100644 frontend/app/components/Alerts/alertItem.module.css delete mode 100644 frontend/app/components/Alerts/alertTypeLabel.module.css delete mode 100644 frontend/app/components/Alerts/alerts.stories.js delete mode 100644 frontend/app/components/Alerts/index.js delete mode 100644 frontend/app/duck/alerts.js diff --git a/frontend/app/components/Alerts/AlertForm.js b/frontend/app/components/Alerts/AlertForm.js index 18ae1ac01..fcec66918 100644 --- a/frontend/app/components/Alerts/AlertForm.js +++ b/frontend/app/components/Alerts/AlertForm.js @@ -43,7 +43,7 @@ const Section = ({ index, title, description, content }) => ( </div> ); -const AlertForm = (props) => { +function AlertForm(props) { const { slackChannels, msTeamsChannels, @@ -53,28 +53,28 @@ const AlertForm = (props) => { } = props; const { alertsStore } = useStore() const { - instance, triggerOptions, loading, } = alertsStore + const instance = alertsStore.instance const deleting = loading - const write = ({ target: { value, name } }) => props.edit({ [name]: value }); - const writeOption = (e, { name, value }) => props.edit({ [name]: value.value }); - const onChangeCheck = ({ target: { checked, name } }) => props.edit({ [name]: checked }); + const write = ({ target: { value, name } }) => alertsStore.edit({ [name]: value }); + const writeOption = (e, { name, value }) => alertsStore.edit({ [name]: value.value }); + const onChangeCheck = ({ target: { checked, name } }) => alertsStore.edit({ [name]: checked }); useEffect(() => { - alertsStore.fetchTriggerOptions(); + void alertsStore.fetchTriggerOptions(); }, []); const writeQueryOption = (e, { name, value }) => { const { query } = instance; - props.edit({ query: { ...query, [name]: value } }); + alertsStore.edit({ query: { ...query, [name]: value } }); }; const writeQuery = ({ target: { value, name } }) => { const { query } = instance; - props.edit({ query: { ...query, [name]: value } }); + alertsStore.edit({ query: { ...query, [name]: value } }); }; const metric = @@ -112,7 +112,7 @@ const AlertForm = (props) => { primary name="detectionMethod" className="my-3" - onSelect={(e, { name, value }) => props.edit({ [name]: value })} + onSelect={(e, { name, value }) => alertsStore.edit({ [name]: value })} value={{ value: instance.detectionMethod }} list={[ { name: 'Threshold', value: 'threshold' }, @@ -294,7 +294,7 @@ const AlertForm = (props) => { selected={instance.slackInput} options={slackChannels} placeholder="Select Channel" - onChange={(selected) => props.edit({ slackInput: selected })} + onChange={(selected) => alertsStore.edit({ slackInput: selected })} /> </div> </div> @@ -308,7 +308,7 @@ const AlertForm = (props) => { selected={instance.msteamsInput} options={msTeamsChannels} placeholder="Select Channel" - onChange={(selected) => props.edit({ msteamsInput: selected })} + onChange={(selected) => alertsStore.edit({ msteamsInput: selected })} /> </div> </div> @@ -323,7 +323,7 @@ const AlertForm = (props) => { validate={validateEmail} selected={instance.emailInput} placeholder="Type and press Enter key" - onChange={(selected) => props.edit({ emailInput: selected })} + onChange={(selected) => alertsStore.edit({ emailInput: selected })} /> </div> </div> @@ -337,7 +337,7 @@ const AlertForm = (props) => { selected={instance.webhookInput} options={webhooks} placeholder="Select Webhook" - onChange={(selected) => props.edit({ webhookInput: selected })} + onChange={(selected) => alertsStore.edit({ webhookInput: selected })} /> </div> )} diff --git a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx index f03c479be..48a46a3a0 100644 --- a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx +++ b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx @@ -5,10 +5,15 @@ import { observer } from 'mobx-react-lite' import { fetchList as fetchWebhooks } from 'Duck/webhook'; import AlertForm from '../AlertForm'; import { connect } from 'react-redux'; -import { setShowAlerts } from 'Duck/dashboard'; -import { SLACK, WEBHOOK } from 'App/constants/schedule'; +import { SLACK, TEAMS, WEBHOOK } from 'App/constants/schedule'; import { confirm } from 'UI'; +interface Select { + label: string; + value: string | number +} + + interface Props { showModal?: boolean; metricId?: number; @@ -25,14 +30,23 @@ function AlertFormModal(props: Props) { props.fetchWebhooks(); }, []); - const slackChannels = webhooks - .filter((hook) => hook.type === SLACK) - .map(({ webhookId, name }) => ({ value: webhookId, text: name })) - .toJS(); - const hooks = webhooks - .filter((hook) => hook.type === WEBHOOK) - .map(({ webhookId, name }) => ({ value: webhookId, text: name })) - .toJS(); + + const slackChannels: Select[] = [] + const hooks: Select[] = [] + const msTeamsChannels: Select[] = [] + + webhooks.forEach((hook) => { + const option = { value: hook.webhookId, label: hook.name } + if (hook.type === SLACK) { + slackChannels.push(option) + } + if (hook.type === WEBHOOK) { + hooks.push(option) + } + if (hook.type === TEAMS) { + msTeamsChannels.push(option) + } + }) const saveAlert = (instance) => { const wasUpdating = instance.exists(); @@ -71,7 +85,7 @@ function AlertFormModal(props: Props) { <SlideModal title={ <div className="flex items-center"> - <span className="mr-3">{'Create Alert'}</span> + <span className="m-3">{'Create Alert'}</span> </div> } isDisplayed={showModal} @@ -83,6 +97,7 @@ function AlertFormModal(props: Props) { metricId={metricId} edit={alertsStore.edit} slackChannels={slackChannels} + msTeamsChannels={msTeamsChannels} webhooks={hooks} onSubmit={saveAlert} onClose={props.onClose} @@ -99,5 +114,5 @@ export default connect( (state) => ({ webhooks: state.getIn(['webhooks', 'list']), }), - { fetchWebhooks, setShowAlerts } + { fetchWebhooks } )(observer(AlertFormModal)); diff --git a/frontend/app/components/Alerts/AlertItem.js b/frontend/app/components/Alerts/AlertItem.js deleted file mode 100644 index 76431bf77..000000000 --- a/frontend/app/components/Alerts/AlertItem.js +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react' -import cn from 'classnames'; -import stl from './alertItem.module.css'; -import AlertTypeLabel from './AlertTypeLabel'; - -const AlertItem = props => { - const { alert, onEdit, active } = props; - - const getThreshold = threshold => { - if (threshold === 15) return '15 Minutes'; - if (threshold === 30) return '30 Minutes'; - if (threshold === 60) return '1 Hour'; - if (threshold === 120) return '2 Hours'; - if (threshold === 240) return '4 Hours'; - if (threshold === 1440) return '1 Day'; - } - - const getNotifyChannel = alert => { - let str = ''; - if (alert.msteams) - str = 'MS Teams' - if (alert.slack) - str = 'Slack'; - if (alert.email) - str += (str === '' ? '' : ' and ')+ 'Email'; - if (alert.webhool) - str += (str === '' ? '' : ' and ')+ 'Webhook'; - if (str === '') - return 'OpenReplay'; - - return str; - } - - const isThreshold = alert.detectionMethod === 'threshold'; - - return ( - <div - className={cn(stl.wrapper, 'p-4 py-6 relative group cursor-pointer', { [stl.active]: active })} - onClick={onEdit} - id="alert-item" - > - <AlertTypeLabel type={alert.detectionMethod} /> - <div className="capitalize font-medium">{alert.name}</div> - <div className="mt-2 text-sm color-gray-medium"> - {alert.detectionMethod === 'threshold' && ( - <div>When <span className="italic font-medium">{alert.metric.text}</span> is {alert.condition.text} <span className="italic font-medium">{alert.query.right} {alert.metric.unit}</span> over the past <span className="italic font-medium">{getThreshold(alert.currentPeriod)}</span>, notify me on <span>{getNotifyChannel(alert)}</span>.</div> - )} - - {alert.detectionMethod === 'change' && ( - <div>When the <span className="italic font-medium">{alert.options.change}</span> of <span className="italic font-medium">{alert.metric.text}</span> is {alert.condition.text} <span className="italic font-medium">{alert.query.right} {alert.metric.unit}</span> over the past <span className="italic font-medium">{getThreshold(alert.currentPeriod)}</span> compared to the previous <span className="italic font-medium">{getThreshold(alert.previousPeriod)}</span>, notify me on {getNotifyChannel(alert)}.</div> - )} - </div> - </div> - ) -} - -export default AlertItem diff --git a/frontend/app/components/Alerts/AlertTypeLabel.js b/frontend/app/components/Alerts/AlertTypeLabel.js deleted file mode 100644 index f867dfdc4..000000000 --- a/frontend/app/components/Alerts/AlertTypeLabel.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import cn from 'classnames' -import stl from './alertTypeLabel.module.css' - -function AlertTypeLabel({ filterKey, type = '' }) { - return ( - <div className={ cn("rounded-full px-2 text-xs mb-2 fit-content uppercase color-gray-darkest", stl.wrapper, { [stl.alert] : filterKey === 'alert', }) }> - { type } - </div> - ) -} - -export default AlertTypeLabel diff --git a/frontend/app/components/Alerts/Alerts.js b/frontend/app/components/Alerts/Alerts.js deleted file mode 100644 index ed825abaf..000000000 --- a/frontend/app/components/Alerts/Alerts.js +++ /dev/null @@ -1,109 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { toast } from 'react-toastify'; -import AlertsList from './AlertsList'; -import { SlideModal, IconButton } from 'UI'; -import { init, edit, save, remove } from 'Duck/alerts'; -import { fetchList as fetchWebhooks } from 'Duck/webhook'; -import AlertForm from './AlertForm'; -import { connect } from 'react-redux'; -import { setShowAlerts } from 'Duck/dashboard'; -import { EMAIL, SLACK, WEBHOOK } from 'App/constants/schedule'; -import { confirm } from 'UI'; - -const Alerts = (props) => { - const { webhooks, setShowAlerts } = props; - const [showForm, setShowForm] = useState(false); - - useEffect(() => { - props.fetchWebhooks(); - }, []); - - const slackChannels = webhooks - .filter((hook) => hook.type === SLACK) - .map(({ webhookId, name }) => ({ value: webhookId, label: name })) - .toJS(); - const hooks = webhooks - .filter((hook) => hook.type === WEBHOOK) - .map(({ webhookId, name }) => ({ value: webhookId, label: name })) - .toJS(); - - const saveAlert = (instance) => { - const wasUpdating = instance.exists(); - props.save(instance).then(() => { - if (!wasUpdating) { - toast.success('New alert saved'); - toggleForm(null, false); - } else { - toast.success('Alert updated'); - } - }); - }; - - const onDelete = async (instance) => { - if ( - await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to permanently delete this alert?`, - }) - ) { - props.remove(instance.alertId).then(() => { - toggleForm(null, false); - }); - } - }; - - const toggleForm = (instance, state) => { - if (instance) { - props.init(instance); - } - return setShowForm(state ? state : !showForm); - }; - - return ( - <div> - <SlideModal - title={ - <div className="flex items-center"> - <span className="mr-3">{'Alerts'}</span> - <IconButton circle size="small" icon="plus" outline id="add-button" onClick={() => toggleForm({}, true)} /> - </div> - } - isDisplayed={true} - onClose={() => { - toggleForm({}, false); - setShowAlerts(false); - }} - size="small" - content={ - <AlertsList - onEdit={(alert) => { - toggleForm(alert, true); - }} - onClickCreate={() => toggleForm({}, true)} - /> - } - detailContent={ - showForm && ( - <AlertForm - edit={props.edit} - slackChannels={slackChannels} - webhooks={hooks} - onSubmit={saveAlert} - onClose={() => toggleForm({}, false)} - onDelete={onDelete} - /> - ) - } - /> - </div> - ); -}; - -export default connect( - (state) => ({ - webhooks: state.getIn(['webhooks', 'list']), - instance: state.getIn(['alerts', 'instance']), - }), - { init, edit, save, remove, fetchWebhooks, setShowAlerts } -)(Alerts); diff --git a/frontend/app/components/Alerts/AlertsList.js b/frontend/app/components/Alerts/AlertsList.js deleted file mode 100644 index a64094a0d..000000000 --- a/frontend/app/components/Alerts/AlertsList.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Loader, NoContent, Input, Button } from 'UI'; -import AlertItem from './AlertItem'; -import { fetchList } from 'Duck/alerts'; -import { connect } from 'react-redux'; -import { getRE } from 'App/utils'; - -const AlertsList = (props) => { - const { loading, list, instance, onEdit } = props; - const [query, setQuery] = useState(''); - - useEffect(() => { - props.fetchList(); - }, []); - - const filterRE = getRE(query, 'i'); - const _filteredList = list.filter(({ name, query: { left } }) => filterRE.test(name) || filterRE.test(left)); - - return ( - <div> - <div className="mb-3 w-full px-3"> - <Input name="searchQuery" placeholder="Search by Name or Metric" onChange={({ target: { value } }) => setQuery(value)} /> - </div> - <Loader loading={loading}> - <NoContent - title="No alerts have been setup yet." - subtext={ - <div className="flex flex-col items-center"> - <div>Alerts helps your team stay up to date with the activity on your app.</div> - <Button variant="primary" className="mt-4" icon="plus" onClick={props.onClickCreate}> - Create - </Button> - </div> - } - size="small" - show={list.size === 0} - > - <div className="bg-white"> - {_filteredList.map((a) => ( - <div className="border-b" key={a.key}> - <AlertItem active={instance.alertId === a.alertId} alert={a} onEdit={() => onEdit(a.toData())} /> - </div> - ))} - </div> - </NoContent> - </Loader> - </div> - ); -}; - -export default connect( - (state) => ({ - list: state.getIn(['alerts', 'list']).sort((a, b) => b.createdAt - a.createdAt), - instance: state.getIn(['alerts', 'instance']), - loading: state.getIn(['alerts', 'loading']), - }), - { fetchList } -)(AlertsList); diff --git a/frontend/app/components/Alerts/Notifications/ListItem/ListItem.js b/frontend/app/components/Alerts/Notifications/ListItem/ListItem.js deleted file mode 100644 index 994ffcfb4..000000000 --- a/frontend/app/components/Alerts/Notifications/ListItem/ListItem.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { Button } from 'UI'; -import stl from './listItem.module.css'; -import cn from 'classnames'; -import AlertTypeLabel from '../../AlertTypeLabel'; - -const ListItem = ({ alert, onClear, loading, onNavigate }) => { - return ( - <div className={cn(stl.wrapper, 'group', { [stl.viewed] : alert.viewed })}> - <div className="flex justify-between items-center"> - <div className="text-sm">{alert.createdAt && alert.createdAt.toFormat('LLL dd, yyyy, hh:mm a')}</div> - <div className={ cn("invisible", { 'group-hover:visible' : !alert.viewed})} > - <Button variant="text" loading={loading}> - <span className={ cn("text-sm color-gray-medium", { 'invisible' : loading })} onClick={onClear}>{'IGNORE'}</span> - </Button> - </div> - </div> - <AlertTypeLabel - type={alert.options.sourceMeta} - filterKey={alert.filterKey} - /> - - <div> - <h2 className="mb-2 flex items-center"> - {alert.title} - </h2> - <div className="mb-2 text-sm text-justify break-all">{alert.description}</div> - </div> - </div> - ) -} - -export default ListItem diff --git a/frontend/app/components/Alerts/Notifications/ListItem/index.js b/frontend/app/components/Alerts/Notifications/ListItem/index.js deleted file mode 100644 index 741aed270..000000000 --- a/frontend/app/components/Alerts/Notifications/ListItem/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ListItem'; diff --git a/frontend/app/components/Alerts/Notifications/ListItem/listItem.module.css b/frontend/app/components/Alerts/Notifications/ListItem/listItem.module.css deleted file mode 100644 index 8472a777c..000000000 --- a/frontend/app/components/Alerts/Notifications/ListItem/listItem.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.wrapper { - padding: 15px; -} - -.viewed { - background-color: $gray-lightest; -} \ No newline at end of file diff --git a/frontend/app/components/Alerts/alertItem.module.css b/frontend/app/components/Alerts/alertItem.module.css deleted file mode 100644 index fe26fe57e..000000000 --- a/frontend/app/components/Alerts/alertItem.module.css +++ /dev/null @@ -1,9 +0,0 @@ -.wrapper { - &:hover { - background-color: $active-blue; - } - - &.active { - background-color: $active-blue; - } -} diff --git a/frontend/app/components/Alerts/alertTypeLabel.module.css b/frontend/app/components/Alerts/alertTypeLabel.module.css deleted file mode 100644 index 62ed31460..000000000 --- a/frontend/app/components/Alerts/alertTypeLabel.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.wrapper { - background-color: white; - color: $gray-dark; - border: solid thin $gray-light; -} - -.alert { - background: #C3E9EA; - color: #32888C; - border: none; -} diff --git a/frontend/app/components/Alerts/alerts.stories.js b/frontend/app/components/Alerts/alerts.stories.js deleted file mode 100644 index 781a7e734..000000000 --- a/frontend/app/components/Alerts/alerts.stories.js +++ /dev/null @@ -1,130 +0,0 @@ -import { storiesOf } from '@storybook/react'; -import { List } from 'immutable'; -import Alert from 'Types/alert'; -import Notification from 'Types/notification'; -import Alerts from '.'; -import Notifications from './Notifications'; -import AlertsList from './AlertsList'; -import AlertForm from './AlertForm'; - -const list = [ - { - "alertId": 2, - "projectId": 1, - "name": "new alert", - "description": null, - "active": true, - "threshold": 240, - "detectionMethod": "threshold", - "query": { - "left": "avgPageLoad", - "right": 1.0, - "operator": ">=" - }, - "createdAt": 1591893324078, - "options": { - "message": [ - { - "type": "slack", - "value": "51" - }, - ], - "LastNotification": 1592929583000, - "renotifyInterval": 120 - } - }, - { - "alertId": 14, - "projectId": 1, - "name": "alert 19.06", - "description": null, - "active": true, - "threshold": 30, - "detectionMethod": "threshold", - "query": { - "left": "avgPageLoad", - "right": 3000.0, - "operator": ">=" - }, - "createdAt": 1592579750935, - "options": { - "message": [ - { - "type": "slack", - "value": "51" - } - ], - "renotifyInterval": 120 - } - }, - { - "alertId": 15, - "projectId": 1, - "name": "notify every 60min", - "description": null, - "active": true, - "threshold": 30, - "detectionMethod": "threshold", - "query": { - "left": "avgPageLoad", - "right": 1.0, - "operator": ">=" - }, - "createdAt": 1592848779604, - "options": { - "message": [ - { - "type": "slack", - "value": "51" - }, - ], - "LastNotification": 1599135058000, - "renotifyInterval": 60 - } - }, - { - "alertId": 21, - "projectId": 1, - "name": "always notify", - "description": null, - "active": true, - "threshold": 30, - "detectionMethod": "threshold", - "query": { - "left": "avgPageLoad", - "right": 1.0, - "operator": ">=" - }, - "createdAt": 1592849011350, - "options": { - "message": [ - { - "type": "slack", - "value": "51" - } - ], - "LastNotification": 1599135058000, - "renotifyInterval": 10 - } - } -] - -const notifications = List([ - { title: 'test', type: 'change', createdAt: 1591893324078, description: 'Lorem ipusm'}, - { title: 'test', type: 'threshold', createdAt: 1591893324078, description: 'Lorem ipusm'}, - { title: 'test', type: 'threshold', createdAt: 1591893324078, description: 'Lorem ipusm'}, - { title: 'test', type: 'threshold', createdAt: 1591893324078, description: 'Lorem ipusm'}, -]) -storiesOf('Alerts', module) - .add('Alerts', () => ( - <Alerts /> - )) - .add('List', () => ( - <AlertsList list={List(list).map(Alert)} /> - )) - .add('AlertForm', () => ( - <AlertForm /> - )) - .add('AlertNotifications', () => ( - <Notifications announcements={notifications.map(Notification)} /> - )) diff --git a/frontend/app/components/Alerts/index.js b/frontend/app/components/Alerts/index.js deleted file mode 100644 index 4f6a6f772..000000000 --- a/frontend/app/components/Alerts/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Alerts' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx index 1022842c9..4a2a97283 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx @@ -131,7 +131,7 @@ function AlertListItem(props: Props) { {' is '} <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}> {alert.query.operator} - {numberWithCommas(alert.query.right)} {alert.metric.unit} + {numberWithCommas(alert.query.right)} {alert.metric?.unit} </span> {' over the past '} <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}>{getThreshold( diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index facb9fcfa..0cde69517 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -87,7 +87,7 @@ function DashboardView(props: Props) { /> <AlertFormModal showModal={showAlertModal} - onClose={() => dashboardStore.updateKey('showAlertModal', false)} + onClose={() => dashboardStore.toggleAlertModal(false)} /> </div> </Loader> diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx index 1ae1fe528..25ef7a0a0 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx @@ -19,7 +19,7 @@ function FunnelIssueDetails(props: Props) { useEffect(() => { setLoading(true); - const _filters = { ...filter, series: widget.data.stages ? widget.toJsonDrilldown().map((item: any) => { + const _filters = { ...filter, series: widget.data.stages ? widget.series.map((item: any) => { return { ...item, filter: { diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx index 826e9a133..5e3564911 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx @@ -49,7 +49,7 @@ function FunnelIssues() { const depsString = JSON.stringify(widget.series); useEffect(() => { - debounceRequest({ ...filter, series: widget.toJsonDrilldown(), page: metricStore.sessionsPage, limit: metricStore.sessionsPageSize }); + debounceRequest({ ...filter, series: widget.series, page: metricStore.sessionsPage, limit: metricStore.sessionsPageSize }); }, [stages.length, drillDownPeriod, filter.filters, depsString, metricStore.sessionsPage]); return useObserver(() => ( diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index ebb07b49c..ada2dd03b 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -81,9 +81,10 @@ function WidgetSessions(props: Props) { const customFilter = { ...filter, ...timeRange, filters: [ ...sessionStore.userFilter.filters, clickFilter]} debounceClickMapSearch(customFilter) } else { + console.log(widget) debounceRequest(widget.metricId, { ...filter, - series: widget.toJsonDrilldown(), + series: widget.series, page: metricStore.sessionsPage, limit: metricStore.sessionsPageSize, }); diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx index 78d858b3e..548bc9570 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx @@ -1,7 +1,5 @@ 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 { @@ -9,20 +7,21 @@ interface Props { initAlert: Function; } function AlertButton(props: Props) { - const { seriesId, initAlert } = props; - const { dashboardStore } = useStore(); + const { seriesId } = props; + const { dashboardStore, alertsStore } = useStore(); const onClick = () => { - initAlert({ query: { left: seriesId }}) - dashboardStore.updateKey('showAlertModal', true); + dashboardStore.toggleAlertModal(true); + alertsStore.init({ query: { left: seriesId }}) } return ( + <div onClick={onClick}> <WidgetIcon className="cursor-pointer" icon="bell-plus" tooltip="Set Alert" - onClick={onClick} /> + </div> ); } -export default connect(null, { initAlert })(AlertButton); \ No newline at end of file +export default 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 index bc804b2b8..b9714b33b 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetIcon.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetIcon.tsx @@ -3,15 +3,14 @@ import { Icon, Tooltip } from 'UI'; interface Props { className: string; - onClick: () => void; icon: string; tooltip: string; } function WidgetIcon(props: Props) { - const { className, onClick, icon, tooltip } = props; + const { className, icon, tooltip } = props; return ( <Tooltip title={tooltip}> - <div className={className} onClick={onClick}> + <div className={className}> {/* @ts-ignore */} <Icon name={icon} size="14" /> </div> diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index b00ebb93b..7ef0028c9 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -12,7 +12,6 @@ import { init as initSite } from 'Duck/site'; import { getInitials } from 'App/utils'; import ErrorGenPanel from 'App/dev/components'; -import Alerts from '../Alerts/Alerts'; import { fetchListActive as fetchMetadata } from 'Duck/customField'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; @@ -91,8 +90,6 @@ const Header = (props) => { {<ErrorGenPanel />} </div> - - {showAlerts && <Alerts />} </div> ); }; @@ -103,7 +100,6 @@ export default withRouter( account: state.getIn(['user', 'account']), siteId: state.getIn(['site', 'siteId']), sites: state.getIn(['site', 'list']), - showAlerts: state.getIn(['dashboard', 'showAlerts']), boardingCompletion: state.getIn(['dashboard', 'boardingCompletion']), }), { onLogoutClick: logout, initSite, fetchMetadata } diff --git a/frontend/app/duck/alerts.js b/frontend/app/duck/alerts.js deleted file mode 100644 index 31721ff45..000000000 --- a/frontend/app/duck/alerts.js +++ /dev/null @@ -1,63 +0,0 @@ -import Alert from 'Types/alert'; -import { Map } from 'immutable'; -import crudDuckGenerator from './tools/crudDuck'; -import { RequestTypes } from 'Duck/requestStateCreator'; -import { reduceDucks } from 'Duck/tools'; - -const name = 'alert' -const idKey = 'alertId'; -const crudDuck = crudDuckGenerator(name, (d) => new Alert(d), { idKey: idKey }); -export const { fetchList, init, edit, remove } = crudDuck.actions; -const FETCH_TRIGGER_OPTIONS = new RequestTypes(`${name}/FETCH_TRIGGER_OPTIONS`); -const CHANGE_SEARCH = `${name}/CHANGE_SEARCH` -console.log(fetchList(), init(), edit(), remove()) -const initialState = Map({ - definedPercent: 0, - triggerOptions: [], - alertsSearch: '', -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - // case GENERATE_LINK.SUCCESS: - // return state.update( - // 'list', - // list => list - // .map(member => { - // if(member.id === action.id) { - // return Member({...member.toJS(), invitationLink: action.data.invitationLink }) - // } - // return member - // }) - // ); - case CHANGE_SEARCH: - return state.set('alertsSearch', action.search); - case FETCH_TRIGGER_OPTIONS.SUCCESS: - return state.set('triggerOptions', action.data.map(({ name, value }) => ({ label: name, value }))); - } - return state; -}; - -export function save(instance) { - return { - types: crudDuck.actionTypes.SAVE.toArray(), - call: client => client.post( instance[idKey] ? `/alerts/${ instance[idKey] }` : '/alerts', instance.toData()), - }; -} - -export function changeSearch(search) { - return { - type: CHANGE_SEARCH, - search, - }; -} - -export function fetchTriggerOptions() { - return { - types: FETCH_TRIGGER_OPTIONS.toArray(), - call: client => client.get('/alerts/triggers'), - }; -} - -// export default crudDuck.reducer; -export default reduceDucks(crudDuck, { initialState, reducer }).reducer; diff --git a/frontend/app/duck/index.ts b/frontend/app/duck/index.ts index 4e8e24d8f..5133d2a10 100644 --- a/frontend/app/duck/index.ts +++ b/frontend/app/duck/index.ts @@ -7,7 +7,6 @@ import assignments from './assignments'; import filters from './filters'; import funnelFilters from './funnelFilters'; import templates from './templates'; -import alerts from './alerts'; import dashboard from './dashboard'; import components from './components'; import sources from './sources'; @@ -32,7 +31,6 @@ const rootReducer = combineReducers({ funnelFilters, templates, - alerts, dashboard, components, members, diff --git a/frontend/app/mstore/alertsStore.ts b/frontend/app/mstore/alertsStore.ts index 744370671..da83610c6 100644 --- a/frontend/app/mstore/alertsStore.ts +++ b/frontend/app/mstore/alertsStore.ts @@ -6,7 +6,7 @@ export default class AlertsStore { alerts: Alert[] = []; triggerOptions: { label: string, value: string | number, unit?: string }[] = []; alertsSearch = ''; - // @ts-ignore + // @ts-ignore instance: Alert = new Alert({}, false); loading = false @@ -70,6 +70,8 @@ export default class AlertsStore { } edit = (diff: Partial<Alert>) => { - Object.assign(this.instance, diff) + const key = Object.keys(diff)[0] + // @ts-ignore + this.instance[key] = diff[key] } -} \ No newline at end of file +} diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 897b90a03..141cc6761 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -405,6 +405,10 @@ export default class DashboardStore { }); } + toggleAlertModal(val: boolean) { + this.showAlertModal = val + } + fetchMetricChartData( metric: Widget, data: any, diff --git a/frontend/app/types/alert.ts b/frontend/app/types/alert.ts index ff127a240..052de7c1d 100644 --- a/frontend/app/types/alert.ts +++ b/frontend/app/types/alert.ts @@ -1,5 +1,6 @@ import { notEmptyString, validateNumber } from 'App/validate'; import { alertMetrics as metrics, alertConditions as conditions } from 'App/constants'; +import { makeAutoObservable } from 'mobx' const metricsMap = {} const conditionsMap = {} @@ -73,7 +74,7 @@ export default class Alert { detectionMethod: IAlert["detectionMethod"] detection_method: IAlert["detection_method"] change: IAlert["change"] - query:IAlert["query"] + query: IAlert["query"] options: IAlert["options"] createdAt?: IAlert["createdAt"] slack: IAlert["slack"] @@ -128,6 +129,8 @@ export default class Alert { hasNotification: !!slack || !!email || !!webhook, isExists, }) + + makeAutoObservable(this) } validate() { From 8d7d1830417921b81e276ae6f18c683b5bf7a53d Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Mon, 9 Jan 2023 17:52:29 +0100 Subject: [PATCH 25/65] change(ui): remove webhook reducer --- .../Alerts/AlertFormModal/AlertFormModal.tsx | 19 +-- .../components/Client/Webhooks/WebhookForm.js | 140 ++++++++---------- .../components/Client/Webhooks/Webhooks.js | 130 ++++++++-------- .../components/Alerts/AlertsList.tsx | 20 +-- .../Dashboard/components/Alerts/NewAlert.tsx | 20 +-- frontend/app/duck/index.ts | 2 - frontend/app/duck/webhook.js | 7 - frontend/app/mstore/index.tsx | 26 +--- frontend/app/mstore/settingsStore.ts | 113 +++++++++----- frontend/app/services/WebhookService.ts | 25 ++++ frontend/app/services/index.ts | 20 ++- frontend/app/types/webhook.js | 22 --- frontend/app/types/webhook.ts | 36 +++++ 13 files changed, 297 insertions(+), 283 deletions(-) delete mode 100644 frontend/app/duck/webhook.js create mode 100644 frontend/app/services/WebhookService.ts delete mode 100644 frontend/app/types/webhook.js create mode 100644 frontend/app/types/webhook.ts diff --git a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx index 48a46a3a0..93999281a 100644 --- a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx +++ b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx @@ -2,9 +2,7 @@ import React, { useEffect, useState } from 'react'; import { SlideModal } from 'UI'; import { useStore } from 'App/mstore' import { observer } from 'mobx-react-lite' -import { fetchList as fetchWebhooks } from 'Duck/webhook'; import AlertForm from '../AlertForm'; -import { connect } from 'react-redux'; import { SLACK, TEAMS, WEBHOOK } from 'App/constants/schedule'; import { confirm } from 'UI'; @@ -18,16 +16,14 @@ interface Props { showModal?: boolean; metricId?: number; onClose?: () => void; - webhooks: any; - fetchWebhooks: Function; } function AlertFormModal(props: Props) { - const { alertsStore } = useStore() - const { metricId = null, showModal = false, webhooks } = props; + const { alertsStore, settingsStore } = useStore() + const { metricId = null, showModal = false } = props; const [showForm, setShowForm] = useState(false); - + const webhooks = settingsStore.webhooks useEffect(() => { - props.fetchWebhooks(); + settingsStore.fetchWebhooks(); }, []); @@ -110,9 +106,4 @@ function AlertFormModal(props: Props) { ); } -export default connect( - (state) => ({ - webhooks: state.getIn(['webhooks', 'list']), - }), - { fetchWebhooks } -)(observer(AlertFormModal)); +export default observer(AlertFormModal); diff --git a/frontend/app/components/Client/Webhooks/WebhookForm.js b/frontend/app/components/Client/Webhooks/WebhookForm.js index b64a63af8..c918dcb93 100644 --- a/frontend/app/components/Client/Webhooks/WebhookForm.js +++ b/frontend/app/components/Client/Webhooks/WebhookForm.js @@ -1,94 +1,74 @@ import React from 'react'; -import { connect } from 'react-redux'; -import { edit, save } from 'Duck/webhook'; import { Form, Button, Input } from 'UI'; import styles from './webhookForm.module.css'; +import { useStore } from 'App/mstore' +import { observer } from 'mobx-react-lite' -@connect( - (state) => ({ - webhook: state.getIn(['webhooks', 'instance']), - loading: state.getIn(['webhooks', 'saveRequest', 'loading']), - }), - { - edit, - save, - } -) -class WebhookForm extends React.PureComponent { - setFocus = () => this.focusElement.focus(); - onChangeSelect = (event, { name, value }) => this.props.edit({ [name]: value }); - write = ({ target: { value, name } }) => this.props.edit({ [name]: value }); +function WebhookForm(props) { + const { settingsStore } = useStore() + const { webhookInst: webhook, hooksLoading: loading, saveWebhook, editWebhook } = settingsStore + const write = ({ target: { value, name } }) => editWebhook({ [name]: value }); - save = () => { - this.props.save(this.props.webhook).then(() => { - this.props.onClose(); + const save = () => { + saveWebhook(webhook).then(() => { + props.onClose(); }); }; - render() { - const { webhook, loading } = this.props; - return ( - <div className="bg-white h-screen overflow-y-auto" style={{ width: '350px' }}> - <h3 className="p-5 text-2xl">{webhook.exists() ? 'Update' : 'Add'} Webhook</h3> - <Form className={styles.wrapper}> - <Form.Field> - <label>{'Name'}</label> - <Input - ref={(ref) => { - this.focusElement = ref; - }} - name="name" - value={webhook.name} - onChange={this.write} - placeholder="Name" - /> - </Form.Field> - <Form.Field> - <label>{'Endpoint'}</label> - <Input - ref={(ref) => { - this.focusElement = ref; - }} - name="endpoint" - value={webhook.endpoint} - onChange={this.write} - placeholder="Endpoint" - /> - </Form.Field> + return ( + <div className="bg-white h-screen overflow-y-auto" style={{ width: '350px' }}> + <h3 className="p-5 text-2xl">{webhook.exists() ? 'Update' : 'Add'} Webhook</h3> + <Form className={styles.wrapper}> + <Form.Field> + <label>{'Name'}</label> + <Input + name="name" + value={webhook.name} + onChange={write} + placeholder="Name" + /> + </Form.Field> - <Form.Field> - <label>{'Auth Header (optional)'}</label> - <Input - ref={(ref) => { - this.focusElement = ref; - }} - name="authHeader" - value={webhook.authHeader} - onChange={this.write} - placeholder="Auth Header" - /> - </Form.Field> + <Form.Field> + <label>{'Endpoint'}</label> + <Input + name="endpoint" + value={webhook.endpoint} + onChange={write} + placeholder="Endpoint" + /> + </Form.Field> - <div className="flex justify-between"> - <div className="flex items-center"> - <Button - onClick={this.save} - disabled={!webhook.validate()} - loading={loading} - variant="primary" - className="float-left mr-2" - > - {webhook.exists() ? 'Update' : 'Add'} - </Button> - {webhook.exists() && <Button onClick={this.props.onClose}>{'Cancel'}</Button>} - </div> - {webhook.exists() && <Button icon="trash" variant="text" onClick={() => this.props.onDelete(webhook.webhookId)}></Button>} + <Form.Field> + <label>{'Auth Header (optional)'}</label> + <Input + name="authHeader" + value={webhook.authHeader} + onChange={write} + placeholder="Auth Header" + /> + </Form.Field> + + <div className="flex justify-between"> + <div className="flex items-center"> + <Button + onClick={save} + disabled={!webhook.validate()} + loading={loading} + variant="primary" + className="float-left mr-2" + > + {webhook.exists() ? 'Update' : 'Add'} + </Button> + {webhook.exists() && <Button onClick={props.onClose}>{'Cancel'}</Button>} </div> - </Form> - </div> - ); - } + {webhook.exists() && + <Button icon="trash" variant="text" onClick={() => props.onDelete(webhook.webhookId)}></Button>} + </div> + </Form> + </div> + ); } -export default WebhookForm; +export default observer(WebhookForm); diff --git a/frontend/app/components/Client/Webhooks/Webhooks.js b/frontend/app/components/Client/Webhooks/Webhooks.js index a87ac2298..0e1a0214c 100644 --- a/frontend/app/components/Client/Webhooks/Webhooks.js +++ b/frontend/app/components/Client/Webhooks/Webhooks.js @@ -1,9 +1,7 @@ import React, { useEffect } from 'react'; -import { connect } from 'react-redux'; import cn from 'classnames'; import withPageTitle from 'HOCs/withPageTitle'; import { Button, Loader, NoContent, Icon } from 'UI'; -import { init, fetchList, remove } from 'Duck/webhook'; import WebhookForm from './WebhookForm'; import ListItem from './ListItem'; import styles from './webhooks.module.css'; @@ -11,79 +9,71 @@ import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import { confirm } from 'UI'; import { toast } from 'react-toastify'; import { useModal } from 'App/components/Modal'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite' -function Webhooks(props) { - const { webhooks, loading } = props; - const { showModal, hideModal } = useModal(); +function Webhooks() { + const { settingsStore } = useStore() + const { webhooks, hooksLoading: loading } = settingsStore; + const { showModal, hideModal } = useModal(); - const noSlackWebhooks = webhooks.filter((hook) => hook.type === 'webhook'); - useEffect(() => { - props.fetchList(); - }, []); + const noSlackWebhooks = webhooks.filter((hook) => hook.type === 'webhook'); + useEffect(() => { + void settingsStore.fetchWebhooks(); + }, []); - const init = (v) => { - props.init(v); - showModal(<WebhookForm onClose={hideModal} onDelete={removeWebhook} />); - }; + const init = (v) => { + settingsStore.initWebhook(v); + showModal(<WebhookForm onClose={hideModal} onDelete={removeWebhook} />); + }; - const removeWebhook = async (id) => { - if ( - await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to remove this webhook?`, - }) - ) { - props.remove(id).then(() => { - toast.success('Webhook removed successfully'); - }); - hideModal(); - } - }; + const removeWebhook = async (id) => { + if ( + await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to remove this webhook?`, + }) + ) { + settingsStore.removeWebhook(id).then(() => { + toast.success('Webhook removed successfully'); + }); + hideModal(); + } + }; - return ( - <div> - <div className={cn(styles.tabHeader, 'px-5 pt-5')}> - <h3 className={cn(styles.tabTitle, 'text-2xl')}>{'Webhooks'}</h3> - {/* <Button rounded={true} icon="plus" variant="outline" onClick={() => init()} /> */} - <Button className="ml-auto" variant="primary" onClick={() => init()}>Add Webhook</Button> - </div> - - <div className="text-base text-disabled-text flex items-center my-3 px-5"> - <Icon name="info-circle-fill" className="mr-2" size={16} /> - Leverage webhooks to push OpenReplay data to other systems. - </div> - - <Loader loading={loading}> - <NoContent - title={ - <div className="flex flex-col items-center justify-center"> - <AnimatedSVG name={ICONS.NO_WEBHOOKS} size={80} /> - <div className="text-center text-gray-600 my-4">None added yet</div> + return ( + <div> + <div className={cn(styles.tabHeader, 'px-5 pt-5')}> + <h3 className={cn(styles.tabTitle, 'text-2xl')}>{'Webhooks'}</h3> + <Button className="ml-auto" variant="primary" onClick={() => init()}>Add Webhook</Button> </div> - } - size="small" - show={noSlackWebhooks.size === 0} - > - <div className="cursor-pointer"> - {noSlackWebhooks.map((webhook) => ( - <ListItem key={webhook.key} webhook={webhook} onEdit={() => init(webhook)} /> - ))} - </div> - </NoContent> - </Loader> - </div> - ); + + <div className="text-base text-disabled-text flex items-center my-3 px-5"> + <Icon name="info-circle-fill" className="mr-2" size={16} /> + Leverage webhooks to push OpenReplay data to other systems. + </div> + + <Loader loading={loading}> + <NoContent + title={ + <div className="flex flex-col items-center justify-center"> + <AnimatedSVG name={ICONS.NO_WEBHOOKS} size={80} /> + <div className="text-center text-gray-600 my-4">None added yet</div> + </div> + } + size="small" + show={noSlackWebhooks.length === 0} + > + <div className="cursor-pointer"> + {noSlackWebhooks.map((webhook) => ( + <ListItem key={webhook.key} webhook={webhook} onEdit={() => init(webhook)} /> + ))} + </div> + </NoContent> + </Loader> + </div> + ); } -export default connect( - (state) => ({ - webhooks: state.getIn(['webhooks', 'list']), - loading: state.getIn(['webhooks', 'loading']), - }), - { - init, - fetchList, - remove, - } -)(withPageTitle('Webhooks - OpenReplay Preferences')(Webhooks)); +export default withPageTitle('Webhooks - OpenReplay Preferences')(observer(Webhooks)); diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx index a6f20b449..e4005098e 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx @@ -1,25 +1,21 @@ import React from 'react'; -import { NoContent, Pagination, Icon } from 'UI'; +import { NoContent, Pagination } from 'UI'; import { filterList } from 'App/utils'; import { sliceListPerPage } from 'App/utils'; -import { connect } from 'react-redux'; -import { fetchList as fetchWebhooks } from 'Duck/webhook'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import AlertListItem from './AlertListItem' import { useStore } from 'App/mstore' import { observer } from 'mobx-react-lite' -import Alert from 'Types/alert' const pageSize = 10; interface Props { siteId: string; - webhooks: Array<any>; - fetchWebhooks: () => void; } -function AlertsList({ siteId, fetchWebhooks, webhooks }: Props) { - const { alertsStore } = useStore(); +function AlertsList({ siteId }: Props) { + const { alertsStore, settingsStore } = useStore(); + const { fetchWebhooks, webhooks } = settingsStore const { alerts: alertsList, alertsSearch, fetchList, init } = alertsStore React.useEffect(() => { fetchList(); fetchWebhooks() }, []); @@ -72,10 +68,4 @@ function AlertsList({ siteId, fetchWebhooks, webhooks }: Props) { ); } -export default connect( - (state) => ({ - // @ts-ignore - webhooks: state.getIn(['webhooks', 'list']), - }), - { fetchWebhooks } -)(observer(AlertsList)); +export default observer(AlertsList); diff --git a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx index 4ad94e8a0..aa6c18714 100644 --- a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx @@ -5,7 +5,6 @@ import { validateEmail } from 'App/validate'; import { confirm } from 'UI'; import { toast } from 'react-toastify'; import { SLACK, WEBHOOK, TEAMS } from 'App/constants/schedule'; -import { fetchList as fetchWebhooks } from 'Duck/webhook'; import Breadcrumb from 'Shared/Breadcrumb'; import { withSiteId, alerts } from 'App/routes'; import { withRouter, RouteComponentProps } from 'react-router-dom'; @@ -57,17 +56,15 @@ interface Select { interface IProps extends RouteComponentProps { siteId: string; slackChannels: any[]; - webhooks: any[]; loading: boolean; deleting: boolean; triggerOptions: any[]; list: any; onSubmit: (instance: Alert) => void; - fetchWebhooks: () => void; } const NewAlert = (props: IProps) => { - const { alertsStore } = useStore(); + const { alertsStore, settingsStore } = useStore(); const { fetchTriggerOptions, init, @@ -81,11 +78,10 @@ const NewAlert = (props: IProps) => { loading, } = alertsStore const deleting = loading - + const webhooks = settingsStore.webhooks + const fetchWebhooks = settingsStore.fetchWebhooks const { siteId, - webhooks, - fetchWebhooks, } = props; useEffect(() => { @@ -288,12 +284,4 @@ const NewAlert = (props: IProps) => { ); }; -export default withRouter( - connect( - (state) => ({ - // @ts-ignore - webhooks: state.getIn(['webhooks', 'list']), - }), - { fetchWebhooks } - )(observer(NewAlert)) -); +export default withRouter(observer(NewAlert)) diff --git a/frontend/app/duck/index.ts b/frontend/app/duck/index.ts index 5133d2a10..d9412eedc 100644 --- a/frontend/app/duck/index.ts +++ b/frontend/app/duck/index.ts @@ -13,7 +13,6 @@ import sources from './sources'; import members from './member'; import site from './site'; import customFields from './customField'; -import webhooks from './webhook'; import integrations from './integrations'; import rehydrate from './rehydrate'; import errors from './errors'; @@ -36,7 +35,6 @@ const rootReducer = combineReducers({ members, site, customFields, - webhooks, rehydrate, errors, funnels, diff --git a/frontend/app/duck/webhook.js b/frontend/app/duck/webhook.js deleted file mode 100644 index 8dc323a75..000000000 --- a/frontend/app/duck/webhook.js +++ /dev/null @@ -1,7 +0,0 @@ -import Webhook from 'Types/webhook'; -import crudDuckGenerator from './tools/crudDuck'; - -const crudDuck = crudDuckGenerator('webhook', Webhook, { idKey: 'webhookId' }); -export const { fetchList, init, edit, save, remove } = crudDuck.actions; - -export default crudDuck.reducer; diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx index b9567c817..e177d86d0 100644 --- a/frontend/app/mstore/index.tsx +++ b/frontend/app/mstore/index.tsx @@ -6,17 +6,7 @@ import RoleStore from './roleStore'; import APIClient from 'App/api_client'; import FunnelStore from './funnelStore'; import { - dashboardService, - metricService, - sessionService, - userService, - auditService, - funnelService, - errorService, - notesService, - recordingsService, - configService, - alertsService, + services } from 'App/services'; import SettingsStore from './settingsStore'; import AuditStore from './auditStore'; @@ -69,17 +59,9 @@ export class RootStore { initClient() { const client = new APIClient(); - dashboardService.initClient(client); - metricService.initClient(client); - funnelService.initClient(client); - sessionService.initClient(client); - userService.initClient(client); - auditService.initClient(client); - errorService.initClient(client); - notesService.initClient(client) - recordingsService.initClient(client); - configService.initClient(client); - alertsService.initClient(client) + services.forEach(service => { + service.initClient(client); + }) } } diff --git a/frontend/app/mstore/settingsStore.ts b/frontend/app/mstore/settingsStore.ts index 45cb9610c..dc4f6baa4 100644 --- a/frontend/app/mstore/settingsStore.ts +++ b/frontend/app/mstore/settingsStore.ts @@ -2,44 +2,89 @@ import { makeAutoObservable, observable, action } from "mobx" import SessionSettings from "./types/sessionSettings" import { sessionService } from "App/services" import { toast } from 'react-toastify'; +import Webhook, { IWebhook } from 'Types/webhook'; +import { + webhookService +} from 'App/services'; +import Alert, { IAlert } from "Types/alert"; export default class SettingsStore { - loadingCaptureRate: boolean = false; - sessionSettings: SessionSettings = new SessionSettings() - captureRateFetched: boolean = false; - limits: any = null; + loadingCaptureRate: boolean = false; + sessionSettings: SessionSettings = new SessionSettings() + captureRateFetched: boolean = false; + limits: any = null; - constructor() { - makeAutoObservable(this, { - sessionSettings: observable, + webhooks: Webhook[] = [] + webhookInst = new Webhook() + + hooksLoading = false + + constructor() { + makeAutoObservable(this, { + sessionSettings: observable, + }) + } + + saveCaptureRate(data: any) { + return sessionService.saveCaptureRate(data) + .then(data => data.json()) + .then(({ data }) => { + this.sessionSettings.merge({ + captureRate: data.rate, + captureAll: data.captureAll }) - } + toast.success("Settings updated successfully"); + }).catch(err => { + toast.error("Error saving capture rate"); + }) + } - saveCaptureRate(data: any) { - return sessionService.saveCaptureRate(data) - .then(data => data.json()) - .then(({ data }) => { - this.sessionSettings.merge({ - captureRate: data.rate, - captureAll: data.captureAll - }) - toast.success("Settings updated successfully"); - }).catch(err => { - toast.error("Error saving capture rate"); - }) - } + fetchCaptureRate(): Promise<any> { + this.loadingCaptureRate = true; + return sessionService.fetchCaptureRate() + .then(data => { + this.sessionSettings.merge({ + captureRate: data.rate, + captureAll: data.captureAll + }) + this.captureRateFetched = true; + }).finally(() => { + this.loadingCaptureRate = false; + }) + } - fetchCaptureRate(): Promise<any> { - this.loadingCaptureRate = true; - return sessionService.fetchCaptureRate() - .then(data => { - this.sessionSettings.merge({ - captureRate: data.rate, - captureAll: data.captureAll - }) - this.captureRateFetched = true; - }).finally(() => { - this.loadingCaptureRate = false; - }) - } + fetchWebhooks = () => { + this.hooksLoading = true + return webhookService.fetchList() + .then(data => { + this.webhooks = data.map(hook => new Webhook(hook)) + this.hooksLoading = false + }) + } + + initWebhook = (inst: Partial<IWebhook> | Webhook) => { + this.webhookInst = inst instanceof Webhook ? inst : new Webhook(inst) + } + + saveWebhook = (inst: Webhook) => { + this.hooksLoading = true + return webhookService.saveWebhook(inst) + .then(data => { + this.webhookInst = new Webhook(data) + this.hooksLoading = false + }) + } + + removeWebhook = (hookId: string) => { + this.hooksLoading = true + return webhookService.removeWebhook(hookId) + .then(() => { + this.webhooks = this.webhooks.filter(hook => hook.webhookId!== hookId) + this.hooksLoading = false + }) + } + + editWebhook = (diff: Partial<IWebhook>) => { + Object.assign(this.webhookInst, diff) + } } diff --git a/frontend/app/services/WebhookService.ts b/frontend/app/services/WebhookService.ts new file mode 100644 index 000000000..2bcefa619 --- /dev/null +++ b/frontend/app/services/WebhookService.ts @@ -0,0 +1,25 @@ +import BaseService from './BaseService'; +import Webhook, { IWebhook } from "Types/webhook"; + +export default class WebhookService extends BaseService { + fetchList(): Promise<IWebhook[]> { + return this.client.get('/webhooks') + .then(r => r.json()) + .then(j => j.data || []) + .catch(Promise.reject) + } + + saveWebhook(inst: Webhook) { + return this.client.put('/webhooks', inst) + .then(r => r.json()) + .then(j => j.data || {}) + .catch(Promise.reject) + } + + removeWebhook(id: Webhook["webhookId"]) { + return this.client.delete('/webhooks/' + id) + .then(r => r.json()) + .then(j => j.data || {}) + .catch(Promise.reject) + } +} \ No newline at end of file diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts index 72d0f4d92..816113e68 100644 --- a/frontend/app/services/index.ts +++ b/frontend/app/services/index.ts @@ -9,6 +9,8 @@ import NotesService from "./NotesService"; import RecordingsService from "./RecordingsService"; import ConfigService from './ConfigService' import AlertsService from './AlertsService' +import WebhookService from './WebhookService' + export const dashboardService = new DashboardService(); export const metricService = new MetricService(); export const sessionService = new SessionSerivce(); @@ -19,4 +21,20 @@ export const errorService = new ErrorService(); export const notesService = new NotesService(); export const recordingsService = new RecordingsService(); export const configService = new ConfigService(); -export const alertsService = new AlertsService(); \ No newline at end of file +export const alertsService = new AlertsService(); +export const webhookService = new WebhookService(); + +export const services = [ + dashboardService, + metricService, + sessionService, + userService, + funnelService, + auditService, + errorService, + notesService, + recordingsService, + configService, + alertsService, + webhookService, +] \ No newline at end of file diff --git a/frontend/app/types/webhook.js b/frontend/app/types/webhook.js deleted file mode 100644 index 5024411f4..000000000 --- a/frontend/app/types/webhook.js +++ /dev/null @@ -1,22 +0,0 @@ -import Record from 'Types/Record'; -import { validateName, validateURL } from 'App/validate'; - -export default Record({ - webhookId: undefined, - type: undefined, - name: '', - endpoint: '', - authHeader: '', -}, { - idKey: 'webhookId', - methods: { - validate() { - return !!this.name && validateName(this.name) && !!this.endpoint && validateURL(this.endpoint); - }, - toData() { - const js = this.toJS(); - delete js.key; - return js; - }, - }, -}); diff --git a/frontend/app/types/webhook.ts b/frontend/app/types/webhook.ts new file mode 100644 index 000000000..a8771a623 --- /dev/null +++ b/frontend/app/types/webhook.ts @@ -0,0 +1,36 @@ +import { validateName, validateURL } from 'App/validate'; +import { makeAutoObservable } from 'mobx' + +export interface IWebhook { + webhookId: string + type: string + name: string + endpoint: string + authHeader: string +} + +export default class Webhook { + webhookId: IWebhook["webhookId"] + type: IWebhook["type"] + name: IWebhook["name"] = '' + endpoint: IWebhook["endpoint"] = '' + authHeader: IWebhook["authHeader"] = '' + + constructor(data: Partial<IWebhook> = {}) { + Object.assign(this, data) + + makeAutoObservable(this) + } + + toData() { + return { ...this }; + } + + validate() { + return !!this.name && validateName(this.name) && !!this.endpoint && validateURL(this.endpoint); + } + + exists() { + return !!this.webhookId + } +} \ No newline at end of file From 36be728a5440d869e1562b57c2ea0a9afb310139 Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Mon, 9 Jan 2023 17:56:14 +0100 Subject: [PATCH 26/65] change(ui): add observer --- .../Dashboard/components/Alerts/AlertForm/NotifyHooks.tsx | 3 ++- .../app/components/Dashboard/components/Alerts/NewAlert.tsx | 2 +- frontend/app/mstore/alertsStore.ts | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertForm/NotifyHooks.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertForm/NotifyHooks.tsx index 15f00a0a3..95efaaaf3 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertForm/NotifyHooks.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertForm/NotifyHooks.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Checkbox } from 'UI'; import DropdownChips from '../DropdownChips'; +import { observer } from 'mobx-react-lite' interface INotifyHooks { instance: Alert; @@ -128,4 +129,4 @@ function NotifyHooks({ ); } -export default NotifyHooks; +export default observer(NotifyHooks); diff --git a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx index aa6c18714..258b550cd 100644 --- a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx @@ -88,7 +88,7 @@ const NewAlert = (props: IProps) => { init({}); if (list.length === 0) fetchList(); fetchTriggerOptions(); - fetchWebhooks(); + void fetchWebhooks(); }, []); useEffect(() => { diff --git a/frontend/app/mstore/alertsStore.ts b/frontend/app/mstore/alertsStore.ts index da83610c6..d377af81e 100644 --- a/frontend/app/mstore/alertsStore.ts +++ b/frontend/app/mstore/alertsStore.ts @@ -71,7 +71,10 @@ export default class AlertsStore { edit = (diff: Partial<Alert>) => { const key = Object.keys(diff)[0] + const oldInst = this.instance // @ts-ignore - this.instance[key] = diff[key] + oldInst[key] = diff[key] + + this.instance = oldInst } } From 6334f1088843e26b04a0c011bae74d3b56527444 Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Tue, 10 Jan 2023 12:59:14 +0100 Subject: [PATCH 27/65] change(ui): extract Live Player and its components --- .../AssistActions/AssistActions.tsx | 16 +- .../app/components/Session/LivePlayer.tsx | 41 +-- .../Player/LivePlayer/AssistDuration.tsx | 28 ++ .../AssistSessionsTabs/AssistSessionsTabs.tsx | 86 +++++ .../LivePlayer/AssistSessionsTabs/index.tsx | 1 + .../Player/LivePlayer/LiveControls.tsx | 131 ++++++++ .../Player/LivePlayer/LivePlayerBlock.tsx | 31 ++ .../LivePlayer/LivePlayerBlockHeader.tsx | 122 +++++++ .../Player/LivePlayer/LivePlayerInst.tsx | 81 +++++ .../Player/LivePlayer/LivePlayerSubHeader.tsx | 40 +++ .../Player/LivePlayer/Overlay/LiveOverlay.tsx | 74 +++++ .../LivePlayer/Overlay/LiveStatusText.tsx | 70 ++++ .../Player/LivePlayer/Overlay/index.ts | 1 + .../Session/Player/LivePlayer/Timeline.tsx | 166 ++++++++++ .../Player/ReplayPlayer/EventsBlock/Event.js | 174 ++++++++++ .../EventsBlock/EventGroupWrapper.js | 131 ++++++++ .../EventsBlock/EventSearch/EventSearch.js | 43 +++ .../EventsBlock/EventSearch/index.js | 1 + .../ReplayPlayer/EventsBlock/EventsBlock.tsx | 185 +++++++++++ .../ReplayPlayer/EventsBlock/LoadInfo.js | 40 +++ .../EventsBlock/Metadata/Metadata.js | 25 ++ .../EventsBlock/Metadata/MetadataItem.js | 75 +++++ .../EventsBlock/Metadata/SessionLine.js | 40 +++ .../EventsBlock/Metadata/SessionList.js | 60 ++++ .../EventsBlock/Metadata/index.js | 1 + .../EventsBlock/Metadata/metadata.module.css | 51 +++ .../Metadata/metadataItem.module.css | 37 +++ .../Metadata/sessionList.module.css | 26 ++ .../ReplayPlayer/EventsBlock/NoteEvent.tsx | 128 ++++++++ .../EventsBlock/UserCard/UserCard.js | 123 ++++++++ .../EventsBlock/UserCard/index.js | 1 + .../ReplayPlayer/EventsBlock/event.module.css | 165 ++++++++++ .../EventsBlock/eventGroupWrapper.module.css | 36 +++ .../EventsBlock/eventsBlock.module.css | 66 ++++ .../Player/ReplayPlayer/EventsBlock/index.js | 1 + .../EventsBlock/loadInfo.module.css | 102 ++++++ .../Player/ReplayPlayer/PlayerBlockHeader.tsx | 145 +++++++++ .../ReplayPlayer/playerBlockHeader.module.css | 21 ++ frontend/app/components/Session/WebPlayer.tsx | 10 +- .../app/components/Session/playerContext.ts | 15 +- .../Player/Controls/components/ReadNote.tsx | 4 +- .../components/Session_/session.stories.js | 298 ------------------ frontend/app/player/web/MessageManager.ts | 2 +- 43 files changed, 2545 insertions(+), 349 deletions(-) create mode 100644 frontend/app/components/Session/Player/LivePlayer/AssistDuration.tsx create mode 100644 frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/AssistSessionsTabs.tsx create mode 100644 frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/index.tsx create mode 100644 frontend/app/components/Session/Player/LivePlayer/LiveControls.tsx create mode 100644 frontend/app/components/Session/Player/LivePlayer/LivePlayerBlock.tsx create mode 100644 frontend/app/components/Session/Player/LivePlayer/LivePlayerBlockHeader.tsx create mode 100644 frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx create mode 100644 frontend/app/components/Session/Player/LivePlayer/LivePlayerSubHeader.tsx create mode 100644 frontend/app/components/Session/Player/LivePlayer/Overlay/LiveOverlay.tsx create mode 100644 frontend/app/components/Session/Player/LivePlayer/Overlay/LiveStatusText.tsx create mode 100644 frontend/app/components/Session/Player/LivePlayer/Overlay/index.ts create mode 100644 frontend/app/components/Session/Player/LivePlayer/Timeline.tsx create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Event.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/EventSearch.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/index.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventsBlock.tsx create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/LoadInfo.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/Metadata.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/MetadataItem.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionLine.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionList.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/index.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadata.module.css create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadataItem.module.css create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/sessionList.module.css create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/UserCard.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/index.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/event.module.css create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventGroupWrapper.module.css create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventsBlock.module.css create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/index.js create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/loadInfo.module.css create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/playerBlockHeader.module.css delete mode 100644 frontend/app/components/Session_/session.stories.js diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index f324ce433..c26c9427b 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -2,9 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Button, Tooltip } from 'UI'; import { connect } from 'react-redux'; import cn from 'classnames'; -import { toggleChatWindow } from 'Duck/sessions'; import ChatWindow from '../../ChatWindow'; -// state enums import { CallingState, ConnectionStatus, @@ -12,7 +10,7 @@ import { RequestLocalStream, } from 'Player'; import type { LocalStream } from 'Player'; -import { PlayerContext } from 'App/components/Session/playerContext'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import { toast } from 'react-toastify'; import { confirm } from 'UI'; @@ -30,15 +28,10 @@ function onError(e: any) { interface Props { userId: string; - calling: CallingState; - annotating: boolean; - peerConnectionStatus: ConnectionStatus; - remoteControlStatus: RemoteControlStatus; hasPermission: boolean; isEnterprise: boolean; isCallActive: boolean; agentIds: string[]; - livePlay: boolean; userDisplayName: string; } @@ -50,7 +43,8 @@ function AssistActions({ agentIds, userDisplayName, }: Props) { - const { player, store } = React.useContext(PlayerContext) + // @ts-ignore ??? + const { player, store } = React.useContext<ILivePlayerContext>(PlayerContext) const { assistManager: { @@ -123,6 +117,7 @@ function AssistActions({ const addIncomeStream = (stream: MediaStream) => { setIncomeStream((oldState) => { + if (oldState === null) return [stream] if (!oldState.find((existingStream) => existingStream.id === stream.id)) { return [...oldState, stream]; } @@ -257,8 +252,7 @@ const con = connect( isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee', userDisplayName: state.getIn(['sessions', 'current']).userDisplayName, }; - }, - { toggleChatWindow } + } ); export default con( diff --git a/frontend/app/components/Session/LivePlayer.tsx b/frontend/app/components/Session/LivePlayer.tsx index b45ebe1a3..cf163d452 100644 --- a/frontend/app/components/Session/LivePlayer.tsx +++ b/frontend/app/components/Session/LivePlayer.tsx @@ -1,41 +1,34 @@ import React from 'react'; import { useEffect, useState } from 'react'; import { connect } from 'react-redux'; -import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; import withRequest from 'HOCs/withRequest'; import withPermissions from 'HOCs/withPermissions'; -import { PlayerContext, defaultContextValue } from './playerContext'; +import { PlayerContext, defaultContextValue, ILivePlayerContext } from './playerContext'; import { makeAutoObservable } from 'mobx'; import { createLiveWebPlayer } from 'Player'; -import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; -import PlayerBlock from '../Session_/PlayerBlock'; +import PlayerBlockHeader from './Player/LivePlayer/LivePlayerBlockHeader'; +import PlayerBlock from './Player/LivePlayer/LivePlayerBlock'; import styles from '../Session_/session.module.css'; import Session from 'App/mstore/types/session'; import withLocationHandlers from 'HOCs/withLocationHandlers'; interface Props { session: Session; - fullscreen: boolean; loadingCredentials: boolean; - assistCredendials: RTCIceServer[]; + assistCredentials: RTCIceServer[]; isEnterprise: boolean; userEmail: string; userName: string; customSession?: Session; isMultiview?: boolean; query?: Record<string, (key: string) => any>; - toggleFullscreen: (isOn: boolean) => void; - closeBottomBlock: () => void; request: () => void; } function LivePlayer({ session, - toggleFullscreen, - closeBottomBlock, - fullscreen, loadingCredentials, - assistCredendials, + assistCredentials, request, isEnterprise, userEmail, @@ -44,11 +37,11 @@ function LivePlayer({ customSession, query }: Props) { - const [contextValue, setContextValue] = useState(defaultContextValue); + // @ts-ignore + const [contextValue, setContextValue] = useState<ILivePlayerContext>(defaultContextValue); const [fullView, setFullView] = useState(false); - const openedFromMultiview = query.get('multi') === 'true' - // @ts-ignore burn immutable - const usedSession = isMultiview ? customSession : session.toJS(); + const openedFromMultiview = query?.get('multi') === 'true' + const usedSession = isMultiview ? customSession! : session; useEffect(() => { if (loadingCredentials || !usedSession.sessionId) return; @@ -59,13 +52,13 @@ function LivePlayer({ name: userName, }, }; - const [player, store] = createLiveWebPlayer(sessionWithAgentData, assistCredendials, (state) => + const [player, store] = createLiveWebPlayer(sessionWithAgentData, assistCredentials, (state) => makeAutoObservable(state) ); setContextValue({ player, store }); return () => player.clean(); - }, [session.sessionId, assistCredendials]); + }, [session.sessionId, assistCredentials]); // LAYOUT (TODO: local layout state - useContext or something..) useEffect(() => { @@ -80,10 +73,6 @@ function LivePlayer({ if (isEnterprise) { request(); } - return () => { - toggleFullscreen(false); - closeBottomBlock(); - }; }, []); const TABS = { @@ -102,7 +91,6 @@ function LivePlayer({ activeTab={activeTab} setActiveTab={setActiveTab} tabs={TABS} - fullscreen={fullscreen} isMultiview={openedFromMultiview} /> )} @@ -112,7 +100,6 @@ function LivePlayer({ height: isMultiview ? '100%' : undefined, width: isMultiview ? '100%' : undefined, }} - data-fullscreen={fullscreen} > <PlayerBlock isMultiview={isMultiview} /> </div> @@ -123,7 +110,7 @@ function LivePlayer({ export default withRequest({ initialData: null, endpoint: '/assist/credentials', - dataName: 'assistCredendials', + dataName: 'assistCredentials', loadingName: 'loadingCredentials', })( withPermissions( @@ -136,13 +123,11 @@ export default withRequest({ return { session: state.getIn(['sessions', 'current']), showAssist: state.getIn(['sessions', 'showChatWindow']), - fullscreen: state.getIn(['components', 'player', 'fullscreen']), isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee', userEmail: state.getIn(['user', 'account', 'email']), userName: state.getIn(['user', 'account', 'name']), }; - }, - { toggleFullscreen, closeBottomBlock } + } )(withLocationHandlers()(LivePlayer)) ) ); diff --git a/frontend/app/components/Session/Player/LivePlayer/AssistDuration.tsx b/frontend/app/components/Session/Player/LivePlayer/AssistDuration.tsx new file mode 100644 index 000000000..e6af38b25 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/AssistDuration.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Duration } from 'luxon'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + +const AssistDurationCont = () => { + // @ts-ignore ??? TODO + const { store } = React.useContext<ILivePlayerContext>(PlayerContext) + const { assistStart } = store.get() + + const [assistDuration, setAssistDuration] = React.useState('00:00'); + React.useEffect(() => { + const interval = setInterval(() => { + setAssistDuration(Duration.fromMillis(+new Date() - assistStart).toFormat('mm:ss')); + } + , 1000); + return () => clearInterval(interval); + }, []) + return ( + <> + Elapsed {assistDuration} + </> + ) +} + +const AssistDuration = observer(AssistDurationCont) + +export default AssistDuration; diff --git a/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/AssistSessionsTabs.tsx b/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/AssistSessionsTabs.tsx new file mode 100644 index 000000000..a6735274a --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/AssistSessionsTabs.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import cn from 'classnames'; +import { Icon } from 'UI'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import { useHistory } from 'react-router-dom'; +import { multiview, liveSession, withSiteId } from 'App/routes'; +import { connect } from 'react-redux'; + +interface ITab { + onClick?: () => void; + classNames?: string; + children: React.ReactNode; +} + +const Tab = (props: ITab) => ( + <div + onClick={props.onClick} + className={cn('p-1 rounded flex items-center justify-center cursor-pointer', props.classNames)} + > + {props.children} + </div> +); + +export const InactiveTab = React.memo((props: Omit<ITab, 'children'>) => ( + <Tab onClick={props.onClick} classNames={cn("hover:bg-gray-bg bg-gray-light", props.classNames)}> + <Icon name="plus" size="22" color="white" /> + </Tab> +)); + +const ActiveTab = React.memo((props: Omit<ITab, 'children'>) => ( + <Tab onClick={props.onClick} classNames="hover:bg-teal bg-borderColor-primary"> + <Icon name="play-fill-new" size="22" color="white" /> + </Tab> +)); + +const CurrentTab = React.memo(() => ( + <Tab classNames="bg-teal color-white"> + <span style={{ fontSize: '0.65rem' }}>PLAYING</span> + </Tab> +)); + +function AssistTabs({ session, siteId }: { session: Record<string, any>; siteId: string }) { + const history = useHistory(); + const { assistMultiviewStore } = useStore(); + + const placeholder = new Array(4 - assistMultiviewStore.sessions.length).fill(0); + + React.useEffect(() => { + if (assistMultiviewStore.sessions.length === 0) { + assistMultiviewStore.setDefault(session); + } + }, []); + + const openGrid = () => { + const sessionIdQuery = encodeURIComponent(assistMultiviewStore.sessions.map((s) => s.sessionId).join(',')); + return history.push(withSiteId(multiview(sessionIdQuery), siteId)); + }; + const openLiveSession = (sessionId: string) => { + assistMultiviewStore.setActiveSession(sessionId); + history.push(withSiteId(liveSession(sessionId), siteId)); + }; + + return ( + <div className="grid grid-cols-2 w-28 h-full" style={{ gap: '4px' }}> + {assistMultiviewStore.sortedSessions.map((session: { key: number, sessionId: string }) => ( + <React.Fragment key={session.key}> + {assistMultiviewStore.isActive(session.sessionId) ? ( + <CurrentTab /> + ) : ( + <ActiveTab onClick={() => openLiveSession(session.sessionId)} /> + )} + </React.Fragment> + ))} + {placeholder.map((_, i) => ( + <React.Fragment key={i}> + <InactiveTab onClick={openGrid} /> + </React.Fragment> + ))} + </div> + ); +} + +export default connect((state: any) => ({ siteId: state.getIn(['site', 'siteId']) }))( + observer(AssistTabs) +); diff --git a/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/index.tsx b/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/index.tsx new file mode 100644 index 000000000..c7668e331 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/index.tsx @@ -0,0 +1 @@ +export { default, InactiveTab } from './AssistSessionsTabs' diff --git a/frontend/app/components/Session/Player/LivePlayer/LiveControls.tsx b/frontend/app/components/Session/Player/LivePlayer/LiveControls.tsx new file mode 100644 index 000000000..ac1e935ba --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/LiveControls.tsx @@ -0,0 +1,131 @@ +import React from 'react'; +import cn from 'classnames'; +import { connect } from 'react-redux'; +import LiveTag from 'Shared/LiveTag'; +import AssistSessionsTabs from './AssistSessionsTabs'; + +import { + CONSOLE, toggleBottomBlock, +} from 'Duck/components/player'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { fetchSessions } from 'Duck/liveSearch'; + +import AssistDuration from './AssistDuration'; +import Timeline from './Timeline'; +import ControlButton from 'Components/Session_/Player/Controls/ControlButton'; + +import styles from 'Components/Session_/Player/Controls/controls.module.css'; + +function Controls(props: any) { + // @ts-ignore ?? TODO + const { player, store } = React.useContext<ILivePlayerContext>(PlayerContext); + + const { jumpToLive } = player; + const { + livePlay, + logMarkedCountNow: logRedCount, + exceptionsList, + } = store.get(); + const showExceptions = exceptionsList.length > 0; + const { + bottomBlock, + toggleBottomBlock, + closedLive, + skipInterval, + session, + fetchSessions: fetchAssistSessions, + totalAssistSessions, + } = props; + + const onKeyDown = (e: any) => { + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { + return; + } + if (e.key === 'ArrowRight') { + forthTenSeconds(); + } + if (e.key === 'ArrowLeft') { + backTenSeconds(); + } + }; + + React.useEffect(() => { + document.addEventListener('keydown', onKeyDown.bind(this)); + if (totalAssistSessions === 0) { + fetchAssistSessions(); + } + return () => { + document.removeEventListener('keydown', onKeyDown.bind(this)); + }; + }, []); + + const forthTenSeconds = () => { + // @ts-ignore + player.jumpInterval(SKIP_INTERVALS[skipInterval]); + }; + + const backTenSeconds = () => { + // @ts-ignore + player.jumpInterval(-SKIP_INTERVALS[skipInterval]); + }; + + + + const toggleBottomTools = (blockName: number) => { + toggleBottomBlock(blockName); + }; + + return ( + <div className={styles.controls}> + <Timeline /> + <div className={cn(styles.buttons, '!px-5 !pt-0')} data-is-live> + <div className="flex items-center"> + {!closedLive && ( + <div className={styles.buttonsLeft}> + <LiveTag isLive={livePlay} onClick={() => (livePlay ? null : jumpToLive())} /> + <div className="font-semibold px-2"> + <AssistDuration /> + </div> + </div> + )} + </div> + + {totalAssistSessions > 1 ? ( + <div> + <AssistSessionsTabs session={session} /> + </div> + ) : null} + + <div className="flex items-center h-full"> + <ControlButton + onClick={() => toggleBottomTools(CONSOLE)} + active={bottomBlock === CONSOLE} + label="CONSOLE" + noIcon + labelClassName="!text-base font-semibold" + hasErrors={logRedCount > 0 || showExceptions} + containerClassName="mx-2" + /> + </div> + </div> + </div> + ); +} + +const ControlPlayer = observer(Controls); + +export default connect( + (state: any) => { + return { + session: state.getIn(['sessions', 'current']), + totalAssistSessions: state.getIn(['liveSearch', 'total']), + closedLive: + !!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current']).live, + }; + }, + { + fetchSessions, + toggleBottomBlock + } +)(ControlPlayer); \ No newline at end of file diff --git a/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlock.tsx b/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlock.tsx new file mode 100644 index 000000000..0bdd169d5 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlock.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import cn from 'classnames'; +import Player from './LivePlayerInst'; +import SubHeader from './LivePlayerSubHeader'; + +import styles from 'Components/Session_/playerBlock.module.css'; + +interface IProps { + fullView?: boolean; + isMultiview?: boolean; +} + +function LivePlayerBlock(props: IProps) { + const { fullView = false, isMultiview } = props; + + const shouldShowSubHeader = !fullView && !isMultiview + + return ( + <div className={cn(styles.playerBlock, 'flex flex-col', 'overflow-x-hidden')} style={{ zIndex: undefined, minWidth: isMultiview ? '100%' : undefined }}> + {shouldShowSubHeader ? ( + <SubHeader /> + ) : null} + <Player + fullView={fullView} + isMultiview={isMultiview} + /> + </div> + ); +} + +export default LivePlayerBlock \ No newline at end of file diff --git a/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlockHeader.tsx b/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlockHeader.tsx new file mode 100644 index 000000000..1fd3b503e --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlockHeader.tsx @@ -0,0 +1,122 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { withRouter } from 'react-router-dom'; +import { + assist as assistRoute, + withSiteId, + multiview, +} from 'App/routes'; +import { BackLink, Icon } from 'UI'; +import cn from 'classnames'; +import SessionMetaList from 'Shared/SessionItem/SessionMetaList'; +import UserCard from '../ReplayPlayer/EventsBlock/UserCard'; +import { PlayerContext } from 'Components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore' +import stl from '../ReplayPlayer/playerBlockHeader.module.css'; +import AssistActions from 'Components/Assist/components/AssistActions'; +import AssistTabs from 'Components/Assist/components/AssistTabs'; + +const ASSIST_ROUTE = assistRoute(); + +// TODO props +function LivePlayerBlockHeader(props: any) { + const [hideBack, setHideBack] = React.useState(false); + const { store } = React.useContext(PlayerContext); + const { assistMultiviewStore } = useStore(); + + const { width, height } = store.get(); + + const { + session, + metaList, + closedLive = false, + siteId, + location, + history, + isMultiview, + } = props; + + React.useEffect(() => { + const queryParams = new URLSearchParams(location.search); + setHideBack(queryParams.has('iframe') && queryParams.get('iframe') === 'true'); + }, []); + + const backHandler = () => { + history.push(withSiteId(ASSIST_ROUTE, siteId)); + }; + + const { userId, userNumericHash, metadata, isCallActive, agentIds } = session; + let _metaList = Object.keys(metadata) + .filter((i) => metaList.includes(i)) + .map((key) => { + const value = metadata[key]; + return { label: key, value }; + }); + + const openGrid = () => { + const sessionIdQuery = encodeURIComponent(assistMultiviewStore.sessions.map((s) => s?.sessionId).join(',')); + return history.push(withSiteId(multiview(sessionIdQuery), siteId)); + }; + + return ( + <div className={cn(stl.header, 'flex justify-between')}> + <div className="flex w-full items-center"> + {!hideBack && ( + <div + className="flex items-center h-full cursor-pointer group" + onClick={() => (assistMultiviewStore.sessions.length > 1 || isMultiview ? openGrid() : backHandler())} + > + {assistMultiviewStore.sessions.length > 1 || isMultiview ? ( + <> + <div className="rounded-full border group-hover:border-teal group-hover:text-teal group-hover:fill-teal p-1 mr-2"> + <Icon name="close" color="inherit" size={13} /> + </div> + <span className="group-hover:text-teal group-hover:fill-teal"> + Close + </span> + <div className={stl.divider} /> + </> + ) : ( + <> + {/* @ts-ignore TODO */} + <BackLink label="Back" className="h-full" /> + <div className={stl.divider} /> + </> + )} + </div> + )} + <UserCard className="" width={width} height={height} /> + <AssistTabs userId={userId} userNumericHash={userNumericHash} /> + + <div className={cn('ml-auto flex items-center h-full', { hidden: closedLive })}> + {_metaList.length > 0 && ( + <div className="border-l h-full flex items-center px-2"> + <SessionMetaList className="" metaList={_metaList} maxLength={2} /> + </div> + )} + + <AssistActions userId={userId} isCallActive={isCallActive} agentIds={agentIds} /> + </div> + </div> + </div> + ); +} + +const PlayerHeaderCont = connect( + (state: any) => { + const isAssist = window.location.pathname.includes('/assist/'); + const session = state.getIn(['sessions', 'current']); + + return { + isAssist, + session, + sessionPath: state.getIn(['sessions', 'sessionPath']), + siteId: state.getIn(['site', 'siteId']), + metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key), + closedLive: !!state.getIn(['sessions', 'errors']) || (isAssist && !session.live), + }; + } +)(observer(LivePlayerBlockHeader)); + +export default withRouter(PlayerHeaderCont); diff --git a/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx b/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx new file mode 100644 index 000000000..2472b6547 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { findDOMNode } from 'react-dom'; +import cn from 'classnames'; +import LiveControls from './LiveControls'; +import ConsolePanel from 'Shared/DevTools/ConsolePanel'; + +import Overlay from './Overlay'; +import stl from 'Components/Session_/Player/player.module.css'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; +import { CONSOLE } from "Duck/components/player"; + +interface IProps { + closedLive: boolean; + fullView: boolean; + isMultiview?: boolean; + bottomBlock: number; +} + +function Player(props: IProps) { + const { + closedLive, + fullView, + isMultiview, + bottomBlock, + } = props; + // @ts-ignore TODO + const playerContext = React.useContext<ILivePlayerContext>(PlayerContext); + const screenWrapper = React.useRef<HTMLDivElement>(null); + + React.useEffect(() => { + if (!props.closedLive || isMultiview) { + const parentElement = findDOMNode(screenWrapper.current) as HTMLDivElement | null; //TODO: good architecture + if (parentElement) { + playerContext.player.attach(parentElement); + playerContext.player.play(); + } + } + }, []); + + React.useEffect(() => { + playerContext.player.scale(); + }, [playerContext.player]); + + if (!playerContext.player) return null; + + const maxWidth = '100vw'; + return ( + <div + className={cn(stl.playerBody, 'flex flex-1 flex-col relative')} + > + <div className="relative flex-1 overflow-hidden"> + <Overlay closedLive={closedLive} /> + <div className={cn(stl.screenWrapper)} ref={screenWrapper} /> + </div> + {bottomBlock === CONSOLE ? ( + <div style={{ maxWidth, width: '100%' }}> + <ConsolePanel /> + </div> + ) : null} + {!fullView && !isMultiview ? ( + <LiveControls + jump={playerContext.player.jump} + /> + ) : null} + </div> + ); +} + +export default connect( + (state: any) => { + const isAssist = window.location.pathname.includes('/assist/'); + return { + sessionId: state.getIn(['sessions', 'current']).sessionId, + bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), + closedLive: + !!state.getIn(['sessions', 'errors']) || + (isAssist && !state.getIn(['sessions', 'current']).live), + }; + } +)(Player); diff --git a/frontend/app/components/Session/Player/LivePlayer/LivePlayerSubHeader.tsx b/frontend/app/components/Session/Player/LivePlayer/LivePlayerSubHeader.tsx new file mode 100644 index 000000000..94892a3a7 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/LivePlayerSubHeader.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Icon, Tooltip } from 'UI'; +import copy from 'copy-to-clipboard'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + +function SubHeader() { + const { store } = React.useContext(PlayerContext) + const { + location: currentLocation, + } = store.get() + const [isCopied, setCopied] = React.useState(false); + + const location = + currentLocation !== undefined ? currentLocation.length > 60 + ? `${currentLocation.slice(0, 60)}...` + : currentLocation : undefined; + + return ( + <div className="w-full px-4 py-2 flex items-center border-b min-h-3"> + {location && ( + <div + className="flex items-center cursor-pointer color-gray-medium text-sm p-1 hover:bg-gray-light-shade rounded-md" + onClick={() => { + copy(currentLocation || ''); + setCopied(true); + setTimeout(() => setCopied(false), 5000); + }} + > + <Icon size="20" name="event/link" className="mr-1" /> + <Tooltip title={isCopied ? 'URL Copied to clipboard' : 'Click to copy'}> + {location} + </Tooltip> + </div> + )} + </div> + ); +} + +export default observer(SubHeader); diff --git a/frontend/app/components/Session/Player/LivePlayer/Overlay/LiveOverlay.tsx b/frontend/app/components/Session/Player/LivePlayer/Overlay/LiveOverlay.tsx new file mode 100644 index 000000000..38eef2ba1 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/Overlay/LiveOverlay.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { + SessionRecordingStatus, + getStatusText, + CallingState, + ConnectionStatus, + RemoteControlStatus, +} from 'Player'; + +import LiveStatusText from './LiveStatusText'; +import Loader from 'Components/Session_/Player/Overlay/Loader'; +import RequestingWindow, { WindowType } from 'App/components/Assist/RequestingWindow'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + +interface Props { + closedLive?: boolean, +} + +function Overlay({ + closedLive, +}: Props) { + // @ts-ignore ?? TODO + const { store } = React.useContext<ILivePlayerContext>(PlayerContext) + + const { + messagesLoading, + cssLoading, + peerConnectionStatus, + livePlay, + calling, + remoteControl, + recordingState, + } = store.get() + const loading = messagesLoading || cssLoading + const liveStatusText = getStatusText(peerConnectionStatus) + const connectionStatus = peerConnectionStatus + + const showLiveStatusText = livePlay && liveStatusText && !loading; + + const showRequestWindow = + (calling === CallingState.Connecting || + remoteControl === RemoteControlStatus.Requesting || + recordingState === SessionRecordingStatus.Requesting); + + const getRequestWindowType = () => { + if (calling === CallingState.Connecting) { + return WindowType.Call + } + if (remoteControl === RemoteControlStatus.Requesting) { + return WindowType.Control + } + if (recordingState === SessionRecordingStatus.Requesting) { + return WindowType.Record + } + + return null; + } + + return ( + <> + {/* @ts-ignore wtf */} + {showRequestWindow ? <RequestingWindow getWindowType={getRequestWindowType} /> : null} + {showLiveStatusText && ( + <LiveStatusText + connectionStatus={closedLive ? ConnectionStatus.Closed : connectionStatus} + /> + )} + {loading ? <Loader /> : null} + </> + ); +} + +export default observer(Overlay); diff --git a/frontend/app/components/Session/Player/LivePlayer/Overlay/LiveStatusText.tsx b/frontend/app/components/Session/Player/LivePlayer/Overlay/LiveStatusText.tsx new file mode 100644 index 000000000..4c7d39f6f --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/Overlay/LiveStatusText.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import ovStl from 'Components/Session_/Player/Overlay/overlay.module.css'; +import { ConnectionStatus } from 'Player'; +import { Loader } from 'UI'; + +interface Props { + connectionStatus: ConnectionStatus; +} + +export default function LiveStatusText({ connectionStatus }: Props) { + const renderView = () => { + switch (connectionStatus) { + case ConnectionStatus.Closed: + return ( + <div className="flex flex-col items-center text-center"> + <div className="text-lg -mt-8">Session not found</div> + <div className="text-sm">The remote session doesn’t exist anymore. <br/> The user may have closed the tab/browser while you were trying to establish a connection.</div> + </div> + ) + + case ConnectionStatus.Connecting: + return ( + <div className="flex flex-col items-center"> + <Loader loading={true} /> + <div className="text-lg -mt-8">Connecting...</div> + <div className="text-sm">Establishing a connection with the remote session.</div> + </div> + ) + case ConnectionStatus.WaitingMessages: + return ( + <div className="flex flex-col items-center"> + <Loader loading={true} /> + <div className="text-lg -mt-8">Waiting for the session to become active...</div> + <div className="text-sm">If it's taking too much time, it could mean the user is simply inactive.</div> + </div> + ) + case ConnectionStatus.Connected: + return ( + <div className="flex flex-col items-center"> + <div className="text-lg -mt-8">Connected</div> + </div> + ) + case ConnectionStatus.Inactive: + return ( + <div className="flex flex-col items-center"> + <Loader loading={true} /> + <div className="text-lg -mt-8">Waiting for the session to become active...</div> + <div className="text-sm">If it's taking too much time, it could mean the user is simply inactive.</div> + </div> + ) + case ConnectionStatus.Disconnected: + return ( + <div className="flex flex-col items-center"> + <div className="text-lg -mt-8">Disconnected</div> + <div className="text-sm">The connection was lost with the remote session. The user may have simply closed the tab/browser.</div> + </div> + ) + case ConnectionStatus.Error: + return ( + <div className="flex flex-col items-center"> + <div className="text-lg -mt-8">Error</div> + <div className="text-sm">Something wrong just happened. Try refreshing the page.</div> + </div> + ) + } + } + return <div className={ovStl.overlay}> + { renderView()} + </div> +} diff --git a/frontend/app/components/Session/Player/LivePlayer/Overlay/index.ts b/frontend/app/components/Session/Player/LivePlayer/Overlay/index.ts new file mode 100644 index 000000000..0cd39ac1a --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/Overlay/index.ts @@ -0,0 +1 @@ +export { default } from './LiveOverlay' \ No newline at end of file diff --git a/frontend/app/components/Session/Player/LivePlayer/Timeline.tsx b/frontend/app/components/Session/Player/LivePlayer/Timeline.tsx new file mode 100644 index 000000000..2bddec240 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/Timeline.tsx @@ -0,0 +1,166 @@ +import React, { useMemo, useContext, useState, useRef } from 'react'; +import { connect } from 'react-redux'; +import TimeTracker from 'Components/Session_/Player/Controls/TimeTracker'; +import stl from 'Components/Session_/Player/Controls/timeline.module.css'; +import { setTimelinePointer, setTimelineHoverTime } from 'Duck/sessions'; +import DraggableCircle from 'Components/Session_/Player/Controls/components/DraggableCircle'; +import CustomDragLayer, { OnDragCallback } from 'Components/Session_/Player/Controls/components/CustomDragLayer'; +import { debounce } from 'App/utils'; +import TooltipContainer from 'Components/Session_/Player/Controls/components/TooltipContainer'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { Duration } from 'luxon'; + +interface IProps { + setTimelineHoverTime: (t: number) => void + startedAt: number + tooltipVisible: boolean +} + +function Timeline(props: IProps) { + // @ts-ignore + const { player, store } = useContext<ILivePlayerContext>(PlayerContext) + const [wasPlaying, setWasPlaying] = useState(false) + const { + playing, + time, + ready, + endTime, + liveTimeTravel, + } = store.get() + + const timelineRef = useRef<HTMLDivElement>(null) + const progressRef = useRef<HTMLDivElement>(null) + + const scale = 100 / endTime; + + const debouncedJump = useMemo(() => debounce(player.jump, 500), []) + const debouncedTooltipChange = useMemo(() => debounce(props.setTimelineHoverTime, 50), []) + + const onDragEnd = () => { + if (!liveTimeTravel) return; + + if (wasPlaying) { + player.togglePlay(); + } + }; + + const onDrag: OnDragCallback = (offset: { x: number }) => { + if ((!liveTimeTravel) || !progressRef.current) return; + + const p = (offset.x) / progressRef.current.offsetWidth; + const time = Math.max(Math.round(p * endTime), 0); + debouncedJump(time); + hideTimeTooltip(); + if (playing) { + setWasPlaying(true) + player.pause(); + } + }; + + const getLiveTime = (e: React.MouseEvent) => { + const duration = new Date().getTime() - props.startedAt; + // @ts-ignore type mismatch from react? + const p = e.nativeEvent.offsetX / e.target.offsetWidth; + const time = Math.max(Math.round(p * duration), 0); + + return [time, duration]; + }; + + const showTimeTooltip = (e: React.MouseEvent<HTMLDivElement>) => { + if (e.target !== progressRef.current && e.target !== timelineRef.current) { + return props.tooltipVisible && hideTimeTooltip(); + } + + const [time, duration] = getLiveTime(e); + const timeLineTooltip = { + time: Duration.fromMillis(duration - time).toFormat(`-mm:ss`), + offset: e.nativeEvent.offsetX, + isVisible: true, + }; + + debouncedTooltipChange(timeLineTooltip); + } + + const hideTimeTooltip = () => { + const timeLineTooltip = { isVisible: false }; + debouncedTooltipChange(timeLineTooltip); + }; + + const seekProgress = (e: React.MouseEvent<HTMLDivElement>) => { + const time = getTime(e); + player.jump(time); + hideTimeTooltip(); + }; + + const loadAndSeek = async (e: React.MouseEvent<HTMLDivElement>) => { + e.persist(); + await player.toggleTimetravel(); + + setTimeout(() => { + seekProgress(e); + }); + }; + + const jumpToTime = (e: React.MouseEvent<HTMLDivElement>) => { + if (!liveTimeTravel) { + void loadAndSeek(e); + } else { + seekProgress(e); + } + }; + + const getTime = (e: React.MouseEvent<HTMLDivElement>, customEndTime?: number) => { + // @ts-ignore type mismatch from react? + const p = e.nativeEvent.offsetX / e.target.offsetWidth; + const targetTime = customEndTime || endTime; + + return Math.max(Math.round(p * targetTime), 0); + }; + + return ( + <div + className="flex items-center absolute w-full" + style={{ + top: '-4px', + zIndex: 100, + maxWidth: 'calc(100% - 1rem)', + left: '0.5rem', + }} + > + <div + className={stl.progress} + onClick={ready ? jumpToTime : undefined } + ref={progressRef} + role="button" + onMouseMoveCapture={showTimeTooltip} + onMouseEnter={showTimeTooltip} + onMouseLeave={hideTimeTooltip} + > + <TooltipContainer live /> + <DraggableCircle + left={time * scale} + onDrop={onDragEnd} + live + /> + <CustomDragLayer + onDrag={onDrag} + minX={0} + maxX={progressRef.current ? progressRef.current.offsetWidth : 0} + /> + <TimeTracker scale={scale} live left={time * scale} /> + + + <div className={stl.timeline} ref={timelineRef} /> + </div> + </div> + ) +} + +export default connect( + (state: any) => ({ + startedAt: state.getIn(['sessions', 'current']).startedAt || 0, + tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']), + }), + { setTimelinePointer, setTimelineHoverTime } +)(observer(Timeline)) diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Event.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Event.js new file mode 100644 index 000000000..e8f985aa0 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Event.js @@ -0,0 +1,174 @@ +import React from 'react'; +import copy from 'copy-to-clipboard'; +import cn from 'classnames'; +import { Icon, TextEllipsis } from 'UI'; +import { TYPES } from 'Types/session/event'; +import { prorata } from 'App/utils'; +import withOverlay from 'Components/hocs/withOverlay'; +import LoadInfo from './LoadInfo'; +import cls from './event.module.css'; +import { numberWithCommas } from 'App/utils'; + +@withOverlay() +export default class Event extends React.PureComponent { + state = { + menuOpen: false, + } + + componentDidMount() { + this.wrapper.addEventListener('contextmenu', this.onContextMenu); + } + + onContextMenu = (e) => { + e.preventDefault(); + this.setState({ menuOpen: true }); + } + onMouseLeave = () => this.setState({ menuOpen: false }) + + copyHandler = (e) => { + e.stopPropagation(); + //const ctrlOrCommandPressed = e.ctrlKey || e.metaKey; + //if (ctrlOrCommandPressed && e.keyCode === 67) { + const { event } = this.props; + copy(event.getIn([ 'target', 'path' ]) || event.url || ''); + this.setState({ menuOpen: false }); + } + + toggleInfo = (e) => { + e.stopPropagation(); + this.props.toggleInfo(); + } + + // eslint-disable-next-line complexity + renderBody = () => { + const { event } = this.props; + let title = event.type; + let body; + switch (event.type) { + case TYPES.LOCATION: + title = 'Visited'; + body = event.url; + break; + case TYPES.CLICK: + title = 'Clicked'; + body = event.label; + break; + case TYPES.INPUT: + title = 'Input'; + body = event.value; + break; + case TYPES.CLICKRAGE: + title = `${ event.count } Clicks`; + body = event.label; + break; + case TYPES.IOS_VIEW: + title = 'View'; + body = event.name; + break; + } + const isLocation = event.type === TYPES.LOCATION; + const isClickrage = event.type === TYPES.CLICKRAGE; + + return ( + <div className={ cn(cls.main, 'flex flex-col w-full') } > + <div className="flex items-center w-full"> + { event.type && <Icon name={`event/${event.type.toLowerCase()}`} size="16" color={isClickrage? 'red' : 'gray-dark' } /> } + <div className="ml-3 w-full"> + <div className="flex w-full items-first justify-between"> + <div className="flex items-center w-full" style={{ minWidth: '0'}}> + <span className={cls.title}>{ title }</span> + {/* { body && !isLocation && <div className={ cls.data }>{ body }</div> } */} + { body && !isLocation && + <TextEllipsis maxWidth="60%" className="w-full ml-2 text-sm color-gray-medium" text={body} /> + } + </div> + { isLocation && event.speedIndex != null && + <div className="color-gray-medium flex font-medium items-center leading-none justify-end"> + <div className="font-size-10 pr-2">{"Speed Index"}</div> + <div>{ numberWithCommas(event.speedIndex || 0) }</div> + </div> + } + </div> + { event.target && event.target.label && + <div className={ cls.badge } >{ event.target.label }</div> + } + </div> + </div> + { isLocation && + <div className="mt-1"> + <span className="text-sm font-normal color-gray-medium">{ body }</span> + </div> + } + </div> + ); + }; + + render() { + const { + event, + selected, + isCurrent, + onClick, + showSelection, + onCheckboxClick, + showLoadInfo, + toggleLoadInfo, + isRed, + extended, + highlight = false, + presentInSearch = false, + isLastInGroup, + whiteBg, + } = this.props; + const { menuOpen } = this.state; + return ( + <div + ref={ ref => { this.wrapper = ref } } + onMouseLeave={ this.onMouseLeave } + data-openreplay-label="Event" + data-type={event.type} + className={ cn(cls.event, { + [ cls.menuClosed ]: !menuOpen, + [ cls.highlighted ]: showSelection ? selected : isCurrent, + [ cls.selected ]: selected, + [ cls.showSelection ]: showSelection, + [ cls.red ]: isRed, + [ cls.clickType ]: event.type === TYPES.CLICK, + [ cls.inputType ]: event.type === TYPES.INPUT, + [ cls.clickrageType ]: event.type === TYPES.CLICKRAGE, + [ cls.highlight ] : presentInSearch, + [ cls.lastInGroup ]: whiteBg, + }) } + onClick={ onClick } + > + { menuOpen && + <button onClick={ this.copyHandler } className={ cls.contextMenu }> + { event.target ? 'Copy CSS' : 'Copy URL' } + </button> + } + <div className={ cls.topBlock }> + <div className={ cls.firstLine }> + { this.renderBody() } + </div> + {/* { event.type === TYPES.LOCATION && + <div className="text-sm font-normal color-gray-medium">{event.url}</div> + } */} + </div> + { event.type === TYPES.LOCATION && (event.fcpTime || event.visuallyComplete || event.timeToInteractive) && + <LoadInfo + showInfo={ showLoadInfo } + onClick={ toggleLoadInfo } + event={ event } + prorata={ prorata({ + parts: 100, + elements: { a: event.fcpTime, b: event.visuallyComplete, c: event.timeToInteractive }, + startDivisorFn: elements => elements / 1.2, + // eslint-disable-next-line no-mixed-operators + divisorFn: (elements, parts) => elements / (2 * parts + 1), + }) } + /> + } + </div> + ); + } +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js new file mode 100644 index 000000000..1ec053462 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js @@ -0,0 +1,131 @@ +import React from 'react'; +import cn from 'classnames'; +import { connect } from 'react-redux' +import { TextEllipsis } from 'UI'; +import withToggle from 'HOCs/withToggle'; +import { TYPES } from 'Types/session/event'; +import Event from './Event' +import stl from './eventGroupWrapper.module.css'; +import NoteEvent from './NoteEvent'; +import { setEditNoteTooltip } from 'Duck/sessions';; + +// TODO: incapsulate toggler in LocationEvent +@withToggle('showLoadInfo', 'toggleLoadInfo') +@connect( + (state) => ({ + members: state.getIn(['members', 'list']), + currentUserId: state.getIn(['user', 'account', 'id']), + }), + { setEditNoteTooltip } +) +class EventGroupWrapper extends React.Component { + toggleLoadInfo = (e) => { + e.stopPropagation(); + this.props.toggleLoadInfo(); + }; + + componentDidUpdate(prevProps) { + if ( + prevProps.showLoadInfo !== this.props.showLoadInfo || + prevProps.query !== this.props.query || + prevProps.event.timestamp !== this.props.event.timestamp || + prevProps.isNote !== this.props.isNote + ) { + this.props.mesureHeight(); + } + } + componentDidMount() { + this.props.toggleLoadInfo(this.props.isFirst); + this.props.mesureHeight(); + } + + onEventClick = (e) => this.props.onEventClick(e, this.props.event); + + onCheckboxClick = (e) => this.props.onCheckboxClick(e, this.props.event); + + render() { + const { + event, + isLastEvent, + isLastInGroup, + isSelected, + isCurrent, + isEditing, + showSelection, + showLoadInfo, + isFirst, + presentInSearch, + isNote, + filterOutNote, + } = this.props; + const isLocation = event.type === TYPES.LOCATION; + + const whiteBg = + (isLastInGroup && event.type !== TYPES.LOCATION) || + (!isLastEvent && event.type !== TYPES.LOCATION); + const safeRef = String(event.referrer || ''); + + return ( + <div + className={cn( + stl.container, + '!py-1', + { + [stl.last]: isLastInGroup, + [stl.first]: event.type === TYPES.LOCATION, + [stl.dashAfter]: isLastInGroup && !isLastEvent, + }, + isLastInGroup && '!pb-2', + event.type === TYPES.LOCATION && '!pt-2 !pb-2' + )} + > + {isFirst && isLocation && event.referrer && ( + <div className={stl.referrer}> + <TextEllipsis> + Referrer: <span className={stl.url}>{safeRef}</span> + </TextEllipsis> + </div> + )} + {isNote ? ( + <NoteEvent + userEmail={this.props.members.find((m) => m.id === event.userId)?.email || event.userId} + note={event} + filterOutNote={filterOutNote} + onEdit={this.props.setEditNoteTooltip} + noEdit={this.props.currentUserId !== event.userId} + /> + ) : isLocation ? ( + <Event + extended={isFirst} + key={event.key} + event={event} + onClick={this.onEventClick} + selected={isSelected} + showLoadInfo={showLoadInfo} + toggleLoadInfo={this.toggleLoadInfo} + isCurrent={isCurrent} + presentInSearch={presentInSearch} + isLastInGroup={isLastInGroup} + whiteBg={whiteBg} + /> + ) : ( + <Event + key={event.key} + event={event} + onClick={this.onEventClick} + onCheckboxClick={this.onCheckboxClick} + selected={isSelected} + isCurrent={isCurrent} + showSelection={showSelection} + overlayed={isEditing} + presentInSearch={presentInSearch} + isLastInGroup={isLastInGroup} + whiteBg={whiteBg} + /> + )} + </div> + ); + } +} + +export default EventGroupWrapper diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/EventSearch.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/EventSearch.js new file mode 100644 index 000000000..419434d22 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/EventSearch.js @@ -0,0 +1,43 @@ +import React from 'react' +import { Input, Icon } from 'UI' +import { PlayerContext } from 'App/components/Session/playerContext'; + +function EventSearch(props) { + const { player } = React.useContext(PlayerContext) + + const { onChange, value, header, setActiveTab } = props; + + const toggleEvents = () => player.toggleEvents() + + return ( + <div className="flex items-center w-full relative"> + <div className="flex flex-1 flex-col"> + <div className='flex flex-center justify-between'> + <span>{header}</span> + <div + onClick={() => { setActiveTab(''); toggleEvents(); }} + className=" flex items-center justify-center bg-white cursor-pointer" + > + <Icon name="close" size="18" /> + </div> + </div> + <div className="flex items-center mt-2"> + <Input + autoFocus + type="text" + placeholder="Filter by Event Type, URL or Keyword" + className="inset-0 w-full" + name="query" + value={value} + onChange={onChange} + wrapperClassName="w-full" + style={{ height: '32px' }} + autoComplete="off chromebugfix" + /> + </div> + </div> + </div> + ) +} + +export default EventSearch diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/index.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/index.js new file mode 100644 index 000000000..8be1f4ddc --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/index.js @@ -0,0 +1 @@ +export { default } from './EventSearch' \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventsBlock.tsx new file mode 100644 index 000000000..bd2ad36ef --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventsBlock.tsx @@ -0,0 +1,185 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import cn from 'classnames'; +import { Icon } from 'UI'; +import { List, AutoSizer, CellMeasurer } from "react-virtualized"; +import { TYPES } from 'Types/session/event'; +import { setEventFilter, filterOutNote } from 'Duck/sessions'; +import EventGroupWrapper from './EventGroupWrapper'; +import styles from './eventsBlock.module.css'; +import EventSearch from './EventSearch/EventSearch'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { RootStore } from 'App/duck' +import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache' +import { InjectedEvent } from 'Types/session/event' +import Session from 'Types/session' + +interface IProps { + setEventFilter: (filter: { query: string }) => void + filteredEvents: InjectedEvent[] + setActiveTab: (tab?: string) => void + query: string + session: Session + filterOutNote: (id: string) => void + eventsIndex: number[] +} + +function EventsBlock(props: IProps) { + const [mouseOver, setMouseOver] = React.useState(true) + const scroller = React.useRef<List>(null) + const cache = useCellMeasurerCache(undefined, { + fixedWidth: true, + defaultHeight: 300 + }); + + const { store, player } = React.useContext(PlayerContext) + + const { eventListNow, playing } = store.get() + + const { session: { events, notesWithEvents }, filteredEvents, + eventsIndex, + filterOutNote, + query, + setActiveTab, + } = props + const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0 + const usedEvents = filteredEvents || notesWithEvents + + const write = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => { + props.setEventFilter({ query: value }) + + setTimeout(() => { + if (!scroller.current) return; + + scroller.current.scrollToRow(0); + }, 100) + } + + const clearSearch = () => { + props.setEventFilter({ query: '' }) + if (scroller.current) { + scroller.current.forceUpdateGrid(); + } + + setTimeout(() => { + if (!scroller.current) return; + + scroller.current.scrollToRow(0); + }, 100) + } + + React.useEffect(() => { + return () => { + clearSearch() + } + }, []) + React.useEffect(() => { + if (scroller.current) { + scroller.current.forceUpdateGrid(); + if (!mouseOver) { + scroller.current.scrollToRow(currentTimeEventIndex); + } + } + }, [currentTimeEventIndex]) + + const onEventClick = (_: React.MouseEvent, event: { time: number }) => player.jump(event.time) + const onMouseOver = () => setMouseOver(true) + const onMouseLeave = () => setMouseOver(false) + + const renderGroup = ({ index, key, style, parent }: { index: number; key: string; style: React.CSSProperties; parent: any }) => { + const isLastEvent = index === usedEvents.length - 1; + const isLastInGroup = isLastEvent || usedEvents[index + 1]?.type === TYPES.LOCATION; + const event = usedEvents[index]; + const isNote = 'noteId' in event + const isCurrent = index === currentTimeEventIndex; + + const heightBug = index === 0 && event?.type === TYPES.LOCATION && 'referrer' in event ? { top: 2 } : {} + return ( + <CellMeasurer + key={key} + cache={cache} + parent={parent} + rowIndex={index} + > + {({measure, registerChild}) => ( + <div style={{ ...style, ...heightBug }} ref={registerChild}> + <EventGroupWrapper + query={query} + presentInSearch={eventsIndex.includes(index)} + isFirst={index==0} + mesureHeight={measure} + onEventClick={ onEventClick } + event={ event } + isLastEvent={ isLastEvent } + isLastInGroup={ isLastInGroup } + isCurrent={ isCurrent } + showSelection={ !playing } + isNote={isNote} + filterOutNote={filterOutNote} + /> + </div> + )} + </CellMeasurer> + ); + } + + const isEmptySearch = query && (usedEvents.length === 0 || !usedEvents) + return ( + <> + <div className={ cn(styles.header, 'p-4') }> + <div className={ cn(styles.hAndProgress, 'mt-3') }> + <EventSearch + onChange={write} + setActiveTab={setActiveTab} + value={query} + header={ + <div className="text-xl">User Steps <span className="color-gray-medium">{ events.length }</span></div> + } + /> + </div> + </div> + <div + className={ cn("flex-1 px-4 pb-4", styles.eventsList) } + id="eventList" + data-openreplay-masked + onMouseOver={ onMouseOver } + onMouseLeave={ onMouseLeave } + > + {isEmptySearch && ( + <div className='flex items-center'> + <Icon name="binoculars" size={18} /> + <span className='ml-2'>No Matching Results</span> + </div> + )} + <AutoSizer disableWidth> + {({ height }) => ( + <List + ref={scroller} + className={ styles.eventsList } + height={height + 10} + width={248} + overscanRowCount={6} + itemSize={230} + rowCount={usedEvents.length} + deferredMeasurementCache={cache} + rowHeight={cache.rowHeight} + rowRenderer={renderGroup} + scrollToAlignment="start" + /> + )} + </AutoSizer> + </div> + </> + ); +} + +export default connect((state: RootStore) => ({ + session: state.getIn([ 'sessions', 'current' ]), + filteredEvents: state.getIn([ 'sessions', 'filteredEvents' ]), + query: state.getIn(['sessions', 'eventsQuery']), + eventsIndex: state.getIn([ 'sessions', 'eventsIndex' ]), +}), { + setEventFilter, + filterOutNote +})(observer(EventsBlock)) diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/LoadInfo.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/LoadInfo.js new file mode 100644 index 000000000..664caeb9b --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/LoadInfo.js @@ -0,0 +1,40 @@ +import React from 'react'; +import styles from './loadInfo.module.css'; +import { numberWithCommas } from 'App/utils' + +const LoadInfo = ({ showInfo = false, onClick, event: { fcpTime, visuallyComplete, timeToInteractive }, prorata: { a, b, c } }) => ( + <div> + <div className={ styles.bar } onClick={ onClick }> + { typeof fcpTime === 'number' && <div style={ { width: `${ a }%` } } /> } + { typeof visuallyComplete === 'number' && <div style={ { width: `${ b }%` } } /> } + { typeof timeToInteractive === 'number' && <div style={ { width: `${ c }%` } } /> } + </div> + <div className={ styles.bottomBlock } data-hidden={ !showInfo }> + { typeof fcpTime === 'number' && + <div className={ styles.wrapper }> + <div className={ styles.lines } /> + <div className={ styles.label } >{ 'Time to Render' }</div> + <div className={ styles.value }>{ `${ numberWithCommas(fcpTime || 0) }ms` }</div> + </div> + } + { typeof visuallyComplete === 'number' && + <div className={ styles.wrapper }> + <div className={ styles.lines } /> + <div className={ styles.label } >{ 'Visually Complete' }</div> + <div className={ styles.value }>{ `${ numberWithCommas(visuallyComplete || 0) }ms` }</div> + </div> + } + { typeof timeToInteractive === 'number' && + <div className={ styles.wrapper }> + <div className={ styles.lines } /> + <div className={ styles.label } >{ 'Time To Interactive' }</div> + <div className={ styles.value }>{ `${ numberWithCommas(timeToInteractive || 0) }ms` }</div> + </div> + } + </div> + </div> +); + +LoadInfo.displayName = 'LoadInfo'; + +export default LoadInfo; diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/Metadata.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/Metadata.js new file mode 100644 index 000000000..ca0a953a2 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/Metadata.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import MetadataItem from './MetadataItem'; + +export default connect(state => ({ + metadata: state.getIn([ 'sessions', 'current' ]).metadata, +}))(function Metadata ({ metadata }) { + + const metaLenth = Object.keys(metadata).length; + + if (metaLenth === 0) { + return ( + (<span className="text-sm color-gray-medium">Check <a href="https://docs.openreplay.com/installation/metadata" target="_blank" className="link">how to use Metadata</a> if you haven’t yet done so.</span>) + ) + } + return ( + <div> + { Object.keys(metadata).map((key) => { + // const key = Object.keys(i)[0] + const value = metadata[key] + return <MetadataItem item={ { value, key } } key={ key } /> + }) } + </div> + ); +}); diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/MetadataItem.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/MetadataItem.js new file mode 100644 index 000000000..47a785c5a --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/MetadataItem.js @@ -0,0 +1,75 @@ +import React from 'react'; +import { List } from 'immutable'; +import cn from 'classnames'; +import { withRequest, withToggle } from 'HOCs'; +import { Button, Icon, SlideModal, TextEllipsis } from 'UI'; +import stl from './metadataItem.module.css'; +import SessionList from './SessionList'; + +@withToggle() +@withRequest({ + initialData: List(), + endpoint: '/metadata/session_search', + dataWrapper: data => Object.values(data), + dataName: 'similarSessions', +}) +export default class extends React.PureComponent { + state = { + requested: false, + } + switchOpen = () => { + const { + item: { + key, value + }, + request, + switchOpen, + } = this.props; + + const { requested } = this.state; + if (!requested) { + this.setState({ requested: true }); + request({ key, value }); + } + switchOpen(); + } + + render() { + const { + item, + similarSessions, + open, + loading, + } = this.props; + + return ( + <div> + <SlideModal + title={ <div className={ stl.searchResultsHeader }>{ `All Sessions Matching - ` } <span>{ item.key + ' - ' + item.value }</span> </div> } + isDisplayed={ open } + content={ open && <SessionList similarSessions={ similarSessions } loading={ loading } /> } + onClose={ open ? this.switchOpen : () => null } + /> + <div className={ cn("flex justify-between items-center p-3 capitalize", stl.field) } > + <div> + <div className={ stl.key }>{ item.key }</div> + <TextEllipsis + maxWidth="210px" + popupProps={ { disabled: item.value && item.value.length < 30 } } + > + { item.value } + </TextEllipsis> + </div> + <Button + onClick={ this.switchOpen } + variant="text" + className={ stl.searchButton } + id="metadata-item" + > + <Icon name="search" size="16" color="teal" /> + </Button> + </div> + </div> + ); + } +} \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionLine.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionLine.js new file mode 100644 index 000000000..729271ece --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionLine.js @@ -0,0 +1,40 @@ +import { BrowserIcon, OsIcon, Icon, CountryFlag, Link } from 'UI'; +import { deviceTypeIcon } from 'App/iconNames'; +import { session as sessionRoute } from 'App/routes'; +import { formatTimeOrDate } from 'App/date'; + + +const SessionLine = ({ session: { + userBrowser, + userOs, + userCountry, + siteId, + sessionId, + viewed, + userDeviceType, + startedAt + } }) => ( + <div className="flex justify-between items-center" style={{ padding: '5px 20px' }}> + <div className="color-gray-medium font-size-10" > + <CountryFlag country={ userCountry } className="mr-4" /> + { formatTimeOrDate(startedAt) } + </div> + <div className="flex"> + <BrowserIcon browser={ userBrowser } className="mr-4" /> + <OsIcon os={ userOs } size="20" className="mr-4" /> + <Icon name={ deviceTypeIcon(userDeviceType) } size="20" className="mr-4" /> + </div> + <Link to={ sessionRoute(sessionId) } siteId={ siteId } > + <Icon + name={ viewed ? 'play-fill' : 'play-circle-light' } + size="20" + color="teal" + /> + </Link> + </div> +); + + +SessionLine.displayName = "SessionLine"; + +export default SessionLine; diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionList.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionList.js new file mode 100644 index 000000000..92ac93432 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionList.js @@ -0,0 +1,60 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { NoContent, Icon, Loader } from 'UI'; +import Session from 'Types/session'; +import SessionItem from 'Shared/SessionItem'; +import stl from './sessionList.module.css'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; + +@connect((state) => ({ + currentSessionId: state.getIn(['sessions', 'current']).sessionId, +})) +class SessionList extends React.PureComponent { + render() { + const { similarSessions, loading, currentSessionId } = this.props; + + const similarSessionWithoutCurrent = similarSessions + .map(({ sessions, ...rest }) => { + return { + ...rest, + sessions: sessions.map(s => new Session(s)).filter(({ sessionId }) => sessionId !== currentSessionId), + }; + }) + .filter((site) => site.sessions.length > 0); + + return ( + <Loader loading={loading}> + <NoContent + show={!loading && (similarSessionWithoutCurrent.length === 0 || similarSessionWithoutCurrent.size === 0)} + title={ + <div className="flex items-center justify-center flex-col"> + <AnimatedSVG name={ICONS.NO_SESSIONS} size={170} /> + <div className="mt-2" /> + <div className="text-center text-gray-600">No sessions found.</div> + </div> + } + > + <div className={stl.sessionList}> + {similarSessionWithoutCurrent.map((site) => ( + <div className={stl.siteWrapper} key={site.host}> + <div className={stl.siteHeader}> + <Icon name="window" size="14" color="gray-medium" marginRight="10" /> + <span>{site.name}</span> + </div> + <div className="bg-white p-3 rounded border"> + {site.sessions.map((session) => ( + <div className="border-b last:border-none"> + <SessionItem key={session.sessionId} session={session} /> + </div> + ))} + </div> + </div> + ))} + </div> + </NoContent> + </Loader> + ); + } +} + +export default SessionList; diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/index.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/index.js new file mode 100644 index 000000000..17932470b --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/index.js @@ -0,0 +1 @@ +export { default } from './Metadata'; \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadata.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadata.module.css new file mode 100644 index 000000000..268e04ba7 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadata.module.css @@ -0,0 +1,51 @@ + +@import 'mixins.css'; + +/* .wrapper { + position: relative; +} */ + +.modal { + /* width: 288px; */ + /* position: absolute; */ + /* top: 50px; */ + /* right: 0; */ + /* background-color: white; */ + /* border-radius: 3px; */ + /* z-index: 99; */ + /* padding: 10px; */ + /* min-height: 90px; */ + max-height: 300px; + overflow: auto; + /* @mixin shadow; */ + /* border: solid thin $gray-light; */ + + /* & .tooltipArrow { + width: 50px; + height: 25px; + position: absolute; + bottom: 100%; + right: 0; + transform: translateX(-50%); + overflow: hidden; + + &::after { + content: ""; + position: absolute; + width: 16px; + height: 16px; + background: white; + transform: translateX(-50%) translateY(50%) rotate(45deg); + bottom: 0; + left: 50%; + box-shadow: 2px 2px 6px 0px rgba(0,0,0,0.6); + } + } */ +} + +.header { + font-size: 18px; + font-weight: 500; + /* padding: 10px 20px; */ + border-bottom: solid thin $gray-light; +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadataItem.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadataItem.module.css new file mode 100644 index 000000000..2dc522306 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadataItem.module.css @@ -0,0 +1,37 @@ + + +.field { + &:not(:last-child) { + border-bottom: solid thin $gray-light-shade; + } + /* padding: 10px 20px; */ +} + +.key { + color: $gray-medium; + font-weight: 500; +} + +.searchResultsHeader { + & span { + padding: 4px 8px; + font-size: 18px; + background-color: $gray-lightest; + border: solid thin $gray-light; + margin-left: 10px; + border-radius: 3px; + } +} + +.searchButton { + border-radius: 3px; + height: 30px !important; + width: 30px !important; + display: flex !important; + align-items: center !important; + padding: 0 !important; + justify-content: center !important; + &:hover { + background-color: $gray-lightest !important; + } +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/sessionList.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/sessionList.module.css new file mode 100644 index 000000000..0c63d2ae0 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/sessionList.module.css @@ -0,0 +1,26 @@ + + +.sessionList { + padding: 0 20px; + background-color: #f6f6f6; + min-height: calc(100vh - 59px); +} + +.siteWrapper { + padding-top: 10px; + margin-bottom: 10px; +} + +.siteHeader { + display: flex; + align-items: center; + margin-bottom: 15px; + font-weight: 400; + font-size: 14px; + background-color: white; + border-top: solid thin $gray-lightest; + margin: -15px; + margin-top: -10px; + margin-bottom: 20px; + padding: 10px; +} \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx new file mode 100644 index 000000000..676b1f901 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import { Icon } from 'UI'; +import { tagProps, Note } from 'App/services/NotesService'; +import { formatTimeOrDate } from 'App/date'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import { ItemMenu } from 'UI'; +import copy from 'copy-to-clipboard'; +import { toast } from 'react-toastify'; +import { session } from 'App/routes'; +import { confirm } from 'UI'; +import { TeamBadge } from 'Shared/SessionListContainer/components/Notes'; + +interface Props { + note: Note; + noEdit: boolean; + userEmail: string; + filterOutNote: (id: number) => void; + onEdit: (noteTooltipObj: Record<string, any>) => void; +} + +function NoteEvent(props: Props) { + const { settingsStore, notesStore } = useStore(); + const { timezone } = settingsStore.sessionSettings; + + const onEdit = () => { + props.onEdit({ + isVisible: true, + isEdit: true, + time: props.note.timestamp, + note: { + timestamp: props.note.timestamp, + tag: props.note.tag, + isPublic: props.note.isPublic, + message: props.note.message, + sessionId: props.note.sessionId, + noteId: props.note.noteId, + }, + }); + }; + + const onCopy = () => { + copy( + `${window.location.origin}/${window.location.pathname.split('/')[1]}${session( + props.note.sessionId + )}${props.note.timestamp > 0 ? `?jumpto=${props.note.timestamp}¬e=${props.note.noteId}` : `?note=${props.note.noteId}`}` + ); + toast.success('Note URL copied to clipboard'); + }; + + const onDelete = async () => { + if ( + await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to delete this note?`, + }) + ) { + notesStore.deleteNote(props.note.noteId).then((r) => { + props.filterOutNote(props.note.noteId); + toast.success('Note deleted'); + }); + } + }; + const menuItems = [ + { icon: 'pencil', text: 'Edit', onClick: onEdit, disabled: props.noEdit }, + { icon: 'link-45deg', text: 'Copy URL', onClick: onCopy }, + { icon: 'trash', text: 'Delete', onClick: onDelete }, + ]; + return ( + <div + className="flex items-start flex-col p-2 border rounded" + style={{ background: '#FFFEF5' }} + > + <div className="flex items-center w-full relative"> + <div className="p-3 bg-gray-light rounded-full"> + <Icon name="quotes" color="main" /> + </div> + <div className="ml-2"> + <div + className="text-base" + style={{ + maxWidth: 150, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }} + > + {props.userEmail}, {props.userEmail} + </div> + <div className="text-disabled-text text-sm"> + {formatTimeOrDate(props.note.createdAt as unknown as number, timezone)} + </div> + </div> + <div className="cursor-pointer absolute" style={{ right: -5 }}> + <ItemMenu bold items={menuItems} /> + </div> + </div> + <div + className="text-base capitalize-first my-3 overflow-y-scroll overflow-x-hidden" + style={{ maxHeight: 200, maxWidth: 220 }} + > + {props.note.message} + </div> + <div> + <div className="flex items-center gap-2 flex-wrap w-full"> + {props.note.tag ? ( + <div + key={props.note.tag} + style={{ + // @ts-ignore + background: tagProps[props.note.tag], + userSelect: 'none', + padding: '1px 6px', + }} + className="rounded-full text-white text-xs select-none w-fit" + > + {props.note.tag} + </div> + ) : null} + {!props.note.isPublic ? null : <TeamBadge />} + </div> + </div> + </div> + ); +} + +export default observer(NoteEvent); diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/UserCard.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/UserCard.js new file mode 100644 index 000000000..ecafe6611 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/UserCard.js @@ -0,0 +1,123 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { List } from 'immutable'; +import { countries } from 'App/constants'; +import { useStore } from 'App/mstore'; +import { browserIcon, osIcon, deviceTypeIcon } from 'App/iconNames'; +import { formatTimeOrDate } from 'App/date'; +import { Avatar, TextEllipsis, CountryFlag, Icon, Tooltip, Popover } from 'UI'; +import cn from 'classnames'; +import { withRequest } from 'HOCs'; +import SessionInfoItem from 'Components/Session_/SessionInfoItem'; +import { useModal } from 'App/components/Modal'; +import UserSessionsModal from 'Shared/UserSessionsModal'; + +function UserCard({ className, request, session, width, height, similarSessions, loading }) { + const { settingsStore } = useStore(); + const { timezone } = settingsStore.sessionSettings; + + const { + userBrowser, + userDevice, + userCountry, + userBrowserVersion, + userOs, + userOsVersion, + startedAt, + userId, + userAnonymousId, + userNumericHash, + userDisplayName, + userDeviceType, + revId, + } = session; + + const hasUserDetails = !!userId || !!userAnonymousId; + + const getDimension = (width, height) => { + return width && height ? ( + <div className="flex items-center"> + {width || 'x'} <Icon name="close" size="12" className="mx-1" /> {height || 'x'} + </div> + ) : ( + <span className="">Resolution N/A</span> + ); + }; + + const avatarbgSize = '38px'; + return ( + <div className={cn('bg-white flex items-center w-full', className)}> + <div className="flex items-center"> + <Avatar iconSize="23" width={avatarbgSize} height={avatarbgSize} seed={userNumericHash} /> + <div className="ml-3 overflow-hidden leading-tight"> + <TextEllipsis + noHint + className={cn('font-medium', { 'color-teal cursor-pointer': hasUserDetails })} + // onClick={hasUserDetails ? showSimilarSessions : undefined} + > + <UserName name={userDisplayName} userId={userId} hash={userNumericHash} /> + </TextEllipsis> + + <div className="text-sm color-gray-medium flex items-center"> + <span style={{ whiteSpace: 'nowrap' }}> + <Tooltip + title={`${formatTimeOrDate(startedAt, timezone, true)} ${timezone.label}`} + className="w-fit !block" + > + {formatTimeOrDate(startedAt, timezone)} + </Tooltip> + + </span> + <span className="mx-1 font-bold text-xl">·</span> + <span>{countries[userCountry]}</span> + <span className="mx-1 font-bold text-xl">·</span> + <span className="capitalize"> + {userBrowser}, {userOs}, {userDevice} + </span> + <span className="mx-1 font-bold text-xl">·</span> + <Popover + render={() => ( + <div className="text-left bg-white"> + <SessionInfoItem + comp={<CountryFlag country={userCountry} />} + label={countries[userCountry]} + value={<span style={{ whiteSpace: 'nowrap' }}>{formatTimeOrDate(startedAt)}</span>} + /> + <SessionInfoItem icon={browserIcon(userBrowser)} label={userBrowser} value={`v${userBrowserVersion}`} /> + <SessionInfoItem icon={osIcon(userOs)} label={userOs} value={userOsVersion} /> + <SessionInfoItem + icon={deviceTypeIcon(userDeviceType)} + label={userDeviceType} + value={getDimension(width, height)} + isLast={!revId} + /> + {revId && <SessionInfoItem icon="info" label="Rev ID:" value={revId} isLast />} + </div> + )} + > + <span className="link">More</span> + </Popover> + </div> + </div> + </div> + </div> + ); +} + +const component = React.memo(connect((state) => ({ session: state.getIn(['sessions', 'current']) }))(UserCard)); + +export default withRequest({ + initialData: List(), + endpoint: '/metadata/session_search', + dataWrapper: (data) => Object.values(data), + dataName: 'similarSessions', +})(component); + +// inner component +function UserName({ name, userId, hash }) { + const { showModal } = useModal(); + const onClick = () => { + showModal(<UserSessionsModal userId={userId} hash={hash} name={name} />, { right: true }); + }; + return <div onClick={userId ? onClick : () => {}}>{name}</div>; +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/index.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/index.js new file mode 100644 index 000000000..30f2f5b8a --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/index.js @@ -0,0 +1 @@ +export { default } from './UserCard'; \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/event.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/event.module.css new file mode 100644 index 000000000..8afea78f2 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/event.module.css @@ -0,0 +1,165 @@ +.contextMenu { + position: absolute; + top: 27px; + right: 15px; + padding: 2px 3px; + background: $white; + border: 1px solid $gray-light; + border-radius: 3px; + cursor: pointer; + color: $gray-medium; + font-size: 12px; + z-index: 2; +} + +.event { + position: relative; + background: #f6f6f6; + border-radius: 3px; + user-select: none; + /* box-shadow: 0px 1px 3px 0 $gray-light; */ + transition: all 0.2s; + cursor: pointer; + border: 1px solid transparent; + &:hover { + background-color: $active-blue; + border: 1px solid $active-blue-border; + } + + & .title { + font-size: 13px; + } + + & .topBlock { + min-height: 30px; + position: relative; + padding: 8px 10px; + } + + & .checkbox { + position: absolute; + left: 10px; + top: 8px; + bottom: 0; + /* margin: auto; */ + display: none; + /* align-items: center; */ + } + + &.menuClosed:hover { + & .edit { + opacity: 1; + transition: all 0.2s; + } + } + + &.menuClosed.showSelection { + &:hover, &.selected { + background-color: #EFFCFB; + + & .checkbox { + display: flex; + } + + & .icon { + opacity: 0; + } + } + } + + &.highlighted { + transition: all 0.2s; + box-shadow: 0px 2px 10px 0 $gray-light; + border: 1px solid $active-blue-border; + /* background-color: red; */ + } + + &.red { + border-color: $red; + } +} + +.firstLine { + display: flex; + justify-content: space-between; + align-items: center; +} + +.main { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: flex; + align-items: flex-start; +} + + +.type { + color: $gray-dark; + font-size: 12px; + text-transform: capitalize; + font-weight: bold; +} + +.data { + margin-left: 5px; + color: $gray-medium; + font-size: 12px; + max-width: 100%; + /* overflow: hidden; */ + /* text-overflow: ellipsis; */ +} + +.badge { + display: inline-block; + padding: 0; + border-radius: 3px; + font-size: 9px; + /* margin-left: 28px; */ + max-width: 170px; + word-wrap: break-word; + line-height: normal; + color: #999; + text-transform: none; +} + +.icon { + margin-right: 10px; + display: inline-flex; + align-items: center; + justify-content: center; + & i { + width: 18px; + height: 18px; + } +} + + +.clickType, .inputType { + /* border: 1px solid $gray-light; */ + background-color: $gray-lightest; + cursor: pointer; +} + +.clickrageType { + background-color: #FFF3F3; + border: 1px solid #CC0000; + box-shadow: + /* The top layer shadow */ + /* 0 1px 1px rgba(0,0,0,0.15), */ + /* The second layer */ + 2px 2px 1px 1px white, + /* The second layer shadow */ + 2px 2px 0px 1px rgba(0,0,0,0.4); + /* Padding for demo purposes */ + /* padding: 12px; */ +} + +.highlight { + border: solid thin red; +} + +.lastInGroup { + background: white; + box-shadow: 0px 1px 1px 0px rgb(0 0 0 / 18%); +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventGroupWrapper.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventGroupWrapper.module.css new file mode 100644 index 000000000..f07fa4f25 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventGroupWrapper.module.css @@ -0,0 +1,36 @@ +.container { + padding: 0px 7px; /*0.35rem 0.5rem */ + background-color: #f6f6f6; +} + +.first { + padding-top: 7px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} + +.last { + padding-bottom: 7px; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; +} + +.dashAfter { + margin-bottom: 0.8rem; +} + +.referrer { + font-size: 14px; + color: $gray-dark; + font-weight: 500 !important; + display: flex; + align-items: center; + & .url { + margin-left: 5px; + font-weight: 300; + color: $gray-medium; + max-width: 70%; + overflow: hidden; + text-overflow: ellipsis; + } +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventsBlock.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventsBlock.module.css new file mode 100644 index 000000000..9efb4be93 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventsBlock.module.css @@ -0,0 +1,66 @@ +.eventsBlock { + width: 270px; + margin-bottom: 5px; +} + +.header { + & .hAndProgress { + display:flex; + justify-content: space-between; + align-items: center; + /* margin-bottom: 5px; */ + /* height: 40px; */ + & .progress { + flex: 1; + margin: 0 0 0 15px; + & :global(.bar) { + background: #ffcc99; + } + & :global(.progress) { + font-size: 9px; + } + } + } + + & h5 { + margin: 0; /* get rid of semantic, please*/ + font-size: 14px; + font-weight: 700; + } +} + +.eventsList { + /* box-shadow: inset 0px 2px 4px rgba(0, 0, 0, 0.1); */ + /* border-top: solid thin $gray-light-shade; */ + &::-webkit-scrollbar { + width: 2px; + background: transparent !important; + background: rgba(0,0,0,0); + } + + &::-webkit-scrollbar-thumb { + background: transparent !important; + } + &::-webkit-scrollbar-track { + background: transparent !important; + } + &:hover { + &::-webkit-scrollbar { + width: 2px; + background: rgba(0,0,0,0.1) + } + &::-webkit-scrollbar-track { + background: rgba(0,0,0,0.1) + } + &::-webkit-scrollbar-thumb { + background: rgba(0,0,0,0.1) + } + } +} + +.sessionDetails { + display: flex; + font-size: 10px; + color: $gray-medium; + justify-content: space-between; +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/index.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/index.js new file mode 100644 index 000000000..47e4d4efb --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/index.js @@ -0,0 +1 @@ +export { default } from './EventsBlock'; \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/loadInfo.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/loadInfo.module.css new file mode 100644 index 000000000..1e2a95927 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/loadInfo.module.css @@ -0,0 +1,102 @@ + + +$green-light: #A0D6AE; +$green-middle: #859D9A; +$green-dark: #3A625E; + +.bar { + display: flex; + overflow: hidden; + cursor: pointer; + + /* margin: 0 -11px; + margin-bottom: -9px; */ + + & div { + height: 5px; + } + & div:nth-child(1) { + background-color: #C5E6E7; + } + & div:nth-child(2) { + background-color: #8BCCCF; + } + & div:nth-child(3) { + background-color :rgba(62, 170, 175, 1); + } +} + +.bottomBlock { + overflow: hidden; +} + +.wrapper { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 5px 12px 34px; + font-size: 13px; + /* font-weight: 500; */ + + & .lines { + border-bottom: 1px solid $gray-light; + border-left: 2px solid; + position: absolute; + height: 100%; + top: -21px; + left: 14px; + width: 15px; + + &:before { + content: ""; + border-radius: 5px; + border: 5px solid; + display: block; + width: 0; + height: 0; + position: absolute; + bottom: -5px; + left: -6px; + z-index: 1; /* in context */ + } + } +} + +.wrapper:nth-child(1) { + /* overflow: hidden; */ + & .lines { + border-left-color: #C5E6E7; + &:before { + border-color: #C5E6E7; + } + } +} + +.wrapper:nth-child(2) { + & .lines { + border-left-color: #8BCCCF; + &:before { + border-color: #8BCCCF; + } + } +} + +.wrapper:nth-child(3) { + & .lines { + border-left-color: rgba(62, 170, 175, 1); + &:before { + border-color: rgba(62, 170, 175, 1); + } + } +} + +.value { + font-weight: 500; + color: $gray-medium; +} + +.download { + display: flex; + justify-content: space-between; +} \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx new file mode 100644 index 000000000..8dd4a41a0 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx @@ -0,0 +1,145 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { withRouter } from 'react-router-dom'; +import { + sessions as sessionsRoute, + liveSession as liveSessionRoute, + withSiteId, +} from 'App/routes'; +import { BackLink, Link } from 'UI'; +import { toggleFavorite, setSessionPath } from 'Duck/sessions'; +import cn from 'classnames'; +import SessionMetaList from 'Shared/SessionItem/SessionMetaList'; +import UserCard from './EventsBlock/UserCard'; +import Tabs from 'Components/Session/Tabs'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import stl from './playerBlockHeader.module.css'; + +const SESSIONS_ROUTE = sessionsRoute(); + +// TODO props +function PlayerBlockHeader(props: any) { + const [hideBack, setHideBack] = React.useState(false); + const { player, store } = React.useContext(PlayerContext); + + const { width, height, showEvents } = store.get(); + + const { + session, + fullscreen, + metaList, + closedLive = false, + siteId, + setActiveTab, + activeTab, + location, + history, + sessionPath, + } = props; + + React.useEffect(() => { + const queryParams = new URLSearchParams(location.search); + setHideBack(queryParams.has('iframe') && queryParams.get('iframe') === 'true'); + }, []); + + const backHandler = () => { + if ( + sessionPath.pathname === history.location.pathname || + sessionPath.pathname.includes('/session/') + ) { + history.push(withSiteId(SESSIONS_ROUTE, siteId)); + } else { + history.push( + sessionPath ? sessionPath.pathname + sessionPath.search : withSiteId(SESSIONS_ROUTE, siteId) + ); + } + }; + + const { sessionId, live, metadata } = session; + let _metaList = Object.keys(metadata) + .filter((i) => metaList.includes(i)) + .map((key) => { + const value = metadata[key]; + return { label: key, value }; + }); + + const TABS = [props.tabs.EVENTS, props.tabs.CLICKMAP].map((tab) => ({ + text: tab, + key: tab, + })); + + return ( + <div className={cn(stl.header, 'flex justify-between', { hidden: fullscreen })}> + <div className="flex w-full items-center"> + {!hideBack && ( + <div + className="flex items-center h-full cursor-pointer group" + onClick={backHandler} + > + {/* @ts-ignore TODO */} + <BackLink label="Back" className="h-full" /> + <div className={stl.divider} /> + </div> + )} + <UserCard className="" width={width} height={height} /> + + <div className={cn('ml-auto flex items-center h-full', { hidden: closedLive })}> + {live && ( + <> + <div className={cn(stl.liveSwitchButton, 'pr-4')}> + <Link to={withSiteId(liveSessionRoute(sessionId), siteId)}> + This Session is Now Continuing Live + </Link> + </div> + {_metaList.length > 0 && <div className={stl.divider} />} + </> + )} + + {_metaList.length > 0 && ( + <div className="border-l h-full flex items-center px-2"> + <SessionMetaList className="" metaList={_metaList} maxLength={2} /> + </div> + )} + </div> + </div> + <div className="relative border-l" style={{ minWidth: '270px' }}> + <Tabs + tabs={TABS} + active={activeTab} + onClick={(tab) => { + if (activeTab === tab) { + setActiveTab(''); + player.toggleEvents(); + } else { + setActiveTab(tab); + !showEvents && player.toggleEvents(); + } + }} + border={false} + /> + </div> + </div> + ); +} + +const PlayerHeaderCont = connect( + (state: any) => { + const session = state.getIn(['sessions', 'current']); + + return { + session, + sessionPath: state.getIn(['sessions', 'sessionPath']), + local: state.getIn(['sessions', 'timezone']), + funnelRef: state.getIn(['funnels', 'navRef']), + siteId: state.getIn(['site', 'siteId']), + metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key), + }; + }, + { + toggleFavorite, + setSessionPath, + } +)(observer(PlayerBlockHeader)); + +export default withRouter(PlayerHeaderCont); diff --git a/frontend/app/components/Session/Player/ReplayPlayer/playerBlockHeader.module.css b/frontend/app/components/Session/Player/ReplayPlayer/playerBlockHeader.module.css new file mode 100644 index 000000000..29c6e1648 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/playerBlockHeader.module.css @@ -0,0 +1,21 @@ +.header { + height: 50px; + border-bottom: solid thin $gray-light; + padding-left: 15px; + padding-right: 0; + background-color: white; +} + +.divider { + width: 1px; + height: 49px; + margin: 0 10px; + background-color: $gray-light; +} + +.liveSwitchButton { + cursor: pointer; + color: $green; + text-decoration: underline; + white-space: nowrap; +} diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index 40b5674dc..8511822f3 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -25,7 +25,6 @@ function WebPlayer(props: any) { session, toggleFullscreen, closeBottomBlock, - live, fullscreen, fetchList, customSession, @@ -37,7 +36,8 @@ function WebPlayer(props: any) { const { notesStore } = useStore(); const [activeTab, setActiveTab] = useState(''); const [showNoteModal, setShowNote] = useState(false); - const [noteItem, setNoteItem] = useState<Note>(); + const [noteItem, setNoteItem] = useState<Note | undefined>(undefined); + // @ts-ignore const [contextValue, setContextValue] = useState<IPlayerContext>(defaultContextValue); useEffect(() => { @@ -55,11 +55,10 @@ function WebPlayer(props: any) { if (!isClickmap) { notesStore.fetchSessionNotes(session.sessionId).then((r) => { - const noteId = props.query.get('note'); - const note = notesStore.getNoteById(parseInt(noteId, 10), r) + const note = props.query.get('note'); if (note) { WebPlayerInst.pause(); - setNoteItem(note); + setNoteItem(notesStore.getNoteById(parseInt(note, 10), r)); setShowNote(true); } }); @@ -124,7 +123,6 @@ function WebPlayer(props: any) { <PlayerContent activeTab={activeTab} fullscreen={fullscreen} - live={live} setActiveTab={setActiveTab} session={session} isClickmap={isClickmap} diff --git a/frontend/app/components/Session/playerContext.ts b/frontend/app/components/Session/playerContext.ts index fed19e0a4..2f6c7c540 100644 --- a/frontend/app/components/Session/playerContext.ts +++ b/frontend/app/components/Session/playerContext.ts @@ -7,9 +7,18 @@ import { } from 'Player' export interface IPlayerContext { - player: IWebPlayer | IWebLivePlayer - store: IWebPlayerStore | IWebLivePlayerStore, + player: IWebPlayer + store: IWebPlayerStore, } + +export interface ILivePlayerContext { + player: IWebLivePlayer + store: IWebLivePlayerStore +} + +type ContextType = + | IPlayerContext + | ILivePlayerContext export const defaultContextValue = { player: undefined, store: undefined} // @ts-ignore -export const PlayerContext = createContext<IPlayerContext>(defaultContextValue); +export const PlayerContext = createContext<ContextType>(defaultContextValue); diff --git a/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx b/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx index 7a9f54bcf..988412b2d 100644 --- a/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx @@ -8,7 +8,7 @@ import { TeamBadge } from 'Shared/SessionListContainer/components/Notes'; interface Props { userEmail: string; - note: Note; + note?: Note; notFound?: boolean; onClose: () => void; } @@ -17,7 +17,7 @@ function ReadNote(props: Props) { const { settingsStore } = useStore(); const { timezone } = settingsStore.sessionSettings; - if (props.notFound) { + if (props.notFound || props.note === undefined) { return ( <div style={{ position: 'absolute', top: '45%', left: 'calc(50% - 200px)' }}> <div diff --git a/frontend/app/components/Session_/session.stories.js b/frontend/app/components/Session_/session.stories.js deleted file mode 100644 index 3118f24f6..000000000 --- a/frontend/app/components/Session_/session.stories.js +++ /dev/null @@ -1,298 +0,0 @@ -import { storiesOf } from '@storybook/react'; -import { List } from 'immutable'; -import EventGroup from './EventsBlock/Event'; - -const groups = [ - { - "page": { - "key": "Location_257", - "time": 2751, - "type": "LOCATION", - "url": "/login", - "pageLoad": false, - "fcpTime": 6787, - "loadTime": 7872, - "domTime": 5821, - "referrer": "Search Engine" - }, - "events": [ - { - "sessionId": 2406625057772570, - "messageId": 76446, - "timestamp": 1586722257371, - "label": "Device Memory: 8.19GB", - "type": "CLICKRAGE", - "count": 3 - }, - { - "key": "Click_256", - "time": 13398, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_262", - "path": "", - "label": null - } - }, - { - "key": "Input_256", - "time": 13438, - "type": "INPUT", - "target": { - "key": "record_263", - "path": "", - "label": null - }, - "value": null - } - ] - }, - { - "page": { - "key": "Location_258", - "time": 15841, - "type": "LOCATION", - "url": "/1/sessions", - "pageLoad": false, - "fcpTime": null, - "loadTime": null, - "domTime": null, - "referrer": "" - }, - "events": [ - { - "key": "Click_257", - "time": 24408, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_264", - "path": "", - "label": null - } - } - ] - }, - { - "page": { - "key": "Location_259", - "time": 25019, - "type": "LOCATION", - "url": "/1/session/2303531983744788", - "pageLoad": false, - "fcpTime": null, - "loadTime": null, - "domTime": null, - "referrer": "" - }, - "events": [ - { - "key": "Click_258", - "time": 31134, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_265", - "path": "", - "label": null - } - }, - { - "key": "Click_259", - "time": 32022, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_266", - "path": "", - "label": null - } - }, - { - "key": "Click_260", - "time": 35951, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_267", - "path": "", - "label": null - } - }, - { - "key": "Click_261", - "time": 164029, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_268", - "path": "", - "label": null - } - }, - { - "key": "Click_262", - "time": 169739, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_269", - "path": "", - "label": null - } - }, - { - "key": "Click_263", - "time": 170524, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_270", - "path": "", - "label": null - } - }, - { - "key": "Click_264", - "time": 172580, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_271", - "path": "", - "label": null - } - }, - { - "key": "Click_265", - "time": 173102, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_272", - "path": "", - "label": null - } - }, - { - "key": "Click_266", - "time": 173698, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_273", - "path": "", - "label": null - } - }, - { - "key": "Click_267", - "time": 173867, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_274", - "path": "", - "label": null - } - }, - { - "key": "Click_268", - "time": 174599, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_275", - "path": "", - "label": null - } - }, - { - "key": "Click_269", - "time": 175148, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_276", - "path": "", - "label": null - } - }, - { - "key": "Click_270", - "time": 175779, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_277", - "path": "", - "label": null - } - }, - { - "key": "Click_271", - "time": 176658, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_278", - "path": "", - "label": null - } - }, - { - "key": "Click_272", - "time": 177267, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_279", - "path": "", - "label": null - } - }, - { - "key": "Click_273", - "time": 187025, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_280", - "path": "", - "label": null - } - }, - { - "key": "Click_274", - "time": 189787, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_281", - "path": "", - "label": null - } - }, - { - "key": "Click_275", - "time": 191326, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_282", - "path": "", - "label": null - } - } - ] - } -] - -// storiesOf('Player', module) -// .add('Event Group', () => ( -// <EventGroup -// group={groups[0]} -// selectedEvents={[]} -// /> -// )) diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index a53c086f1..6c5d3f0eb 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -401,7 +401,7 @@ export default class MessageManager { case MType.Fetch: case MType.NetworkRequest: // @ts-ignore burn immutable - this.lists.lists.fetch.insert(Resource({ + this.lists.lists.fetch.insert(new Resource({ method: msg.method, url: msg.url, request: msg.request, From 5f5efe18e60a92827b8bec16c40b6ade1becb7bb Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Tue, 10 Jan 2023 15:20:19 +0100 Subject: [PATCH 28/65] change(ui): remove live player refs inside replay player, extract some components --- .../app/components/Session/LiveSession.js | 3 - .../Player/ReplayPlayer/PlayerBlock.tsx | 53 +++++ .../ReplayPlayer/PlayerContent.tsx} | 29 ++- .../Player/ReplayPlayer/PlayerInst.tsx | 126 ++++++++++++ frontend/app/components/Session/WebPlayer.tsx | 4 +- .../Session_/Player/Controls/Controls.tsx | 192 ++++++------------ .../Session_/Player/Controls/Time.js | 23 +-- .../Session_/Player/Controls/TimeTracker.js | 2 +- .../Session_/Player/Controls/Timeline.tsx | 71 ++----- .../Controls/components/DraggableCircle.tsx | 2 +- .../Controls/components/PlayerControls.tsx | 47 ++--- .../Controls/components/TooltipContainer.tsx | 4 +- .../components/Session_/Player/Overlay.tsx | 51 +---- .../Player/Overlay/ElementsMarker.tsx | 11 +- .../app/components/Session_/PlayerBlock.js | 36 ---- frontend/app/components/Session_/Subheader.js | 4 - .../ui/EscapeButton/EscapeButton.js | 2 +- 17 files changed, 320 insertions(+), 340 deletions(-) create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/PlayerBlock.tsx rename frontend/app/components/Session/{PlayerContent.js => Player/ReplayPlayer/PlayerContent.tsx} (73%) create mode 100644 frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx delete mode 100644 frontend/app/components/Session_/PlayerBlock.js diff --git a/frontend/app/components/Session/LiveSession.js b/frontend/app/components/Session/LiveSession.js index 8ff90aa75..54df7d5c7 100644 --- a/frontend/app/components/Session/LiveSession.js +++ b/frontend/app/components/Session/LiveSession.js @@ -28,9 +28,6 @@ function LiveSession({ } else { console.error('No sessionID in route.'); } - return () => { - if (!session.exists()) return; - }; }, [sessionId, hasSessionsPath]); return ( diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlock.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlock.tsx new file mode 100644 index 000000000..27756f170 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlock.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import cn from 'classnames'; +import { connect } from 'react-redux'; +import Player from './PlayerInst'; +import SubHeader from 'Components/Session_/Subheader'; + +import styles from 'Components/Session_/playerBlock.module.css'; + +interface IProps { + fullscreen: boolean; + sessionId: string; + disabled: boolean; + activeTab: string; + jiraConfig: Record<string, any> + fullView?: boolean + isClickmap?: boolean +} + +function PlayerBlock(props: IProps) { + const { + fullscreen, + sessionId, + disabled, + activeTab, + jiraConfig, + fullView = false, + isClickmap + } = props; + + const shouldShowSubHeader = !fullscreen && !fullView && !isClickmap + return ( + <div + className={cn(styles.playerBlock, 'flex flex-col', !isClickmap ? 'overflow-x-hidden' : 'overflow-visible')} + style={{ zIndex: isClickmap ? 1 : undefined, minWidth: isClickmap ? '100%' : undefined }} + > + {shouldShowSubHeader ? ( + <SubHeader sessionId={sessionId} disabled={disabled} jiraConfig={jiraConfig} /> + ) : null} + <Player + activeTab={activeTab} + fullView={fullView} + isClickmap={isClickmap} + /> + </div> + ); +} + +export default connect((state: any) => ({ + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + sessionId: state.getIn(['sessions', 'current']).sessionId, + disabled: state.getIn(['components', 'targetDefiner', 'inspectorMode']), + jiraConfig: state.getIn(['issues', 'list'])[0], +}))(PlayerBlock) \ No newline at end of file diff --git a/frontend/app/components/Session/PlayerContent.js b/frontend/app/components/Session/Player/ReplayPlayer/PlayerContent.tsx similarity index 73% rename from frontend/app/components/Session/PlayerContent.js rename to frontend/app/components/Session/Player/ReplayPlayer/PlayerContent.tsx index b3bcfede3..8e46b1163 100644 --- a/frontend/app/components/Session/PlayerContent.js +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerContent.tsx @@ -1,18 +1,27 @@ import React from 'react'; -import PlayerBlock from '../Session_/PlayerBlock'; -import styles from '../Session_/session.module.css'; -import { countDaysFrom } from 'App/date'; -import cn from 'classnames'; -import RightBlock from './RightBlock'; -import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; +import cn from 'classnames'; +import styles from 'Components/Session_/session.module.css'; +import { countDaysFrom } from 'App/date'; +import RightBlock from 'Components/Session/RightBlock'; +import { PlayerContext } from 'Components/Session/playerContext'; +import Session from 'Types/session' +import PlayerBlock from './PlayerBlock'; const TABS = { EVENTS: 'User Steps', HEATMAPS: 'Click Map', }; -function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, isClickmap }) { +interface IProps { + fullscreen: boolean; + activeTab: string; + setActiveTab: (tab: string) => void; + isClickmap: boolean; + session: Session +} + +function PlayerContent({ session, fullscreen, activeTab, setActiveTab, isClickmap }: IProps) { const { store } = React.useContext(PlayerContext) const { @@ -60,7 +69,6 @@ function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, isC setActiveTab={setActiveTab} fullscreen={fullscreen} tabs={TABS} - live={live} /> )} </div> @@ -69,10 +77,9 @@ function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, isC ); } -function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) { +function RightMenu({ tabs, activeTab, setActiveTab, fullscreen }: any) { return ( - !live && - !fullscreen && <RightBlock tabs={tabs} setActiveTab={setActiveTab} activeTab={activeTab} /> + !fullscreen ? <RightBlock tabs={tabs} setActiveTab={setActiveTab} activeTab={activeTab} /> : null ); } diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx new file mode 100644 index 000000000..faa2b3c48 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx @@ -0,0 +1,126 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { findDOMNode } from 'react-dom'; +import cn from 'classnames'; +import { EscapeButton } from 'UI'; +import { + NONE, + CONSOLE, + NETWORK, + STACKEVENTS, + STORAGE, + PROFILER, + PERFORMANCE, + GRAPHQL, + EXCEPTIONS, + INSPECTOR, + OVERVIEW, + fullscreenOff, +} from 'Duck/components/player'; +import NetworkPanel from 'Shared/DevTools/NetworkPanel'; +import Storage from 'Components/Session_/Storage'; +import { ConnectedPerformance } from 'Components/Session_/Performance'; +import GraphQL from 'Components/Session_/GraphQL'; +import Exceptions from 'Components/Session_/Exceptions/Exceptions'; +import Inspector from 'Components/Session_/Inspector'; +import Controls from 'Components/Session_/Player/Controls'; +import Overlay from 'Components/Session_/Player/Overlay'; +import stl from 'Components/Session_/Player/player.module.css'; +import { updateLastPlayedSession } from 'Duck/sessions'; +import OverviewPanel from 'Components/Session_/OverviewPanel'; +import ConsolePanel from 'Shared/DevTools/ConsolePanel'; +import ProfilerPanel from 'Shared/DevTools/ProfilerPanel'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import StackEventPanel from 'Shared/DevTools/StackEventPanel'; + + +interface IProps { + fullView: boolean; + isMultiview?: boolean; + bottomBlock: number; + fullscreen: boolean; + fullscreenOff: () => any; + nextId: string; + sessionId: string; + activeTab: string; + isClickmap?: boolean; + updateLastPlayedSession: (id: string) => void +} + +function Player(props: IProps) { + const { + fullscreen, + fullscreenOff, + nextId, + bottomBlock, + activeTab, + fullView, + isClickmap, + } = props; + const playerContext = React.useContext(PlayerContext); + const screenWrapper = React.useRef<HTMLDivElement>(null); + const bottomBlockIsActive = !fullscreen && bottomBlock !== NONE; + + React.useEffect(() => { + props.updateLastPlayedSession(props.sessionId); + const parentElement = findDOMNode(screenWrapper.current) as HTMLDivElement | null; //TODO: good architecture + if (parentElement) { + playerContext.player.attach(parentElement); + playerContext.player.play(); + } + }, []); + + React.useEffect(() => { + playerContext.player.scale(); + }, [props.bottomBlock, props.fullscreen, playerContext.player]); + + if (!playerContext.player) return null; + + const maxWidth = activeTab ? 'calc(100vw - 270px)' : '100vw'; + return ( + <div + className={cn(stl.playerBody, 'flex-1 flex flex-col relative', fullscreen && 'pb-2')} + data-bottom-block={bottomBlockIsActive} + > + {fullscreen && <EscapeButton onClose={fullscreenOff} />} + <div className={cn("relative flex-1", isClickmap ? 'overflow-visible' : 'overflow-hidden')}> + <Overlay nextId={nextId} isClickmap={isClickmap} /> + <div className={cn(stl.screenWrapper, isClickmap && '!overflow-y-scroll')} ref={screenWrapper} /> + </div> + {!fullscreen && !!bottomBlock && ( + <div style={{ maxWidth, width: '100%' }}> + {bottomBlock === OVERVIEW && <OverviewPanel />} + {bottomBlock === CONSOLE && <ConsolePanel />} + {bottomBlock === NETWORK && <NetworkPanel />} + {bottomBlock === STACKEVENTS && <StackEventPanel />} + {bottomBlock === STORAGE && <Storage />} + {bottomBlock === PROFILER && <ProfilerPanel />} + {bottomBlock === PERFORMANCE && <ConnectedPerformance />} + {bottomBlock === GRAPHQL && <GraphQL />} + {bottomBlock === EXCEPTIONS && <Exceptions />} + {bottomBlock === INSPECTOR && <Inspector />} + </div> + )} + {!fullView && !isClickmap ? ( + <Controls + speedDown={playerContext.player.speedDown} + speedUp={playerContext.player.speedUp} + jump={playerContext.player.jump} + /> + ) : null} + </div> + ); +} + +export default connect( + (state: any) => ({ + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + nextId: state.getIn(['sessions', 'nextId']), + sessionId: state.getIn(['sessions', 'current']).sessionId, + bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), + }), + { + fullscreenOff, + updateLastPlayedSession, + } +)(Player); diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index 8511822f3..210e2bba1 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -7,10 +7,10 @@ import { createWebPlayer } from 'Player'; import { makeAutoObservable } from 'mobx'; import withLocationHandlers from 'HOCs/withLocationHandlers'; import { useStore } from 'App/mstore'; -import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; +import PlayerBlockHeader from './Player/ReplayPlayer/PlayerBlockHeader'; import ReadNote from '../Session_/Player/Controls/components/ReadNote'; import { fetchList as fetchMembers } from 'Duck/member'; -import PlayerContent from './PlayerContent'; +import PlayerContent from './Player/ReplayPlayer/PlayerContent'; import { IPlayerContext, PlayerContext, defaultContextValue } from './playerContext'; import { observer } from 'mobx-react-lite'; import { Note } from "App/services/NotesService"; diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx index 29ede7fd0..f55e54c04 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.tsx +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -2,15 +2,12 @@ import React from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; import { STORAGE_TYPES, selectStorageType } from 'Player'; -import LiveTag from 'Shared/LiveTag'; -import AssistSessionsTabs from './AssistSessionsTabs'; import { Icon, Tooltip } from 'UI'; import { fullscreenOn, fullscreenOff, toggleBottomBlock, - changeSkipInterval, OVERVIEW, CONSOLE, NETWORK, @@ -25,7 +22,6 @@ import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import { fetchSessions } from 'Duck/liveSearch'; -import { AssistDuration } from './Time'; import Timeline from './Timeline'; import ControlButton from './ControlButton'; import PlayerControls from './components/PlayerControls'; @@ -63,47 +59,32 @@ function getStorageName(type: any) { function Controls(props: any) { const { player, store } = React.useContext(PlayerContext); - const { jumpToLive, toggleInspectorMode } = player; const { - live, - livePlay, playing, completed, skip, - // skipToIssue, UPDATE speed, cssLoading, messagesLoading, inspectorMode, markedTargets, - // messagesLoading: fullscreenDisabled, UPDATE - // stackList, exceptionsList, profilesList, graphqlList, - // fetchList, - liveTimeTravel, logMarkedCountNow: logRedCount, resourceMarkedCountNow: resourceRedCount, stackMarkedCountNow: stackRedCount, } = store.get(); - // const storageCount = selectStorageListNow(store.get()).length UPDATE const { bottomBlock, toggleBottomBlock, fullscreen, - closedLive, changeSkipInterval, skipInterval, disabledRedux, showStorageRedux, - session, - // showStackRedux, - fetchSessions: fetchAssistSessions, - totalAssistSessions, } = props; - const isAssist = window.location.pathname.includes('/assist/'); const storageType = selectStorageType(store.get()); const disabled = disabledRedux || cssLoading || messagesLoading || inspectorMode || markedTargets; const profilesCount = profilesList.length; @@ -112,10 +93,6 @@ function Controls(props: any) { const showProfiler = profilesCount > 0; const showExceptions = exceptionsList.length > 0; const showStorage = storageType !== STORAGE_TYPES.NONE || showStorageRedux; - // const fetchCount = fetchList.length; - // const stackCount = stackList.length; - // const showStack = stackCount > 0 || showStackRedux UPDATE - // const showFetch = fetchCount > 0 UPDATE const onKeyDown = (e: any) => { if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { @@ -145,9 +122,6 @@ function Controls(props: any) { React.useEffect(() => { document.addEventListener('keydown', onKeyDown.bind(this)); - if (isAssist && totalAssistSessions === 0) { - fetchAssistSessions(); - } return () => { document.removeEventListener('keydown', onKeyDown.bind(this)); }; @@ -223,57 +197,31 @@ function Controls(props: any) { return ( <div className={styles.controls}> - <Timeline - live={live} - jump={(t: number) => player.jump(t)} - liveTimeTravel={liveTimeTravel} - pause={() => player.pause()} - togglePlay={() => player.togglePlay()} - /> + <Timeline /> {!fullscreen && ( - <div className={cn(styles.buttons, live ? '!px-5 !pt-0' : '!px-2')} data-is-live={live}> + <div className={cn(styles.buttons, '!px-2')}> <div className="flex items-center"> - {!live && ( - <> - <PlayerControls - live={live} - skip={skip} - speed={speed} - disabled={disabled} - backTenSeconds={backTenSeconds} - forthTenSeconds={forthTenSeconds} - toggleSpeed={() => player.toggleSpeed()} - toggleSkip={() => player.toggleSkip()} - playButton={renderPlayBtn()} - controlIcon={controlIcon} - skipIntervals={SKIP_INTERVALS} - setSkipInterval={changeSkipInterval} - currentInterval={skipInterval} - /> - <div className={cn('mx-2')} /> - <XRayButton - isActive={bottomBlock === OVERVIEW && !inspectorMode} - onClick={() => toggleBottomTools(OVERVIEW)} - /> - </> - )} - - {live && !closedLive && ( - <div className={styles.buttonsLeft}> - <LiveTag isLive={livePlay} onClick={() => (livePlay ? null : jumpToLive())} /> - <div className="font-semibold px-2"> - <AssistDuration /> - </div> - </div> - )} + <PlayerControls + skip={skip} + speed={speed} + disabled={disabled} + backTenSeconds={backTenSeconds} + forthTenSeconds={forthTenSeconds} + toggleSpeed={() => player.toggleSpeed()} + toggleSkip={() => player.toggleSkip()} + playButton={renderPlayBtn()} + controlIcon={controlIcon} + skipIntervals={SKIP_INTERVALS} + setSkipInterval={changeSkipInterval} + currentInterval={skipInterval} + /> + <div className={cn('mx-2')} /> + <XRayButton + isActive={bottomBlock === OVERVIEW && !inspectorMode} + onClick={() => toggleBottomTools(OVERVIEW)} + /> </div> - {isAssist && totalAssistSessions > 1 ? ( - <div> - <AssistSessionsTabs session={session} /> - </div> - ) : null} - <div className="flex items-center h-full"> <ControlButton disabled={disabled && !inspectorMode} @@ -285,30 +233,29 @@ function Controls(props: any) { hasErrors={logRedCount > 0 || showExceptions} containerClassName="mx-2" /> - {!live && ( - <ControlButton - disabled={disabled && !inspectorMode} - onClick={() => toggleBottomTools(NETWORK)} - active={bottomBlock === NETWORK && !inspectorMode} - label="NETWORK" - hasErrors={resourceRedCount > 0} - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && ( - <ControlButton - disabled={disabled && !inspectorMode} - onClick={() => toggleBottomTools(PERFORMANCE)} - active={bottomBlock === PERFORMANCE && !inspectorMode} - label="PERFORMANCE" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && showGraphql && ( + + <ControlButton + disabled={disabled && !inspectorMode} + onClick={() => toggleBottomTools(NETWORK)} + active={bottomBlock === NETWORK && !inspectorMode} + label="NETWORK" + hasErrors={resourceRedCount > 0} + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + + <ControlButton + disabled={disabled && !inspectorMode} + onClick={() => toggleBottomTools(PERFORMANCE)} + active={bottomBlock === PERFORMANCE && !inspectorMode} + label="PERFORMANCE" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + + {showGraphql && ( <ControlButton disabled={disabled && !inspectorMode} onClick={() => toggleBottomTools(GRAPHQL)} @@ -319,7 +266,8 @@ function Controls(props: any) { containerClassName="mx-2" /> )} - {!live && showStorage && ( + + {showStorage && ( <ControlButton disabled={disabled && !inspectorMode} onClick={() => toggleBottomTools(STORAGE)} @@ -330,19 +278,17 @@ function Controls(props: any) { containerClassName="mx-2" /> )} - {!live && ( - <ControlButton - disabled={disabled && !inspectorMode} - onClick={() => toggleBottomTools(STACKEVENTS)} - active={bottomBlock === STACKEVENTS && !inspectorMode} - label="EVENTS" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - hasErrors={stackRedCount > 0} - /> - )} - {!live && showProfiler && ( + <ControlButton + disabled={disabled && !inspectorMode} + onClick={() => toggleBottomTools(STACKEVENTS)} + active={bottomBlock === STACKEVENTS && !inspectorMode} + label="EVENTS" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + hasErrors={stackRedCount > 0} + /> + {showProfiler && ( <ControlButton disabled={disabled && !inspectorMode} onClick={() => toggleBottomTools(PROFILER)} @@ -353,17 +299,16 @@ function Controls(props: any) { containerClassName="mx-2" /> )} - {!live && ( - <Tooltip title="Fullscreen" delay={0} placement="top-start" className="mx-4"> - {controlIcon( - 'arrows-angle-extend', - 16, - props.fullscreenOn, - false, - 'rounded hover:bg-gray-light-shade color-gray-medium' - )} - </Tooltip> - )} + + <Tooltip title="Fullscreen" delay={0} placement="top-start" className="mx-4"> + {controlIcon( + 'arrows-angle-extend', + 16, + props.fullscreenOn, + false, + 'rounded hover:bg-gray-light-shade color-gray-medium' + )} + </Tooltip> </div> </div> )} @@ -385,8 +330,6 @@ export default connect( showStackRedux: !state.getIn(['components', 'player', 'hiddenHints', 'stack']), session: state.getIn(['sessions', 'current']), totalAssistSessions: state.getIn(['liveSearch', 'total']), - closedLive: - !!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current']).live, skipInterval: state.getIn(['components', 'player', 'skipInterval']), }; }, @@ -394,7 +337,6 @@ export default connect( fullscreenOn, fullscreenOff, toggleBottomBlock, - changeSkipInterval, fetchSessions, } )(ControlPlayer); diff --git a/frontend/app/components/Session_/Player/Controls/Time.js b/frontend/app/components/Session_/Player/Controls/Time.js index db665fc8e..5c2c0da0c 100644 --- a/frontend/app/components/Session_/Player/Controls/Time.js +++ b/frontend/app/components/Session_/Player/Controls/Time.js @@ -19,28 +19,7 @@ const ReduxTime = observer(({ format, name, isCustom }) => { return <Time format={format} time={time} isCustom={isCustom} /> }) -const AssistDurationCont = () => { - const { store } = React.useContext(PlayerContext) - const { assistStart } = store.get() - - const [assistDuration, setAssistDuration] = React.useState('00:00'); - React.useEffect(() => { - const interval = setInterval(() => { - setAssistDuration(Duration.fromMillis(+new Date() - assistStart).toFormat('mm:ss')); - } - , 1000); - return () => clearInterval(interval); - }, []) - return ( - <> - Elapsed {assistDuration} - </> - ) -} - -const AssistDuration = observer(AssistDurationCont) - ReduxTime.displayName = "ReduxTime"; export default React.memo(Time); -export { ReduxTime, AssistDuration }; +export { ReduxTime }; diff --git a/frontend/app/components/Session_/Player/Controls/TimeTracker.js b/frontend/app/components/Session_/Player/Controls/TimeTracker.js index 85051c6a5..00d22d1e3 100644 --- a/frontend/app/components/Session_/Player/Controls/TimeTracker.js +++ b/frontend/app/components/Session_/Player/Controls/TimeTracker.js @@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite'; import styles from './timeTracker.module.css'; import cn from 'classnames' -const TimeTracker = ({ scale, live, left }) => { +const TimeTracker = ({ scale, live = false, left }) => { const { store } = React.useContext(PlayerContext) const time = store.get().time diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.tsx b/frontend/app/components/Session_/Player/Controls/Timeline.tsx index 4a32d8efd..fea596066 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.tsx +++ b/frontend/app/components/Session_/Player/Controls/Timeline.tsx @@ -40,8 +40,6 @@ function Timeline(props: IProps) { skipToIssue, ready, endTime, - live, - liveTimeTravel, } = store.get() const { issues } = props; const notes = notesStore.sessionNotes @@ -64,16 +62,13 @@ function Timeline(props: IProps) { const debouncedTooltipChange = useMemo(() => debounce(props.setTimelineHoverTime, 50), []) const onDragEnd = () => { - if (live && !liveTimeTravel) return; - if (wasPlaying) { player.togglePlay(); } }; const onDrag: OnDragCallback = (offset) => { - if ((live && !liveTimeTravel) || !progressRef.current) return; - + // @ts-ignore react mismatch const p = (offset.x) / progressRef.current.offsetWidth; const time = Math.max(Math.round(p * endTime), 0); debouncedJump(time); @@ -84,39 +79,20 @@ function Timeline(props: IProps) { } }; - const getLiveTime = (e: React.MouseEvent) => { - const duration = new Date().getTime() - props.startedAt; - const p = e.nativeEvent.offsetX / e.target.offsetWidth; - const time = Math.max(Math.round(p * duration), 0); - - return [time, duration]; - }; - const showTimeTooltip = (e: React.MouseEvent<HTMLDivElement>) => { if (e.target !== progressRef.current && e.target !== timelineRef.current) { return props.tooltipVisible && hideTimeTooltip(); } - let timeLineTooltip; - - if (live) { - const [time, duration] = getLiveTime(e); - timeLineTooltip = { - time: Duration.fromMillis(duration - time).toFormat(`-mm:ss`), - offset: e.nativeEvent.offsetX, - isVisible: true, - }; - } else { - const time = getTime(e); - const tz = settingsStore.sessionSettings.timezone.value - const timeStr = DateTime.fromMillis(props.startedAt + time).setZone(tz).toFormat(`hh:mm:ss a`) - timeLineTooltip = { - time: Duration.fromMillis(time).toFormat(`mm:ss`), - timeStr, - offset: e.nativeEvent.offsetX, - isVisible: true, - }; - } + const time = getTime(e); + const tz = settingsStore.sessionSettings.timezone.value + const timeStr = DateTime.fromMillis(props.startedAt + time).setZone(tz).toFormat(`hh:mm:ss a`) + const timeLineTooltip = { + time: Duration.fromMillis(time).toFormat(`mm:ss`), + timeStr, + offset: e.nativeEvent.offsetX, + isVisible: true, + }; debouncedTooltipChange(timeLineTooltip); } @@ -132,29 +108,16 @@ function Timeline(props: IProps) { hideTimeTooltip(); }; - const loadAndSeek = async (e: React.MouseEvent<HTMLDivElement>) => { - e.persist(); - await player.toggleTimetravel(); - - setTimeout(() => { - seekProgress(e); - }); - }; - const jumpToTime = (e: React.MouseEvent<HTMLDivElement>) => { - if (live && !liveTimeTravel) { - loadAndSeek(e); - } else { - seekProgress(e); - } + seekProgress(e); }; const getTime = (e: React.MouseEvent<HTMLDivElement>, customEndTime?: number) => { + // @ts-ignore react mismatch const p = e.nativeEvent.offsetX / e.target.offsetWidth; const targetTime = customEndTime || endTime; - const time = Math.max(Math.round(p * targetTime), 0); - return time; + return Math.max(Math.round(p * targetTime), 0); }; return ( @@ -176,21 +139,19 @@ function Timeline(props: IProps) { onMouseEnter={showTimeTooltip} onMouseLeave={hideTimeTooltip} > - <TooltipContainer live={live} /> - {/* custo color is live */} + <TooltipContainer /> <DraggableCircle left={time * scale} onDrop={onDragEnd} - live={live} /> <CustomDragLayer onDrag={onDrag} minX={0} maxX={progressRef.current ? progressRef.current.offsetWidth : 0} /> - <TimeTracker scale={scale} live={live} left={time * scale} /> + <TimeTracker scale={scale} left={time * scale} /> - {!live && skip ? + {skip ? skipIntervals.map((interval) => ( <div key={interval.start} diff --git a/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx b/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx index cb1e0f9d4..12d8e618c 100644 --- a/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx @@ -32,7 +32,7 @@ const ItemTypes = { interface Props { left: number - live: boolean + live?: boolean onDrop?: () => void } diff --git a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx index 918fdb8b6..65e38bf39 100644 --- a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx @@ -1,13 +1,11 @@ import React from 'react'; import { Icon, Tooltip, Popover } from 'UI'; import cn from 'classnames'; -import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; import { ReduxTime } from '../Time'; // @ts-ignore import styles from '../controls.module.css'; interface Props { - live: boolean; skip: boolean; speed: number; disabled: boolean; @@ -30,7 +28,6 @@ interface Props { function PlayerControls(props: Props) { const { - live, skip, speed, disabled, @@ -45,10 +42,10 @@ function PlayerControls(props: Props) { controlIcon, } = props; const [showTooltip, setShowTooltip] = React.useState(false); - const speedRef = React.useRef(null); - const arrowBackRef = React.useRef(null); - const arrowForwardRef = React.useRef(null); - const skipRef = React.useRef<HTMLDivElement>(); + const speedRef = React.useRef<HTMLButtonElement>(null); + const arrowBackRef = React.useRef<HTMLButtonElement>(null); + const arrowForwardRef = React.useRef<HTMLButtonElement>(null); + const skipRef = React.useRef<HTMLDivElement>(null); React.useEffect(() => { const handleKeyboard = (e: KeyboardEvent) => { @@ -56,16 +53,16 @@ function PlayerControls(props: Props) { return; } if (e.key === 'ArrowRight') { - arrowForwardRef.current.focus(); + arrowForwardRef.current?.focus(); } if (e.key === 'ArrowLeft') { - arrowBackRef.current.focus(); + arrowBackRef.current?.focus(); } if (e.key === 'ArrowDown') { - speedRef.current.focus(); + speedRef.current?.focus(); } if (e.key === 'ArrowUp') { - speedRef.current.focus(); + speedRef.current?.focus(); } }; document.addEventListener('keydown', handleKeyboard); @@ -75,22 +72,20 @@ function PlayerControls(props: Props) { const toggleTooltip = () => { setShowTooltip(!showTooltip); }; - const handleClickOutside = () => { - setShowTooltip(false); - }; + return ( <div className="flex items-center"> {playButton} <div className="mx-1" /> - {!live && ( - <div className="flex items-center font-semibold text-center" style={{ minWidth: 85 }}> - {/* @ts-ignore */} - <ReduxTime isCustom name="time" format="mm:ss" /> - <span className="px-1">/</span> - {/* @ts-ignore */} - <ReduxTime isCustom name="endTime" format="mm:ss" /> - </div> - )} + + <div className="flex items-center font-semibold text-center" style={{ minWidth: 85 }}> + {/* @ts-ignore */} + <ReduxTime isCustom name="time" format="mm:ss" /> + <span className="px-1">/</span> + {/* @ts-ignore */} + <ReduxTime isCustom name="endTime" format="mm:ss" /> + </div> + <div className="rounded ml-4 bg-active-blue border border-active-blue-border flex items-stretch"> {/* @ts-ignore */} @@ -115,8 +110,6 @@ function PlayerControls(props: Props) { <div className="p-1 border-l border-r bg-active-blue-border border-active-blue-border flex items-center"> <Popover - // open={showTooltip} - // interactive // @ts-ignore theme="nopadding" animation="none" @@ -176,7 +169,7 @@ function PlayerControls(props: Props) { </Tooltip> </div> - {!live && ( + <div className="flex items-center"> <div className="mx-2" /> {/* @ts-ignore */} @@ -203,7 +196,7 @@ function PlayerControls(props: Props) { {'Skip Inactivity'} </button> </div> - )} + </div> ); } diff --git a/frontend/app/components/Session_/Player/Controls/components/TooltipContainer.tsx b/frontend/app/components/Session_/Player/Controls/components/TooltipContainer.tsx index d1b98eca6..5b14b807a 100644 --- a/frontend/app/components/Session_/Player/Controls/components/TooltipContainer.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/TooltipContainer.tsx @@ -4,12 +4,12 @@ import CreateNote from './CreateNote'; import store from 'App/store'; import { Provider } from 'react-redux'; -function TooltipContainer({ live }: { live: boolean }) { +function TooltipContainer() { return ( <Provider store={store}> <> - <TimeTooltip liveTimeTravel={live} /> + <TimeTooltip /> <CreateNote /> </> </Provider> diff --git a/frontend/app/components/Session_/Player/Overlay.tsx b/frontend/app/components/Session_/Player/Overlay.tsx index e7645b34e..8a94ebceb 100644 --- a/frontend/app/components/Session_/Player/Overlay.tsx +++ b/frontend/app/components/Session_/Player/Overlay.tsx @@ -1,18 +1,8 @@ import React from 'react'; -import { - SessionRecordingStatus, - getStatusText, - CallingState, - ConnectionStatus, - RemoteControlStatus, -} from 'Player'; - import AutoplayTimer from './Overlay/AutoplayTimer'; import PlayIconLayer from './Overlay/PlayIconLayer'; -import LiveStatusText from './Overlay/LiveStatusText'; import Loader from './Overlay/Loader'; import ElementsMarker from './Overlay/ElementsMarker'; -import RequestingWindow, { WindowType } from 'App/components/Assist/RequestingWindow'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; @@ -24,7 +14,6 @@ interface Props { function Overlay({ nextId, - closedLive, isClickmap, }: Props) { const { player, store } = React.useContext(PlayerContext) @@ -37,53 +26,17 @@ function Overlay({ completed, autoplay, inspectorMode, - live, - peerConnectionStatus, markedTargets, activeTargetIndex, - livePlay, - calling, - remoteControl, - recordingState, } = store.get() const loading = messagesLoading || cssLoading - const liveStatusText = getStatusText(peerConnectionStatus) - const concetionStatus = peerConnectionStatus - const showAutoplayTimer = !live && completed && autoplay && nextId - const showPlayIconLayer = !isClickmap && !live && !markedTargets && !inspectorMode && !loading && !showAutoplayTimer; - const showLiveStatusText = live && livePlay && liveStatusText && !loading; - - const showRequestWindow = - live && - (calling === CallingState.Connecting || - remoteControl === RemoteControlStatus.Requesting || - recordingState === SessionRecordingStatus.Requesting); - - const getRequestWindowType = () => { - if (calling === CallingState.Connecting) { - return WindowType.Call - } - if (remoteControl === RemoteControlStatus.Requesting) { - return WindowType.Control - } - if (recordingState === SessionRecordingStatus.Requesting) { - return WindowType.Record - } - - return null; - } + const showAutoplayTimer = completed && autoplay && nextId + const showPlayIconLayer = !isClickmap && !markedTargets && !inspectorMode && !loading && !showAutoplayTimer; return ( <> - {showRequestWindow ? <RequestingWindow getWindowType={getRequestWindowType} /> : null} {showAutoplayTimer && <AutoplayTimer />} - {showLiveStatusText && ( - <LiveStatusText - text={liveStatusText} - concetionStatus={closedLive ? ConnectionStatus.Closed : concetionStatus} - /> - )} {loading ? <Loader /> : null} {showPlayIconLayer && <PlayIconLayer playing={playing} togglePlay={togglePlay} />} {markedTargets && <ElementsMarker targets={markedTargets} activeIndex={activeTargetIndex} />} diff --git a/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx b/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx index 7be45a432..ce52b62e7 100644 --- a/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx +++ b/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx @@ -3,5 +3,14 @@ import Marker from './ElementsMarker/Marker'; import type { MarkedTarget } from 'Player'; export default function ElementsMarker({ targets, activeIndex }: { targets: MarkedTarget[], activeIndex: number }) { - return targets && targets.map(t => <React.Fragment key={t.index}><Marker target={t} active={activeIndex === t.index}/></React.Fragment>) + return targets ? <> + {targets.map( + t => <React.Fragment key={t.index}> + <Marker + target={t} + active={activeIndex === t.index} + /> + </React.Fragment> + )} + </> : null } diff --git a/frontend/app/components/Session_/PlayerBlock.js b/frontend/app/components/Session_/PlayerBlock.js deleted file mode 100644 index 314e3383c..000000000 --- a/frontend/app/components/Session_/PlayerBlock.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { connect } from 'react-redux'; -import Player from './Player'; -import SubHeader from './Subheader'; - -import styles from './playerBlock.module.css'; - -@connect((state) => ({ - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - sessionId: state.getIn(['sessions', 'current']).sessionId, - disabled: state.getIn(['components', 'targetDefiner', 'inspectorMode']), - jiraConfig: state.getIn(['issues', 'list'])[0], -})) -export default class PlayerBlock extends React.PureComponent { - render() { - const { fullscreen, sessionId, disabled, activeTab, jiraConfig, fullView = false, isMultiview, isClickmap } = this.props; - - const shouldShowSubHeader = !fullscreen && !fullView && !isMultiview && !isClickmap - return ( - <div className={cn(styles.playerBlock, 'flex flex-col', !isClickmap ? 'overflow-x-hidden' : 'overflow-visible')} style={{ zIndex: isClickmap ? 1 : undefined, minWidth: isMultiview || isClickmap ? '100%' : undefined }}> - {shouldShowSubHeader ? ( - <SubHeader sessionId={sessionId} disabled={disabled} jiraConfig={jiraConfig} /> - ) : null} - <Player - className="flex-1" - fullscreen={fullscreen} - activeTab={activeTab} - fullView={fullView} - isMultiview={isMultiview} - isClickmap={isClickmap} - /> - </div> - ); - } -} diff --git a/frontend/app/components/Session_/Subheader.js b/frontend/app/components/Session_/Subheader.js index 72ae28e3d..55d7cfc31 100644 --- a/frontend/app/components/Session_/Subheader.js +++ b/frontend/app/components/Session_/Subheader.js @@ -11,7 +11,6 @@ import { useModal } from 'App/components/Modal'; import BugReportModal from './BugReport/BugReportModal'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; -import { useStore } from 'App/mstore'; import AutoplayToggle from 'Shared/AutoplayToggle'; function SubHeader(props) { @@ -35,7 +34,6 @@ function SubHeader(props) { const [isCopied, setCopied] = React.useState(false); const { showModal, hideModal } = useModal(); - const isAssist = window.location.pathname.includes('/assist/'); const location = currentLocation && currentLocation.length > 60 @@ -72,7 +70,6 @@ function SubHeader(props) { </Tooltip> </div> )} - {!isAssist ? ( <div className="ml-auto text-sm flex items-center color-gray-medium gap-2" style={{ width: 'max-content' }} @@ -111,7 +108,6 @@ function SubHeader(props) { <QueueControls /> </div> </div> - ) : null} </div> ); } diff --git a/frontend/app/components/ui/EscapeButton/EscapeButton.js b/frontend/app/components/ui/EscapeButton/EscapeButton.js index 1f9b372a0..b913c9c55 100644 --- a/frontend/app/components/ui/EscapeButton/EscapeButton.js +++ b/frontend/app/components/ui/EscapeButton/EscapeButton.js @@ -2,7 +2,7 @@ import React from 'react' import { Icon } from 'UI'; import stl from './escapeButton.module.css' -function EscapeButton({ onClose = null}) { +function EscapeButton({ onClose = () => null }) { return ( <div className={ stl.closeWrapper } onClick={ onClose }> <Icon name="close" size="16" /> From cfa22a5004ad8af0692a91d3444599b66787bb34 Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Tue, 10 Jan 2023 15:31:08 +0100 Subject: [PATCH 29/65] change(ui): small cleanup --- frontend/app/components/Alerts/Alerts.js | 0 .../Assist/RecordingsList/EditRecordingModal.tsx | 2 +- .../components/Assist/RecordingsList/RecordsListItem.tsx | 2 +- .../Assist/components/AssistTabs/AssistTabs.tsx | 3 +-- .../components/Client/Integrations/IntegrationForm.js | 1 - .../components/Client/Integrations/IntegrationItem.tsx | 1 - .../app/components/Client/Integrations/SlackForm.tsx | 1 - .../app/components/Client/ProfileSettings/Settings.js | 1 - .../Client/Sites/AddProjectButton/AddProjectButton.tsx | 2 +- frontend/app/components/Client/TabItem.js | 1 - .../Widgets/BusiestTimeOfTheDay/BusiestTimeOfTheDay.js | 0 .../CustomMetriLineChart/CustomMetriLineChart.tsx | 2 +- .../Dashboard/Widgets/ErrorsByType/ErrorsByType.js | 0 .../Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js | 0 .../BreakdownOfLoadedResources.tsx | 3 +-- .../Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx | 4 ++-- .../PredefinedWidgets/CallWithErrors/CallWithErrors.tsx | 4 ++-- .../PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx | 2 +- .../Widgets/PredefinedWidgets/Crashes/Crashes.tsx | 4 ++-- .../PredefinedWidgets/ErrorsByType/ErrorsByType.tsx | 2 +- .../Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx | 4 ++-- .../MemoryConsumption/MemoryConsumption.tsx | 4 ++-- .../MissingResources/MissingResources.tsx | 2 +- .../ResponseTimeDistribution.tsx | 4 ++-- .../SessionsAffectedByJSErrors.tsx | 2 +- .../SessionsImpactedBySlowRequests.tsx | 4 ++-- .../SlowestResources/SlowestResources.tsx | 2 +- .../Dashboard/Widgets/SessionsPerBrowser/Bar.js | 0 .../components/DashbaordListModal/DashbaordListModal.tsx | 4 ++-- .../Dashboard/components/DashboardList/DashboardList.tsx | 2 +- .../components/DashboardSideMenu/DashboardSideMenu.tsx | 2 +- .../components/Errors/ErrorDetails/ErrorDetails.tsx | 2 -- .../components/Errors/ErrorListItem/ErrorListItem.tsx | 4 ---- .../FunnelIssuesListItem/FunnelIssuesListItem.tsx | 2 -- .../components/MetricTypeList/MetricTypeList.tsx | 2 +- .../Dashboard/components/MetricsList/GridView.tsx | 1 - .../Dashboard/components/MetricsList/MetricsList.tsx | 2 +- .../Dashboard/components/WidgetChart/WidgetChart.tsx | 1 - .../components/WidgetSessions/WidgetSessions.tsx | 2 +- frontend/app/components/Errors/Error/MainSection.js | 6 ++---- frontend/app/components/Errors/List/List.js | 4 ++-- frontend/app/components/Errors/List/ListItem/ListItem.js | 3 +-- .../app/components/Funnels/FunnelGraph/FunnelGraph.js | 3 +-- .../app/components/Funnels/FunnelIssues/FunnelIssues.js | 2 +- .../Funnels/FunnelSessionList/FunnelSessionList.js | 2 +- .../app/components/Funnels/FunnelWidget/FunnelBar.tsx | 2 +- frontend/app/components/Funnels/funnels.stories.js | 1 - .../Header/DefaultMenuView/DefaultMenuView.tsx | 5 ++--- .../Header/NewProjectButton/NewProjectButton.tsx | 1 - .../components/Header/OnboardingExplore/FeatureItem.js | 2 +- .../Header/PreferencesView/PreferencesView.tsx | 1 - .../Onboarding/components/MetadataList/MetadataList.js | 2 +- .../ProjectCodeSnippet/ProjectCodeSnippet.js | 1 - .../app/components/Onboarding/components/SideMenu.js | 2 +- frontend/app/components/Session/Layout/Header/Section.js | 1 - .../app/components/Session/Layout/Player/Controls.js | 5 +---- frontend/app/components/Session/Layout/ToolPanel.js | 1 - .../components/Session/Layout/ToolPanel/Performance.tsx | 2 -- frontend/app/components/Session_/Autoscroll.tsx | 1 - .../Session_/EventsBlock/EventSearch/EventSearch.js | 2 +- .../components/Session_/EventsBlock/UserCard/UserCard.js | 2 +- .../Session_/Fetch/components/Headers/Headers.tsx | 2 +- frontend/app/components/Session_/GraphQL/GQLDetails.js | 2 +- frontend/app/components/Session_/HeaderInfo.js | 1 - frontend/app/components/Session_/Issues/IssueHeader.js | 4 +--- .../app/components/Session_/Network/NetworkContent.js | 2 +- .../components/StackEventModal/StackEventModal.tsx | 2 +- .../Player/Controls/components/DraggableCircle.tsx | 2 +- .../Session_/StackEvents/StackEvents.DEPRECATED.js | 1 - .../Session_/StackEvents/UserEvent/UserEvent.js | 6 +----- .../app/components/Session_/Storage/Storge.DEPRECATED.js | 2 -- frontend/app/components/Session_/TimeTable/TimeTable.tsx | 2 +- .../app/components/Session_/components/HeaderMenu.tsx | 2 +- frontend/app/components/Signup/SignupForm/SignupForm.js | 2 +- frontend/app/components/hocs/withReport.tsx | 2 +- .../components/shared/CustomMetrics/CustomMetrics.tsx | 2 +- .../shared/DevTools/ProfilerPanel/ProfilerPanel.tsx | 2 +- .../shared/DevTools/StackEventModal/StackEventModal.tsx | 2 +- .../shared/DevTools/StackEventPanel/StackEventPanel.tsx | 4 ++-- .../shared/DevTools/StackEventRow/StackEventRow.tsx | 2 +- .../app/components/shared/DevTools/TimeTable/BarRow.tsx | 1 - frontend/app/components/shared/DevTools/useAutoscroll.ts | 2 +- .../FetchDetailsModal/components/Headers/Headers.tsx | 2 +- .../components/shared/Filters/FilterItem/FilterItem.tsx | 2 +- .../shared/Filters/FilterModal/FilterModal.tsx | 2 +- .../shared/Filters/LiveFilterModal/LiveFilterModal.tsx | 1 - .../shared/GraphQLDetailsModal/GraphQLDetailsModal.tsx | 2 +- .../shared/LiveSessionList/LiveSessionList.tsx | 2 +- .../shared/SelectDateRange/SelectDateRange.tsx | 2 +- .../SessionItem/SessionMetaList/SessionMetaList.tsx | 1 - .../SessionListContainer/components/Notes/NoteItem.tsx | 2 +- .../SessionListContainer/components/Notes/NoteList.tsx | 2 +- .../components/SessionList/SessionList.tsx | 2 +- .../ProjectCodeSnippet/ProjectCodeSnippet.js | 1 - .../shared/TrackingCodeModal/TrackingCodeModal.js | 4 +--- .../shared/UpdateFunnelButton/UpdateFunnelButton.tsx | 3 +-- .../shared/UserSessionsModal/UserSessionsModal.tsx | 3 --- .../shared/WidgetAutoComplete/WidgetAutoComplete.js | 2 +- frontend/app/components/shared/XRayButton/XRayButton.tsx | 3 +-- frontend/app/components/ui/ErrorItem/ErrorItem.tsx | 2 -- frontend/app/components/ui/Label/Label.js | 1 - .../ui/NoSessionPermission/NoSessionPermission.tsx | 2 +- frontend/app/components/ui/Tooltip/FloatingTooltip.tsx | 2 +- frontend/app/components/ui/Tooltip/Tooltip.tsx | 2 +- frontend/app/components/ui/ui.stories.js | 1 - frontend/app/constants/card.ts | 2 +- frontend/app/constants/countryShortName.js | 2 -- frontend/app/duck/alerts.js | 0 frontend/app/duck/components/targetDefiner.js | 1 - frontend/app/duck/errors.js | 4 ++-- frontend/app/duck/filters.js | 4 ++-- frontend/app/duck/funnelFilters.js | 9 +++------ frontend/app/duck/funnels.js | 4 ---- frontend/app/duck/integrations/actions.js | 1 - frontend/app/duck/integrations/integrations.js | 1 - frontend/app/duck/issues.js | 2 +- frontend/app/duck/liveSearch.js | 2 +- frontend/app/duck/rehydrate.js | 2 +- frontend/app/duck/search.js | 2 +- frontend/app/duck/sessions.ts | 2 +- frontend/app/duck/site.js | 1 - frontend/app/mstore/assistMultiviewStore.ts | 2 +- frontend/app/mstore/auditStore.ts | 5 ++--- frontend/app/mstore/errorStore.ts | 2 +- frontend/app/mstore/funnelStore.ts | 2 +- frontend/app/mstore/metricStore.ts | 2 +- frontend/app/mstore/roleStore.ts | 2 +- frontend/app/mstore/settingsStore.ts | 2 +- frontend/app/mstore/types/filterItem.ts | 2 +- frontend/app/mstore/types/filterSeries.ts | 2 +- frontend/app/mstore/types/sessionSettings.ts | 2 +- frontend/app/player/player/Animator.ts | 1 - frontend/app/player/player/Player.ts | 2 +- frontend/app/player/web/WebPlayer.ts | 2 +- frontend/app/player/web/assist/AssistManager.ts | 1 - frontend/app/player/web/managers/DOM/DOMManager.ts | 3 +-- frontend/app/player/web/managers/DOM/StylesManager.ts | 1 - frontend/app/player/web/managers/MouseMoveManager.ts | 1 - frontend/app/player/web/managers/PagesManager.ts | 1 - frontend/app/player/web/messages/JSONRawMessageReader.ts | 1 - frontend/app/player/web/messages/MStreamReader.ts | 1 - frontend/app/types/alert.js | 0 frontend/app/types/errorInfo.js | 1 - frontend/app/types/filter/customFilter.js | 1 - frontend/app/types/filter/filter.js | 1 - frontend/app/types/filter/index.js | 1 - frontend/app/types/filter/savedFilter.js | 3 +-- frontend/app/types/funnel.js | 1 - frontend/app/types/session/error.ts | 2 -- frontend/app/types/session/event.ts | 3 --- frontend/app/types/synthetics/index.js | 7 +------ frontend/app/types/ts/search.ts | 2 -- frontend/app/utils/index.ts | 1 - frontend/scripts/constants.js | 2 +- frontend/webpack.config.ts | 2 +- 155 files changed, 114 insertions(+), 214 deletions(-) create mode 100644 frontend/app/components/Alerts/Alerts.js create mode 100644 frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/BusiestTimeOfTheDay.js create mode 100644 frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js create mode 100644 frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js create mode 100644 frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.js create mode 100644 frontend/app/duck/alerts.js create mode 100644 frontend/app/types/alert.js diff --git a/frontend/app/components/Alerts/Alerts.js b/frontend/app/components/Alerts/Alerts.js new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/components/Assist/RecordingsList/EditRecordingModal.tsx b/frontend/app/components/Assist/RecordingsList/EditRecordingModal.tsx index 902fff4f9..a45de1fa0 100644 --- a/frontend/app/components/Assist/RecordingsList/EditRecordingModal.tsx +++ b/frontend/app/components/Assist/RecordingsList/EditRecordingModal.tsx @@ -1,6 +1,6 @@ import { useObserver } from 'mobx-react-lite'; import React from 'react'; -import { Button, Modal, Form, Icon, Checkbox, Input } from 'UI'; +import { Button, Modal, Form, Icon, Input } from 'UI'; interface Props { show: boolean; diff --git a/frontend/app/components/Assist/RecordingsList/RecordsListItem.tsx b/frontend/app/components/Assist/RecordingsList/RecordsListItem.tsx index 987ab0fa2..1a9952a84 100644 --- a/frontend/app/components/Assist/RecordingsList/RecordsListItem.tsx +++ b/frontend/app/components/Assist/RecordingsList/RecordsListItem.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon, ItemMenu, Tooltip } from 'UI'; +import { Icon, ItemMenu } from 'UI'; import { durationFromMs, formatTimeOrDate } from 'App/date'; import { IRecord } from 'App/services/RecordingsService'; import { useStore } from 'App/mstore'; diff --git a/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx b/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx index 2fd09105b..759bc9bab 100644 --- a/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx +++ b/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx @@ -1,6 +1,5 @@ import { useModal } from 'App/components/Modal'; -import React, { useEffect, useState } from 'react'; -import { SlideModal, Avatar, TextEllipsis, Icon } from 'UI'; +import React, { useState } from 'react'; import SessionList from '../SessionList'; import stl from './assistTabs.module.css' diff --git a/frontend/app/components/Client/Integrations/IntegrationForm.js b/frontend/app/components/Client/Integrations/IntegrationForm.js index c1115f405..cdde52bd9 100644 --- a/frontend/app/components/Client/Integrations/IntegrationForm.js +++ b/frontend/app/components/Client/Integrations/IntegrationForm.js @@ -1,7 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; import { Input, Form, Button, Checkbox, Loader } from 'UI'; -import SiteDropdown from 'Shared/SiteDropdown'; import { save, init, edit, remove } from 'Duck/integrations/actions'; import { fetchIntegrationList } from 'Duck/integrations/integrations'; diff --git a/frontend/app/components/Client/Integrations/IntegrationItem.tsx b/frontend/app/components/Client/Integrations/IntegrationItem.tsx index d04ac3b7e..06e950ae6 100644 --- a/frontend/app/components/Client/Integrations/IntegrationItem.tsx +++ b/frontend/app/components/Client/Integrations/IntegrationItem.tsx @@ -2,7 +2,6 @@ import React from 'react'; import cn from 'classnames'; import { Icon, Tooltip } from 'UI'; import stl from './integrationItem.module.css'; -import { connect } from 'react-redux'; interface Props { integration: any; diff --git a/frontend/app/components/Client/Integrations/SlackForm.tsx b/frontend/app/components/Client/Integrations/SlackForm.tsx index 79c6b2a00..819284a77 100644 --- a/frontend/app/components/Client/Integrations/SlackForm.tsx +++ b/frontend/app/components/Client/Integrations/SlackForm.tsx @@ -3,7 +3,6 @@ import SlackChannelList from './SlackChannelList/SlackChannelList'; import { fetchList, init } from 'Duck/integrations/slack'; import { connect } from 'react-redux'; import SlackAddForm from './SlackAddForm'; -import { useModal } from 'App/components/Modal'; import { Button } from 'UI'; interface Props { diff --git a/frontend/app/components/Client/ProfileSettings/Settings.js b/frontend/app/components/Client/ProfileSettings/Settings.js index f0de1358c..5329d98dc 100644 --- a/frontend/app/components/Client/ProfileSettings/Settings.js +++ b/frontend/app/components/Client/ProfileSettings/Settings.js @@ -1,5 +1,4 @@ import React from 'react'; -import copy from 'copy-to-clipboard'; import { connect } from 'react-redux'; import { Button, Input, Form } from 'UI'; import { updateAccount, updateClient } from 'Duck/user'; diff --git a/frontend/app/components/Client/Sites/AddProjectButton/AddProjectButton.tsx b/frontend/app/components/Client/Sites/AddProjectButton/AddProjectButton.tsx index 36bf497e2..0a2712462 100644 --- a/frontend/app/components/Client/Sites/AddProjectButton/AddProjectButton.tsx +++ b/frontend/app/components/Client/Sites/AddProjectButton/AddProjectButton.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Tooltip, Button, IconButton } from 'UI'; +import { Tooltip, Button } from 'UI'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; import { init, remove, fetchGDPR } from 'Duck/site'; diff --git a/frontend/app/components/Client/TabItem.js b/frontend/app/components/Client/TabItem.js index dae16e7df..8d97eb060 100644 --- a/frontend/app/components/Client/TabItem.js +++ b/frontend/app/components/Client/TabItem.js @@ -1,6 +1,5 @@ import React from 'react'; import { Icon } from 'UI'; -import styles from './client.module.css'; const TabItem = ({ active = false, onClick, icon, label }) => { return ( diff --git a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/BusiestTimeOfTheDay.js b/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/BusiestTimeOfTheDay.js new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx index 4da7631fa..0118617ba 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Styles } from '../../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; +import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts'; import { LineChart, Line, Legend } from 'recharts'; interface Props { diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js b/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.tsx index f29ef22ac..35cd05063 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.tsx @@ -2,9 +2,8 @@ import React from 'react'; import { NoContent } from 'UI'; import { Styles } from '../../common'; import { - AreaChart, Area, BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + Legend, ResponsiveContainer, XAxis, YAxis } from 'recharts'; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx index 0ddfd0d1d..2344de348 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx @@ -3,8 +3,8 @@ import { NoContent } from 'UI'; import { Styles } from '../../common'; import { AreaChart, Area, - BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + CartesianGrid, Tooltip, + ResponsiveContainer, XAxis, YAxis } from 'recharts'; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx index 9fc69d018..3ef08f392 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { Styles, Table } from '../../common'; +import { NoContent } from 'UI'; +import { Table } from '../../common'; import { getRE } from 'App/utils'; import ImageInfo from './ImageInfo'; import MethodType from './MethodType'; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx index 09c86b60c..ee8bb056f 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.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'; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx index 30463860c..ff801eb07 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx @@ -3,8 +3,8 @@ import { NoContent } from 'UI'; import { Styles } from '../../common'; import { AreaChart, Area, - BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + CartesianGrid, Tooltip, + ResponsiveContainer, XAxis, YAxis } from 'recharts'; import { NO_METRIC_DATA } from 'App/constants/messages' diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx index ec952487c..d2acb58a5 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx @@ -3,7 +3,7 @@ import { NoContent } from 'UI'; import { Styles } from '../../common'; import { BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + Legend, ResponsiveContainer, XAxis, YAxis } from 'recharts'; import { NO_METRIC_DATA } from 'App/constants/messages' diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx index 5a5efb961..2c534a7fa 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx @@ -3,8 +3,8 @@ import { NoContent } from 'UI'; import { Styles, AvgLabel } from '../../common'; import { AreaChart, Area, - BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + CartesianGrid, Tooltip, + ResponsiveContainer, XAxis, YAxis } from 'recharts'; import { NO_METRIC_DATA } from 'App/constants/messages' diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx index 6fb22c784..3a3a491b0 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx @@ -3,8 +3,8 @@ import { NoContent } from 'UI'; import { Styles, AvgLabel } from '../../common'; import { AreaChart, Area, - BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + CartesianGrid, Tooltip, + ResponsiveContainer, XAxis, YAxis } from 'recharts'; import { NO_METRIC_DATA } from 'App/constants/messages' diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MissingResources/MissingResources.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MissingResources/MissingResources.tsx index c0b3767e2..6a2b5ed80 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MissingResources/MissingResources.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MissingResources/MissingResources.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { NoContent } from 'UI'; -import { Styles, Table } from '../../common'; +import { Table } from '../../common'; import { List } from 'immutable'; import Chart from './Chart'; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx index 4326b3b3a..687778b5f 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Loader, NoContent } from 'UI'; +import { NoContent } from 'UI'; import { Styles, AvgLabel } from '../../common'; import { ComposedChart, Bar, BarChart, CartesianGrid, ResponsiveContainer, - XAxis, YAxis, ReferenceLine, Tooltip, Legend + XAxis, YAxis, ReferenceLine, Tooltip } from 'recharts'; import { NO_METRIC_DATA } from 'App/constants/messages' diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx index e798d5b4c..8dd76ffea 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx @@ -3,7 +3,7 @@ import { NoContent } from 'UI'; import { Styles } from '../../common'; import { BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + Legend, ResponsiveContainer, XAxis, YAxis } from 'recharts'; import { NO_METRIC_DATA } from 'App/constants/messages' diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.tsx index def859f10..492bc73fe 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.tsx @@ -3,8 +3,8 @@ import { NoContent } from 'UI'; import { Styles } from '../../common'; import { AreaChart, Area, - BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + CartesianGrid, Tooltip, + ResponsiveContainer, XAxis, YAxis } from 'recharts'; import { NO_METRIC_DATA } from 'App/constants/messages' diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx index ae873f2fb..e514d9c18 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { NoContent } from 'UI'; -import { Styles, Table } from '../../common'; +import { Table } from '../../common'; import { List } from 'immutable'; import { numberWithCommas } from 'App/utils'; diff --git a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.js b/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.js new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/components/Dashboard/components/DashbaordListModal/DashbaordListModal.tsx b/frontend/app/components/Dashboard/components/DashbaordListModal/DashbaordListModal.tsx index 9b93b9942..236602493 100644 --- a/frontend/app/components/Dashboard/components/DashbaordListModal/DashbaordListModal.tsx +++ b/frontend/app/components/Dashboard/components/DashbaordListModal/DashbaordListModal.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useStore } from 'App/mstore'; -import { SideMenuitem, SideMenuHeader, Icon, Button } from 'UI'; -import { withSiteId, dashboardSelected, metrics } from 'App/routes'; +import { SideMenuitem, Icon } from 'UI'; +import { withSiteId, dashboardSelected } from 'App/routes'; import { withRouter } from 'react-router-dom'; import { useModal } from 'App/components/Modal'; diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index 64ff7e143..ace14e03e 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -1,6 +1,6 @@ import { observer } from 'mobx-react-lite'; import React from 'react'; -import { NoContent, Pagination, Icon } from 'UI'; +import { NoContent, Pagination } from 'UI'; import { useStore } from 'App/mstore'; import { filterList } from 'App/utils'; import { sliceListPerPage } from 'App/utils'; diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx index e689bce51..7ead7f17f 100644 --- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { SideMenuitem, SideMenuHeader } from 'UI'; +import { SideMenuitem } from 'UI'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import { withSiteId, metrics, dashboard, alerts } from 'App/routes'; import { connect } from 'react-redux'; diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorDetails.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorDetails.tsx index a45226fb3..3a102a9c4 100644 --- a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorDetails.tsx +++ b/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorDetails.tsx @@ -1,8 +1,6 @@ import React, { useState } from 'react' import ErrorFrame from './ErrorFrame' -import cn from 'classnames'; import { IconButton, Icon } from 'UI'; -import { connect } from 'react-redux'; const docLink = 'https://docs.openreplay.com/installation/upload-sourcemaps'; diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx index 2d76241b3..145a215ab 100644 --- a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx +++ b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx @@ -1,16 +1,12 @@ import React from 'react'; import cn from 'classnames'; import moment from 'moment'; -import { error as errorRoute } from 'App/routes'; import { IGNORED, RESOLVED } from 'Types/errorInfo'; -import { Link, Label } from 'UI'; import ErrorName from '../ErrorName'; import ErrorLabel from '../ErrorLabel'; import { BarChart, Bar, YAxis, Tooltip, XAxis } from 'recharts'; import { Styles } from '../../../Widgets/common'; import { diffFromNowString } from 'App/date'; -import { useModal } from '../../../../Modal'; -import ErrorDetailsModal from '../ErrorDetailsModal'; interface Props { error: any; diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesListItem/FunnelIssuesListItem.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesListItem/FunnelIssuesListItem.tsx index 89aa09be3..b692ca30b 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesListItem/FunnelIssuesListItem.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesListItem/FunnelIssuesListItem.tsx @@ -2,8 +2,6 @@ import React from 'react'; import cn from 'classnames'; import { Icon, TextEllipsis } from 'UI'; import FunnelIssueGraph from '../FunnelIssueGraph'; -import { useModal } from 'App/components/Modal'; -import FunnelIssueModal from '../FunnelIssueModal'; interface Props { issue: any; diff --git a/frontend/app/components/Dashboard/components/MetricTypeList/MetricTypeList.tsx b/frontend/app/components/Dashboard/components/MetricTypeList/MetricTypeList.tsx index e53d6e7a2..46c540d07 100644 --- a/frontend/app/components/Dashboard/components/MetricTypeList/MetricTypeList.tsx +++ b/frontend/app/components/Dashboard/components/MetricTypeList/MetricTypeList.tsx @@ -4,7 +4,7 @@ import MetricsLibraryModal from '../MetricsLibraryModal'; import MetricTypeItem, { MetricType } from '../MetricTypeItem/MetricTypeItem'; import { TYPES, LIBRARY } from 'App/constants/card'; import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { dashboardMetricCreate, metricCreate, withSiteId } from 'App/routes'; +import { dashboardMetricCreate, withSiteId } from 'App/routes'; import { useStore } from 'App/mstore'; interface Props extends RouteComponentProps { diff --git a/frontend/app/components/Dashboard/components/MetricsList/GridView.tsx b/frontend/app/components/Dashboard/components/MetricsList/GridView.tsx index 9b131bb2a..df556b46e 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/GridView.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/GridView.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import MetricListItem from '../MetricListItem'; import WidgetWrapper from 'App/components/Dashboard/components/WidgetWrapper'; interface Props { diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index cb30afad1..44423944b 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -1,6 +1,6 @@ import { observer, useObserver } from 'mobx-react-lite'; import React, { useEffect, useState } from 'react'; -import { NoContent, Pagination, Icon } from 'UI'; +import { NoContent, Pagination } from 'UI'; import { useStore } from 'App/mstore'; import { filterList } from 'App/utils'; import { sliceListPerPage } from 'App/utils'; diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index c32d0d498..3291c0006 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -15,7 +15,6 @@ import useIsMounted from 'App/hooks/useIsMounted' import { FilterKey } from 'Types/filter/filterType'; import { TIMESERIES, TABLE, CLICKMAP, FUNNEL, ERRORS, PERFORMANCE, RESOURCE_MONITORING, WEB_VITALS } from 'App/constants/card'; import FunnelWidget from 'App/components/Funnels/FunnelWidget'; -import ErrorsWidget from '../Errors/ErrorsWidget'; import SessionWidget from '../Sessions/SessionWidget'; import CustomMetricTableSessions from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions'; import CustomMetricTableErrors from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors'; diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index ada2dd03b..32127c3dc 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -4,7 +4,7 @@ import Select from 'Shared/Select'; import cn from 'classnames'; import { useStore } from 'App/mstore'; import SessionItem from 'Shared/SessionItem'; -import { observer, useObserver } from 'mobx-react-lite'; +import { observer } from 'mobx-react-lite'; import { DateTime } from 'luxon'; import { debounce } from 'App/utils'; import useIsMounted from 'App/hooks/useIsMounted'; diff --git a/frontend/app/components/Errors/Error/MainSection.js b/frontend/app/components/Errors/Error/MainSection.js index d65c884cb..a74755408 100644 --- a/frontend/app/components/Errors/Error/MainSection.js +++ b/frontend/app/components/Errors/Error/MainSection.js @@ -2,17 +2,15 @@ import React from 'react'; import { connect } from 'react-redux'; import cn from 'classnames'; import withSiteIdRouter from 'HOCs/withSiteIdRouter'; -import { ErrorDetails, IconButton, Icon, Loader, Button } from 'UI'; +import { ErrorDetails, Icon, Loader, Button } from 'UI'; import { sessions as sessionsRoute } from 'App/routes'; -import { TYPES as EV_FILER_TYPES } from 'Types/filter/event'; -import { UNRESOLVED, RESOLVED, IGNORED } from 'Types/errorInfo'; +import { RESOLVED } from 'Types/errorInfo'; import { addFilterByKeyAndValue } from 'Duck/search'; import { resolve, unresolve, ignore, toggleFavorite } from 'Duck/errors'; import { resentOrDate } from 'App/date'; import Divider from 'Components/Errors/ui/Divider'; import ErrorName from 'Components/Errors/ui/ErrorName'; import Label from 'Components/Errors/ui/Label'; -import SharePopup from 'Shared/SharePopup'; import { FilterKey } from 'Types/filter/filterType'; import SessionBar from './SessionBar'; diff --git a/frontend/app/components/Errors/List/List.js b/frontend/app/components/Errors/List/List.js index 4318951ab..d41ebd50c 100644 --- a/frontend/app/components/Errors/List/List.js +++ b/frontend/app/components/Errors/List/List.js @@ -1,7 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Set, List as ImmutableList } from "immutable"; -import { NoContent, Loader, Checkbox, LoadMoreButton, IconButton, Input, DropdownPlain, Pagination } from 'UI'; +import { Set } from "immutable"; +import { NoContent, Loader, Checkbox, IconButton, Input, Pagination } from 'UI'; import { merge, resolve, unresolve, ignore, updateCurrentPage, editOptions } from "Duck/errors"; import { applyFilter } from 'Duck/filters'; import { IGNORED, UNRESOLVED } from 'Types/errorInfo'; diff --git a/frontend/app/components/Errors/List/ListItem/ListItem.js b/frontend/app/components/Errors/List/ListItem/ListItem.js index 1dde46e59..51e416b81 100644 --- a/frontend/app/components/Errors/List/ListItem/ListItem.js +++ b/frontend/app/components/Errors/List/ListItem/ListItem.js @@ -5,8 +5,7 @@ import moment from 'moment'; import { diffFromNowString } from 'App/date'; import { error as errorRoute } from 'App/routes'; import { IGNORED, RESOLVED } from 'Types/errorInfo'; -import { diffFromNowShortString } from 'App/date'; -import { Checkbox, Link } from 'UI'; +import { Checkbox, Link } from 'UI'; import ErrorName from 'Components/Errors/ui/ErrorName'; import Label from 'Components/Errors/ui/Label'; import stl from './listItem.module.css'; diff --git a/frontend/app/components/Funnels/FunnelGraph/FunnelGraph.js b/frontend/app/components/Funnels/FunnelGraph/FunnelGraph.js index 8f3cbb5ea..221a59e8f 100644 --- a/frontend/app/components/Funnels/FunnelGraph/FunnelGraph.js +++ b/frontend/app/components/Funnels/FunnelGraph/FunnelGraph.js @@ -9,9 +9,8 @@ import { YAxis, CartesianGrid, Tooltip, - Legend, LabelList, - Label, + } from 'recharts'; import { connect } from 'react-redux'; import { setActiveStages } from 'Duck/funnels'; diff --git a/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js b/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js index 4a96c76c0..0175008a8 100644 --- a/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js +++ b/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js @@ -1,7 +1,7 @@ import React, { useState } from 'react' import { connect } from 'react-redux' import { fetchIssues, fetchIssuesFiltered } from 'Duck/funnels' -import { LoadMoreButton, NoContent, Loader } from 'UI' +import { LoadMoreButton, NoContent } from 'UI' import FunnelIssuesHeader from '../FunnelIssuesHeader' import IssueItem from '../IssueItem'; import { funnelIssue as funnelIssueRoute, withSiteId } from 'App/routes' diff --git a/frontend/app/components/Funnels/FunnelSessionList/FunnelSessionList.js b/frontend/app/components/Funnels/FunnelSessionList/FunnelSessionList.js index c16a98407..f51538696 100644 --- a/frontend/app/components/Funnels/FunnelSessionList/FunnelSessionList.js +++ b/frontend/app/components/Funnels/FunnelSessionList/FunnelSessionList.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux' import SessionItem from 'Shared/SessionItem' import { fetchSessions, fetchSessionsFiltered } from 'Duck/funnels' import { setFunnelPage } from 'Duck/sessions' -import { LoadMoreButton, NoContent, Loader } from 'UI' +import { LoadMoreButton, NoContent } from 'UI' import FunnelSessionsHeader from '../FunnelSessionsHeader' import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx index b3e1e2f89..4e738935d 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx @@ -1,6 +1,6 @@ import React from 'react'; import FunnelStepText from './FunnelStepText'; -import { Icon, Tooltip } from 'UI'; +import { Icon } from 'UI'; interface Props { filter: any; diff --git a/frontend/app/components/Funnels/funnels.stories.js b/frontend/app/components/Funnels/funnels.stories.js index 67849e0fc..58197bbcc 100644 --- a/frontend/app/components/Funnels/funnels.stories.js +++ b/frontend/app/components/Funnels/funnels.stories.js @@ -1,5 +1,4 @@ import { storiesOf } from '@storybook/react'; -import { List } from 'immutable'; import Funnel from 'Types/funnel' import FunnelIssue from 'Types/funnelIssue' import FunnelList from './FunnelList'; diff --git a/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx b/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx index 9b5c74879..8e6a68e58 100644 --- a/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx +++ b/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx @@ -1,13 +1,12 @@ import React from 'react'; -import { NavLink, withRouter } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; import { sessions, metrics, assist, - client, dashboard, withSiteId, - CLIENT_DEFAULT_TAB, + } from 'App/routes'; import SiteDropdown from '../SiteDropdown'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; diff --git a/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx b/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx index a0dd30244..2ac9e95ae 100644 --- a/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx +++ b/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { Icon } from 'UI'; -import cn from 'classnames'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; import { useModal } from 'App/components/Modal'; diff --git a/frontend/app/components/Header/OnboardingExplore/FeatureItem.js b/frontend/app/components/Header/OnboardingExplore/FeatureItem.js index d30ce85a1..c5841b19b 100644 --- a/frontend/app/components/Header/OnboardingExplore/FeatureItem.js +++ b/frontend/app/components/Header/OnboardingExplore/FeatureItem.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Checkbox, Icon } from 'UI'; +import { Icon } from 'UI'; import cn from 'classnames'; import stl from './featureItem.module.css'; diff --git a/frontend/app/components/Header/PreferencesView/PreferencesView.tsx b/frontend/app/components/Header/PreferencesView/PreferencesView.tsx index a5282aea8..f9da1b933 100644 --- a/frontend/app/components/Header/PreferencesView/PreferencesView.tsx +++ b/frontend/app/components/Header/PreferencesView/PreferencesView.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Icon } from 'UI'; import { withRouter } from 'react-router-dom'; -import ProjectCodeSnippet from 'App/components/Onboarding/components/OnboardingTabs/ProjectCodeSnippet'; interface Props { history: any; diff --git a/frontend/app/components/Onboarding/components/MetadataList/MetadataList.js b/frontend/app/components/Onboarding/components/MetadataList/MetadataList.js index 96dfc6345..76068fddf 100644 --- a/frontend/app/components/Onboarding/components/MetadataList/MetadataList.js +++ b/frontend/app/components/Onboarding/components/MetadataList/MetadataList.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react' import { Button, SlideModal, TagBadge } from 'UI' import { connect } from 'react-redux' -import { init, fetchList, save, remove } from 'Duck/customField'; +import { fetchList, save, remove } from 'Duck/customField'; import CustomFieldForm from '../../../Client/CustomFields/CustomFieldForm'; import { confirm } from 'UI'; diff --git a/frontend/app/components/Onboarding/components/OnboardingTabs/ProjectCodeSnippet/ProjectCodeSnippet.js b/frontend/app/components/Onboarding/components/OnboardingTabs/ProjectCodeSnippet/ProjectCodeSnippet.js index 8b520aa49..7bf9ef5bb 100644 --- a/frontend/app/components/Onboarding/components/OnboardingTabs/ProjectCodeSnippet/ProjectCodeSnippet.js +++ b/frontend/app/components/Onboarding/components/OnboardingTabs/ProjectCodeSnippet/ProjectCodeSnippet.js @@ -7,7 +7,6 @@ import GDPR from 'Types/site/gdpr'; import cn from 'classnames' import stl from './projectCodeSnippet.module.css' import CircleNumber from '../../CircleNumber'; -import Highlight from 'react-highlight' import Select from 'Shared/Select' import CodeSnippet from 'Shared/CodeSnippet'; diff --git a/frontend/app/components/Onboarding/components/SideMenu.js b/frontend/app/components/Onboarding/components/SideMenu.js index 537305831..64d35a2e4 100644 --- a/frontend/app/components/Onboarding/components/SideMenu.js +++ b/frontend/app/components/Onboarding/components/SideMenu.js @@ -1,7 +1,7 @@ import React from 'react' import stl from './sideMenu.module.css' import cn from 'classnames' -import { SideMenuitem, Icon } from 'UI' +import { SideMenuitem } from 'UI' import OnboardingMenu from './OnboardingMenu/OnboardingMenu' export default function SideMenu() { diff --git a/frontend/app/components/Session/Layout/Header/Section.js b/frontend/app/components/Session/Layout/Header/Section.js index c98849a19..a432cd169 100644 --- a/frontend/app/components/Session/Layout/Header/Section.js +++ b/frontend/app/components/Session/Layout/Header/Section.js @@ -1,6 +1,5 @@ import React from 'react'; import { Icon } from 'UI'; -import styles from './section.module.css'; export default function Section({ icon, label }) { return ( diff --git a/frontend/app/components/Session/Layout/Player/Controls.js b/frontend/app/components/Session/Layout/Player/Controls.js index 69af9a111..fc1360972 100644 --- a/frontend/app/components/Session/Layout/Player/Controls.js +++ b/frontend/app/components/Session/Layout/Player/Controls.js @@ -1,11 +1,8 @@ import React from 'react'; import { observer } from 'mobx-react-lite'; -import { useEffect, useCallback } from 'react'; -import { connect } from 'react-redux'; +import { useEffect } from 'react'; import cn from 'classnames'; -import { Popup, Icon } from 'UI'; - import Timeline from './Timeline'; import ControlButton from './ControlButton'; diff --git a/frontend/app/components/Session/Layout/ToolPanel.js b/frontend/app/components/Session/Layout/ToolPanel.js index 2f4779c71..6e7fc259e 100644 --- a/frontend/app/components/Session/Layout/ToolPanel.js +++ b/frontend/app/components/Session/Layout/ToolPanel.js @@ -1,5 +1,4 @@ import React from 'react'; -import { useCallback } from 'react'; import { observer } from 'mobx-react-lite'; import { CloseButton } from 'UI'; diff --git a/frontend/app/components/Session/Layout/ToolPanel/Performance.tsx b/frontend/app/components/Session/Layout/ToolPanel/Performance.tsx index 790a9b96d..c5ae6f809 100644 --- a/frontend/app/components/Session/Layout/ToolPanel/Performance.tsx +++ b/frontend/app/components/Session/Layout/ToolPanel/Performance.tsx @@ -9,10 +9,8 @@ import { Tooltip, ResponsiveContainer, ReferenceLine, - CartesianGrid, Label, } from 'recharts'; -import { Checkbox } from 'UI'; import { durationFromMsFormatted } from 'App/date'; import { formatBytes } from 'App/utils'; diff --git a/frontend/app/components/Session_/Autoscroll.tsx b/frontend/app/components/Session_/Autoscroll.tsx index ad2e82e01..88db41d59 100644 --- a/frontend/app/components/Session_/Autoscroll.tsx +++ b/frontend/app/components/Session_/Autoscroll.tsx @@ -1,5 +1,4 @@ import React, { ReactNode } from 'react'; -import { IconButton } from 'UI'; import cn from 'classnames'; import stl from './autoscroll.module.css'; diff --git a/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js b/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js index 965e9726d..419434d22 100644 --- a/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js +++ b/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React from 'react' import { Input, Icon } from 'UI' import { PlayerContext } from 'App/components/Session/playerContext'; diff --git a/frontend/app/components/Session_/EventsBlock/UserCard/UserCard.js b/frontend/app/components/Session_/EventsBlock/UserCard/UserCard.js index 2e4e4daa1..bda25ad88 100644 --- a/frontend/app/components/Session_/EventsBlock/UserCard/UserCard.js +++ b/frontend/app/components/Session_/EventsBlock/UserCard/UserCard.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { connect } from 'react-redux'; import { List } from 'immutable'; import { countries } from 'App/constants'; diff --git a/frontend/app/components/Session_/Fetch/components/Headers/Headers.tsx b/frontend/app/components/Session_/Fetch/components/Headers/Headers.tsx index c2ec31a07..17214a215 100644 --- a/frontend/app/components/Session_/Fetch/components/Headers/Headers.tsx +++ b/frontend/app/components/Session_/Fetch/components/Headers/Headers.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { NoContent, TextEllipsis } from 'UI' +import { NoContent } from 'UI' import stl from './headers.module.css' import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; diff --git a/frontend/app/components/Session_/GraphQL/GQLDetails.js b/frontend/app/components/Session_/GraphQL/GQLDetails.js index 73eadb3ab..e7b00384e 100644 --- a/frontend/app/components/Session_/GraphQL/GQLDetails.js +++ b/frontend/app/components/Session_/GraphQL/GQLDetails.js @@ -1,5 +1,5 @@ import React from 'react'; -import { JSONTree, Button } from 'UI'; +import { JSONTree } from 'UI'; import cn from 'classnames'; export default class GQLDetails extends React.PureComponent { diff --git a/frontend/app/components/Session_/HeaderInfo.js b/frontend/app/components/Session_/HeaderInfo.js index c1cb5ae14..e53bd0c07 100644 --- a/frontend/app/components/Session_/HeaderInfo.js +++ b/frontend/app/components/Session_/HeaderInfo.js @@ -1,6 +1,5 @@ import React from 'react'; import { Icon } from 'UI'; -import styles from './headerInfo.module.css'; const HeaderInfo = ({ icon, label }) => { return ( diff --git a/frontend/app/components/Session_/Issues/IssueHeader.js b/frontend/app/components/Session_/Issues/IssueHeader.js index e98288589..6d666382b 100644 --- a/frontend/app/components/Session_/Issues/IssueHeader.js +++ b/frontend/app/components/Session_/Issues/IssueHeader.js @@ -1,7 +1,5 @@ import React from 'react'; -import { Icon, Input } from 'UI'; -import ActiveIssueClose from './ActiveIssueClose'; -import stl from './issueHeader.module.css'; +import { Icon } from 'UI'; const GotoSessionLink = props => ( <a className="flex items-center absolute right-0 mr-3 cursor-pointer"> diff --git a/frontend/app/components/Session_/Network/NetworkContent.js b/frontend/app/components/Session_/Network/NetworkContent.js index 2891a0597..d568feacf 100644 --- a/frontend/app/components/Session_/Network/NetworkContent.js +++ b/frontend/app/components/Session_/Network/NetworkContent.js @@ -1,6 +1,6 @@ import React from 'react'; import cn from 'classnames'; -import { QuestionMarkHint, Tooltip, Tabs, Input, NoContent, Icon, Toggler, Button } from 'UI'; +import { QuestionMarkHint, Tooltip, Tabs, Input, NoContent, Icon, Toggler } from 'UI'; import { getRE } from 'App/utils'; import { TYPES } from 'Types/session/resource'; import { formatBytes } from 'App/utils'; diff --git a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx index 76490900a..51b150add 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx @@ -1,7 +1,7 @@ import React from 'react'; import JsonViewer from './components/JsonViewer'; import Sentry from './components/Sentry'; -import { OPENREPLAY, SENTRY, DATADOG, STACKDRIVER } from 'Types/session/stackEvent'; +import { SENTRY, DATADOG, STACKDRIVER } from 'Types/session/stackEvent'; interface Props { event: any; diff --git a/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx b/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx index 12d8e618c..1a8c9b3ab 100644 --- a/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx @@ -1,4 +1,4 @@ -import React, { memo, FC, useEffect, useRef, CSSProperties } from 'react'; +import React, { memo, FC, useEffect, CSSProperties } from 'react'; import type { DragSourceMonitor } from 'react-dnd' import { useDrag } from 'react-dnd' import { getEmptyImage } from 'react-dnd-html5-backend' diff --git a/frontend/app/components/Session_/StackEvents/StackEvents.DEPRECATED.js b/frontend/app/components/Session_/StackEvents/StackEvents.DEPRECATED.js index 516a9adde..3ed7bab84 100644 --- a/frontend/app/components/Session_/StackEvents/StackEvents.DEPRECATED.js +++ b/frontend/app/components/Session_/StackEvents/StackEvents.DEPRECATED.js @@ -11,7 +11,6 @@ import { DATADOG, SENTRY, STACKDRIVER, typeList } from 'Types/session/stackEvent import { NoContent, SlideModal, Tabs, Link } from 'UI'; import Autoscroll from '../Autoscroll'; import BottomBlock from '../BottomBlock'; -import UserEvent from './UserEvent'; const ALL = 'ALL'; diff --git a/frontend/app/components/Session_/StackEvents/UserEvent/UserEvent.js b/frontend/app/components/Session_/StackEvents/UserEvent/UserEvent.js index 2dfd4d8aa..7403a3fa0 100644 --- a/frontend/app/components/Session_/StackEvents/UserEvent/UserEvent.js +++ b/frontend/app/components/Session_/StackEvents/UserEvent/UserEvent.js @@ -1,12 +1,8 @@ import React from 'react'; import cn from 'classnames'; -import { OPENREPLAY, SENTRY, DATADOG, STACKDRIVER } from 'Types/session/stackEvent'; +import { OPENREPLAY } from 'Types/session/stackEvent'; import { Icon } from 'UI'; import withToggle from 'HOCs/withToggle'; -import Sentry from './Sentry'; -import JsonViewer from './JsonViewer'; -import stl from './userEvent.module.css'; -import { Duration } from 'luxon'; import JumpButton from 'Shared/DevTools/JumpButton'; // const modalSources = [ SENTRY, DATADOG ]; diff --git a/frontend/app/components/Session_/Storage/Storge.DEPRECATED.js b/frontend/app/components/Session_/Storage/Storge.DEPRECATED.js index 660d2057e..fc5b19416 100644 --- a/frontend/app/components/Session_/Storage/Storge.DEPRECATED.js +++ b/frontend/app/components/Session_/Storage/Storge.DEPRECATED.js @@ -11,8 +11,6 @@ import { import { JSONTree, NoContent } from 'UI'; import { formatMs } from 'App/date'; import { diff } from 'deep-diff'; -import DiffTree from './DiffTree' -import { setIn } from 'immutable'; import { jump } from 'Player'; import Autoscroll from '../Autoscroll'; import BottomBlock from '../BottomBlock/index'; diff --git a/frontend/app/components/Session_/TimeTable/TimeTable.tsx b/frontend/app/components/Session_/TimeTable/TimeTable.tsx index d81a22dcf..5df7242ec 100644 --- a/frontend/app/components/Session_/TimeTable/TimeTable.tsx +++ b/frontend/app/components/Session_/TimeTable/TimeTable.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { List, AutoSizer } from 'react-virtualized'; import cn from 'classnames'; import { Duration } from "luxon"; -import { NoContent, IconButton, Button } from 'UI'; +import { NoContent, Button } from 'UI'; import { percentOf } from 'App/utils'; import BarRow from './BarRow'; diff --git a/frontend/app/components/Session_/components/HeaderMenu.tsx b/frontend/app/components/Session_/components/HeaderMenu.tsx index 003ca5ab3..9d70d8aab 100644 --- a/frontend/app/components/Session_/components/HeaderMenu.tsx +++ b/frontend/app/components/Session_/components/HeaderMenu.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon, Button } from 'UI'; +import { Button } from 'UI'; import styles from './menu.module.css'; import cn from 'classnames'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; diff --git a/frontend/app/components/Signup/SignupForm/SignupForm.js b/frontend/app/components/Signup/SignupForm/SignupForm.js index 90580973d..f110d2dae 100644 --- a/frontend/app/components/Signup/SignupForm/SignupForm.js +++ b/frontend/app/components/Signup/SignupForm/SignupForm.js @@ -1,5 +1,5 @@ import React from 'react' -import { Form, Input, Icon, Button, Link, CircularLoader } from 'UI' +import { Form, Input, Icon, Button, Link } from 'UI' import { login } from 'App/routes' import ReCAPTCHA from 'react-google-recaptcha' import stl from './signup.module.css' diff --git a/frontend/app/components/hocs/withReport.tsx b/frontend/app/components/hocs/withReport.tsx index aebd72998..bbe34bc33 100644 --- a/frontend/app/components/hocs/withReport.tsx +++ b/frontend/app/components/hocs/withReport.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import { convertElementToImage } from 'App/utils'; import { jsPDF } from 'jspdf'; import { useStore } from 'App/mstore'; -import { observer, useObserver } from 'mobx-react-lite'; +import { useObserver } from 'mobx-react-lite'; import { connect } from 'react-redux'; import { fileNameFormat } from 'App/utils'; import { toast } from 'react-toastify'; diff --git a/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx b/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx index 77a49a7e1..aedd4a097 100644 --- a/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx +++ b/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { IconButton } from 'UI'; import { connect } from 'react-redux'; import { edit, init } from 'Duck/customMetrics'; diff --git a/frontend/app/components/shared/DevTools/ProfilerPanel/ProfilerPanel.tsx b/frontend/app/components/shared/DevTools/ProfilerPanel/ProfilerPanel.tsx index f5709093b..0eabda0e4 100644 --- a/frontend/app/components/shared/DevTools/ProfilerPanel/ProfilerPanel.tsx +++ b/frontend/app/components/shared/DevTools/ProfilerPanel/ProfilerPanel.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { observer } from 'mobx-react-lite'; import { TextEllipsis, Input } from 'UI'; import { PlayerContext } from 'App/components/Session/playerContext'; diff --git a/frontend/app/components/shared/DevTools/StackEventModal/StackEventModal.tsx b/frontend/app/components/shared/DevTools/StackEventModal/StackEventModal.tsx index c68c0ca80..991cca1ef 100644 --- a/frontend/app/components/shared/DevTools/StackEventModal/StackEventModal.tsx +++ b/frontend/app/components/shared/DevTools/StackEventModal/StackEventModal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { DATADOG, SENTRY, STACKDRIVER, typeList } from 'Types/session/stackEvent'; +import { DATADOG, SENTRY, STACKDRIVER } from 'Types/session/stackEvent'; import JsonViewer from 'Components/Session_/StackEvents/UserEvent/JsonViewer'; import Sentry from 'Components/Session_/StackEvents/UserEvent/Sentry'; diff --git a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx index b97e82e97..e36c17ccf 100644 --- a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx +++ b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { observer } from 'mobx-react-lite'; -import { Tooltip, Tabs, Input, NoContent, Icon, Toggler } from 'UI'; +import { Tabs, Input, NoContent, Icon } from 'UI'; import { List, CellMeasurer, AutoSizer } from 'react-virtualized'; import { PlayerContext } from 'App/components/Session/playerContext'; import BottomBlock from '../BottomBlock'; diff --git a/frontend/app/components/shared/DevTools/StackEventRow/StackEventRow.tsx b/frontend/app/components/shared/DevTools/StackEventRow/StackEventRow.tsx index 0d2eeb554..b32846c5b 100644 --- a/frontend/app/components/shared/DevTools/StackEventRow/StackEventRow.tsx +++ b/frontend/app/components/shared/DevTools/StackEventRow/StackEventRow.tsx @@ -2,7 +2,7 @@ import React from 'react'; import JumpButton from '../JumpButton'; import { Icon } from 'UI'; import cn from 'classnames'; -import { OPENREPLAY, SENTRY, DATADOG, STACKDRIVER } from 'Types/session/stackEvent'; +import { OPENREPLAY } from 'Types/session/stackEvent'; interface Props { event: any; diff --git a/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx b/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx index f283eb7ac..eaaebb754 100644 --- a/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx +++ b/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx @@ -1,4 +1,3 @@ -import { Tooltip } from 'UI'; import { percentOf } from 'App/utils'; import styles from './barRow.module.css'; import tableStyles from './timeTable.module.css'; diff --git a/frontend/app/components/shared/DevTools/useAutoscroll.ts b/frontend/app/components/shared/DevTools/useAutoscroll.ts index c466eb34f..5225f59ea 100644 --- a/frontend/app/components/shared/DevTools/useAutoscroll.ts +++ b/frontend/app/components/shared/DevTools/useAutoscroll.ts @@ -1,4 +1,4 @@ -import { useEffect, useState, useRef, useMemo } from 'react' +import { useEffect, useState, useMemo } from 'react' import { Timed } from 'Player' import useLatestRef from 'App/hooks/useLatestRef' import useCancelableTimeout from 'App/hooks/useCancelableTimeout' diff --git a/frontend/app/components/shared/FetchDetailsModal/components/Headers/Headers.tsx b/frontend/app/components/shared/FetchDetailsModal/components/Headers/Headers.tsx index 49bf12676..dbfdf81a8 100644 --- a/frontend/app/components/shared/FetchDetailsModal/components/Headers/Headers.tsx +++ b/frontend/app/components/shared/FetchDetailsModal/components/Headers/Headers.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { NoContent, TextEllipsis } from 'UI'; +import { NoContent } from 'UI'; import stl from './headers.module.css'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index 854a15b75..60dd9b3a4 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -2,7 +2,7 @@ import React from 'react'; import FilterOperator from '../FilterOperator'; import FilterSelection from '../FilterSelection'; import FilterValue from '../FilterValue'; -import { Icon, Button } from 'UI'; +import { Button } from 'UI'; import FilterSource from '../FilterSource'; import { FilterKey, FilterType } from 'App/types/filter/filterType'; import SubFilterItem from '../SubFilterItem'; diff --git a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx index fa259b9f6..4e3656d20 100644 --- a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx +++ b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { Icon, Loader } from 'UI'; import { connect } from 'react-redux'; import cn from 'classnames'; diff --git a/frontend/app/components/shared/Filters/LiveFilterModal/LiveFilterModal.tsx b/frontend/app/components/shared/Filters/LiveFilterModal/LiveFilterModal.tsx index db7913093..0716d3dd6 100644 --- a/frontend/app/components/shared/Filters/LiveFilterModal/LiveFilterModal.tsx +++ b/frontend/app/components/shared/Filters/LiveFilterModal/LiveFilterModal.tsx @@ -39,7 +39,6 @@ function LiveFilterModal(props: Props) { const isResultEmpty = (!filterSearchList || Object.keys(filterSearchList).filter(i => filtersMap[i].isLive).length === 0) && matchingCategories.length === 0 && matchingFilters.length === 0 - getMatchingEntries return ( <div className={stl.wrapper} style={{ width: '490px', maxHeight: '400px', overflowY: 'auto'}}> <div className=""> diff --git a/frontend/app/components/shared/GraphQLDetailsModal/GraphQLDetailsModal.tsx b/frontend/app/components/shared/GraphQLDetailsModal/GraphQLDetailsModal.tsx index d64a91b5d..ad4d91be9 100644 --- a/frontend/app/components/shared/GraphQLDetailsModal/GraphQLDetailsModal.tsx +++ b/frontend/app/components/shared/GraphQLDetailsModal/GraphQLDetailsModal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { JSONTree, Button } from 'UI'; +import { JSONTree } from 'UI'; import cn from 'classnames'; interface Props { diff --git a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx index 9e1792d78..077e3e446 100644 --- a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx @@ -1,4 +1,4 @@ -import React, { Fragment, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { NoContent, Loader, Pagination, Button } from 'UI'; import { List } from 'immutable'; diff --git a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx index e7d8c8831..627956675 100644 --- a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx +++ b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { DATE_RANGE_OPTIONS, CUSTOM_RANGE } from 'App/dateRange'; import Select from 'Shared/Select'; -import Period, { LAST_7_DAYS } from 'Types/app/period'; +import Period from 'Types/app/period'; import { components } from 'react-select'; import DateRangePopup from 'Shared/DateRangeDropdown/DateRangePopup'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; diff --git a/frontend/app/components/shared/SessionItem/SessionMetaList/SessionMetaList.tsx b/frontend/app/components/shared/SessionItem/SessionMetaList/SessionMetaList.tsx index 8257c47d0..1ed439fef 100644 --- a/frontend/app/components/shared/SessionItem/SessionMetaList/SessionMetaList.tsx +++ b/frontend/app/components/shared/SessionItem/SessionMetaList/SessionMetaList.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Popup } from 'UI'; import cn from 'classnames'; import MetaItem from '../MetaItem'; import MetaMoreButton from '../MetaMoreButton'; diff --git a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteItem.tsx b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteItem.tsx index d445e926c..1ece07d7f 100644 --- a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteItem.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteItem.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon, Link } from 'UI'; +import { Link } from 'UI'; import PlayLink from 'Shared/SessionItem/PlayLink'; import { tagProps, Note } from 'App/services/NotesService'; import { formatTimeOrDate } from 'App/date'; diff --git a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteList.tsx b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteList.tsx index a6bc9c977..28db4a6ca 100644 --- a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteList.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteList.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { NoContent, Pagination, Loader, Icon } from 'UI'; +import { NoContent, Pagination, Loader } from 'UI'; import { sliceListPerPage } from 'App/utils'; import NoteItem from './NoteItem'; import { observer } from 'mobx-react-lite'; diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx index 7657cb66a..b8df9e446 100644 --- a/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { FilterKey } from 'Types/filter/filterType'; import SessionItem from 'Shared/SessionItem'; diff --git a/frontend/app/components/shared/TrackingCodeModal/ProjectCodeSnippet/ProjectCodeSnippet.js b/frontend/app/components/shared/TrackingCodeModal/ProjectCodeSnippet/ProjectCodeSnippet.js index 496d7aec8..201e51ce7 100644 --- a/frontend/app/components/shared/TrackingCodeModal/ProjectCodeSnippet/ProjectCodeSnippet.js +++ b/frontend/app/components/shared/TrackingCodeModal/ProjectCodeSnippet/ProjectCodeSnippet.js @@ -6,7 +6,6 @@ import { Checkbox } from 'UI'; import GDPR from 'Types/site/gdpr'; import cn from 'classnames' import styles from './projectCodeSnippet.module.css' -import Highlight from 'react-highlight' import Select from 'Shared/Select' import CodeSnippet from '../../CodeSnippet'; diff --git a/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js b/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js index 9534d875f..d8cc8b020 100644 --- a/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js +++ b/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js @@ -1,11 +1,9 @@ import React from 'react'; -import { Modal, Icon, Tabs } from 'UI'; -import styles from './trackingCodeModal.module.css'; +import { Tabs } from 'UI'; import { editGDPR, saveGDPR } from 'Duck/site'; import { connect } from 'react-redux'; import ProjectCodeSnippet from './ProjectCodeSnippet'; import InstallDocs from './InstallDocs'; -import cn from 'classnames'; const PROJECT = 'Using Script'; const DOCUMENTATION = 'Using NPM'; diff --git a/frontend/app/components/shared/UpdateFunnelButton/UpdateFunnelButton.tsx b/frontend/app/components/shared/UpdateFunnelButton/UpdateFunnelButton.tsx index 50c1215ed..9f7a2eb06 100644 --- a/frontend/app/components/shared/UpdateFunnelButton/UpdateFunnelButton.tsx +++ b/frontend/app/components/shared/UpdateFunnelButton/UpdateFunnelButton.tsx @@ -1,6 +1,5 @@ -import React, { useState } from 'react'; +import React from 'react'; import { IconButton } from 'UI'; -import FunnelSaveModal from 'App/components/Funnels/FunnelSaveModal'; import { connect } from 'react-redux'; import { save } from 'Duck/funnels'; diff --git a/frontend/app/components/shared/UserSessionsModal/UserSessionsModal.tsx b/frontend/app/components/shared/UserSessionsModal/UserSessionsModal.tsx index cb5baf47f..8692133f0 100644 --- a/frontend/app/components/shared/UserSessionsModal/UserSessionsModal.tsx +++ b/frontend/app/components/shared/UserSessionsModal/UserSessionsModal.tsx @@ -1,12 +1,9 @@ import React, { useEffect } from 'react'; import { useStore } from 'App/mstore'; -import Filter from 'Types/filter'; -import { filtersMap } from 'Types/filter/newFilter'; import { FilterKey } from 'App/types/filter/filterType'; import { NoContent, Pagination, Loader, Avatar } from 'UI'; import SessionItem from 'Shared/SessionItem'; import SelectDateRange from 'Shared/SelectDateRange'; -import Period from 'Types/app/period'; import { useObserver, observer } from 'mobx-react-lite'; import { useModal } from 'App/components/Modal'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; diff --git a/frontend/app/components/shared/WidgetAutoComplete/WidgetAutoComplete.js b/frontend/app/components/shared/WidgetAutoComplete/WidgetAutoComplete.js index 97fbc9b90..f7de177e6 100644 --- a/frontend/app/components/shared/WidgetAutoComplete/WidgetAutoComplete.js +++ b/frontend/app/components/shared/WidgetAutoComplete/WidgetAutoComplete.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Icon, CircularLoader, Button, TextEllipsis } from 'UI'; +import { Icon, CircularLoader, Button } from 'UI'; import cn from 'classnames'; import stl from './widgetAutoComplete.module.css'; import { debounce } from 'App/utils'; diff --git a/frontend/app/components/shared/XRayButton/XRayButton.tsx b/frontend/app/components/shared/XRayButton/XRayButton.tsx index ae94df33a..ec7842991 100644 --- a/frontend/app/components/shared/XRayButton/XRayButton.tsx +++ b/frontend/app/components/shared/XRayButton/XRayButton.tsx @@ -1,8 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import stl from './xrayButton.module.css'; import cn from 'classnames'; import { Tooltip } from 'UI'; -import { FEATURE_KEYS } from 'Shared/GuidePopup'; import { PlayerContext } from 'App/components/Session/playerContext'; interface Props { diff --git a/frontend/app/components/ui/ErrorItem/ErrorItem.tsx b/frontend/app/components/ui/ErrorItem/ErrorItem.tsx index 74ad634d9..d64d24eb9 100644 --- a/frontend/app/components/ui/ErrorItem/ErrorItem.tsx +++ b/frontend/app/components/ui/ErrorItem/ErrorItem.tsx @@ -1,8 +1,6 @@ import React from 'react'; import cn from 'classnames'; -import { IconButton } from 'UI'; import stl from './errorItem.module.css'; -import { Duration } from 'luxon'; import { useModal } from 'App/components/Modal'; import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal'; import JumpButton from 'Shared/DevTools/JumpButton'; diff --git a/frontend/app/components/ui/Label/Label.js b/frontend/app/components/ui/Label/Label.js index 371b80c9b..74ebb0f7d 100644 --- a/frontend/app/components/ui/Label/Label.js +++ b/frontend/app/components/ui/Label/Label.js @@ -1,5 +1,4 @@ import React from 'react'; -import styles from './label.module.css'; import cn from 'classnames'; export default ({ diff --git a/frontend/app/components/ui/NoSessionPermission/NoSessionPermission.tsx b/frontend/app/components/ui/NoSessionPermission/NoSessionPermission.tsx index 4d66e3f2e..ca62e8fb6 100644 --- a/frontend/app/components/ui/NoSessionPermission/NoSessionPermission.tsx +++ b/frontend/app/components/ui/NoSessionPermission/NoSessionPermission.tsx @@ -1,6 +1,6 @@ import React from "react"; import stl from "./NoSessionPermission.module.css"; -import { Icon, Button, Link } from "UI"; +import { Icon, Button } from "UI"; import { connect } from "react-redux"; import { sessions as sessionsRoute, diff --git a/frontend/app/components/ui/Tooltip/FloatingTooltip.tsx b/frontend/app/components/ui/Tooltip/FloatingTooltip.tsx index c157940de..a15382767 100644 --- a/frontend/app/components/ui/Tooltip/FloatingTooltip.tsx +++ b/frontend/app/components/ui/Tooltip/FloatingTooltip.tsx @@ -13,7 +13,7 @@ import { useInteractions, FloatingPortal, arrow, - computePosition, + } from '@floating-ui/react-dom-interactions'; import type { Placement } from '@floating-ui/react-dom-interactions'; import { INDEXES } from 'App/constants/zindex'; diff --git a/frontend/app/components/ui/Tooltip/Tooltip.tsx b/frontend/app/components/ui/Tooltip/Tooltip.tsx index eb1c16c2d..fcb5e1687 100644 --- a/frontend/app/components/ui/Tooltip/Tooltip.tsx +++ b/frontend/app/components/ui/Tooltip/Tooltip.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTooltipState, TooltipAnchor, FloatingTooltip, FloatingArrow } from './FloatingTooltip'; +import { useTooltipState, TooltipAnchor, FloatingTooltip } from './FloatingTooltip'; import type { Placement } from '@floating-ui/react-dom-interactions'; import cn from 'classnames'; diff --git a/frontend/app/components/ui/ui.stories.js b/frontend/app/components/ui/ui.stories.js index e8c276c27..eb20ca86b 100644 --- a/frontend/app/components/ui/ui.stories.js +++ b/frontend/app/components/ui/ui.stories.js @@ -1,5 +1,4 @@ import { storiesOf } from '@storybook/react'; -import { List } from 'immutable'; import SideMenuitem from './SideMenuitem'; import { Avatar, ErrorItem, ErrorFrame, ErrorDetails, TimelinePointer } from 'UI'; import Error from 'Types/session/error'; diff --git a/frontend/app/constants/card.ts b/frontend/app/constants/card.ts index 92032b2b2..5ffcd646f 100644 --- a/frontend/app/constants/card.ts +++ b/frontend/app/constants/card.ts @@ -1,5 +1,5 @@ import { IconNames } from 'App/components/ui/SVG'; -import { FilterKey, IssueType } from 'Types/filter/filterType'; +import { FilterKey } from 'Types/filter/filterType'; export interface CardType { title: string; diff --git a/frontend/app/constants/countryShortName.js b/frontend/app/constants/countryShortName.js index 05948ccd6..ef2a61eab 100644 --- a/frontend/app/constants/countryShortName.js +++ b/frontend/app/constants/countryShortName.js @@ -63,8 +63,6 @@ export default (countryName) => { return 'ATG'; case 'American Samoa': return 'ASM'; - case 'Brunei Darussalam': - return 'BRN'; case 'Palestine, State Of': return 'PSE'; case 'Saint Kitts and Nevis': diff --git a/frontend/app/duck/alerts.js b/frontend/app/duck/alerts.js new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/duck/components/targetDefiner.js b/frontend/app/duck/components/targetDefiner.js index 9f28e8c5c..c58726aa9 100644 --- a/frontend/app/duck/components/targetDefiner.js +++ b/frontend/app/duck/components/targetDefiner.js @@ -1,7 +1,6 @@ import { Map } from 'immutable'; import Target from 'Types/target'; import TargetCustom from 'Types/targetCustom'; -import withRequestState, { RequestTypes } from 'Duck/requestStateCreator'; const EDIT = 'targetDefiner/EDIT'; const SHOW = 'targetDefiner/SHOW'; diff --git a/frontend/app/duck/errors.js b/frontend/app/duck/errors.js index 8a23875cc..80d731bee 100644 --- a/frontend/app/duck/errors.js +++ b/frontend/app/duck/errors.js @@ -1,9 +1,9 @@ import { List, Map } from 'immutable'; import { clean as cleanParams } from 'App/api_client'; import ErrorInfo, { RESOLVED, UNRESOLVED, IGNORED, BOOKMARK } from 'Types/errorInfo'; -import { createFetch, fetchListType, fetchType } from './funcTools/crud'; +import { fetchListType, fetchType } from './funcTools/crud'; import { createRequestReducer, ROOT_KEY } from './funcTools/request'; -import { array, request, success, failure, createListUpdater, mergeReducers } from './funcTools/tools'; +import { array, success, failure, createListUpdater, mergeReducers } from './funcTools/tools'; import { reduceThenFetchResource } from './search' const name = "error"; diff --git a/frontend/app/duck/filters.js b/frontend/app/duck/filters.js index 8d9dbbff9..544fcb93c 100644 --- a/frontend/app/duck/filters.js +++ b/frontend/app/duck/filters.js @@ -2,12 +2,12 @@ import { List, Map, Set } from 'immutable'; import { errors as errorsRoute, isRoute } from "App/routes"; import Filter from 'Types/filter'; import SavedFilter from 'Types/filter/savedFilter'; -import Event, { TYPES } from 'Types/filter/event'; +import Event from 'Types/filter/event'; import CustomFilter, { KEYS } from 'Types/filter/customFilter'; import withRequestState, { RequestTypes } from './requestStateCreator'; import { fetchList as fetchSessionList } from './sessions'; import { fetchList as fetchErrorsList } from './errors'; -import { fetchListType, fetchType, saveType, editType, initType, removeType } from './funcTools/crud/types'; +import { editType } from './funcTools/crud/types'; const ERRORS_ROUTE = errorsRoute(); diff --git a/frontend/app/duck/funnelFilters.js b/frontend/app/duck/funnelFilters.js index 298cdd445..c9f40846a 100644 --- a/frontend/app/duck/funnelFilters.js +++ b/frontend/app/duck/funnelFilters.js @@ -1,14 +1,11 @@ -import { fromJS, List, Map, Set } from 'immutable'; -import { errors as errorsRoute, isRoute } from "App/routes"; +import { List, Map, Set } from 'immutable'; +import { errors as errorsRoute } from "App/routes"; import Filter from 'Types/filter'; import SavedFilter from 'Types/filter/savedFilter'; -import Event, { TYPES } from 'Types/filter/event'; +import Event from 'Types/filter/event'; import CustomFilter from 'Types/filter/customFilter'; import withRequestState, { RequestTypes } from './requestStateCreator'; -import { fetchList as fetchSessionList } from './sessions'; -import { fetchList as fetchErrorsList } from './errors'; import { fetch as fetchFunnel, fetchInsights, fetchIssuesFiltered, fetchSessionsFiltered } from './funnels'; -import logger from 'App/logger'; const ERRORS_ROUTE = errorsRoute(); diff --git a/frontend/app/duck/funnels.js b/frontend/app/duck/funnels.js index d175b64d5..330828882 100644 --- a/frontend/app/duck/funnels.js +++ b/frontend/app/duck/funnels.js @@ -145,10 +145,6 @@ const reducer = (state = initialState, action = {}) => { return state.update('list', itemInListUpdater(CustomField(action.data))) case REMOVE_SUCCESS: return state.update('list', list => list.filter(item => item.index !== action.index)); - case INIT: - return state.set('instance', Funnel(action.instance)); - case EDIT: - return state.mergeIn([ 'instance' ], action.instance); case APPLY_FILTER: return state.mergeIn([ action.filterType ], Array.isArray(action.filter) ? action.filter : Map(action.filter)); case APPLY_ISSUE_FILTER: diff --git a/frontend/app/duck/integrations/actions.js b/frontend/app/duck/integrations/actions.js index 9ab831c41..1f926014f 100644 --- a/frontend/app/duck/integrations/actions.js +++ b/frontend/app/duck/integrations/actions.js @@ -1,4 +1,3 @@ -import { array } from '../funcTools/tools'; import { fetchListType, fetchType, saveType, editType, initType, removeType } from '../funcTools/types'; export function fetchList(name) { diff --git a/frontend/app/duck/integrations/integrations.js b/frontend/app/duck/integrations/integrations.js index 7f6999ea8..c21952b9b 100644 --- a/frontend/app/duck/integrations/integrations.js +++ b/frontend/app/duck/integrations/integrations.js @@ -1,5 +1,4 @@ import { Map } from 'immutable'; -import withRequestState from 'Duck/requestStateCreator'; import { fetchListType } from '../funcTools/types'; import { createRequestReducer } from '../funcTools/request'; diff --git a/frontend/app/duck/issues.js b/frontend/app/duck/issues.js index 6cc97a96f..ce68d31e4 100644 --- a/frontend/app/duck/issues.js +++ b/frontend/app/duck/issues.js @@ -2,7 +2,7 @@ import Assignment from 'Types/session/assignment'; import Activity from 'Types/session/activity'; import { List, Map, Set } from 'immutable'; import withRequestState, { RequestTypes } from 'Duck/requestStateCreator'; -import { createListUpdater, createItemInListUpdater } from './funcTools/tools'; +import { createListUpdater } from './funcTools/tools'; import { editType, initType } from './funcTools/crud/types'; import { createInit, createEdit } from './funcTools/crud'; diff --git a/frontend/app/duck/liveSearch.js b/frontend/app/duck/liveSearch.js index 8500158bd..499f6c740 100644 --- a/frontend/app/duck/liveSearch.js +++ b/frontend/app/duck/liveSearch.js @@ -3,7 +3,7 @@ import { fetchListType, fetchType, editType } from './funcTools/crud'; import { createRequestReducer } from './funcTools/request'; import { mergeReducers, success } from './funcTools/tools'; import Filter from 'Types/filter'; -import { liveFiltersMap, filtersMap } from 'Types/filter/newFilter'; +import { liveFiltersMap } from 'Types/filter/newFilter'; import { filterMap, checkFilterValue, hasFilterApplied } from './search'; import Session from 'Types/session'; diff --git a/frontend/app/duck/rehydrate.js b/frontend/app/duck/rehydrate.js index 4dd424ba4..df0b4ff50 100644 --- a/frontend/app/duck/rehydrate.js +++ b/frontend/app/duck/rehydrate.js @@ -1,7 +1,7 @@ import { List, Map } from 'immutable'; import RehydrateJob from 'Types/rehydrateJob'; -import { mergeReducers, success, array } from './funcTools/tools'; +import { mergeReducers } from './funcTools/tools'; import { createRequestReducer } from './funcTools/request'; import { createCRUDReducer, diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index 569eec435..715bddcfb 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -1,7 +1,7 @@ import { List, Map } from 'immutable'; import { fetchListType, fetchType, saveType, removeType, editType } from './funcTools/crud'; import { createRequestReducer, ROOT_KEY } from './funcTools/request'; -import { array, success, createListUpdater, mergeReducers } from './funcTools/tools'; +import { array, success, mergeReducers } from './funcTools/tools'; import Filter from 'Types/filter'; import SavedFilter from 'Types/filter/savedFilter'; import { errors as errorsRoute, isRoute } from 'App/routes'; diff --git a/frontend/app/duck/sessions.ts b/frontend/app/duck/sessions.ts index 79298de87..5fe92192d 100644 --- a/frontend/app/duck/sessions.ts +++ b/frontend/app/duck/sessions.ts @@ -1,7 +1,7 @@ import { List, Map } from 'immutable'; import Session from 'Types/session'; import ErrorStack from 'Types/session/errorStack'; -import { Location, InjectedEvent } from 'Types/session/event' +import { Location } from 'Types/session/event' import Watchdog from 'Types/watchdog'; import { clean as cleanParams } from 'App/api_client'; import withRequestState, { RequestTypes } from './requestStateCreator'; diff --git a/frontend/app/duck/site.js b/frontend/app/duck/site.js index 54d1e1688..4ce55bba5 100644 --- a/frontend/app/duck/site.js +++ b/frontend/app/duck/site.js @@ -10,7 +10,6 @@ import { import { createCRUDReducer, getCRUDRequestTypes, - createFetchList, createInit, createEdit, createRemove, diff --git a/frontend/app/mstore/assistMultiviewStore.ts b/frontend/app/mstore/assistMultiviewStore.ts index 379f852cb..e05a5e433 100644 --- a/frontend/app/mstore/assistMultiviewStore.ts +++ b/frontend/app/mstore/assistMultiviewStore.ts @@ -2,7 +2,7 @@ import { makeAutoObservable } from 'mobx'; import { sessionService } from 'App/services'; import Filter from 'Types/filter'; import Session from 'Types/session'; -import { List, Map } from 'immutable'; +import { List } from 'immutable'; type MultiSessions = [ LiveSessionListItem?, diff --git a/frontend/app/mstore/auditStore.ts b/frontend/app/mstore/auditStore.ts index efc34d65d..2603c1ac4 100644 --- a/frontend/app/mstore/auditStore.ts +++ b/frontend/app/mstore/auditStore.ts @@ -1,11 +1,10 @@ -import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" +import { makeAutoObservable, runInAction, observable, action } from "mobx" import { auditService } from "App/services" import Audit from './types/audit' import Period, { LAST_7_DAYS } from 'Types/app/period'; import { toast } from 'react-toastify'; import { exportCSVFile } from 'App/utils'; -import { formatDateTimeDefault } from 'App/date'; -import { DateTime, Duration } from 'luxon'; // TODO +import { DateTime } from 'luxon'; // TODO export default class AuditStore { list: any[] = []; diff --git a/frontend/app/mstore/errorStore.ts b/frontend/app/mstore/errorStore.ts index 32b095ade..b7aaed549 100644 --- a/frontend/app/mstore/errorStore.ts +++ b/frontend/app/mstore/errorStore.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" +import { makeAutoObservable } from "mobx" import { errorService } from "App/services" import Error from "./types/error" diff --git a/frontend/app/mstore/funnelStore.ts b/frontend/app/mstore/funnelStore.ts index f0a9d5384..818c51e59 100644 --- a/frontend/app/mstore/funnelStore.ts +++ b/frontend/app/mstore/funnelStore.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" +import { makeAutoObservable, action } from "mobx" import { funnelService } from "App/services" import Funnel, { IFunnel } from "./types/funnel"; import Session from './types/session'; diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index dc4d31aea..ac29401aa 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -3,7 +3,7 @@ import Widget from './types/widget'; import { metricService, errorService } from 'App/services'; import { toast } from 'react-toastify'; import Error from './types/error'; -import { TIMESERIES, TABLE, CLICKMAP, FUNNEL, ERRORS, RESOURCE_MONITORING, PERFORMANCE, WEB_VITALS } from 'App/constants/card'; +import { TIMESERIES, TABLE, FUNNEL, ERRORS, RESOURCE_MONITORING, PERFORMANCE, WEB_VITALS } from 'App/constants/card'; export default class MetricStore { isLoading: boolean = false; diff --git a/frontend/app/mstore/roleStore.ts b/frontend/app/mstore/roleStore.ts index 6f87b4bcb..45c8b65d0 100644 --- a/frontend/app/mstore/roleStore.ts +++ b/frontend/app/mstore/roleStore.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, observable, action } from "mobx" +import { makeAutoObservable, observable } from "mobx" import { userService } from "App/services"; import Role, { IRole } from "./types/role"; diff --git a/frontend/app/mstore/settingsStore.ts b/frontend/app/mstore/settingsStore.ts index dc4f6baa4..2f1dcba4e 100644 --- a/frontend/app/mstore/settingsStore.ts +++ b/frontend/app/mstore/settingsStore.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, observable, action } from "mobx" +import { makeAutoObservable, observable } from "mobx" import SessionSettings from "./types/sessionSettings" import { sessionService } from "App/services" import { toast } from 'react-toastify'; diff --git a/frontend/app/mstore/types/filterItem.ts b/frontend/app/mstore/types/filterItem.ts index 9214fb9bd..65d79e22d 100644 --- a/frontend/app/mstore/types/filterItem.ts +++ b/frontend/app/mstore/types/filterItem.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, runInAction, observable, action, reaction } from 'mobx'; +import { makeAutoObservable, observable, action } from 'mobx'; import { FilterKey, FilterType, FilterCategory } from 'Types/filter/filterType'; import { filtersMap } from 'Types/filter/newFilter'; diff --git a/frontend/app/mstore/types/filterSeries.ts b/frontend/app/mstore/types/filterSeries.ts index c3051e612..fa693a2d4 100644 --- a/frontend/app/mstore/types/filterSeries.ts +++ b/frontend/app/mstore/types/filterSeries.ts @@ -1,6 +1,6 @@ // import Filter from 'Types/filter'; import Filter from './filter' -import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" +import { makeAutoObservable, observable, action } from "mobx" export default class FilterSeries { public static get ID_KEY():string { return "seriesId" } diff --git a/frontend/app/mstore/types/sessionSettings.ts b/frontend/app/mstore/types/sessionSettings.ts index bde66cdaf..ece447dbb 100644 --- a/frontend/app/mstore/types/sessionSettings.ts +++ b/frontend/app/mstore/types/sessionSettings.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, runInAction, action } from 'mobx'; +import { makeAutoObservable, runInAction } from 'mobx'; import moment from 'moment'; import { SKIP_TO_ISSUE, TIMEZONE, DURATION_FILTER } from 'App/constants/storageKeys'; diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts index fe52392c2..265e4a422 100644 --- a/frontend/app/player/player/Animator.ts +++ b/frontend/app/player/player/Animator.ts @@ -1,5 +1,4 @@ import type { Store, Moveable, Interval } from '../common/types'; -import * as localStorage from './localStorage'; const fps = 60 const performance: { now: () => number } = window.performance || { now: Date.now.bind(Date) } diff --git a/frontend/app/player/player/Player.ts b/frontend/app/player/player/Player.ts index 95a3c45de..837443f3f 100644 --- a/frontend/app/player/player/Player.ts +++ b/frontend/app/player/player/Player.ts @@ -2,7 +2,7 @@ import * as typedLocalStorage from './localStorage'; import type { Moveable, Cleanable, Store } from '../common/types'; import Animator from './Animator'; -import type { GetState as AnimatorGetState, SetState as AnimatorSetState } from './Animator'; +import type { GetState as AnimatorGetState } from './Animator'; /* == separate this == */ diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index e27432daf..81df4d3ca 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -1,7 +1,7 @@ import { Log, LogLevel } from './types' import type { Store } from 'App/player' -import Player, { State as PlayerState } from '../player/Player' +import Player from '../player/Player' import MessageManager from './MessageManager' import InspectorController from './addons/InspectorController' diff --git a/frontend/app/player/web/assist/AssistManager.ts b/frontend/app/player/web/assist/AssistManager.ts index ec41e5382..13de4ebdd 100644 --- a/frontend/app/player/web/assist/AssistManager.ts +++ b/frontend/app/player/web/assist/AssistManager.ts @@ -4,7 +4,6 @@ import type { Store } from '../../common/types' import type { Message } from '../messages'; import MStreamReader from '../messages/MStreamReader'; import JSONRawMessageReader from '../messages/JSONRawMessageReader' -import appStore from 'App/store'; import Call, { CallingState } from './Call'; import RemoteControl, { RemoteControlStatus } from './RemoteControl' import ScreenRecording, { SessionRecordingStatus } from './ScreenRecording' diff --git a/frontend/app/player/web/managers/DOM/DOMManager.ts b/frontend/app/player/web/managers/DOM/DOMManager.ts index e47a803fd..198c096ad 100644 --- a/frontend/app/player/web/managers/DOM/DOMManager.ts +++ b/frontend/app/player/web/managers/DOM/DOMManager.ts @@ -1,8 +1,7 @@ import logger from 'App/logger'; import type Screen from '../../Screen/Screen'; -import type MessageManager from '../../MessageManager'; -import type { Message, SetNodeScroll, CreateElementNode } from '../../messages'; +import type { Message, SetNodeScroll } from '../../messages'; import { MType } from '../../messages'; import ListWalker from '../../../common/ListWalker'; diff --git a/frontend/app/player/web/managers/DOM/StylesManager.ts b/frontend/app/player/web/managers/DOM/StylesManager.ts index c38ea0281..295b95d2f 100644 --- a/frontend/app/player/web/managers/DOM/StylesManager.ts +++ b/frontend/app/player/web/managers/DOM/StylesManager.ts @@ -1,5 +1,4 @@ import type Screen from '../../Screen/Screen'; -import type MessageManager from '../../MessageManager' import type { CssInsertRule, CssDeleteRule } from '../../messages'; type CSSRuleMessage = CssInsertRule | CssDeleteRule; diff --git a/frontend/app/player/web/managers/MouseMoveManager.ts b/frontend/app/player/web/managers/MouseMoveManager.ts index ea08e1bc9..1b19d7e5b 100644 --- a/frontend/app/player/web/managers/MouseMoveManager.ts +++ b/frontend/app/player/web/managers/MouseMoveManager.ts @@ -1,5 +1,4 @@ import type Screen from '../Screen/Screen' -import type { Point } from '../Screen/types' import type { MouseMove } from '../messages' import ListWalker from '../../common/ListWalker' diff --git a/frontend/app/player/web/managers/PagesManager.ts b/frontend/app/player/web/managers/PagesManager.ts index 35d47a670..dbc64bb72 100644 --- a/frontend/app/player/web/managers/PagesManager.ts +++ b/frontend/app/player/web/managers/PagesManager.ts @@ -1,6 +1,5 @@ import type Screen from '../Screen/Screen'; import type { Message } from '../messages'; -import type MessageManager from '../MessageManager'; import { MType } from '../messages'; import ListWalker from '../../common/ListWalker'; diff --git a/frontend/app/player/web/messages/JSONRawMessageReader.ts b/frontend/app/player/web/messages/JSONRawMessageReader.ts index 6a9cfb4f9..ca193c326 100644 --- a/frontend/app/player/web/messages/JSONRawMessageReader.ts +++ b/frontend/app/player/web/messages/JSONRawMessageReader.ts @@ -1,6 +1,5 @@ import type { RawMessage } from './raw.gen' import type { TrackerMessage } from './tracker.gen' -import { MType } from './raw.gen' import translate from './tracker.gen' import { TP_MAP } from './tracker-legacy.gen' import resolveURL from './urlBasedResolver' diff --git a/frontend/app/player/web/messages/MStreamReader.ts b/frontend/app/player/web/messages/MStreamReader.ts index 4b08aae9e..a61e374cd 100644 --- a/frontend/app/player/web/messages/MStreamReader.ts +++ b/frontend/app/player/web/messages/MStreamReader.ts @@ -1,7 +1,6 @@ import type { Message } from './message.gen' import type { RawMessage } from './raw.gen' import { MType } from './raw.gen' -import RawMessageReader from './RawMessageReader.gen' interface RawMessageReaderI { readMessage(): RawMessage | null diff --git a/frontend/app/types/alert.js b/frontend/app/types/alert.js new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/types/errorInfo.js b/frontend/app/types/errorInfo.js index 7da9c3a9b..28f3b2708 100644 --- a/frontend/app/types/errorInfo.js +++ b/frontend/app/types/errorInfo.js @@ -1,4 +1,3 @@ -import { List } from 'immutable'; import Record from './Record'; import Session from './session'; diff --git a/frontend/app/types/filter/customFilter.js b/frontend/app/types/filter/customFilter.js index 59e47d985..29557da21 100644 --- a/frontend/app/types/filter/customFilter.js +++ b/frontend/app/types/filter/customFilter.js @@ -1,6 +1,5 @@ import Record from 'Types/Record'; import Target from 'Types/target'; -import { camelCased } from 'App/utils'; import { getEventIcon } from 'Types/filter'; const CLICK = 'CLICK'; diff --git a/frontend/app/types/filter/filter.js b/frontend/app/types/filter/filter.js index 864613678..3ae206d45 100644 --- a/frontend/app/types/filter/filter.js +++ b/frontend/app/types/filter/filter.js @@ -5,7 +5,6 @@ import { TYPES } from 'Types/filter/event'; import { DATE_RANGE_VALUES, CUSTOM_RANGE, - dateRangeValues, getDateRangeFromValue } from 'App/dateRange'; import Event from './event'; diff --git a/frontend/app/types/filter/index.js b/frontend/app/types/filter/index.js index 386ea96a0..57ffd3fc9 100644 --- a/frontend/app/types/filter/index.js +++ b/frontend/app/types/filter/index.js @@ -170,7 +170,6 @@ export const defaultOperator = (filter) => { case TYPES.USER_COUNTRY: case TYPES.METADATA: case 'metadata': - case TYPES.CUSTOM: case TYPES.LOCATION: case TYPES.VIEW: return 'is'; diff --git a/frontend/app/types/filter/savedFilter.js b/frontend/app/types/filter/savedFilter.js index 7b414430f..006f22047 100644 --- a/frontend/app/types/filter/savedFilter.js +++ b/frontend/app/types/filter/savedFilter.js @@ -1,7 +1,6 @@ import Record from 'Types/Record'; import Filter from './filter'; -import { List } from 'immutable'; -import { notEmptyString, validateName } from 'App/validate'; +import { notEmptyString } from 'App/validate'; export default Record({ searchId: undefined, diff --git a/frontend/app/types/funnel.js b/frontend/app/types/funnel.js index d06e518b5..24d4e02a8 100644 --- a/frontend/app/types/funnel.js +++ b/frontend/app/types/funnel.js @@ -1,6 +1,5 @@ import Record from 'Types/Record'; import Filter from 'Types/filter'; -import { truncate } from 'App/utils'; // import { validateURL, validateName } from 'App/validate'; const getRedableName = ({ type, value, operator }) => { diff --git a/frontend/app/types/session/error.ts b/frontend/app/types/session/error.ts index 66d2db096..45bd759ef 100644 --- a/frontend/app/types/session/error.ts +++ b/frontend/app/types/session/error.ts @@ -1,5 +1,3 @@ -import Record from 'Types/Record'; - function getStck0InfoString(stack: Stack) { const stack0 = stack[0]; if (!stack0) return ""; diff --git a/frontend/app/types/session/event.ts b/frontend/app/types/session/event.ts index 569a1903e..6a99d536a 100644 --- a/frontend/app/types/session/event.ts +++ b/frontend/app/types/session/event.ts @@ -1,6 +1,3 @@ -import Record from 'Types/Record'; -import Target from 'Types/target'; - const CONSOLE = 'CONSOLE'; const CLICK = 'CLICK'; const INPUT = 'INPUT'; diff --git a/frontend/app/types/synthetics/index.js b/frontend/app/types/synthetics/index.js index 1c693ae88..1cec8da81 100644 --- a/frontend/app/types/synthetics/index.js +++ b/frontend/app/types/synthetics/index.js @@ -1,10 +1,5 @@ -import { Map, List } from 'immutable'; -import Session from 'Types/session'; -import { camelCased } from 'App/utils'; - -import { getChartFormatter } from './helper'; +import { getChartFormatter } from './helper'; import DomBuildingTime from './domBuildingTime'; -import ResourceLoadingTime from './resourceLoadingTime'; export const WIDGET_LIST = [ { diff --git a/frontend/app/types/ts/search.ts b/frontend/app/types/ts/search.ts index 32660818b..432f9e3f7 100644 --- a/frontend/app/types/ts/search.ts +++ b/frontend/app/types/ts/search.ts @@ -1,5 +1,3 @@ -import { List } from 'immutable'; - export interface SavedSearch { count: number; createdAt: number; diff --git a/frontend/app/utils/index.ts b/frontend/app/utils/index.ts index b9ab9f8bf..c9bed5e46 100644 --- a/frontend/app/utils/index.ts +++ b/frontend/app/utils/index.ts @@ -2,7 +2,6 @@ import JSBI from 'jsbi'; import chroma from 'chroma-js'; import * as htmlToImage from 'html-to-image'; import { SESSION_FILTER } from 'App/constants/storageKeys'; -import { useEffect, useRef, useState } from 'react'; export function debounce(callback, wait, context = this) { let timeout = null; diff --git a/frontend/scripts/constants.js b/frontend/scripts/constants.js index 32b4de9a6..759dcb621 100644 --- a/frontend/scripts/constants.js +++ b/frontend/scripts/constants.js @@ -3,7 +3,7 @@ const countries = require('country-data').countries; delete countries['UK']; delete countries['EU']; -for (code in countries) { +for (let code in countries) { const country = countries[code]; if (code.length != 2) { delete countries[code]; diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index 94df4d579..6dd02728f 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -1,6 +1,6 @@ import webpack from "webpack"; import path from "path"; -import { Configuration as WebpackConfiguration, HotModuleReplacementPlugin } from "webpack"; +import { Configuration as WebpackConfiguration } from "webpack"; import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server'; import CopyWebpackPlugin from 'copy-webpack-plugin'; import HtmlWebpackPlugin from "html-webpack-plugin"; From a1ce424df9009c5b82729d92f3b0638d82d616cc Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Tue, 10 Jan 2023 15:33:03 +0100 Subject: [PATCH 30/65] change(ui): remove unused files --- .../app/components/Session/LiveSession.js | 1 - frontend/app/components/Session/Session.js | 2 - .../app/components/Session_/HeaderInfo.js | 13 -- .../app/components/Session_/Player/Player.js | 122 ------------ .../app/components/Session_/Player/index.js | 1 - .../components/Session_/PlayerBlockHeader.tsx | 185 ------------------ 6 files changed, 324 deletions(-) delete mode 100644 frontend/app/components/Session_/HeaderInfo.js delete mode 100644 frontend/app/components/Session_/Player/Player.js delete mode 100644 frontend/app/components/Session_/Player/index.js delete mode 100644 frontend/app/components/Session_/PlayerBlockHeader.tsx diff --git a/frontend/app/components/Session/LiveSession.js b/frontend/app/components/Session/LiveSession.js index 54df7d5c7..19b7d8614 100644 --- a/frontend/app/components/Session/LiveSession.js +++ b/frontend/app/components/Session/LiveSession.js @@ -11,7 +11,6 @@ import LivePlayer from './LivePlayer'; function LiveSession({ sessionId, loading, - session, fetchSession, fetchSlackList, hasSessionsPath, diff --git a/frontend/app/components/Session/Session.js b/frontend/app/components/Session/Session.js index dae27df35..e3801f84a 100644 --- a/frontend/app/components/Session/Session.js +++ b/frontend/app/components/Session/Session.js @@ -16,9 +16,7 @@ function Session({ sessionId, loading, hasErrors, - session, fetchSession, - fetchSlackList, }) { usePageTitle("OpenReplay Session Player"); const [ initializing, setInitializing ] = useState(true) diff --git a/frontend/app/components/Session_/HeaderInfo.js b/frontend/app/components/Session_/HeaderInfo.js deleted file mode 100644 index e53bd0c07..000000000 --- a/frontend/app/components/Session_/HeaderInfo.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { Icon } from 'UI'; - -const HeaderInfo = ({ icon, label }) => { - return ( - <div className="flex items-center mx-4"> - <Icon name={ icon } size="18" color="color-dark" /> - <div className="ml-2 mt-1 font-sm font-normal color-gray-darkest text-sm">{ label }</div> - </div> - ); -}; - -export default HeaderInfo; diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js deleted file mode 100644 index 6fa429eed..000000000 --- a/frontend/app/components/Session_/Player/Player.js +++ /dev/null @@ -1,122 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { findDOMNode } from 'react-dom'; -import cn from 'classnames'; -import { EscapeButton } from 'UI'; -import { - NONE, - CONSOLE, - NETWORK, - STACKEVENTS, - STORAGE, - PROFILER, - PERFORMANCE, - GRAPHQL, - EXCEPTIONS, - INSPECTOR, - OVERVIEW, - fullscreenOff, -} from 'Duck/components/player'; -import NetworkPanel from 'Shared/DevTools/NetworkPanel'; -import Storage from '../Storage'; -import { ConnectedPerformance } from '../Performance'; -import GraphQL from '../GraphQL'; -import Exceptions from '../Exceptions/Exceptions'; -import Inspector from '../Inspector'; -import Controls from './Controls'; -import Overlay from './Overlay'; -import stl from './player.module.css'; -import { updateLastPlayedSession } from 'Duck/sessions'; -import OverviewPanel from '../OverviewPanel'; -import ConsolePanel from 'Shared/DevTools/ConsolePanel'; -import ProfilerPanel from 'Shared/DevTools/ProfilerPanel'; -import { PlayerContext } from 'App/components/Session/playerContext'; -import StackEventPanel from 'Shared/DevTools/StackEventPanel'; - -function Player(props) { - const { - className, - fullscreen, - fullscreenOff, - nextId, - closedLive, - bottomBlock, - activeTab, - fullView, - isMultiview, - isClickmap, - } = props; - const playerContext = React.useContext(PlayerContext); - const screenWrapper = React.useRef(); - const bottomBlockIsActive = !fullscreen && bottomBlock !== NONE; - - React.useEffect(() => { - props.updateLastPlayedSession(props.sessionId); - if (!props.closedLive || isMultiview) { - const parentElement = findDOMNode(screenWrapper.current); //TODO: good architecture - playerContext.player.attach(parentElement); - playerContext.player.play(); - } - }, []); - - React.useEffect(() => { - playerContext.player.scale(); - }, [props.bottomBlock, props.fullscreen, playerContext.player]); - - if (!playerContext.player) return null; - - const maxWidth = activeTab ? 'calc(100vw - 270px)' : '100vw'; - return ( - <div - className={cn(className, stl.playerBody, 'flex flex-col relative', fullscreen && 'pb-2')} - data-bottom-block={bottomBlockIsActive} - > - {fullscreen && <EscapeButton onClose={fullscreenOff} />} - <div className={cn("relative flex-1", isClickmap ? 'overflow-visible' : 'overflow-hidden')}> - <Overlay nextId={nextId} closedLive={closedLive} isClickmap={isClickmap} /> - <div className={cn(stl.screenWrapper, isClickmap && '!overflow-y-scroll')} ref={screenWrapper} /> - </div> - {!fullscreen && !!bottomBlock && ( - <div style={{ maxWidth, width: '100%' }}> - {bottomBlock === OVERVIEW && <OverviewPanel />} - {bottomBlock === CONSOLE && <ConsolePanel />} - {bottomBlock === NETWORK && <NetworkPanel />} - {/* {bottomBlock === STACKEVENTS && <StackEvents />} */} - {bottomBlock === STACKEVENTS && <StackEventPanel />} - {bottomBlock === STORAGE && <Storage />} - {bottomBlock === PROFILER && <ProfilerPanel />} - {bottomBlock === PERFORMANCE && <ConnectedPerformance />} - {bottomBlock === GRAPHQL && <GraphQL />} - {bottomBlock === EXCEPTIONS && <Exceptions />} - {bottomBlock === INSPECTOR && <Inspector />} - </div> - )} - {!fullView && !isMultiview && !isClickmap ? ( - <Controls - speedDown={playerContext.player.speedDown} - speedUp={playerContext.player.speedUp} - jump={playerContext.player.jump} - /> - ) : null} - </div> - ); -} - -export default connect( - (state) => { - const isAssist = window.location.pathname.includes('/assist/'); - return { - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - nextId: state.getIn(['sessions', 'nextId']), - sessionId: state.getIn(['sessions', 'current']).sessionId, - bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), - closedLive: - !!state.getIn(['sessions', 'errors']) || - (isAssist && !state.getIn(['sessions', 'current']).live), - }; - }, - { - fullscreenOff, - updateLastPlayedSession, - } -)(Player); diff --git a/frontend/app/components/Session_/Player/index.js b/frontend/app/components/Session_/Player/index.js deleted file mode 100644 index 2b570d433..000000000 --- a/frontend/app/components/Session_/Player/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Player'; diff --git a/frontend/app/components/Session_/PlayerBlockHeader.tsx b/frontend/app/components/Session_/PlayerBlockHeader.tsx deleted file mode 100644 index cd19712c7..000000000 --- a/frontend/app/components/Session_/PlayerBlockHeader.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { withRouter } from 'react-router-dom'; -import { - sessions as sessionsRoute, - assist as assistRoute, - liveSession as liveSessionRoute, - withSiteId, - multiview, -} from 'App/routes'; -import { BackLink, Link, Icon } from 'UI'; -import { toggleFavorite, setSessionPath } from 'Duck/sessions'; -import cn from 'classnames'; -import SessionMetaList from 'Shared/SessionItem/SessionMetaList'; -import UserCard from './EventsBlock/UserCard'; -import Tabs from 'Components/Session/Tabs'; -import { PlayerContext } from 'App/components/Session/playerContext'; -import { observer } from 'mobx-react-lite'; -import { useStore } from 'App/mstore' -import stl from './playerBlockHeader.module.css'; -import AssistActions from '../Assist/components/AssistActions'; -import AssistTabs from '../Assist/components/AssistTabs'; - -const SESSIONS_ROUTE = sessionsRoute(); -const ASSIST_ROUTE = assistRoute(); - -// TODO props -function PlayerBlockHeader(props: any) { - const [hideBack, setHideBack] = React.useState(false); - const { player, store } = React.useContext(PlayerContext); - const { assistMultiviewStore } = useStore(); - - const { width, height, showEvents } = store.get(); - - const { - session, - fullscreen, - metaList, - closedLive = false, - siteId, - isAssist, - setActiveTab, - activeTab, - location, - history, - sessionPath, - isMultiview, - } = props; - - React.useEffect(() => { - const queryParams = new URLSearchParams(location.search); - setHideBack(queryParams.has('iframe') && queryParams.get('iframe') === 'true'); - }, []); - - const backHandler = () => { - if ( - sessionPath.pathname === history.location.pathname || - sessionPath.pathname.includes('/session/') || - isAssist - ) { - history.push(withSiteId(isAssist ? ASSIST_ROUTE : SESSIONS_ROUTE, siteId)); - } else { - history.push( - sessionPath ? sessionPath.pathname + sessionPath.search : withSiteId(SESSIONS_ROUTE, siteId) - ); - } - }; - - const { sessionId, userId, userNumericHash, live, metadata, isCallActive, agentIds } = session; - let _metaList = Object.keys(metadata) - .filter((i) => metaList.includes(i)) - .map((key) => { - const value = metadata[key]; - return { label: key, value }; - }); - - const TABS = [props.tabs.EVENTS, props.tabs.CLICKMAP].map((tab) => ({ - text: tab, - key: tab, - })); - - const openGrid = () => { - const sessionIdQuery = encodeURIComponent(assistMultiviewStore.sessions.map((s) => s.sessionId).join(',')); - return history.push(withSiteId(multiview(sessionIdQuery), siteId)); - }; - - return ( - <div className={cn(stl.header, 'flex justify-between', { hidden: fullscreen })}> - <div className="flex w-full items-center"> - {!hideBack && ( - <div - className="flex items-center h-full cursor-pointer group" - onClick={() => (assistMultiviewStore.sessions.length > 1 || isMultiview ? openGrid() : backHandler())} - > - {assistMultiviewStore.sessions.length > 1 || isMultiview ? ( - <> - <div className="rounded-full border group-hover:border-teal group-hover:text-teal group-hover:fill-teal p-1 mr-2"> - <Icon name="close" color="inherit" size={13} /> - </div> - <span className="group-hover:text-teal group-hover:fill-teal"> - Close - </span> - <div className={stl.divider} /> - </> - ) : ( - <> - {/* @ts-ignore TODO */} - <BackLink label="Back" className="h-full" /> - <div className={stl.divider} /> - </> - )} - </div> - )} - <UserCard className="" width={width} height={height} /> - {isAssist && <AssistTabs userId={userId} userNumericHash={userNumericHash} />} - - <div className={cn('ml-auto flex items-center h-full', { hidden: closedLive })}> - {live && !isAssist && ( - <> - <div className={cn(stl.liveSwitchButton, 'pr-4')}> - <Link to={withSiteId(liveSessionRoute(sessionId), siteId)}> - This Session is Now Continuing Live - </Link> - </div> - {_metaList.length > 0 && <div className={stl.divider} />} - </> - )} - - {_metaList.length > 0 && ( - <div className="border-l h-full flex items-center px-2"> - <SessionMetaList className="" metaList={_metaList} maxLength={2} /> - </div> - )} - - {isAssist && ( - // @ts-ignore TODO - <AssistActions userId={userId} isCallActive={isCallActive} agentIds={agentIds} /> - )} - </div> - </div> - {!isAssist && ( - <div className="relative border-l" style={{ minWidth: '270px' }}> - <Tabs - tabs={TABS} - active={activeTab} - onClick={(tab) => { - if (activeTab === tab) { - setActiveTab(''); - player.toggleEvents(); - } else { - setActiveTab(tab); - !showEvents && player.toggleEvents(); - } - }} - border={false} - /> - </div> - )} - </div> - ); -} - -const PlayerHeaderCont = connect( - (state: any) => { - const isAssist = window.location.pathname.includes('/assist/'); - const session = state.getIn(['sessions', 'current']); - - return { - isAssist, - session, - sessionPath: state.getIn(['sessions', 'sessionPath']), - local: state.getIn(['sessions', 'timezone']), - funnelRef: state.getIn(['funnels', 'navRef']), - siteId: state.getIn(['site', 'siteId']), - metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key), - closedLive: !!state.getIn(['sessions', 'errors']) || (isAssist && !session.live), - }; - }, - { - toggleFavorite, - setSessionPath, - } -)(observer(PlayerBlockHeader)); - -export default withRouter(PlayerHeaderCont); From 579586b56bf080734eb477edd147a0ed6a251ff6 Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Wed, 11 Jan 2023 16:57:31 +0100 Subject: [PATCH 31/65] change(ui): extract some player components --- .../Session_/Player/Controls/Controls.tsx | 78 ++----- .../Session_/Player/Controls/Time.js | 14 +- .../Session_/Player/Controls/TimeTracker.js | 15 +- .../Controls/components/DraggableCircle.tsx | 6 +- .../Controls/components/PlayerControls.tsx | 34 +-- .../player/components/FullScreenButton.tsx | 25 ++ frontend/app/player/components/PlayButton.tsx | 44 ++++ frontend/app/player/components/PlayTime.tsx | 27 +++ .../app/player/components/ProgressBar.tsx | 31 +++ .../app/player/components/ProgressCircle.tsx | 16 ++ frontend/app/player/components/SkipButton.tsx | 27 +++ frontend/app/player/components/index.tsx | 6 + .../app/player/components/styles.module.css | 213 ++++++++++++++++++ .../src/ScreenRecordingState.ts | 2 +- 14 files changed, 432 insertions(+), 106 deletions(-) create mode 100644 frontend/app/player/components/FullScreenButton.tsx create mode 100644 frontend/app/player/components/PlayButton.tsx create mode 100644 frontend/app/player/components/PlayTime.tsx create mode 100644 frontend/app/player/components/ProgressBar.tsx create mode 100644 frontend/app/player/components/ProgressCircle.tsx create mode 100644 frontend/app/player/components/SkipButton.tsx create mode 100644 frontend/app/player/components/index.tsx create mode 100644 frontend/app/player/components/styles.module.css diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx index f55e54c04..bbf1b5ee8 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.tsx +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -1,22 +1,23 @@ import React from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; -import { STORAGE_TYPES, selectStorageType } from 'Player'; +import { selectStorageType, STORAGE_TYPES } from 'Player'; +import { PlayButton, PlayingState, FullScreenButton } from 'Player/components' import { Icon, Tooltip } from 'UI'; import { - fullscreenOn, - fullscreenOff, - toggleBottomBlock, - OVERVIEW, CONSOLE, - NETWORK, - STACKEVENTS, - STORAGE, - PROFILER, - PERFORMANCE, + fullscreenOff, + fullscreenOn, GRAPHQL, INSPECTOR, + NETWORK, + OVERVIEW, + PERFORMANCE, + PROFILER, + STACKEVENTS, + STORAGE, + toggleBottomBlock, } from 'Duck/components/player'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; @@ -138,53 +139,13 @@ function Controls(props: any) { }; const renderPlayBtn = () => { - let label; - let icon; - if (completed) { - icon = 'arrow-clockwise' as const; - label = 'Replay this session'; - } else if (playing) { - icon = 'pause-fill' as const; - label = 'Pause'; - } else { - icon = 'play-fill-new' as const; - label = 'Pause'; - label = 'Play'; - } + const state = completed ? PlayingState.Completed : playing ? PlayingState.Playing : PlayingState.Paused return ( - <Tooltip title={label} className="mr-4"> - <div - onClick={() => player.togglePlay()} - className="hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade" - > - <Icon name={icon} size="36" color="inherit" /> - </div> - </Tooltip> + <PlayButton state={state} togglePlay={player.togglePlay} iconSize={36} /> ); }; - const controlIcon = ( - icon: string, - size: number, - action: (args: any) => any, - isBackwards: boolean, - additionalClasses: string - ) => ( - <div - onClick={action} - className={cn('py-1 px-2 hover-main cursor-pointer bg-gray-lightest', additionalClasses)} - style={{ transform: isBackwards ? 'rotate(180deg)' : '' }} - > - <Icon - // @ts-ignore - name={icon} - size={size} - color="inherit" - /> - </div> - ); - const toggleBottomTools = (blockName: number) => { if (blockName === INSPECTOR) { // player.toggleInspectorMode(false); @@ -210,7 +171,6 @@ function Controls(props: any) { toggleSpeed={() => player.toggleSpeed()} toggleSkip={() => player.toggleSkip()} playButton={renderPlayBtn()} - controlIcon={controlIcon} skipIntervals={SKIP_INTERVALS} setSkipInterval={changeSkipInterval} currentInterval={skipInterval} @@ -301,13 +261,11 @@ function Controls(props: any) { )} <Tooltip title="Fullscreen" delay={0} placement="top-start" className="mx-4"> - {controlIcon( - 'arrows-angle-extend', - 16, - props.fullscreenOn, - false, - 'rounded hover:bg-gray-light-shade color-gray-medium' - )} + <FullScreenButton + size={16} + onClick={props.fullscreenOn} + customClasses={'rounded hover:bg-gray-light-shade color-gray-medium'} + /> </Tooltip> </div> </div> diff --git a/frontend/app/components/Session_/Player/Controls/Time.js b/frontend/app/components/Session_/Player/Controls/Time.js index 5c2c0da0c..3a8bbfa2f 100644 --- a/frontend/app/components/Session_/Player/Controls/Time.js +++ b/frontend/app/components/Session_/Player/Controls/Time.js @@ -1,25 +1,15 @@ import React from 'react'; -import { Duration } from 'luxon'; -import styles from './time.module.css'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; - -const Time = ({ time, isCustom, format = 'm:ss', }) => ( - <div className={ !isCustom ? styles.time : undefined }> - { Duration.fromMillis(time).toFormat(format) } - </div> -) - -Time.displayName = "Time"; +import { PlayTime } from 'Player/components' const ReduxTime = observer(({ format, name, isCustom }) => { const { store } = React.useContext(PlayerContext) const time = store.get()[name] - return <Time format={format} time={time} isCustom={isCustom} /> + return <PlayTime format={format} time={time} isCustom={isCustom} /> }) ReduxTime.displayName = "ReduxTime"; -export default React.memo(Time); export { ReduxTime }; diff --git a/frontend/app/components/Session_/Player/Controls/TimeTracker.js b/frontend/app/components/Session_/Player/Controls/TimeTracker.js index 00d22d1e3..8e9d782df 100644 --- a/frontend/app/components/Session_/Player/Controls/TimeTracker.js +++ b/frontend/app/components/Session_/Player/Controls/TimeTracker.js @@ -1,20 +1,19 @@ import React from 'react'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; -import styles from './timeTracker.module.css'; -import cn from 'classnames' +import { ProgressBar } from 'Player/components' const TimeTracker = ({ scale, live = false, left }) => { const { store } = React.useContext(PlayerContext) const time = store.get().time return ( - <React.Fragment> - <span - className={ cn(styles.playedTimeline, live && left > 99 ? styles.liveTime : null) } - style={ { width: `${ time * scale }%` } } - /> - </React.Fragment> + <ProgressBar + scale={scale} + live={live} + left={left} + time={time} + /> );} TimeTracker.displayName = 'TimeTracker'; diff --git a/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx b/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx index 1a8c9b3ab..c55cc37bc 100644 --- a/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx @@ -2,7 +2,7 @@ import React, { memo, FC, useEffect, 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' +import { ProgressCircle } from 'Player/components' function getStyles( left: number, @@ -41,7 +41,7 @@ const DraggableCircle: FC<Props> = memo(function DraggableCircle({ live, onDrop, }) { - const [{ isDragging, item }, dragRef, preview] = useDrag( + const [{ isDragging }, dragRef, preview] = useDrag( () => ({ type: ItemTypes.BOX, item: { left }, @@ -64,7 +64,7 @@ const DraggableCircle: FC<Props> = memo(function DraggableCircle({ style={getStyles(left, isDragging)} role="DraggableBox" > - <Circle isGreen={left > 99 && live} /> + <ProgressCircle isGreen={left > 99 && live} /> </div> ); }) diff --git a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx index 65e38bf39..fc6473164 100644 --- a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx @@ -4,6 +4,7 @@ import cn from 'classnames'; import { ReduxTime } from '../Time'; // @ts-ignore import styles from '../controls.module.css'; +import { SkipButton } from 'Player/components' interface Props { skip: boolean; @@ -17,13 +18,6 @@ interface Props { forthTenSeconds: () => void; toggleSpeed: () => void; toggleSkip: () => void; - controlIcon: ( - icon: string, - size: number, - action: () => void, - isBackwards: boolean, - additionalClasses: string - ) => JSX.Element; } function PlayerControls(props: Props) { @@ -39,7 +33,6 @@ function PlayerControls(props: Props) { skipIntervals, setSkipInterval, currentInterval, - controlIcon, } = props; const [showTooltip, setShowTooltip] = React.useState(false); const speedRef = React.useRef<HTMLButtonElement>(null); @@ -98,13 +91,12 @@ function PlayerControls(props: Props) { ref={arrowBackRef} className="h-full bg-transparent" > - {controlIcon( - 'skip-forward-fill', - 18, - backTenSeconds, - true, - 'hover:bg-active-blue-border color-main h-full flex items-center' - )} + <SkipButton + size={18} + onClick={backTenSeconds} + isBackwards={true} + customClasses={'hover:bg-active-blue-border color-main h-full flex items-center'} + /> </button> </Tooltip> @@ -158,13 +150,11 @@ function PlayerControls(props: Props) { ref={arrowForwardRef} className="h-full bg-transparent" > - {controlIcon( - 'skip-forward-fill', - 18, - forthTenSeconds, - false, - 'hover:bg-active-blue-border color-main h-full flex items-center' - )} + <SkipButton + size={18} + onClick={forthTenSeconds} + customClasses={'hover:bg-active-blue-border color-main h-full flex items-center'} + /> </button> </Tooltip> </div> diff --git a/frontend/app/player/components/FullScreenButton.tsx b/frontend/app/player/components/FullScreenButton.tsx new file mode 100644 index 000000000..1f7fd2822 --- /dev/null +++ b/frontend/app/player/components/FullScreenButton.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import { Icon } from 'UI' +import cn from 'classnames' + +interface IProps { + size: number; + onClick: () => void; + customClasses: string; +} + +export function FullScreenButton({ size = 18, onClick, customClasses }: IProps) { + + return ( + <div + onClick={onClick} + className={cn('py-1 px-2 hover-main cursor-pointer bg-gray-lightest', customClasses)} + > + <Icon + name="arrows-angle-extend" + size={size} + color="inherit" + /> + </div> + ) +} \ No newline at end of file diff --git a/frontend/app/player/components/PlayButton.tsx b/frontend/app/player/components/PlayButton.tsx new file mode 100644 index 000000000..1781f481a --- /dev/null +++ b/frontend/app/player/components/PlayButton.tsx @@ -0,0 +1,44 @@ +import React from 'react' +import { Icon, Tooltip } from "UI"; + +export enum PlayingState { + Playing, + Paused, + Completed +} + +interface IProps { + togglePlay: () => void; + iconSize: number; + state: PlayingState; +} + +const Values = { + [PlayingState.Playing]: { + icon: 'pause-fill' as const, + label: 'Pause' + }, + [PlayingState.Completed]: { + icon: 'arrow-clockwise' as const, + label: 'Replay this session', + }, + [PlayingState.Paused]: { + icon: 'play-fill-new' as const, + label: 'Play' + } +} + +export function PlayButton({ togglePlay, iconSize, state }: IProps) { + const { icon, label } = Values[state]; + + return ( + <Tooltip title={label} className="mr-4"> + <div + onClick={togglePlay} + className="hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade" + > + <Icon name={icon} size={iconSize} color="inherit" /> + </div> + </Tooltip> + ) +} \ No newline at end of file diff --git a/frontend/app/player/components/PlayTime.tsx b/frontend/app/player/components/PlayTime.tsx new file mode 100644 index 000000000..7a6260291 --- /dev/null +++ b/frontend/app/player/components/PlayTime.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { Duration } from 'luxon'; + +const styles = { + padding: '0 12px', + width: '70px', + 'text-align': 'center', +} + +interface IProps { + /** current time in ms */ + time: number; + isCustom?: boolean; + format?: string; +} + +/** Play time timer */ +export const PlayTime = ({ time, isCustom, format = 'm:ss', }: IProps) => ( + <div + style={!isCustom ? styles : undefined} + className={!isCustom ? 'color-gray-medium' : undefined} + > + {Duration.fromMillis(time).toFormat(format)} + </div> +) + +PlayTime.displayName = "PlayTime"; \ No newline at end of file diff --git a/frontend/app/player/components/ProgressBar.tsx b/frontend/app/player/components/ProgressBar.tsx new file mode 100644 index 000000000..3f78ebd4f --- /dev/null +++ b/frontend/app/player/components/ProgressBar.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +interface IProps { + scale: number; + live?: boolean; + left: number; + time: number; +} + +const styles = { + display: 'block', + pointerEvents: 'none' as const, + height: '10px', + zIndex: 1, +} +const replayBg = '#d0d4f2' // active blue border +const liveBg = 'rgba(66, 174, 94, 0.3)' // light green shade + +/** Playtime progress bar */ +export const ProgressBar = ({ scale, live = false, left, time }: IProps) => { + return ( + <div + style={{ + ...styles, + width: `${ time * scale }%`, + backgroundColor: live && left > 99 ? liveBg : replayBg + }} + /> + );} + +ProgressBar.displayName = 'ProgressBar'; diff --git a/frontend/app/player/components/ProgressCircle.tsx b/frontend/app/player/components/ProgressCircle.tsx new file mode 100644 index 000000000..6b1945080 --- /dev/null +++ b/frontend/app/player/components/ProgressCircle.tsx @@ -0,0 +1,16 @@ +import React, { memo } from 'react'; +import cn from 'classnames'; +import styles from './styles.module.css'; + +interface IProps { + preview?: boolean; + isGreen?: boolean; +} + +export const ProgressCircle = memo(({ preview, isGreen }: IProps) => ( + <div + className={cn(styles.positionTracker, { [styles.greenTracker]: isGreen })} + role={preview ? 'BoxPreview' : 'Box'} + /> + ) +) \ No newline at end of file diff --git a/frontend/app/player/components/SkipButton.tsx b/frontend/app/player/components/SkipButton.tsx new file mode 100644 index 000000000..1206bc421 --- /dev/null +++ b/frontend/app/player/components/SkipButton.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { Icon } from 'UI' +import cn from 'classnames' + +interface IProps { + size: number; + onClick: () => void; + isBackwards?: boolean; + customClasses: string; +} + +export function SkipButton({ size = 18, onClick, isBackwards, customClasses }: IProps) { + + return ( + <div + onClick={onClick} + className={cn('py-1 px-2 hover-main cursor-pointer bg-gray-lightest', customClasses)} + style={{ transform: isBackwards ? 'rotate(180deg)' : '' }} + > + <Icon + name="skip-forward-fill" + size={size} + color="inherit" + /> + </div> + ) +} \ No newline at end of file diff --git a/frontend/app/player/components/index.tsx b/frontend/app/player/components/index.tsx new file mode 100644 index 000000000..0a79c4aff --- /dev/null +++ b/frontend/app/player/components/index.tsx @@ -0,0 +1,6 @@ +export { PlayButton, PlayingState } from './PlayButton' +export { SkipButton } from './SkipButton' +export { FullScreenButton } from './FullScreenButton' +export { PlayTime } from './PlayTime' +export { ProgressBar } from './ProgressBar' +export { ProgressCircle } from './ProgressCircle' \ No newline at end of file diff --git a/frontend/app/player/components/styles.module.css b/frontend/app/player/components/styles.module.css new file mode 100644 index 000000000..fed6e8044 --- /dev/null +++ b/frontend/app/player/components/styles.module.css @@ -0,0 +1,213 @@ +.positionTracker { + width: 15px; + height: 15px; + box-shadow: 0 0 0 1px #2331A8; + margin-left: -7px; + border-radius: 50%; + background-color: $main; + position: absolute; + left: 0; + z-index: 98; + top: 3px; + transition: all 0.2s ease-out; +&:hover, +&:focus { + transition: all 0.1s ease-in; + width: 20px; + height: 20px; + top: 1px; + left: -2px; + } + +} + +.greenTracker { + background-color: #42AE5E!important; + box-shadow: 0 0 0 1px #42AE5E; +} + +.progress { + height: 10px; + padding: 8px 0; + cursor: pointer; + width: 100%; + max-width: 100%; + position: relative; + display: flex; + align-items: center; + +} + + +.skipInterval { + position: absolute; + top: 3px; + height: 10px; + bottom: 0; + background: repeating-linear-gradient( 125deg, #efefef, #efefef 3px, #ddd 3px, #efefef 5px ); + pointer-events: none; + z-index: 2; +} + + +.event { + position: absolute; + width: 2px; + height: 10px; + background: $main; + z-index: 3; + pointer-events: none; + /* top: 0; */ + /* bottom: 0; */ + /* &:hover { + width: 10px; + height: 10px; + margin-left: -6px; + z-index: 1; + };*/ +} + +/* .event.click, .event.input { + background: $green; +} +.event.location { + background: $blue; +} */ +.redEvent { + position: absolute; + width: 2px; + height: 10px; + background: $red; + z-index: 3; + pointer-events: none; + /* top: 0; */ + /* bottom: 0; */ + /* &:hover { + width: 10px; + height: 10px; + margin-left: -6px; + z-index: 1; + };*/ +} + +.markup { + position: absolute; + width: 2px; + height: 8px; + margin-left: -8px; +&:hover { + z-index: 9999; + } +} + +/* .markup.log { + background: $blue; +} + +.markup.error { + background: $red; +} + +.markup.warning { + background: $orange; +} */ + +.markup.info { + background: $blue2; +} + +.popup { + max-width: 300px !important; + /* max-height: 300px !important; */ + overflow: hidden; + text-overflow: ellipsis; +& span { + display: block; + max-height: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +.timeline { + overflow: hidden; + position: absolute; + left: 0; + right: 0; + height: 10px; + border: 1px solid $gray-light; + display: flex; + align-items: center; +} + +.clickRage { + position: absolute; + width: 2px; + height: 8px; + margin-left: -1px; + /* background: $red; */ +} + +.returningLocation { + position: absolute; + height: 20%; + border-radius: 50%; + /* background: $red; */ + width: 12px; +} + +.feedbackIcon { + position: absolute; + margin-top: -20px; + margin-left: -9px; + background-color: $gray-lightest; + padding: 2px; + box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.1); + +& .tooltipArrow { + width: 50px; + height: 25px; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + overflow: hidden; +&::after { + content: ""; + position: absolute; + width: 6px; + height: 6px; + background: $gray-lightest; + transform: translateX(-50%) translateY(50%) rotate(45deg); + bottom: 100%; + left: 50%; + box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.1); + } +} +} + +.timeTooltip { + position: absolute; + padding: 0.25rem; + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + background: black; + top: -35px; + color: white; + +&:after { + content:''; + position: absolute; + top: 100%; + left: 0; + right: 0; + margin: 0 auto; + width: 0; + height: 0; + border-top: solid 5px black; + border-left: solid 5px transparent; + border-right: solid 5px transparent; + } +} diff --git a/tracker/tracker-assist/src/ScreenRecordingState.ts b/tracker/tracker-assist/src/ScreenRecordingState.ts index 4a05c0f4d..4667590a8 100644 --- a/tracker/tracker-assist/src/ScreenRecordingState.ts +++ b/tracker/tracker-assist/src/ScreenRecordingState.ts @@ -15,7 +15,7 @@ const borderStyles = { left: 0, top: 0, position: 'fixed', - 'pointer-events': 'none', + pointerEvents: 'none', } const buttonStyles = { From 309b96f2da05628d1de4f70e79149a69424e710b Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Fri, 13 Jan 2023 10:39:41 +0100 Subject: [PATCH 32/65] change(ui): extract clickmap renderer --- .../ClickMapCard/ClickMapCard.tsx | 7 +- .../Player/ClickMapRenderer/Renderer.tsx | 38 +++++++++++ .../Player/ClickMapRenderer/ThinPlayer.tsx | 64 +++++++++++++++++++ .../ClickMapRenderer/ThinPlayerContent.tsx | 30 +++++++++ .../Session/Player/ClickMapRenderer/index.ts | 1 + .../components/Session_/Player/Overlay.tsx | 2 +- .../app/player/components/ProgressBar.tsx | 5 +- frontend/app/player/player/Animator.ts | 2 +- frontend/app/player/web/MessageManager.ts | 1 - 9 files changed, 141 insertions(+), 9 deletions(-) create mode 100644 frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx create mode 100644 frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayer.tsx create mode 100644 frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayerContent.tsx create mode 100644 frontend/app/components/Session/Player/ClickMapRenderer/index.ts diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx index 775782804..f18e294a8 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx @@ -1,7 +1,7 @@ import React from 'react' import { useStore } from 'App/mstore' import { observer } from 'mobx-react-lite' -import WebPlayer from 'App/components/Session/WebPlayer' +import ClickMapRenderer from 'App/components/Session/Player/ClickMapRenderer' import { connect } from 'react-redux' import { setCustomSession } from 'App/duck/sessions' import { fetchInsights } from 'Duck/sessions'; @@ -54,10 +54,9 @@ function ClickMapCard({ return ( <div id="clickmap-render"> - <WebPlayer - isClickmap + <ClickMapRenderer customSession={metricStore.instance.data} - customTimestamp={jumpTimestamp} + jumpTimestamp={jumpTimestamp} onMarkerClick={onMarkerClick} /> </div> diff --git a/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx b/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx new file mode 100644 index 000000000..869fe6cda --- /dev/null +++ b/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { findDOMNode } from 'react-dom'; +import cn from 'classnames'; +import Overlay from 'Components/Session_/Player/Overlay'; +import stl from 'Components/Session_/Player/player.module.css'; +import { PlayerContext } from 'App/components/Session/playerContext'; + +function Player() { + const playerContext = React.useContext(PlayerContext); + const screenWrapper = React.useRef<HTMLDivElement>(null); + + React.useEffect(() => { + const parentElement = findDOMNode(screenWrapper.current) as HTMLDivElement | null; //TODO: good architecture + if (parentElement) { + playerContext.player.attach(parentElement); + playerContext.player.play(); + } + }, []); + + React.useEffect(() => { + playerContext.player.scale(); + }, [playerContext.player]); + + if (!playerContext.player) return null; + + return ( + <div + className={cn(stl.playerBody, 'flex-1 flex flex-col relative')} + > + <div className={cn("relative flex-1", 'overflow-visible')}> + <Overlay isClickmap /> + <div className={cn(stl.screenWrapper, '!overflow-y-scroll')} ref={screenWrapper} /> + </div> + </div> + ); +} + +export default Player; diff --git a/frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayer.tsx b/frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayer.tsx new file mode 100644 index 000000000..58ad80535 --- /dev/null +++ b/frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayer.tsx @@ -0,0 +1,64 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { createWebPlayer } from 'Player'; +import { makeAutoObservable } from 'mobx'; +import withLocationHandlers from 'HOCs/withLocationHandlers'; +import PlayerContent from './ThinPlayerContent'; +import { IPlayerContext, PlayerContext, defaultContextValue } from '../../playerContext'; +import { observer } from 'mobx-react-lite'; + + +function WebPlayer(props: any) { + const { + session, + customSession, + insights, + jumpTimestamp, + onMarkerClick, + } = props; + // @ts-ignore + const [contextValue, setContextValue] = useState<IPlayerContext>(defaultContextValue); + + useEffect(() => { + const [WebPlayerInst, PlayerStore] = createWebPlayer(customSession, (state) => + makeAutoObservable(state) + ); + setContextValue({ player: WebPlayerInst, store: PlayerStore }); + WebPlayerInst.setMarkerClick(onMarkerClick) + + return () => WebPlayerInst.clean(); + }, [session.sessionId]); + + const isPlayerReady = contextValue.store?.get().ready + + React.useEffect(() => { + contextValue.player && contextValue.player.play() + if (isPlayerReady && insights.size > 0) { + setTimeout(() => { + contextValue.player.jump(jumpTimestamp) + contextValue.player.pause() + contextValue.player.scaleFullPage() + setTimeout(() => { contextValue.player.showClickmap(insights) }, 250) + }, 500) + } + return () => { + isPlayerReady && contextValue.player.showClickmap(null) + } + }, [insights, isPlayerReady, jumpTimestamp]) + + if (!contextValue.player || !session) return null; + + return ( + <PlayerContext.Provider value={contextValue}> + <PlayerContent /> + </PlayerContext.Provider> + ); +} + +export default connect( + (state: any) => ({ + session: state.getIn(['sessions', 'current']), + insights: state.getIn(['sessions', 'insights']), + jwt: state.getIn(['user', 'jwt']), + }) +)(withLocationHandlers()(observer(WebPlayer))); diff --git a/frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayerContent.tsx b/frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayerContent.tsx new file mode 100644 index 000000000..c6a9b1eae --- /dev/null +++ b/frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayerContent.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import cn from 'classnames'; +import styles from 'Components/Session_/session.module.css'; +import Renderer from './Renderer'; + +function PlayerContent() { + + return ( + <div className="relative"> + <div className={'flex'}> + <div + className="w-full" + > + <div className={cn(styles.session, 'relative')}> + <div + className={cn(styles.playerBlock, 'flex flex-col', 'overflow-visible')} + style={{ zIndex: 1, minWidth: '100%' }} + > + <Renderer /> + </div> + </div> + </div> + </div> + </div> + ); +} + + +export default observer(PlayerContent); diff --git a/frontend/app/components/Session/Player/ClickMapRenderer/index.ts b/frontend/app/components/Session/Player/ClickMapRenderer/index.ts new file mode 100644 index 000000000..23e0a0566 --- /dev/null +++ b/frontend/app/components/Session/Player/ClickMapRenderer/index.ts @@ -0,0 +1 @@ +export { default } from './ThinPlayer' \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Overlay.tsx b/frontend/app/components/Session_/Player/Overlay.tsx index 8a94ebceb..ff0344757 100644 --- a/frontend/app/components/Session_/Player/Overlay.tsx +++ b/frontend/app/components/Session_/Player/Overlay.tsx @@ -7,7 +7,7 @@ import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; interface Props { - nextId: string, + nextId?: string, closedLive?: boolean, isClickmap?: boolean, } diff --git a/frontend/app/player/components/ProgressBar.tsx b/frontend/app/player/components/ProgressBar.tsx index 3f78ebd4f..362b5563f 100644 --- a/frontend/app/player/components/ProgressBar.tsx +++ b/frontend/app/player/components/ProgressBar.tsx @@ -17,7 +17,7 @@ const replayBg = '#d0d4f2' // active blue border const liveBg = 'rgba(66, 174, 94, 0.3)' // light green shade /** Playtime progress bar */ -export const ProgressBar = ({ scale, live = false, left, time }: IProps) => { +export function ProgressBar ({ scale, live = false, left, time }: IProps) { return ( <div style={{ @@ -26,6 +26,7 @@ export const ProgressBar = ({ scale, live = false, left, time }: IProps) => { backgroundColor: live && left > 99 ? liveBg : replayBg }} /> - );} + ) +} ProgressBar.displayName = 'ProgressBar'; diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts index 265e4a422..8095bd1af 100644 --- a/frontend/app/player/player/Animator.ts +++ b/frontend/app/player/player/Animator.ts @@ -139,7 +139,7 @@ export default class Animator { this.store.update({ playing: false }) } - togglePlay() { + togglePlay = () => { const { playing, completed } = this.store.get() if (playing) { this.pause() diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index 6c5d3f0eb..829327484 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -400,7 +400,6 @@ export default class MessageManager { break; case MType.Fetch: case MType.NetworkRequest: - // @ts-ignore burn immutable this.lists.lists.fetch.insert(new Resource({ method: msg.method, url: msg.url, From ef65998938caf38798568a07d2a4b3ccc20d2db7 Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Fri, 13 Jan 2023 17:09:35 +0100 Subject: [PATCH 33/65] change(ui): some small fixes --- .../Player/LivePlayer/AssistDuration.tsx | 2 +- .../Player/LivePlayer/LiveControls.tsx | 2 +- .../LivePlayer}/LiveTag/LiveTag.module.css | 0 .../Player/LivePlayer}/LiveTag/LiveTag.tsx | 0 .../Player/LivePlayer}/LiveTag/index.js | 0 .../app/player/components/styles.module.css | 37 ------------------- 6 files changed, 2 insertions(+), 39 deletions(-) rename frontend/app/components/{shared => Session/Player/LivePlayer}/LiveTag/LiveTag.module.css (100%) rename frontend/app/components/{shared => Session/Player/LivePlayer}/LiveTag/LiveTag.tsx (100%) rename frontend/app/components/{shared => Session/Player/LivePlayer}/LiveTag/index.js (100%) diff --git a/frontend/app/components/Session/Player/LivePlayer/AssistDuration.tsx b/frontend/app/components/Session/Player/LivePlayer/AssistDuration.tsx index e6af38b25..226017722 100644 --- a/frontend/app/components/Session/Player/LivePlayer/AssistDuration.tsx +++ b/frontend/app/components/Session/Player/LivePlayer/AssistDuration.tsx @@ -13,7 +13,7 @@ const AssistDurationCont = () => { const interval = setInterval(() => { setAssistDuration(Duration.fromMillis(+new Date() - assistStart).toFormat('mm:ss')); } - , 1000); + , 500); return () => clearInterval(interval); }, []) return ( diff --git a/frontend/app/components/Session/Player/LivePlayer/LiveControls.tsx b/frontend/app/components/Session/Player/LivePlayer/LiveControls.tsx index ac1e935ba..c17902380 100644 --- a/frontend/app/components/Session/Player/LivePlayer/LiveControls.tsx +++ b/frontend/app/components/Session/Player/LivePlayer/LiveControls.tsx @@ -1,7 +1,7 @@ import React from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; -import LiveTag from 'Shared/LiveTag'; +import LiveTag from './LiveTag'; import AssistSessionsTabs from './AssistSessionsTabs'; import { diff --git a/frontend/app/components/shared/LiveTag/LiveTag.module.css b/frontend/app/components/Session/Player/LivePlayer/LiveTag/LiveTag.module.css similarity index 100% rename from frontend/app/components/shared/LiveTag/LiveTag.module.css rename to frontend/app/components/Session/Player/LivePlayer/LiveTag/LiveTag.module.css diff --git a/frontend/app/components/shared/LiveTag/LiveTag.tsx b/frontend/app/components/Session/Player/LivePlayer/LiveTag/LiveTag.tsx similarity index 100% rename from frontend/app/components/shared/LiveTag/LiveTag.tsx rename to frontend/app/components/Session/Player/LivePlayer/LiveTag/LiveTag.tsx diff --git a/frontend/app/components/shared/LiveTag/index.js b/frontend/app/components/Session/Player/LivePlayer/LiveTag/index.js similarity index 100% rename from frontend/app/components/shared/LiveTag/index.js rename to frontend/app/components/Session/Player/LivePlayer/LiveTag/index.js diff --git a/frontend/app/player/components/styles.module.css b/frontend/app/player/components/styles.module.css index fed6e8044..611cbf756 100644 --- a/frontend/app/player/components/styles.module.css +++ b/frontend/app/player/components/styles.module.css @@ -57,22 +57,8 @@ background: $main; z-index: 3; pointer-events: none; - /* top: 0; */ - /* bottom: 0; */ - /* &:hover { - width: 10px; - height: 10px; - margin-left: -6px; - z-index: 1; - };*/ } -/* .event.click, .event.input { - background: $green; -} -.event.location { - background: $blue; -} */ .redEvent { position: absolute; width: 2px; @@ -80,14 +66,6 @@ background: $red; z-index: 3; pointer-events: none; - /* top: 0; */ - /* bottom: 0; */ - /* &:hover { - width: 10px; - height: 10px; - margin-left: -6px; - z-index: 1; - };*/ } .markup { @@ -100,25 +78,12 @@ } } -/* .markup.log { - background: $blue; -} - -.markup.error { - background: $red; -} - -.markup.warning { - background: $orange; -} */ - .markup.info { background: $blue2; } .popup { max-width: 300px !important; - /* max-height: 300px !important; */ overflow: hidden; text-overflow: ellipsis; & span { @@ -146,14 +111,12 @@ width: 2px; height: 8px; margin-left: -1px; - /* background: $red; */ } .returningLocation { position: absolute; height: 20%; border-radius: 50%; - /* background: $red; */ width: 12px; } From 5c4247885f489ac0bdf8edb25ff91efe4e0ec2b7 Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Fri, 13 Jan 2023 17:26:13 +0100 Subject: [PATCH 34/65] change(ui): move components to new fold --- frontend/app/components/Session_/Player/Controls/Controls.tsx | 2 +- frontend/app/components/Session_/Player/Controls/Time.js | 2 +- frontend/app/components/Session_/Player/Controls/TimeTracker.js | 2 +- .../Session_/Player/Controls/components/DraggableCircle.tsx | 2 +- .../Session_/Player/Controls/components/PlayerControls.tsx | 2 +- .../app/{player/components => player-ui}/FullScreenButton.tsx | 0 frontend/app/{player/components => player-ui}/PlayButton.tsx | 0 frontend/app/{player/components => player-ui}/PlayTime.tsx | 0 frontend/app/{player/components => player-ui}/ProgressBar.tsx | 0 .../app/{player/components => player-ui}/ProgressCircle.tsx | 0 frontend/app/{player/components => player-ui}/SkipButton.tsx | 0 frontend/app/{player/components => player-ui}/index.tsx | 0 frontend/app/{player/components => player-ui}/styles.module.css | 0 13 files changed, 5 insertions(+), 5 deletions(-) rename frontend/app/{player/components => player-ui}/FullScreenButton.tsx (100%) rename frontend/app/{player/components => player-ui}/PlayButton.tsx (100%) rename frontend/app/{player/components => player-ui}/PlayTime.tsx (100%) rename frontend/app/{player/components => player-ui}/ProgressBar.tsx (100%) rename frontend/app/{player/components => player-ui}/ProgressCircle.tsx (100%) rename frontend/app/{player/components => player-ui}/SkipButton.tsx (100%) rename frontend/app/{player/components => player-ui}/index.tsx (100%) rename frontend/app/{player/components => player-ui}/styles.module.css (100%) diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx index bbf1b5ee8..98061c537 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.tsx +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -2,7 +2,7 @@ import React from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; import { selectStorageType, STORAGE_TYPES } from 'Player'; -import { PlayButton, PlayingState, FullScreenButton } from 'Player/components' +import { PlayButton, PlayingState, FullScreenButton } from 'App/player-ui' import { Icon, Tooltip } from 'UI'; import { diff --git a/frontend/app/components/Session_/Player/Controls/Time.js b/frontend/app/components/Session_/Player/Controls/Time.js index 3a8bbfa2f..85291009f 100644 --- a/frontend/app/components/Session_/Player/Controls/Time.js +++ b/frontend/app/components/Session_/Player/Controls/Time.js @@ -1,7 +1,7 @@ import React from 'react'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; -import { PlayTime } from 'Player/components' +import { PlayTime } from 'App/player-ui' const ReduxTime = observer(({ format, name, isCustom }) => { const { store } = React.useContext(PlayerContext) diff --git a/frontend/app/components/Session_/Player/Controls/TimeTracker.js b/frontend/app/components/Session_/Player/Controls/TimeTracker.js index 8e9d782df..4775ce147 100644 --- a/frontend/app/components/Session_/Player/Controls/TimeTracker.js +++ b/frontend/app/components/Session_/Player/Controls/TimeTracker.js @@ -1,7 +1,7 @@ import React from 'react'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; -import { ProgressBar } from 'Player/components' +import { ProgressBar } from 'App/player-ui' const TimeTracker = ({ scale, live = false, left }) => { const { store } = React.useContext(PlayerContext) diff --git a/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx b/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx index c55cc37bc..91e5d68c7 100644 --- a/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx @@ -2,7 +2,7 @@ import React, { memo, FC, useEffect, CSSProperties } from 'react'; import type { DragSourceMonitor } from 'react-dnd' import { useDrag } from 'react-dnd' import { getEmptyImage } from 'react-dnd-html5-backend' -import { ProgressCircle } from 'Player/components' +import { ProgressCircle } from 'App/player-ui' function getStyles( left: number, diff --git a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx index fc6473164..a1562ac4d 100644 --- a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx @@ -4,7 +4,7 @@ import cn from 'classnames'; import { ReduxTime } from '../Time'; // @ts-ignore import styles from '../controls.module.css'; -import { SkipButton } from 'Player/components' +import { SkipButton } from 'App/player-ui' interface Props { skip: boolean; diff --git a/frontend/app/player/components/FullScreenButton.tsx b/frontend/app/player-ui/FullScreenButton.tsx similarity index 100% rename from frontend/app/player/components/FullScreenButton.tsx rename to frontend/app/player-ui/FullScreenButton.tsx diff --git a/frontend/app/player/components/PlayButton.tsx b/frontend/app/player-ui/PlayButton.tsx similarity index 100% rename from frontend/app/player/components/PlayButton.tsx rename to frontend/app/player-ui/PlayButton.tsx diff --git a/frontend/app/player/components/PlayTime.tsx b/frontend/app/player-ui/PlayTime.tsx similarity index 100% rename from frontend/app/player/components/PlayTime.tsx rename to frontend/app/player-ui/PlayTime.tsx diff --git a/frontend/app/player/components/ProgressBar.tsx b/frontend/app/player-ui/ProgressBar.tsx similarity index 100% rename from frontend/app/player/components/ProgressBar.tsx rename to frontend/app/player-ui/ProgressBar.tsx diff --git a/frontend/app/player/components/ProgressCircle.tsx b/frontend/app/player-ui/ProgressCircle.tsx similarity index 100% rename from frontend/app/player/components/ProgressCircle.tsx rename to frontend/app/player-ui/ProgressCircle.tsx diff --git a/frontend/app/player/components/SkipButton.tsx b/frontend/app/player-ui/SkipButton.tsx similarity index 100% rename from frontend/app/player/components/SkipButton.tsx rename to frontend/app/player-ui/SkipButton.tsx diff --git a/frontend/app/player/components/index.tsx b/frontend/app/player-ui/index.tsx similarity index 100% rename from frontend/app/player/components/index.tsx rename to frontend/app/player-ui/index.tsx diff --git a/frontend/app/player/components/styles.module.css b/frontend/app/player-ui/styles.module.css similarity index 100% rename from frontend/app/player/components/styles.module.css rename to frontend/app/player-ui/styles.module.css From 5895a537c6118d58a69e909ab7f50d04930de17a Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Mon, 16 Jan 2023 10:35:54 +0100 Subject: [PATCH 35/65] change(ui) - session search - maintain the date on project change --- frontend/app/duck/search.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index 715bddcfb..9d26e19fe 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -318,9 +318,17 @@ export function fetchFilterSearch(params) { } export const clearSearch = () => (dispatch, getState) => { - // const filter = getState().getIn(['search', 'instance']); - // dispatch(applySavedSearch(new SavedFilter({}))); - dispatch(edit(new Filter({ filters: [] }))); + const instance = getState().getIn(['search', 'instance']); + dispatch( + edit( + new Filter({ + rangeValue: instance.rangeValue, + startDate: instance.startDate, + endDate: instance.endDate, + filters: [], + }) + ) + ); return dispatch({ type: CLEAR_SEARCH, }); From c12536c0849fcd84a026c058414d0d45ed31213e Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Mon, 16 Jan 2023 11:49:12 +0100 Subject: [PATCH 36/65] change(ui) - project id check on login --- frontend/app/duck/site.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/app/duck/site.js b/frontend/app/duck/site.js index 4ce55bba5..967429806 100644 --- a/frontend/app/duck/site.js +++ b/frontend/app/duck/site.js @@ -61,11 +61,12 @@ const reducer = (state = initialState, action = {}) => { return state.setIn([ 'instance', 'gdpr' ], gdpr); case FETCH_LIST_SUCCESS: let siteId = state.get("siteId"); - const siteExists = action.data.map(s => s.projectId).includes(siteId); - if (action.siteIdFromPath) { + const siteIds = action.data.map(s => parseInt(s.projectId)) + const siteExists = siteIds.includes(siteId); + if (action.siteIdFromPath && siteIds.includes(parseInt(action.siteIdFromPath))) { siteId = action.siteIdFromPath; } else if (!siteId || !siteExists) { - siteId = !!action.data.find(s => s.projectId === parseInt(storedSiteId)) + siteId = siteIds.includes(parseInt(storedSiteId)) ? storedSiteId : action.data[0].projectId; } From c812f2faedf82ffb79b1efb3fc53ca118132b228 Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Mon, 16 Jan 2023 11:50:33 +0100 Subject: [PATCH 37/65] change(ui): add loader bar on timeline, fix metadata on session load, add exceptions archive for logger --- .../Player/ReplayPlayer/PlayerBlockHeader.tsx | 5 +++++ .../Player/ReplayPlayer/PlayerInst.tsx | 9 ++++++-- .../Session_/Player/Controls/Timeline.tsx | 5 ++++- .../Player/Controls/timeline.module.css | 21 +++++++++++++++++++ frontend/app/dev/console.js | 1 + frontend/app/logger/index.js | 4 ++++ frontend/app/player/player/Animator.ts | 12 ++++++++--- frontend/app/player/web/MessageManager.ts | 13 ++++++------ .../app/player/web/messages/MFileReader.ts | 1 + 9 files changed, 58 insertions(+), 13 deletions(-) diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx index 8dd4a41a0..899593eb7 100644 --- a/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx @@ -15,6 +15,7 @@ import Tabs from 'Components/Session/Tabs'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import stl from './playerBlockHeader.module.css'; +import { fetchListActive as fetchMetadata } from 'Duck/customField'; const SESSIONS_ROUTE = sessionsRoute(); @@ -36,11 +37,14 @@ function PlayerBlockHeader(props: any) { location, history, sessionPath, + fetchMetadata, } = props; React.useEffect(() => { const queryParams = new URLSearchParams(location.search); setHideBack(queryParams.has('iframe') && queryParams.get('iframe') === 'true'); + + if (metaList.size === 0) fetchMetadata(); }, []); const backHandler = () => { @@ -139,6 +143,7 @@ const PlayerHeaderCont = connect( { toggleFavorite, setSessionPath, + fetchMetadata, } )(observer(PlayerBlockHeader)); diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx index faa2b3c48..cac7e87ec 100644 --- a/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx @@ -58,17 +58,22 @@ function Player(props: IProps) { isClickmap, } = props; const playerContext = React.useContext(PlayerContext); + const isReady = playerContext.store.get().ready const screenWrapper = React.useRef<HTMLDivElement>(null); const bottomBlockIsActive = !fullscreen && bottomBlock !== NONE; + const [isAttached, setAttached] = React.useState(false); React.useEffect(() => { props.updateLastPlayedSession(props.sessionId); const parentElement = findDOMNode(screenWrapper.current) as HTMLDivElement | null; //TODO: good architecture - if (parentElement) { + if (parentElement && !isAttached) { playerContext.player.attach(parentElement); + setAttached(true) + } + if (isAttached && isReady) { playerContext.player.play(); } - }, []); + }, [isReady]); React.useEffect(() => { playerContext.player.scale(); diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.tsx b/frontend/app/components/Session_/Player/Controls/Timeline.tsx index fea596066..ffffde0dd 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.tsx +++ b/frontend/app/components/Session_/Player/Controls/Timeline.tsx @@ -40,6 +40,7 @@ function Timeline(props: IProps) { skipToIssue, ready, endTime, + devtoolsLoading, } = store.get() const { issues } = props; const notes = notesStore.sessionNotes @@ -162,7 +163,9 @@ function Timeline(props: IProps) { }} /> )) : null} - <div className={stl.timeline} ref={timelineRef} /> + <div className={stl.timeline} ref={timelineRef}> + {devtoolsLoading || !ready ? <div className={stl.stripes} /> : null} + </div> {events.map((e) => ( <div diff --git a/frontend/app/components/Session_/Player/Controls/timeline.module.css b/frontend/app/components/Session_/Player/Controls/timeline.module.css index c935d4fd4..4c84176b5 100644 --- a/frontend/app/components/Session_/Player/Controls/timeline.module.css +++ b/frontend/app/components/Session_/Player/Controls/timeline.module.css @@ -141,6 +141,8 @@ align-items: center; } + + .clickRage { position: absolute; width: 2px; @@ -211,3 +213,22 @@ border-right: solid 5px transparent; } } + + +.stripes { + background-size: 30px 30px; + width: 100%; + height: 100%; + background-image: linear-gradient(135deg, rgba(0, 0, 0, 0.15) 25%, transparent 25%, transparent 50%, rgba(0, 0, 0, 0.15) 50%, rgba(0, 0, 0, 0.15) 75%, transparent 75%, transparent); + animation: animate-stripes 1.5s linear infinite; + animation-direction: reverse; +} + +@keyframes animate-stripes { + 0% { + background-position: 0 0; + } + 100% { + background-position: 60px 0; + } +} \ No newline at end of file diff --git a/frontend/app/dev/console.js b/frontend/app/dev/console.js index 57fd400ca..9c571e8fd 100644 --- a/frontend/app/dev/console.js +++ b/frontend/app/dev/console.js @@ -7,6 +7,7 @@ export const options = { }, enableCrash: false, verbose: false, + exceptionsLogs: [] } const storedString = localStorage.getItem(KEY) diff --git a/frontend/app/logger/index.js b/frontend/app/logger/index.js index 8b6d7168f..fb8687dd7 100644 --- a/frontend/app/logger/index.js +++ b/frontend/app/logger/index.js @@ -13,11 +13,13 @@ function warn(...args) { if (!window.env.PRODUCTION || options.verbose) { console.warn(...args); } + options.exceptionsLogs.push(args) } function error(...args) { if (!window.env.PRODUCTION || options.verbose) { console.error(...args); + options.exceptionsLogs.push(args) } } @@ -33,6 +35,8 @@ function group(groupName, ...args) { console.groupCollapsed(groupName); } console.log(...args); + + options.exceptionsLogs.push(args) } } diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts index 8095bd1af..a5c399c69 100644 --- a/frontend/app/player/player/Animator.ts +++ b/frontend/app/player/player/Animator.ts @@ -129,9 +129,15 @@ export default class Animator { } play() { - cancelAnimationFrame(this.animationFrameRequestId) - this.store.update({ playing: true }) - this.startAnimation() + if (!this.store.get().ready) { + cancelAnimationFrame(this.animationFrameRequestId) + this.store.update({ playing: true }) + this.startAnimation() + } else { + setTimeout(() => { + this.play() + }, 250) + } } pause() { diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index 829327484..c6cf403c4 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -88,9 +88,7 @@ export default class MessageManager { messagesLoading: false, cssLoading: false, - get ready() { - return !this.messagesLoading && !this.cssLoading - }, + ready: false, lastMessageTime: 0, } @@ -125,7 +123,7 @@ export default class MessageManager { ) { this.pagesManager = new PagesManager(screen, this.session.isMobile, cssLoading => { screen.displayFrame(!cssLoading) - state.update({ cssLoading }) + state.update({ cssLoading, ready: !state.get().messagesLoading && !cssLoading }) }) this.mouseMoveManager = new MouseMoveManager(screen) @@ -191,11 +189,12 @@ export default class MessageManager { } private onFileReadFinally = () => { this.waitingForFiles = false - this.setMessagesLoading(false) + // this.setMessagesLoading(false) // this.state.update({ filesLoaded: true }) } private async loadMessages() { + this.setMessagesLoading(true) // TODO: reuseable decryptor instance const createNewParser = (shouldDecrypt=true) => { const decrypt = shouldDecrypt && this.session.fileKey @@ -216,7 +215,7 @@ export default class MessageManager { this.setMessagesLoading(false) }) } - this.setMessagesLoading(true) + this.waitingForFiles = true let fileReadPromise = this.session.domURL && this.session.domURL.length > 0 @@ -482,7 +481,7 @@ export default class MessageManager { setMessagesLoading(messagesLoading: boolean) { this.screen.display(!messagesLoading); - this.state.update({ messagesLoading }); + this.state.update({ messagesLoading, ready: !messagesLoading && !this.state.get().cssLoading }); } private setSize({ height, width }: { height: number, width: number }) { diff --git a/frontend/app/player/web/messages/MFileReader.ts b/frontend/app/player/web/messages/MFileReader.ts index 8c753979c..d1b131595 100644 --- a/frontend/app/player/web/messages/MFileReader.ts +++ b/frontend/app/player/web/messages/MFileReader.ts @@ -60,6 +60,7 @@ export default class MFileReader extends RawMessageReader { return null } this.logger.group("Openreplay: Skipping messages ", skippedMessage) + } this.pLastMessageID = this.p From 7df87f1c9b11476e82be9df170a8584c00991444 Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Mon, 16 Jan 2023 12:23:23 +0100 Subject: [PATCH 38/65] change(ui): clear exc log on each session --- .../app/components/Session/LiveSession.js | 94 ++++++++++--------- frontend/app/components/Session/Session.js | 2 + frontend/app/dev/console.js | 6 +- 3 files changed, 55 insertions(+), 47 deletions(-) diff --git a/frontend/app/components/Session/LiveSession.js b/frontend/app/components/Session/LiveSession.js index 19b7d8614..8f898b787 100644 --- a/frontend/app/components/Session/LiveSession.js +++ b/frontend/app/components/Session/LiveSession.js @@ -7,61 +7,63 @@ import { fetchList as fetchSlackList } from 'Duck/integrations/slack'; import { Loader } from 'UI'; import withPermissions from 'HOCs/withPermissions'; import LivePlayer from './LivePlayer'; +import { clearLogs } from 'App/dev/console'; function LiveSession({ - sessionId, - loading, - fetchSession, - fetchSlackList, - hasSessionsPath, + sessionId, + loading, + fetchSession, + fetchSlackList, + hasSessionsPath, }) { - usePageTitle('OpenReplay Assist'); + usePageTitle('OpenReplay Assist'); - useEffect(() => { - fetchSlackList(); - }, []); + useEffect(() => { + clearLogs(); + fetchSlackList(); + }, []); - useEffect(() => { - if (sessionId != null) { - fetchSession(sessionId, true); - } else { - console.error('No sessionID in route.'); - } - }, [sessionId, hasSessionsPath]); + useEffect(() => { + if (sessionId != null) { + fetchSession(sessionId, true); + } else { + console.error('No sessionID in route.'); + } + }, [sessionId, hasSessionsPath]); - return ( - <Loader className="flex-1" loading={loading}> - <LivePlayer /> - </Loader> - ); + return ( + <Loader className="flex-1" loading={loading}> + <LivePlayer /> + </Loader> + ); } export default withPermissions( - ['ASSIST_LIVE'], - '', - true + ['ASSIST_LIVE'], + '', + true )( - connect( - (state, props) => { - const { - match: { - params: { sessionId }, + connect( + (state, props) => { + const { + match: { + params: { sessionId }, + }, + } = props; + const isAssist = state.getIn(['sessions', 'activeTab']).type === 'live'; + const hasSessiosPath = state + .getIn(['sessions', 'sessionPath']) + .pathname.includes('/sessions'); + return { + sessionId, + loading: state.getIn(['sessions', 'loading']), + session: state.getIn(['sessions', 'current']), + hasSessionsPath: hasSessiosPath && !isAssist, + }; }, - } = props; - const isAssist = state.getIn(['sessions', 'activeTab']).type === 'live'; - const hasSessiosPath = state - .getIn(['sessions', 'sessionPath']) - .pathname.includes('/sessions'); - return { - sessionId, - loading: state.getIn(['sessions', 'loading']), - session: state.getIn(['sessions', 'current']), - hasSessionsPath: hasSessiosPath && !isAssist, - }; - }, - { - fetchSession, - fetchSlackList, - } - )(LiveSession) + { + fetchSession, + fetchSlackList, + } + )(LiveSession) ); diff --git a/frontend/app/components/Session/Session.js b/frontend/app/components/Session/Session.js index e3801f84a..2a7cf81d8 100644 --- a/frontend/app/components/Session/Session.js +++ b/frontend/app/components/Session/Session.js @@ -9,6 +9,7 @@ import { sessions as sessionsRoute } from 'App/routes'; import withPermissions from 'HOCs/withPermissions' import WebPlayer from './WebPlayer'; import { useStore } from 'App/mstore'; +import { clearLogs } from 'App/dev/console'; const SESSIONS_ROUTE = sessionsRoute(); @@ -31,6 +32,7 @@ function Session({ },[ sessionId ]); useEffect(() => { + clearLogs() sessionStore.resetUserFilter(); } ,[]) diff --git a/frontend/app/dev/console.js b/frontend/app/dev/console.js index 9c571e8fd..b040a595a 100644 --- a/frontend/app/dev/console.js +++ b/frontend/app/dev/console.js @@ -7,7 +7,11 @@ export const options = { }, enableCrash: false, verbose: false, - exceptionsLogs: [] + exceptionsLogs: [], +} + +export const clearLogs = () => { + options.exceptionsLogs = [] } const storedString = localStorage.getItem(KEY) From 902ca06611e5e4e6f530af62cabf9b304cf9e9cf Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Mon, 16 Jan 2023 13:49:55 +0100 Subject: [PATCH 39/65] change(ui) - fix modal width --- .../components/AssistTabs/AssistTabs.tsx | 2 +- .../components/SessionList/SessionList.tsx | 72 +++++++++---------- .../AuditDetailModal/AuditDetailModal.tsx | 2 +- .../Client/Audit/AuditList/AuditList.tsx | 2 +- .../Client/CustomFields/CustomFieldForm.js | 2 +- .../Client/Integrations/Integrations.tsx | 6 +- .../Sites/InstallButton/InstallButton.tsx | 2 +- .../Users/components/UserForm/UserForm.tsx | 2 +- .../CustomMetricTableErrors.tsx | 1 + .../ErrorDetailsModal/ErrorDetailsModal.tsx | 2 +- .../EventsBlock/UserCard/UserCard.js | 2 +- .../Session_/BugReport/BugReportModal.tsx | 2 +- .../Session_/Multiview/Multiview.tsx | 4 +- .../StackEventModal/StackEventModal.tsx | 2 +- .../TimelinePointer/TimelinePointer.tsx | 8 +-- .../AssistSessionsModal.tsx | 2 +- frontend/app/components/Session_/Subheader.js | 2 +- .../AlertTriggersModal/AlertTriggersModal.tsx | 2 +- .../DevTools/ConsolePanel/ConsolePanel.tsx | 3 +- .../DevTools/NetworkPanel/NetworkPanel.tsx | 1 + .../DevTools/ProfilerModal/ProfilerModal.tsx | 2 +- .../DevTools/ProfilerPanel/ProfilerPanel.tsx | 2 +- .../StackEventModal/StackEventModal.tsx | 2 +- .../StackEventPanel/StackEventPanel.tsx | 1 + .../shared/SavedSearch/SavedSearch.tsx | 2 +- .../SavedSearchModal/SavedSearchModal.tsx | 2 +- .../SessionSettingButton.tsx | 2 +- .../SessionSettings/SessionSettings.tsx | 2 +- .../UserSessionsModal/UserSessionsModal.tsx | 2 +- .../app/components/ui/ErrorItem/ErrorItem.tsx | 2 +- 30 files changed, 71 insertions(+), 69 deletions(-) diff --git a/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx b/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx index 759bc9bab..47f93eaa8 100644 --- a/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx +++ b/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx @@ -19,7 +19,7 @@ const AssistTabs = (props: Props) => { <> <div className={stl.btnLink} - onClick={() => showModal(<SessionList userId={props.userId} />, { right: true })} + onClick={() => showModal(<SessionList userId={props.userId} />, { right: true, width: 700 })} > Active Sessions </div> diff --git a/frontend/app/components/Assist/components/SessionList/SessionList.tsx b/frontend/app/components/Assist/components/SessionList/SessionList.tsx index da3ffbf06..a919fcf09 100644 --- a/frontend/app/components/Assist/components/SessionList/SessionList.tsx +++ b/frontend/app/components/Assist/components/SessionList/SessionList.tsx @@ -24,45 +24,43 @@ function SessionList(props: Props) { }, []); return ( - <div style={{ width: '50vw' }}> - <div - className="border-r shadow h-screen overflow-y-auto" - style={{ backgroundColor: '#FAFAFA', zIndex: 999, width: '100%', minWidth: '700px' }} - > - <div className="p-4"> - <div className="text-2xl"> - {props.userId}'s <span className="color-gray-medium">Live Sessions</span>{' '} - </div> + <div + className="border-r shadow h-screen overflow-y-auto" + style={{ backgroundColor: '#FAFAFA', zIndex: 999, width: '100%', minWidth: '700px' }} + > + <div className="p-4"> + <div className="text-2xl"> + {props.userId}'s <span className="color-gray-medium">Live Sessions</span>{' '} </div> - <Loader loading={props.loading}> - <NoContent - show={!props.loading && props.list.length === 0} - title={ - <div className="flex items-center justify-center flex-col"> - <AnimatedSVG name={ICONS.NO_LIVE_SESSIONS} size={170} /> - <div className="mt-2" /> - <div className="text-center text-gray-600">No live sessions found.</div> - </div> - } - > - <div className="p-4"> - {props.list.map((session: any) => ( - <div className="mb-6"> - {session.pageTitle && session.pageTitle !== '' && ( - <div className="flex items-center mb-2"> - <Label size="small" className="p-1"> - <span className="color-gray-medium">TAB</span> - </Label> - <span className="ml-2 font-medium">{session.pageTitle}</span> - </div> - )} - <SessionItem compact={true} onClick={() => hideModal()} key={session.sessionId} session={session} /> - </div> - ))} - </div> - </NoContent> - </Loader> </div> + <Loader loading={props.loading}> + <NoContent + show={!props.loading && props.list.length === 0} + title={ + <div className="flex items-center justify-center flex-col"> + <AnimatedSVG name={ICONS.NO_LIVE_SESSIONS} size={170} /> + <div className="mt-2" /> + <div className="text-center text-gray-600">No live sessions found.</div> + </div> + } + > + <div className="p-4"> + {props.list.map((session: any) => ( + <div className="mb-6"> + {session.pageTitle && session.pageTitle !== '' && ( + <div className="flex items-center mb-2"> + <Label size="small" className="p-1"> + <span className="color-gray-medium">TAB</span> + </Label> + <span className="ml-2 font-medium">{session.pageTitle}</span> + </div> + )} + <SessionItem compact={true} onClick={() => hideModal()} key={session.sessionId} session={session} /> + </div> + ))} + </div> + </NoContent> + </Loader> </div> ); } diff --git a/frontend/app/components/Client/Audit/AuditDetailModal/AuditDetailModal.tsx b/frontend/app/components/Client/Audit/AuditDetailModal/AuditDetailModal.tsx index 934604dbb..634eace5d 100644 --- a/frontend/app/components/Client/Audit/AuditDetailModal/AuditDetailModal.tsx +++ b/frontend/app/components/Client/Audit/AuditDetailModal/AuditDetailModal.tsx @@ -11,7 +11,7 @@ function AuditDetailModal(props: Props) { // console.log('jsonResponse', jsonResponse) return ( - <div style={{ width: '500px' }} className="bg-white h-screen overflow-y-auto"> + <div className="bg-white h-screen overflow-y-auto"> <h1 className="text-2xl p-4">Audit Details</h1> <div className="p-4"> <h5 className="mb-2">{ 'URL'}</h5> diff --git a/frontend/app/components/Client/Audit/AuditList/AuditList.tsx b/frontend/app/components/Client/Audit/AuditList/AuditList.tsx index f795e0c1b..4d7c93a54 100644 --- a/frontend/app/components/Client/Audit/AuditList/AuditList.tsx +++ b/frontend/app/components/Client/Audit/AuditList/AuditList.tsx @@ -54,7 +54,7 @@ function AuditList(props: Props) { <AuditListItem key={index} audit={item} - onShowDetails={() => showModal(<AuditDetailModal audit={item} />, { right: true })} + onShowDetails={() => showModal(<AuditDetailModal audit={item} />, { right: true, width: 500 })} /> ))} diff --git a/frontend/app/components/Client/CustomFields/CustomFieldForm.js b/frontend/app/components/Client/CustomFields/CustomFieldForm.js index adc9ac884..ca13a20b4 100644 --- a/frontend/app/components/Client/CustomFields/CustomFieldForm.js +++ b/frontend/app/components/Client/CustomFields/CustomFieldForm.js @@ -24,7 +24,7 @@ class CustomFieldForm extends React.PureComponent { const { field, errors } = this.props; const exists = field.exists(); return ( - <div className="bg-white h-screen overflow-y-auto" style={{ width: '350px' }}> + <div className="bg-white h-screen overflow-y-auto"> <h3 className="p-5 text-2xl">{exists ? 'Update' : 'Add'} Metadata Field</h3> <Form className={styles.wrapper}> <Form.Field> diff --git a/frontend/app/components/Client/Integrations/Integrations.tsx b/frontend/app/components/Client/Integrations/Integrations.tsx index 73572c9b0..fe281235b 100644 --- a/frontend/app/components/Client/Integrations/Integrations.tsx +++ b/frontend/app/components/Client/Integrations/Integrations.tsx @@ -63,11 +63,11 @@ function Integrations(props: Props) { } }, []); - const onClick = (integration: any) => { + const onClick = (integration: any, width: number) => { if (integration.slug) { props.fetch(integration.slug, props.siteId); } - showModal(integration.component, { right: true }); + showModal(integration.component, { right: true, width }); }; const onChangeSelect = ({ value }: any) => { @@ -100,7 +100,7 @@ function Integrations(props: Props) { integrated={integratedList.includes(integration.slug)} key={integration.name} integration={integration} - onClick={() => onClick(integration)} + onClick={() => onClick(integration, cat.title === "Plugins" ? 500 : 350)} hide={ (integration.slug === 'github' && integratedList.includes('jira')) || (integration.slug === 'jira' && integratedList.includes('github')) diff --git a/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx b/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx index 0fe5fce65..da1a2fb44 100644 --- a/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx +++ b/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx @@ -12,7 +12,7 @@ function InstallButton(props: Props) { const onClick = () => { showModal( <TrackingCodeModal title="Tracking Code" subTitle={`(Unique to ${site.host})`} onClose={hideModal} site={site} />, - { right: true } + { right: true, width: 700 } ); }; return ( diff --git a/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx b/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx index d1799cc6a..9bd37624d 100644 --- a/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx +++ b/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx @@ -49,7 +49,7 @@ function UserForm(props: Props) { } return useObserver(() => ( - <div className="bg-white h-screen p-6" style={{ width: '400px'}}> + <div className="bg-white h-screen p-6"> <div className=""> <h1 className="text-2xl mb-4">{`${user.exists() ? 'Update' : 'Invite'} User`}</h1> </div> diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx index dbc3c5504..94a3e5c4f 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx @@ -30,6 +30,7 @@ function CustomMetricTableErrors(props: RouteComponentProps & Props) { showModal(<ErrorDetailsModal errorId={errorId} />, { right: true, + width: 1200, onClose: () => { if (props.history.location.pathname.includes("/dashboard") || props.history.location.pathname.includes("/metrics/")) { props.history.replace({ search: "" }); diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorDetailsModal/ErrorDetailsModal.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorDetailsModal/ErrorDetailsModal.tsx index 38f86af66..b8513580b 100644 --- a/frontend/app/components/Dashboard/components/Errors/ErrorDetailsModal/ErrorDetailsModal.tsx +++ b/frontend/app/components/Dashboard/components/Errors/ErrorDetailsModal/ErrorDetailsModal.tsx @@ -7,7 +7,7 @@ interface Props { function ErrorDetailsModal(props: Props) { return ( <div - style={{ width: '85vw', maxWidth: '1200px' }} + // style={{ width: '85vw', maxWidth: '1200px' }} className="bg-white h-screen p-4 overflow-y-auto" > <ErrorInfo errorId={props.errorId} /> diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/UserCard.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/UserCard.js index ecafe6611..e6893ca3e 100644 --- a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/UserCard.js +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/UserCard.js @@ -117,7 +117,7 @@ export default withRequest({ function UserName({ name, userId, hash }) { const { showModal } = useModal(); const onClick = () => { - showModal(<UserSessionsModal userId={userId} hash={hash} name={name} />, { right: true }); + showModal(<UserSessionsModal userId={userId} hash={hash} name={name} />, { right: true, width: 700 }); }; return <div onClick={userId ? onClick : () => {}}>{name}</div>; } diff --git a/frontend/app/components/Session_/BugReport/BugReportModal.tsx b/frontend/app/components/Session_/BugReport/BugReportModal.tsx index da4fa64b7..d7909675d 100644 --- a/frontend/app/components/Session_/BugReport/BugReportModal.tsx +++ b/frontend/app/components/Session_/BugReport/BugReportModal.tsx @@ -184,7 +184,7 @@ function BugReportModal({ hideModal, session, width, height, account, xrayProps, return ( <div className="bg-white overflow-y-scroll" - style={{ maxWidth: '70vw', width: 620, height: '100vh' }} + style={{ height: '100vh' }} > <div className="flex flex-col p-4 gap-8 bg-white relative" ref={reportRef}> <Title userName={account.name} /> diff --git a/frontend/app/components/Session_/Multiview/Multiview.tsx b/frontend/app/components/Session_/Multiview/Multiview.tsx index 420df0602..098fa3267 100644 --- a/frontend/app/components/Session_/Multiview/Multiview.tsx +++ b/frontend/app/components/Session_/Multiview/Multiview.tsx @@ -64,12 +64,12 @@ function Multiview({ }; const openListModal = () => { - showModal(<AssistSessionsModal onAdd={hideModal} />, { right: true }); + showModal(<AssistSessionsModal onAdd={hideModal} />, { right: true, width: 700 }); }; const replaceSession = (e: React.MouseEvent, sessionId: string) => { e.stopPropagation(); - showModal(<AssistSessionsModal onAdd={hideModal} replaceTarget={sessionId} />, { right: true }); + showModal(<AssistSessionsModal onAdd={hideModal} replaceTarget={sessionId} />, { right: true, width: 700 }); }; const deleteSession = (e: React.MouseEvent, sessionId: string) => { diff --git a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx index 51b150add..0afbc6d30 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx @@ -23,7 +23,7 @@ function StackEventModal(props: Props) { } }; return ( - <div className="bg-white h-screen overflow-y-auto" style={{ width: '450px' }}> + <div className="bg-white h-screen overflow-y-auto"> {renderPopupContent()} </div> ); diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx index 206832329..499c38d25 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx @@ -27,18 +27,18 @@ const TimelinePointer = React.memo((props: Props) => { } if (type === 'ERRORS') { - showModal(<ErrorDetailsModal errorId={pointer.errorId} />, { right: true }); + showModal(<ErrorDetailsModal errorId={pointer.errorId} />, { right: true, width: 1200 }); } if (type === 'EVENT') { - showModal(<StackEventModal event={pointer} />, { right: true }); + showModal(<StackEventModal event={pointer} />, { right: true, width: 450 }); } if (type === NETWORK) { if (pointer.tp === 'graph_ql') { - showModal(<GraphQLDetailsModal resource={pointer} />, { right: true }); + showModal(<GraphQLDetailsModal resource={pointer} />, { right: true, width: 500 }); } else { - showModal(<FetchDetails resource={pointer} fetchPresented={props.fetchPresented} />, { right: true }); + showModal(<FetchDetails resource={pointer} fetchPresented={props.fetchPresented} />, { right: true, width: 500 }); } } // props.toggleBottomBlock(type); diff --git a/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/AssistSessionsModal.tsx b/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/AssistSessionsModal.tsx index 5fba5ea54..c1f192360 100644 --- a/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/AssistSessionsModal.tsx +++ b/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/AssistSessionsModal.tsx @@ -73,7 +73,7 @@ function AssistSessionsModal(props: Props) { }; return ( - <div className="bg-gray-lightest box-shadow h-screen p-4" style={{ width: '1000px', maxWidth: '60vw' }}> + <div className="bg-gray-lightest box-shadow h-screen p-4"> <div className="flex flex-col my-2 w-full gap-2 "> <div className="flex items-center gap-2 w-full"> <Tooltip title="Refresh" placement="top" delay={200}> diff --git a/frontend/app/components/Session_/Subheader.js b/frontend/app/components/Session_/Subheader.js index 55d7cfc31..8e1cfe71e 100644 --- a/frontend/app/components/Session_/Subheader.js +++ b/frontend/app/components/Session_/Subheader.js @@ -49,7 +49,7 @@ function SubHeader(props) { eventsList: eventsList, endTime: endTime, } - showModal(<BugReportModal width={width} height={height} xrayProps={xrayProps} hideModal={hideModal} />, { right: true }); + showModal(<BugReportModal width={width} height={height} xrayProps={xrayProps} hideModal={hideModal} />, { right: true, width: 620 }); }; return ( diff --git a/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx b/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx index 594fa0f8b..4f6ee030d 100644 --- a/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx +++ b/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx @@ -31,7 +31,7 @@ function AlertTriggersModal(props: Props) { }, []) return useObserver(() => ( - <div className="bg-white box-shadow h-screen overflow-y-auto" style={{ width: '350px'}}> + <div className="bg-white box-shadow h-screen overflow-y-auto"> <div className="flex items-center justify-between p-5 text-2xl"> <div>Alerts</div> { count > 0 && ( diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index 1c7e9c425..f98a2a93d 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -116,7 +116,8 @@ function ConsolePanel() { showModal( <ErrorDetailsModal errorId={log.errorId} />, { - right: true, + right: true, + width: 1200, onClose: () => { setIsDetailsModalActive(false) timeoutStartAutoscroll() diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index 1f2212152..d1de4dca7 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -234,6 +234,7 @@ function NetworkPanel({ startedAt }: { startedAt: number }) { <FetchDetailsModal time={item.time + startedAt} resource={item} rows={filteredList} fetchPresented={fetchList.length > 0} />, { right: true, + width: 500, onClose: () => { setIsDetailsModalActive(false) timeoutStartAutoscroll() diff --git a/frontend/app/components/shared/DevTools/ProfilerModal/ProfilerModal.tsx b/frontend/app/components/shared/DevTools/ProfilerModal/ProfilerModal.tsx index a5909ada7..fb562b06f 100644 --- a/frontend/app/components/shared/DevTools/ProfilerModal/ProfilerModal.tsx +++ b/frontend/app/components/shared/DevTools/ProfilerModal/ProfilerModal.tsx @@ -9,7 +9,7 @@ function ProfilerModal(props: Props) { } = props; return ( - <div className="bg-white overflow-y-auto h-screen p-5" style={{ width: '500px' }}> + <div className="bg-white overflow-y-auto h-screen p-5"> <h5 className="mb-2 text-2xl">{name}</h5> <h5 className="py-3">{'Arguments'}</h5> <ul className="color-gray-medium"> diff --git a/frontend/app/components/shared/DevTools/ProfilerPanel/ProfilerPanel.tsx b/frontend/app/components/shared/DevTools/ProfilerPanel/ProfilerPanel.tsx index 0eabda0e4..0b7a8bfc1 100644 --- a/frontend/app/components/shared/DevTools/ProfilerPanel/ProfilerPanel.tsx +++ b/frontend/app/components/shared/DevTools/ProfilerPanel/ProfilerPanel.tsx @@ -23,7 +23,7 @@ function ProfilerPanel() { const filtered = useRegExListFilterMemo(profiles, pr => pr.name, filter) const onRowClick = (profile: any) => { - showModal(<ProfilerModal profile={profile} />, { right: true }); + showModal(<ProfilerModal profile={profile} />, { right: true, width: 500 }); }; return ( <BottomBlock> diff --git a/frontend/app/components/shared/DevTools/StackEventModal/StackEventModal.tsx b/frontend/app/components/shared/DevTools/StackEventModal/StackEventModal.tsx index 991cca1ef..7dc5b0ebf 100644 --- a/frontend/app/components/shared/DevTools/StackEventModal/StackEventModal.tsx +++ b/frontend/app/components/shared/DevTools/StackEventModal/StackEventModal.tsx @@ -22,7 +22,7 @@ function StackEventModal(props: Props) { } }; return ( - <div className="bg-white overflow-y-auto h-screen p-5" style={{ width: '500px' }}> + <div className="bg-white overflow-y-auto h-screen p-5"> <h5 className="mb-2 text-2xl">Stack Event</h5> {renderPopupContent()} </div> diff --git a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx index e36c17ccf..bfa06f7bb 100644 --- a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx +++ b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx @@ -66,6 +66,7 @@ function StackEventPanel() { <StackEventModal event={item} />, { right: true, + width: 500, onClose: () => { setIsDetailsModalActive(false) timeoutStartAutoscroll() diff --git a/frontend/app/components/shared/SavedSearch/SavedSearch.tsx b/frontend/app/components/shared/SavedSearch/SavedSearch.tsx index 92b6585bc..e9d299292 100644 --- a/frontend/app/components/shared/SavedSearch/SavedSearch.tsx +++ b/frontend/app/components/shared/SavedSearch/SavedSearch.tsx @@ -25,7 +25,7 @@ function SavedSearch(props: Props) { <div className={cn("flex items-center", { [stl.disabled] : list.size === 0})}> <Button variant="outline" - onClick={() => showModal(<SavedSearchModal />, { right: true })} + onClick={() => showModal(<SavedSearchModal />, { right: true, width: 450 })} > <span className="mr-1">Saved Search</span> <span className="font-bold mr-2">{list.size}</span> diff --git a/frontend/app/components/shared/SavedSearch/components/SavedSearchModal/SavedSearchModal.tsx b/frontend/app/components/shared/SavedSearch/components/SavedSearchModal/SavedSearchModal.tsx index 02fdd236e..0e1cccedd 100644 --- a/frontend/app/components/shared/SavedSearch/components/SavedSearchModal/SavedSearchModal.tsx +++ b/frontend/app/components/shared/SavedSearch/components/SavedSearchModal/SavedSearchModal.tsx @@ -62,7 +62,7 @@ function SavedSearchModal(props: Props) { const shownItems = props.list.filter((item) => item.name.includes(filterQuery)); return ( - <div className="bg-white box-shadow h-screen" style={{ width: '450px' }}> + <div className="bg-white box-shadow h-screen"> <div className="p-6"> <h1 className="text-2xl"> Saved Search <span className="color-gray-medium">{props.list.size}</span> diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/SessionSettingButton.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/SessionSettingButton.tsx index 428b8f153..7b9726000 100644 --- a/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/SessionSettingButton.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/SessionSettingButton.tsx @@ -7,7 +7,7 @@ function SessionSettingButton(props: any) { const { showModal } = useModal(); const handleClick = () => { - showModal(<SessionSettings />, { right: true }); + showModal(<SessionSettings />, { right: true, width: 450 }); }; return ( diff --git a/frontend/app/components/shared/SessionSettings/SessionSettings.tsx b/frontend/app/components/shared/SessionSettings/SessionSettings.tsx index 9a097a47a..ff98fb53c 100644 --- a/frontend/app/components/shared/SessionSettings/SessionSettings.tsx +++ b/frontend/app/components/shared/SessionSettings/SessionSettings.tsx @@ -6,7 +6,7 @@ import CaptureRate from './components/CaptureRate'; function SessionSettings() { return ( - <div className="bg-white box-shadow h-screen overflow-y-auto" style={{ width: '450px'}}> + <div className="bg-white box-shadow h-screen overflow-y-auto"> <div className="px-6 pt-6"> <h1 className="text-2xl">Sessions Settings</h1> </div> diff --git a/frontend/app/components/shared/UserSessionsModal/UserSessionsModal.tsx b/frontend/app/components/shared/UserSessionsModal/UserSessionsModal.tsx index 8692133f0..0c8b1b480 100644 --- a/frontend/app/components/shared/UserSessionsModal/UserSessionsModal.tsx +++ b/frontend/app/components/shared/UserSessionsModal/UserSessionsModal.tsx @@ -46,7 +46,7 @@ function UserSessionsModal(props: Props) { useEffect(fetchData, [filter.page, filter.startDate, filter.endDate]); return ( - <div className="h-screen overflow-y-auto bg-white" style={{ width: '700px' }}> + <div className="h-screen overflow-y-auto bg-white"> <div className="flex items-center justify-between w-full px-5 py-3"> <div className="text-lg flex items-center"> <Avatar isActive={false} seed={hash} isAssist={false} className={''} /> diff --git a/frontend/app/components/ui/ErrorItem/ErrorItem.tsx b/frontend/app/components/ui/ErrorItem/ErrorItem.tsx index d64d24eb9..19908cbb1 100644 --- a/frontend/app/components/ui/ErrorItem/ErrorItem.tsx +++ b/frontend/app/components/ui/ErrorItem/ErrorItem.tsx @@ -15,7 +15,7 @@ function ErrorItem({ error = {}, onJump, inactive, selected }: Props) { const { showModal } = useModal(); const onErrorClick = () => { - showModal(<ErrorDetailsModal errorId={error.errorId} />, { right: true }); + showModal(<ErrorDetailsModal errorId={error.errorId} />, { right: true, width: 1200 }); }; return ( <div From 126de51eb5d9a3a81b494e2703e4744ab29ae61b Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Mon, 16 Jan 2023 14:42:22 +0100 Subject: [PATCH 40/65] change(tracker): change input node checks --- tracker/tracker/package.json | 2 +- tracker/tracker/src/main/app/guards.ts | 24 ++++++++-------- .../tracker/src/main/app/observer/observer.ts | 8 +++--- .../src/main/app/observer/top_observer.ts | 2 +- tracker/tracker/src/main/modules/cssrules.ts | 2 +- tracker/tracker/src/main/modules/focus.ts | 4 +-- tracker/tracker/src/main/modules/img.ts | 2 +- tracker/tracker/src/main/modules/input.ts | 28 +++++++------------ tracker/tracker/src/main/modules/mouse.ts | 2 +- tracker/tracker/src/main/modules/timing.ts | 2 +- tracker/tracker/src/webworker/QueueSender.ts | 5 ++-- tracker/tracker/src/webworker/index.ts | 3 +- 12 files changed, 39 insertions(+), 45 deletions(-) diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index 5c560150c..ff84fa655 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "4.1.9", + "version": "4.1.9-beta.2", "keywords": [ "logging", "replay" diff --git a/tracker/tracker/src/main/app/guards.ts b/tracker/tracker/src/main/app/guards.ts index 56366a2cf..c58d03a74 100644 --- a/tracker/tracker/src/main/app/guards.ts +++ b/tracker/tracker/src/main/app/guards.ts @@ -24,21 +24,21 @@ export function isRootNode(node: Node): node is Document | DocumentFragment { } type TagTypeMap = { - HTML: HTMLHtmlElement - BODY: HTMLBodyElement - IMG: HTMLImageElement - INPUT: HTMLInputElement - TEXTAREA: HTMLTextAreaElement - SELECT: HTMLSelectElement - LABEL: HTMLLabelElement - IFRAME: HTMLIFrameElement - STYLE: HTMLStyleElement - style: SVGStyleElement - LINK: HTMLLinkElement + html: HTMLHtmlElement + body: HTMLBodyElement + img: HTMLImageElement + input: HTMLInputElement + textarea: HTMLTextAreaElement + select: HTMLSelectElement + label: HTMLLabelElement + iframe: HTMLIFrameElement + style: HTMLStyleElement | SVGStyleElement + link: HTMLLinkElement } export function hasTag<T extends keyof TagTypeMap>( el: Node, tagName: T, ): el is TagTypeMap[typeof tagName] { - return el.nodeName === tagName + // @ts-ignore + return el.localName === tagName } diff --git a/tracker/tracker/src/main/app/observer/observer.ts b/tracker/tracker/src/main/app/observer/observer.ts index dec086509..98abcb994 100644 --- a/tracker/tracker/src/main/app/observer/observer.ts +++ b/tracker/tracker/src/main/app/observer/observer.ts @@ -144,7 +144,7 @@ export default abstract class Observer { } if ( name === 'value' && - hasTag(node, 'INPUT') && + hasTag(node, 'input') && node.type !== 'button' && node.type !== 'reset' && node.type !== 'submit' @@ -155,7 +155,7 @@ export default abstract class Observer { this.app.send(RemoveNodeAttribute(id, name)) return } - if (name === 'style' || (name === 'href' && hasTag(node, 'LINK'))) { + if (name === 'style' || (name === 'href' && hasTag(node, 'link'))) { this.app.send(SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref())) return } @@ -166,7 +166,7 @@ export default abstract class Observer { } private sendNodeData(id: number, parentElement: Element, data: string): void { - if (hasTag(parentElement, 'STYLE') || hasTag(parentElement, 'style')) { + if (hasTag(parentElement, 'style')) { this.app.send(SetCSSDataURLBased(id, data, this.app.getBaseHref())) return } @@ -242,7 +242,7 @@ export default abstract class Observer { // Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before) // TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though) // TODO: Clean the logic (though now it workd fine) - if (!hasTag(node, 'HTML') || !this.isTopContext) { + if (!hasTag(node, 'html') || !this.isTopContext) { if (parent === null) { // Sometimes one observation contains attribute mutations for the removimg node, which gets ignored here. // That shouldn't affect the visual rendering ( should it? maybe when transition applied? ) diff --git a/tracker/tracker/src/main/app/observer/top_observer.ts b/tracker/tracker/src/main/app/observer/top_observer.ts index be1a1ce33..38944c5c9 100644 --- a/tracker/tracker/src/main/app/observer/top_observer.ts +++ b/tracker/tracker/src/main/app/observer/top_observer.ts @@ -34,7 +34,7 @@ export default class TopObserver extends Observer { // IFrames this.app.nodes.attachNodeCallback((node) => { if ( - hasTag(node, 'IFRAME') && + hasTag(node, 'iframe') && ((this.options.captureIFrames && !hasOpenreplayAttribute(node, 'obscured')) || hasOpenreplayAttribute(node, 'capture')) ) { diff --git a/tracker/tracker/src/main/modules/cssrules.ts b/tracker/tracker/src/main/modules/cssrules.ts index 08db23af8..8636c68c0 100644 --- a/tracker/tracker/src/main/modules/cssrules.ts +++ b/tracker/tracker/src/main/modules/cssrules.ts @@ -87,7 +87,7 @@ export default function (app: App | null) { app.observer.attachContextCallback(patchContext) app.nodes.attachNodeCallback((node: Node): void => { - if (!(hasTag(node, 'STYLE') || hasTag(node, 'style')) || !node.sheet) { + if (!hasTag(node, 'style') || !node.sheet) { return } if (node.textContent !== null && node.textContent.trim().length > 0) { diff --git a/tracker/tracker/src/main/modules/focus.ts b/tracker/tracker/src/main/modules/focus.ts index d9db865db..3c95fd774 100644 --- a/tracker/tracker/src/main/modules/focus.ts +++ b/tracker/tracker/src/main/modules/focus.ts @@ -12,7 +12,7 @@ export default function (app: App): void { let blurred = false app.nodes.attachNodeCallback((node) => { - if (!hasTag(node, 'BODY')) { + if (!hasTag(node, 'body')) { return } app.nodes.attachNodeListener(node, 'focus', (e: FocusEvent): void => { @@ -35,7 +35,7 @@ export default function (app: App): void { }) app.attachStartCallback(() => { let elem = document.activeElement - while (elem && hasTag(elem, 'IFRAME') && elem.contentDocument) { + while (elem && hasTag(elem, 'iframe') && elem.contentDocument) { elem = elem.contentDocument.activeElement } if (elem && elem !== elem.ownerDocument.body) { diff --git a/tracker/tracker/src/main/modules/img.ts b/tracker/tracker/src/main/modules/img.ts index c6dedc6e0..1538fc3ff 100644 --- a/tracker/tracker/src/main/modules/img.ts +++ b/tracker/tracker/src/main/modules/img.ts @@ -104,7 +104,7 @@ export default function (app: App): void { }) app.nodes.attachNodeCallback((node: Node): void => { - if (!hasTag(node, 'IMG')) { + if (!hasTag(node, 'img')) { return } app.nodes.attachNodeListener(node, 'error', () => sendImgError(node)) diff --git a/tracker/tracker/src/main/modules/input.ts b/tracker/tracker/src/main/modules/input.ts index 2eeb3a2b3..15acecaa9 100644 --- a/tracker/tracker/src/main/modules/input.ts +++ b/tracker/tracker/src/main/modules/input.ts @@ -8,10 +8,10 @@ const INPUT_TYPES = ['text', 'password', 'email', 'search', 'number', 'range', ' // TODO: take into consideration "contenteditable" attribute type TextEditableElement = HTMLInputElement | HTMLTextAreaElement function isTextEditable(node: any): node is TextEditableElement { - if (hasTag(node, 'TEXTAREA')) { + if (hasTag(node, 'textarea')) { return true } - if (!hasTag(node, 'INPUT')) { + if (!hasTag(node, 'input')) { return false } @@ -19,7 +19,7 @@ function isTextEditable(node: any): node is TextEditableElement { } function isCheckable(node: any): node is HTMLInputElement { - if (!hasTag(node, 'INPUT')) { + if (!hasTag(node, 'input')) { return false } const type = node.type @@ -31,7 +31,7 @@ const labelElementFor: (element: TextEditableElement) => HTMLLabelElement | unde ? (node) => { let p: Node | null = node while ((p = p.parentNode) !== null) { - if (hasTag(p, 'LABEL')) { + if (hasTag(p, 'label')) { return p } } @@ -43,7 +43,7 @@ const labelElementFor: (element: TextEditableElement) => HTMLLabelElement | unde : (node) => { let p: Node | null = node while ((p = p.parentNode) !== null) { - if (hasTag(p, 'LABEL')) { + if (hasTag(p, 'label')) { return p } } @@ -142,12 +142,8 @@ export default function (app: App, opts: Partial<Options>): void { app.ticker.attach((): void => { inputValues.forEach((value, id) => { - const node = app.nodes.getNode(id) - if (!node) return - if (!isTextEditable(node)) { - inputValues.delete(id) - return - } + const node = app.nodes.getNode(id) as HTMLInputElement + if (!node) return inputValues.delete(id) if (value !== node.value) { inputValues.set(id, node.value) if (!registeredTargets.has(id)) { @@ -158,12 +154,8 @@ export default function (app: App, opts: Partial<Options>): void { } }) checkableValues.forEach((checked, id) => { - const node = app.nodes.getNode(id) - if (!node) return - if (!isCheckable(node)) { - checkableValues.delete(id) - return - } + const node = app.nodes.getNode(id) as HTMLInputElement + if (!node) return checkableValues.delete(id) if (checked !== node.checked) { checkableValues.set(id, node.checked) app.send(SetInputChecked(id, node.checked)) @@ -179,7 +171,7 @@ export default function (app: App, opts: Partial<Options>): void { return } // TODO: support multiple select (?): use selectedOptions; Need send target? - if (hasTag(node, 'SELECT')) { + if (hasTag(node, 'select')) { sendInputValue(id, node) app.attachEventListener(node, 'change', () => { sendInputValue(id, node) diff --git a/tracker/tracker/src/main/modules/mouse.ts b/tracker/tracker/src/main/modules/mouse.ts index 15c5e786c..b00d6d304 100644 --- a/tracker/tracker/src/main/modules/mouse.ts +++ b/tracker/tracker/src/main/modules/mouse.ts @@ -86,7 +86,7 @@ export default function (app: App): void { if (dl !== null) { return dl } - if (hasTag(target, 'INPUT')) { + if (hasTag(target, 'input')) { return getInputLabel(target) } if (isClickable(target)) { diff --git a/tracker/tracker/src/main/modules/timing.ts b/tracker/tracker/src/main/modules/timing.ts index 89f26ee7c..54c094126 100644 --- a/tracker/tracker/src/main/modules/timing.ts +++ b/tracker/tracker/src/main/modules/timing.ts @@ -21,7 +21,7 @@ function getPaintBlocks(resources: ResourcesTimeMap): Array<PaintBlock> { for (let i = 0; i < elements.length; i++) { const element = elements[i] let src = '' - if (hasTag(element, 'IMG')) { + if (hasTag(element, 'img')) { src = element.currentSrc || element.src } if (!src) { diff --git a/tracker/tracker/src/webworker/QueueSender.ts b/tracker/tracker/src/webworker/QueueSender.ts index 2a2863192..d37d7c313 100644 --- a/tracker/tracker/src/webworker/QueueSender.ts +++ b/tracker/tracker/src/webworker/QueueSender.ts @@ -57,6 +57,7 @@ export default class QueueSender { private sendBatch(batch: Uint8Array): void { this.busy = true + // @ts-ignore fetch(this.ingestURL, { body: batch, method: 'POST', @@ -66,7 +67,7 @@ export default class QueueSender { }, keepalive: batch.length < KEEPALIVE_SIZE_LIMIT, }) - .then((r) => { + .then((r: Record<string, any>) => { if (r.status === 401) { // TODO: continuous session ? this.busy = false @@ -81,7 +82,7 @@ export default class QueueSender { this.attemptsCount = 0 this.sendNext() }) - .catch((e) => { + .catch((e: any) => { console.warn('OpenReplay:', e) this.retry(batch) }) diff --git a/tracker/tracker/src/webworker/index.ts b/tracker/tracker/src/webworker/index.ts index 2c6253210..0f27167c0 100644 --- a/tracker/tracker/src/webworker/index.ts +++ b/tracker/tracker/src/webworker/index.ts @@ -69,7 +69,8 @@ function initiateFailure(reason: string): void { let sendIntervalID: ReturnType<typeof setInterval> | null = null let restartTimeoutID: ReturnType<typeof setTimeout> -self.onmessage = ({ data }: MessageEvent<ToWorkerData>): any => { +// @ts-ignore +self.onmessage = ({ data }: any): any => { if (data == null) { finalize() return From ca5381850a24127a33b0013fb6f67699d08b44f7 Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Mon, 16 Jan 2023 17:24:00 +0100 Subject: [PATCH 41/65] change(tracker): change multiview tab color --- .../LivePlayer/AssistSessionsTabs/AssistSessionsTabs.tsx | 4 +++- .../Controls/AssistSessionsTabs/AssistSessionsTabs.tsx | 4 +++- .../shared/DevTools/ConsolePanel/ConsolePanel.tsx | 9 ++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/AssistSessionsTabs.tsx b/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/AssistSessionsTabs.tsx index a6735274a..07311665a 100644 --- a/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/AssistSessionsTabs.tsx +++ b/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/AssistSessionsTabs.tsx @@ -11,12 +11,14 @@ interface ITab { onClick?: () => void; classNames?: string; children: React.ReactNode; + style?: Record<string, any>; } const Tab = (props: ITab) => ( <div onClick={props.onClick} className={cn('p-1 rounded flex items-center justify-center cursor-pointer', props.classNames)} + style={props.style} > {props.children} </div> @@ -29,7 +31,7 @@ export const InactiveTab = React.memo((props: Omit<ITab, 'children'>) => ( )); const ActiveTab = React.memo((props: Omit<ITab, 'children'>) => ( - <Tab onClick={props.onClick} classNames="hover:bg-teal bg-borderColor-primary"> + <Tab onClick={props.onClick} classNames="hover:bg-teal" style={{ background: 'rgba(57, 78, 255, 0.5)' }}> <Icon name="play-fill-new" size="22" color="white" /> </Tab> )); diff --git a/frontend/app/components/Session_/Player/Controls/AssistSessionsTabs/AssistSessionsTabs.tsx b/frontend/app/components/Session_/Player/Controls/AssistSessionsTabs/AssistSessionsTabs.tsx index a6735274a..07311665a 100644 --- a/frontend/app/components/Session_/Player/Controls/AssistSessionsTabs/AssistSessionsTabs.tsx +++ b/frontend/app/components/Session_/Player/Controls/AssistSessionsTabs/AssistSessionsTabs.tsx @@ -11,12 +11,14 @@ interface ITab { onClick?: () => void; classNames?: string; children: React.ReactNode; + style?: Record<string, any>; } const Tab = (props: ITab) => ( <div onClick={props.onClick} className={cn('p-1 rounded flex items-center justify-center cursor-pointer', props.classNames)} + style={props.style} > {props.children} </div> @@ -29,7 +31,7 @@ export const InactiveTab = React.memo((props: Omit<ITab, 'children'>) => ( )); const ActiveTab = React.memo((props: Omit<ITab, 'children'>) => ( - <Tab onClick={props.onClick} classNames="hover:bg-teal bg-borderColor-primary"> + <Tab onClick={props.onClick} classNames="hover:bg-teal" style={{ background: 'rgba(57, 78, 255, 0.5)' }}> <Icon name="play-fill-new" size="22" color="white" /> </Tab> )); diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index f98a2a93d..21eb6c021 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -101,7 +101,7 @@ function ConsolePanel() { timeoutStartAutoscroll() } - const _list = useRef(); // TODO: fix react-virtualized types & incapsulate scrollToRow logic + const _list = useRef(null); // TODO: fix react-virtualized types & incapsulate scrollToRow logic useEffect(() => { if (_list.current) { // @ts-ignore @@ -130,8 +130,7 @@ function ConsolePanel() { const item = filteredList[index]; return ( - <React.Fragment key={key}> - {/* @ts-ignore */} + // @ts-ignore <CellMeasurer cache={cache} columnIndex={0} key={key} rowIndex={index} parent={parent}> {({ measure }: any) => ( <ConsoleRow @@ -142,13 +141,12 @@ function ConsolePanel() { renderWithNL={renderWithNL} onClick={() => showDetails(item)} recalcHeight={() => { - measure(); (_list as any).current.recomputeRowHeights(index); + cache.clear(index, 0) }} /> )} </CellMeasurer> - </React.Fragment> ) } @@ -195,6 +193,7 @@ function ConsolePanel() { ref={_list} deferredMeasurementCache={cache} overscanRowCount={5} + estimatedRowSize={36} rowCount={Math.ceil(filteredList.length || 1)} rowHeight={cache.rowHeight} rowRenderer={_rowRenderer} From 2b8e008a1872dd7d7303d6a388bec7708dfdcd9d Mon Sep 17 00:00:00 2001 From: rjshrjndrn <rjshrjndrn@gmail.com> Date: Mon, 16 Jan 2023 17:38:48 +0100 Subject: [PATCH 42/65] chore(helm): Adding shared pvc name as global value Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> --- .../charts/chalice/templates/deployment.yaml | 4 ++-- .../openreplay/charts/chalice/values.yaml | 2 +- .../charts/sink/templates/deployment.yaml | 4 ++-- .../openreplay/charts/sink/values.yaml | 2 +- .../charts/storage/templates/deployment.yaml | 4 ++-- .../openreplay/charts/storage/values.yaml | 2 +- .../charts/utilities/templates/efs-cron.yaml | 4 ++-- .../openreplay/charts/utilities/values.yaml | 2 +- scripts/helmcharts/vars.yaml | 21 ++++--------------- 9 files changed, 16 insertions(+), 29 deletions(-) diff --git a/scripts/helmcharts/openreplay/charts/chalice/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/chalice/templates/deployment.yaml index 30b02b563..e8d925175 100644 --- a/scripts/helmcharts/openreplay/charts/chalice/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/chalice/templates/deployment.yaml @@ -148,7 +148,7 @@ spec: {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} - {{- if eq .Values.pvc.name "hostPath" }} + {{- if eq (tpl .Values.pvc.name . ) "hostPath" }} volumes: - name: datadir hostPath: @@ -162,7 +162,7 @@ spec: volumes: - name: datadir persistentVolumeClaim: - claimName: {{ .Values.pvc.name }} + claimName: "{{ tpl .Values.pvc.name . }}" {{- with .Values.persistence.volumes }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/scripts/helmcharts/openreplay/charts/chalice/values.yaml b/scripts/helmcharts/openreplay/charts/chalice/values.yaml index 801dd26f7..3269aa503 100644 --- a/scripts/helmcharts/openreplay/charts/chalice/values.yaml +++ b/scripts/helmcharts/openreplay/charts/chalice/values.yaml @@ -133,7 +133,7 @@ pvc: # In case of pvc, you'll have to provide the pvc name. # For example # name: openreplay-efs - name: hostPath + name: "{{ .Values.global.pvcRWXName }}" hostMountPath: /openreplay/storage/nfs persistence: {} diff --git a/scripts/helmcharts/openreplay/charts/sink/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/sink/templates/deployment.yaml index 257af0d1a..7381541a1 100644 --- a/scripts/helmcharts/openreplay/charts/sink/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/sink/templates/deployment.yaml @@ -89,7 +89,7 @@ spec: {{- with .Values.persistence.mounts }} {{- toYaml . | nindent 10 }} {{- end }} - {{- if eq .Values.pvc.name "hostPath" }} + {{- if eq (tpl .Values.pvc.name . ) "hostPath" }} volumes: - name: datadir hostPath: @@ -104,7 +104,7 @@ spec: volumes: - name: datadir persistentVolumeClaim: - claimName: {{ .Values.pvc.name }} + claimName: "{{ tpl .Values.pvc.name . }}" {{- include "openreplay.volume.redis_ca_certificate" .Values.global.redis | nindent 6 }} {{- with .Values.persistence.volumes }} {{- toYaml . | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/sink/values.yaml b/scripts/helmcharts/openreplay/charts/sink/values.yaml index 23ea52025..bdf9b824b 100644 --- a/scripts/helmcharts/openreplay/charts/sink/values.yaml +++ b/scripts/helmcharts/openreplay/charts/sink/values.yaml @@ -102,7 +102,7 @@ pvc: # In case of pvc, you'll have to provide the pvc name. # For example # name: openreplay-efs - name: hostPath + name: "{{ .Values.global.pvcRWXName }}" hostMountPath: /openreplay/storage/nfs persistence: {} diff --git a/scripts/helmcharts/openreplay/charts/storage/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/storage/templates/deployment.yaml index d20059fdc..9cb2cca22 100644 --- a/scripts/helmcharts/openreplay/charts/storage/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/storage/templates/deployment.yaml @@ -97,7 +97,7 @@ spec: {{- with .Values.persistence.mounts }} {{- toYaml . | nindent 10 }} {{- end }} - {{- if eq .Values.pvc.name "hostPath" }} + {{- if eq (tpl .Values.pvc.name . ) "hostPath" }} volumes: {{- with .Values.persistence.volumes }} {{- toYaml . | nindent 6 }} @@ -114,7 +114,7 @@ spec: {{- end }} - name: datadir persistentVolumeClaim: - claimName: {{ .Values.pvc.name }} + claimName: "{{ tpl .Values.pvc.name . }}" {{- end }} {{- include "openreplay.volume.redis_ca_certificate" .Values.global.redis | nindent 6 }} {{- with .Values.nodeSelector }} diff --git a/scripts/helmcharts/openreplay/charts/storage/values.yaml b/scripts/helmcharts/openreplay/charts/storage/values.yaml index 85fa7ea65..7f165aa4b 100644 --- a/scripts/helmcharts/openreplay/charts/storage/values.yaml +++ b/scripts/helmcharts/openreplay/charts/storage/values.yaml @@ -103,7 +103,7 @@ pvc: # In case of pvc, you'll have to provide the pvc name. # For example # name: openreplay-efs - name: hostPath + name: "{{ .Values.global.pvcRWXName }}" hostMountPath: /openreplay/storage/nfs persistence: {} diff --git a/scripts/helmcharts/openreplay/charts/utilities/templates/efs-cron.yaml b/scripts/helmcharts/openreplay/charts/utilities/templates/efs-cron.yaml index ea9413538..7d2a0e6d7 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/templates/efs-cron.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/templates/efs-cron.yaml @@ -40,7 +40,7 @@ spec: - mountPath: /mnt/efs name: datadir restartPolicy: Never - {{- if eq .Values.efsCleaner.pvc.name "hostPath" }} + {{- if eq (tpl .Values.efsCleaner.pvc.name . ) "hostPath" }} volumes: - name: datadir hostPath: @@ -51,6 +51,6 @@ spec: volumes: - name: datadir persistentVolumeClaim: - claimName: {{ .Values.efsCleaner.pvc.name }} + claimName: {{ tpl .Values.efsCleaner.pvc.name . }} {{- end }} diff --git a/scripts/helmcharts/openreplay/charts/utilities/values.yaml b/scripts/helmcharts/openreplay/charts/utilities/values.yaml index feba1be7c..0c58d630f 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/values.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/values.yaml @@ -15,7 +15,7 @@ efsCleaner: # In case of pvc, you'll have to provide the pvc name. # For example # name: openreplay-efs - name: hostPath + name: "{{ .Values.global.pvcRWXName }}" hostMountPath: /openreplay/storage/nfs telemetry: diff --git a/scripts/helmcharts/vars.yaml b/scripts/helmcharts/vars.yaml index c77881963..c24f8feb3 100644 --- a/scripts/helmcharts/vars.yaml +++ b/scripts/helmcharts/vars.yaml @@ -108,6 +108,10 @@ global: # secret key to inject to assist and peers service assistKey: "SetARandomStringHere" assistJWTSecret: "SetARandomStringHere" + # In case of multiple nodes in the kubernetes cluster, + # we'll have to create an RWX PVC for shared components. + # If it's a single node, we'll use hostVolume, which is the default for the community/oss edition. + pvcRWXName: "hostPath" s3: region: "us-east-1" endpoint: "http://minio.db.svc.cluster.local:9000" @@ -117,9 +121,6 @@ global: vaultBucket: "vault-data" # This is only for enterpriseEdition quickwitBucket: "quickwit" - # if you're using one node installation, where - # you're using local s3, make sure these variables - # are same as minio.global.minio.accesskey and secretKey accessKey: "changeMeMinioAccessKey" secretKey: "changeMeMinioPassword" email: @@ -136,20 +137,6 @@ global: enterpriseEditionLicense: "" domainName: "" -# If there is multiple nodes in the kubernetes cluster, -# we'll have to create a NFS share PVC for both the containers to share data. -# If it's the single node, we'll use hostVolume, which is default for community installation. -# Note: Both PVC name should be same. -# sink: -# pvc: -# name: mysharedpersistence -# storage: -# pvc: -# name: mysharedpersistence -# chalice: -# pvc: -# name: mysharedpersistence - chalice: env: jwt_secret: "SetARandomStringHere" From 9b558d444a1a1e38ba38b218ce9e0309cb6e533f Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Mon, 9 Jan 2023 16:13:06 +0100 Subject: [PATCH 43/65] feat(ui) - insights - wip --- .../InsightsCard/InsightItem.tsx | 25 ++++ .../InsightsCard/InsightsCard.tsx | 31 +++++ .../InsightsCard/index.ts | 1 + .../components/WidgetChart/WidgetChart.tsx | 7 +- .../components/WidgetForm/WidgetForm.tsx | 117 ++++++++++-------- .../MetricTypeDropdown/MetricTypeDropdown.tsx | 1 + .../components/WidgetView/WidgetView.tsx | 16 ++- frontend/app/components/ui/SVG.tsx | 3 +- frontend/app/constants/card.ts | 7 ++ frontend/app/constants/filterOptions.js | 10 +- frontend/app/mstore/metricStore.ts | 89 ++++++++++--- .../app/svg/icons/arrow-counterclockwise.svg | 4 + frontend/app/types/filter/filterType.ts | 7 ++ 13 files changed, 244 insertions(+), 74 deletions(-) create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/index.ts create mode 100644 frontend/app/svg/icons/arrow-counterclockwise.svg diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx new file mode 100644 index 000000000..41ac503be --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Icon } from 'UI'; + +interface Props { + item: any; + onClick?: (e: React.MouseEvent<HTMLDivElement>) => void; +} +function InsightItem(props: Props) { + const { item, onClick = () => {} } = props; + return ( + <div + className="flex items-center py-4 hover:bg-active-blue -mx-4 px-4 border-b last:border-transparent cursor-pointer" + onClick={onClick} + > + <Icon name={item.icon} size={20} className="mr-2" color={item.iconColor} /> + <div className="mx-1 font-medium">{item.ratio}</div> + <div className="mx-1">on</div> + <div className="mx-1 bg-gray-100 px-2 rounded">Update</div> + <div className="mx-1">increased by</div> + <div className="font-medium text-red">{item.increase}</div> + </div> + ); +} + +export default InsightItem; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx new file mode 100644 index 000000000..a100158e6 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx @@ -0,0 +1,31 @@ +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; +import InsightItem from './InsightItem'; + +const data = [ + { icon: 'dizzy', ratio: 'Click Rage', increase: 10, iconColor: 'red' }, + { icon: 'dizzy', ratio: 'Click Rage', increase: 10, iconColor: 'yello' }, + { icon: 'dizzy', ratio: 'Click Rage', increase: 10, iconColor: 'green' }, + { icon: 'dizzy', ratio: 'Click Rage', increase: 10, iconColor: 'gray' }, + { icon: 'dizzy', ratio: 'Click Rage', increase: 10, iconColor: 'red' }, +]; +interface Props {} +function InsightsCard(props: Props) { + const { metricStore } = useStore(); + const metric = metricStore.instance; + + const clickHanddler = (e: React.MouseEvent<HTMLDivElement>) => { + console.log(e); + }; + + return ( + <div> + {data.map((item) => ( + <InsightItem item={item} onClick={clickHanddler} /> + ))} + </div> + ); +} + +export default observer(InsightsCard); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/index.ts new file mode 100644 index 000000000..bd85f2e4b --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/index.ts @@ -0,0 +1 @@ +export { default } from './InsightsCard' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 3291c0006..d522c299a 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -13,12 +13,13 @@ import { getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper'; import { debounce } from 'App/utils'; import useIsMounted from 'App/hooks/useIsMounted' import { FilterKey } from 'Types/filter/filterType'; -import { TIMESERIES, TABLE, CLICKMAP, FUNNEL, ERRORS, PERFORMANCE, RESOURCE_MONITORING, WEB_VITALS } from 'App/constants/card'; +import { TIMESERIES, TABLE, CLICKMAP, FUNNEL, ERRORS, PERFORMANCE, RESOURCE_MONITORING, WEB_VITALS, INSIGHTS } from 'App/constants/card'; import FunnelWidget from 'App/components/Funnels/FunnelWidget'; import SessionWidget from '../Sessions/SessionWidget'; import CustomMetricTableSessions from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions'; import CustomMetricTableErrors from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors'; import ClickMapCard from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard' +import InsightsCard from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard'; interface Props { metric: any; @@ -193,6 +194,10 @@ function WidgetChart(props: Props) { ) } + if (metricType === INSIGHTS) { + return <InsightsCard /> + } + return <div>Unknown metric type</div>; } return ( diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index d9eaf0e65..64ebef507 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { metricOf, issueOptions } from 'App/constants/filterOptions'; +import React, { useEffect, useState } from 'react'; +import { metricOf, issueOptions, issueCategories } from 'App/constants/filterOptions'; import { FilterKey } from 'Types/filter/filterType'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; @@ -18,9 +18,11 @@ import { RESOURCE_MONITORING, PERFORMANCE, WEB_VITALS, + INSIGHTS, } from 'App/constants/card'; -import { clickmapFilter, eventKeys } from 'App/types/filter/newFilter'; +import { eventKeys } from 'App/types/filter/newFilter'; import { renderClickmapThumbnail } from './renderMap'; +import Widget from 'App/mstore/types/widget'; interface Props { history: any; match: any; @@ -37,68 +39,45 @@ function WidgetForm(props: Props) { const { metricStore, dashboardStore } = useStore(); const isSaving = metricStore.isSaving; const metric: any = metricStore.instance; + const [initialInstance, setInitialInstance] = useState(); const timeseriesOptions = metricOf.filter((i) => i.type === 'timeseries'); const tableOptions = metricOf.filter((i) => i.type === 'table'); - const isTable = metric.metricType === 'table'; + const isTable = metric.metricType === TABLE; const isClickmap = metric.metricType === CLICKMAP; - const isFunnel = metric.metricType === 'funnel'; + const isFunnel = metric.metricType === FUNNEL; + const isInsights = metric.metricType === INSIGHTS; const canAddSeries = metric.series.length < 3; const eventsLength = metric.series[0].filter.filters.filter((i: any) => i.isEvent).length; const cannotSaveFunnel = isFunnel && (!metric.series[0] || eventsLength <= 1); + const isPredefined = [ERRORS, PERFORMANCE, RESOURCE_MONITORING, WEB_VITALS].includes( metric.metricType ); - const excludeFilterKeys = isClickmap ? eventKeys : [] + const excludeFilterKeys = isClickmap ? eventKeys : []; + + useEffect(() => { + if (!!metric && !initialInstance) { + setInitialInstance(metric.toJson()); + } + }, [metric]); const writeOption = ({ value, name }: { value: any; name: any }) => { value = Array.isArray(value) ? value : value.value; const obj: any = { [name]: value }; - if (name === 'metricValue') { - obj.metricValue = value; - - if (Array.isArray(obj.metricValue) && obj.metricValue.length > 1) { - obj.metricValue = obj.metricValue.filter((i: any) => i.value !== 'all'); - } - } - if (name === 'metricType') { switch (value) { case TIMESERIES: obj.metricOf = timeseriesOptions[0].value; - obj.viewType = 'lineChart'; break; case TABLE: obj.metricOf = tableOptions[0].value; - obj.viewType = 'table'; - break; - case FUNNEL: - obj.metricOf = 'sessionCount'; - break; - case ERRORS: - case RESOURCE_MONITORING: - case PERFORMANCE: - case WEB_VITALS: - obj.viewType = 'chart'; - break; - case CLICKMAP: - obj.viewType = 'chart'; - - if (value !== CLICKMAP) { - metric.series[0].filter.removeFilter(0); - } - - if (metric.series[0].filter.filters.length < 1) { - metric.series[0].filter.addFilter({ - ...clickmapFilter, - value: [''], - }); - } break; } } + metricStore.merge(obj); }; @@ -112,10 +91,16 @@ function WidgetForm(props: Props) { } } const savedMetric = await metricStore.save(metric); + setInitialInstance(metric.toJson()) if (wasCreating) { if (parseInt(dashboardId, 10) > 0) { - history.replace(withSiteId(dashboardMetricDetails(dashboardId, savedMetric.metricId), siteId)); - dashboardStore.addWidgetToDashboard(dashboardStore.getDashboard(parseInt(dashboardId, 10))!, [savedMetric.metricId]); + history.replace( + withSiteId(dashboardMetricDetails(dashboardId, savedMetric.metricId), siteId) + ); + dashboardStore.addWidgetToDashboard( + dashboardStore.getDashboard(parseInt(dashboardId, 10))!, + [savedMetric.metricId] + ); } else { history.replace(withSiteId(metricDetails(savedMetric.metricId), siteId)); } @@ -134,6 +119,11 @@ function WidgetForm(props: Props) { } }; + const undoChnages = () => { + const w = new Widget(); + metricStore.merge(w.fromJson(initialInstance), false); + }; + return ( <div className="p-6"> <div className="form-group"> @@ -142,7 +132,7 @@ function WidgetForm(props: Props) { <MetricTypeDropdown onSelect={writeOption} /> <MetricSubtypeDropdown onSelect={writeOption} /> - {metric.metricOf === FilterKey.ISSUE && ( + {metric.metricOf === FilterKey.ISSUE && metric.metricType === TABLE && ( <> <span className="mx-3">issue type</span> <Select @@ -156,6 +146,20 @@ function WidgetForm(props: Props) { </> )} + {metric.metricType === INSIGHTS && ( + <> + <span className="mx-3">issue category</span> + <Select + name="metricValue" + options={issueCategories} + value={metric.metricValue} + onChange={writeOption} + isMulti={true} + placeholder="All Categories" + /> + </> + )} + {metric.metricType === 'table' && !(metric.metricOf === FilterKey.ERRORS || metric.metricOf === FilterKey.SESSIONS) && ( <> @@ -183,8 +187,8 @@ function WidgetForm(props: Props) { {!isPredefined && ( <div className="form-group"> <div className="flex items-center font-medium py-2"> - {`${isTable || isFunnel || isClickmap ? 'Filter by' : 'Chart Series'}`} - {!isTable && !isFunnel && !isClickmap && ( + {`${isTable || isFunnel || isClickmap || isInsights ? 'Filter by' : 'Chart Series'}`} + {!isTable && !isFunnel && !isClickmap && !isInsights && ( <Button className="ml-2" variant="text-primary" @@ -198,14 +202,14 @@ function WidgetForm(props: Props) { {metric.series.length > 0 && metric.series - .slice(0, isTable || isFunnel || isClickmap ? 1 : metric.series.length) + .slice(0, isTable || isFunnel || isClickmap || isInsights ? 1 : metric.series.length) .map((series: any, index: number) => ( <div className="mb-2" key={series.name}> <FilterSeries supportsEmpty={!isClickmap} excludeFilterKeys={excludeFilterKeys} observeChanges={() => metric.updateKey('hasChanged', true)} - hideHeader={isTable || isClickmap} + hideHeader={isTable || isClickmap || isInsights} seriesIndex={index} series={series} onRemoveSeries={() => metric.removeSeries(index)} @@ -226,13 +230,20 @@ function WidgetForm(props: Props) { title="Cannot save funnel metric without at least 2 events" disabled={!cannotSaveFunnel} > - <Button variant="primary" onClick={onSave} disabled={isSaving || cannotSaveFunnel}> - {metric.exists() - ? 'Update' - : parseInt(dashboardId) > 0 - ? 'Create & Add to Dashboard' - : 'Create'} - </Button> + <div className="flex items-center"> + <Button variant="primary" onClick={onSave} disabled={isSaving || cannotSaveFunnel}> + {metric.exists() + ? 'Update' + : parseInt(dashboardId) > 0 + ? 'Create & Add to Dashboard' + : 'Create'} + </Button> + {metric.exists() && metric.hasChanged && ( + <Button onClick={undoChnages} variant="text" icon="arrow-counterclockwise" className="ml-2"> + Undo + </Button> + )} + </div> </Tooltip> <div className="flex items-center"> {metric.exists() && ( diff --git a/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.tsx b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.tsx index ac47765b8..f2faf0d5a 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.tsx @@ -43,6 +43,7 @@ function MetricTypeDropdown(props: Props) { const onChange = (type: string) => { metricStore.changeType(type); }; + return ( <Select name="metricType" diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx index 12419c7ec..b65a9d28d 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -13,6 +13,17 @@ import Breadcrumb from 'Shared/Breadcrumb'; import { FilterKey } from 'Types/filter/filterType'; import { Prompt } from 'react-router'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import { + TIMESERIES, + TABLE, + CLICKMAP, + FUNNEL, + ERRORS, + RESOURCE_MONITORING, + PERFORMANCE, + WEB_VITALS, + INSIGHTS, + } from 'App/constants/card'; interface Props { history: any; @@ -110,10 +121,11 @@ function WidgetView(props: Props) { </div> <WidgetPreview className="mt-8" name={widget.name} /> + {widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && ( <> - {(widget.metricType === 'table' || widget.metricType === 'timeseries' || widget.metricType === 'clickMap') && <WidgetSessions className="mt-8" />} - {widget.metricType === 'funnel' && <FunnelIssues />} + {(widget.metricType === TABLE || widget.metricType === TIMESERIES || widget.metricType === CLICKMAP || widget.metricType === INSIGHTS) && <WidgetSessions className="mt-8" />} + {widget.metricType === FUNNEL && <FunnelIssues />} </> )} </NoContent> diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index 6a6230b2d..e8fb24f3d 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -1,7 +1,7 @@ import React from 'react'; -export type IconNames = 'activity' | 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-down-short' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up-short' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-checklist' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cubes' | 'dash' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | 'errors-icon' | 'event/click' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/link' | 'event/location' | 'event/resize' | 'event/view' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'gear' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-1x2' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grid' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-arrow' | 'list-ul' | 'list' | 'lock-alt' | 'magic' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'percent' | 'performance-icon' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus' | 'pointer-sessions-search' | 'prev1' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'quotes' | 'record-circle' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signpost-split' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; +export type IconNames = 'activity' | 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-counterclockwise' | 'arrow-down-short' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up-short' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-checklist' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cubes' | 'dash' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | 'errors-icon' | 'event/click' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/link' | 'event/location' | 'event/resize' | 'event/view' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'gear' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-1x2' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grid' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-arrow' | 'list-ul' | 'list' | 'lock-alt' | 'magic' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'percent' | 'performance-icon' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus' | 'pointer-sessions-search' | 'prev1' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'quotes' | 'record-circle' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signpost-split' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; interface Props { name: IconNames; @@ -24,6 +24,7 @@ const SVG = (props: Props) => { case 'arrow-alt-square-right': return <svg viewBox="0 0 448 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48H48C21.5 32 0 53.5 0 80zm400-16c8.8 0 16 7.2 16 16v352c0 8.8-7.2 16-16 16H48c-8.8 0-16-7.2-16-16V80c0-8.8 7.2-16 16-16h352zm-208 64v64H88c-13.2 0-24 10.8-24 24v80c0 13.2 10.8 24 24 24h104v64c0 28.4 34.5 42.8 54.6 22.6l128-128c12.5-12.5 12.5-32.8 0-45.3l-128-128c-20.1-20-54.6-5.8-54.6 22.7zm160 128L224 384v-96H96v-64h128v-96l128 128z"/></svg>; case 'arrow-bar-left': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5zM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5z"/></svg>; case 'arrow-clockwise': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/><path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/></svg>; + case 'arrow-counterclockwise': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z"/><path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z"/></svg>; case 'arrow-down-short': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 4a.5.5 0 0 1 .5.5v5.793l2.146-2.147a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-3-3a.5.5 0 1 1 .708-.708L7.5 10.293V4.5A.5.5 0 0 1 8 4z"/></svg>; case 'arrow-down': return <svg viewBox="0 0 448 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="m443.5 248.5-7.1-7.1c-4.7-4.7-12.3-4.7-17 0L241 419.9V44c0-6.6-5.4-12-12-12h-10c-6.6 0-12 5.4-12 12v375.9L28.5 241.4c-4.7-4.7-12.3-4.7-17 0l-7.1 7.1c-4.7 4.7-4.7 12.3 0 17l211 211.1c4.7 4.7 12.3 4.7 17 0l211-211.1c4.8-4.8 4.8-12.3.1-17z"/></svg>; case 'arrow-repeat': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z"/><path d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"/></svg>; diff --git a/frontend/app/constants/card.ts b/frontend/app/constants/card.ts index 5ffcd646f..b1354b660 100644 --- a/frontend/app/constants/card.ts +++ b/frontend/app/constants/card.ts @@ -21,6 +21,7 @@ export const WEB_VITALS = 'webVitals'; export const USER_PATH = 'userPath'; export const RETENTION = 'retention'; export const FEATURE_ADOPTION = 'featureAdoption'; +export const INSIGHTS = 'insights'; export const TYPES: CardType[] = [ { @@ -222,4 +223,10 @@ export const TYPES: CardType[] = [ description: 'Find the adoption of your all features in your app.', slug: FEATURE_ADOPTION, }, + { + title: 'Insights', + icon: 'lightbulb', + description: 'Find the adoption of your all features in your app.', + slug: INSIGHTS, + }, ]; diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index 442717ff5..43937fe8d 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -1,4 +1,4 @@ -import { FilterKey, IssueType } from 'Types/filter/filterType'; +import { FilterKey, IssueType, IssueCategory } from 'Types/filter/filterType'; // TODO remove text property from options export const options = [ { key: 'on', label: 'on', value: 'on' }, @@ -118,6 +118,13 @@ export const issueOptions = [ { label: 'Error', value: IssueType.JS_EXCEPTION }, ] +export const issueCategories = [ + { label: 'Resources', value: IssueCategory.RESOURCES }, + { label: 'Network', value: IssueCategory.NETWORK }, + { label: 'Click Rage', value: IssueCategory.CLICK_RAGE }, + { label: 'Errors', value: IssueCategory.ERRORS }, +] + export default { options, baseOperators, @@ -130,6 +137,7 @@ export default { metricTypes, metricOf, issueOptions, + issueCategories, methodOptions, pageUrlOperators, } diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index ac29401aa..067fba38f 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -3,7 +3,18 @@ import Widget from './types/widget'; import { metricService, errorService } from 'App/services'; import { toast } from 'react-toastify'; import Error from './types/error'; -import { TIMESERIES, TABLE, FUNNEL, ERRORS, RESOURCE_MONITORING, PERFORMANCE, WEB_VITALS } from 'App/constants/card'; +import { + TIMESERIES, + TABLE, + + FUNNEL, + ERRORS, + RESOURCE_MONITORING, + PERFORMANCE, + WEB_VITALS, + INSIGHTS, +} from 'App/constants/card'; +import { clickmapFilter } from 'App/types/filter/newFilter'; export default class MetricStore { isLoading: boolean = false; @@ -19,18 +30,20 @@ export default class MetricStore { sessionsPage: number = 1; sessionsPageSize: number = 10; - listView?: boolean = true - clickMapFilter: boolean = false + listView?: boolean = true; + clickMapFilter: boolean = false; - clickMapSearch = '' - clickMapLabel = '' + clickMapSearch = ''; + clickMapLabel = ''; constructor() { makeAutoObservable(this); } get sortedWidgets() { - return [...this.metrics].sort((a, b) => this.sort.by === 'desc' ? b.lastModified - a.lastModified : a.lastModified - b.lastModified) + return [...this.metrics].sort((a, b) => + this.sort.by === 'desc' ? b.lastModified - a.lastModified : a.lastModified - b.lastModified + ); } // State Actions @@ -44,35 +57,79 @@ export default class MetricStore { } setClickMaps(val: boolean) { - this.clickMapFilter = val + this.clickMapFilter = val; } changeClickMapSearch(val: string, label: string) { - this.clickMapSearch = val - this.clickMapLabel = label + this.clickMapSearch = val; + this.clickMapLabel = label; } - merge(object: any) { - Object.assign(this.instance, object); - this.instance.updateKey('hasChanged', true); + merge(obj: any, updateChangeFlag: boolean = true) { + const type = obj.metricType; + + // handle metricType change + if (obj.hasOwnProperty('metricType') && type !== this.instance.metricType) { + this.changeType(type); + } + + // handle metricValue change + if (obj.hasOwnProperty('metricValue') && obj.metricValue !== this.instance.metricValue) { + if (Array.isArray(obj.metricValue) && obj.metricValue.length > 1) { + obj.metricValue = obj.metricValue.filter((i: any) => i.value !== 'all'); + } + } + + + Object.assign(this.instance, obj); + this.instance.updateKey('hasChanged', updateChangeFlag); } changeType(value: string) { - const obj: any = { metricType: value}; + const obj: any = { metricType: value }; + obj.series = this.instance.series + + obj['metricValue'] = []; + if (value === TABLE || value === TIMESERIES) { obj['viewType'] = 'table'; } if (value === TIMESERIES) { obj['viewType'] = 'lineChart'; } - if (value === ERRORS || value === RESOURCE_MONITORING || value === PERFORMANCE || value === WEB_VITALS) { + if ( + value === ERRORS || + value === RESOURCE_MONITORING || + value === PERFORMANCE || + value === WEB_VITALS || + value === CLICKMAP + ) { obj['viewType'] = 'chart'; - } + } if (value === FUNNEL) { obj['metricOf'] = 'sessionCount'; } - this.instance.update(obj) + + if (value === INSIGHTS) { + obj['metricOf'] = 'issueCategories'; + obj['viewType'] = 'list'; + } + + if (value === CLICKMAP) { + obj.series = obj.series.slice(0, 1) + if (this.instance.metricType !== CLICKMAP) { + obj.series[0].filter.removeFilter(0); + } + + if (obj.series[0] && obj.series[0].filter.filters.length < 1) { + obj.series[0].filter.addFilter({ + ...clickmapFilter, + value: [''], + }); + } + } + this.instance.update(obj); } reset(id: string) { diff --git a/frontend/app/svg/icons/arrow-counterclockwise.svg b/frontend/app/svg/icons/arrow-counterclockwise.svg new file mode 100644 index 000000000..db6047930 --- /dev/null +++ b/frontend/app/svg/icons/arrow-counterclockwise.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z"/> + <path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts index 179a5dbf8..5042847cd 100644 --- a/frontend/app/types/filter/filterType.ts +++ b/frontend/app/types/filter/filterType.ts @@ -149,6 +149,13 @@ export enum IssueType { JS_EXCEPTION = 'js_exception', } +export enum IssueCategory { + RESOURCES = 'resources', + NETWORK = 'network', + CLICK_RAGE = 'click_rage', + ERRORS = 'errors' +} + export enum FilterType { STRING = 'STRING', ISSUE = 'ISSUE', From 3182cdbf06abb3971101f46a8e36ffcc10c9b251 Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Mon, 9 Jan 2023 16:34:31 +0100 Subject: [PATCH 44/65] feat(ui) - insights - wip --- .../InsightsCard/InsightsCard.tsx | 11 ++++++++++- .../Dashboard/components/WidgetChart/WidgetChart.tsx | 8 -------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx index a100158e6..489c76fd2 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx @@ -12,11 +12,20 @@ const data = [ ]; interface Props {} function InsightsCard(props: Props) { - const { metricStore } = useStore(); + const { metricStore, dashboardStore } = useStore(); const metric = metricStore.instance; + const drillDownFilter = dashboardStore.drillDownFilter; + const period = dashboardStore.period; const clickHanddler = (e: React.MouseEvent<HTMLDivElement>) => { console.log(e); + // TODO update drillDownFilter + // const periodTimestamps = period.toTimestamps(); + // drillDownFilter.merge({ + // filters: event, + // startTimestamp: periodTimestamps.startTimestamp, + // endTimestamp: periodTimestamps.endTimestamp, + // }); }; return ( diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index d522c299a..5d615815b 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -95,15 +95,7 @@ function WidgetChart(props: Props) { const renderChart = () => { const { metricType, viewType, metricOf } = metric; - const metricWithData = { ...metric, data }; - if (metricType === 'sessions') { - return <SessionWidget metric={metric} data={data} /> - } - - // if (metricType === ERRORS) { - // return <ErrorsWidget metric={metric} data={data} /> - // } if (metricType === FUNNEL) { return <FunnelWidget metric={metric} data={data} isWidget={isWidget || isTemplate} /> From b4e684b7b445c95a9828b72d20b3f7f4ccb3b56a Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Mon, 9 Jan 2023 17:16:11 +0100 Subject: [PATCH 45/65] feat(ui) - insights - fixed an issue with dropdown --- frontend/app/constants/filterOptions.js | 2 +- frontend/app/mstore/types/widget.ts | 14 +++++++++----- frontend/app/types/filter/filterType.ts | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index 43937fe8d..edd899d2c 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -121,7 +121,7 @@ export const issueOptions = [ export const issueCategories = [ { label: 'Resources', value: IssueCategory.RESOURCES }, { label: 'Network', value: IssueCategory.NETWORK }, - { label: 'Click Rage', value: IssueCategory.CLICK_RAGE }, + { label: 'Rage', value: IssueCategory.RAGE }, { label: 'Errors', value: IssueCategory.ERRORS }, ] diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index 9d8f5a151..e712cc224 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -3,11 +3,11 @@ import FilterSeries from "./filterSeries"; import { DateTime } from 'luxon'; import Session from "App/mstore/types/session"; import Funnelissue from 'App/mstore/types/funnelIssue'; -import { issueOptions } from 'App/constants/filterOptions'; +import { issueOptions, issueCategories } from 'App/constants/filterOptions'; import { FilterKey } from 'Types/filter/filterType'; import Period, { LAST_24_HOURS } from 'Types/app/period'; import { metricService } from "App/services"; -import { WEB_VITALS } from "App/constants/card"; +import { INSIGHTS, TABLE, WEB_VITALS } from "App/constants/card"; export default class Widget { public static get ID_KEY():string { return "metricId" } @@ -76,7 +76,7 @@ export default class Widget { runInAction(() => { this.metricId = json.metricId this.widgetId = json.widgetId - this.metricValue = this.metricValueFromArray(json.metricValue) + this.metricValue = this.metricValueFromArray(json.metricValue, json.metricType) this.metricOf = json.metricOf this.metricType = json.metricType this.metricFormat = json.metricFormat @@ -172,9 +172,13 @@ export default class Widget { }) } - private metricValueFromArray(metricValue: any) { + private metricValueFromArray(metricValue: any, metricType: string) { if (!Array.isArray(metricValue)) return metricValue; - return issueOptions.filter((i: any) => metricValue.includes(i.value)) + if (metricType === TABLE) { + return issueOptions.filter((i: any) => metricValue.includes(i.value)) + } else if (metricType === INSIGHTS) { + return issueCategories.filter((i: any) => metricValue.includes(i.value)) + } } private metricValueToArray(metricValue: any) { diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts index 5042847cd..3f2aa2266 100644 --- a/frontend/app/types/filter/filterType.ts +++ b/frontend/app/types/filter/filterType.ts @@ -152,7 +152,7 @@ export enum IssueType { export enum IssueCategory { RESOURCES = 'resources', NETWORK = 'network', - CLICK_RAGE = 'click_rage', + RAGE = 'rage', ERRORS = 'errors' } From da686de022b5235b70454afc0f5b3437765f532b Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Mon, 9 Jan 2023 17:37:19 +0100 Subject: [PATCH 46/65] change(ui) - svg for no vaulted session message --- frontend/app/svg/ca-no-sessions-in-vault.svg | 66 +++++++++++++++++--- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/frontend/app/svg/ca-no-sessions-in-vault.svg b/frontend/app/svg/ca-no-sessions-in-vault.svg index ca9bbc9c7..da02f5086 100644 --- a/frontend/app/svg/ca-no-sessions-in-vault.svg +++ b/frontend/app/svg/ca-no-sessions-in-vault.svg @@ -1,14 +1,62 @@ -<svg viewBox="0 0 250 100" fill="none" xmlns="http://www.w3.org/2000/svg"> -<rect width="250" height="100" rx="13.1579" fill="#3EAAAF" fill-opacity="0.08"/> -<rect opacity="0.6" x="86.8947" y="29.579" width="138.158" height="14.4737" rx="6.57895" fill="#3EAAAF" fill-opacity="0.5"/> -<rect opacity="0.3" x="86.8947" y="55.8948" width="46.0526" height="14.4737" rx="6.57895" fill="#3EAAAF" fill-opacity="0.5"/> -<g clip-path="url(#clip0_2_20)"> -<path d="M54 67C58.5087 67 62.8327 65.2089 66.0208 62.0208C69.2089 58.8327 71 54.5087 71 50C71 45.4913 69.2089 41.1673 66.0208 37.9792C62.8327 34.7911 58.5087 33 54 33C49.4913 33 45.1673 34.7911 41.9792 37.9792C38.7911 41.1673 37 45.4913 37 50C37 54.5087 38.7911 58.8327 41.9792 62.0208C45.1673 65.2089 49.4913 67 54 67V67ZM51.875 46.8125C51.875 48.572 50.923 50 49.75 50C48.577 50 47.625 48.572 47.625 46.8125C47.625 45.053 48.577 43.625 49.75 43.625C50.923 43.625 51.875 45.053 51.875 46.8125ZM46.1056 59.4201C45.8616 59.2792 45.6835 59.0472 45.6106 58.775C45.5377 58.5028 45.5759 58.2128 45.7168 57.9688C46.5559 56.5145 47.7632 55.3069 49.2174 54.4676C50.6715 53.6282 52.321 53.1867 54 53.1875C55.6789 53.1872 57.3283 53.6289 58.7823 54.4682C60.2364 55.3075 61.4438 56.5148 62.2832 57.9688C62.4219 58.2127 62.4585 58.5015 62.385 58.7723C62.3115 59.0431 62.1338 59.2738 61.8909 59.414C61.6479 59.5543 61.3593 59.5928 61.088 59.5211C60.8168 59.4494 60.5849 59.2733 60.443 59.0312C59.7904 57.9 58.8513 56.9607 57.7202 56.3079C56.5891 55.655 55.306 55.3117 54 55.3125C52.694 55.3117 51.4109 55.655 50.2798 56.3079C49.1487 56.9607 48.2096 57.9 47.557 59.0312C47.4161 59.2753 47.184 59.4533 46.9118 59.5263C46.6397 59.5992 46.3497 59.561 46.1056 59.4201ZM58.25 50C57.077 50 56.125 48.572 56.125 46.8125C56.125 45.053 57.077 43.625 58.25 43.625C59.423 43.625 60.375 45.053 60.375 46.8125C60.375 48.572 59.423 50 58.25 50Z" fill="#3EAAAF" fill-opacity="0.5"/> +<svg viewBox="0 0 210 197" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_39_211)"> +<rect width="40.2491" height="80.702" rx="4" transform="matrix(0.938191 0.346117 -0.00820296 0.999966 165.932 38.3288)" fill="#F4F5FF"/> +<rect x="13" y="8" width="88.2438" height="80.702" rx="4" fill="#FDF9F3"/> +<path d="M163.204 103.088C152.823 101.957 142.32 101.896 131.902 101.793C121.447 101.691 110.986 101.751 100.531 101.793C79.5514 101.884 58.5769 102.011 37.597 102.217C25.8209 102.332 14.0501 102.447 2.27933 102.767C2.21516 102.767 2.21516 102.882 2.27933 102.882C23.2539 102.997 44.2337 102.695 65.2083 102.555C86.1561 102.41 107.115 102.199 128.062 102.289C133.924 102.314 139.78 102.392 145.641 102.549C151.497 102.707 157.348 103.033 163.204 103.196C163.262 103.196 163.262 103.094 163.204 103.088Z" fill="#010101"/> +<path d="M162.351 84.5336C162.587 84.5336 162.593 84.1647 162.351 84.1647C162.115 84.1647 162.115 84.5336 162.351 84.5336Z" fill="black"/> +<path d="M150.594 12.3377C150.395 16.184 150.431 20.0484 150.425 23.9007C150.419 27.753 150.419 31.6053 150.431 35.4636C150.455 43.1985 150.516 50.9273 150.619 58.6621C150.679 63.0043 150.8 67.3464 150.963 71.6826C150.969 71.8096 151.163 71.8156 151.163 71.6826C151.235 63.9779 151.181 56.2673 151.121 48.5627C151.06 40.8278 151.024 33.099 151 25.3642C150.988 21.0281 151.048 16.6738 150.848 12.3377C150.842 12.1744 150.607 12.1744 150.594 12.3377Z" fill="black"/> +<path d="M106.429 146.316C92.4935 147.93 78.5994 150.011 64.7156 152.146C50.9408 154.262 37.1815 156.506 23.443 158.901C15.648 160.261 7.85814 161.658 0.0890588 163.188C-0.00948097 163.207 0.0320104 163.382 0.13055 163.364C14.0091 161.072 27.8773 158.653 41.761 156.397C55.6137 154.147 69.4818 151.994 83.3656 149.962C91.0672 148.838 98.7637 147.688 106.45 146.485C106.528 146.473 106.512 146.31 106.429 146.316Z" fill="black"/> +<path d="M171.351 146.635C173.673 147.839 176.084 148.874 178.464 149.96C180.858 151.054 183.254 152.145 185.647 153.236C188.03 154.322 190.416 155.408 192.799 156.491C195.184 157.575 197.57 158.658 199.956 159.736C202.344 160.817 204.74 161.878 207.13 162.953C207.431 163.089 207.732 163.225 208.036 163.361C208.084 163.384 208.13 163.31 208.079 163.286C205.691 162.185 203.31 161.066 200.919 159.97C198.539 158.878 196.158 157.787 193.775 156.699C191.382 155.605 188.989 154.514 186.593 153.42C184.2 152.329 181.804 151.238 179.408 150.147C178.217 149.604 177.022 149.064 175.831 148.521C174.655 147.985 173.481 147.435 172.289 146.94C171.986 146.815 171.682 146.692 171.376 146.571C171.34 146.553 171.315 146.615 171.351 146.635Z" fill="black"/> +<path d="M20.5054 73.1279C20.5054 73.1279 15.9516 71.3076 15.4254 75.3353L15.2803 88.6158C15.2803 88.6158 16.1451 90.2002 20.076 89.5289C20.1667 85.6404 20.5054 73.1279 20.5054 73.1279Z" fill="white"/> +<path d="M18.2922 90.0853C15.6615 90.0853 14.96 88.8637 14.9297 88.8033L14.8813 88.7126L15.0265 75.3293C15.1837 74.0956 15.6796 73.2489 16.5081 72.7651C18.1773 71.7854 20.554 72.7167 20.6568 72.759L20.9108 72.8618L20.9047 73.14C20.9047 73.14 20.5721 85.6525 20.4754 89.535L20.4693 89.8616L20.1488 89.916C19.4352 90.037 18.8244 90.0853 18.2922 90.0853ZM15.6736 88.4888C15.8913 88.7367 16.8589 89.5955 19.6831 89.1843C19.7799 85.3622 20.052 75.2507 20.1004 73.4061C19.4956 73.2126 17.9596 72.8316 16.9073 73.4485C16.3086 73.7992 15.9397 74.4524 15.8187 75.3837L15.6736 88.4888Z" fill="#020202"/> +<path d="M42.8447 36.7881C43.897 36.8364 121.137 38.56 121.137 38.56L119.649 119.059L44.3748 120.16L42.8447 36.7881Z" fill="white"/> +<path d="M43.9879 120.565L42.4397 36.3768L42.863 36.395C43.9032 36.4434 120.375 38.1488 121.149 38.1669L121.542 38.173L121.536 38.566L120.042 119.446L43.9879 120.565ZM43.2501 37.1933L44.768 119.755L119.262 118.666L120.732 38.947C114.376 38.808 49.1888 37.3505 43.2501 37.1933Z" fill="#020202"/> +<path d="M54.0088 113.907L53.3315 46.9057L53.271 45.5328L55.0732 45.1095H55.1216L113.632 45.545L111.884 111.863L54.0088 113.907ZM54.0935 46.1618L54.1237 46.8875L54.7889 113.09L111.11 111.107L112.815 46.3432L55.1578 45.9138L54.0935 46.1618Z" fill="#020202"/> +<path d="M53.9847 113.235L53.2046 103.378L102.553 102.501L111.074 111.125L53.9847 113.235ZM54.0633 104.152L54.7165 112.413L109.241 110.399L102.226 103.299L54.0633 104.152Z" fill="#020202"/> +<path d="M53.6343 103.765L102.396 102.9L102.777 45.9925L53.6827 45.7989L53.6343 103.765Z" fill="#E0E1E0"/> +<path d="M53.2349 104.17L53.2832 45.4058L103.176 45.5994L102.789 103.293L53.2349 104.17ZM54.0755 46.1981L54.0271 103.36L101.996 102.507L102.371 46.3795L54.0755 46.1981Z" fill="#020202"/> +<path d="M29.8065 41.5838L55.3635 48.2966L56.2767 107.315L30.1451 120.021L20.7472 120.069L20.0759 40.5799L29.8065 41.5838Z" fill="white"/> +<path d="M20.348 120.462L19.6707 40.1324L29.9031 41.1967L55.7505 47.9882L56.6697 107.557L30.2297 120.414L20.348 120.462ZM20.4689 41.0153L21.1342 119.67L30.0483 119.622L55.8714 107.067L54.9703 48.599L29.7338 41.9708L20.4689 41.0153Z" fill="#020202"/> +<path d="M20.348 120.462L19.6707 40.1445L28.8025 40.8278L30.5442 120.408L20.348 120.462ZM20.4689 41.0032L21.1342 119.67L29.7338 119.628L28.0224 41.5717L20.4689 41.0032Z" fill="#020202"/> +<path d="M111.999 161.616C111.926 165.734 132.27 165.196 133.462 162.807C134.653 160.425 138.415 151.462 126.453 152.654C126.453 152.66 114.345 151.97 111.999 161.616Z" fill="white"/> +<path d="M123.12 164.942C120.193 164.942 117.272 164.7 115.24 164.204C112.876 163.624 111.684 162.753 111.702 161.61L111.708 161.543C114.091 151.765 126.344 152.351 126.471 152.357C130.492 151.952 133.201 152.678 134.465 154.516C136.286 157.159 134.556 161.289 133.728 162.946C133.074 164.253 128.091 164.942 123.12 164.942ZM112.295 161.652C112.307 162.438 113.402 163.14 115.379 163.624C121.288 165.069 132.421 164.223 133.195 162.674C133.976 161.114 135.614 157.232 133.976 154.855C132.845 153.21 130.323 152.575 126.483 152.956C126.313 152.95 114.581 152.4 112.295 161.652Z" fill="#020202"/> +<path d="M157.707 163.86C157.525 167.978 140.471 166.242 139.606 163.823C138.736 161.368 136.099 152.224 146.108 154.105C146.071 154.069 156.298 154.069 157.707 163.86Z" fill="white"/> +<path d="M150.655 166.629C145.781 166.629 139.89 165.505 139.328 163.926C138.681 162.094 137.399 157.57 139.491 155.127C140.749 153.657 142.999 153.216 146.168 153.809V153.815C146.44 153.784 146.966 153.809 147.994 153.978C150.643 154.425 156.926 156.276 158.009 163.817L158.015 163.848V163.878C157.978 164.767 157.253 165.928 154.011 166.418C153.013 166.557 151.864 166.629 150.655 166.629ZM143.658 154.147C141.965 154.147 140.719 154.601 139.939 155.514C138.052 157.715 139.273 161.985 139.89 163.721C140.398 165.13 148.726 166.599 153.915 165.819C155.191 165.626 157.325 165.118 157.41 163.872C156.14 155.181 147.643 154.456 146.319 154.401L146.301 154.444L146.053 154.395C145.176 154.232 144.378 154.147 143.658 154.147Z" fill="#020202"/> +<path d="M121.935 149.225C121.681 150.198 122.334 154.069 123.199 154.97C124.07 155.871 126.162 158.078 132.162 157.026C135.808 156.161 134.581 149.327 134.581 149.327L123.846 148.711C123.852 148.717 122.189 148.287 121.935 149.225Z" fill="white"/> +<path d="M129.198 157.606C125.316 157.606 123.743 155.968 122.993 155.187C122.056 154.208 121.367 150.234 121.645 149.152C121.905 148.184 123.199 148.245 123.925 148.432L134.835 149.049L134.877 149.279C134.931 149.569 136.111 156.397 132.228 157.316C131.085 157.522 130.081 157.606 129.198 157.606ZM122.225 149.303C121.996 150.204 122.637 153.954 123.417 154.764C124.282 155.665 126.289 157.752 132.113 156.736C135.07 156.034 134.496 150.791 134.327 149.618L123.834 149.019C123.761 149.001 122.395 148.68 122.225 149.303Z" fill="#020202"/> +<path d="M138.488 148.862L138.923 155.151C138.923 155.151 143.259 158.151 151.931 155.042L150.268 147.132L138.488 148.862Z" fill="white"/> +<path d="M144.541 156.76C140.785 156.76 138.868 155.478 138.747 155.399L138.626 155.314L138.167 148.608L150.498 146.793L152.269 155.236L152.021 155.326C149.016 156.403 146.518 156.76 144.541 156.76ZM139.207 154.976C139.938 155.399 144.099 157.449 151.58 154.849L150.032 147.465L138.802 149.116L139.207 154.976Z" fill="#020202"/> +<path d="M100.938 127.653C100.938 127.653 97.9026 132.781 101.374 138.387C101.374 138.387 106.72 143.516 111.31 138.962L110.875 127.979L100.938 127.653Z" fill="white"/> +<path d="M107.064 141.139C103.865 141.139 101.204 138.641 101.168 138.605L101.12 138.545C97.5939 132.854 100.654 127.556 100.684 127.502L100.775 127.35L111.165 127.689L111.612 139.083L111.521 139.173C110.052 140.631 108.504 141.139 107.064 141.139ZM101.61 138.194C102.057 138.611 106.823 142.862 111.007 138.841L110.59 128.27L101.114 127.961C100.666 128.838 98.6341 133.362 101.61 138.194Z" fill="#020202"/> +<path d="M109.683 100.227C109.465 99.326 100.182 110.671 98.5552 128.373C98.5552 128.373 102.347 132.672 112.537 134.553C112.537 134.553 114.309 119.198 109.683 100.227Z" fill="white"/> +<path d="M112.797 134.904L112.482 134.843C102.31 132.969 98.4882 128.747 98.331 128.566L98.2463 128.469L98.2584 128.342C99.8127 111.409 108.461 99.955 109.598 99.8764C109.773 99.8643 109.936 99.9792 109.979 100.155C114.563 118.956 112.857 134.432 112.839 134.589L112.797 134.904ZM98.8632 128.27C99.474 128.887 103.363 132.491 112.271 134.202C112.482 131.916 113.571 117.68 109.465 100.608C107.868 101.957 100.357 112.467 98.8632 128.27Z" fill="#020202"/> +<path d="M108.092 132.243C106.647 118.545 106.611 94.6634 109.683 81.909C112.755 69.191 128.727 61.4561 142.527 64.6372C142.527 64.6372 168.145 67.0925 167.062 103.299C168.689 122.343 166.554 133.906 166.554 133.906C166.554 133.906 165.798 144.713 158.245 148.287C152.028 151.65 142.418 156.312 118.065 149.95C112.573 146.479 109.507 145.898 108.092 132.243Z" fill="white"/> +<path d="M139.316 153.409C133.807 153.409 126.876 152.557 117.992 150.234L117.908 150.198C117.406 149.884 116.928 149.587 116.462 149.309C111.866 146.515 109.09 144.828 107.796 132.273C106.248 117.626 106.423 94.1796 109.393 81.8365C112.404 69.3724 128.225 61.0328 142.594 64.3408C142.618 64.3408 149.125 65.0182 155.39 70.1525C161.16 74.8878 167.921 84.4792 167.359 103.299C168.967 122.131 166.869 133.833 166.851 133.948C166.844 134.027 165.998 144.937 158.372 148.547C154.695 150.549 149.397 153.409 139.316 153.409ZM118.192 149.672C143.065 156.161 152.482 151.069 158.112 148.021C165.435 144.55 166.258 133.991 166.27 133.882C166.294 133.731 168.381 122.101 166.778 103.323C167.334 84.7091 160.694 75.2688 155.028 70.6182C148.901 65.5927 142.576 64.9335 142.515 64.9274C128.406 61.6799 112.924 69.8078 109.985 81.9695C107.028 94.2521 106.859 117.608 108.401 132.201C109.665 144.465 112.193 146.001 116.789 148.789C117.23 149.073 117.702 149.364 118.192 149.672Z" fill="#020202"/> +<path d="M138.137 85.9488C121.113 85.9488 109.985 81.6006 109.822 81.5341L110.046 80.9777C110.275 81.0684 133.117 89.9886 163.518 82.0844L163.669 82.665C154.308 85.1021 145.648 85.9488 138.137 85.9488Z" fill="#020202"/> +<path d="M109.937 81.401C109.937 81.401 114.854 83.7838 128.727 85.2654L127.463 104.346C127.463 104.346 108.963 102.682 107.736 98.2012C107.808 94.9174 108.746 84.8723 109.937 81.401Z" fill="#C8E2E2"/> +<path d="M127.741 104.672L127.438 104.642C126.676 104.575 108.715 102.906 107.445 98.2798L107.433 98.2375V98.1951C107.505 94.8871 108.443 84.8179 109.652 81.3043L109.761 80.9837L110.064 81.1289C110.112 81.1531 115.113 83.5116 128.757 84.9691L129.041 84.9993L127.741 104.672ZM108.032 98.1649C109.066 101.582 121.905 103.517 127.184 104.019L128.412 85.5315C116.589 84.2494 111.388 82.3384 110.118 81.8062C108.981 85.5013 108.11 94.9295 108.032 98.1649Z" fill="#020202"/> +<path d="M137.665 105.646C118.96 105.646 107.651 100.106 107.487 100.022L107.753 99.4893C107.983 99.6042 131.309 111.022 166.542 100.808L166.711 101.382C155.789 104.551 145.992 105.646 137.665 105.646Z" fill="#020202"/> +<path d="M133.934 67.0199C134.188 63.5486 133.643 47.2201 148.569 43.6762C148.569 43.6762 159.267 41.9769 156.159 50.7217C154.641 53.0318 154.066 55.0215 146.694 57.2228C146.694 57.2228 138.778 57.7671 137.332 68.3503C135.881 71.2834 133.68 70.4912 133.934 67.0199Z" fill="white"/> +<path d="M135.367 70.4549C135.246 70.4549 135.131 70.4368 135.01 70.4065C134.03 70.1404 133.498 68.8342 133.637 67.0017C133.655 66.7356 133.673 66.391 133.685 65.9797C133.891 61.2142 134.514 46.7061 148.496 43.386C148.738 43.3436 153.951 42.5514 156.116 45.1519C157.198 46.4581 157.307 48.3631 156.436 50.8184L156.406 50.885C156.255 51.1148 156.116 51.3385 155.977 51.5623C154.713 53.5822 153.522 55.4932 146.772 57.5131L146.706 57.5252C146.391 57.5494 139.013 58.1904 137.622 68.3987L137.592 68.4895C136.975 69.7474 136.159 70.4549 135.367 70.4549ZM134.23 67.0441C134.103 68.7616 134.611 69.6808 135.167 69.832C135.754 69.9893 136.479 69.3784 137.042 68.2717C138.469 57.9848 146.035 56.9991 146.633 56.9386C153.141 54.9913 154.217 53.2556 155.469 51.2539C155.602 51.0362 155.741 50.8184 155.886 50.5947C156.672 48.3692 156.594 46.6698 155.656 45.545C153.709 43.2045 148.659 43.9726 148.611 43.9786C135.089 47.1899 134.478 61.3533 134.278 66.0099C134.266 66.4212 134.248 66.7719 134.23 67.0441Z" fill="#020202"/> +<path d="M173.968 134.263C173.968 134.263 175.994 146.298 165.87 145.79C155.753 145.282 159.514 133.827 159.514 133.827L173.968 134.263Z" fill="white"/> +<path d="M166.482 146.104C166.276 146.104 166.07 146.098 165.859 146.086C163.077 145.947 161.051 144.979 159.835 143.213C157.35 139.597 159.152 133.973 159.231 133.737L159.303 133.525L174.222 133.973L174.265 134.214C174.307 134.468 175.263 140.401 172.227 143.818C170.872 145.336 168.943 146.104 166.482 146.104ZM159.732 134.136C159.436 135.188 158.269 139.881 160.325 142.881C161.426 144.483 163.301 145.366 165.883 145.493C168.453 145.62 170.437 144.925 171.773 143.425C174.289 140.601 173.847 135.696 173.702 134.559L159.732 134.136Z" fill="#020202"/> +<path d="M163.561 102.362C162.805 102.688 157.997 103.735 157.489 125.851C157.489 125.851 157.416 134.704 158.136 136.513C158.862 138.321 175.154 139.258 175.553 133.513C175.958 127.798 174.761 97.4089 163.561 102.362Z" fill="white"/> +<path d="M165.157 138.188C161.565 138.188 158.245 137.565 157.864 136.615C157.126 134.777 157.186 126.207 157.192 125.839C157.416 116.017 158.668 103.934 163.355 102.114L163.439 102.078C165.393 101.213 167.171 101.334 168.737 102.434C175.371 107.091 176.218 128.252 175.849 133.519C175.698 135.726 173.454 137.232 169.36 137.873C168.03 138.091 166.572 138.188 165.157 138.188ZM165.913 102.108C165.211 102.108 164.468 102.283 163.681 102.628L163.573 102.67C160.156 103.995 158.099 112.225 157.791 125.845C157.791 125.929 157.725 134.656 158.42 136.386C158.728 137.154 164.171 138.085 169.275 137.281C171.434 136.942 175.087 135.992 175.262 133.477C175.613 128.481 174.736 107.369 168.398 102.924C167.618 102.386 166.796 102.108 165.913 102.108Z" fill="#020202"/> +<path d="M135.596 72.8255C131.49 72.8255 121.856 72.5474 119.486 69.9953C119.087 69.5659 118.911 69.0942 118.96 68.5862L119.552 68.6406C119.522 68.9793 119.643 69.2877 119.921 69.5901C121.778 71.5858 129.845 72.3478 137.03 72.2208C146.222 72.0515 152.778 70.9085 152.959 69.445L153.552 69.5175C153.207 72.2268 141.886 72.7227 137.036 72.8135C136.721 72.8135 136.225 72.8255 135.596 72.8255Z" fill="#020202"/> +<path d="M175.414 68.1387L191.748 57.4042L194.965 64.5948L178.202 71.3862L175.414 68.1387Z" fill="#F0F3C2"/> +<path d="M178.111 71.7491L174.966 68.0782L191.875 56.9688L195.365 64.7641L178.111 71.7491ZM175.861 68.2052L178.286 71.0355L194.566 64.4376L191.621 57.8518L175.861 68.2052Z" fill="#020202"/> +<path d="M173.787 69.1486L175.559 71.8217L171.839 73.013C171.839 73.013 170.068 71.3862 170.866 71.1685C171.652 70.9206 173.787 69.1486 173.787 69.1486Z" fill="#F0F3C2"/> +<path d="M171.761 73.3577L171.634 73.2428C171.204 72.8497 170.231 71.8761 170.37 71.2834C170.394 71.1685 170.485 70.969 170.781 70.8903C171.374 70.7029 173.013 69.4087 173.593 68.9249L173.847 68.7132L176.018 71.991L171.761 73.3577ZM170.957 71.4588C170.999 71.6705 171.422 72.2087 171.912 72.6804L175.087 71.6644L173.714 69.5962C173.109 70.086 171.646 71.2351 170.957 71.4588Z" fill="#020202"/> +<path d="M55.7377 165.051C55.6955 165.093 55.6533 165.135 55.6172 165.178C55.5991 165.202 55.575 165.22 55.5569 165.238C55.5448 165.25 55.5328 165.262 55.5267 165.268C55.5147 165.28 55.5026 165.292 55.4966 165.31C55.4785 165.334 55.5147 165.371 55.5388 165.352C55.5508 165.34 55.5629 165.334 55.581 165.322C55.593 165.31 55.6051 165.298 55.6111 165.292C55.6352 165.268 55.6533 165.25 55.6774 165.232C55.7196 165.19 55.7618 165.147 55.8101 165.111C55.8281 165.093 55.8281 165.063 55.8101 165.045C55.786 165.033 55.7558 165.027 55.7377 165.051Z" fill="#010101"/> +<path d="M85.8127 61.8673C82.4442 63.7058 79.0575 65.5261 75.689 67.3646C72.3205 69.203 68.9339 71.0113 65.6501 73.007C65.5714 73.0553 65.638 73.1702 65.7226 73.1279C69.1637 71.4285 72.5201 69.5477 75.8825 67.6972C79.245 65.8406 82.5954 63.9719 85.9578 62.1213C86.1211 62.0246 85.9759 61.7766 85.8127 61.8673Z" fill="#010101"/> +<path d="M89.8652 64.9879C82.4025 68.8705 75.0305 72.9042 67.7855 77.1859C65.7232 78.4014 63.661 79.623 61.6835 80.9777C61.6169 81.0261 61.6774 81.1228 61.75 81.0865C65.4451 79.0909 69.0131 76.8472 72.6538 74.7547C76.2944 72.6562 79.9592 70.594 83.6362 68.556C85.7347 67.4009 87.8392 66.264 89.9559 65.1452C90.0526 65.0907 89.9619 64.9395 89.8652 64.9879Z" fill="#010101"/> +<path d="M90.0403 72.5595C84.0411 75.5772 78.0963 78.7219 72.206 81.9513C70.5248 82.8706 68.8375 83.7838 67.1805 84.7635C67.1139 84.8058 67.1744 84.9026 67.2409 84.8663C70.2708 83.4209 73.2462 81.8364 76.2095 80.258C79.1789 78.6796 82.1482 77.1133 85.0934 75.5046C86.7746 74.5854 88.4437 73.6299 90.1129 72.6925C90.2036 72.6441 90.131 72.5171 90.0403 72.5595Z" fill="#010101"/> +<path d="M111.999 90.1035C112.9 89.6681 113.765 89.148 114.636 88.6521C115.513 88.1622 116.36 87.63 117.206 87.0918C117.321 87.0192 117.218 86.8559 117.103 86.9104C116.202 87.3579 115.319 87.8175 114.442 88.3194C113.572 88.8153 112.689 89.2931 111.854 89.8374C111.685 89.9462 111.824 90.1821 111.999 90.1035Z" fill="#010101"/> +<path d="M110.983 93.8046C112.422 93.0426 113.795 92.1415 115.216 91.3311C116.631 90.5207 118.077 89.7467 119.467 88.9C119.54 88.8577 119.48 88.7609 119.401 88.7911C117.913 89.4503 116.474 90.2667 115.065 91.0832C113.656 91.8996 112.204 92.7039 110.898 93.6715C110.825 93.7259 110.892 93.8529 110.983 93.8046Z" fill="#010101"/> </g> -<path d="M28.375 28.0625C28.375 26.7198 28.9084 25.4322 29.8578 24.4828C30.8072 23.5334 32.0948 23 33.4375 23H73.9375C75.2802 23 76.5678 23.5334 77.5172 24.4828C78.4666 25.4322 79 26.7198 79 28.0625V71.9375C79 73.2802 78.4666 74.5678 77.5172 75.5172C76.5678 76.4666 75.2802 77 73.9375 77H33.4375C32.0948 77 30.8072 76.4666 29.8578 75.5172C28.9084 74.5678 28.375 73.2802 28.375 71.9375V66.875H26.6875C26.2399 66.875 25.8107 66.6972 25.4943 66.3807C25.1778 66.0643 25 65.6351 25 65.1875C25 64.7399 25.1778 64.3107 25.4943 63.9943C25.8107 63.6778 26.2399 63.5 26.6875 63.5H28.375V51.6875H26.6875C26.2399 51.6875 25.8107 51.5097 25.4943 51.1932C25.1778 50.8768 25 50.4476 25 50C25 49.5524 25.1778 49.1232 25.4943 48.8068C25.8107 48.4903 26.2399 48.3125 26.6875 48.3125H28.375V36.5H26.6875C26.2399 36.5 25.8107 36.3222 25.4943 36.0057C25.1778 35.6893 25 35.2601 25 34.8125C25 34.3649 25.1778 33.9357 25.4943 33.6193C25.8107 33.3028 26.2399 33.125 26.6875 33.125H28.375V28.0625ZM33.4375 26.375C32.9899 26.375 32.5607 26.5528 32.2443 26.8693C31.9278 27.1857 31.75 27.6149 31.75 28.0625V71.9375C31.75 72.3851 31.9278 72.8143 32.2443 73.1307C32.5607 73.4472 32.9899 73.625 33.4375 73.625H73.9375C74.3851 73.625 74.8143 73.4472 75.1307 73.1307C75.4472 72.8143 75.625 72.3851 75.625 71.9375V28.0625C75.625 27.6149 75.4472 27.1857 75.1307 26.8693C74.8143 26.5528 74.3851 26.375 73.9375 26.375H33.4375Z" fill="#3EAAAF" fill-opacity="0.5"/> <defs> -<clipPath id="clip0_2_20"> -<rect width="34" height="34" fill="white" transform="translate(37 33)"/> +<clipPath id="clip0_39_211"> +<rect width="210" height="197" fill="white"/> </clipPath> </defs> </svg> From 02004427327cc0cdd994dc0a351184b8a5485cf8 Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Mon, 9 Jan 2023 17:59:27 +0100 Subject: [PATCH 47/65] change(ui) - svg for no bookmarked session message --- frontend/app/svg/ca-no-bookmarked-session.svg | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/frontend/app/svg/ca-no-bookmarked-session.svg b/frontend/app/svg/ca-no-bookmarked-session.svg index f3d84d404..fc69149aa 100644 --- a/frontend/app/svg/ca-no-bookmarked-session.svg +++ b/frontend/app/svg/ca-no-bookmarked-session.svg @@ -1,14 +1,27 @@ -<svg viewBox="0 0 250 100" fill="none" xmlns="http://www.w3.org/2000/svg"> -<rect width="250" height="100" rx="13.1579" fill="#3EAAAF" fill-opacity="0.08"/> -<rect opacity="0.6" x="85.8947" y="28.579" width="138.158" height="14.4737" rx="6.57895" fill="#3EAAAF" fill-opacity="0.5"/> -<rect opacity="0.3" x="85.8947" y="54.8948" width="46.0526" height="14.4737" rx="6.57895" fill="#3EAAAF" fill-opacity="0.5"/> -<g clip-path="url(#clip0_2_27)"> -<path d="M52.5 58C55.8152 58 58.9946 56.683 61.3388 54.3388C63.683 51.9946 65 48.8152 65 45.5C65 42.1848 63.683 39.0054 61.3388 36.6612C58.9946 34.317 55.8152 33 52.5 33C49.1848 33 46.0054 34.317 43.6612 36.6612C41.317 39.0054 40 42.1848 40 45.5C40 48.8152 41.317 51.9946 43.6612 54.3388C46.0054 56.683 49.1848 58 52.5 58V58ZM50.9375 43.1562C50.9375 44.45 50.2375 45.5 49.375 45.5C48.5125 45.5 47.8125 44.45 47.8125 43.1562C47.8125 41.8625 48.5125 40.8125 49.375 40.8125C50.2375 40.8125 50.9375 41.8625 50.9375 43.1562ZM46.6953 52.4266C46.5159 52.323 46.385 52.1523 46.3313 51.9522C46.2777 51.7521 46.3058 51.5388 46.4094 51.3594C47.0264 50.2901 47.9141 49.4022 48.9833 48.785C50.0526 48.1678 51.2655 47.8432 52.5 47.8438C53.7345 47.8435 54.9473 48.1683 56.0164 48.7854C57.0856 49.4025 57.9734 50.2903 58.5906 51.3594C58.6926 51.5387 58.7195 51.7511 58.6654 51.9502C58.6114 52.1493 58.4808 52.3189 58.3021 52.4221C58.1234 52.5252 57.9112 52.5535 57.7118 52.5008C57.5123 52.4481 57.3418 52.3186 57.2375 52.1406C56.7576 51.3088 56.0671 50.6182 55.2354 50.1381C54.4037 49.6581 53.4603 49.4057 52.5 49.4062C51.5397 49.4057 50.5963 49.6581 49.7646 50.1381C48.9329 50.6182 48.2424 51.3088 47.7625 52.1406C47.6589 52.3201 47.4883 52.451 47.2881 52.5046C47.088 52.5582 46.8747 52.5302 46.6953 52.4266ZM55.625 45.5C54.7625 45.5 54.0625 44.45 54.0625 43.1562C54.0625 41.8625 54.7625 40.8125 55.625 40.8125C56.4875 40.8125 57.1875 41.8625 57.1875 43.1562C57.1875 44.45 56.4875 45.5 55.625 45.5Z" fill="#3EAAAF" fill-opacity="0.5"/> -</g> -<path d="M31.875 28.875C31.875 27.0516 32.5993 25.303 33.8886 24.0136C35.178 22.7243 36.9266 22 38.75 22H66.25C68.0734 22 69.822 22.7243 71.1114 24.0136C72.4007 25.303 73.125 27.0516 73.125 28.875V75.2812C73.1248 75.5921 73.0404 75.8972 72.8805 76.1639C72.7207 76.4305 72.4916 76.6489 72.2175 76.7956C71.9434 76.9424 71.6347 77.012 71.3241 76.9971C71.0136 76.9823 70.7129 76.8835 70.4541 76.7113L52.5 67.0347L34.5459 76.7113C34.2871 76.8835 33.9864 76.9823 33.6759 76.9971C33.3653 77.012 33.0566 76.9424 32.7825 76.7956C32.5084 76.6489 32.2793 76.4305 32.1195 76.1639C31.9596 75.8972 31.8752 75.5921 31.875 75.2812V28.875ZM38.75 25.4375C37.8383 25.4375 36.964 25.7997 36.3193 26.4443C35.6747 27.089 35.3125 27.9633 35.3125 28.875V72.0706L51.5478 63.5387C51.8299 63.351 52.1612 63.2509 52.5 63.2509C52.8388 63.2509 53.1701 63.351 53.4522 63.5387L69.6875 72.0706V28.875C69.6875 27.9633 69.3253 27.089 68.6807 26.4443C68.036 25.7997 67.1617 25.4375 66.25 25.4375H38.75Z" fill="#3EAAAF" fill-opacity="0.5"/> -<defs> -<clipPath id="clip0_2_27"> -<rect width="25" height="25" fill="white" transform="translate(40 33)"/> -</clipPath> -</defs> +<svg viewBox="0 0 210 197" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect width="210" height="197" fill="white"/> +<path d="M125.355 144.82V51.0932C125.355 47.8823 127.883 45.2405 131.09 45.0991L176.201 43.1098C179.754 42.9531 182.663 45.904 182.455 49.4544L176.014 159.538C175.666 165.49 167.789 167.346 164.821 162.175L155.192 145.401C153.572 142.579 149.997 141.563 147.135 143.11L134.208 150.098C130.21 152.259 125.355 149.364 125.355 144.82Z" fill="#FDF9F3" stroke="black" stroke-linecap="round"/> +<path d="M11.7727 185.874C15.0263 184.5 15.9446 184.649 20.3026 183.518C29.6116 181.102 30.5103 181.623 41.8592 180.47C67.2667 177.889 103.054 183.704 121.082 178.953C139.11 174.202 154.41 181.603 170.471 183.704C178.025 184.693 186.937 186.093 197.768 187.542" stroke="black" stroke-linecap="round"/> +<path d="M155.814 104.849C154.768 105.367 154.018 105.803 153.22 106.117C152.092 106.558 150.932 106.923 149.776 107.295C148.996 107.545 148.766 107.942 149.024 108.753C149.528 110.337 149.91 111.957 150.378 113.689C148.305 113.777 146.693 113.274 145.43 112.195C145.994 110.909 146.63 109.755 146.996 108.527C147.289 107.546 147.353 106.469 147.308 105.442C147.293 105.119 146.533 104.513 146.214 104.569C144.386 104.887 142.556 105.269 140.79 105.821C139.338 106.275 137.977 106.995 136.576 107.597C136.484 107.49 136.392 107.383 136.298 107.277C137.008 106.37 137.674 105.429 138.436 104.563C139.628 103.209 139.634 103.245 138.544 101.783C137.822 100.815 137.17 99.8019 136.376 98.6473C139.102 98.4913 141.281 99.2713 143.196 100.305C144.817 98.1019 146.36 96.0046 147.901 93.9086C148.04 93.9579 148.177 94.0073 148.314 94.0566C148.349 94.4166 148.422 94.7779 148.412 95.1353C148.353 96.9166 148.264 98.6966 148.201 100.478C148.145 102.097 148.178 102.018 149.758 102.465C151.418 102.934 153.029 103.565 154.653 104.149C154.95 104.255 155.205 104.475 155.814 104.849Z" fill="#EBC885"/> +<path d="M140.433 90.4433C141.155 92.683 141.79 94.6582 142.425 96.6321C141.525 96.5321 139.969 92.1561 140.433 90.4433Z" fill="black"/> +<path d="M154.099 109.964C155.128 109.397 160.3 110.38 160.951 111.221C158.631 110.796 156.511 110.406 154.099 109.964Z" fill="black"/> +<path d="M156.5 95.1787C155.239 96.8133 153.305 97.7947 151.844 99.5C151.775 99.1413 151.648 98.872 151.72 98.808C153.199 97.4787 154.701 96.172 156.199 94.8613C156.3 94.9667 156.4 95.0733 156.5 95.1787Z" fill="black"/> +<path d="M56.3003 170.162C57.4327 171.807 57.8185 172.262 59.0498 172.547C60.9838 172.995 62.8895 173.644 64.9329 173.513C65.8124 173.457 66.7032 173.615 67.5892 173.618C69.1957 173.623 70.8306 173.862 72.5442 173.146C72.4623 172.505 72.3829 171.886 72.2921 171.18C66.8491 170.834 61.5812 170.498 56.3003 170.162ZM94.2303 170.022C92.9536 170.163 91.7321 170.355 90.5041 170.424C86.5258 170.647 82.545 170.839 78.5643 171.014C77.4595 171.061 76.3506 171.001 75.2442 171.006C74.6347 171.008 74.0397 171.082 73.8249 171.795C73.6109 172.507 73.9343 173.26 74.749 173.552C75.7111 173.897 76.7227 174.251 77.7286 174.303C82.2305 174.533 86.7235 174.521 91.1631 173.507C93.5915 172.952 93.7528 172.852 94.2303 170.022ZM64.4563 47.7206C64.4044 48.0878 64.3023 48.3707 64.3355 48.6365C64.5957 50.7067 65.4063 52.4511 67.2147 53.6605C67.8445 54.082 68.6526 54.4005 68.9744 55.3781C67.2139 55.8985 65.5789 55.2929 63.6644 55.2711C64.1256 55.9682 64.3704 56.5072 64.7627 56.8995C65.724 57.86 66.8896 58.489 68.2498 58.7079C70.0168 58.9932 71.3381 59.9578 72.5361 61.2588C73.5209 62.3295 74.0762 63.5867 74.6744 64.8561C74.8657 65.2606 75.2239 65.5864 75.7079 66.2081C75.9235 65.4486 76.0864 65.0482 76.1472 64.634C76.6538 61.1396 76.0183 57.8041 74.274 54.7896C73.0265 52.6335 71.6023 50.4911 69.0903 49.5071C67.5859 48.917 66.0742 48.3439 64.4563 47.7206ZM134.062 112.02C134.923 115.549 135.773 119.045 136.637 122.537C136.721 122.878 136.897 123.195 137.024 123.506C141.098 122.687 143.124 120.503 143.42 116.616C143.499 115.589 143.529 114.536 143.391 113.521C143.255 112.517 142.877 111.545 142.592 110.516C142.818 110.209 143.061 109.931 143.244 109.618C143.445 109.275 143.592 108.899 143.871 108.306C143.042 108.306 142.437 108.17 141.924 108.33C139.17 109.182 136.525 110.288 134.062 112.02ZM56.841 173.057C56.4681 175.558 57.0744 177.805 58.9549 179.32C61.1573 181.096 63.7089 182.343 66.7202 182.124C68.4192 182.001 70.1254 181.98 71.8228 181.84C72.3651 181.796 72.8879 181.519 73.281 181.393C73.161 179.1 73.0549 177.057 72.9487 175.025C70.149 174.904 67.4895 174.951 64.8746 174.633C62.2475 174.312 59.665 173.628 56.841 173.057ZM94.2449 173.765C87.7465 175.714 81.2449 175.897 74.5998 174.938C74.2967 176.761 74.4207 178.381 74.6339 179.977C74.7036 180.501 75.1024 181.148 75.5457 181.423C76.6635 182.115 77.8348 182.883 79.088 183.169C84.9768 184.511 90.5705 183.472 95.7947 180.552C98.5587 179.007 100.866 176.973 101.429 173.412C99.589 172.165 97.6817 171.203 95.2848 171.896C94.9103 172.568 94.5561 173.205 94.2449 173.765ZM109.869 83.1937C107.548 85.4098 104.796 86.7148 102.183 88.2209C102.183 88.6083 102.133 88.9099 102.191 89.1887C103.073 93.4223 103.287 97.7135 103.296 102.02C103.302 105.283 103.269 108.548 103.273 111.811C103.274 112.372 103.374 112.933 103.438 113.612C107.3 112.642 110.758 111.238 113.867 109.22C115.511 101.252 113.348 88.5467 109.869 83.1937ZM53.8013 67.5432C53.3304 68.9244 53.7186 69.859 54.5705 70.5739C55.3025 71.1875 56.0717 71.8416 56.9398 72.1885C58.7896 72.927 60.6815 73.6168 62.6155 74.078C68.2279 75.4171 73.9416 75.4187 79.6432 74.9153C83.4829 74.5765 87.3023 73.9272 90.8575 72.3263C92.1236 71.7557 93.3767 71.0254 94.4232 70.1216C96.3442 68.4623 96.1221 67.1038 93.9036 65.8531C89.3725 63.299 84.4807 61.9469 79.3182 61.5222C78.8999 61.4882 78.4687 61.6106 77.9888 61.6673C77.3817 63.5511 77.9856 65.8061 76.1707 67.1784C74.9103 67.1038 74.2245 66.4991 73.7836 65.4138C73.3694 64.3941 72.806 63.427 72.2378 62.4787C71.8276 61.7946 71.1816 61.5295 70.3419 61.6073C64.3128 62.1658 58.8009 64.1298 53.8013 67.5432ZM132.433 111.51C131.691 111.848 131.275 111.998 130.897 112.215C126.82 114.556 122.398 116.032 117.953 117.476C114.715 118.528 111.499 119.657 108.315 120.863C105.199 122.044 102.134 123.36 99.0516 124.629C96.5834 125.644 94.1192 126.675 91.9631 128.291C91.4338 128.686 90.8275 129.077 90.4927 129.617C89.3166 131.514 88.3496 133.513 88.1526 135.795C87.9102 138.608 88.9672 140.399 91.621 141.261C92.7988 141.643 94.0795 141.811 95.3237 141.869C99.7527 142.075 104.018 141.085 108.215 139.827C117.274 137.111 125.789 133.137 133.995 128.471C134.564 128.147 135.086 127.739 135.76 127.282C135.682 121.861 134.253 116.781 132.433 111.51ZM39.673 84.6317C40.4212 84.8854 40.9513 85.1302 41.5073 85.2428C47.1643 86.389 52.81 87.6056 58.4913 88.6164C63.5955 89.5251 68.7442 90.1192 73.9489 90.1541C78.476 90.1849 82.9755 89.9255 87.4596 89.3078C92.3757 88.6302 97.2748 87.8439 102.073 86.5624C105.026 85.7729 107.355 84.0286 109.196 81.3877C106.188 75.8467 102.219 71.2921 96.9805 67.8277C95.0473 71.3707 93.8931 72.3523 89.9026 73.8089C88.9299 74.1639 87.9402 74.489 86.9359 74.7386C78.6746 76.7885 70.3694 76.881 62.0489 75.1512C59.8725 74.6989 57.7512 74.0067 55.8034 72.8889C54.5705 72.1813 53.439 71.3593 52.7646 70.0446C52.5765 69.6774 52.3293 69.341 52.0091 68.8295C46.4364 73.0477 42.4363 78.2233 39.673 84.6317ZM101.996 114.114C102.891 109.478 101.973 92.0606 100.667 88.55C100.325 88.55 99.9562 88.5111 99.5979 88.5556C93.2762 89.3338 86.9675 90.2424 80.6305 90.8585C77.3282 91.1795 73.9692 91.1462 70.6499 90.9833C67.4011 90.8228 64.1742 90.2651 60.9327 89.9336C54.6638 89.2916 48.4929 88.0977 42.3536 86.7173C41.3558 86.4927 40.3734 86.1993 39.2077 85.8905C36.0546 93.4791 35.5991 101.259 36.4113 109.164C40.7957 111.881 45.45 113.325 50.1635 114.65C55.4768 116.143 61.0041 116.234 66.3887 117.242C66.6051 117.282 66.8329 117.268 67.055 117.272C70.3159 117.327 73.5769 117.427 76.837 117.42C79.3563 117.415 81.8852 117.399 84.3899 117.17C87.4847 116.886 90.5592 116.382 93.6402 115.962C96.5007 115.573 99.3069 114.95 101.996 114.114ZM113.75 117.239C114.05 114.149 113.965 111.907 113.468 110.827C112.813 111.156 112.169 111.499 111.51 111.809C110.438 112.312 109.382 112.863 108.273 113.272C103.327 115.1 98.2799 116.55 93.0168 117.135C90.6613 117.397 88.3123 117.711 85.9535 117.93C83.9652 118.115 81.9679 118.218 79.9731 118.313C79.3133 118.345 78.6478 118.203 77.9848 118.207C75.0278 118.226 72.066 118.376 69.1138 118.269C66.5306 118.176 63.9594 117.769 61.3777 117.559C53.1034 116.881 45.1411 115.063 37.7317 111.179C37.4853 111.05 37.2008 110.992 36.8198 110.863C36.7347 111.246 36.6317 111.512 36.6236 111.781C36.4972 115.78 36.3626 119.781 36.2775 123.781C36.2459 125.262 36.3221 126.745 36.378 128.227C36.438 129.781 36.5815 131.333 36.6026 132.888C36.6512 136.52 36.6698 140.154 36.6601 143.788C36.648 148.427 37.7738 152.753 40.2453 156.681C44.2698 163.076 50.0475 167.091 57.3354 168.922C58.7523 169.278 60.2559 169.337 61.7255 169.42C64.3144 169.565 66.9091 169.631 69.5013 169.713C72.3059 169.803 75.0926 170.133 77.9199 169.903C81.4629 169.615 85.0254 169.506 88.5806 169.467C90.2301 169.449 91.8415 169.333 93.4108 168.874C103.936 165.787 110.99 159.276 113.246 148.279C113.807 145.545 113.763 142.688 113.981 139.887C113.989 139.78 113.825 139.66 113.676 139.457C113.013 139.669 112.314 139.889 111.616 140.113C107.162 141.54 102.663 142.758 97.9654 143.064C95.6496 143.215 93.3589 143.063 91.1598 142.31C87.9702 141.216 86.4342 138.655 87.1686 135.342C87.5633 133.556 88.4258 131.878 89.0296 130.135C89.4884 128.808 90.3776 127.875 91.4922 127.081C93.6264 125.562 96.0103 124.543 98.4169 123.573C103.362 121.579 108.318 119.61 113.267 117.624C113.398 117.571 113.498 117.444 113.75 117.239ZM68.9298 60.4214C68.2222 60.158 67.5438 59.9254 66.8823 59.6538C65.1809 58.9568 63.8419 57.8333 62.9738 56.1976C62.4599 55.2289 62.4469 54.8277 62.7727 53.3557C63.9416 53.6767 64.8486 54.8788 66.4001 54.4265C66.0969 54.0811 65.8626 53.7764 65.5919 53.5097C64.1369 52.0798 63.5874 50.2512 63.3288 48.3107C63.2291 47.5674 63.3328 46.8298 64.0056 46.1465C64.3517 46.2956 64.7376 46.4504 65.1137 46.6255C65.784 46.9376 66.4195 47.3688 67.1215 47.5641C72.7347 49.1245 75.4866 53.3249 77.2552 58.4501C77.4465 59.0046 77.5851 59.5768 77.7732 60.2285C79.691 60.5228 81.5886 60.7635 83.4667 61.1112C89.4884 62.225 94.9185 64.6988 99.6295 68.5961C108.707 76.1069 114.153 85.6619 115.542 97.446C116.043 101.695 115.847 105.88 114.683 110.01C114.543 110.503 114.424 111.043 114.479 111.542C114.672 113.287 114.945 115.023 115.207 116.898C121.324 115.303 127.024 113.077 132.482 110.218C132.934 110.46 133.315 110.662 133.72 110.879C133.912 110.812 134.127 110.759 134.322 110.664C136.383 109.656 138.408 108.571 140.511 107.662C141.361 107.295 142.385 107.14 143.315 107.187C144.758 107.261 145.245 108.22 144.644 109.508C144.458 109.906 144.175 110.259 143.88 110.72C144.041 111.235 144.219 111.791 144.385 112.349C145.138 114.882 144.788 117.337 143.833 119.74C143.198 121.339 141.963 122.388 140.516 123.208C139.359 123.863 138.173 124.469 136.9 125.15C136.856 126.116 136.813 127.064 136.759 128.211C134.189 129.641 131.592 131.218 128.879 132.562C124.962 134.502 120.993 136.345 116.981 138.078C115.614 138.669 114.963 139.429 115.034 140.888C115.073 141.701 115.004 142.519 115 143.335C114.982 147.325 114.084 151.073 112.316 154.683C111.033 157.303 109.557 159.757 107.626 161.914C104.756 165.117 101.251 167.431 97.2391 168.984C96.8606 169.13 96.5323 169.424 96.2121 169.688C96.1408 169.747 96.2016 169.966 96.2016 170.224C98.5206 170.641 100.798 171.241 102.617 173.181C102.287 175.207 101.814 177.221 100.259 178.733C98.8238 180.127 97.214 181.295 95.378 182.18C90.0323 184.757 84.4588 185.383 78.6916 184.206C77.1183 183.884 75.6244 183.169 73.7876 182.527C71.5853 183.008 69.0109 183.188 66.4154 183.231C63.0921 183.287 60.2997 181.887 57.9482 179.805C56.0596 178.132 55.1979 175.791 55.7021 173.153C55.771 172.794 55.8942 172.447 55.9996 172.067C55.0634 170.401 53.8508 169.263 52.0286 168.436C42.2255 163.99 36.8295 156.195 35.535 145.601C35.2149 142.977 35.6347 140.269 35.6015 137.602C35.5302 131.894 35.3308 126.187 35.2919 120.48C35.2708 117.443 35.4872 114.404 35.5667 111.365C35.5861 110.631 35.5739 109.875 35.4305 109.159C33.755 100.809 34.7934 92.7625 38.2261 85.028C41.2909 78.1227 45.7726 72.2842 51.8705 67.7653C56.3408 64.4524 61.2999 62.1934 66.7745 61.1104C67.4279 60.9816 68.0804 60.8494 68.7313 60.7108C68.7718 60.7019 68.7961 60.6208 68.9298 60.4214Z" fill="black"/> +<path d="M113.75 117.239C113.497 117.444 113.398 117.572 113.266 117.624C108.318 119.609 103.362 121.579 98.4168 123.573C96.0102 124.543 93.6263 125.561 91.4921 127.081C90.3775 127.875 89.4883 128.808 89.0295 130.135C88.4257 131.877 87.5632 133.556 87.1685 135.342C86.4349 138.655 87.9701 141.216 91.1597 142.31C93.3588 143.063 95.6503 143.215 97.9653 143.064C102.663 142.758 107.162 141.54 111.617 140.113C112.314 139.889 113.013 139.669 113.676 139.457C113.825 139.66 113.989 139.78 113.981 139.887C113.763 142.688 113.807 145.545 113.246 148.279C110.99 159.276 103.936 165.787 93.4107 168.874C91.8414 169.334 90.23 169.449 88.5805 169.467C85.0253 169.506 81.4628 169.615 77.9198 169.903C75.0925 170.132 72.3058 169.803 69.5012 169.714C66.909 169.631 64.3144 169.565 61.7254 169.42C60.2558 169.337 58.7522 169.278 57.3361 168.922C50.0483 167.092 44.2705 163.076 40.246 156.681C37.7746 152.753 36.6479 148.427 36.66 143.788C36.6698 140.154 36.6511 136.52 36.6025 132.887C36.5814 131.334 36.4379 129.78 36.3788 128.227C36.322 126.745 36.2458 125.262 36.2774 123.781C36.3625 119.78 36.4971 115.78 36.6236 111.781C36.6325 111.512 36.7354 111.246 36.8197 110.863C37.2007 110.992 37.4852 111.05 37.7316 111.179C45.1411 115.063 53.1033 116.881 61.3776 117.559C63.9593 117.769 66.5305 118.176 69.1138 118.27C72.0659 118.377 75.0277 118.226 77.9847 118.207C78.6477 118.203 79.3132 118.345 79.973 118.313C81.9678 118.218 83.9651 118.115 85.9534 117.93C88.3122 117.711 90.6612 117.397 93.0168 117.136C98.2806 116.55 103.326 115.1 108.273 113.272C109.381 112.863 110.438 112.312 111.51 111.808C112.169 111.499 112.813 111.156 113.468 110.827C113.965 111.907 114.05 114.149 113.75 117.239Z" fill="white"/> +<path d="M101.996 114.114C99.3066 114.95 96.5012 115.573 93.6407 115.962C90.5589 116.381 87.4844 116.886 84.3897 117.169C81.885 117.4 79.356 117.416 76.8375 117.421C73.5766 117.426 70.3157 117.327 67.0547 117.272C66.8326 117.268 66.6057 117.282 66.3893 117.242C61.0038 116.235 55.4765 116.143 50.1632 114.65C45.4497 113.325 40.7954 111.882 36.4118 109.164C35.5996 101.259 36.0552 93.4796 39.2075 85.891C40.3731 86.199 41.3555 86.4924 42.3533 86.7169C48.4926 88.0973 54.6635 89.2921 60.9325 89.9333C64.174 90.2656 67.4008 90.8233 70.6496 90.983C73.9689 91.1467 77.3279 91.1791 80.6302 90.859C86.9673 90.2429 93.276 89.3335 99.5976 88.5553C99.9559 88.5115 100.325 88.5496 100.667 88.5496C101.973 92.061 102.891 109.479 101.996 114.114Z" fill="white"/> +<path d="M39.6729 84.632C42.4361 78.2228 46.4355 73.0473 52.0082 68.8299C52.3291 69.3405 52.5764 69.6777 52.7644 70.0441C53.4388 71.3597 54.5704 72.1808 55.8033 72.8892C57.7511 74.007 59.8715 74.6992 62.0479 75.1515C70.3684 76.8813 78.6736 76.7881 86.9358 74.7381C87.9401 74.4893 88.9298 74.1642 89.9024 73.8092C93.8929 72.3518 95.0472 71.3702 96.9804 67.8272C102.218 71.2924 106.187 75.847 109.195 81.3881C107.355 84.0281 105.025 85.7733 102.072 86.562C97.2738 87.8435 92.3755 88.6305 87.4586 89.3082C82.9745 89.9258 78.475 90.1844 73.9479 90.1536C68.7441 90.1188 63.5953 89.5254 58.4911 88.6168C52.809 87.6052 47.1642 86.3893 41.5072 85.2432C40.9503 85.1305 40.421 84.8857 39.6729 84.632Z" fill="white"/> +<path d="M132.432 111.51C134.253 116.781 135.682 121.861 135.759 127.281C135.085 127.739 134.564 128.147 133.994 128.472C125.789 133.137 117.274 137.111 108.215 139.827C104.017 141.085 99.7522 142.075 95.3232 141.869C94.0798 141.811 92.7991 141.643 91.6213 141.261C88.9667 140.399 87.9097 138.608 88.1521 135.795C88.349 133.513 89.3161 131.514 90.493 129.617C90.827 129.077 91.4341 128.686 91.9626 128.291C94.1195 126.675 96.5828 125.644 99.051 124.629C102.134 123.36 105.199 122.044 108.315 120.863C111.498 119.657 114.715 118.528 117.953 117.476C122.397 116.032 126.82 114.556 130.898 112.215C131.275 111.998 131.69 111.848 132.432 111.51Z" fill="white"/> +<path d="M53.8006 67.543C58.8003 64.1296 64.3121 62.1656 70.3412 61.6079C71.1809 61.5301 71.8278 61.7944 72.2379 62.4793C72.8053 63.4269 73.3687 64.3939 73.7829 65.4136C74.2246 66.4989 74.9104 67.1036 76.17 67.179C77.9849 65.8067 77.381 63.5509 77.9889 61.6671C78.468 61.6112 78.8992 61.488 79.3175 61.522C84.48 61.9468 89.3726 63.2996 93.9037 65.8537C96.1215 67.1036 96.3444 68.463 94.4225 70.1222C93.3761 71.0252 92.1237 71.7555 90.8568 72.3262C87.3024 73.9278 83.4822 74.5763 79.6425 74.9151C73.9409 75.4185 68.2272 75.4169 62.6148 74.0778C60.6808 73.6166 58.7897 72.9268 56.94 72.1884C56.0711 71.8422 55.3018 71.1873 54.5707 70.5737C53.718 69.8588 53.3305 68.9242 53.8006 67.543Z" fill="white"/> +<path d="M109.869 83.1941C113.348 88.5463 115.511 101.252 113.867 109.22C110.758 111.238 107.299 112.641 103.438 113.612C103.373 112.933 103.274 112.371 103.273 111.811C103.269 108.547 103.302 105.284 103.296 102.02C103.287 97.7131 103.073 93.4227 102.191 89.1891C102.132 88.9103 102.183 88.6087 102.183 88.2213C104.796 86.7152 107.548 85.4094 109.869 83.1941Z" fill="#CAE4E4"/> +<path d="M94.2448 173.765C94.5561 173.206 94.9103 172.569 95.2848 171.895C97.6816 171.204 99.5889 172.164 101.428 173.412C100.866 176.973 98.5587 179.007 95.7946 180.552C90.5705 183.473 84.9759 184.512 79.0871 183.169C77.8348 182.883 76.6635 182.116 75.5457 181.423C75.1015 181.148 74.7035 180.501 74.633 179.977C74.4206 178.38 74.2966 176.761 74.5998 174.937C81.244 175.896 87.7456 175.714 94.2448 173.765Z" fill="white"/> +<path d="M56.8403 173.057C59.6652 173.627 62.2476 174.312 64.8739 174.633C67.4888 174.952 70.1483 174.904 72.9488 175.024C73.0542 177.057 73.1612 179.1 73.2803 181.392C72.8872 181.519 72.3644 181.796 71.8221 181.841C70.1256 181.98 68.4185 182.001 66.7196 182.124C63.7091 182.343 61.1566 181.096 58.9543 179.32C57.0746 177.805 56.4674 175.558 56.8403 173.057Z" fill="white"/> +<path d="M134.062 112.02C136.525 110.288 139.17 109.182 141.924 108.33C142.437 108.17 143.042 108.306 143.871 108.306C143.592 108.899 143.445 109.275 143.244 109.618C143.061 109.931 142.818 110.209 142.592 110.516C142.876 111.545 143.254 112.517 143.391 113.521C143.528 114.536 143.498 115.589 143.42 116.616C143.124 120.503 141.098 122.687 137.023 123.506C136.897 123.195 136.721 122.878 136.637 122.537C135.773 119.045 134.923 115.549 134.062 112.02Z" fill="white"/> +<path d="M64.4563 47.7209C66.0734 48.3434 67.5851 48.9173 69.0904 49.5074C71.6015 50.4914 73.0257 52.6338 74.2732 54.7899C76.0183 57.8036 76.6538 61.1399 76.1464 64.6335C76.0864 65.0485 75.9235 65.4481 75.7079 66.2084C75.2232 65.5867 74.8649 65.2609 74.6744 64.8564C74.0754 63.587 73.521 62.3298 72.5353 61.2591C71.3381 59.9573 70.0168 58.9935 68.2498 58.7082C66.8888 58.4893 65.7241 57.8603 64.7627 56.899C64.3704 56.5075 64.1248 55.9685 63.6636 55.2706C65.5781 55.2932 67.2131 55.8987 68.9744 55.3784C68.6527 54.4008 67.8445 54.0823 67.2139 53.6599C65.4063 52.4506 64.5949 50.707 64.3355 48.6368C64.3015 48.371 64.4036 48.0881 64.4563 47.7209Z" fill="white"/> +<path d="M94.2306 170.022C93.7524 172.852 93.5919 172.952 91.1634 173.507C86.7239 174.521 82.2309 174.533 77.729 174.303C76.7231 174.252 75.7115 173.897 74.7485 173.552C73.9347 173.26 73.6113 172.507 73.8253 171.795C74.0401 171.082 74.635 171.009 75.2438 171.005C76.351 171.001 77.4599 171.062 78.5647 171.013C82.5454 170.839 86.5253 170.647 90.5036 170.424C91.7325 170.355 92.954 170.163 94.2306 170.022Z" fill="white"/> +<path d="M56.3 170.162C61.5809 170.498 66.8488 170.834 72.2918 171.18C72.3826 171.886 72.4621 172.505 72.5439 173.146C70.8304 173.862 69.1955 173.623 67.5889 173.618C66.7029 173.615 65.8121 173.457 64.9327 173.513C62.8892 173.644 60.9835 172.996 59.0495 172.547C57.8183 172.262 57.4324 171.807 56.3 170.162Z" fill="white"/> +<path d="M21.5141 64.4412C22.6241 64.3912 23.8841 65.5912 23.8541 66.6712C23.8341 67.5112 22.5341 68.6112 21.5141 68.6612C20.4341 68.7112 19.4641 67.7212 19.4641 66.5612C19.4641 65.3812 20.3141 64.5012 21.5141 64.4512V64.4412ZM21.2941 68.0112C21.8641 67.5112 22.5041 67.2112 22.5641 66.8312C22.6241 66.4512 22.0841 65.9912 21.8141 65.5712C21.4241 65.8712 20.8041 66.1112 20.7041 66.4912C20.6041 66.8512 21.0241 67.3712 21.3041 68.0212L21.2941 68.0112Z" fill="black"/> +<path d="M57.8341 13.7912C57.7741 15.0512 56.7141 16.0712 55.5141 16.0112C54.3241 15.9512 53.5641 15.1612 53.6741 14.0912C53.7841 12.9412 54.7941 11.9612 55.8141 12.0012C57.0141 12.0512 57.8841 12.8212 57.8441 13.7912H57.8341ZM55.6741 12.8212C55.2641 13.3912 54.8741 13.7512 54.7741 14.1812C54.7341 14.3612 55.4741 14.9512 55.5941 14.8912C55.9941 14.6612 56.3641 14.2612 56.5541 13.8412C56.6141 13.7012 56.0941 13.3012 55.6741 12.8212Z" fill="black"/> +<path d="M81.2641 35.9812C80.7641 36.8412 80.2641 37.7012 79.7741 38.5612C78.7641 37.8112 77.7641 37.0612 76.7041 36.2712C77.2441 35.2812 77.7641 34.3312 78.3441 33.2612C79.1341 34.4112 79.8841 35.5012 80.6441 36.6012C80.8541 36.3912 81.0641 36.1912 81.2741 35.9812H81.2641Z" fill="white"/> +<path d="M21.294 68.0212C21.014 67.3712 20.594 66.8612 20.694 66.4912C20.804 66.1112 21.414 65.8712 21.804 65.5712C22.084 65.9912 22.614 66.4612 22.554 66.8312C22.494 67.2112 21.864 67.5112 21.284 68.0112L21.294 68.0212Z" fill="white"/> +<path d="M65.674 2.81122C66.094 3.28122 66.614 3.68122 66.554 3.83122C66.374 4.25122 65.994 4.65122 65.594 4.88122C65.474 4.95122 64.734 4.35122 64.774 4.17122C64.874 3.75122 65.264 3.39122 65.674 2.81122Z" fill="white"/> </svg> From d15f5e779cf81d0f05376f51442d9e360e56e56c Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Tue, 10 Jan 2023 10:22:30 +0100 Subject: [PATCH 48/65] feat(ui) - insights - data format --- .../InsightsCard/InsightItem.tsx | 1 + .../InsightsCard/InsightsCard.tsx | 14 +- frontend/app/mstore/dashboardStore.ts | 73 +---- frontend/app/mstore/types/widget.ts | 271 +++++++++++------- 4 files changed, 178 insertions(+), 181 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx index 41ac503be..2ee037fd6 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx @@ -8,6 +8,7 @@ interface Props { function InsightItem(props: Props) { const { item, onClick = () => {} } = props; return ( + // TODO update according to the new response format <div className="flex items-center py-4 hover:bg-active-blue -mx-4 px-4 border-b last:border-transparent cursor-pointer" onClick={onClick} diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx index 489c76fd2..0b2d3fa24 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx @@ -1,15 +1,9 @@ +import { NoContent } from 'App/components/ui'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; import React from 'react'; import InsightItem from './InsightItem'; -const data = [ - { icon: 'dizzy', ratio: 'Click Rage', increase: 10, iconColor: 'red' }, - { icon: 'dizzy', ratio: 'Click Rage', increase: 10, iconColor: 'yello' }, - { icon: 'dizzy', ratio: 'Click Rage', increase: 10, iconColor: 'green' }, - { icon: 'dizzy', ratio: 'Click Rage', increase: 10, iconColor: 'gray' }, - { icon: 'dizzy', ratio: 'Click Rage', increase: 10, iconColor: 'red' }, -]; interface Props {} function InsightsCard(props: Props) { const { metricStore, dashboardStore } = useStore(); @@ -29,11 +23,11 @@ function InsightsCard(props: Props) { }; return ( - <div> - {data.map((item) => ( + <NoContent> + {metric.data.issues.map((item: any) => ( <InsightItem item={item} onClick={clickHanddler} /> ))} - </div> + </NoContent> ); } diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 141cc6761..5b89e82d5 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -429,77 +429,8 @@ export default class DashboardStore { return metricService .getMetricChartData(metric, params, isWidget) .then((data: any) => { - if ( - metric.metricType === "predefined" && - metric.viewType === "overview" - ) { - const _data = { - ...data, - chart: getChartFormatter(period)(data.chart), - }; - metric.setData(_data); - resolve(_data); - } else if (metric.metricType === "funnel") { - const _data = { ...data }; - _data.funnel = new Funnel().fromJSON(data); - metric.setData(_data); - resolve(_data); - } else { - const _data = { - ...data, - }; - - // TODO refactor to widget class - if (metric.metricOf === FilterKey.SESSIONS) { - _data["sessions"] = data.sessions.map((s: any) => - new Session().fromJson(s) - ); - } else if (metric.metricOf === FilterKey.ERRORS) { - _data["errors"] = data.errors.map((s: any) => - new Error().fromJSON(s) - ); - } else { - if (data.hasOwnProperty("chart")) { - _data["chart"] = getChartFormatter(period)( - data.chart - ); - _data["namesMap"] = data.chart - .map((i: any) => Object.keys(i)) - .flat() - .filter( - (i: any) => i !== "time" && i !== "timestamp" - ) - .reduce((unique: any, item: any) => { - if (!unique.includes(item)) { - unique.push(item); - } - return unique; - }, []); - } else { - _data["chart"] = getChartFormatter(period)( - 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) => { - if (!unique.includes(item)) { - unique.push(item); - } - return unique; - }, []) - : []; - } - } - metric.setData(_data); - resolve(_data); - } + metric.setData(data, period); + resolve(metric.data); }) .catch((err: any) => { reject(err); diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index e712cc224..37d7247e5 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -1,13 +1,15 @@ -import { makeAutoObservable, runInAction } from "mobx" -import FilterSeries from "./filterSeries"; +import { makeAutoObservable, runInAction } from 'mobx'; +import FilterSeries from './filterSeries'; import { DateTime } from 'luxon'; -import Session from "App/mstore/types/session"; +import Session from 'App/mstore/types/session'; import Funnelissue from 'App/mstore/types/funnelIssue'; import { issueOptions, issueCategories } from 'App/constants/filterOptions'; import { FilterKey } from 'Types/filter/filterType'; import Period, { LAST_24_HOURS } from 'Types/app/period'; -import { metricService } from "App/services"; -import { INSIGHTS, TABLE, WEB_VITALS } from "App/constants/card"; +import { metricService } from 'App/services'; +import { INSIGHTS, TABLE, WEB_VITALS } from 'App/constants/card'; +import Error from '../types/error'; +import { getChartFormatter } from 'Types/dashboard/helper'; export default class Widget { public static get ID_KEY():string { return "metricId" } @@ -33,12 +35,13 @@ export default class Widget { thumbnail?: string params: any = { density: 70 } - period: Record<string, any> = Period({ rangeName: LAST_24_HOURS }) // temp value in detail view - hasChanged: boolean = false + period: Record<string, any> = Period({ rangeName: LAST_24_HOURS }); // temp value in detail view + hasChanged: boolean = false; position: number = 0 data: any = { sessions: [], + issues: [], total: 0, chart: [], namesMap: {}, @@ -50,54 +53,59 @@ export default class Widget { dashboardId: any = undefined predefinedKey: string = '' - constructor() { - makeAutoObservable(this) + constructor() { + makeAutoObservable(this); - const filterSeries = new FilterSeries() - this.series.push(filterSeries) - } + const filterSeries = new FilterSeries(); + this.series.push(filterSeries); + } - updateKey(key: string, value: any) { - this[key] = value - } + updateKey(key: string, value: any) { + this[key] = value; + } - removeSeries(index: number) { - this.series.splice(index, 1) - } + removeSeries(index: number) { + this.series.splice(index, 1); + } - addSeries() { - const series = new FilterSeries() - series.name = "Series " + (this.series.length + 1) - this.series.push(series) - } + addSeries() { + const series = new FilterSeries(); + series.name = 'Series ' + (this.series.length + 1); + this.series.push(series); + } - fromJson(json: any, period?: any) { - json.config = json.config || {} - runInAction(() => { - this.metricId = json.metricId - this.widgetId = json.widgetId - this.metricValue = this.metricValueFromArray(json.metricValue, json.metricType) - this.metricOf = json.metricOf - this.metricType = json.metricType - this.metricFormat = json.metricFormat - this.viewType = json.viewType - this.name = json.name - this.series = json.series ? json.series.map((series: any) => new FilterSeries().fromJson(series)) : [] - this.dashboards = json.dashboards || [] - this.owner = json.ownerEmail - this.lastModified = json.editedAt || json.createdAt ? DateTime.fromMillis(json.editedAt || json.createdAt) : null - this.config = json.config - this.position = json.config.position - this.predefinedKey = json.predefinedKey - this.category = json.category - this.thumbnail = json.thumbnail + fromJson(json: any, period?: any) { + json.config = json.config || {}; + runInAction(() => { + this.metricId = json.metricId; + this.widgetId = json.widgetId; + this.metricValue = this.metricValueFromArray(json.metricValue, json.metricType); + this.metricOf = json.metricOf; + this.metricType = json.metricType; + this.metricFormat = json.metricFormat; + this.viewType = json.viewType; + this.name = json.name; + this.series = json.series + ? json.series.map((series: any) => new FilterSeries().fromJson(series)) + : []; + this.dashboards = json.dashboards || []; + this.owner = json.ownerEmail; + this.lastModified = + json.editedAt || json.createdAt + ? DateTime.fromMillis(json.editedAt || json.createdAt) + : null; + this.config = json.config; + this.position = json.config.position; + this.predefinedKey = json.predefinedKey; + this.category = json.category; + this.thumbnail = json.thumbnail; - if (period) { - this.period = period - } - }) - return this - } + if (period) { + this.period = period; + } + }); + return this; + } toWidget(): any { return { @@ -128,61 +136,124 @@ export default class Widget { } } - validate() { - this.isValid = this.name.length > 0 + validate() { + this.isValid = this.name.length > 0; + } + + update(data: any) { + runInAction(() => { + Object.assign(this, data); + }); + } + + exists() { + return this.metricId !== undefined; + } + + setData(data: any, period: any) { + const _data: any = {}; + + if (this.metricOf === FilterKey.ERRORS) { + _data['errors'] = data.errors.map((s: any) => new Error().fromJSON(s)); + } else if (this.metricType === INSIGHTS) { + // TODO read fromt the response + _data['issues'] = [1, 2, 3].map((i: any) => ({ + icon: 'dizzy', + ratio: 'Click Rage', + increase: 10, + iconColor: 'red', + })); + } else { + if (data.hasOwnProperty('chart')) { + _data['chart'] = getChartFormatter(period)(data.chart); + _data['namesMap'] = data.chart + .map((i: any) => Object.keys(i)) + .flat() + .filter((i: any) => i !== 'time' && i !== 'timestamp') + .reduce((unique: any, item: any) => { + if (!unique.includes(item)) { + unique.push(item); + } + return unique; + }, []); + } else { + _data['chart'] = getChartFormatter(period)(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) => { + if (!unique.includes(item)) { + unique.push(item); + } + return unique; + }, []) + : []; + } } - update(data: any) { - runInAction(() => { - Object.assign(this, data) + this.data = _data; + } + + fetchSessions(metricId: any, filter: any): Promise<any> { + return new Promise((resolve) => { + metricService.fetchSessions(metricId, filter).then((response: any[]) => { + resolve( + response.map((cat: { sessions: any[] }) => { + return { + ...cat, + sessions: cat.sessions.map((s: any) => new Session().fromJson(s)), + }; + }) + ); + }); + }); + } + + fetchIssues(filter: any): Promise<any> { + return new Promise((resolve) => { + metricService.fetchIssues(filter).then((response: any) => { + const significantIssues = response.issues.significant + ? response.issues.significant.map((issue: any) => new Funnelissue().fromJSON(issue)) + : []; + const insignificantIssues = response.issues.insignificant + ? response.issues.insignificant.map((issue: any) => new Funnelissue().fromJSON(issue)) + : []; + resolve({ + issues: significantIssues.length > 0 ? significantIssues : insignificantIssues, + }); + }); + }); + } + + fetchIssue(funnelId: any, issueId: any, params: any): Promise<any> { + return new Promise((resolve, reject) => { + metricService + .fetchIssue(funnelId, issueId, params) + .then((response: any) => { + resolve({ + issue: new Funnelissue().fromJSON(response.issue), + sessions: response.sessions.sessions.map((s: any) => new Session().fromJson(s)), + }); }) - } + .catch((error: any) => { + reject(error); + }); + }); + } - exists() { - return this.metricId !== undefined + private metricValueFromArray(metricValue: any, metricType: string) { + if (!Array.isArray(metricValue)) return metricValue; + if (metricType === TABLE) { + return issueOptions.filter((i: any) => metricValue.includes(i.value)); + } else if (metricType === INSIGHTS) { + return issueCategories.filter((i: any) => metricValue.includes(i.value)); } + } - setData(data: any) { - this.data = data; - } - - fetchSessions(metricId: any, filter: any): Promise<any> { - return new Promise((resolve) => { - metricService.fetchSessions(metricId, filter).then((response: any[]) => { - resolve(response.map((cat: { sessions: any[]; }) => { - return { - ...cat, - sessions: cat.sessions.map((s: any) => new Session().fromJson(s)) - } - })) - }) - }) - } - - fetchIssue(funnelId: any, issueId: any, params: any): Promise<any> { - return new Promise((resolve, reject) => { - metricService.fetchIssue(funnelId, issueId, params).then((response: any) => { - resolve({ - issue: new Funnelissue().fromJSON(response.issue), - sessions: response.sessions.sessions.map((s: any) => new Session().fromJson(s)), - }) - }).catch((error: any) => { - reject(error) - }) - }) - } - - private metricValueFromArray(metricValue: any, metricType: string) { - if (!Array.isArray(metricValue)) return metricValue; - if (metricType === TABLE) { - return issueOptions.filter((i: any) => metricValue.includes(i.value)) - } else if (metricType === INSIGHTS) { - return issueCategories.filter((i: any) => metricValue.includes(i.value)) - } - } - - private metricValueToArray(metricValue: any) { - if (!Array.isArray(metricValue)) return metricValue; - return metricValue.map((i: any) => i.value) - } + private metricValueToArray(metricValue: any) { + if (!Array.isArray(metricValue)) return metricValue; + return metricValue.map((i: any) => i.value); + } } From aa70ee2841be4c3c78f997931dacfcb32d12ceaf Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Tue, 10 Jan 2023 16:12:14 +0100 Subject: [PATCH 49/65] change(ui) - insights - no content message --- .../CustomMetricsWidgets/InsightsCard/InsightsCard.tsx | 9 +++++++-- .../Dashboard/components/WidgetView/WidgetView.tsx | 4 ---- frontend/app/mstore/types/widget.ts | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx index 0b2d3fa24..465e2c7aa 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx @@ -1,8 +1,9 @@ -import { NoContent } from 'App/components/ui'; +import { NoContent } from 'UI'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; import React from 'react'; import InsightItem from './InsightItem'; +import { NO_METRIC_DATA } from 'App/constants/messages'; interface Props {} function InsightsCard(props: Props) { @@ -23,7 +24,11 @@ function InsightsCard(props: Props) { }; return ( - <NoContent> + <NoContent + show={metric.data.issues.length === 0} + title={NO_METRIC_DATA} + style={{ padding: '100px 0' }} + > {metric.data.issues.map((item: any) => ( <InsightItem item={item} onClick={clickHanddler} /> ))} diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx index b65a9d28d..97aacecdc 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -18,10 +18,6 @@ import { TABLE, CLICKMAP, FUNNEL, - ERRORS, - RESOURCE_MONITORING, - PERFORMANCE, - WEB_VITALS, INSIGHTS, } from 'App/constants/card'; diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index 37d7247e5..ebdf6a483 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -99,6 +99,7 @@ export default class Widget { this.predefinedKey = json.predefinedKey; this.category = json.category; this.thumbnail = json.thumbnail; + this.isPublic = json.isPublic; if (period) { this.period = period; From 1a1ec33d7dd41ad6acfd65693f7fa0415564e971 Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Tue, 10 Jan 2023 17:48:02 +0100 Subject: [PATCH 50/65] change(ui) - insights - row item with random data and other fixes --- .../InsightsCard/InsightItem.tsx | 20 +++++-- .../InsightsCard/InsightsCard.tsx | 4 +- frontend/app/components/ui/SVG.tsx | 6 +- frontend/app/constants/filterOptions.js | 5 ++ frontend/app/mstore/types/widget.ts | 57 +++++++++++++++---- frontend/app/svg/icons/ic-errors.svg | 1 + frontend/app/svg/icons/ic-network.svg | 1 + frontend/app/svg/icons/ic-rage.svg | 1 + frontend/app/svg/icons/ic-resources.svg | 1 + frontend/app/types/filter/filterType.ts | 4 +- 10 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 frontend/app/svg/icons/ic-errors.svg create mode 100644 frontend/app/svg/icons/ic-network.svg create mode 100644 frontend/app/svg/icons/ic-rage.svg create mode 100644 frontend/app/svg/icons/ic-resources.svg diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx index 2ee037fd6..db81f9afb 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx @@ -1,3 +1,4 @@ +import { IssueCategory } from 'App/types/filter/filterType'; import React from 'react'; import { Icon } from 'UI'; @@ -8,17 +9,24 @@ interface Props { function InsightItem(props: Props) { const { item, onClick = () => {} } = props; return ( - // TODO update according to the new response format <div className="flex items-center py-4 hover:bg-active-blue -mx-4 px-4 border-b last:border-transparent cursor-pointer" onClick={onClick} > <Icon name={item.icon} size={20} className="mr-2" color={item.iconColor} /> - <div className="mx-1 font-medium">{item.ratio}</div> - <div className="mx-1">on</div> - <div className="mx-1 bg-gray-100 px-2 rounded">Update</div> - <div className="mx-1">increased by</div> - <div className="font-medium text-red">{item.increase}</div> + <div className="mx-1 font-medium">{item.label}</div> + {item.category === IssueCategory.RAGE && ( + <> + <div className="mx-1">on</div> + <div className="mx-1 bg-gray-100 px-2 rounded">{item.name}</div> + </> + )} + {item.increase && ( + <> + <div className="mx-1">increased by</div> + <div className="font-medium text-red">{item.increase}%</div> + </> + )} </div> ); } diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx index 465e2c7aa..78d384c21 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx @@ -25,11 +25,11 @@ function InsightsCard(props: Props) { return ( <NoContent - show={metric.data.issues.length === 0} + show={metric.data.issues && metric.data.issues.length === 0} title={NO_METRIC_DATA} style={{ padding: '100px 0' }} > - {metric.data.issues.map((item: any) => ( + {metric.data.issues && metric.data.issues.map((item: any) => ( <InsightItem item={item} onClick={clickHanddler} /> ))} </NoContent> diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index e8fb24f3d..22fe3491f 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -1,7 +1,7 @@ import React from 'react'; -export type IconNames = 'activity' | 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-counterclockwise' | 'arrow-down-short' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up-short' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-checklist' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cubes' | 'dash' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | 'errors-icon' | 'event/click' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/link' | 'event/location' | 'event/resize' | 'event/view' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'gear' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-1x2' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grid' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-arrow' | 'list-ul' | 'list' | 'lock-alt' | 'magic' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'percent' | 'performance-icon' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus' | 'pointer-sessions-search' | 'prev1' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'quotes' | 'record-circle' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signpost-split' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; +export type IconNames = 'activity' | 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-counterclockwise' | 'arrow-down-short' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up-short' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-checklist' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cubes' | 'dash' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | 'errors-icon' | 'event/click' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/link' | 'event/location' | 'event/resize' | 'event/view' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'gear' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-1x2' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grid' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'ic-errors' | 'ic-network' | 'ic-rage' | 'ic-resources' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-arrow' | 'list-ul' | 'list' | 'lock-alt' | 'magic' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'percent' | 'performance-icon' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus' | 'pointer-sessions-search' | 'prev1' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'quotes' | 'record-circle' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signpost-split' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; interface Props { name: IconNames; @@ -259,6 +259,10 @@ const SVG = (props: Props) => { case 'high-engagement': return <svg viewBox="0 0 640 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="m638.59 368.22-33.37-211.59c-8.86-50.26-48.4-90.77-100.66-103.13h-.07a803.14 803.14 0 0 0-369 0C83.17 65.86 43.64 106.36 34.78 156.63L1.41 368.22C-8.9 426.73 38.8 480 101.51 480c49.67 0 93.77-30.07 109.48-74.64l7.52-21.36h203l7.49 21.36C444.72 449.93 488.82 480 538.49 480c62.71 0 110.41-53.27 100.1-111.78zm-45.11 54.88c-13.28 15.82-33.33 24.9-55 24.9-36.2 0-68.07-21.41-79.29-53.27l-7.53-21.36-7.52-21.37H195.86l-7.53 21.37-7.53 21.36C169.58 426.59 137.71 448 101.51 448c-21.66 0-41.71-9.08-55-24.9A59.93 59.93 0 0 1 33 373.2l33.28-211c6.66-37.7 36.72-68.14 76.53-77.57a771.07 771.07 0 0 1 354.38 0c39.84 9.42 69.87 39.86 76.42 77l33.47 212.15c3.11 17.64-1.72 35.16-13.6 49.32zm-339.3-218.74h-42.54v-42.54a9.86 9.86 0 0 0-9.82-9.82h-19.64a9.86 9.86 0 0 0-9.82 9.82v42.54h-42.54a9.86 9.86 0 0 0-9.82 9.82v19.64a9.86 9.86 0 0 0 9.82 9.82h42.54v42.54a9.86 9.86 0 0 0 9.82 9.82h19.64a9.86 9.86 0 0 0 9.82-9.82v-42.54h42.54a9.86 9.86 0 0 0 9.82-9.82v-19.64a9.86 9.86 0 0 0-9.82-9.82zM416 224a32 32 0 1 0 32 32 32 32 0 0 0-32-32zm64-64a32 32 0 1 0 32 32 32 32 0 0 0-32-32z"/></svg>; case 'history': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M20 24h10c6.627 0 12 5.373 12 12v94.625C85.196 57.047 165.239 7.715 256.793 8.001 393.18 8.428 504.213 120.009 504 256.396 503.786 393.181 392.834 504 256 504c-63.926 0-122.202-24.187-166.178-63.908-5.113-4.618-5.354-12.561-.482-17.433l7.069-7.069c4.503-4.503 11.749-4.714 16.482-.454C150.782 449.238 200.935 470 256 470c117.744 0 214-95.331 214-214 0-117.744-95.331-214-214-214-82.862 0-154.737 47.077-190.289 116H164c6.627 0 12 5.373 12 12v10c0 6.627-5.373 12-12 12H20c-6.627 0-12-5.373-12-12V36c0-6.627 5.373-12 12-12zm321.647 315.235 4.706-6.47c3.898-5.36 2.713-12.865-2.647-16.763L272 263.853V116c0-6.627-5.373-12-12-12h-8c-6.627 0-12 5.373-12 12v164.147l84.884 61.734c5.36 3.899 12.865 2.714 16.763-2.646z"/></svg>; case 'hourglass-start': return <svg viewBox="0 0 384 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M368 32h4c6.627 0 12-5.373 12-12v-8c0-6.627-5.373-12-12-12H12C5.373 0 0 5.373 0 12v8c0 6.627 5.373 12 12 12h4c0 91.821 44.108 193.657 129.646 224C59.832 286.441 16 388.477 16 480h-4c-6.627 0-12 5.373-12 12v8c0 6.627 5.373 12 12 12h360c6.627 0 12-5.373 12-12v-8c0-6.627-5.373-12-12-12h-4c0-91.821-44.108-193.657-129.646-224C324.168 225.559 368 123.523 368 32zM48 32h288c0 110.457-64.471 200-144 200S48 142.457 48 32zm288 448H48c0-110.457 64.471-200 144-200s144 89.543 144 200zM285.621 96H98.379a12.01 12.01 0 0 1-11.602-8.903 199.464 199.464 0 0 1-2.059-8.43C83.054 71.145 88.718 64 96.422 64h191.157c7.704 0 13.368 7.145 11.704 14.667a199.464 199.464 0 0 1-2.059 8.43A12.013 12.013 0 0 1 285.621 96zm-15.961 50.912a141.625 141.625 0 0 1-6.774 8.739c-2.301 2.738-5.671 4.348-9.248 4.348H130.362c-3.576 0-6.947-1.61-9.248-4.348a142.319 142.319 0 0 1-6.774-8.739c-5.657-7.91.088-18.912 9.813-18.912h135.694c9.725 0 15.469 11.003 9.813 18.912z"/></svg>; + case 'ic-errors': return <svg viewBox="0 0 496 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg>; + case 'ic-network': return <svg viewBox="0 0 496 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg>; + case 'ic-rage': return <svg viewBox="0 0 496 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg>; + case 'ic-resources': return <svg viewBox="0 0 496 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg>; case 'id-card': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/><path d="M3 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0-5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5v-1z"/></svg>; case 'image': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M4.502 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/><path d="M14.002 13a2 2 0 0 1-2 2h-10a2 2 0 0 1-2-2V5A2 2 0 0 1 2 3a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v8a2 2 0 0 1-1.998 2zM14 2H4a1 1 0 0 0-1 1h9.002a2 2 0 0 1 2 2v7A1 1 0 0 0 15 11V3a1 1 0 0 0-1-1zM2.002 4a1 1 0 0 0-1 1v8l2.646-2.354a.5.5 0 0 1 .63-.062l2.66 1.773 3.71-3.71a.5.5 0 0 1 .577-.094l1.777 1.947V5a1 1 0 0 0-1-1h-10z"/></svg>; case 'info-circle-fill': return <svg viewBox="0 0 36 36" width={ `${ width }px` } height={ `${ height }px` } ><path d="M17.75 35.5a17.75 17.75 0 1 0 0-35.5 17.75 17.75 0 0 0 0 35.5Zm2.064-20.883-2.22 10.44c-.155.754.065 1.182.675 1.182.43 0 1.08-.155 1.522-.546l-.195.923c-.637.768-2.041 1.327-3.25 1.327-1.56 0-2.224-.937-1.793-2.927l1.637-7.694c.142-.65.014-.886-.637-1.043l-1-.18.182-.845 5.08-.637h-.002Zm-2.064-2.414a2.219 2.219 0 1 1 0-4.437 2.219 2.219 0 0 1 0 4.437Z"/></svg>; diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index edd899d2c..af54c35ab 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -125,6 +125,11 @@ export const issueCategories = [ { label: 'Errors', value: IssueCategory.ERRORS }, ] +export const issueCategoriesMap = issueCategories.reduce((acc, {value, label}) => { + acc[value] = label; + return acc; +}, {}) + export default { options, baseOperators, diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index ebdf6a483..6d1e7d48e 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -3,7 +3,7 @@ import FilterSeries from './filterSeries'; import { DateTime } from 'luxon'; import Session from 'App/mstore/types/session'; import Funnelissue from 'App/mstore/types/funnelIssue'; -import { issueOptions, issueCategories } from 'App/constants/filterOptions'; +import { issueOptions, issueCategories, issueCategoriesMap } from 'App/constants/filterOptions'; import { FilterKey } from 'Types/filter/filterType'; import Period, { LAST_24_HOURS } from 'Types/app/period'; import { metricService } from 'App/services'; @@ -11,6 +11,47 @@ import { INSIGHTS, TABLE, WEB_VITALS } from 'App/constants/card'; import Error from '../types/error'; import { getChartFormatter } from 'Types/dashboard/helper'; +export class InishtIssue { + icon: string; + iconColor: string; + increase: number; + isNew = false; + category: string; + label: string; + + constructor( + category: string, + public name: string, + public ratio: number, + increase = 0, + isNew = false + ) { + this.category = category; + // @ts-ignore + this.label = issueCategoriesMap[category]; + this.icon = `ic-${category}`; + this.iconColor = 'red'; + + this.increase = increase; + this.isNew = isNew; + } +} + +function generateRandomString(stringArray: string[]): string { + const randomIndex = Math.floor(Math.random() * stringArray.length); + return stringArray[randomIndex]; +} + +function randomIssue() { + return new InishtIssue( + generateRandomString(['rage', 'resources', 'network', 'errors']), + generateRandomString(['Login', 'Update', '/sessions/data', 'Reload']), + Math.floor(Math.random() * 50), + Math.floor(Math.random() * 100), + false + ); +} + export default class Widget { public static get ID_KEY():string { return "metricId" } metricId: any = undefined @@ -85,9 +126,9 @@ export default class Widget { this.metricFormat = json.metricFormat; this.viewType = json.viewType; this.name = json.name; - this.series = json.series + this.series = (json.series && json.series.length > 0) ? json.series.map((series: any) => new FilterSeries().fromJson(series)) - : []; + : [new FilterSeries()]; this.dashboards = json.dashboards || []; this.owner = json.ownerEmail; this.lastModified = @@ -157,13 +198,9 @@ export default class Widget { if (this.metricOf === FilterKey.ERRORS) { _data['errors'] = data.errors.map((s: any) => new Error().fromJSON(s)); } else if (this.metricType === INSIGHTS) { - // TODO read fromt the response - _data['issues'] = [1, 2, 3].map((i: any) => ({ - icon: 'dizzy', - ratio: 'Click Rage', - increase: 10, - iconColor: 'red', - })); + // TODO read from the response + _data['issues'] = [1, 2, 3, 4].map((i: any) => randomIssue()); + console.log('_data', _data); } else { if (data.hasOwnProperty('chart')) { _data['chart'] = getChartFormatter(period)(data.chart); diff --git a/frontend/app/svg/icons/ic-errors.svg b/frontend/app/svg/icons/ic-errors.svg new file mode 100644 index 000000000..4f026cd64 --- /dev/null +++ b/frontend/app/svg/icons/ic-errors.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/ic-network.svg b/frontend/app/svg/icons/ic-network.svg new file mode 100644 index 000000000..4f026cd64 --- /dev/null +++ b/frontend/app/svg/icons/ic-network.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/ic-rage.svg b/frontend/app/svg/icons/ic-rage.svg new file mode 100644 index 000000000..4f026cd64 --- /dev/null +++ b/frontend/app/svg/icons/ic-rage.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/ic-resources.svg b/frontend/app/svg/icons/ic-resources.svg new file mode 100644 index 000000000..4f026cd64 --- /dev/null +++ b/frontend/app/svg/icons/ic-resources.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg> \ No newline at end of file diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts index 3f2aa2266..68bb1fd8d 100644 --- a/frontend/app/types/filter/filterType.ts +++ b/frontend/app/types/filter/filterType.ts @@ -256,8 +256,8 @@ export enum FilterKey { ERRORS_PER_DOMAINS = 'errorsPerDomains', ERRORS_PER_TYPE = 'errorsPerType', CALLS_ERRORS = 'callsErrors', - DOMAINS_ERRORS_4XX = 'domainsErrors4Xx', - DOMAINS_ERRORS_5XX = 'domainsErrors5Xx', + DOMAINS_ERRORS_4XX = 'domainsErrors4xx', + DOMAINS_ERRORS_5XX = 'domainsErrors5xx', IMPACTED_SESSIONS_BY_JS_ERRORS = 'impactedSessionsByJsErrors', // Performance From eb4c07a8767cf4faa7dcd3e0bef3662432c16796 Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Fri, 13 Jan 2023 12:56:35 +0100 Subject: [PATCH 51/65] change(ui) - cards - filters --- .../MetricViewHeader/MetricViewHeader.tsx | 108 +++++++++++++++--- .../MetricsLibraryModal.tsx | 2 +- .../components/MetricsList/MetricsList.tsx | 34 +++--- .../MetricsSearch/MetricsSearch.tsx | 51 +++++---- .../MetricTypeDropdown/MetricTypeDropdown.tsx | 29 ++--- frontend/app/constants/card.ts | 21 +++- frontend/app/mstore/metricStore.ts | 30 ++++- 7 files changed, 193 insertions(+), 82 deletions(-) diff --git a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx index 434538ea8..a958cd0ec 100644 --- a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx +++ b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx @@ -3,14 +3,17 @@ import { Icon, PageTitle, Button, Link, SegmentSelection } from 'UI'; import MetricsSearch from '../MetricsSearch'; import Select from 'Shared/Select'; import { useStore } from 'App/mstore'; -import { useObserver } from 'mobx-react-lite'; +import { observer, useObserver } from 'mobx-react-lite'; +import { DROPDOWN_OPTIONS, Option } from 'App/constants/card'; function MetricViewHeader() { const { metricStore } = useStore(); - const sort = useObserver(() => metricStore.sort); - const listView = useObserver(() => metricStore.listView); + const sort = metricStore.sort; + const listView = metricStore.listView; + const filter = metricStore.filter; + + const writeOption = (e: any, { name, value }: any) => {}; - return ( <div> <div className="flex items-center mb-4 justify-between px-6"> @@ -21,16 +24,16 @@ function MetricViewHeader() { <Link to={'/metrics/create'}> <Button variant="primary">New Card</Button> </Link> - <SegmentSelection - name="viewType" - className="mx-3" - primary - onSelect={ () => metricStore.updateKey('listView', !listView) } - value={{ value: listView ? 'list' : 'grid' }} - list={ [ - { value: 'list', name: '', icon: 'graph-up-arrow' }, - { value: 'grid', name: '', icon: 'hash' }, - ]} + {/* <SegmentSelection + name="viewType" + className="mx-3" + primary + onSelect={() => metricStore.updateKey('listView', !listView)} + value={{ value: listView ? 'list' : 'grid' }} + list={[ + { value: 'list', name: '', icon: 'graph-up-arrow' }, + { value: 'grid', name: '', icon: 'hash' }, + ]} /> <div className="mx-2"> <Select @@ -42,7 +45,7 @@ function MetricViewHeader() { plain onChange={({ value }) => metricStore.updateKey('sort', { by: value.value })} /> - </div> + </div> */} <div className="ml-4 w-1/4" style={{ minWidth: 300 }}> <MetricsSearch /> </div> @@ -52,8 +55,81 @@ function MetricViewHeader() { <Icon name="info-circle-fill" className="mr-2" size={16} /> Create custom Cards to capture key interactions and track KPIs. </div> + <div className="border-y px-3 py-1 mt-2 flex items-center w-full justify-between"> + <ListViewToggler /> + + <div className="items-center flex gap-4"> + <Select + options={[{ label: 'All Types', value: 'all' }, ...DROPDOWN_OPTIONS]} + name="type" + defaultValue={filter.type} + onChange={({ value }) => metricStore.updateKey('filter', { ...filter, type: value.value})} + plain={true} + /> + + <Select + options={[ + { label: 'Newest', value: 'desc' }, + { label: 'Oldest', value: 'asc' }, + ]} + name="sort" + defaultValue={metricStore.sort.by} + onChange={({ value }) => metricStore.updateKey('sort', { by: value.value })} + plain={true} + /> + + <DashboardDropdown + plain={true} + onChange={(value: any) => metricStore.updateKey('filter', { ...filter, dashboard: value})} + /> + </div> + </div> </div> ); } -export default MetricViewHeader; +export default observer(MetricViewHeader); + +function DashboardDropdown({ onChange, plain = false }: { plain?: boolean; onChange: any }) { + const { dashboardStore, metricStore } = useStore(); + const dashboardOptions = dashboardStore.dashboards.map((i: any) => ({ + key: i.id, + label: i.name, + value: i.dashboardId, + })); + + return ( + <Select + isSearchable={true} + placeholder="Select Dashboard" + plain={plain} + options={dashboardOptions} + value={metricStore.filter.dashboard} + onChange={({ value }: any) => onChange(value)} + isMulti={true} + /> + ); +} + +function ListViewToggler({}) { + const { metricStore } = useStore(); + const listView = useObserver(() => metricStore.listView); + return ( + <div className="flex items-center"> + <Button + icon="list-alt" + variant={listView ? 'text-primary' : 'text'} + onClick={() => metricStore.updateKey('listView', true)} + > + List + </Button> + <Button + icon="grid" + variant={!listView ? 'text-primary' : 'text'} + onClick={() => metricStore.updateKey('listView', false)} + > + Grid + </Button> + </div> + ); +} diff --git a/frontend/app/components/Dashboard/components/MetricsLibraryModal/MetricsLibraryModal.tsx b/frontend/app/components/Dashboard/components/MetricsLibraryModal/MetricsLibraryModal.tsx index 5227b7171..cfa6a4a6d 100644 --- a/frontend/app/components/Dashboard/components/MetricsLibraryModal/MetricsLibraryModal.tsx +++ b/frontend/app/components/Dashboard/components/MetricsLibraryModal/MetricsLibraryModal.tsx @@ -70,7 +70,7 @@ function MetricSearch({ onChange }: any) { function SelectedContent({ dashboardId, selected }: any) { const { hideModal } = useModal(); const { metricStore, dashboardStore } = useStore(); - const total = useObserver(() => metricStore.sortedWidgets.length); + const total = useObserver(() => metricStore.metrics.length); const dashboard = useMemo(() => dashboardStore.getDashboard(dashboardId), [dashboardId]); const addSelectedToDashboard = () => { diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index 44423944b..14245093d 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -17,11 +17,11 @@ function MetricsList({ onSelectionChange?: (selected: any[]) => void; }) { const { metricStore } = useStore(); - const metrics = metricStore.sortedWidgets; + const cards = metricStore.filteredCards; const metricsSearch = metricStore.metricsSearch; const listView = useObserver(() => metricStore.listView); const [selectedMetrics, setSelectedMetrics] = useState<any>([]); - const sortBy = useObserver(() => metricStore.sort.by); + // const sortBy = useObserver(() => metricStore.sort.by); useEffect(() => { metricStore.fetchList(); @@ -42,17 +42,17 @@ function MetricsList({ } }; - const filterByDashboard = (item: Widget, searchRE: RegExp) => { - const dashboardsStr = item.dashboards.map((d: any) => d.name).join(' '); - return searchRE.test(dashboardsStr); - }; + // const filterByDashboard = (item: Widget, searchRE: RegExp) => { + // const dashboardsStr = item.dashboards.map((d: any) => d.name).join(' '); + // return searchRE.test(dashboardsStr); + // }; - const list = - metricsSearch !== '' - ? filterList(metrics, metricsSearch, ['name', 'metricType', 'owner'], filterByDashboard) - : metrics; + // const list = + // metricsSearch !== '' + // ? filterList(metrics, metricsSearch, ['name', 'metricType', 'owner'], filterByDashboard) + // : metrics; - const lenth = list.length; + const lenth = cards.length; useEffect(() => { metricStore.updateKey('sessionsPage', 1); @@ -74,18 +74,18 @@ function MetricsList({ <ListView disableSelection={!onSelectionChange} siteId={siteId} - list={sliceListPerPage(list, metricStore.page - 1, metricStore.pageSize)} + list={sliceListPerPage(cards, metricStore.page - 1, metricStore.pageSize)} selectedList={selectedMetrics} toggleSelection={toggleMetricSelection} - allSelected={list.length === selectedMetrics.length} + allSelected={cards.length === selectedMetrics.length} toggleAll={({ target: { checked, name } }) => - setSelectedMetrics(checked ? list.map((i: any) => i.metricId) : []) + setSelectedMetrics(checked ? cards.map((i: any) => i.metricId) : []) } /> ) : ( <GridView siteId={siteId} - list={sliceListPerPage(list, metricStore.page - 1, metricStore.pageSize)} + list={sliceListPerPage(cards, metricStore.page - 1, metricStore.pageSize)} selectedList={selectedMetrics} toggleSelection={toggleMetricSelection} /> @@ -94,8 +94,8 @@ function MetricsList({ <div className="w-full flex items-center justify-between py-4 px-6 border-t"> <div className="text-disabled-text"> Showing{' '} - <span className="font-semibold">{Math.min(list.length, metricStore.pageSize)}</span> out - of <span className="font-semibold">{list.length}</span> cards + <span className="font-semibold">{Math.min(cards.length, metricStore.pageSize)}</span> out + of <span className="font-semibold">{cards.length}</span> cards </div> <Pagination page={metricStore.page} diff --git a/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx b/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx index cf27661d9..368816ce2 100644 --- a/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx +++ b/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx @@ -4,31 +4,34 @@ import { useStore } from 'App/mstore'; import { Icon } from 'UI'; import { debounce } from 'App/utils'; -let debounceUpdate: any = () => {} -function MetricsSearch(props) { - const { metricStore } = useStore(); - const [query, setQuery] = useState(metricStore.metricsSearch); - useEffect(() => { - debounceUpdate = debounce((key, value) => metricStore.updateKey(key, value), 500); - }, []) +let debounceUpdate: any = () => {}; +function MetricsSearch() { + const { metricStore } = useStore(); + const [query, setQuery] = useState(metricStore.filter.query); + useEffect(() => { + debounceUpdate = debounce( + (key: any, value: any) => metricStore.updateKey('filter', { ...metricStore.filter, query: value }), + 500 + ); + }, []); - const write = ({ target: { value } }) => { - setQuery(value); - debounceUpdate('metricsSearch', value); - } - - return useObserver(() => ( - <div className="relative"> - <Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" /> - <input - value={query} - name="metricsSearch" - className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10" - placeholder="Filter by title, type, dashboard and owner" - onChange={write} - /> - </div> - )); + const write = ({ target: { value } }: any) => { + setQuery(value); + debounceUpdate('metricsSearch', value); + }; + + return useObserver(() => ( + <div className="relative"> + <Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" /> + <input + value={query} + name="metricsSearch" + className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10" + placeholder="Filter by title and owner" + onChange={write} + /> + </div> + )); } export default MetricsSearch; diff --git a/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.tsx b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.tsx index f2faf0d5a..c85389529 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.tsx @@ -1,7 +1,6 @@ -import React, { useMemo } from 'react'; -import { TYPES, LIBRARY } from 'App/constants/card'; +import React from 'react'; +import { DROPDOWN_OPTIONS, Option } from 'App/constants/card'; import Select from 'Shared/Select'; -import { MetricType } from 'App/components/Dashboard/components/MetricTypeItem/MetricTypeItem'; import { components } from 'react-select'; import CustomDropdownOption from 'Shared/CustomDropdownOption'; import { observer } from 'mobx-react-lite'; @@ -22,20 +21,11 @@ interface Props { function MetricTypeDropdown(props: Props) { const { metricStore } = useStore(); const metric: any = metricStore.instance; - const options: Options[] = useMemo(() => { - // TYPES.shift(); // remove "Add from library" item - return TYPES.filter((i: MetricType) => i.slug !== LIBRARY).map((i: MetricType) => ({ - label: i.title, - icon: i.icon, - value: i.slug, - description: i.description, - })); - }, []); React.useEffect(() => { const queryCardType = props.query.get('type'); - if (queryCardType && options.length > 0 && metric.metricType) { - const type = options.find((i) => i.value === queryCardType); + if (queryCardType && DROPDOWN_OPTIONS.length > 0 && metric.metricType) { + const type: Option = DROPDOWN_OPTIONS.find((i) => i.value === queryCardType) as Option; setTimeout(() => onChange(type.value), 0); } }, []); @@ -48,13 +38,16 @@ function MetricTypeDropdown(props: Props) { <Select name="metricType" placeholder="Select Card Type" - options={options} - value={options.find((i: any) => i.value === metric.metricType) || options[0]} + options={DROPDOWN_OPTIONS} + value={ + DROPDOWN_OPTIONS.find((i: any) => i.value === metric.metricType) || DROPDOWN_OPTIONS[0] + } onChange={props.onSelect} - // onSelect={onSelect} components={{ SingleValue: ({ children, ...props }: any) => { - const { data: { icon, label } } = props; + const { + data: { icon, label }, + } = props; return ( <components.SingleValue {...props}> <div className="flex items-center"> diff --git a/frontend/app/constants/card.ts b/frontend/app/constants/card.ts index b1354b660..1cd764864 100644 --- a/frontend/app/constants/card.ts +++ b/frontend/app/constants/card.ts @@ -1,5 +1,6 @@ import { IconNames } from 'App/components/ui/SVG'; import { FilterKey } from 'Types/filter/filterType'; +import { MetricType } from 'App/components/Dashboard/components/MetricTypeItem/MetricTypeItem'; export interface CardType { title: string; @@ -23,6 +24,13 @@ export const RETENTION = 'retention'; export const FEATURE_ADOPTION = 'featureAdoption'; export const INSIGHTS = 'insights'; +export interface Option { + label: string; + icon: string; + value: string; + description: string; +} + export const TYPES: CardType[] = [ { title: 'Add from Library', @@ -35,9 +43,7 @@ export const TYPES: CardType[] = [ icon: 'puzzle-piece', description: 'Track the features that are being used the most.', slug: CLICKMAP, - subTypes: [ - { title: 'Visited URL', slug: FilterKey.CLICKMAP_URL, description: "" }, - ] + subTypes: [{ title: 'Visited URL', slug: FilterKey.CLICKMAP_URL, description: '' }], }, { title: 'Timeseries', @@ -230,3 +236,12 @@ export const TYPES: CardType[] = [ slug: INSIGHTS, }, ]; + +export const DROPDOWN_OPTIONS = TYPES.filter((i: MetricType) => i.slug !== LIBRARY).map( + (i: MetricType) => ({ + label: i.title, + icon: i.icon, + value: i.slug, + description: i.description, + }) +); diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index 067fba38f..1a0d52697 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -15,7 +15,14 @@ import { INSIGHTS, } from 'App/constants/card'; import { clickmapFilter } from 'App/types/filter/newFilter'; +import { filterList, getRE } from 'App/utils'; +interface MetricFilter { + query?: string; + showMine?: boolean; + type?: string; + dashboard?: []; +} export default class MetricStore { isLoading: boolean = false; isSaving: boolean = false; @@ -28,6 +35,8 @@ export default class MetricStore { metricsSearch: string = ''; sort: any = { by: 'desc' }; + filter: MetricFilter = { type: 'all', dashboard: [], query: '' }; + sessionsPage: number = 1; sessionsPageSize: number = 10; listView?: boolean = true; @@ -46,6 +55,22 @@ export default class MetricStore { ); } + get filteredCards() { + const filterRE = this.filter.query ? getRE(this.filter.query, 'i') : null; + const dbIds = this.filter.dashboard ? this.filter.dashboard.map((i) => i.value) : []; + return this.metrics + .filter( + (card) => + (this.filter.type === 'all' || card.metricType === this.filter.type) && + (!dbIds.length || + card.dashboards.map((i) => i.dashboardId).some((id) => dbIds.includes(id))) && + (!filterRE || ['name', 'owner'].some((key) => filterRE.test(card[key]))) + ) + .sort((a, b) => + this.sort.by === 'desc' ? b.lastModified - a.lastModified : a.lastModified - b.lastModified + ); + } + // State Actions init(metric?: Widget | null) { this.instance.update(metric || new Widget()); @@ -80,14 +105,13 @@ export default class MetricStore { } } - Object.assign(this.instance, obj); this.instance.updateKey('hasChanged', updateChangeFlag); } changeType(value: string) { const obj: any = { metricType: value }; - obj.series = this.instance.series + obj.series = this.instance.series; obj['metricValue'] = []; @@ -117,7 +141,7 @@ export default class MetricStore { } if (value === CLICKMAP) { - obj.series = obj.series.slice(0, 1) + obj.series = obj.series.slice(0, 1); if (this.instance.metricType !== CLICKMAP) { obj.series[0].filter.removeFilter(0); } From 4b254b3752fe5bb62b287db352022279b0390054 Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Fri, 13 Jan 2023 12:57:39 +0100 Subject: [PATCH 52/65] change(ui) - cards - filters - removed logs and unused --- .../components/MetricsList/MetricsList.tsx | 13 ------------- frontend/app/mstore/metricStore.ts | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index 14245093d..d815d901d 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -2,9 +2,7 @@ import { observer, useObserver } from 'mobx-react-lite'; import React, { useEffect, useState } from 'react'; import { NoContent, Pagination } from 'UI'; import { useStore } from 'App/mstore'; -import { filterList } from 'App/utils'; import { sliceListPerPage } from 'App/utils'; -import Widget from 'App/mstore/types/widget'; import GridView from './GridView'; import ListView from './ListView'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; @@ -21,7 +19,6 @@ function MetricsList({ const metricsSearch = metricStore.metricsSearch; const listView = useObserver(() => metricStore.listView); const [selectedMetrics, setSelectedMetrics] = useState<any>([]); - // const sortBy = useObserver(() => metricStore.sort.by); useEffect(() => { metricStore.fetchList(); @@ -42,16 +39,6 @@ function MetricsList({ } }; - // const filterByDashboard = (item: Widget, searchRE: RegExp) => { - // const dashboardsStr = item.dashboards.map((d: any) => d.name).join(' '); - // return searchRE.test(dashboardsStr); - // }; - - // const list = - // metricsSearch !== '' - // ? filterList(metrics, metricsSearch, ['name', 'metricType', 'owner'], filterByDashboard) - // : metrics; - const lenth = cards.length; useEffect(() => { diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index 1a0d52697..82d611ee1 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -15,7 +15,7 @@ import { INSIGHTS, } from 'App/constants/card'; import { clickmapFilter } from 'App/types/filter/newFilter'; -import { filterList, getRE } from 'App/utils'; +import { getRE } from 'App/utils'; interface MetricFilter { query?: string; From d5e3b2b9a444c0c1fa2dabcdeb82f8668aa1aa86 Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Fri, 13 Jan 2023 15:19:49 +0100 Subject: [PATCH 53/65] change(ui) - dashboard - filters --- .../DashboardList/DashboardList.tsx | 13 +- .../DashboardList/DashboardListItem.tsx | 1 - .../DashboardList/DashboardSearch.tsx | 52 +- .../components/DashboardList/Header.tsx | 61 +- .../MetricViewHeader/MetricViewHeader.tsx | 29 +- frontend/app/mstore/dashboardStore.ts | 872 +++++++++--------- frontend/app/mstore/metricStore.ts | 3 +- 7 files changed, 503 insertions(+), 528 deletions(-) diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index ace14e03e..2b60ad40a 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -2,22 +2,14 @@ import { observer } from 'mobx-react-lite'; import React from 'react'; import { NoContent, Pagination } from 'UI'; import { useStore } from 'App/mstore'; -import { filterList } from 'App/utils'; import { sliceListPerPage } from 'App/utils'; import DashboardListItem from './DashboardListItem'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; function DashboardList() { const { dashboardStore } = useStore(); - const [shownDashboards, setDashboards] = React.useState([]); - const dashboards = dashboardStore.sortedDashboards; + const list = dashboardStore.filteredList; const dashboardsSearch = dashboardStore.dashboardsSearch; - - React.useEffect(() => { - setDashboards(filterList(dashboards, dashboardsSearch, ['name', 'owner', 'description'])); - }, [dashboardsSearch]); - - const list = dashboardsSearch !== '' ? shownDashboards : dashboards; const lenth = list.length; return ( @@ -38,9 +30,6 @@ function DashboardList() { )} </div> <AnimatedSVG name={ICONS.NO_DASHBOARDS} size={180} /> - {/* <div className="my-2 bg-active-blue rounded flex items-center justify-center px-80 py-20"> - <Icon name="grid-1x2" size={40} color="figmaColors-accent-secondary" /> - </div> */} </div> } > diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx index a3cb6e436..e076dd6d5 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx @@ -32,7 +32,6 @@ function DashboardListItem(props: Props) { <div className="link capitalize-first">{dashboard.name}</div> </div> </div> - {/* <div><Label className="capitalize">{metric.metricType}</Label></div> */} <div className="col-span-2"> <div className="flex items-center"> <Icon name={dashboard.isPublic ? 'user-friends' : 'person-fill'} className="mr-2" /> diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx index a3b13f1d3..d60a6886c 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx @@ -4,33 +4,37 @@ import { useStore } from 'App/mstore'; import { Icon } from 'UI'; import { debounce } from 'App/utils'; -let debounceUpdate: any = () => {} +let debounceUpdate: any = () => {}; function DashboardSearch() { - const { dashboardStore } = useStore(); - const [query, setQuery] = useState(dashboardStore.dashboardsSearch); - useEffect(() => { - debounceUpdate = debounce((key: string, value: any) => dashboardStore.updateKey(key, value), 500); - }, []) - - // @ts-ignore - const write = ({ target: { value } }) => { - setQuery(value); - debounceUpdate('dashboardsSearch', value); - } - - return ( - <div className="relative"> - <Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" /> - <input - value={query} - name="dashboardsSearch" - className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10" - placeholder="Filter by title or description" - onChange={write} - /> - </div> + const { dashboardStore } = useStore(); + const [query, setQuery] = useState(dashboardStore.dashboardsSearch); + useEffect(() => { + debounceUpdate = debounce( + (key: string, value: any) => + dashboardStore.updateKey('filter', { ...dashboardStore.filter, query: value }), + 500 ); + }, []); + + // @ts-ignore + const write = ({ target: { value } }) => { + setQuery(value); + debounceUpdate('dashboardsSearch', value); + }; + + return ( + <div className="relative"> + <Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" /> + <input + value={query} + name="dashboardsSearch" + className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10" + placeholder="Filter by title or description" + onChange={write} + /> + </div> + ); } export default observer(DashboardSearch); diff --git a/frontend/app/components/Dashboard/components/DashboardList/Header.tsx b/frontend/app/components/Dashboard/components/DashboardList/Header.tsx index f77d3ae69..560c27c61 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/Header.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/Header.tsx @@ -19,30 +19,49 @@ function Header({ history, siteId }: { history: any; siteId: string }) { }; return ( - <div className="flex items-center mb-4 justify-between px-6"> - <div className="flex items-baseline mr-3"> - <PageTitle title="Dashboards" /> - </div> - <div className="ml-auto flex items-center"> - <Button variant="primary" onClick={onAddDashboardClick}> - New Dashboard - </Button> - <div className="mx-2"> - <Select - options={[ - { label: 'Newest', value: 'desc' }, - { label: 'Oldest', value: 'asc' }, - ]} - defaultValue={sort.by} - plain - onChange={({ value }) => dashboardStore.updateKey('sort', { by: value.value })} - /> + <> + <div className="flex items-center mb-4 justify-between px-6"> + <div className="flex items-baseline mr-3"> + <PageTitle title="Dashboards" /> </div> - <div className="w-1/4" style={{ minWidth: 300 }}> - <DashboardSearch /> + <div className="ml-auto flex items-center"> + <Button variant="primary" onClick={onAddDashboardClick}> + New Dashboard + </Button> + <div className="mx-2"></div> + <div className="w-1/4" style={{ minWidth: 300 }}> + <DashboardSearch /> + </div> </div> </div> - </div> + <div className="border-y px-3 py-1 mt-2 flex items-center w-full justify-end gap-4"> + <Select + options={[ + { label: 'Visibility - All', value: 'all' }, + { label: 'Visibility - Private', value: 'private' }, + { label: 'Visibility - Team', value: 'team' }, + ]} + defaultValue={'all'} + plain + onChange={({ value }) => + dashboardStore.updateKey('filter', { + ...dashboardStore.filter, + visibility: value.value, + }) + } + /> + + <Select + options={[ + { label: 'Newest', value: 'desc' }, + { label: 'Oldest', value: 'asc' }, + ]} + defaultValue={sort.by} + plain + onChange={({ value }) => dashboardStore.updateKey('sort', { by: value.value })} + /> + </div> + </> ); } diff --git a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx index a958cd0ec..6dcecabb2 100644 --- a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx +++ b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon, PageTitle, Button, Link, SegmentSelection } from 'UI'; +import { Icon, PageTitle, Button, Link } from 'UI'; import MetricsSearch from '../MetricsSearch'; import Select from 'Shared/Select'; import { useStore } from 'App/mstore'; @@ -8,12 +8,8 @@ import { DROPDOWN_OPTIONS, Option } from 'App/constants/card'; function MetricViewHeader() { const { metricStore } = useStore(); - const sort = metricStore.sort; - const listView = metricStore.listView; const filter = metricStore.filter; - const writeOption = (e: any, { name, value }: any) => {}; - return ( <div> <div className="flex items-center mb-4 justify-between px-6"> @@ -24,28 +20,6 @@ function MetricViewHeader() { <Link to={'/metrics/create'}> <Button variant="primary">New Card</Button> </Link> - {/* <SegmentSelection - name="viewType" - className="mx-3" - primary - onSelect={() => metricStore.updateKey('listView', !listView)} - value={{ value: listView ? 'list' : 'grid' }} - list={[ - { value: 'list', name: '', icon: 'graph-up-arrow' }, - { value: 'grid', name: '', icon: 'hash' }, - ]} - /> - <div className="mx-2"> - <Select - options={[ - { label: 'Newest', value: 'desc' }, - { label: 'Oldest', value: 'asc' }, - ]} - defaultValue={sort.by} - plain - onChange={({ value }) => metricStore.updateKey('sort', { by: value.value })} - /> - </div> */} <div className="ml-4 w-1/4" style={{ minWidth: 300 }}> <MetricsSearch /> </div> @@ -65,6 +39,7 @@ function MetricViewHeader() { defaultValue={filter.type} onChange={({ value }) => metricStore.updateKey('filter', { ...filter, type: value.value})} plain={true} + isSearchable={true} /> <Select diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 5b89e82d5..8632d639c 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -1,444 +1,432 @@ -import { - makeAutoObservable, - runInAction, -} from "mobx"; -import Dashboard from "./types/dashboard"; -import Widget from "./types/widget"; -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 Funnel from "./types/funnel"; -import Session from "./types/session"; -import Error from "./types/error"; -import { FilterKey } from "Types/filter/filterType"; +import { makeAutoObservable, runInAction } from 'mobx'; +import Dashboard from './types/dashboard'; +import Widget from './types/widget'; +import { dashboardService, metricService } from 'App/services'; +import { toast } from 'react-toastify'; +import Period, { LAST_24_HOURS, LAST_7_DAYS } from 'Types/app/period'; +import Filter from './types/filter'; +import { getRE } from 'App/utils'; -export default class DashboardStore { - siteId: any = null; - dashboards: Dashboard[] = []; - selectedDashboard: Dashboard | null = null; - dashboardInstance: Dashboard = new Dashboard(); - selectedWidgets: Widget[] = []; - currentWidget: Widget = new Widget(); - widgetCategories: any[] = []; - widgets: Widget[] = []; - period: Record<string, any> = Period({ rangeName: LAST_24_HOURS }); - drillDownFilter: Filter = new Filter(); - drillDownPeriod: Record<string, any> = Period({ rangeName: LAST_7_DAYS }); - startTimestamp: number = 0; - endTimestamp: number = 0; - pendingRequests: number = 0; - - // Metrics - metricsPage: number = 1; - metricsPageSize: number = 10; - metricsSearch: string = ""; - - // Loading states - isLoading: boolean = true; - isSaving: boolean = false; - isDeleting: boolean = false; - loadingTemplates: boolean = false - fetchingDashboard: boolean = false; - sessionsLoading: boolean = false; - showAlertModal: boolean = false; - - // Pagination - page: number = 1 - pageSize: number = 10 - dashboardsSearch: string = '' - sort: any = { by: 'desc'} - - constructor() { - makeAutoObservable(this); - - this.drillDownPeriod = Period({ rangeName: LAST_7_DAYS }); - const timeStamps = this.drillDownPeriod.toTimestamps(); - this.drillDownFilter.updateKey( - "startTimestamp", - timeStamps.startTimestamp - ); - this.drillDownFilter.updateKey("endTimestamp", timeStamps.endTimestamp); - } - - get sortedDashboards() { - const sortOrder = this.sort.by - return [...this.dashboards].sort((a, b) => sortOrder === 'desc' ? b.createdAt - a.createdAt : a.createdAt - b.createdAt) - } - - toggleAllSelectedWidgets(isSelected: boolean) { - if (isSelected) { - const allWidgets = this.widgetCategories.reduce((acc, cat) => { - return acc.concat(cat.widgets); - }, []); - - this.selectedWidgets = allWidgets; - } else { - this.selectedWidgets = []; - } - } - - selectWidgetsByCategory(category: string) { - const selectedWidgetIds = this.selectedWidgets.map( - (widget: any) => widget.metricId - ); - const widgets = this.widgetCategories - .find((cat) => cat.name === category) - ?.widgets.filter( - (widget: any) => !selectedWidgetIds.includes(widget.metricId) - ); - this.selectedWidgets = this.selectedWidgets.concat(widgets) || []; - } - - removeSelectedWidgetByCategory = (category: any) => { - const categoryWidgetIds = category.widgets.map((w: Widget) => w.metricId); - this.selectedWidgets = this.selectedWidgets.filter( - (widget: any) => !categoryWidgetIds.includes(widget.metricId) - ); - }; - - toggleWidgetSelection = (widget: any) => { - const selectedWidgetIds = this.selectedWidgets.map( - (widget: any) => widget.metricId - ); - if (selectedWidgetIds.includes(widget.metricId)) { - this.selectedWidgets = this.selectedWidgets.filter( - (w: any) => w.metricId !== widget.metricId - ); - } else { - this.selectedWidgets.push(widget); - } - }; - - findByIds(ids: string[]) { - return this.dashboards.filter((d) => ids.includes(d.dashboardId)); - } - - initDashboard(dashboard?: Dashboard) { - this.dashboardInstance = dashboard - ? new Dashboard().fromJson(dashboard) - : new Dashboard(); - this.selectedWidgets = []; - } - - updateKey(key: string, value: any) { - // @ts-ignore - this[key] = value; - } - - resetCurrentWidget() { - this.currentWidget = new Widget(); - } - - editWidget(widget: any) { - this.currentWidget.update(widget); - } - - fetchList(): Promise<any> { - this.isLoading = true; - - return dashboardService - .getDashboards() - .then((list: any) => { - runInAction(() => { - this.dashboards = list.map((d: Record<string, any>) => - new Dashboard().fromJson(d) - ); - }); - }) - .finally(() => { - runInAction(() => { - this.isLoading = false; - }); - }); - } - - fetch(dashboardId: string): Promise<any> { - this.setFetchingDashboard(true); - return dashboardService - .getDashboard(dashboardId) - .then((response) => { - this.selectedDashboard?.update({ - widgets: new Dashboard().fromJson(response).widgets, - }); - }) - .finally(() => { - this.setFetchingDashboard(false); - }); - } - - setFetchingDashboard(value: boolean) { - this.fetchingDashboard = value; - } - - save(dashboard: Dashboard): Promise<any> { - this.isSaving = true; - const isCreating = !dashboard.dashboardId; - - dashboard.metrics = this.selectedWidgets.map((w) => w.metricId); - - return new Promise((resolve, reject) => { - dashboardService - .saveDashboard(dashboard) - .then((_dashboard) => { - runInAction(() => { - if (isCreating) { - toast.success("Dashboard created successfully"); - this.addDashboard( - new Dashboard().fromJson(_dashboard) - ); - } else { - toast.success("Dashboard successfully updated "); - this.updateDashboard( - new Dashboard().fromJson(_dashboard) - ); - } - resolve(_dashboard); - }); - }) - .catch((error) => { - toast.error("Error saving dashboard"); - reject(); - }) - .finally(() => { - runInAction(() => { - this.isSaving = false; - }); - }); - }); - } - - saveMetric(metric: Widget, dashboardId: string): Promise<any> { - const isCreating = !metric.widgetId; - return dashboardService - .saveMetric(metric, dashboardId) - .then((metric) => { - runInAction(() => { - if (isCreating) { - this.selectedDashboard?.widgets.push(metric); - } else { - this.selectedDashboard?.widgets.map((w) => { - if (w.widgetId === metric.widgetId) { - w.update(metric); - } - }); - } - }); - }); - } - - deleteDashboard(dashboard: Dashboard): Promise<any> { - this.isDeleting = true; - return dashboardService - .deleteDashboard(dashboard.dashboardId) - .then(() => { - toast.success("Dashboard deleted successfully"); - runInAction(() => { - this.removeDashboard(dashboard); - }); - }) - .catch(() => { - toast.error("Dashboard could not be deleted"); - }) - .finally(() => { - runInAction(() => { - this.isDeleting = false; - }); - }); - } - - toJson() { - return { - dashboards: this.dashboards.map((d) => d.toJson()), - }; - } - - fromJson(json: any) { - runInAction(() => { - this.dashboards = json.dashboards.map((d: Record<string, any>) => - new Dashboard().fromJson(d) - ); - }); - return this; - } - - addDashboard(dashboard: Dashboard) { - this.dashboards.push(new Dashboard().fromJson(dashboard)); - } - - removeDashboard(dashboard: Dashboard) { - this.dashboards = this.dashboards.filter( - (d) => d.dashboardId !== dashboard.dashboardId - ); - } - - getDashboard(dashboardId: string|number): Dashboard | null { - return ( - this.dashboards.find((d) => d.dashboardId == dashboardId) || null - ); - } - - getDashboardByIndex(index: number) { - return this.dashboards[index]; - } - - getDashboardCount() { - return this.dashboards.length; - } - - updateDashboard(dashboard: Dashboard) { - const index = this.dashboards.findIndex( - (d) => d.dashboardId === dashboard.dashboardId - ); - if (index >= 0) { - this.dashboards[index] = dashboard; - if (this.selectedDashboard?.dashboardId === dashboard.dashboardId) { - this.selectDashboardById(dashboard.dashboardId); - } - } - } - - selectDashboardById = (dashboardId: any) => { - this.selectedDashboard = - this.dashboards.find((d) => d.dashboardId == dashboardId) || - new Dashboard(); - }; - - getDashboardById = (dashboardId: string) => { - const dashboard = this.dashboards.find((d) => d.dashboardId == dashboardId) - - if (dashboard) { - this.selectedDashboard = dashboard - return true; - } else { - this.selectedDashboard = null - return false; - } - } - - setSiteId = (siteId: any) => { - this.siteId = siteId; - }; - - fetchTemplates(hardRefresh: boolean): Promise<any> { - this.loadingTemplates = true - return new Promise((resolve, reject) => { - if (this.widgetCategories.length > 0 && !hardRefresh) { - resolve(this.widgetCategories); - } else { - metricService - .getTemplates() - .then((response) => { - const categories: any[] = []; - response.forEach((category: any) => { - const widgets: any[] = []; - category.widgets - .forEach((widget: any) => { - const w = new Widget().fromJson(widget); - widgets.push(w); - }); - const c: any = {}; - c.widgets = widgets; - c.name = category.category; - c.description = category.description; - categories.push(c); - }); - this.widgetCategories = categories; - resolve(this.widgetCategories); - }) - .catch((error) => { - reject(error); - }).finally(() => { - this.loadingTemplates = false - }); - } - }); - } - - deleteDashboardWidget(dashboardId: string, widgetId: string) { - this.isDeleting = true; - return dashboardService - .deleteWidget(dashboardId, widgetId) - .then(() => { - toast.success("Dashboard updated successfully"); - runInAction(() => { - this.selectedDashboard?.removeWidget(widgetId); - }); - }) - .finally(() => { - this.isDeleting = false; - }); - } - - addWidgetToDashboard(dashboard: Dashboard, metricIds: any): Promise<any> { - this.isSaving = true; - return dashboardService - .addWidget(dashboard, metricIds) - .then((response) => { - toast.success("Card added to dashboard."); - }) - .catch(() => { - toast.error("Card could not be added."); - }) - .finally(() => { - this.isSaving = false; - }); - } - - setPeriod(period: any) { - this.period = Period({ - start: period.start, - end: period.end, - rangeName: period.rangeName, - }); - } - - setDrillDownPeriod(period: any) { - this.drillDownPeriod = Period({ - start: period.start, - end: period.end, - rangeName: period.rangeName, - }); - } - - toggleAlertModal(val: boolean) { - this.showAlertModal = val - } - - fetchMetricChartData( - metric: Widget, - data: any, - isWidget: boolean = false, - period: Record<string, any> - ): Promise<any> { - period = period.toTimestamps(); - const params = { ...period, ...data, key: metric.predefinedKey }; - - - if (metric.page && metric.limit) { - params["page"] = metric.page; - params["limit"] = metric.limit; - } - - return new Promise((resolve, reject) => { - this.pendingRequests += 1 - return metricService - .getMetricChartData(metric, params, isWidget) - .then((data: any) => { - metric.setData(data, period); - resolve(metric.data); - }) - .catch((err: any) => { - reject(err); - }).finally(() => { - setTimeout(() => { - this.pendingRequests = this.pendingRequests - 1 - }, 100) - }); - }); - } +interface DashboardFilter { + query?: string; + visibility?: string; +} +export default class DashboardStore { + siteId: any = null; + dashboards: Dashboard[] = []; + selectedDashboard: Dashboard | null = null; + dashboardInstance: Dashboard = new Dashboard(); + selectedWidgets: Widget[] = []; + currentWidget: Widget = new Widget(); + widgetCategories: any[] = []; + widgets: Widget[] = []; + period: Record<string, any> = Period({ rangeName: LAST_24_HOURS }); + drillDownFilter: Filter = new Filter(); + drillDownPeriod: Record<string, any> = Period({ rangeName: LAST_7_DAYS }); + startTimestamp: number = 0; + endTimestamp: number = 0; + pendingRequests: number = 0; + + filter: DashboardFilter = { visibility: 'all', query: '' }; + + // Metrics + metricsPage: number = 1; + metricsPageSize: number = 10; + metricsSearch: string = ''; + + // Loading states + isLoading: boolean = true; + isSaving: boolean = false; + isDeleting: boolean = false; + loadingTemplates: boolean = false; + fetchingDashboard: boolean = false; + sessionsLoading: boolean = false; + showAlertModal: boolean = false; + + // Pagination + page: number = 1; + pageSize: number = 10; + dashboardsSearch: string = ''; + sort: any = { by: 'desc' }; + + constructor() { + makeAutoObservable(this); + + this.drillDownPeriod = Period({ rangeName: LAST_7_DAYS }); + const timeStamps = this.drillDownPeriod.toTimestamps(); + this.drillDownFilter.updateKey('startTimestamp', timeStamps.startTimestamp); + this.drillDownFilter.updateKey('endTimestamp', timeStamps.endTimestamp); + } + + get sortedDashboards() { + const sortOrder = this.sort.by; + return [...this.dashboards].sort((a, b) => + sortOrder === 'desc' ? b.createdAt - a.createdAt : a.createdAt - b.createdAt + ); + } + + get filteredList() { + const filterRE = this.filter.query ? getRE(this.filter.query, 'i') : null; + return this.dashboards + .filter( + (dashboard) => + (this.filter.visibility === 'all' || + (this.filter.visibility === 'team' ? dashboard.isPublic : !dashboard.isPublic)) && + (!filterRE || + // @ts-ignore + ['name', 'owner', 'description'].some((key) => filterRE.test(dashboard[key]))) + ) + .sort((a, b) => + this.sort.by === 'desc' ? b.createdAt - a.createdAt : a.createdAt - b.createdAt + ); + } + + toggleAllSelectedWidgets(isSelected: boolean) { + if (isSelected) { + const allWidgets = this.widgetCategories.reduce((acc, cat) => { + return acc.concat(cat.widgets); + }, []); + + this.selectedWidgets = allWidgets; + } else { + this.selectedWidgets = []; + } + } + + selectWidgetsByCategory(category: string) { + const selectedWidgetIds = this.selectedWidgets.map((widget: any) => widget.metricId); + const widgets = this.widgetCategories + .find((cat) => cat.name === category) + ?.widgets.filter((widget: any) => !selectedWidgetIds.includes(widget.metricId)); + this.selectedWidgets = this.selectedWidgets.concat(widgets) || []; + } + + removeSelectedWidgetByCategory = (category: any) => { + const categoryWidgetIds = category.widgets.map((w: Widget) => w.metricId); + this.selectedWidgets = this.selectedWidgets.filter( + (widget: any) => !categoryWidgetIds.includes(widget.metricId) + ); + }; + + toggleWidgetSelection = (widget: any) => { + const selectedWidgetIds = this.selectedWidgets.map((widget: any) => widget.metricId); + if (selectedWidgetIds.includes(widget.metricId)) { + this.selectedWidgets = this.selectedWidgets.filter( + (w: any) => w.metricId !== widget.metricId + ); + } else { + this.selectedWidgets.push(widget); + } + }; + + findByIds(ids: string[]) { + return this.dashboards.filter((d) => ids.includes(d.dashboardId)); + } + + initDashboard(dashboard?: Dashboard) { + this.dashboardInstance = dashboard ? new Dashboard().fromJson(dashboard) : new Dashboard(); + this.selectedWidgets = []; + } + + updateKey(key: string, value: any) { + // @ts-ignore + this[key] = value; + } + + resetCurrentWidget() { + this.currentWidget = new Widget(); + } + + editWidget(widget: any) { + this.currentWidget.update(widget); + } + + fetchList(): Promise<any> { + this.isLoading = true; + + return dashboardService + .getDashboards() + .then((list: any) => { + runInAction(() => { + this.dashboards = list.map((d: Record<string, any>) => new Dashboard().fromJson(d)); + }); + }) + .finally(() => { + runInAction(() => { + this.isLoading = false; + }); + }); + } + + fetch(dashboardId: string): Promise<any> { + this.setFetchingDashboard(true); + return dashboardService + .getDashboard(dashboardId) + .then((response) => { + this.selectedDashboard?.update({ + widgets: new Dashboard().fromJson(response).widgets, + }); + }) + .finally(() => { + this.setFetchingDashboard(false); + }); + } + + setFetchingDashboard(value: boolean) { + this.fetchingDashboard = value; + } + + save(dashboard: Dashboard): Promise<any> { + this.isSaving = true; + const isCreating = !dashboard.dashboardId; + + dashboard.metrics = this.selectedWidgets.map((w) => w.metricId); + + return new Promise((resolve, reject) => { + dashboardService + .saveDashboard(dashboard) + .then((_dashboard) => { + runInAction(() => { + if (isCreating) { + toast.success('Dashboard created successfully'); + this.addDashboard(new Dashboard().fromJson(_dashboard)); + } else { + toast.success('Dashboard successfully updated '); + this.updateDashboard(new Dashboard().fromJson(_dashboard)); + } + resolve(_dashboard); + }); + }) + .catch((error) => { + toast.error('Error saving dashboard'); + reject(); + }) + .finally(() => { + runInAction(() => { + this.isSaving = false; + }); + }); + }); + } + + saveMetric(metric: Widget, dashboardId: string): Promise<any> { + const isCreating = !metric.widgetId; + return dashboardService.saveMetric(metric, dashboardId).then((metric) => { + runInAction(() => { + if (isCreating) { + this.selectedDashboard?.widgets.push(metric); + } else { + this.selectedDashboard?.widgets.map((w) => { + if (w.widgetId === metric.widgetId) { + w.update(metric); + } + }); + } + }); + }); + } + + deleteDashboard(dashboard: Dashboard): Promise<any> { + this.isDeleting = true; + return dashboardService + .deleteDashboard(dashboard.dashboardId) + .then(() => { + toast.success('Dashboard deleted successfully'); + runInAction(() => { + this.removeDashboard(dashboard); + }); + }) + .catch(() => { + toast.error('Dashboard could not be deleted'); + }) + .finally(() => { + runInAction(() => { + this.isDeleting = false; + }); + }); + } + + toJson() { + return { + dashboards: this.dashboards.map((d) => d.toJson()), + }; + } + + fromJson(json: any) { + runInAction(() => { + this.dashboards = json.dashboards.map((d: Record<string, any>) => + new Dashboard().fromJson(d) + ); + }); + return this; + } + + addDashboard(dashboard: Dashboard) { + this.dashboards.push(new Dashboard().fromJson(dashboard)); + } + + removeDashboard(dashboard: Dashboard) { + this.dashboards = this.dashboards.filter((d) => d.dashboardId !== dashboard.dashboardId); + } + + getDashboard(dashboardId: string | number): Dashboard | null { + return this.dashboards.find((d) => d.dashboardId == dashboardId) || null; + } + + getDashboardByIndex(index: number) { + return this.dashboards[index]; + } + + getDashboardCount() { + return this.dashboards.length; + } + + updateDashboard(dashboard: Dashboard) { + const index = this.dashboards.findIndex((d) => d.dashboardId === dashboard.dashboardId); + if (index >= 0) { + this.dashboards[index] = dashboard; + if (this.selectedDashboard?.dashboardId === dashboard.dashboardId) { + this.selectDashboardById(dashboard.dashboardId); + } + } + } + + selectDashboardById = (dashboardId: any) => { + this.selectedDashboard = + this.dashboards.find((d) => d.dashboardId == dashboardId) || new Dashboard(); + }; + + getDashboardById = (dashboardId: string) => { + const dashboard = this.dashboards.find((d) => d.dashboardId == dashboardId); + + if (dashboard) { + this.selectedDashboard = dashboard; + return true; + } else { + this.selectedDashboard = null; + return false; + } + }; + + setSiteId = (siteId: any) => { + this.siteId = siteId; + }; + + fetchTemplates(hardRefresh: boolean): Promise<any> { + this.loadingTemplates = true; + return new Promise((resolve, reject) => { + if (this.widgetCategories.length > 0 && !hardRefresh) { + resolve(this.widgetCategories); + } else { + metricService + .getTemplates() + .then((response) => { + const categories: any[] = []; + response.forEach((category: any) => { + const widgets: any[] = []; + category.widgets.forEach((widget: any) => { + const w = new Widget().fromJson(widget); + widgets.push(w); + }); + const c: any = {}; + c.widgets = widgets; + c.name = category.category; + c.description = category.description; + categories.push(c); + }); + this.widgetCategories = categories; + resolve(this.widgetCategories); + }) + .catch((error) => { + reject(error); + }) + .finally(() => { + this.loadingTemplates = false; + }); + } + }); + } + + deleteDashboardWidget(dashboardId: string, widgetId: string) { + this.isDeleting = true; + return dashboardService + .deleteWidget(dashboardId, widgetId) + .then(() => { + toast.success('Dashboard updated successfully'); + runInAction(() => { + this.selectedDashboard?.removeWidget(widgetId); + }); + }) + .finally(() => { + this.isDeleting = false; + }); + } + + addWidgetToDashboard(dashboard: Dashboard, metricIds: any): Promise<any> { + this.isSaving = true; + return dashboardService + .addWidget(dashboard, metricIds) + .then((response) => { + toast.success('Card added to dashboard.'); + }) + .catch(() => { + toast.error('Card could not be added.'); + }) + .finally(() => { + this.isSaving = false; + }); + } + + setPeriod(period: any) { + this.period = Period({ + start: period.start, + end: period.end, + rangeName: period.rangeName, + }); + } + + setDrillDownPeriod(period: any) { + this.drillDownPeriod = Period({ + start: period.start, + end: period.end, + rangeName: period.rangeName, + }); + } + + toggleAlertModal(val: boolean) { + this.showAlertModal = val + } + + fetchMetricChartData( + metric: Widget, + data: any, + isWidget: boolean = false, + period: Record<string, any> + ): Promise<any> { + period = period.toTimestamps(); + const params = { ...period, ...data, key: metric.predefinedKey }; + + if (metric.page && metric.limit) { + params['page'] = metric.page; + params['limit'] = metric.limit; + } + + return new Promise((resolve, reject) => { + this.pendingRequests += 1; + return metricService + .getMetricChartData(metric, params, isWidget) + .then((data: any) => { + metric.setData(data, period); + resolve(metric.data); + }) + .catch((err: any) => { + reject(err); + }) + .finally(() => { + setTimeout(() => { + this.pendingRequests = this.pendingRequests - 1; + }, 100); + }); + }); + } } diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index 82d611ee1..af7050955 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -57,13 +57,14 @@ export default class MetricStore { get filteredCards() { const filterRE = this.filter.query ? getRE(this.filter.query, 'i') : null; - const dbIds = this.filter.dashboard ? this.filter.dashboard.map((i) => i.value) : []; + const dbIds = this.filter.dashboard ? this.filter.dashboard.map((i: any) => i.value) : []; return this.metrics .filter( (card) => (this.filter.type === 'all' || card.metricType === this.filter.type) && (!dbIds.length || card.dashboards.map((i) => i.dashboardId).some((id) => dbIds.includes(id))) && + // @ts-ignore (!filterRE || ['name', 'owner'].some((key) => filterRE.test(card[key]))) ) .sort((a, b) => From 16c22f8cf685951b1c14539a3df754cc7be921d4 Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Fri, 13 Jan 2023 18:20:42 +0100 Subject: [PATCH 54/65] feat(ui) - insights - data format --- .../InsightsCard/InsightItem.tsx | 84 ++++++++++++++++--- .../InsightsCard/InsightsCard.tsx | 9 +- .../MetricViewHeader/MetricViewHeader.tsx | 2 +- frontend/app/mstore/types/widget.ts | 39 ++++----- 4 files changed, 94 insertions(+), 40 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx index db81f9afb..439fcee32 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx @@ -8,27 +8,87 @@ interface Props { } function InsightItem(props: Props) { const { item, onClick = () => {} } = props; + const className = + 'flex items-center py-4 hover:bg-active-blue -mx-4 px-4 border-b last:border-transparent cursor-pointer'; + + switch (item.category) { + case IssueCategory.RAGE: + return <RageItem onClick={onClick} item={item} className={className} />; + case IssueCategory.RESOURCES: + return <ResourcesItem onClick={onClick} item={item} className={className} />; + case IssueCategory.ERRORS: + return <ErrorItem onClick={onClick} item={item} className={className} />; + case IssueCategory.NETWORK: + return <NetworkItem onClick={onClick} item={item} className={className} />; + default: + return null; + } +} + +export default InsightItem; + +function ErrorItem({ item, className, onClick }: any) { return ( - <div - className="flex items-center py-4 hover:bg-active-blue -mx-4 px-4 border-b last:border-transparent cursor-pointer" - onClick={onClick} - > + <div className={className} onClick={onClick}> <Icon name={item.icon} size={20} className="mr-2" color={item.iconColor} /> - <div className="mx-1 font-medium">{item.label}</div> - {item.category === IssueCategory.RAGE && ( + {item.isNew ? ( <> - <div className="mx-1">on</div> <div className="mx-1 bg-gray-100 px-2 rounded">{item.name}</div> + <div className="mx-1">error observed</div> + <div className="mx-1 font-medium color-red">{item.ratio}%</div> + <div className="mx-1">more than other new errors</div> </> - )} - {item.increase && ( + ) : ( <> - <div className="mx-1">increased by</div> - <div className="font-medium text-red">{item.increase}%</div> + <div className="mx-1">Increase</div> + <div className="mx-1">in</div> + <div className="mx-1">{item.name}</div> + <div className="mx-1 font-medium color-red">{item.change}%</div> </> )} </div> ); } -export default InsightItem; +function NetworkItem({ item, className, onClick }: any) { + return ( + <div className={className} onClick={onClick}> + <Icon name={item.icon} size={20} className="mr-2" color={item.iconColor} /> + <div className="mx-1">Network request</div> + <div className="mx-1 bg-gray-100 px-2 rounded">{item.name}</div> + <div className="mx-1">{item.change > 0 ? 'increased' : 'decreased'}</div> + <div className="font-medium text-red">{item.change}%</div> + </div> + ); +} + +function ResourcesItem({ item, className, onClick }: any) { + return ( + <div className={className} onClick={onClick}> + <Icon name={item.icon} size={20} className="mr-2" color={item.iconColor} /> + <div className="mx-1">{item.change > 0 ? 'Inrease' : 'Decrease'}</div> + <div className="mx-1">in</div> + <div className="mx-1 bg-gray-100 px-2 rounded">{item.name}</div> + <div className="font-medium text-red">{item.change}%</div> + </div> + ); +} + +function RageItem({ item, className, onClick }: any) { + return ( + <div className={className} onClick={onClick}> + <Icon name={item.icon} size={20} className="mr-2" color={item.iconColor} /> + <div className="mx-1 bg-gray-100 px-2 rounded">{item.isNew ? item.name : 'Click Rage'}</div> + {item.isNew && <div className="mx-1">has</div>} + {!item.isNew && <div className="mx-1">on</div>} + {item.isNew && <div className="font-medium text-red">{item.ratio}%</div>} + {item.isNew && <div className="mx-1">more clickrage than other raged elements.</div>} + {!item.isNew && ( + <> + <div className="mx-1">increase by</div> + <div className="mx-1">{item.change}</div> + </> + )} + </div> + ); +} diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx index 78d384c21..48f9d29c7 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx @@ -29,9 +29,12 @@ function InsightsCard(props: Props) { title={NO_METRIC_DATA} style={{ padding: '100px 0' }} > - {metric.data.issues && metric.data.issues.map((item: any) => ( - <InsightItem item={item} onClick={clickHanddler} /> - ))} + <div className="overflow-y-auto" style={{ maxHeight: '240px' }}> + {metric.data.issues && + metric.data.issues.map((item: any) => ( + <InsightItem item={item} onClick={clickHanddler} /> + ))} + </div> </NoContent> ); } diff --git a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx index 6dcecabb2..ea5311082 100644 --- a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx +++ b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx @@ -4,7 +4,7 @@ import MetricsSearch from '../MetricsSearch'; import Select from 'Shared/Select'; import { useStore } from 'App/mstore'; import { observer, useObserver } from 'mobx-react-lite'; -import { DROPDOWN_OPTIONS, Option } from 'App/constants/card'; +import { DROPDOWN_OPTIONS } from 'App/constants/card'; function MetricViewHeader() { const { metricStore } = useStore(); diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index 6d1e7d48e..c5f6b6b12 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -14,44 +14,34 @@ import { getChartFormatter } from 'Types/dashboard/helper'; export class InishtIssue { icon: string; iconColor: string; - increase: number; + change: number; isNew = false; category: string; label: string; + value: number; + isIncreased?: boolean; constructor( category: string, public name: string, public ratio: number, - increase = 0, + value = 0, + change = 0, isNew = false ) { this.category = category; + this.value = Math.round(value); // @ts-ignore this.label = issueCategoriesMap[category]; this.icon = `ic-${category}`; this.iconColor = 'red'; - this.increase = increase; + this.change = parseInt(change.toFixed(2)); + this.isIncreased = this.change > 0; this.isNew = isNew; } } -function generateRandomString(stringArray: string[]): string { - const randomIndex = Math.floor(Math.random() * stringArray.length); - return stringArray[randomIndex]; -} - -function randomIssue() { - return new InishtIssue( - generateRandomString(['rage', 'resources', 'network', 'errors']), - generateRandomString(['Login', 'Update', '/sessions/data', 'Reload']), - Math.floor(Math.random() * 50), - Math.floor(Math.random() * 100), - false - ); -} - export default class Widget { public static get ID_KEY():string { return "metricId" } metricId: any = undefined @@ -126,9 +116,10 @@ export default class Widget { this.metricFormat = json.metricFormat; this.viewType = json.viewType; this.name = json.name; - this.series = (json.series && json.series.length > 0) - ? json.series.map((series: any) => new FilterSeries().fromJson(series)) - : [new FilterSeries()]; + this.series = + json.series && json.series.length > 0 + ? json.series.map((series: any) => new FilterSeries().fromJson(series)) + : [new FilterSeries()]; this.dashboards = json.dashboards || []; this.owner = json.ownerEmail; this.lastModified = @@ -198,9 +189,9 @@ export default class Widget { if (this.metricOf === FilterKey.ERRORS) { _data['errors'] = data.errors.map((s: any) => new Error().fromJSON(s)); } else if (this.metricType === INSIGHTS) { - // TODO read from the response - _data['issues'] = [1, 2, 3, 4].map((i: any) => randomIssue()); - console.log('_data', _data); + _data['issues'] = data.filter((i: any) => i.change > 0 || i.change < 0).map( + (i: any) => new InishtIssue(i.category, i.name, i.ratio, i.value, i.change, i.isNew) + ); } else { if (data.hasOwnProperty('chart')) { _data['chart'] = getChartFormatter(period)(data.chart); From 0d83e0d94a9877ac6b9ef9dc712701fbf4f51ae8 Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Fri, 13 Jan 2023 18:28:54 +0100 Subject: [PATCH 55/65] feat(ui) - insights - data format arrow --- .../InsightsCard/InsightItem.tsx | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx index 439fcee32..d8cf51a88 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx @@ -1,6 +1,7 @@ import { IssueCategory } from 'App/types/filter/filterType'; import React from 'react'; import { Icon } from 'UI'; +import cn from 'classnames'; interface Props { item: any; @@ -27,6 +28,25 @@ function InsightItem(props: Props) { export default InsightItem; + +function Change({ change, isIncreased }: any) { + return ( + <div + className={cn('font-medium flex items-center', { + 'text-red': isIncreased, + 'text-tealx': !isIncreased, + })} + > + <Icon + name={isIncreased ? 'arrow-up-short' : 'arrow-down-short'} + color={isIncreased ? 'red' : 'tealx'} + size={20} + /> + {change}% + </div> + ); +} + function ErrorItem({ item, className, onClick }: any) { return ( <div className={className} onClick={onClick}> @@ -43,7 +63,7 @@ function ErrorItem({ item, className, onClick }: any) { <div className="mx-1">Increase</div> <div className="mx-1">in</div> <div className="mx-1">{item.name}</div> - <div className="mx-1 font-medium color-red">{item.change}%</div> + <Change change={item.change} isIncreased={item.isIncreased} /> </> )} </div> @@ -57,7 +77,7 @@ function NetworkItem({ item, className, onClick }: any) { <div className="mx-1">Network request</div> <div className="mx-1 bg-gray-100 px-2 rounded">{item.name}</div> <div className="mx-1">{item.change > 0 ? 'increased' : 'decreased'}</div> - <div className="font-medium text-red">{item.change}%</div> + <Change change={item.change} isIncreased={item.isIncreased} /> </div> ); } @@ -69,7 +89,7 @@ function ResourcesItem({ item, className, onClick }: any) { <div className="mx-1">{item.change > 0 ? 'Inrease' : 'Decrease'}</div> <div className="mx-1">in</div> <div className="mx-1 bg-gray-100 px-2 rounded">{item.name}</div> - <div className="font-medium text-red">{item.change}%</div> + <Change change={item.change} isIncreased={item.isIncreased} /> </div> ); } @@ -86,7 +106,7 @@ function RageItem({ item, className, onClick }: any) { {!item.isNew && ( <> <div className="mx-1">increase by</div> - <div className="mx-1">{item.change}</div> + <Change change={item.change} isIncreased={item.isIncreased} /> </> )} </div> From 286b0b25f8a949b002fecb1a4f122113645f2fb3 Mon Sep 17 00:00:00 2001 From: Alexander <zavorotynskiy@pm.me> Date: Tue, 17 Jan 2023 16:03:40 +0100 Subject: [PATCH 56/65] Kafka library upgrade (#932) * feat(backend): upgrade go modules * upgrade alpine version in dockerfile --- backend/Dockerfile | 2 +- backend/go.mod | 2 +- backend/go.sum | 57 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index ad60c1b75..05a46c87f 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.18-alpine3.15 AS prepare +FROM golang:1.18-alpine3.17 AS prepare RUN apk add --no-cache git openssh openssl-dev pkgconf gcc g++ make libc-dev bash librdkafka-dev cyrus-sasl cyrus-sasl-gssapiv2 krb5 diff --git a/backend/go.mod b/backend/go.mod index 61d644a17..0615fb0cb 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -8,7 +8,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/aws/aws-sdk-go v1.44.98 github.com/btcsuite/btcutil v1.0.2 - github.com/confluentinc/confluent-kafka-go v1.8.2 + github.com/confluentinc/confluent-kafka-go v1.9.2 github.com/elastic/go-elasticsearch/v7 v7.13.1 github.com/go-redis/redis v6.15.9+incompatible github.com/google/uuid v1.3.0 diff --git a/backend/go.sum b/backend/go.sum index c7abea25e..5aa3ae3de 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -68,6 +68,9 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/actgardner/gogen-avro/v10 v10.1.0/go.mod h1:o+ybmVjEa27AAr35FRqU98DJu1fXES56uXniYFv4yDA= +github.com/actgardner/gogen-avro/v10 v10.2.1/go.mod h1:QUhjeHPchheYmMDni/Nx7VB0RsT/ee8YIgGY/xpEQgQ= +github.com/actgardner/gogen-avro/v9 v9.1.0/go.mod h1:nyTj6wPqDJoxM3qdnjcLv+EnMDSDFqE0qDpva2QRmKc= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -115,11 +118,12 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/confluentinc/confluent-kafka-go v1.8.2 h1:PBdbvYpyOdFLehj8j+9ba7FL4c4Moxn79gy9cYKxG5E= -github.com/confluentinc/confluent-kafka-go v1.8.2/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= +github.com/confluentinc/confluent-kafka-go v1.9.2 h1:gV/GxhMBUb03tFWkN+7kdhg+zf+QUM+wVkI9zwh770Q= +github.com/confluentinc/confluent-kafka-go v1.9.2/go.mod h1:ptXNqsuDfYbAE/LBW6pnwWZElUoWxHoV8E43DCrliyo= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -136,6 +140,10 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= +github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= +github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= +github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -195,10 +203,13 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -233,6 +244,7 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20211008130755-947d60d73cc0/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -250,12 +262,17 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hamba/avro v1.5.6/go.mod h1:3vNT0RLXXpFm2Tb/5KC71ZRJlOroggq1Rcitb6k4Fr8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/heetch/avro v0.3.1/go.mod h1:4xn38Oz/+hiEUTpbVfGVLfvOg0yKLlRP7Q9+gJJILgA= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/invopop/jsonschema v0.4.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -301,6 +318,11 @@ github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jackc/puddle v1.2.2-0.20220404125616-4e959849469a h1:oH7y/b+q2BEerCnARr/HZc1NxOYbKSJor4MqQXlhh+s= github.com/jackc/puddle v1.2.2-0.20220404125616-4e959849469a/go.mod h1:ZQuO1Un86Xpe1ShKl08ERTzYhzWq+OvrvotbpeE3XO0= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= +github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= +github.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -314,6 +336,7 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/juju/qthttptest v0.1.1/go.mod h1:aTlAv8TYaflIiTDIQYzxnl1QdPjAg8Q8qJMErpKy6A4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -327,16 +350,23 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/linkedin/goavro v2.1.0+incompatible/go.mod h1:bBCwI2eGYpUI/4820s67MElg9tdeLbINjLjiM2xZFYM= +github.com/linkedin/goavro/v2 v2.10.0/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA= +github.com/linkedin/goavro/v2 v2.10.1/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA= +github.com/linkedin/goavro/v2 v2.11.1/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -354,6 +384,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nrwiersma/avro-benchmarks v0.0.0-20210913175520-21aec48c8f76/go.mod h1:iKyFMidsk/sVYONJRE372sJuX/QTRPacU7imPqqsu7g= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -367,6 +398,7 @@ github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKf github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -395,11 +427,16 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a/go.mod h1:4r5QyqhjIWCcK8DO4KMclc5Iknq5qVBAlbYYzAbUScQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sethvargo/go-envconfig v0.7.0 h1:P/ljQXSRjgAgsnIripHs53Jg/uNVXu2FYQ9yLSDappA= github.com/sethvargo/go-envconfig v0.7.0/go.mod h1:00S1FAhRUuTNJazWBWcJGvEHOM+NO6DhoRMAOX7FY5o= @@ -420,6 +457,7 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -539,6 +577,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -663,6 +702,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -729,6 +769,7 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -882,6 +923,7 @@ google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220503193339-ba3ae3f07e29/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= @@ -933,14 +975,19 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/avro.v0 v0.0.0-20171217001914-a730b5802183/go.mod h1:FvqrFXt+jCsyQibeRv4xxEJBL5iG2DDW5aeJwzDiq4A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v1 v1.0.0/go.mod h1:CxwszS/Xz1C49Ucd2i6Zil5UToP1EmyrFhKaMVbg1mk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/httprequest.v1 v1.2.1/go.mod h1:x2Otw96yda5+8+6ZeWwHIJTFkEHWP/qP8pJOzqEtWPM= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -948,11 +995,13 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 270f02029926d0089b57f98fd286da2d5fe86814 Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Wed, 18 Jan 2023 11:13:42 +0100 Subject: [PATCH 57/65] feat(ui) - insights - drilldown filters --- .../InsightsCard/InsightItem.tsx | 10 +- .../InsightsCard/InsightsCard.tsx | 62 ++++++-- .../components/WidgetForm/WidgetForm.tsx | 2 +- .../WidgetSessions/WidgetSessions.tsx | 1 - frontend/app/components/ui/SVG.tsx | 8 +- frontend/app/mstore/types/widget.ts | 150 ++++++++++-------- frontend/app/svg/icons/ic-errors.svg | 5 +- frontend/app/svg/icons/ic-network.svg | 5 +- frontend/app/svg/icons/ic-rage.svg | 5 +- frontend/app/svg/icons/ic-resources.svg | 5 +- 10 files changed, 160 insertions(+), 93 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx index d8cf51a88..3705ceaa7 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx @@ -40,7 +40,7 @@ function Change({ change, isIncreased }: any) { <Icon name={isIncreased ? 'arrow-up-short' : 'arrow-down-short'} color={isIncreased ? 'red' : 'tealx'} - size={20} + size={18} /> {change}% </div> @@ -50,7 +50,7 @@ function Change({ change, isIncreased }: any) { function ErrorItem({ item, className, onClick }: any) { return ( <div className={className} onClick={onClick}> - <Icon name={item.icon} size={20} className="mr-2" color={item.iconColor} /> + <Icon name={item.icon} size={18} className="mr-2" color={item.iconColor} /> {item.isNew ? ( <> <div className="mx-1 bg-gray-100 px-2 rounded">{item.name}</div> @@ -73,7 +73,7 @@ function ErrorItem({ item, className, onClick }: any) { function NetworkItem({ item, className, onClick }: any) { return ( <div className={className} onClick={onClick}> - <Icon name={item.icon} size={20} className="mr-2" color={item.iconColor} /> + <Icon name={item.icon} size={18} className="mr-2" color={item.iconColor} /> <div className="mx-1">Network request</div> <div className="mx-1 bg-gray-100 px-2 rounded">{item.name}</div> <div className="mx-1">{item.change > 0 ? 'increased' : 'decreased'}</div> @@ -85,7 +85,7 @@ function NetworkItem({ item, className, onClick }: any) { function ResourcesItem({ item, className, onClick }: any) { return ( <div className={className} onClick={onClick}> - <Icon name={item.icon} size={20} className="mr-2" color={item.iconColor} /> + <Icon name={item.icon} size={18} className="mr-2" color={item.iconColor} /> <div className="mx-1">{item.change > 0 ? 'Inrease' : 'Decrease'}</div> <div className="mx-1">in</div> <div className="mx-1 bg-gray-100 px-2 rounded">{item.name}</div> @@ -97,7 +97,7 @@ function ResourcesItem({ item, className, onClick }: any) { function RageItem({ item, className, onClick }: any) { return ( <div className={className} onClick={onClick}> - <Icon name={item.icon} size={20} className="mr-2" color={item.iconColor} /> + <Icon name={item.icon} size={18} className="mr-2" color={item.iconColor} /> <div className="mx-1 bg-gray-100 px-2 rounded">{item.isNew ? item.name : 'Click Rage'}</div> {item.isNew && <div className="mx-1">has</div>} {!item.isNew && <div className="mx-1">on</div>} diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx index 48f9d29c7..9ac804853 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx @@ -4,23 +4,61 @@ import { observer } from 'mobx-react-lite'; import React from 'react'; import InsightItem from './InsightItem'; import { NO_METRIC_DATA } from 'App/constants/messages'; +import { InishtIssue } from 'App/mstore/types/widget'; +import { FilterKey, IssueCategory, IssueType } from 'App/types/filter/filterType'; +import { filtersMap } from 'Types/filter/newFilter'; -interface Props {} -function InsightsCard(props: Props) { +function InsightsCard() { const { metricStore, dashboardStore } = useStore(); const metric = metricStore.instance; const drillDownFilter = dashboardStore.drillDownFilter; const period = dashboardStore.period; - const clickHanddler = (e: React.MouseEvent<HTMLDivElement>) => { - console.log(e); - // TODO update drillDownFilter - // const periodTimestamps = period.toTimestamps(); - // drillDownFilter.merge({ - // filters: event, - // startTimestamp: periodTimestamps.startTimestamp, - // endTimestamp: periodTimestamps.endTimestamp, - // }); + const clickHanddler = (e: React.MouseEvent<HTMLDivElement>, item: InishtIssue) => { + let filter: any = {}; + switch (item.category) { + case IssueCategory.RESOURCES: + filter = { + ...filtersMap[ + item.name === IssueType.MEMORY ? FilterKey.AVG_MEMORY_USAGE : FilterKey.AVG_CPU + ], + }; + filter.source = [item.oldValue]; + filter.value = []; + break; + case IssueCategory.RAGE: + filter = { ...filtersMap[FilterKey.CLICK] }; + filter.value = [item.name]; + break; + case IssueCategory.NETWORK: + filter = { ...filtersMap[FilterKey.FETCH_URL] }; + filter.filters = [ + { ...filtersMap[FilterKey.FETCH_URL], value: [item.name] }, + { ...filtersMap[FilterKey.FETCH_DURATION], value: [item.oldValue] }, + ]; + filter.value = []; + break; + case IssueCategory.ERRORS: + filter = { ...filtersMap[FilterKey.ERROR] }; + break; + } + + filter.type = filter.key; + delete filter.key; + delete filter.operatorOptions; + delete filter.sourceOperatorOptions; + delete filter.placeholder; + delete filter.sourcePlaceholder; + delete filter.sourceType; + delete filter.sourceUnit; + delete filter.category; + delete filter.icon; + delete filter.label; + delete filter.options; + + drillDownFilter.merge({ + filters: [filter], + }); }; return ( @@ -32,7 +70,7 @@ function InsightsCard(props: Props) { <div className="overflow-y-auto" style={{ maxHeight: '240px' }}> {metric.data.issues && metric.data.issues.map((item: any) => ( - <InsightItem item={item} onClick={clickHanddler} /> + <InsightItem item={item} onClick={(e) => clickHanddler(e, item)} /> ))} </div> </NoContent> diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index 64ebef507..591011214 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -112,7 +112,7 @@ function WidgetForm(props: Props) { await confirm({ header: 'Confirm', confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to permanently delete this metric?`, + confirmation: `Are you sure you want to permanently delete this card?`, }) ) { metricStore.delete(metric).then(props.onDelete); diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index 32127c3dc..79f42612c 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -81,7 +81,6 @@ function WidgetSessions(props: Props) { const customFilter = { ...filter, ...timeRange, filters: [ ...sessionStore.userFilter.filters, clickFilter]} debounceClickMapSearch(customFilter) } else { - console.log(widget) debounceRequest(widget.metricId, { ...filter, series: widget.series, diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index 22fe3491f..5e2a484ab 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -259,10 +259,10 @@ const SVG = (props: Props) => { case 'high-engagement': return <svg viewBox="0 0 640 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="m638.59 368.22-33.37-211.59c-8.86-50.26-48.4-90.77-100.66-103.13h-.07a803.14 803.14 0 0 0-369 0C83.17 65.86 43.64 106.36 34.78 156.63L1.41 368.22C-8.9 426.73 38.8 480 101.51 480c49.67 0 93.77-30.07 109.48-74.64l7.52-21.36h203l7.49 21.36C444.72 449.93 488.82 480 538.49 480c62.71 0 110.41-53.27 100.1-111.78zm-45.11 54.88c-13.28 15.82-33.33 24.9-55 24.9-36.2 0-68.07-21.41-79.29-53.27l-7.53-21.36-7.52-21.37H195.86l-7.53 21.37-7.53 21.36C169.58 426.59 137.71 448 101.51 448c-21.66 0-41.71-9.08-55-24.9A59.93 59.93 0 0 1 33 373.2l33.28-211c6.66-37.7 36.72-68.14 76.53-77.57a771.07 771.07 0 0 1 354.38 0c39.84 9.42 69.87 39.86 76.42 77l33.47 212.15c3.11 17.64-1.72 35.16-13.6 49.32zm-339.3-218.74h-42.54v-42.54a9.86 9.86 0 0 0-9.82-9.82h-19.64a9.86 9.86 0 0 0-9.82 9.82v42.54h-42.54a9.86 9.86 0 0 0-9.82 9.82v19.64a9.86 9.86 0 0 0 9.82 9.82h42.54v42.54a9.86 9.86 0 0 0 9.82 9.82h19.64a9.86 9.86 0 0 0 9.82-9.82v-42.54h42.54a9.86 9.86 0 0 0 9.82-9.82v-19.64a9.86 9.86 0 0 0-9.82-9.82zM416 224a32 32 0 1 0 32 32 32 32 0 0 0-32-32zm64-64a32 32 0 1 0 32 32 32 32 0 0 0-32-32z"/></svg>; case 'history': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M20 24h10c6.627 0 12 5.373 12 12v94.625C85.196 57.047 165.239 7.715 256.793 8.001 393.18 8.428 504.213 120.009 504 256.396 503.786 393.181 392.834 504 256 504c-63.926 0-122.202-24.187-166.178-63.908-5.113-4.618-5.354-12.561-.482-17.433l7.069-7.069c4.503-4.503 11.749-4.714 16.482-.454C150.782 449.238 200.935 470 256 470c117.744 0 214-95.331 214-214 0-117.744-95.331-214-214-214-82.862 0-154.737 47.077-190.289 116H164c6.627 0 12 5.373 12 12v10c0 6.627-5.373 12-12 12H20c-6.627 0-12-5.373-12-12V36c0-6.627 5.373-12 12-12zm321.647 315.235 4.706-6.47c3.898-5.36 2.713-12.865-2.647-16.763L272 263.853V116c0-6.627-5.373-12-12-12h-8c-6.627 0-12 5.373-12 12v164.147l84.884 61.734c5.36 3.899 12.865 2.714 16.763-2.646z"/></svg>; case 'hourglass-start': return <svg viewBox="0 0 384 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M368 32h4c6.627 0 12-5.373 12-12v-8c0-6.627-5.373-12-12-12H12C5.373 0 0 5.373 0 12v8c0 6.627 5.373 12 12 12h4c0 91.821 44.108 193.657 129.646 224C59.832 286.441 16 388.477 16 480h-4c-6.627 0-12 5.373-12 12v8c0 6.627 5.373 12 12 12h360c6.627 0 12-5.373 12-12v-8c0-6.627-5.373-12-12-12h-4c0-91.821-44.108-193.657-129.646-224C324.168 225.559 368 123.523 368 32zM48 32h288c0 110.457-64.471 200-144 200S48 142.457 48 32zm288 448H48c0-110.457 64.471-200 144-200s144 89.543 144 200zM285.621 96H98.379a12.01 12.01 0 0 1-11.602-8.903 199.464 199.464 0 0 1-2.059-8.43C83.054 71.145 88.718 64 96.422 64h191.157c7.704 0 13.368 7.145 11.704 14.667a199.464 199.464 0 0 1-2.059 8.43A12.013 12.013 0 0 1 285.621 96zm-15.961 50.912a141.625 141.625 0 0 1-6.774 8.739c-2.301 2.738-5.671 4.348-9.248 4.348H130.362c-3.576 0-6.947-1.61-9.248-4.348a142.319 142.319 0 0 1-6.774-8.739c-5.657-7.91.088-18.912 9.813-18.912h135.694c9.725 0 15.469 11.003 9.813 18.912z"/></svg>; - case 'ic-errors': return <svg viewBox="0 0 496 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg>; - case 'ic-network': return <svg viewBox="0 0 496 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg>; - case 'ic-rage': return <svg viewBox="0 0 496 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg>; - case 'ic-resources': return <svg viewBox="0 0 496 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg>; + case 'ic-errors': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/></svg>; + case 'ic-network': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M7.646 10.854a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 9.293V5.5a.5.5 0 0 0-1 0v3.793L6.354 8.146a.5.5 0 1 0-.708.708l2 2z"/><path d="M4.406 3.342A5.53 5.53 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773 16 11.569 14.502 13 12.687 13H3.781C1.708 13 0 11.366 0 9.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383zm.653.757c-.757.653-1.153 1.44-1.153 2.056v.448l-.445.049C2.064 6.805 1 7.952 1 9.318 1 10.785 2.23 12 3.781 12h8.906C13.98 12 15 10.988 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3a4.53 4.53 0 0 0-2.941 1.1z"/></svg>; + case 'ic-rage': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="M4.285 12.433a.5.5 0 0 0 .683-.183A3.498 3.498 0 0 1 8 10.5c1.295 0 2.426.703 3.032 1.75a.5.5 0 0 0 .866-.5A4.498 4.498 0 0 0 8 9.5a4.5 4.5 0 0 0-3.898 2.25.5.5 0 0 0 .183.683zm6.991-8.38a.5.5 0 1 1 .448.894l-1.009.504c.176.27.285.64.285 1.049 0 .828-.448 1.5-1 1.5s-1-.672-1-1.5c0-.247.04-.48.11-.686a.502.502 0 0 1 .166-.761l2-1zm-6.552 0a.5.5 0 0 0-.448.894l1.009.504A1.94 1.94 0 0 0 5 6.5C5 7.328 5.448 8 6 8s1-.672 1-1.5c0-.247-.04-.48-.11-.686a.502.502 0 0 0-.166-.761l-2-1z"/></svg>; + case 'ic-resources': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/><path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/></svg>; case 'id-card': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/><path d="M3 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0-5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5v-1z"/></svg>; case 'image': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M4.502 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/><path d="M14.002 13a2 2 0 0 1-2 2h-10a2 2 0 0 1-2-2V5A2 2 0 0 1 2 3a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v8a2 2 0 0 1-1.998 2zM14 2H4a1 1 0 0 0-1 1h9.002a2 2 0 0 1 2 2v7A1 1 0 0 0 15 11V3a1 1 0 0 0-1-1zM2.002 4a1 1 0 0 0-1 1v8l2.646-2.354a.5.5 0 0 1 .63-.062l2.66 1.773 3.71-3.71a.5.5 0 0 1 .577-.094l1.777 1.947V5a1 1 0 0 0-1-1h-10z"/></svg>; case 'info-circle-fill': return <svg viewBox="0 0 36 36" width={ `${ width }px` } height={ `${ height }px` } ><path d="M17.75 35.5a17.75 17.75 0 1 0 0-35.5 17.75 17.75 0 0 0 0 35.5Zm2.064-20.883-2.22 10.44c-.155.754.065 1.182.675 1.182.43 0 1.08-.155 1.522-.546l-.195.923c-.637.768-2.041 1.327-3.25 1.327-1.56 0-2.224-.937-1.793-2.927l1.637-7.694c.142-.65.014-.886-.637-1.043l-1-.18.182-.845 5.08-.637h-.002Zm-2.064-2.414a2.219 2.219 0 1 1 0-4.437 2.219 2.219 0 0 1 0 4.437Z"/></svg>; diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index c5f6b6b12..56e68f2d5 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -19,18 +19,21 @@ export class InishtIssue { category: string; label: string; value: number; + oldValue: number; isIncreased?: boolean; constructor( category: string, public name: string, public ratio: number, + oldValue = 0, value = 0, change = 0, isNew = false ) { this.category = category; this.value = Math.round(value); + this.oldValue = Math.round(oldValue); // @ts-ignore this.label = issueCategoriesMap[category]; this.icon = `ic-${category}`; @@ -43,46 +46,48 @@ export class InishtIssue { } export default class Widget { - public static get ID_KEY():string { return "metricId" } - metricId: any = undefined - widgetId: any = undefined - category?: string = undefined - name: string = "Untitled Card" - metricType: string = "timeseries" - metricOf: string = "sessionCount" - metricValue: string = "" - viewType: string = "lineChart" - metricFormat: string = "sessionCount" - series: FilterSeries[] = [] - sessions: [] = [] - isPublic: boolean = true - owner: string = "" - lastModified: number = new Date().getTime() - dashboards: any[] = [] - dashboardIds: any[] = [] - config: any = {} - page: number = 1 - limit: number = 5 - thumbnail?: string - params: any = { density: 70 } + public static get ID_KEY(): string { + return 'metricId'; + } + metricId: any = undefined; + widgetId: any = undefined; + category?: string = undefined; + name: string = 'Untitled Card'; + metricType: string = 'timeseries'; + metricOf: string = 'sessionCount'; + metricValue: string = ''; + viewType: string = 'lineChart'; + metricFormat: string = 'sessionCount'; + series: FilterSeries[] = []; + sessions: [] = []; + isPublic: boolean = true; + owner: string = ''; + lastModified: number = new Date().getTime(); + dashboards: any[] = []; + dashboardIds: any[] = []; + config: any = {}; + page: number = 1; + limit: number = 5; + thumbnail?: string; + params: any = { density: 70 }; period: Record<string, any> = Period({ rangeName: LAST_24_HOURS }); // temp value in detail view hasChanged: boolean = false; - position: number = 0 - data: any = { - sessions: [], - issues: [], - total: 0, - chart: [], - namesMap: {}, - avg: 0, - percentiles: [], - } - isLoading: boolean = false - isValid: boolean = false - dashboardId: any = undefined - predefinedKey: string = '' + position: number = 0; + data: any = { + sessions: [], + issues: [], + total: 0, + chart: [], + namesMap: {}, + avg: 0, + percentiles: [], + }; + isLoading: boolean = false; + isValid: boolean = false; + dashboardId: any = undefined; + predefinedKey: string = ''; constructor() { makeAutoObservable(this); @@ -140,34 +145,44 @@ export default class Widget { return this; } - toWidget(): any { - return { - config: { - position: this.position, - col: this.config.col, - row: this.config.row, - } - } - } + toWidget(): any { + return { + config: { + position: this.position, + col: this.config.col, + row: this.config.row, + }, + }; + } - toJson() { - return { - metricId: this.metricId, - widgetId: this.widgetId, - metricOf: this.metricOf, - metricValue: this.metricValueToArray(this.metricValue), - metricType: this.metricType, - metricFormat: this.metricFormat, - viewType: this.viewType, - name: this.name, - series: this.series.map((series: any) => series.toJson()), - thumbnail: this.thumbnail, - config: { - ...this.config, - col: (this.metricType === 'funnel' || this.metricOf === FilterKey.ERRORS || this.metricOf === FilterKey.SESSIONS || this.metricOf === FilterKey.SLOWEST_RESOURCES || this.metricOf === FilterKey.MISSING_RESOURCES || this.metricOf === FilterKey.PAGES_RESPONSE_TIME_DISTRIBUTION) ? 4 : (this.metricType === WEB_VITALS ? 1 : 2) - }, - } - } + toJson() { + return { + metricId: this.metricId, + widgetId: this.widgetId, + metricOf: this.metricOf, + metricValue: this.metricValueToArray(this.metricValue), + metricType: this.metricType, + metricFormat: this.metricFormat, + viewType: this.viewType, + name: this.name, + series: this.series.map((series: any) => series.toJson()), + thumbnail: this.thumbnail, + config: { + ...this.config, + col: + this.metricType === 'funnel' || + this.metricOf === FilterKey.ERRORS || + this.metricOf === FilterKey.SESSIONS || + this.metricOf === FilterKey.SLOWEST_RESOURCES || + this.metricOf === FilterKey.MISSING_RESOURCES || + this.metricOf === FilterKey.PAGES_RESPONSE_TIME_DISTRIBUTION + ? 4 + : this.metricType === WEB_VITALS + ? 1 + : 2, + }, + }; + } validate() { this.isValid = this.name.length > 0; @@ -189,9 +204,12 @@ export default class Widget { if (this.metricOf === FilterKey.ERRORS) { _data['errors'] = data.errors.map((s: any) => new Error().fromJSON(s)); } else if (this.metricType === INSIGHTS) { - _data['issues'] = data.filter((i: any) => i.change > 0 || i.change < 0).map( - (i: any) => new InishtIssue(i.category, i.name, i.ratio, i.value, i.change, i.isNew) - ); + _data['issues'] = data + .filter((i: any) => i.change > 0 || i.change < 0) + .map( + (i: any) => + new InishtIssue(i.category, i.name, i.ratio, i.oldValue, i.value, i.change, i.isNew) + ); } else { if (data.hasOwnProperty('chart')) { _data['chart'] = getChartFormatter(period)(data.chart); diff --git a/frontend/app/svg/icons/ic-errors.svg b/frontend/app/svg/icons/ic-errors.svg index 4f026cd64..64f4043d1 100644 --- a/frontend/app/svg/icons/ic-errors.svg +++ b/frontend/app/svg/icons/ic-errors.svg @@ -1 +1,4 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-exclamation-circle" viewBox="0 0 16 16"> + <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> + <path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/ic-network.svg b/frontend/app/svg/icons/ic-network.svg index 4f026cd64..626ffd4d9 100644 --- a/frontend/app/svg/icons/ic-network.svg +++ b/frontend/app/svg/icons/ic-network.svg @@ -1 +1,4 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-cloud-arrow-down" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M7.646 10.854a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 9.293V5.5a.5.5 0 0 0-1 0v3.793L6.354 8.146a.5.5 0 1 0-.708.708l2 2z"/> + <path d="M4.406 3.342A5.53 5.53 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773 16 11.569 14.502 13 12.687 13H3.781C1.708 13 0 11.366 0 9.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383zm.653.757c-.757.653-1.153 1.44-1.153 2.056v.448l-.445.049C2.064 6.805 1 7.952 1 9.318 1 10.785 2.23 12 3.781 12h8.906C13.98 12 15 10.988 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3a4.53 4.53 0 0 0-2.941 1.1z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/ic-rage.svg b/frontend/app/svg/icons/ic-rage.svg index 4f026cd64..898b083d8 100644 --- a/frontend/app/svg/icons/ic-rage.svg +++ b/frontend/app/svg/icons/ic-rage.svg @@ -1 +1,4 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-emoji-angry" viewBox="0 0 16 16"> + <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> + <path d="M4.285 12.433a.5.5 0 0 0 .683-.183A3.498 3.498 0 0 1 8 10.5c1.295 0 2.426.703 3.032 1.75a.5.5 0 0 0 .866-.5A4.498 4.498 0 0 0 8 9.5a4.5 4.5 0 0 0-3.898 2.25.5.5 0 0 0 .183.683zm6.991-8.38a.5.5 0 1 1 .448.894l-1.009.504c.176.27.285.64.285 1.049 0 .828-.448 1.5-1 1.5s-1-.672-1-1.5c0-.247.04-.48.11-.686a.502.502 0 0 1 .166-.761l2-1zm-6.552 0a.5.5 0 0 0-.448.894l1.009.504A1.94 1.94 0 0 0 5 6.5C5 7.328 5.448 8 6 8s1-.672 1-1.5c0-.247-.04-.48-.11-.686a.502.502 0 0 0-.166-.761l-2-1z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/ic-resources.svg b/frontend/app/svg/icons/ic-resources.svg index 4f026cd64..5cc666d44 100644 --- a/frontend/app/svg/icons/ic-resources.svg +++ b/frontend/app/svg/icons/ic-resources.svg @@ -1 +1,4 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216zm0-184c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm0 96c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32zm-28.7-140.7c6.2-6.2 6.2-16.4 0-22.6L190.6 192l28.7-28.7c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L168 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.3 16.4 6.3 22.6 0zm160-102.6c-6.2-6.2-16.4-6.2-22.6 0L328 169.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l28.7 28.7-28.7 28.7c-6.2 6.2-6.2 16.4 0 22.6 6.2 6.2 16.4 6.3 22.6 0l28.7-28.7 28.7 28.7c6.2 6.2 16.4 6.3 22.6 0 6.2-6.2 6.2-16.4 0-22.6L350.6 192l28.7-28.7c6.3-6.2 6.3-16.4 0-22.6z"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-file-earmark-text" viewBox="0 0 16 16"> + <path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/> + <path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/> +</svg> \ No newline at end of file From dcde8574ea5798fc7fa8dcd7c0cb4f321d038ced Mon Sep 17 00:00:00 2001 From: nick-delirium <nikita@openreplay.com> Date: Wed, 18 Jan 2023 11:17:55 +0100 Subject: [PATCH 58/65] change(ui): changed 403 logout action --- frontend/app/api_middleware.js | 4 ++-- frontend/app/duck/user.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/app/api_middleware.js b/frontend/app/api_middleware.js index 9c71c6d19..f98ad344b 100644 --- a/frontend/app/api_middleware.js +++ b/frontend/app/api_middleware.js @@ -1,6 +1,6 @@ import logger from 'App/logger'; import APIClient from './api_client'; -import { LOGIN, UPDATE_JWT } from './duck/user'; +import { FETCH_ACCOUNT, UPDATE_JWT } from './duck/user'; export default () => (next) => (action) => { const { types, call, ...rest } = action; @@ -14,7 +14,7 @@ export default () => (next) => (action) => { return call(client) .then(async (response) => { if (response.status === 403) { - next({ type: LOGIN.FAILURE }); + next({ type: FETCH_ACCOUNT.FAILURE }); } if (!response.ok) { const text = await response.text(); diff --git a/frontend/app/duck/user.js b/frontend/app/duck/user.js index fc627e214..ce0f66fec 100644 --- a/frontend/app/duck/user.js +++ b/frontend/app/duck/user.js @@ -8,7 +8,7 @@ export const LOGIN = new RequestTypes('user/LOGIN'); export const SIGNUP = new RequestTypes('user/SIGNUP'); export const RESET_PASSWORD = new RequestTypes('user/RESET_PASSWORD'); export const REQUEST_RESET_PASSWORD = new RequestTypes('user/REQUEST_RESET_PASSWORD'); -const FETCH_ACCOUNT = new RequestTypes('user/FETCH_ACCOUNT'); +export const FETCH_ACCOUNT = new RequestTypes('user/FETCH_ACCOUNT'); const FETCH_TENANTS = new RequestTypes('user/FETCH_TENANTS'); const UPDATE_ACCOUNT = new RequestTypes('user/UPDATE_ACCOUNT'); const RESEND_EMAIL_VERIFICATION = new RequestTypes('user/RESEND_EMAIL_VERIFICATION'); From a6e4779652c24dcef704bead2d42813ee7fd3d71 Mon Sep 17 00:00:00 2001 From: Alexander <zavorotynskiy@pm.me> Date: Wed, 18 Jan 2023 12:36:34 +0100 Subject: [PATCH 59/65] [HTTP] added custom project beacon size (#926) * feat(backend): added project beacon size select on each sessionStart request --- backend/internal/http/router/handlers-web.go | 7 ++- backend/internal/http/router/router.go | 51 +++++++++++++++++++- backend/pkg/db/postgres/project.go | 4 +- backend/pkg/db/types/project.go | 1 + 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/backend/internal/http/router/handlers-web.go b/backend/internal/http/router/handlers-web.go index 08c4c75c5..7afd184e5 100644 --- a/backend/internal/http/router/handlers-web.go +++ b/backend/internal/http/router/handlers-web.go @@ -152,12 +152,15 @@ func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) } } + // Save information about session beacon size + e.addBeaconSize(tokenData.ID, p.BeaconSize) + ResponseWithJSON(w, &StartSessionResponse{ Token: e.services.Tokenizer.Compose(*tokenData), UserUUID: userUUID, SessionID: strconv.FormatUint(tokenData.ID, 10), ProjectID: strconv.FormatUint(uint64(p.ProjectID), 10), - BeaconSizeLimit: e.cfg.BeaconSizeLimit, + BeaconSizeLimit: e.getBeaconSize(tokenData.ID), StartTimestamp: int64(flakeid.ExtractTimestamp(tokenData.ID)), Delay: tokenData.Delay, }) @@ -177,7 +180,7 @@ func (e *Router) pushMessagesHandlerWeb(w http.ResponseWriter, r *http.Request) return } - bodyBytes, err := e.readBody(w, r, e.cfg.BeaconSizeLimit) + bodyBytes, err := e.readBody(w, r, e.getBeaconSize(sessionData.ID)) if err != nil { log.Printf("error while reading request body: %s", err) ResponseWithError(w, http.StatusRequestEntityTooLarge, err) diff --git a/backend/internal/http/router/router.go b/backend/internal/http/router/router.go index 6d31b7396..964016dfd 100644 --- a/backend/internal/http/router/router.go +++ b/backend/internal/http/router/router.go @@ -12,9 +12,15 @@ import ( http2 "openreplay/backend/internal/http/services" "openreplay/backend/internal/http/util" "openreplay/backend/pkg/monitoring" + "sync" "time" ) +type BeaconSize struct { + size int64 + time time.Time +} + type Router struct { router *mux.Router cfg *http3.Config @@ -22,6 +28,8 @@ type Router struct { requestSize syncfloat64.Histogram requestDuration syncfloat64.Histogram totalRequests syncfloat64.Counter + mutex *sync.RWMutex + beaconSizeCache map[uint64]*BeaconSize // Cache for session's beaconSize } func NewRouter(cfg *http3.Config, services *http2.ServicesBuilder, metrics *monitoring.Metrics) (*Router, error) { @@ -34,14 +42,53 @@ func NewRouter(cfg *http3.Config, services *http2.ServicesBuilder, metrics *moni return nil, fmt.Errorf("metrics is empty") } e := &Router{ - cfg: cfg, - services: services, + cfg: cfg, + services: services, + mutex: &sync.RWMutex{}, + beaconSizeCache: make(map[uint64]*BeaconSize), } e.initMetrics(metrics) e.init() + go e.clearBeaconSizes() return e, nil } +func (e *Router) addBeaconSize(sessionID uint64, size int64) { + if size <= 0 { + return + } + e.mutex.Lock() + defer e.mutex.Unlock() + e.beaconSizeCache[sessionID] = &BeaconSize{ + size: size, + time: time.Now(), + } +} + +func (e *Router) getBeaconSize(sessionID uint64) int64 { + e.mutex.RLock() + defer e.mutex.RUnlock() + if beaconSize, ok := e.beaconSizeCache[sessionID]; ok { + beaconSize.time = time.Now() + return beaconSize.size + } + return e.cfg.BeaconSizeLimit +} + +func (e *Router) clearBeaconSizes() { + for { + time.Sleep(time.Minute * 2) + now := time.Now() + e.mutex.Lock() + for sid, bs := range e.beaconSizeCache { + if now.Sub(bs.time) > time.Minute*3 { + delete(e.beaconSizeCache, sid) + } + } + e.mutex.Unlock() + } +} + func (e *Router) init() { e.router = mux.NewRouter() diff --git a/backend/pkg/db/postgres/project.go b/backend/pkg/db/postgres/project.go index f38161885..3239aefb7 100644 --- a/backend/pkg/db/postgres/project.go +++ b/backend/pkg/db/postgres/project.go @@ -7,12 +7,12 @@ import ( func (conn *Conn) GetProjectByKey(projectKey string) (*Project, error) { p := &Project{ProjectKey: projectKey} if err := conn.c.QueryRow(` - SELECT max_session_duration, sample_rate, project_id + SELECT max_session_duration, sample_rate, project_id, beacon_size FROM projects WHERE project_key=$1 AND active = true `, projectKey, - ).Scan(&p.MaxSessionDuration, &p.SampleRate, &p.ProjectID); err != nil { + ).Scan(&p.MaxSessionDuration, &p.SampleRate, &p.ProjectID, &p.BeaconSize); err != nil { return nil, err } return p, nil diff --git a/backend/pkg/db/types/project.go b/backend/pkg/db/types/project.go index a5bc2e82c..85ac960ae 100644 --- a/backend/pkg/db/types/project.go +++ b/backend/pkg/db/types/project.go @@ -8,6 +8,7 @@ type Project struct { MaxSessionDuration int64 SampleRate byte SaveRequestPayloads bool + BeaconSize int64 Metadata1 *string Metadata2 *string Metadata3 *string From da7d2038efeaac310ca6a0085a997cb69e4ed5c5 Mon Sep 17 00:00:00 2001 From: Alexander <zavorotynskiy@pm.me> Date: Wed, 18 Jan 2023 13:38:10 +0100 Subject: [PATCH 60/65] [DB] added url field for click rage issues (#925) * feat(backend): added url field for click rage issues --- backend/pkg/handlers/web/clickRage.go | 9 + backend/pkg/messages/filters.go | 4 +- .../pkg/messages/legacy-message-transform.go | 10 + backend/pkg/messages/messages.go | 897 ++++--- backend/pkg/messages/read-message.go | 2325 +++++++++-------- backend/pkg/messages/session-iterator.go | 7 + ee/backend/pkg/db/clickhouse/connector.go | 3 +- ee/connectors/msgcodec/messages.py | 71 +- ee/connectors/msgcodec/msgcodec.py | 57 +- .../app/player/web/messages/tracker.gen.ts | 32 +- mobs/messages.rb | 66 +- tracker/tracker/src/common/messages.gen.ts | 36 +- tracker/tracker/src/main/app/messages.gen.ts | 56 +- .../src/webworker/MessageEncoder.gen.ts | 16 +- 14 files changed, 1858 insertions(+), 1731 deletions(-) diff --git a/backend/pkg/handlers/web/clickRage.go b/backend/pkg/handlers/web/clickRage.go index e22eb6454..6974ee1b0 100644 --- a/backend/pkg/handlers/web/clickRage.go +++ b/backend/pkg/handlers/web/clickRage.go @@ -22,6 +22,7 @@ type ClickRageDetector struct { firstInARawTimestamp uint64 firstInARawMessageId uint64 countsInARow int + url string } func (crd *ClickRageDetector) reset() { @@ -30,6 +31,7 @@ func (crd *ClickRageDetector) reset() { crd.firstInARawTimestamp = 0 crd.firstInARawMessageId = 0 crd.countsInARow = 0 + crd.url = "" } func (crd *ClickRageDetector) Build() Message { @@ -45,6 +47,7 @@ func (crd *ClickRageDetector) Build() Message { Payload: string(payload), Timestamp: crd.firstInARawTimestamp, MessageID: crd.firstInARawMessageId, + URL: crd.url, } return event } @@ -54,6 +57,9 @@ func (crd *ClickRageDetector) Build() Message { func (crd *ClickRageDetector) Handle(message Message, messageID uint64, timestamp uint64) Message { switch msg := message.(type) { case *MouseClick: + if crd.url == "" && msg.Url != "" { + crd.url = msg.Url + } // TODO: check if we it is ok to capture clickRage event without the connected ClickEvent in db. if msg.Label == "" { return crd.Build() @@ -69,6 +75,9 @@ func (crd *ClickRageDetector) Handle(message Message, messageID uint64, timestam crd.firstInARawTimestamp = timestamp crd.firstInARawMessageId = messageID crd.countsInARow = 1 + if crd.url == "" && msg.Url != "" { + crd.url = msg.Url + } return event } return nil diff --git a/backend/pkg/messages/filters.go b/backend/pkg/messages/filters.go index 6c96383c9..8041c596e 100644 --- a/backend/pkg/messages/filters.go +++ b/backend/pkg/messages/filters.go @@ -2,7 +2,7 @@ package messages func IsReplayerType(id int) bool { - return 80 != id && 81 != id && 82 != id && 1 != id && 3 != id && 17 != id && 23 != id && 24 != id && 25 != id && 26 != id && 27 != id && 28 != id && 29 != id && 30 != id && 31 != id && 32 != id && 33 != id && 35 != id && 42 != id && 52 != id && 56 != id && 62 != id && 63 != id && 64 != id && 66 != id && 78 != id && 126 != id && 127 != id && 107 != id && 91 != id && 92 != id && 94 != id && 95 != id && 97 != id && 98 != id && 99 != id && 101 != id && 104 != id && 110 != id && 111 != id + return 1 != id && 3 != id && 17 != id && 23 != id && 24 != id && 25 != id && 26 != id && 27 != id && 28 != id && 29 != id && 30 != id && 31 != id && 32 != id && 33 != id && 35 != id && 42 != id && 52 != id && 56 != id && 62 != id && 63 != id && 64 != id && 66 != id && 78 != id && 80 != id && 81 != id && 82 != id && 125 != id && 126 != id && 127 != id && 107 != id && 91 != id && 92 != id && 94 != id && 95 != id && 97 != id && 98 != id && 99 != id && 101 != id && 104 != id && 110 != id && 111 != id } func IsIOSType(id int) bool { @@ -11,4 +11,4 @@ func IsIOSType(id int) bool { func IsDOMType(id int) bool { return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 37 == id || 38 == id || 49 == id || 54 == id || 55 == id || 57 == id || 58 == id || 59 == id || 60 == id || 61 == id || 67 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id -} +} \ No newline at end of file diff --git a/backend/pkg/messages/legacy-message-transform.go b/backend/pkg/messages/legacy-message-transform.go index 72757baf4..6d3bb4d54 100644 --- a/backend/pkg/messages/legacy-message-transform.go +++ b/backend/pkg/messages/legacy-message-transform.go @@ -25,6 +25,16 @@ func transformDeprecated(msg Message) Message { Timestamp: m.Timestamp, Duration: m.Duration, } + case *IssueEventDeprecated: + return &IssueEvent{ + MessageID: m.MessageID, + Timestamp: m.Timestamp, + Type: m.Type, + ContextString: m.ContextString, + Context: m.Context, + Payload: m.Payload, + URL: "", + } } return msg } diff --git a/backend/pkg/messages/messages.go b/backend/pkg/messages/messages.go index 418d34867..318eafc25 100644 --- a/backend/pkg/messages/messages.go +++ b/backend/pkg/messages/messages.go @@ -2,180 +2,105 @@ package messages const ( - MsgBatchMeta = 80 - MsgBatchMetadata = 81 - MsgPartitionedMessage = 82 - MsgTimestamp = 0 - MsgSessionStart = 1 - MsgSessionEndDeprecated = 3 - MsgSetPageLocation = 4 - MsgSetViewportSize = 5 - MsgSetViewportScroll = 6 - MsgCreateDocument = 7 - MsgCreateElementNode = 8 - MsgCreateTextNode = 9 - MsgMoveNode = 10 - MsgRemoveNode = 11 - MsgSetNodeAttribute = 12 - MsgRemoveNodeAttribute = 13 - MsgSetNodeData = 14 - MsgSetCSSData = 15 - MsgSetNodeScroll = 16 - MsgSetInputTarget = 17 - MsgSetInputValue = 18 - MsgSetInputChecked = 19 - MsgMouseMove = 20 - MsgNetworkRequest = 21 - MsgConsoleLog = 22 - MsgPageLoadTiming = 23 - MsgPageRenderTiming = 24 - MsgJSExceptionDeprecated = 25 - MsgIntegrationEvent = 26 - MsgCustomEvent = 27 - MsgUserID = 28 - MsgUserAnonymousID = 29 - MsgMetadata = 30 - MsgPageEvent = 31 - MsgInputEvent = 32 - MsgClickEvent = 33 - MsgResourceEvent = 35 - MsgCSSInsertRule = 37 - MsgCSSDeleteRule = 38 - MsgFetch = 39 - MsgProfiler = 40 - MsgOTable = 41 - MsgStateAction = 42 - MsgRedux = 44 - MsgVuex = 45 - MsgMobX = 46 - MsgNgRx = 47 - MsgGraphQL = 48 - MsgPerformanceTrack = 49 - MsgDOMDrop = 52 - MsgResourceTiming = 53 - MsgConnectionInformation = 54 - MsgSetPageVisibility = 55 - MsgPerformanceTrackAggr = 56 - MsgLoadFontFace = 57 - MsgSetNodeFocus = 58 - MsgLongTask = 59 - MsgSetNodeAttributeURLBased = 60 - MsgSetCSSDataURLBased = 61 - MsgIssueEvent = 62 - MsgTechnicalInfo = 63 - MsgCustomIssue = 64 - MsgAssetCache = 66 - MsgCSSInsertRuleURLBased = 67 - MsgMouseClick = 69 - MsgCreateIFrameDocument = 70 - MsgAdoptedSSReplaceURLBased = 71 - MsgAdoptedSSReplace = 72 - MsgAdoptedSSInsertRuleURLBased = 73 - MsgAdoptedSSInsertRule = 74 - MsgAdoptedSSDeleteRule = 75 - MsgAdoptedSSAddOwner = 76 - MsgAdoptedSSRemoveOwner = 77 - MsgZustand = 79 - MsgJSException = 78 - MsgSessionEnd = 126 - MsgSessionSearch = 127 - MsgIOSBatchMeta = 107 - MsgIOSSessionStart = 90 - MsgIOSSessionEnd = 91 - MsgIOSMetadata = 92 - MsgIOSCustomEvent = 93 - MsgIOSUserID = 94 - MsgIOSUserAnonymousID = 95 - MsgIOSScreenChanges = 96 - MsgIOSCrash = 97 - MsgIOSScreenEnter = 98 - MsgIOSScreenLeave = 99 - MsgIOSClickEvent = 100 - MsgIOSInputEvent = 101 - MsgIOSPerformanceEvent = 102 - MsgIOSLog = 103 - MsgIOSInternalError = 104 - MsgIOSNetworkCall = 105 - MsgIOSPerformanceAggregated = 110 - MsgIOSIssueEvent = 111 + MsgTimestamp = 0 + MsgSessionStart = 1 + MsgSessionEndDeprecated = 3 + MsgSetPageLocation = 4 + MsgSetViewportSize = 5 + MsgSetViewportScroll = 6 + MsgCreateDocument = 7 + MsgCreateElementNode = 8 + MsgCreateTextNode = 9 + MsgMoveNode = 10 + MsgRemoveNode = 11 + MsgSetNodeAttribute = 12 + MsgRemoveNodeAttribute = 13 + MsgSetNodeData = 14 + MsgSetCSSData = 15 + MsgSetNodeScroll = 16 + MsgSetInputTarget = 17 + MsgSetInputValue = 18 + MsgSetInputChecked = 19 + MsgMouseMove = 20 + MsgNetworkRequest = 21 + MsgConsoleLog = 22 + MsgPageLoadTiming = 23 + MsgPageRenderTiming = 24 + MsgJSExceptionDeprecated = 25 + MsgIntegrationEvent = 26 + MsgCustomEvent = 27 + MsgUserID = 28 + MsgUserAnonymousID = 29 + MsgMetadata = 30 + MsgPageEvent = 31 + MsgInputEvent = 32 + MsgClickEvent = 33 + MsgResourceEvent = 35 + MsgCSSInsertRule = 37 + MsgCSSDeleteRule = 38 + MsgFetch = 39 + MsgProfiler = 40 + MsgOTable = 41 + MsgStateAction = 42 + MsgRedux = 44 + MsgVuex = 45 + MsgMobX = 46 + MsgNgRx = 47 + MsgGraphQL = 48 + MsgPerformanceTrack = 49 + MsgDOMDrop = 52 + MsgResourceTiming = 53 + MsgConnectionInformation = 54 + MsgSetPageVisibility = 55 + MsgPerformanceTrackAggr = 56 + MsgLoadFontFace = 57 + MsgSetNodeFocus = 58 + MsgLongTask = 59 + MsgSetNodeAttributeURLBased = 60 + MsgSetCSSDataURLBased = 61 + MsgIssueEventDeprecated = 62 + MsgTechnicalInfo = 63 + MsgCustomIssue = 64 + MsgAssetCache = 66 + MsgCSSInsertRuleURLBased = 67 + MsgMouseClick = 69 + MsgCreateIFrameDocument = 70 + MsgAdoptedSSReplaceURLBased = 71 + MsgAdoptedSSReplace = 72 + MsgAdoptedSSInsertRuleURLBased = 73 + MsgAdoptedSSInsertRule = 74 + MsgAdoptedSSDeleteRule = 75 + MsgAdoptedSSAddOwner = 76 + MsgAdoptedSSRemoveOwner = 77 + MsgZustand = 79 + MsgJSException = 78 + MsgBatchMeta = 80 + MsgBatchMetadata = 81 + MsgPartitionedMessage = 82 + MsgIssueEvent = 125 + MsgSessionEnd = 126 + MsgSessionSearch = 127 + MsgIOSBatchMeta = 107 + MsgIOSSessionStart = 90 + MsgIOSSessionEnd = 91 + MsgIOSMetadata = 92 + MsgIOSCustomEvent = 93 + MsgIOSUserID = 94 + MsgIOSUserAnonymousID = 95 + MsgIOSScreenChanges = 96 + MsgIOSCrash = 97 + MsgIOSScreenEnter = 98 + MsgIOSScreenLeave = 99 + MsgIOSClickEvent = 100 + MsgIOSInputEvent = 101 + MsgIOSPerformanceEvent = 102 + MsgIOSLog = 103 + MsgIOSInternalError = 104 + MsgIOSNetworkCall = 105 + MsgIOSPerformanceAggregated = 110 + MsgIOSIssueEvent = 111 ) -type BatchMeta struct { - message - PageNo uint64 - FirstIndex uint64 - Timestamp int64 -} - -func (msg *BatchMeta) Encode() []byte { - buf := make([]byte, 31) - buf[0] = 80 - p := 1 - p = WriteUint(msg.PageNo, buf, p) - p = WriteUint(msg.FirstIndex, buf, p) - p = WriteInt(msg.Timestamp, buf, p) - return buf[:p] -} - -func (msg *BatchMeta) Decode() Message { - return msg -} - -func (msg *BatchMeta) TypeID() int { - return 80 -} - -type BatchMetadata struct { - message - Version uint64 - PageNo uint64 - FirstIndex uint64 - Timestamp int64 - Location string -} - -func (msg *BatchMetadata) Encode() []byte { - buf := make([]byte, 51+len(msg.Location)) - buf[0] = 81 - p := 1 - p = WriteUint(msg.Version, buf, p) - p = WriteUint(msg.PageNo, buf, p) - p = WriteUint(msg.FirstIndex, buf, p) - p = WriteInt(msg.Timestamp, buf, p) - p = WriteString(msg.Location, buf, p) - return buf[:p] -} - -func (msg *BatchMetadata) Decode() Message { - return msg -} - -func (msg *BatchMetadata) TypeID() int { - return 81 -} - -type PartitionedMessage struct { - message - PartNo uint64 - PartTotal uint64 -} - -func (msg *PartitionedMessage) Encode() []byte { - buf := make([]byte, 21) - buf[0] = 82 - p := 1 - p = WriteUint(msg.PartNo, buf, p) - p = WriteUint(msg.PartTotal, buf, p) - return buf[:p] -} - -func (msg *PartitionedMessage) Decode() Message { - return msg -} - -func (msg *PartitionedMessage) TypeID() int { - return 82 -} type Timestamp struct { message @@ -200,22 +125,22 @@ func (msg *Timestamp) TypeID() int { type SessionStart struct { message - Timestamp uint64 - ProjectID uint64 - TrackerVersion string - RevID string - UserUUID string - UserAgent string - UserOS string - UserOSVersion string - UserBrowser string - UserBrowserVersion string - UserDevice string - UserDeviceType string + Timestamp uint64 + ProjectID uint64 + TrackerVersion string + RevID string + UserUUID string + UserAgent string + UserOS string + UserOSVersion string + UserBrowser string + UserBrowserVersion string + UserDevice string + UserDeviceType string UserDeviceMemorySize uint64 - UserDeviceHeapSize uint64 - UserCountry string - UserID string + UserDeviceHeapSize uint64 + UserCountry string + UserID string } func (msg *SessionStart) Encode() []byte { @@ -272,8 +197,8 @@ func (msg *SessionEndDeprecated) TypeID() int { type SetPageLocation struct { message - URL string - Referrer string + URL string + Referrer string NavigationStart uint64 } @@ -297,7 +222,7 @@ func (msg *SetPageLocation) TypeID() int { type SetViewportSize struct { message - Width uint64 + Width uint64 Height uint64 } @@ -343,6 +268,7 @@ func (msg *SetViewportScroll) TypeID() int { type CreateDocument struct { message + } func (msg *CreateDocument) Encode() []byte { @@ -363,11 +289,11 @@ func (msg *CreateDocument) TypeID() int { type CreateElementNode struct { message - ID uint64 + ID uint64 ParentID uint64 - index uint64 - Tag string - SVG bool + index uint64 + Tag string + SVG bool } func (msg *CreateElementNode) Encode() []byte { @@ -392,9 +318,9 @@ func (msg *CreateElementNode) TypeID() int { type CreateTextNode struct { message - ID uint64 + ID uint64 ParentID uint64 - Index uint64 + Index uint64 } func (msg *CreateTextNode) Encode() []byte { @@ -417,9 +343,9 @@ func (msg *CreateTextNode) TypeID() int { type MoveNode struct { message - ID uint64 + ID uint64 ParentID uint64 - Index uint64 + Index uint64 } func (msg *MoveNode) Encode() []byte { @@ -463,8 +389,8 @@ func (msg *RemoveNode) TypeID() int { type SetNodeAttribute struct { message - ID uint64 - Name string + ID uint64 + Name string Value string } @@ -488,7 +414,7 @@ func (msg *SetNodeAttribute) TypeID() int { type RemoveNodeAttribute struct { message - ID uint64 + ID uint64 Name string } @@ -511,7 +437,7 @@ func (msg *RemoveNodeAttribute) TypeID() int { type SetNodeData struct { message - ID uint64 + ID uint64 Data string } @@ -534,7 +460,7 @@ func (msg *SetNodeData) TypeID() int { type SetCSSData struct { message - ID uint64 + ID uint64 Data string } @@ -558,8 +484,8 @@ func (msg *SetCSSData) TypeID() int { type SetNodeScroll struct { message ID uint64 - X int64 - Y int64 + X int64 + Y int64 } func (msg *SetNodeScroll) Encode() []byte { @@ -582,7 +508,7 @@ func (msg *SetNodeScroll) TypeID() int { type SetInputTarget struct { message - ID uint64 + ID uint64 Label string } @@ -605,9 +531,9 @@ func (msg *SetInputTarget) TypeID() int { type SetInputValue struct { message - ID uint64 + ID uint64 Value string - Mask int64 + Mask int64 } func (msg *SetInputValue) Encode() []byte { @@ -630,7 +556,7 @@ func (msg *SetInputValue) TypeID() int { type SetInputChecked struct { message - ID uint64 + ID uint64 Checked bool } @@ -676,14 +602,14 @@ func (msg *MouseMove) TypeID() int { type NetworkRequest struct { message - Type string - Method string - URL string - Request string - Response string - Status uint64 + Type string + Method string + URL string + Request string + Response string + Status uint64 Timestamp uint64 - Duration uint64 + Duration uint64 } func (msg *NetworkRequest) Encode() []byte { @@ -734,15 +660,15 @@ func (msg *ConsoleLog) TypeID() int { type PageLoadTiming struct { message - RequestStart uint64 - ResponseStart uint64 - ResponseEnd uint64 + RequestStart uint64 + ResponseStart uint64 + ResponseEnd uint64 DomContentLoadedEventStart uint64 - DomContentLoadedEventEnd uint64 - LoadEventStart uint64 - LoadEventEnd uint64 - FirstPaint uint64 - FirstContentfulPaint uint64 + DomContentLoadedEventEnd uint64 + LoadEventStart uint64 + LoadEventEnd uint64 + FirstPaint uint64 + FirstContentfulPaint uint64 } func (msg *PageLoadTiming) Encode() []byte { @@ -771,8 +697,8 @@ func (msg *PageLoadTiming) TypeID() int { type PageRenderTiming struct { message - SpeedIndex uint64 - VisuallyComplete uint64 + SpeedIndex uint64 + VisuallyComplete uint64 TimeToInteractive uint64 } @@ -796,7 +722,7 @@ func (msg *PageRenderTiming) TypeID() int { type JSExceptionDeprecated struct { message - Name string + Name string Message string Payload string } @@ -822,10 +748,10 @@ func (msg *JSExceptionDeprecated) TypeID() int { type IntegrationEvent struct { message Timestamp uint64 - Source string - Name string - Message string - Payload string + Source string + Name string + Message string + Payload string } func (msg *IntegrationEvent) Encode() []byte { @@ -850,7 +776,7 @@ func (msg *IntegrationEvent) TypeID() int { type CustomEvent struct { message - Name string + Name string Payload string } @@ -915,7 +841,7 @@ func (msg *UserAnonymousID) TypeID() int { type Metadata struct { message - Key string + Key string Value string } @@ -938,23 +864,23 @@ func (msg *Metadata) TypeID() int { type PageEvent struct { message - MessageID uint64 - Timestamp uint64 - URL string - Referrer string - Loaded bool - RequestStart uint64 - ResponseStart uint64 - ResponseEnd uint64 + MessageID uint64 + Timestamp uint64 + URL string + Referrer string + Loaded bool + RequestStart uint64 + ResponseStart uint64 + ResponseEnd uint64 DomContentLoadedEventStart uint64 - DomContentLoadedEventEnd uint64 - LoadEventStart uint64 - LoadEventEnd uint64 - FirstPaint uint64 - FirstContentfulPaint uint64 - SpeedIndex uint64 - VisuallyComplete uint64 - TimeToInteractive uint64 + DomContentLoadedEventEnd uint64 + LoadEventStart uint64 + LoadEventEnd uint64 + FirstPaint uint64 + FirstContentfulPaint uint64 + SpeedIndex uint64 + VisuallyComplete uint64 + TimeToInteractive uint64 } func (msg *PageEvent) Encode() []byte { @@ -991,11 +917,11 @@ func (msg *PageEvent) TypeID() int { type InputEvent struct { message - MessageID uint64 - Timestamp uint64 - Value string + MessageID uint64 + Timestamp uint64 + Value string ValueMasked bool - Label string + Label string } func (msg *InputEvent) Encode() []byte { @@ -1020,11 +946,11 @@ func (msg *InputEvent) TypeID() int { type ClickEvent struct { message - MessageID uint64 - Timestamp uint64 + MessageID uint64 + Timestamp uint64 HesitationTime uint64 - Label string - Selector string + Label string + Selector string } func (msg *ClickEvent) Encode() []byte { @@ -1049,18 +975,18 @@ func (msg *ClickEvent) TypeID() int { type ResourceEvent struct { message - MessageID uint64 - Timestamp uint64 - Duration uint64 - TTFB uint64 - HeaderSize uint64 + MessageID uint64 + Timestamp uint64 + Duration uint64 + TTFB uint64 + HeaderSize uint64 EncodedBodySize uint64 DecodedBodySize uint64 - URL string - Type string - Success bool - Method string - Status uint64 + URL string + Type string + Success bool + Method string + Status uint64 } func (msg *ResourceEvent) Encode() []byte { @@ -1092,8 +1018,8 @@ func (msg *ResourceEvent) TypeID() int { type CSSInsertRule struct { message - ID uint64 - Rule string + ID uint64 + Rule string Index uint64 } @@ -1117,7 +1043,7 @@ func (msg *CSSInsertRule) TypeID() int { type CSSDeleteRule struct { message - ID uint64 + ID uint64 Index uint64 } @@ -1140,13 +1066,13 @@ func (msg *CSSDeleteRule) TypeID() int { type Fetch struct { message - Method string - URL string - Request string - Response string - Status uint64 + Method string + URL string + Request string + Response string + Status uint64 Timestamp uint64 - Duration uint64 + Duration uint64 } func (msg *Fetch) Encode() []byte { @@ -1173,10 +1099,10 @@ func (msg *Fetch) TypeID() int { type Profiler struct { message - Name string + Name string Duration uint64 - Args string - Result string + Args string + Result string } func (msg *Profiler) Encode() []byte { @@ -1200,7 +1126,7 @@ func (msg *Profiler) TypeID() int { type OTable struct { message - Key string + Key string Value string } @@ -1244,8 +1170,8 @@ func (msg *StateAction) TypeID() int { type Redux struct { message - Action string - State string + Action string + State string Duration uint64 } @@ -1270,7 +1196,7 @@ func (msg *Redux) TypeID() int { type Vuex struct { message Mutation string - State string + State string } func (msg *Vuex) Encode() []byte { @@ -1292,7 +1218,7 @@ func (msg *Vuex) TypeID() int { type MobX struct { message - Type string + Type string Payload string } @@ -1315,8 +1241,8 @@ func (msg *MobX) TypeID() int { type NgRx struct { message - Action string - State string + Action string + State string Duration uint64 } @@ -1342,8 +1268,8 @@ type GraphQL struct { message OperationKind string OperationName string - Variables string - Response string + Variables string + Response string } func (msg *GraphQL) Encode() []byte { @@ -1367,10 +1293,10 @@ func (msg *GraphQL) TypeID() int { type PerformanceTrack struct { message - Frames int64 - Ticks int64 + Frames int64 + Ticks int64 TotalJSHeapSize uint64 - UsedJSHeapSize uint64 + UsedJSHeapSize uint64 } func (msg *PerformanceTrack) Encode() []byte { @@ -1415,14 +1341,14 @@ func (msg *DOMDrop) TypeID() int { type ResourceTiming struct { message - Timestamp uint64 - Duration uint64 - TTFB uint64 - HeaderSize uint64 + Timestamp uint64 + Duration uint64 + TTFB uint64 + HeaderSize uint64 EncodedBodySize uint64 DecodedBodySize uint64 - URL string - Initiator string + URL string + Initiator string } func (msg *ResourceTiming) Encode() []byte { @@ -1451,7 +1377,7 @@ func (msg *ResourceTiming) TypeID() int { type ConnectionInformation struct { message Downlink uint64 - Type string + Type string } func (msg *ConnectionInformation) Encode() []byte { @@ -1494,20 +1420,20 @@ func (msg *SetPageVisibility) TypeID() int { type PerformanceTrackAggr struct { message - TimestampStart uint64 - TimestampEnd uint64 - MinFPS uint64 - AvgFPS uint64 - MaxFPS uint64 - MinCPU uint64 - AvgCPU uint64 - MaxCPU uint64 + TimestampStart uint64 + TimestampEnd uint64 + MinFPS uint64 + AvgFPS uint64 + MaxFPS uint64 + MinCPU uint64 + AvgCPU uint64 + MaxCPU uint64 MinTotalJSHeapSize uint64 AvgTotalJSHeapSize uint64 MaxTotalJSHeapSize uint64 - MinUsedJSHeapSize uint64 - AvgUsedJSHeapSize uint64 - MaxUsedJSHeapSize uint64 + MinUsedJSHeapSize uint64 + AvgUsedJSHeapSize uint64 + MaxUsedJSHeapSize uint64 } func (msg *PerformanceTrackAggr) Encode() []byte { @@ -1541,9 +1467,9 @@ func (msg *PerformanceTrackAggr) TypeID() int { type LoadFontFace struct { message - ParentID uint64 - Family string - Source string + ParentID uint64 + Family string + Source string Descriptors string } @@ -1589,12 +1515,12 @@ func (msg *SetNodeFocus) TypeID() int { type LongTask struct { message - Timestamp uint64 - Duration uint64 - Context uint64 + Timestamp uint64 + Duration uint64 + Context uint64 ContainerType uint64 - ContainerSrc string - ContainerId string + ContainerSrc string + ContainerId string ContainerName string } @@ -1622,9 +1548,9 @@ func (msg *LongTask) TypeID() int { type SetNodeAttributeURLBased struct { message - ID uint64 - Name string - Value string + ID uint64 + Name string + Value string BaseURL string } @@ -1649,8 +1575,8 @@ func (msg *SetNodeAttributeURLBased) TypeID() int { type SetCSSDataURLBased struct { message - ID uint64 - Data string + ID uint64 + Data string BaseURL string } @@ -1672,17 +1598,17 @@ func (msg *SetCSSDataURLBased) TypeID() int { return 61 } -type IssueEvent struct { +type IssueEventDeprecated struct { message - MessageID uint64 - Timestamp uint64 - Type string + MessageID uint64 + Timestamp uint64 + Type string ContextString string - Context string - Payload string + Context string + Payload string } -func (msg *IssueEvent) Encode() []byte { +func (msg *IssueEventDeprecated) Encode() []byte { buf := make([]byte, 61+len(msg.Type)+len(msg.ContextString)+len(msg.Context)+len(msg.Payload)) buf[0] = 62 p := 1 @@ -1695,17 +1621,17 @@ func (msg *IssueEvent) Encode() []byte { return buf[:p] } -func (msg *IssueEvent) Decode() Message { +func (msg *IssueEventDeprecated) Decode() Message { return msg } -func (msg *IssueEvent) TypeID() int { +func (msg *IssueEventDeprecated) TypeID() int { return 62 } type TechnicalInfo struct { message - Type string + Type string Value string } @@ -1728,7 +1654,7 @@ func (msg *TechnicalInfo) TypeID() int { type CustomIssue struct { message - Name string + Name string Payload string } @@ -1772,9 +1698,9 @@ func (msg *AssetCache) TypeID() int { type CSSInsertRuleURLBased struct { message - ID uint64 - Rule string - Index uint64 + ID uint64 + Rule string + Index uint64 BaseURL string } @@ -1799,10 +1725,10 @@ func (msg *CSSInsertRuleURLBased) TypeID() int { type MouseClick struct { message - ID uint64 + ID uint64 HesitationTime uint64 - Label string - Selector string + Label string + Selector string } func (msg *MouseClick) Encode() []byte { @@ -1827,7 +1753,7 @@ func (msg *MouseClick) TypeID() int { type CreateIFrameDocument struct { message FrameID uint64 - ID uint64 + ID uint64 } func (msg *CreateIFrameDocument) Encode() []byte { @@ -1850,7 +1776,7 @@ func (msg *CreateIFrameDocument) TypeID() int { type AdoptedSSReplaceURLBased struct { message SheetID uint64 - Text string + Text string BaseURL string } @@ -1875,7 +1801,7 @@ func (msg *AdoptedSSReplaceURLBased) TypeID() int { type AdoptedSSReplace struct { message SheetID uint64 - Text string + Text string } func (msg *AdoptedSSReplace) Encode() []byte { @@ -1898,8 +1824,8 @@ func (msg *AdoptedSSReplace) TypeID() int { type AdoptedSSInsertRuleURLBased struct { message SheetID uint64 - Rule string - Index uint64 + Rule string + Index uint64 BaseURL string } @@ -1925,8 +1851,8 @@ func (msg *AdoptedSSInsertRuleURLBased) TypeID() int { type AdoptedSSInsertRule struct { message SheetID uint64 - Rule string - Index uint64 + Rule string + Index uint64 } func (msg *AdoptedSSInsertRule) Encode() []byte { @@ -1950,7 +1876,7 @@ func (msg *AdoptedSSInsertRule) TypeID() int { type AdoptedSSDeleteRule struct { message SheetID uint64 - Index uint64 + Index uint64 } func (msg *AdoptedSSDeleteRule) Encode() []byte { @@ -1973,7 +1899,7 @@ func (msg *AdoptedSSDeleteRule) TypeID() int { type AdoptedSSAddOwner struct { message SheetID uint64 - ID uint64 + ID uint64 } func (msg *AdoptedSSAddOwner) Encode() []byte { @@ -1996,7 +1922,7 @@ func (msg *AdoptedSSAddOwner) TypeID() int { type AdoptedSSRemoveOwner struct { message SheetID uint64 - ID uint64 + ID uint64 } func (msg *AdoptedSSRemoveOwner) Encode() []byte { @@ -2019,7 +1945,7 @@ func (msg *AdoptedSSRemoveOwner) TypeID() int { type Zustand struct { message Mutation string - State string + State string } func (msg *Zustand) Encode() []byte { @@ -2041,9 +1967,9 @@ func (msg *Zustand) TypeID() int { type JSException struct { message - Name string - Message string - Payload string + Name string + Message string + Payload string Metadata string } @@ -2066,9 +1992,119 @@ func (msg *JSException) TypeID() int { return 78 } +type BatchMeta struct { + message + PageNo uint64 + FirstIndex uint64 + Timestamp int64 +} + +func (msg *BatchMeta) Encode() []byte { + buf := make([]byte, 31) + buf[0] = 80 + p := 1 + p = WriteUint(msg.PageNo, buf, p) + p = WriteUint(msg.FirstIndex, buf, p) + p = WriteInt(msg.Timestamp, buf, p) + return buf[:p] +} + +func (msg *BatchMeta) Decode() Message { + return msg +} + +func (msg *BatchMeta) TypeID() int { + return 80 +} + +type BatchMetadata struct { + message + Version uint64 + PageNo uint64 + FirstIndex uint64 + Timestamp int64 + Location string +} + +func (msg *BatchMetadata) Encode() []byte { + buf := make([]byte, 51+len(msg.Location)) + buf[0] = 81 + p := 1 + p = WriteUint(msg.Version, buf, p) + p = WriteUint(msg.PageNo, buf, p) + p = WriteUint(msg.FirstIndex, buf, p) + p = WriteInt(msg.Timestamp, buf, p) + p = WriteString(msg.Location, buf, p) + return buf[:p] +} + +func (msg *BatchMetadata) Decode() Message { + return msg +} + +func (msg *BatchMetadata) TypeID() int { + return 81 +} + +type PartitionedMessage struct { + message + PartNo uint64 + PartTotal uint64 +} + +func (msg *PartitionedMessage) Encode() []byte { + buf := make([]byte, 21) + buf[0] = 82 + p := 1 + p = WriteUint(msg.PartNo, buf, p) + p = WriteUint(msg.PartTotal, buf, p) + return buf[:p] +} + +func (msg *PartitionedMessage) Decode() Message { + return msg +} + +func (msg *PartitionedMessage) TypeID() int { + return 82 +} + +type IssueEvent struct { + message + MessageID uint64 + Timestamp uint64 + Type string + ContextString string + Context string + Payload string + URL string +} + +func (msg *IssueEvent) Encode() []byte { + buf := make([]byte, 71+len(msg.Type)+len(msg.ContextString)+len(msg.Context)+len(msg.Payload)+len(msg.URL)) + buf[0] = 125 + p := 1 + p = WriteUint(msg.MessageID, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteString(msg.Type, buf, p) + p = WriteString(msg.ContextString, buf, p) + p = WriteString(msg.Context, buf, p) + p = WriteString(msg.Payload, buf, p) + p = WriteString(msg.URL, buf, p) + return buf[:p] +} + +func (msg *IssueEvent) Decode() Message { + return msg +} + +func (msg *IssueEvent) TypeID() int { + return 125 +} + type SessionEnd struct { message - Timestamp uint64 + Timestamp uint64 EncryptionKey string } @@ -2114,8 +2150,8 @@ func (msg *SessionSearch) TypeID() int { type IOSBatchMeta struct { message - Timestamp uint64 - Length uint64 + Timestamp uint64 + Length uint64 FirstIndex uint64 } @@ -2139,16 +2175,16 @@ func (msg *IOSBatchMeta) TypeID() int { type IOSSessionStart struct { message - Timestamp uint64 - ProjectID uint64 + Timestamp uint64 + ProjectID uint64 TrackerVersion string - RevID string - UserUUID string - UserOS string - UserOSVersion string - UserDevice string + RevID string + UserUUID string + UserOS string + UserOSVersion string + UserDevice string UserDeviceType string - UserCountry string + UserCountry string } func (msg *IOSSessionStart) Encode() []byte { @@ -2200,9 +2236,9 @@ func (msg *IOSSessionEnd) TypeID() int { type IOSMetadata struct { message Timestamp uint64 - Length uint64 - Key string - Value string + Length uint64 + Key string + Value string } func (msg *IOSMetadata) Encode() []byte { @@ -2227,9 +2263,9 @@ func (msg *IOSMetadata) TypeID() int { type IOSCustomEvent struct { message Timestamp uint64 - Length uint64 - Name string - Payload string + Length uint64 + Name string + Payload string } func (msg *IOSCustomEvent) Encode() []byte { @@ -2254,8 +2290,8 @@ func (msg *IOSCustomEvent) TypeID() int { type IOSUserID struct { message Timestamp uint64 - Length uint64 - Value string + Length uint64 + Value string } func (msg *IOSUserID) Encode() []byte { @@ -2279,8 +2315,8 @@ func (msg *IOSUserID) TypeID() int { type IOSUserAnonymousID struct { message Timestamp uint64 - Length uint64 - Value string + Length uint64 + Value string } func (msg *IOSUserAnonymousID) Encode() []byte { @@ -2304,11 +2340,11 @@ func (msg *IOSUserAnonymousID) TypeID() int { type IOSScreenChanges struct { message Timestamp uint64 - Length uint64 - X uint64 - Y uint64 - Width uint64 - Height uint64 + Length uint64 + X uint64 + Y uint64 + Width uint64 + Height uint64 } func (msg *IOSScreenChanges) Encode() []byte { @@ -2334,10 +2370,10 @@ func (msg *IOSScreenChanges) TypeID() int { type IOSCrash struct { message - Timestamp uint64 - Length uint64 - Name string - Reason string + Timestamp uint64 + Length uint64 + Name string + Reason string Stacktrace string } @@ -2364,9 +2400,9 @@ func (msg *IOSCrash) TypeID() int { type IOSScreenEnter struct { message Timestamp uint64 - Length uint64 - Title string - ViewName string + Length uint64 + Title string + ViewName string } func (msg *IOSScreenEnter) Encode() []byte { @@ -2391,9 +2427,9 @@ func (msg *IOSScreenEnter) TypeID() int { type IOSScreenLeave struct { message Timestamp uint64 - Length uint64 - Title string - ViewName string + Length uint64 + Title string + ViewName string } func (msg *IOSScreenLeave) Encode() []byte { @@ -2418,10 +2454,10 @@ func (msg *IOSScreenLeave) TypeID() int { type IOSClickEvent struct { message Timestamp uint64 - Length uint64 - Label string - X uint64 - Y uint64 + Length uint64 + Label string + X uint64 + Y uint64 } func (msg *IOSClickEvent) Encode() []byte { @@ -2446,11 +2482,11 @@ func (msg *IOSClickEvent) TypeID() int { type IOSInputEvent struct { message - Timestamp uint64 - Length uint64 - Value string + Timestamp uint64 + Length uint64 + Value string ValueMasked bool - Label string + Label string } func (msg *IOSInputEvent) Encode() []byte { @@ -2476,9 +2512,9 @@ func (msg *IOSInputEvent) TypeID() int { type IOSPerformanceEvent struct { message Timestamp uint64 - Length uint64 - Name string - Value uint64 + Length uint64 + Name string + Value uint64 } func (msg *IOSPerformanceEvent) Encode() []byte { @@ -2503,9 +2539,9 @@ func (msg *IOSPerformanceEvent) TypeID() int { type IOSLog struct { message Timestamp uint64 - Length uint64 - Severity string - Content string + Length uint64 + Severity string + Content string } func (msg *IOSLog) Encode() []byte { @@ -2530,8 +2566,8 @@ func (msg *IOSLog) TypeID() int { type IOSInternalError struct { message Timestamp uint64 - Length uint64 - Content string + Length uint64 + Content string } func (msg *IOSInternalError) Encode() []byte { @@ -2555,14 +2591,14 @@ func (msg *IOSInternalError) TypeID() int { type IOSNetworkCall struct { message Timestamp uint64 - Length uint64 - Duration uint64 - Headers string - Body string - URL string - Success bool - Method string - Status uint64 + Length uint64 + Duration uint64 + Headers string + Body string + URL string + Success bool + Method string + Status uint64 } func (msg *IOSNetworkCall) Encode() []byte { @@ -2592,19 +2628,19 @@ func (msg *IOSNetworkCall) TypeID() int { type IOSPerformanceAggregated struct { message TimestampStart uint64 - TimestampEnd uint64 - MinFPS uint64 - AvgFPS uint64 - MaxFPS uint64 - MinCPU uint64 - AvgCPU uint64 - MaxCPU uint64 - MinMemory uint64 - AvgMemory uint64 - MaxMemory uint64 - MinBattery uint64 - AvgBattery uint64 - MaxBattery uint64 + TimestampEnd uint64 + MinFPS uint64 + AvgFPS uint64 + MaxFPS uint64 + MinCPU uint64 + AvgCPU uint64 + MaxCPU uint64 + MinMemory uint64 + AvgMemory uint64 + MaxMemory uint64 + MinBattery uint64 + AvgBattery uint64 + MaxBattery uint64 } func (msg *IOSPerformanceAggregated) Encode() []byte { @@ -2638,11 +2674,11 @@ func (msg *IOSPerformanceAggregated) TypeID() int { type IOSIssueEvent struct { message - Timestamp uint64 - Type string + Timestamp uint64 + Type string ContextString string - Context string - Payload string + Context string + Payload string } func (msg *IOSIssueEvent) Encode() []byte { @@ -2664,3 +2700,4 @@ func (msg *IOSIssueEvent) Decode() Message { func (msg *IOSIssueEvent) TypeID() int { return 111 } + diff --git a/backend/pkg/messages/read-message.go b/backend/pkg/messages/read-message.go index 29dddb02f..c65149888 100644 --- a/backend/pkg/messages/read-message.go +++ b/backend/pkg/messages/read-message.go @@ -5,1701 +5,1722 @@ import ( "fmt" ) -func DecodeBatchMeta(reader BytesReader) (Message, error) { - var err error = nil - msg := &BatchMeta{} - if msg.PageNo, err = reader.ReadUint(); err != nil { - return nil, err - } - if msg.FirstIndex, err = reader.ReadUint(); err != nil { - return nil, err - } - if msg.Timestamp, err = reader.ReadInt(); err != nil { - return nil, err - } - return msg, err -} - -func DecodeBatchMetadata(reader BytesReader) (Message, error) { - var err error = nil - msg := &BatchMetadata{} - if msg.Version, err = reader.ReadUint(); err != nil { - return nil, err - } - if msg.PageNo, err = reader.ReadUint(); err != nil { - return nil, err - } - if msg.FirstIndex, err = reader.ReadUint(); err != nil { - return nil, err - } - if msg.Timestamp, err = reader.ReadInt(); err != nil { - return nil, err - } - if msg.Location, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err -} - -func DecodePartitionedMessage(reader BytesReader) (Message, error) { - var err error = nil - msg := &PartitionedMessage{} - if msg.PartNo, err = reader.ReadUint(); err != nil { - return nil, err - } - if msg.PartTotal, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err -} - func DecodeTimestamp(reader BytesReader) (Message, error) { - var err error = nil - msg := &Timestamp{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &Timestamp{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } + return msg, err } func DecodeSessionStart(reader BytesReader) (Message, error) { - var err error = nil - msg := &SessionStart{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SessionStart{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.ProjectID, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.TrackerVersion, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.RevID, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserUUID, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserAgent, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserOS, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserOSVersion, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserBrowser, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserBrowserVersion, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserDevice, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserDeviceType, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserDeviceMemorySize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.UserDeviceHeapSize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.UserCountry, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserID, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSessionEndDeprecated(reader BytesReader) (Message, error) { - var err error = nil - msg := &SessionEndDeprecated{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SessionEndDeprecated{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } + return msg, err } func DecodeSetPageLocation(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetPageLocation{} - if msg.URL, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &SetPageLocation{} + if msg.URL, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Referrer, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.NavigationStart, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetViewportSize(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetViewportSize{} - if msg.Width, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SetViewportSize{} + if msg.Width, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Height, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetViewportScroll(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetViewportScroll{} - if msg.X, err = reader.ReadInt(); err != nil { - return nil, err - } + var err error = nil + msg := &SetViewportScroll{} + if msg.X, err = reader.ReadInt(); err != nil { + return nil, err + } if msg.Y, err = reader.ReadInt(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeCreateDocument(reader BytesReader) (Message, error) { - var err error = nil - msg := &CreateDocument{} - - return msg, err + var err error = nil + msg := &CreateDocument{} + + return msg, err } func DecodeCreateElementNode(reader BytesReader) (Message, error) { - var err error = nil - msg := &CreateElementNode{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &CreateElementNode{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.ParentID, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.index, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Tag, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.SVG, err = reader.ReadBoolean(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeCreateTextNode(reader BytesReader) (Message, error) { - var err error = nil - msg := &CreateTextNode{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &CreateTextNode{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.ParentID, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Index, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeMoveNode(reader BytesReader) (Message, error) { - var err error = nil - msg := &MoveNode{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &MoveNode{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.ParentID, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Index, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeRemoveNode(reader BytesReader) (Message, error) { - var err error = nil - msg := &RemoveNode{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &RemoveNode{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } + return msg, err } func DecodeSetNodeAttribute(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetNodeAttribute{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SetNodeAttribute{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Value, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeRemoveNodeAttribute(reader BytesReader) (Message, error) { - var err error = nil - msg := &RemoveNodeAttribute{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &RemoveNodeAttribute{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetNodeData(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetNodeData{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SetNodeData{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Data, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetCSSData(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetCSSData{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SetCSSData{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Data, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetNodeScroll(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetNodeScroll{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SetNodeScroll{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.X, err = reader.ReadInt(); err != nil { - return nil, err - } + return nil, err + } if msg.Y, err = reader.ReadInt(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetInputTarget(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetInputTarget{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SetInputTarget{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Label, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetInputValue(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetInputValue{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SetInputValue{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Value, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Mask, err = reader.ReadInt(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetInputChecked(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetInputChecked{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SetInputChecked{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Checked, err = reader.ReadBoolean(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeMouseMove(reader BytesReader) (Message, error) { - var err error = nil - msg := &MouseMove{} - if msg.X, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &MouseMove{} + if msg.X, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Y, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeNetworkRequest(reader BytesReader) (Message, error) { - var err error = nil - msg := &NetworkRequest{} - if msg.Type, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &NetworkRequest{} + if msg.Type, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Method, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.URL, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Request, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Response, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Status, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Duration, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeConsoleLog(reader BytesReader) (Message, error) { - var err error = nil - msg := &ConsoleLog{} - if msg.Level, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &ConsoleLog{} + if msg.Level, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Value, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodePageLoadTiming(reader BytesReader) (Message, error) { - var err error = nil - msg := &PageLoadTiming{} - if msg.RequestStart, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &PageLoadTiming{} + if msg.RequestStart, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.ResponseStart, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.ResponseEnd, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.DomContentLoadedEventStart, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.DomContentLoadedEventEnd, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.LoadEventStart, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.LoadEventEnd, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.FirstPaint, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.FirstContentfulPaint, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodePageRenderTiming(reader BytesReader) (Message, error) { - var err error = nil - msg := &PageRenderTiming{} - if msg.SpeedIndex, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &PageRenderTiming{} + if msg.SpeedIndex, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.VisuallyComplete, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.TimeToInteractive, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeJSExceptionDeprecated(reader BytesReader) (Message, error) { - var err error = nil - msg := &JSExceptionDeprecated{} - if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &JSExceptionDeprecated{} + if msg.Name, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Message, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Payload, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIntegrationEvent(reader BytesReader) (Message, error) { - var err error = nil - msg := &IntegrationEvent{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IntegrationEvent{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Source, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Message, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Payload, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeCustomEvent(reader BytesReader) (Message, error) { - var err error = nil - msg := &CustomEvent{} - if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &CustomEvent{} + if msg.Name, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Payload, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeUserID(reader BytesReader) (Message, error) { - var err error = nil - msg := &UserID{} - if msg.ID, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &UserID{} + if msg.ID, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err } func DecodeUserAnonymousID(reader BytesReader) (Message, error) { - var err error = nil - msg := &UserAnonymousID{} - if msg.ID, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &UserAnonymousID{} + if msg.ID, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err } func DecodeMetadata(reader BytesReader) (Message, error) { - var err error = nil - msg := &Metadata{} - if msg.Key, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &Metadata{} + if msg.Key, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Value, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodePageEvent(reader BytesReader) (Message, error) { - var err error = nil - msg := &PageEvent{} - if msg.MessageID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &PageEvent{} + if msg.MessageID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.URL, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Referrer, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Loaded, err = reader.ReadBoolean(); err != nil { - return nil, err - } + return nil, err + } if msg.RequestStart, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.ResponseStart, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.ResponseEnd, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.DomContentLoadedEventStart, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.DomContentLoadedEventEnd, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.LoadEventStart, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.LoadEventEnd, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.FirstPaint, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.FirstContentfulPaint, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.SpeedIndex, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.VisuallyComplete, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.TimeToInteractive, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeInputEvent(reader BytesReader) (Message, error) { - var err error = nil - msg := &InputEvent{} - if msg.MessageID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &InputEvent{} + if msg.MessageID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Value, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.ValueMasked, err = reader.ReadBoolean(); err != nil { - return nil, err - } + return nil, err + } if msg.Label, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeClickEvent(reader BytesReader) (Message, error) { - var err error = nil - msg := &ClickEvent{} - if msg.MessageID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &ClickEvent{} + if msg.MessageID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.HesitationTime, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Label, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Selector, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeResourceEvent(reader BytesReader) (Message, error) { - var err error = nil - msg := &ResourceEvent{} - if msg.MessageID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &ResourceEvent{} + if msg.MessageID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Duration, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.TTFB, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.HeaderSize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.EncodedBodySize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.DecodedBodySize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.URL, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Type, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Success, err = reader.ReadBoolean(); err != nil { - return nil, err - } + return nil, err + } if msg.Method, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Status, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeCSSInsertRule(reader BytesReader) (Message, error) { - var err error = nil - msg := &CSSInsertRule{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &CSSInsertRule{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Rule, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Index, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeCSSDeleteRule(reader BytesReader) (Message, error) { - var err error = nil - msg := &CSSDeleteRule{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &CSSDeleteRule{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Index, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeFetch(reader BytesReader) (Message, error) { - var err error = nil - msg := &Fetch{} - if msg.Method, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &Fetch{} + if msg.Method, err = reader.ReadString(); err != nil { + return nil, err + } if msg.URL, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Request, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Response, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Status, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Duration, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeProfiler(reader BytesReader) (Message, error) { - var err error = nil - msg := &Profiler{} - if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &Profiler{} + if msg.Name, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Duration, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Args, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Result, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeOTable(reader BytesReader) (Message, error) { - var err error = nil - msg := &OTable{} - if msg.Key, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &OTable{} + if msg.Key, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Value, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeStateAction(reader BytesReader) (Message, error) { - var err error = nil - msg := &StateAction{} - if msg.Type, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &StateAction{} + if msg.Type, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err } func DecodeRedux(reader BytesReader) (Message, error) { - var err error = nil - msg := &Redux{} - if msg.Action, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &Redux{} + if msg.Action, err = reader.ReadString(); err != nil { + return nil, err + } if msg.State, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Duration, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeVuex(reader BytesReader) (Message, error) { - var err error = nil - msg := &Vuex{} - if msg.Mutation, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &Vuex{} + if msg.Mutation, err = reader.ReadString(); err != nil { + return nil, err + } if msg.State, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeMobX(reader BytesReader) (Message, error) { - var err error = nil - msg := &MobX{} - if msg.Type, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &MobX{} + if msg.Type, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Payload, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeNgRx(reader BytesReader) (Message, error) { - var err error = nil - msg := &NgRx{} - if msg.Action, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &NgRx{} + if msg.Action, err = reader.ReadString(); err != nil { + return nil, err + } if msg.State, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Duration, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeGraphQL(reader BytesReader) (Message, error) { - var err error = nil - msg := &GraphQL{} - if msg.OperationKind, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &GraphQL{} + if msg.OperationKind, err = reader.ReadString(); err != nil { + return nil, err + } if msg.OperationName, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Variables, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Response, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodePerformanceTrack(reader BytesReader) (Message, error) { - var err error = nil - msg := &PerformanceTrack{} - if msg.Frames, err = reader.ReadInt(); err != nil { - return nil, err - } + var err error = nil + msg := &PerformanceTrack{} + if msg.Frames, err = reader.ReadInt(); err != nil { + return nil, err + } if msg.Ticks, err = reader.ReadInt(); err != nil { - return nil, err - } + return nil, err + } if msg.TotalJSHeapSize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.UsedJSHeapSize, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeDOMDrop(reader BytesReader) (Message, error) { - var err error = nil - msg := &DOMDrop{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &DOMDrop{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } + return msg, err } func DecodeResourceTiming(reader BytesReader) (Message, error) { - var err error = nil - msg := &ResourceTiming{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &ResourceTiming{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Duration, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.TTFB, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.HeaderSize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.EncodedBodySize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.DecodedBodySize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.URL, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Initiator, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeConnectionInformation(reader BytesReader) (Message, error) { - var err error = nil - msg := &ConnectionInformation{} - if msg.Downlink, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &ConnectionInformation{} + if msg.Downlink, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Type, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetPageVisibility(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetPageVisibility{} - if msg.hidden, err = reader.ReadBoolean(); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetPageVisibility{} + if msg.hidden, err = reader.ReadBoolean(); err != nil { + return nil, err + } + return msg, err } func DecodePerformanceTrackAggr(reader BytesReader) (Message, error) { - var err error = nil - msg := &PerformanceTrackAggr{} - if msg.TimestampStart, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &PerformanceTrackAggr{} + if msg.TimestampStart, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.TimestampEnd, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MinFPS, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.AvgFPS, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MaxFPS, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MinCPU, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.AvgCPU, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MaxCPU, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MinTotalJSHeapSize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.AvgTotalJSHeapSize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MaxTotalJSHeapSize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MinUsedJSHeapSize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.AvgUsedJSHeapSize, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MaxUsedJSHeapSize, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeLoadFontFace(reader BytesReader) (Message, error) { - var err error = nil - msg := &LoadFontFace{} - if msg.ParentID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &LoadFontFace{} + if msg.ParentID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Family, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Source, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Descriptors, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetNodeFocus(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetNodeFocus{} - if msg.ID, err = reader.ReadInt(); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetNodeFocus{} + if msg.ID, err = reader.ReadInt(); err != nil { + return nil, err + } + return msg, err } func DecodeLongTask(reader BytesReader) (Message, error) { - var err error = nil - msg := &LongTask{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &LongTask{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Duration, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Context, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.ContainerType, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.ContainerSrc, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.ContainerId, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.ContainerName, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetNodeAttributeURLBased(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetNodeAttributeURLBased{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SetNodeAttributeURLBased{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Value, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.BaseURL, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetCSSDataURLBased(reader BytesReader) (Message, error) { - var err error = nil - msg := &SetCSSDataURLBased{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SetCSSDataURLBased{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Data, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.BaseURL, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } -func DecodeIssueEvent(reader BytesReader) (Message, error) { - var err error = nil - msg := &IssueEvent{} - if msg.MessageID, err = reader.ReadUint(); err != nil { - return nil, err - } +func DecodeIssueEventDeprecated(reader BytesReader) (Message, error) { + var err error = nil + msg := &IssueEventDeprecated{} + if msg.MessageID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Type, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.ContextString, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Context, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Payload, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeTechnicalInfo(reader BytesReader) (Message, error) { - var err error = nil - msg := &TechnicalInfo{} - if msg.Type, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &TechnicalInfo{} + if msg.Type, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Value, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeCustomIssue(reader BytesReader) (Message, error) { - var err error = nil - msg := &CustomIssue{} - if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &CustomIssue{} + if msg.Name, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Payload, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeAssetCache(reader BytesReader) (Message, error) { - var err error = nil - msg := &AssetCache{} - if msg.URL, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &AssetCache{} + if msg.URL, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err } func DecodeCSSInsertRuleURLBased(reader BytesReader) (Message, error) { - var err error = nil - msg := &CSSInsertRuleURLBased{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &CSSInsertRuleURLBased{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Rule, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Index, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.BaseURL, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeMouseClick(reader BytesReader) (Message, error) { - var err error = nil - msg := &MouseClick{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &MouseClick{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.HesitationTime, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Label, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Selector, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeCreateIFrameDocument(reader BytesReader) (Message, error) { - var err error = nil - msg := &CreateIFrameDocument{} - if msg.FrameID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &CreateIFrameDocument{} + if msg.FrameID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeAdoptedSSReplaceURLBased(reader BytesReader) (Message, error) { - var err error = nil - msg := &AdoptedSSReplaceURLBased{} - if msg.SheetID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &AdoptedSSReplaceURLBased{} + if msg.SheetID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Text, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.BaseURL, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeAdoptedSSReplace(reader BytesReader) (Message, error) { - var err error = nil - msg := &AdoptedSSReplace{} - if msg.SheetID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &AdoptedSSReplace{} + if msg.SheetID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Text, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeAdoptedSSInsertRuleURLBased(reader BytesReader) (Message, error) { - var err error = nil - msg := &AdoptedSSInsertRuleURLBased{} - if msg.SheetID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &AdoptedSSInsertRuleURLBased{} + if msg.SheetID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Rule, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Index, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.BaseURL, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeAdoptedSSInsertRule(reader BytesReader) (Message, error) { - var err error = nil - msg := &AdoptedSSInsertRule{} - if msg.SheetID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &AdoptedSSInsertRule{} + if msg.SheetID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Rule, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Index, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeAdoptedSSDeleteRule(reader BytesReader) (Message, error) { - var err error = nil - msg := &AdoptedSSDeleteRule{} - if msg.SheetID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &AdoptedSSDeleteRule{} + if msg.SheetID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Index, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeAdoptedSSAddOwner(reader BytesReader) (Message, error) { - var err error = nil - msg := &AdoptedSSAddOwner{} - if msg.SheetID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &AdoptedSSAddOwner{} + if msg.SheetID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeAdoptedSSRemoveOwner(reader BytesReader) (Message, error) { - var err error = nil - msg := &AdoptedSSRemoveOwner{} - if msg.SheetID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &AdoptedSSRemoveOwner{} + if msg.SheetID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeZustand(reader BytesReader) (Message, error) { - var err error = nil - msg := &Zustand{} - if msg.Mutation, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &Zustand{} + if msg.Mutation, err = reader.ReadString(); err != nil { + return nil, err + } if msg.State, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeJSException(reader BytesReader) (Message, error) { - var err error = nil - msg := &JSException{} - if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &JSException{} + if msg.Name, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Message, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Payload, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Metadata, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err +} + +func DecodeBatchMeta(reader BytesReader) (Message, error) { + var err error = nil + msg := &BatchMeta{} + if msg.PageNo, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.FirstIndex, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Timestamp, err = reader.ReadInt(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeBatchMetadata(reader BytesReader) (Message, error) { + var err error = nil + msg := &BatchMetadata{} + if msg.Version, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.PageNo, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.FirstIndex, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Timestamp, err = reader.ReadInt(); err != nil { + return nil, err + } + if msg.Location, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err +} + +func DecodePartitionedMessage(reader BytesReader) (Message, error) { + var err error = nil + msg := &PartitionedMessage{} + if msg.PartNo, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.PartTotal, err = reader.ReadUint(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeIssueEvent(reader BytesReader) (Message, error) { + var err error = nil + msg := &IssueEvent{} + if msg.MessageID, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Type, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.ContextString, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Context, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Payload, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.URL, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err } func DecodeSessionEnd(reader BytesReader) (Message, error) { - var err error = nil - msg := &SessionEnd{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SessionEnd{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.EncryptionKey, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSessionSearch(reader BytesReader) (Message, error) { - var err error = nil - msg := &SessionSearch{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &SessionSearch{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Partition, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSBatchMeta(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSBatchMeta{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSBatchMeta{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.FirstIndex, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSSessionStart(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSSessionStart{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSSessionStart{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.ProjectID, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.TrackerVersion, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.RevID, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserUUID, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserOS, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserOSVersion, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserDevice, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserDeviceType, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.UserCountry, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSSessionEnd(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSSessionEnd{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSSessionEnd{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } + return msg, err } func DecodeIOSMetadata(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSMetadata{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSMetadata{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Key, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Value, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSCustomEvent(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSCustomEvent{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSCustomEvent{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Payload, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSUserID(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSUserID{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSUserID{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Value, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSUserAnonymousID(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSUserAnonymousID{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSUserAnonymousID{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Value, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSScreenChanges(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSScreenChanges{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSScreenChanges{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.X, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Y, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Width, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Height, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSCrash(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSCrash{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSCrash{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Reason, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Stacktrace, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSScreenEnter(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSScreenEnter{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSScreenEnter{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Title, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.ViewName, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSScreenLeave(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSScreenLeave{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSScreenLeave{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Title, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.ViewName, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSClickEvent(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSClickEvent{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSClickEvent{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Label, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.X, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Y, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSInputEvent(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSInputEvent{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSInputEvent{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Value, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.ValueMasked, err = reader.ReadBoolean(); err != nil { - return nil, err - } + return nil, err + } if msg.Label, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSPerformanceEvent(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSPerformanceEvent{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSPerformanceEvent{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Value, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSLog(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSLog{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSLog{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Severity, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Content, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSInternalError(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSInternalError{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSInternalError{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Content, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSNetworkCall(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSNetworkCall{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSNetworkCall{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Length, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Duration, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Headers, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Body, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.URL, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Success, err = reader.ReadBoolean(); err != nil { - return nil, err - } + return nil, err + } if msg.Method, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Status, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSPerformanceAggregated(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSPerformanceAggregated{} - if msg.TimestampStart, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSPerformanceAggregated{} + if msg.TimestampStart, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.TimestampEnd, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MinFPS, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.AvgFPS, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MaxFPS, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MinCPU, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.AvgCPU, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MaxCPU, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MinMemory, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.AvgMemory, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MaxMemory, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MinBattery, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.AvgBattery, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.MaxBattery, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIOSIssueEvent(reader BytesReader) (Message, error) { - var err error = nil - msg := &IOSIssueEvent{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IOSIssueEvent{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Type, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.ContextString, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Context, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Payload, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func ReadMessage(t uint64, reader BytesReader) (Message, error) { switch t { - case 80: - return DecodeBatchMeta(reader) - case 81: - return DecodeBatchMetadata(reader) - case 82: - return DecodePartitionedMessage(reader) case 0: return DecodeTimestamp(reader) case 1: @@ -1813,7 +1834,7 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) { case 61: return DecodeSetCSSDataURLBased(reader) case 62: - return DecodeIssueEvent(reader) + return DecodeIssueEventDeprecated(reader) case 63: return DecodeTechnicalInfo(reader) case 64: @@ -1844,6 +1865,14 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) { return DecodeZustand(reader) case 78: return DecodeJSException(reader) + case 80: + return DecodeBatchMeta(reader) + case 81: + return DecodeBatchMetadata(reader) + case 82: + return DecodePartitionedMessage(reader) + case 125: + return DecodeIssueEvent(reader) case 126: return DecodeSessionEnd(reader) case 127: diff --git a/backend/pkg/messages/session-iterator.go b/backend/pkg/messages/session-iterator.go index 45daae4b8..eb9f32387 100644 --- a/backend/pkg/messages/session-iterator.go +++ b/backend/pkg/messages/session-iterator.go @@ -40,6 +40,13 @@ func SplitMessages(data []byte) ([]*msgInfo, error) { return nil, fmt.Errorf("read message type err: %s", err) } + if msgType == MsgRedux { + log.Printf("redux") + } + if msgType == MsgFetch { + log.Printf("fetch") + } + // Read message body _, err = ReadMessage(msgType, reader) if err != nil { diff --git a/ee/backend/pkg/db/clickhouse/connector.go b/ee/backend/pkg/db/clickhouse/connector.go index 20996c073..8762a683c 100644 --- a/ee/backend/pkg/db/clickhouse/connector.go +++ b/ee/backend/pkg/db/clickhouse/connector.go @@ -87,7 +87,7 @@ var batches = map[string]string{ "requests": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, request_body, response_body, status, method, duration, success, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "custom": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, name, payload, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", "graphql": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, name, request_body, response_body, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", - "issuesEvents": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, issue_id, issue_type, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", + "issuesEvents": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, issue_id, issue_type, event_type, url) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", "issues": "INSERT INTO experimental.issues (project_id, issue_id, type, context_string) VALUES (?, ?, ?, ?)", } @@ -132,6 +132,7 @@ func (c *connectorImpl) InsertIssue(session *types.Session, msg *messages.IssueE issueID, msg.Type, "ISSUE", + msg.URL, ); err != nil { c.checkError("issuesEvents", err) return fmt.Errorf("can't append to issuesEvents batch: %s", err) diff --git a/ee/connectors/msgcodec/messages.py b/ee/connectors/msgcodec/messages.py index 313fdf12c..517db5378 100644 --- a/ee/connectors/msgcodec/messages.py +++ b/ee/connectors/msgcodec/messages.py @@ -6,34 +6,6 @@ class Message(ABC): pass -class BatchMeta(Message): - __id__ = 80 - - def __init__(self, page_no, first_index, timestamp): - self.page_no = page_no - self.first_index = first_index - self.timestamp = timestamp - - -class BatchMetadata(Message): - __id__ = 81 - - def __init__(self, version, page_no, first_index, timestamp, location): - self.version = version - self.page_no = page_no - self.first_index = first_index - self.timestamp = timestamp - self.location = location - - -class PartitionedMessage(Message): - __id__ = 82 - - def __init__(self, part_no, part_total): - self.part_no = part_no - self.part_total = part_total - - class Timestamp(Message): __id__ = 0 @@ -586,7 +558,7 @@ class SetCSSDataURLBased(Message): self.base_url = base_url -class IssueEvent(Message): +class IssueEventDeprecated(Message): __id__ = 62 def __init__(self, message_id, timestamp, type, context_string, context, payload): @@ -727,6 +699,47 @@ class JSException(Message): self.metadata = metadata +class BatchMeta(Message): + __id__ = 80 + + def __init__(self, page_no, first_index, timestamp): + self.page_no = page_no + self.first_index = first_index + self.timestamp = timestamp + + +class BatchMetadata(Message): + __id__ = 81 + + def __init__(self, version, page_no, first_index, timestamp, location): + self.version = version + self.page_no = page_no + self.first_index = first_index + self.timestamp = timestamp + self.location = location + + +class PartitionedMessage(Message): + __id__ = 82 + + def __init__(self, part_no, part_total): + self.part_no = part_no + self.part_total = part_total + + +class IssueEvent(Message): + __id__ = 125 + + def __init__(self, message_id, timestamp, type, context_string, context, payload, url): + self.message_id = message_id + self.timestamp = timestamp + self.type = type + self.context_string = context_string + self.context = context + self.payload = payload + self.url = url + + class SessionEnd(Message): __id__ = 126 diff --git a/ee/connectors/msgcodec/msgcodec.py b/ee/connectors/msgcodec/msgcodec.py index dc63ffa79..5ecd0bd81 100644 --- a/ee/connectors/msgcodec/msgcodec.py +++ b/ee/connectors/msgcodec/msgcodec.py @@ -76,28 +76,6 @@ class MessageCodec(Codec): def read_head_message(self, reader: io.BytesIO, message_id) -> Message: - if message_id == 80: - return BatchMeta( - page_no=self.read_uint(reader), - first_index=self.read_uint(reader), - timestamp=self.read_int(reader) - ) - - if message_id == 81: - return BatchMetadata( - version=self.read_uint(reader), - page_no=self.read_uint(reader), - first_index=self.read_uint(reader), - timestamp=self.read_int(reader), - location=self.read_string(reader) - ) - - if message_id == 82: - return PartitionedMessage( - part_no=self.read_uint(reader), - part_total=self.read_uint(reader) - ) - if message_id == 0: return Timestamp( timestamp=self.read_uint(reader) @@ -539,7 +517,7 @@ class MessageCodec(Codec): ) if message_id == 62: - return IssueEvent( + return IssueEventDeprecated( message_id=self.read_uint(reader), timestamp=self.read_uint(reader), type=self.read_string(reader), @@ -647,6 +625,39 @@ class MessageCodec(Codec): metadata=self.read_string(reader) ) + if message_id == 80: + return BatchMeta( + page_no=self.read_uint(reader), + first_index=self.read_uint(reader), + timestamp=self.read_int(reader) + ) + + if message_id == 81: + return BatchMetadata( + version=self.read_uint(reader), + page_no=self.read_uint(reader), + first_index=self.read_uint(reader), + timestamp=self.read_int(reader), + location=self.read_string(reader) + ) + + if message_id == 82: + return PartitionedMessage( + part_no=self.read_uint(reader), + part_total=self.read_uint(reader) + ) + + if message_id == 125: + return IssueEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + type=self.read_string(reader), + context_string=self.read_string(reader), + context=self.read_string(reader), + payload=self.read_string(reader), + url=self.read_string(reader) + ) + if message_id == 126: return SessionEnd( timestamp=self.read_uint(reader), diff --git a/frontend/app/player/web/messages/tracker.gen.ts b/frontend/app/player/web/messages/tracker.gen.ts index 0788b85d1..b510f0df1 100644 --- a/frontend/app/player/web/messages/tracker.gen.ts +++ b/frontend/app/player/web/messages/tracker.gen.ts @@ -5,21 +5,6 @@ import type { RawMessage } from './raw.gen' import { MType } from './raw.gen' -type TrBatchMetadata = [ - type: 81, - version: number, - pageNo: number, - firstIndex: number, - timestamp: number, - location: string, -] - -type TrPartitionedMessage = [ - type: 82, - partNo: number, - partTotal: number, -] - type TrTimestamp = [ type: 0, timestamp: number, @@ -423,8 +408,23 @@ type TrJSException = [ metadata: string, ] +type TrBatchMetadata = [ + type: 81, + version: number, + pageNo: number, + firstIndex: number, + timestamp: number, + location: string, +] -export type TrackerMessage = TrBatchMetadata | TrPartitionedMessage | TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequest | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrJSExceptionDeprecated | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrResourceTiming | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrZustand | TrJSException +type TrPartitionedMessage = [ + type: 82, + partNo: number, + partTotal: number, +] + + +export type TrackerMessage = TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequest | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrJSExceptionDeprecated | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrResourceTiming | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrZustand | TrJSException | TrBatchMetadata | TrPartitionedMessage export default function translate(tMsg: TrackerMessage): RawMessage | null { switch(tMsg[0]) { diff --git a/mobs/messages.rb b/mobs/messages.rb index 7090aaeb1..47d24c800 100644 --- a/mobs/messages.rb +++ b/mobs/messages.rb @@ -1,27 +1,4 @@ -# Special one for Batch Metadata. Message id could define the version - -# Deprecated since tracker 3.6.0 in favor of BatchMetadata -message 80, 'BatchMeta', :replayer => false, :tracker => false do - uint 'PageNo' - uint 'FirstIndex' - int 'Timestamp' -end - -# since tracker 3.6.0 TODO: for webworker only -message 81, 'BatchMetadata', :replayer => false do - uint 'Version' - uint 'PageNo' - uint 'FirstIndex' - int 'Timestamp' - string 'Location' -end - -# since tracker 3.6.0 -message 82, 'PartitionedMessage', :replayer => false do - uint 'PartNo' - uint 'PartTotal' -end - +# OpenReplay messages definition message 0, 'Timestamp' do uint 'Timestamp' @@ -408,7 +385,7 @@ message 61, 'SetCSSDataURLBased' do string 'Data' string 'BaseURL' end -message 62, 'IssueEvent', :replayer => false, :tracker => false do +message 62, 'IssueEventDeprecated', :replayer => false, :tracker => false do uint 'MessageID' uint 'Timestamp' string 'Type' @@ -493,15 +470,48 @@ message 78, 'JSException', :replayer => false do string 'Metadata' end +# 80 -- 90 reserved + +# Special one for Batch Metadata. Message id could define the version + +# Deprecated since tracker 3.6.0 in favor of BatchMetadata +message 80, 'BatchMeta', :replayer => false, :tracker => false do + uint 'PageNo' + uint 'FirstIndex' + int 'Timestamp' +end + +# since tracker 3.6.0 TODO: for webworker only +message 81, 'BatchMetadata', :replayer => false do + uint 'Version' + uint 'PageNo' + uint 'FirstIndex' + int 'Timestamp' + string 'Location' +end + +# since tracker 3.6.0 +message 82, 'PartitionedMessage', :replayer => false do + uint 'PartNo' + uint 'PartTotal' +end + +message 125, 'IssueEvent', :replayer => false, :tracker => false do + uint 'MessageID' + uint 'Timestamp' + string 'Type' + string 'ContextString' + string 'Context' + string 'Payload' + string 'URL' +end message 126, 'SessionEnd', :tracker => false, :replayer => false do uint 'Timestamp' string 'EncryptionKey' end + message 127, 'SessionSearch', :tracker => false, :replayer => false do uint 'Timestamp' uint 'Partition' end - - -# 80 -- 90 reserved diff --git a/tracker/tracker/src/common/messages.gen.ts b/tracker/tracker/src/common/messages.gen.ts index 2d6624b5e..a712f87e0 100644 --- a/tracker/tracker/src/common/messages.gen.ts +++ b/tracker/tracker/src/common/messages.gen.ts @@ -2,8 +2,6 @@ /* eslint-disable */ export declare const enum Type { - BatchMetadata = 81, - PartitionedMessage = 82, Timestamp = 0, SetPageLocation = 4, SetViewportSize = 5, @@ -62,24 +60,11 @@ export declare const enum Type { AdoptedSSRemoveOwner = 77, Zustand = 79, JSException = 78, + BatchMetadata = 81, + PartitionedMessage = 82, } -export type BatchMetadata = [ - /*type:*/ Type.BatchMetadata, - /*version:*/ number, - /*pageNo:*/ number, - /*firstIndex:*/ number, - /*timestamp:*/ number, - /*location:*/ string, -] - -export type PartitionedMessage = [ - /*type:*/ Type.PartitionedMessage, - /*partNo:*/ number, - /*partTotal:*/ number, -] - export type Timestamp = [ /*type:*/ Type.Timestamp, /*timestamp:*/ number, @@ -483,6 +468,21 @@ export type JSException = [ /*metadata:*/ string, ] +export type BatchMetadata = [ + /*type:*/ Type.BatchMetadata, + /*version:*/ number, + /*pageNo:*/ number, + /*firstIndex:*/ number, + /*timestamp:*/ number, + /*location:*/ string, +] -type Message = BatchMetadata | PartitionedMessage | Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequest | ConsoleLog | PageLoadTiming | PageRenderTiming | JSExceptionDeprecated | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | ResourceTiming | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | Zustand | JSException +export type PartitionedMessage = [ + /*type:*/ Type.PartitionedMessage, + /*partNo:*/ number, + /*partTotal:*/ number, +] + + +type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequest | ConsoleLog | PageLoadTiming | PageRenderTiming | JSExceptionDeprecated | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | ResourceTiming | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | Zustand | JSException | BatchMetadata | PartitionedMessage export default Message diff --git a/tracker/tracker/src/main/app/messages.gen.ts b/tracker/tracker/src/main/app/messages.gen.ts index 82f106e7d..14446c122 100644 --- a/tracker/tracker/src/main/app/messages.gen.ts +++ b/tracker/tracker/src/main/app/messages.gen.ts @@ -5,34 +5,6 @@ import * as Messages from '../../common/messages.gen.js' export { default, Type } from '../../common/messages.gen.js' -export function BatchMetadata( - version: number, - pageNo: number, - firstIndex: number, - timestamp: number, - location: string, -): Messages.BatchMetadata { - return [ - Messages.Type.BatchMetadata, - version, - pageNo, - firstIndex, - timestamp, - location, - ] -} - -export function PartitionedMessage( - partNo: number, - partTotal: number, -): Messages.PartitionedMessage { - return [ - Messages.Type.PartitionedMessage, - partNo, - partTotal, - ] -} - export function Timestamp( timestamp: number, ): Messages.Timestamp { @@ -781,3 +753,31 @@ export function JSException( ] } +export function BatchMetadata( + version: number, + pageNo: number, + firstIndex: number, + timestamp: number, + location: string, +): Messages.BatchMetadata { + return [ + Messages.Type.BatchMetadata, + version, + pageNo, + firstIndex, + timestamp, + location, + ] +} + +export function PartitionedMessage( + partNo: number, + partTotal: number, +): Messages.PartitionedMessage { + return [ + Messages.Type.PartitionedMessage, + partNo, + partTotal, + ] +} + diff --git a/tracker/tracker/src/webworker/MessageEncoder.gen.ts b/tracker/tracker/src/webworker/MessageEncoder.gen.ts index d7772d54a..4bb30f036 100644 --- a/tracker/tracker/src/webworker/MessageEncoder.gen.ts +++ b/tracker/tracker/src/webworker/MessageEncoder.gen.ts @@ -10,14 +10,6 @@ export default class MessageEncoder extends PrimitiveEncoder { encode(msg: Message): boolean { switch(msg[0]) { - case Messages.Type.BatchMetadata: - return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.int(msg[4]) && this.string(msg[5]) - break - - case Messages.Type.PartitionedMessage: - return this.uint(msg[1]) && this.uint(msg[2]) - break - case Messages.Type.Timestamp: return this.uint(msg[1]) break @@ -250,6 +242,14 @@ export default class MessageEncoder extends PrimitiveEncoder { return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) break + case Messages.Type.BatchMetadata: + return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.int(msg[4]) && this.string(msg[5]) + break + + case Messages.Type.PartitionedMessage: + return this.uint(msg[1]) && this.uint(msg[2]) + break + } } From 861302bba7971cf92ddc746325deb4eb608e4b38 Mon Sep 17 00:00:00 2001 From: Alexander <zavorotynskiy@pm.me> Date: Wed, 18 Jan 2023 14:43:24 +0100 Subject: [PATCH 61/65] [Ender] new message iterator (#929) * feat(backend): added new message iterator especially for ender --- backend/cmd/ender/main.go | 2 +- backend/pkg/messages/iterator-ender.go | 179 +++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 backend/pkg/messages/iterator-ender.go diff --git a/backend/cmd/ender/main.go b/backend/cmd/ender/main.go index be582c2fd..10644456c 100644 --- a/backend/cmd/ender/main.go +++ b/backend/cmd/ender/main.go @@ -42,7 +42,7 @@ func main() { consumer := queue.NewConsumer( cfg.GroupEnder, []string{cfg.TopicRawWeb}, - messages.NewMessageIterator( + messages.NewEnderMessageIterator( func(msg messages.Message) { sessions.UpdateSession(msg) }, []int{messages.MsgTimestamp}, false), diff --git a/backend/pkg/messages/iterator-ender.go b/backend/pkg/messages/iterator-ender.go new file mode 100644 index 000000000..15183ef51 --- /dev/null +++ b/backend/pkg/messages/iterator-ender.go @@ -0,0 +1,179 @@ +package messages + +import ( + "fmt" + "log" +) + +type enderMessageIteratorImpl struct { + filter map[int]struct{} + preFilter map[int]struct{} + handler MessageHandler + autoDecode bool + version uint64 + size uint64 + canSkip bool + broken bool + messageInfo *message + batchInfo *BatchInfo + urls *pageLocations +} + +func NewEnderMessageIterator(messageHandler MessageHandler, messageFilter []int, autoDecode bool) MessageIterator { + iter := &enderMessageIteratorImpl{ + handler: messageHandler, + autoDecode: autoDecode, + urls: NewPageLocations(), + } + if len(messageFilter) != 0 { + filter := make(map[int]struct{}, len(messageFilter)) + for _, msgType := range messageFilter { + filter[msgType] = struct{}{} + } + iter.filter = filter + } + iter.preFilter = map[int]struct{}{ + MsgBatchMetadata: {}, MsgBatchMeta: {}, MsgTimestamp: {}, + MsgSessionStart: {}, MsgSessionEnd: {}, MsgSetPageLocation: {}, + MsgSessionEndDeprecated: {}} + return iter +} + +func (i *enderMessageIteratorImpl) prepareVars(batchInfo *BatchInfo) { + i.batchInfo = batchInfo + i.messageInfo = &message{batch: batchInfo} + i.version = 0 + i.canSkip = false + i.broken = false + i.size = 0 +} + +func (i *enderMessageIteratorImpl) Iterate(batchData []byte, batchInfo *BatchInfo) { + // Create new message reader + reader := NewMessageReader(batchData) + + // Pre-decode batch data + if err := reader.Parse(); err != nil { + log.Printf("pre-decode batch err: %s, info: %s", err, batchInfo.Info()) + return + } + + // Prepare iterator before processing messages in batch + i.prepareVars(batchInfo) + + // Store last timestamp message here + var lastMessage Message + + for reader.Next() { + // Increase message index (can be overwritten by batch info message) + i.messageInfo.Index++ + + msg := reader.Message() + + // Preprocess "system" messages + if _, ok := i.preFilter[msg.TypeID()]; ok { + msg = msg.Decode() + if msg == nil { + log.Printf("decode error, type: %d, info: %s", msg.TypeID(), i.batchInfo.Info()) + return + } + msg = transformDeprecated(msg) + if err := i.preprocessing(msg); err != nil { + log.Printf("message preprocessing err: %s", err) + return + } + } + + // Skip messages we don't have in filter + if i.filter != nil { + if _, ok := i.filter[msg.TypeID()]; !ok { + continue + } + } + + if i.autoDecode { + msg = msg.Decode() + if msg == nil { + log.Printf("decode error, type: %d, info: %s", msg.TypeID(), i.batchInfo.Info()) + return + } + } + + // Set meta information for message + msg.Meta().SetMeta(i.messageInfo) + + // Update last timestamp message + lastMessage = msg + } + + if lastMessage != nil { + i.handler(lastMessage) + } + +} + +func (i *enderMessageIteratorImpl) zeroTsLog(msgType string) { + log.Printf("zero timestamp in %s, info: %s", msgType, i.batchInfo.Info()) +} + +func (i *enderMessageIteratorImpl) preprocessing(msg Message) error { + switch m := msg.(type) { + case *BatchMetadata: + if i.messageInfo.Index > 1 { // Might be several 0-0 BatchMeta in a row without an error though + return fmt.Errorf("batchMetadata found at the end of the batch, info: %s", i.batchInfo.Info()) + } + if m.Version > 1 { + return fmt.Errorf("incorrect batch version: %d, skip current batch, info: %s", i.version, i.batchInfo.Info()) + } + i.messageInfo.Index = m.PageNo<<32 + m.FirstIndex // 2^32 is the maximum count of messages per page (ha-ha) + i.messageInfo.Timestamp = m.Timestamp + if m.Timestamp == 0 { + i.zeroTsLog("BatchMetadata") + } + i.messageInfo.Url = m.Location + i.version = m.Version + i.batchInfo.version = m.Version + + case *BatchMeta: // Is not required to be present in batch since IOS doesn't have it (though we might change it) + if i.messageInfo.Index > 1 { // Might be several 0-0 BatchMeta in a row without an error though + return fmt.Errorf("batchMeta found at the end of the batch, info: %s", i.batchInfo.Info()) + } + i.messageInfo.Index = m.PageNo<<32 + m.FirstIndex // 2^32 is the maximum count of messages per page (ha-ha) + i.messageInfo.Timestamp = m.Timestamp + if m.Timestamp == 0 { + i.zeroTsLog("BatchMeta") + } + // Try to get saved session's page url + if savedURL := i.urls.Get(i.messageInfo.batch.sessionID); savedURL != "" { + i.messageInfo.Url = savedURL + } + + case *Timestamp: + i.messageInfo.Timestamp = int64(m.Timestamp) + if m.Timestamp == 0 { + i.zeroTsLog("Timestamp") + } + + case *SessionStart: + i.messageInfo.Timestamp = int64(m.Timestamp) + if m.Timestamp == 0 { + i.zeroTsLog("SessionStart") + log.Printf("zero session start, project: %d, UA: %s, tracker: %s, info: %s", + m.ProjectID, m.UserAgent, m.TrackerVersion, i.batchInfo.Info()) + } + + case *SessionEnd: + i.messageInfo.Timestamp = int64(m.Timestamp) + if m.Timestamp == 0 { + i.zeroTsLog("SessionEnd") + } + // Delete session from urls cache layer + i.urls.Delete(i.messageInfo.batch.sessionID) + + case *SetPageLocation: + i.messageInfo.Url = m.URL + // Save session page url in cache for using in next batches + i.urls.Set(i.messageInfo.batch.sessionID, m.URL) + } + return nil +} From b730c46a18c09cc3b8870748f293d345228e1147 Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Wed, 18 Jan 2023 15:06:55 +0100 Subject: [PATCH 62/65] feat(ui) - insights - toggle for own cards --- .../MetricViewHeader/MetricViewHeader.tsx | 19 ++++++++++++++++--- .../components/ui/Toggler/toggler.module.css | 2 +- frontend/app/mstore/metricStore.ts | 4 +++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx index ea5311082..b9e62cb12 100644 --- a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx +++ b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon, PageTitle, Button, Link } from 'UI'; +import { Icon, PageTitle, Button, Link, Toggler } from 'UI'; import MetricsSearch from '../MetricsSearch'; import Select from 'Shared/Select'; import { useStore } from 'App/mstore'; @@ -33,11 +33,22 @@ function MetricViewHeader() { <ListViewToggler /> <div className="items-center flex gap-4"> + <Toggler + label="My Cards" + checked={filter.showMine} + name="test" + className="font-medium mr-2" + onChange={() => + metricStore.updateKey('filter', { ...filter, showMine: !filter.showMine }) + } + /> <Select options={[{ label: 'All Types', value: 'all' }, ...DROPDOWN_OPTIONS]} name="type" defaultValue={filter.type} - onChange={({ value }) => metricStore.updateKey('filter', { ...filter, type: value.value})} + onChange={({ value }) => + metricStore.updateKey('filter', { ...filter, type: value.value }) + } plain={true} isSearchable={true} /> @@ -55,7 +66,9 @@ function MetricViewHeader() { <DashboardDropdown plain={true} - onChange={(value: any) => metricStore.updateKey('filter', { ...filter, dashboard: value})} + onChange={(value: any) => + metricStore.updateKey('filter', { ...filter, dashboard: value }) + } /> </div> </div> diff --git a/frontend/app/components/ui/Toggler/toggler.module.css b/frontend/app/components/ui/Toggler/toggler.module.css index 26d82880c..0b2d7ac68 100644 --- a/frontend/app/components/ui/Toggler/toggler.module.css +++ b/frontend/app/components/ui/Toggler/toggler.module.css @@ -13,7 +13,7 @@ & span { padding-left: 10px; - color: $gray-medium; + /* color: $gray-dark; */ } } .switch input { diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index af7050955..cfcb5f19f 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -6,7 +6,6 @@ import Error from './types/error'; import { TIMESERIES, TABLE, - FUNNEL, ERRORS, RESOURCE_MONITORING, @@ -61,6 +60,9 @@ export default class MetricStore { return this.metrics .filter( (card) => + (this.filter.showMine + ? card.owner === JSON.parse(localStorage.getItem('user')!).account.email + : true) && (this.filter.type === 'all' || card.metricType === this.filter.type) && (!dbIds.length || card.dashboards.map((i) => i.dashboardId).some((id) => dbIds.includes(id))) && From 53309dfac56a3e3fc28a3d4966ce5f09055254be Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Wed, 18 Jan 2023 15:26:08 +0100 Subject: [PATCH 63/65] fix(ui) - css --- .../Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/Bar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/Bar.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/Bar.tsx index a6ca35923..163c7d0a5 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/Bar.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/Bar.tsx @@ -1,5 +1,5 @@ import React from 'react' -import stl from './Bar.module.css' +import stl from './bar.module.css' const Bar = ({ className = '', width = 0, avg, domain, color }) => { return ( From b27095968afdc196da0ddb543e75aa048502ef3b Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Wed, 18 Jan 2023 17:25:37 +0100 Subject: [PATCH 64/65] fix(ui) - card selection --- frontend/app/mstore/metricStore.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index cfcb5f19f..b4c5bdfac 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -12,6 +12,7 @@ import { PERFORMANCE, WEB_VITALS, INSIGHTS, + CLICKMAP } from 'App/constants/card'; import { clickmapFilter } from 'App/types/filter/newFilter'; import { getRE } from 'App/utils'; From c5f948d19db7e7c0146bf57029caa85357f30af9 Mon Sep 17 00:00:00 2001 From: Shekar Siri <sshekarsiri@gmail.com> Date: Wed, 18 Jan 2023 17:45:39 +0100 Subject: [PATCH 65/65] change(ui) - cards and dashboard --- .../components/DashboardList/Header.tsx | 28 +++++++++++++++---- .../DashboardSideMenu/DashboardSideMenu.tsx | 2 +- .../MetricViewHeader/MetricViewHeader.tsx | 2 +- frontend/app/components/ui/SVG.tsx | 3 +- frontend/app/constants/filterOptions.js | 6 ++-- frontend/app/mstore/dashboardStore.ts | 9 +++--- frontend/app/svg/icons/card-text.svg | 4 +++ 7 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 frontend/app/svg/icons/card-text.svg diff --git a/frontend/app/components/Dashboard/components/DashboardList/Header.tsx b/frontend/app/components/Dashboard/components/DashboardList/Header.tsx index 560c27c61..ad0cecdc9 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/Header.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/Header.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Button, PageTitle } from 'UI'; +import { Button, PageTitle, Toggler, Icon } from 'UI'; import Select from 'Shared/Select'; import DashboardSearch from './DashboardSearch'; import { useStore } from 'App/mstore'; -import { useObserver } from 'mobx-react-lite'; +import { observer, useObserver } from 'mobx-react-lite'; import { withSiteId } from 'App/routes'; function Header({ history, siteId }: { history: any; siteId: string }) { @@ -20,7 +20,7 @@ function Header({ history, siteId }: { history: any; siteId: string }) { return ( <> - <div className="flex items-center mb-4 justify-between px-6"> + <div className="flex items-center justify-between px-6"> <div className="flex items-baseline mr-3"> <PageTitle title="Dashboards" /> </div> @@ -34,8 +34,24 @@ function Header({ history, siteId }: { history: any; siteId: string }) { </div> </div> </div> + <div className="text-base text-disabled-text flex items-center px-6"> + <Icon name="info-circle-fill" className="mr-2" size={16} /> + A Dashboard is a collection of Metrics that can be shared across teams. + </div> <div className="border-y px-3 py-1 mt-2 flex items-center w-full justify-end gap-4"> - <Select + <Toggler + label="Private Dashboards" + checked={dashboardStore.filter.showMine} + name="test" + className="font-medium mr-2" + onChange={() => + dashboardStore.updateKey('filter', { + ...dashboardStore.filter, + showMine: !dashboardStore.filter.showMine, + }) + } + /> + {/* <Select options={[ { label: 'Visibility - All', value: 'all' }, { label: 'Visibility - Private', value: 'private' }, @@ -49,7 +65,7 @@ function Header({ history, siteId }: { history: any; siteId: string }) { visibility: value.value, }) } - /> + /> */} <Select options={[ @@ -65,4 +81,4 @@ function Header({ history, siteId }: { history: any; siteId: string }) { ); } -export default Header; +export default observer(Header); diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx index 7ead7f17f..d2451b68e 100644 --- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx @@ -39,7 +39,7 @@ function DashboardSideMenu(props: Props) { active={isMetric} id="menu-manage-alerts" title="Cards" - iconName="bar-chart-line" + iconName="card-text" onClick={() => redirect(withSiteId(metrics(), siteId))} /> </div> diff --git a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx index b9e62cb12..081dd21e5 100644 --- a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx +++ b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx @@ -12,7 +12,7 @@ function MetricViewHeader() { return ( <div> - <div className="flex items-center mb-4 justify-between px-6"> + <div className="flex items-center justify-between px-6"> <div className="flex items-baseline mr-3"> <PageTitle title="Cards" className="" /> </div> diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index 5e2a484ab..2bd33f4c9 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -1,7 +1,7 @@ import React from 'react'; -export type IconNames = 'activity' | 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-counterclockwise' | 'arrow-down-short' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up-short' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-checklist' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cubes' | 'dash' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | 'errors-icon' | 'event/click' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/link' | 'event/location' | 'event/resize' | 'event/view' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'gear' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-1x2' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grid' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'ic-errors' | 'ic-network' | 'ic-rage' | 'ic-resources' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-arrow' | 'list-ul' | 'list' | 'lock-alt' | 'magic' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'percent' | 'performance-icon' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus' | 'pointer-sessions-search' | 'prev1' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'quotes' | 'record-circle' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signpost-split' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; +export type IconNames = 'activity' | 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-counterclockwise' | 'arrow-down-short' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up-short' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-checklist' | 'card-text' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cubes' | 'dash' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | 'errors-icon' | 'event/click' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/link' | 'event/location' | 'event/resize' | 'event/view' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'gear' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-1x2' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grid' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'ic-errors' | 'ic-network' | 'ic-rage' | 'ic-resources' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-arrow' | 'list-ul' | 'list' | 'lock-alt' | 'magic' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'percent' | 'performance-icon' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus' | 'pointer-sessions-search' | 'prev1' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'quotes' | 'record-circle' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signpost-split' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; interface Props { name: IconNames; @@ -100,6 +100,7 @@ const SVG = (props: Props) => { case 'camera-video': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M0 5a2 2 0 0 1 2-2h7.5a2 2 0 0 1 1.983 1.738l3.11-1.382A1 1 0 0 1 16 4.269v7.462a1 1 0 0 1-1.406.913l-3.111-1.382A2 2 0 0 1 9.5 13H2a2 2 0 0 1-2-2V5zm11.5 5.175 3.5 1.556V4.269l-3.5 1.556v4.35zM2 4a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h7.5a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H2z"/></svg>; case 'camera': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M324.3 64c3.3 0 6.3 2.1 7.5 5.2l22.1 58.8H464c8.8 0 16 7.2 16 16v288c0 8.8-7.2 16-16 16H48c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16h110.2l20.1-53.6c2.3-6.2 8.3-10.4 15-10.4h131m0-32h-131c-20 0-37.9 12.4-44.9 31.1L136 96H48c-26.5 0-48 21.5-48 48v288c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V144c0-26.5-21.5-48-48-48h-88l-14.3-38c-5.8-15.7-20.7-26-37.4-26zM256 408c-66.2 0-120-53.8-120-120s53.8-120 120-120 120 53.8 120 120-53.8 120-120 120zm0-208c-48.5 0-88 39.5-88 88s39.5 88 88 88 88-39.5 88-88-39.5-88-88-88z"/></svg>; case 'card-checklist': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/><path d="M7 5.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm-1.496-.854a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0zM7 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm-1.496-.854a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0z"/></svg>; + case 'card-text': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/><path d="M3 5.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3 8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 8zm0 2.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5z"/></svg>; case 'caret-down-fill': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/></svg>; case 'caret-left-fill': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="m3.86 8.753 5.482 4.796c.646.566 1.658.106 1.658-.753V3.204a1 1 0 0 0-1.659-.753l-5.48 4.796a1 1 0 0 0 0 1.506z"/></svg>; case 'caret-right-fill': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/></svg>; diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index af54c35ab..662dc1294 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -120,9 +120,9 @@ export const issueOptions = [ export const issueCategories = [ { label: 'Resources', value: IssueCategory.RESOURCES }, - { label: 'Network', value: IssueCategory.NETWORK }, - { label: 'Rage', value: IssueCategory.RAGE }, - { label: 'Errors', value: IssueCategory.ERRORS }, + { label: 'Network Request', value: IssueCategory.NETWORK }, + { label: 'Click Rage', value: IssueCategory.RAGE }, + { label: 'JS Errors', value: IssueCategory.ERRORS }, ] export const issueCategoriesMap = issueCategories.reduce((acc, {value, label}) => { diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 8632d639c..b5d1c2ba7 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -9,7 +9,7 @@ import { getRE } from 'App/utils'; interface DashboardFilter { query?: string; - visibility?: string; + showMine?: boolean; } export default class DashboardStore { siteId: any = null; @@ -27,7 +27,7 @@ export default class DashboardStore { endTimestamp: number = 0; pendingRequests: number = 0; - filter: DashboardFilter = { visibility: 'all', query: '' }; + filter: DashboardFilter = { showMine: false, query: '' }; // Metrics metricsPage: number = 1; @@ -70,8 +70,7 @@ export default class DashboardStore { return this.dashboards .filter( (dashboard) => - (this.filter.visibility === 'all' || - (this.filter.visibility === 'team' ? dashboard.isPublic : !dashboard.isPublic)) && + (this.filter.showMine ? !dashboard.isPublic : dashboard.isPublic) && (!filterRE || // @ts-ignore ['name', 'owner', 'description'].some((key) => filterRE.test(dashboard[key]))) @@ -394,7 +393,7 @@ export default class DashboardStore { } toggleAlertModal(val: boolean) { - this.showAlertModal = val + this.showAlertModal = val; } fetchMetricChartData( diff --git a/frontend/app/svg/icons/card-text.svg b/frontend/app/svg/icons/card-text.svg new file mode 100644 index 000000000..5ec2a474e --- /dev/null +++ b/frontend/app/svg/icons/card-text.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-card-text" viewBox="0 0 16 16"> + <path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/> + <path d="M3 5.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3 8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 8zm0 2.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5z"/> +</svg> \ No newline at end of file