From fbbd69732e4f8d9b047e2254f4c5a903eabf4d66 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 20 Jul 2022 18:16:02 +0200 Subject: [PATCH 001/168] feat(ui) - timeline overview - wip --- .../Session_/BottomBlock/BottomBlock.js | 6 +- .../Session_/OverviewPanel/OverviewPanel.tsx | 116 ++++++++++++++++++ .../components/EventRow/EventRow.tsx | 39 ++++++ .../components/EventRow/index.ts | 1 + .../Session_/OverviewPanel/index.ts | 1 + .../OverviewPanel/overviewPanel.module.css | 13 ++ .../Session_/Player/Controls/Controls.js | 12 ++ .../app/components/Session_/Player/Player.js | 5 + .../app/components/Session_/PlayerBlock.js | 7 +- frontend/app/duck/components/player.js | 1 + frontend/app/utils.ts | 13 +- 11 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx create mode 100644 frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx create mode 100644 frontend/app/components/Session_/OverviewPanel/components/EventRow/index.ts create mode 100644 frontend/app/components/Session_/OverviewPanel/index.ts create mode 100644 frontend/app/components/Session_/OverviewPanel/overviewPanel.module.css diff --git a/frontend/app/components/Session_/BottomBlock/BottomBlock.js b/frontend/app/components/Session_/BottomBlock/BottomBlock.js index 39983c0c1..069757e60 100644 --- a/frontend/app/components/Session_/BottomBlock/BottomBlock.js +++ b/frontend/app/components/Session_/BottomBlock/BottomBlock.js @@ -3,9 +3,9 @@ import cn from 'classnames'; import stl from './bottomBlock.module.css'; const BottomBlock = ({ - children, - className, - additionalHeight, + children = null, + className = '', + additionalHeight = 0, ...props }) => (
diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx new file mode 100644 index 000000000..d634643b0 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -0,0 +1,116 @@ +import { connectPlayer } from 'App/player'; +import React from 'react'; +import BottomBlock from '../BottomBlock'; +import EventRow from './components/EventRow'; +import { TYPES } from 'Types/session/event'; +import { Icon } from 'UI'; +import { Tooltip } from 'react-tippy'; +import stl from './overviewPanel.module.css'; + +interface Props { + resourceList: any[]; + exceptionsList: any[]; + eventsList: any[]; + endTime: number; +} +function OverviewPanel(props: Props) { + const { resourceList, exceptionsList, eventsList, endTime } = props; + const clickRageList = React.useMemo(() => { + return eventsList.filter((item: any) => item.type === TYPES.CLICKRAGE); + }, [eventsList]); + + const containerRef = React.useRef(null); + const innerRef = React.createRef(); + const scale = 100 / endTime; + + let width = 100; + const SPEED = 5; + + const onWheel = (e: React.UIEvent) => { + e.preventDefault(); + e.stopPropagation(); + const delta = e.deltaY; + if (delta > 0) { + width += SPEED; + } else { + width -= SPEED; + } + if (width < 100) { + width = 100; + } + if (innerRef.current) { + innerRef.current.style.width = width + '%'; + if (containerRef.current) { + containerRef.current.style.left = (100 - width) / 2 + '%'; + } + } + }; + + const renderNetworkElement = (item: any) => { + return
; + }; + + const renderClickRageElement = (item: any) => { + return ( +
+ +
+ ); + }; + + const renderExceptionElement = (item: any) => { + // console.log('item', item); + return ( + + {'Exception'} +
+ {item.message} +
+ } + delay={0} + position="top" + > + + + ); + }; + + return ( + + +
+ Overview +
+
+ +
+
+ + +
+ +
+ +
+
+
+
+ ); +} + +export default connectPlayer((state: any) => ({ + resourceList: state.resourceList, + exceptionsList: state.exceptionsList, + eventsList: state.eventList, + endTime: state.endTime, +}))(OverviewPanel); + +const VerticalPointerLine = connectPlayer((state: any) => ({ + time: state.time, + scale: 100 / state.endTime, +}))(({ time, scale }: any) => { + const left = time * scale; + return
; +}); diff --git a/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx b/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx new file mode 100644 index 000000000..b1fec7cec --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import cn from 'classnames' +import { getTimelinePosition } from 'App/utils'; + +interface Props { + list?: any[]; + scale?: number; + title: string; + className?: string; + renderElement?: (item: any) => React.ReactNode; +} +function EventRow(props: Props) { + const { title, className, list = [], scale = 0 } = props; + const _list = React.useMemo(() => { + return list.map((item: any, _index: number) => { + return { + ...item.toJS(), + left: getTimelinePosition(item.time, scale), + } + }) + }, [list]); + return ( +
+
{title}
+
+ {_list.map((item: any, index: number) => { + return ( +
+ {props.renderElement ? props.renderElement(item) : null} +
+ ) + } + )} +
+
+ ); +} + +export default EventRow; \ No newline at end of file diff --git a/frontend/app/components/Session_/OverviewPanel/components/EventRow/index.ts b/frontend/app/components/Session_/OverviewPanel/components/EventRow/index.ts new file mode 100644 index 000000000..ec0281d5a --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/EventRow/index.ts @@ -0,0 +1 @@ +export { default } from './EventRow'; \ No newline at end of file diff --git a/frontend/app/components/Session_/OverviewPanel/index.ts b/frontend/app/components/Session_/OverviewPanel/index.ts new file mode 100644 index 000000000..328795cd7 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/index.ts @@ -0,0 +1 @@ +export { default } from './OverviewPanel'; \ No newline at end of file diff --git a/frontend/app/components/Session_/OverviewPanel/overviewPanel.module.css b/frontend/app/components/Session_/OverviewPanel/overviewPanel.module.css new file mode 100644 index 000000000..979eebb13 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/overviewPanel.module.css @@ -0,0 +1,13 @@ +.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; + } +} diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js index e92099393..0aa33283d 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ b/frontend/app/components/Session_/Player/Controls/Controls.js @@ -15,6 +15,7 @@ import { fullscreenOn, fullscreenOff, toggleBottomBlock, + OVERVIEW, CONSOLE, NETWORK, STACKEVENTS, @@ -377,6 +378,17 @@ export default class Controls extends React.Component { containerClassName="mx-2" /> )} */} + toggleBottomTools(OVERVIEW) } + active={ bottomBlock === OVERVIEW && !inspectorMode} + label="OVERVIEW" + noIcon + labelClassName="!text-base font-semibold" + // count={ logCount } + // hasErrors={ logRedCount > 0 } + containerClassName="mx-2" + /> toggleBottomTools(CONSOLE) } diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index 4b5006338..6d7089d77 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -18,6 +18,7 @@ import { EXCEPTIONS, LONGTASKS, INSPECTOR, + OVERVIEW, } from 'Duck/components/player'; import Network from '../Network'; import Console from '../Console/Console'; @@ -40,6 +41,7 @@ import Controls from './Controls'; import Overlay from './Overlay'; import stl from './player.module.css'; import { updateLastPlayedSession } from 'Duck/sessions'; +import OverviewPanel from '../OverviewPanel'; @connectPlayer(state => ({ live: state.live, @@ -104,6 +106,9 @@ export default class Player extends React.PureComponent {
{ !fullscreen && !!bottomBlock &&
+ { //bottomBlock === OVERVIEW && + + } { bottomBlock === CONSOLE && } diff --git a/frontend/app/components/Session_/PlayerBlock.js b/frontend/app/components/Session_/PlayerBlock.js index e6bcbaa33..487809649 100644 --- a/frontend/app/components/Session_/PlayerBlock.js +++ b/frontend/app/components/Session_/PlayerBlock.js @@ -3,7 +3,7 @@ import cn from "classnames"; import { connect } from 'react-redux'; import { } from 'Player'; import { - NONE, + NONE, OVERVIEW, } from 'Duck/components/player'; import Player from './Player'; import SubHeader from './Subheader'; @@ -37,8 +37,9 @@ export default class PlayerBlock extends React.PureComponent { />} diff --git a/frontend/app/duck/components/player.js b/frontend/app/duck/components/player.js index 1a34bcd95..cf6263bf4 100644 --- a/frontend/app/duck/components/player.js +++ b/frontend/app/duck/components/player.js @@ -12,6 +12,7 @@ export const FETCH = 8; export const EXCEPTIONS = 9; export const LONGTASKS = 10; export const INSPECTOR = 11; +export const OVERVIEW = 12; const TOGGLE_FULLSCREEN = 'player/TOGGLE_FS'; const TOGGLE_BOTTOM_BLOCK = 'player/SET_BOTTOM_BLOCK'; diff --git a/frontend/app/utils.ts b/frontend/app/utils.ts index 9765d69c3..53d7375ad 100644 --- a/frontend/app/utils.ts +++ b/frontend/app/utils.ts @@ -324,8 +324,12 @@ export const fetchErrorCheck = async (response: any) => { export const cleanSessionFilters = (data: any) => { const { filters, ...rest } = data; const _fitlers = filters.filter((f: any) => { - if (f.operator === 'isAny' || f.operator === 'onAny') { return true } // ignore filter with isAny/onAny operator - if (Array.isArray(f.filters) && f.filters.length > 0) { return true } // ignore subfilters + if (f.operator === 'isAny' || f.operator === 'onAny') { + return true; + } // ignore filter with isAny/onAny operator + if (Array.isArray(f.filters) && f.filters.length > 0) { + return true; + } // ignore subfilters return f.value !== '' && Array.isArray(f.value) && f.value.length > 0; }); @@ -343,3 +347,8 @@ export const setSessionFilter = (filter: any) => { export const compareJsonObjects = (obj1: any, obj2: any) => { return JSON.stringify(obj1) === JSON.stringify(obj2); }; + +export function getTimelinePosition(value: any, scale: any) { + const pos = value * scale; + return pos > 100 ? 100 : pos; +} From b368dc3adf30b08714ae3d216ac600fe7cdb6385 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Thu, 21 Jul 2022 12:06:40 +0200 Subject: [PATCH 002/168] TODO: revert in 1.8.0 force run http as root, as v1.7.0 http listens in 80 Signed-off-by: rjshrjndrn --- scripts/helmcharts/openreplay/values.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/helmcharts/openreplay/values.yaml b/scripts/helmcharts/openreplay/values.yaml index 65b67c6bc..1652dedab 100644 --- a/scripts/helmcharts/openreplay/values.yaml +++ b/scripts/helmcharts/openreplay/values.yaml @@ -92,6 +92,13 @@ nginx-ingress: # Overrides the image tag whose default is the chart appVersion. tag: "buster" +# By default http listens to 80 port, and for v1.7.0 http listens in 80 +http: + podSecurityContext: + runAsUser: 0 + runAsGroup: 0 + fsGroup: 0 + fsGroupChangePolicy: "OnRootMismatch" # Running sink and storage as non root users, because of existing volume permission change will take time sink: podSecurityContext: From a4f0b323c50394cbca741ac6fb944a81098b95a5 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 21 Jul 2022 15:35:01 +0200 Subject: [PATCH 003/168] fix(ui) - click maps count and percentage, dropdown --- .../PageInsightsPanel/PageInsightsPanel.tsx | 178 +++++++++--------- .../components/SelectorCard/SelectorCard.tsx | 44 +++-- .../SelectorsList/SelectorsList.tsx | 41 ++-- .../StatedScreen/StatedScreen.ts | 4 +- 4 files changed, 133 insertions(+), 134 deletions(-) diff --git a/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx b/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx index cec3e1af2..03d74a247 100644 --- a/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx +++ b/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Dropdown, Loader, Icon } from 'UI'; -import DateRange from 'Shared/DateRange'; +import { Loader, Icon } from 'UI'; import { connect } from 'react-redux'; import { fetchInsights } from 'Duck/sessions'; import SelectorsList from './components/SelectorsList/SelectorsList'; @@ -11,100 +10,103 @@ import Period from 'Types/app/period'; const JUMP_OFFSET = 1000; interface Props { - filters: any - fetchInsights: (filters: Record) => void - urls: [] - insights: any - events: Array - urlOptions: Array - loading: boolean - host: string - setActiveTab: (tab: string) => void + filters: any; + fetchInsights: (filters: Record) => void; + urls: []; + insights: any; + events: Array; + urlOptions: Array; + loading: boolean; + host: string; + setActiveTab: (tab: string) => void; } -function PageInsightsPanel({ - filters, fetchInsights, events = [], insights, urlOptions, host, loading = true, setActiveTab -}: Props) { - const [insightsFilters, setInsightsFilters] = useState(filters) - const defaultValue = (urlOptions && urlOptions[0]) ? urlOptions[0].value : '' +function PageInsightsPanel({ filters, fetchInsights, events = [], insights, urlOptions, host, loading = true, setActiveTab }: Props) { + const [insightsFilters, setInsightsFilters] = useState(filters); + const defaultValue = urlOptions && urlOptions[0] ? urlOptions[0].value : ''; - const period = new Period({ - start: insightsFilters.startDate, - end: insightsFilters.endDate, - rangeName: insightsFilters.rangeValue - }); + const period = Period({ + start: insightsFilters.startDate, + end: insightsFilters.endDate, + rangeName: insightsFilters.rangeValue, + }); - const onDateChange = (e) => { - const { startDate, endDate, rangeValue } = e.toJSON(); - setInsightsFilters({ ...insightsFilters, startDate, endDate, rangeValue }) - } + const onDateChange = (e: any) => { + const { startDate, endDate, rangeValue } = e.toJSON(); + setInsightsFilters({ ...insightsFilters, startDate, endDate, rangeValue }); + }; - useEffect(() => { - markTargets(insights.toJS()); - return () => { - markTargets(null) - } - }, [insights]) + useEffect(() => { + markTargets(insights.toJS()); + return () => { + markTargets(null); + }; + }, [insights]); - useEffect(() => { - if (urlOptions && urlOptions[0]) { - const url = insightsFilters.url ? insightsFilters.url : host + urlOptions[0].value; - Player.pause(); - fetchInsights({ ...insightsFilters, url }) - } - }, [insightsFilters]) + useEffect(() => { + if (urlOptions && urlOptions[0]) { + const url = insightsFilters.url ? insightsFilters.url : host + urlOptions[0].value; + Player.pause(); + fetchInsights({ ...insightsFilters, url }); + } + }, [insightsFilters]); - const onPageSelect = ({ value }: { value: Array }) => { - const event = events.find(item => item.url === value) - Player.jump(event.time + JUMP_OFFSET) - setInsightsFilters({ ...insightsFilters, url: host + value }) - markTargets([]) - }; + const onPageSelect = ({ value }: any) => { + const event = events.find((item) => item.url === value.value); + Player.jump(event.time + JUMP_OFFSET); + setInsightsFilters({ ...insightsFilters, url: host + value.value }); + markTargets([]); + }; - return ( -
-
-
- Clicks - + return ( +
+
+
+ Clicks + +
+
{ + setActiveTab(''); + }} + className="ml-auto flex items-center justify-center bg-white cursor-pointer" + > + +
+
+
+
In Page
+ -
- - - -
- ) + ); } -export default connect(state => { - const events = state.getIn([ 'sessions', 'visitedEvents' ]) - return { - filters: state.getIn(['sessions', 'insightFilters']), - host: state.getIn([ 'sessions', 'host' ]), - insights: state.getIn([ 'sessions', 'insights' ]), - events: events, - urlOptions: events.map(({ url, host }: any) => ({ label: url, value: url, host })), - loading: state.getIn([ 'sessions', 'fetchInsightsRequest', 'loading' ]), - } -}, { fetchInsights })(PageInsightsPanel); +export default connect( + (state) => { + const events = state.getIn(['sessions', 'visitedEvents']); + return { + filters: state.getIn(['sessions', 'insightFilters']), + host: state.getIn(['sessions', 'host']), + insights: state.getIn(['sessions', 'insights']), + events: events, + urlOptions: events.map(({ url, host }: any) => ({ label: url, value: url, host })), + loading: state.getIn(['sessions', 'fetchInsightsRequest', 'loading']), + }; + }, + { fetchInsights } +)(PageInsightsPanel); diff --git a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx index 9007b6684..9a3ecc210 100644 --- a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx +++ b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx @@ -1,30 +1,34 @@ -import React, { useState } from 'react' -import stl from './SelectorCard.module.css' +import React, { useState } from 'react'; +import stl from './SelectorCard.module.css'; import cn from 'classnames'; import type { MarkedTarget } from 'Player/MessageDistributor/StatedScreen/StatedScreen'; import { activeTarget } from 'Player'; import { Tooltip } from 'react-tippy'; interface Props { - index?: number, - target: MarkedTarget, - showContent: boolean + index?: number; + target: MarkedTarget; + showContent: boolean; } -export default function SelectorCard({ index = 1, target, showContent } : Props) { - return ( -
activeTarget(index)}> -
- {/* @ts-ignore */} -
{index + 1}
-
{target.selector}
-
- { showContent && ( -
-
{target.count} Clicks - {target.percent}%
-
TOTAL CLICKS
+export default function SelectorCard({ index = 1, target, showContent }: Props) { + return ( +
activeTarget(index)}> +
+ {/* @ts-ignore */} + +
{index + 1}
+
+
{target.selector}
+
+ {showContent && ( +
+
+ {target.count} Clicks - {target.percent}% +
+
TOTAL CLICKS
+
+ )}
- ) } -
- ) + ); } diff --git a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorsList/SelectorsList.tsx b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorsList/SelectorsList.tsx index aceefb3b7..86274baba 100644 --- a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorsList/SelectorsList.tsx +++ b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorsList/SelectorsList.tsx @@ -1,33 +1,26 @@ -import React, { useState } from 'react' -import { NoContent } from 'UI' +import React from 'react'; +import { NoContent } from 'UI'; import { connectPlayer } from 'Player/store'; import SelectorCard from '../SelectorCard/SelectorCard'; import type { MarkedTarget } from 'Player/MessageDistributor/StatedScreen/StatedScreen'; -import stl from './selectorList.module.css' +import stl from './selectorList.module.css'; interface Props { - targets: Array, - activeTargetIndex: number + targets: Array; + activeTargetIndex: number; } -function SelectorsList({ targets, activeTargetIndex }: Props) { - return ( - -
- { targets && targets.map((target, index) => ( - - ))} -
-
- ) +function SelectorsList({ targets, activeTargetIndex }: Props) { + return ( + +
+ {targets && targets.map((target, index) => )} +
+
+ ); } - -export default connectPlayer(state => ({ - targets: state.markedTargets, - activeTargetIndex: state.activeTargetIndex, -}))(SelectorsList) +export default connectPlayer((state: any) => ({ + targets: state.markedTargets, + activeTargetIndex: state.activeTargetIndex, +}))(SelectorsList); diff --git a/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts b/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts index 5b74d9a3f..177419f35 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts +++ b/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts @@ -145,9 +145,9 @@ export default class StatedScreen extends Screen { ...s, el, index: index++, - percent: 0, + percent: Math.round((s.count * 100) / totalCount), boundingRect: this.calculateRelativeBoundingRect(el), - count: Math.round((s.count * 100) / totalCount) + count: s.count, }) }); From 26100b81e67e7e2babfdeadad8e592ef03d8d690 Mon Sep 17 00:00:00 2001 From: sylenien Date: Thu, 21 Jul 2022 16:12:05 +0200 Subject: [PATCH 004/168] fix(ui): fix clickmap card items selectors --- .../MessageDistributor/StatedScreen/Screen/BaseScreen.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts b/frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts index fc52660f5..81ced7774 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts +++ b/frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts @@ -138,7 +138,8 @@ export default abstract class BaseScreen { getElementBySelector(selector: string): Element | null { if (!selector) return null; try { - return this.document?.querySelector(selector) || null; + const safeSelector = selector.replace(/:/g, '\\\\3A ').replace(/\//g, '\\/'); + return this.document?.querySelector(safeSelector) || null; } catch (e) { console.error("Can not select element. ", e) return null @@ -186,4 +187,4 @@ export default abstract class BaseScreen { clean() { window.removeEventListener('resize', this.scale); } -} \ No newline at end of file +} From 00a3c52e8db30c5d3795609c4f8a1a2b3744f58b Mon Sep 17 00:00:00 2001 From: Delirium Date: Thu, 21 Jul 2022 17:28:28 +0300 Subject: [PATCH 005/168] fix(ui): fix timezone settings (#629) --- .../SessionList/SessionListHeader.js | 26 ++++++++++++++++--- .../SelectDateRange/SelectDateRange.tsx | 17 +++++++----- .../components/shared/SessionItem/Counter.tsx | 4 +-- frontend/app/types/app/period.js | 11 +++++++- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/frontend/app/components/BugFinder/SessionList/SessionListHeader.js b/frontend/app/components/BugFinder/SessionList/SessionListHeader.js index bf4bf8b55..5e2702639 100644 --- a/frontend/app/components/BugFinder/SessionList/SessionListHeader.js +++ b/frontend/app/components/BugFinder/SessionList/SessionListHeader.js @@ -4,7 +4,10 @@ import SortDropdown from '../Filters/SortDropdown'; import { numberWithCommas } from 'App/utils'; import SelectDateRange from 'Shared/SelectDateRange'; import { applyFilter } from 'Duck/search'; -import Period from 'Types/app/period'; +import Record from 'Types/app/period'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; +import { moment } from 'App/dateRange'; const sortOptionsMap = { 'startTs-desc': 'Newest', @@ -15,13 +18,30 @@ const sortOptionsMap = { const sortOptions = Object.entries(sortOptionsMap).map(([value, label]) => ({ value, label })); function SessionListHeader({ activeTab, count, applyFilter, filter }) { + const { settingsStore } = useStore(); + + const label = useObserver(() => settingsStore.sessionSettings.timezone.label); + const getTimeZoneOffset = React.useCallback(() => { + return label.slice(-6); + }, [label]); + const { startDate, endDate, rangeValue } = filter; - const period = new Period({ start: startDate, end: endDate, rangeName: rangeValue }); + const period = new Record({ start: startDate, end: endDate, rangeName: rangeValue }); const onDateChange = (e) => { const dateValues = e.toJSON(); + dateValues.startDate = moment(dateValues.startDate).utcOffset(getTimeZoneOffset(), true).valueOf(); + dateValues.endDate = moment(dateValues.endDate).utcOffset(getTimeZoneOffset(), true).valueOf(); applyFilter(dateValues); }; + + React.useEffect(() => { + const dateValues = period.toJSON(); + dateValues.startDate = moment(dateValues.startDate).startOf('day').utcOffset(getTimeZoneOffset(), true).valueOf(); + dateValues.endDate = moment(dateValues.endDate).endOf('day').utcOffset(getTimeZoneOffset(), true).valueOf(); + applyFilter(dateValues); + }, [label]); + return (
@@ -32,7 +52,7 @@ function SessionListHeader({ activeTab, count, applyFilter, filter }) { {
Sessions Captured in - +
}
diff --git a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx index 12b6ba016..da8a940a5 100644 --- a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx +++ b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx @@ -6,17 +6,19 @@ import { components } from 'react-select'; import DateRangePopup from 'Shared/DateRangeDropdown/DateRangePopup'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; import cn from 'classnames'; +import { observer } from 'mobx-react-lite'; interface Props { period: any; onChange: (data: any) => void; disableCustom?: boolean; right?: boolean; + timezone?: string; [x: string]: any; } function SelectDateRange(props: Props) { const [isCustom, setIsCustom] = React.useState(false); - const { right = false, period, disableCustom = false, ...rest } = props; + const { right = false, period, disableCustom = false, timezone, ...rest } = props; let selectedValue = DATE_RANGE_OPTIONS.find((obj: any) => obj.value === period.rangeName); const options = DATE_RANGE_OPTIONS.filter((obj: any) => (disableCustom ? obj.value !== CUSTOM_RANGE : true)); @@ -24,15 +26,20 @@ function SelectDateRange(props: Props) { if (value === CUSTOM_RANGE) { setIsCustom(true); } else { + // @ts-ignore props.onChange(new Period({ rangeName: value })); } }; const onApplyDateRange = (value: any) => { - props.onChange(new Period({ rangeName: CUSTOM_RANGE, start: value.start, end: value.end })); + // @ts-ignore + const range = new Period({ rangeName: CUSTOM_RANGE, start: value.start, end: value.end }) + props.onChange(range); setIsCustom(false); }; + const isCustomRange = period.rangeName === CUSTOM_RANGE; + const customRange = isCustomRange ? period.rangeFormatted(undefined, timezone) : ''; return (
-
- - Upload Source Maps - and see source code context obtained from stack traces in their original form. - - } - /> +
+
+ +
+ + Upload Source Maps + and see source code context obtained from stack traces in their original form. + + } + /> +
diff --git a/frontend/app/components/Session_/Fetch/Fetch.js b/frontend/app/components/Session_/Fetch/Fetch.js index 2dff482c9..1b62d88c8 100644 --- a/frontend/app/components/Session_/Fetch/Fetch.js +++ b/frontend/app/components/Session_/Fetch/Fetch.js @@ -116,7 +116,7 @@ export default class Fetch extends React.PureComponent { /> -

Fetch

+ Fetch
-

GraphQL

+ GraphQL
-

Long Tasks

+ Long Tasks
(); const scale = 100 / endTime; + const createEventClickHandler = (pointer: any) => (e: any) => { + console.log('here...'); + e.stopPropagation(); + Controls.jump(pointer.time); + // props.setTimelinePointer(pointer); + }; + let width = 100; const SPEED = 5; - - const onWheel = (e: React.UIEvent) => { + const onWheel = (e: any) => { e.preventDefault(); e.stopPropagation(); - const delta = e.deltaY; - if (delta > 0) { - width += SPEED; - } else { - width -= SPEED; + // console.log('e', e) + + // horizontal + if (e.deltaX != '-0') { + // e.preventDefault(); + console.log('e.deltaX', e.deltaX); } - if (width < 100) { - width = 100; - } - if (innerRef.current) { - innerRef.current.style.width = width + '%'; - if (containerRef.current) { - containerRef.current.style.left = (100 - width) / 2 + '%'; + // Vertical + if (e.deltaY != '-0') { + console.log('e.deltaY', e.deltaY); + // e.preventDefault(); + const delta = e.deltaY; + if (delta > 0) { + width += SPEED; + } else { + width -= SPEED; + } + if (width < 100) { + width = 100; + } + + if (innerRef.current) { + innerRef.current.style.width = width + '%'; + if (containerRef.current) { + containerRef.current.style.left = (100 - width) / 2 + '%'; + } } } }; + useEffect(() => { + if (containerRef.current) { + containerRef.current.addEventListener('wheel', onWheel, { passive: false }); + } + + return () => { + if (containerRef.current) { + containerRef.current.removeEventListener('wheel', onWheel); + } + }; + }, []); + const renderNetworkElement = (item: any) => { - return
; + return ( + + {item.success ? 'Slow resource: ' : 'Missing resource:'} +
+ {item.name} +
+ } + delay={0} + position="top" + > +
+
+
+ + ); }; const renderClickRageElement = (item: any) => { return ( -
- -
+ + {'Click Rage'} +
+ } + delay={0} + position="top" + > +
+ +
+ ); }; const renderExceptionElement = (item: any) => { - // console.log('item', item); return ( - +
+ +
); }; return ( - + -
- Overview -
+ Overview
@@ -101,7 +157,7 @@ function OverviewPanel(props: Props) { } export default connectPlayer((state: any) => ({ - resourceList: state.resourceList, + resourceList: state.resourceList.filter((r: any) => r.isRed() || r.isYellow()), exceptionsList: state.exceptionsList, eventsList: state.eventList, endTime: state.endTime, @@ -112,5 +168,5 @@ const VerticalPointerLine = connectPlayer((state: any) => ({ scale: 100 / state.endTime, }))(({ time, scale }: any) => { const left = time * scale; - return
; + return
; }); diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index 6d7089d77..9a95121ac 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -106,7 +106,7 @@ export default class Player extends React.PureComponent {
{ !fullscreen && !!bottomBlock &&
- { //bottomBlock === OVERVIEW && + { bottomBlock === OVERVIEW && } { bottomBlock === CONSOLE && diff --git a/frontend/app/components/Session_/PlayerBlock.js b/frontend/app/components/Session_/PlayerBlock.js index 487809649..1000a63a3 100644 --- a/frontend/app/components/Session_/PlayerBlock.js +++ b/frontend/app/components/Session_/PlayerBlock.js @@ -37,9 +37,9 @@ export default class PlayerBlock extends React.PureComponent { />} diff --git a/frontend/app/components/Session_/Profiler/Profiler.js b/frontend/app/components/Session_/Profiler/Profiler.js index 83b13c89c..9f8fdc284 100644 --- a/frontend/app/components/Session_/Profiler/Profiler.js +++ b/frontend/app/components/Session_/Profiler/Profiler.js @@ -42,7 +42,9 @@ export default class Profiler extends React.PureComponent { /> -

Profiler

+
+ Profiler +
Date: Mon, 25 Jul 2022 13:50:18 +0200 Subject: [PATCH 007/168] feat(ui) - timeline overview - show panels on click --- .../Session_/OverviewPanel/OverviewPanel.tsx | 94 ++++++------------- 1 file changed, 27 insertions(+), 67 deletions(-) diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index 844d31488..87dc542d6 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -1,82 +1,38 @@ import { connectPlayer, Controls } from 'App/player'; -import React, { useEffect } from 'react'; +import { toggleBottomBlock, NETWORK, EXCEPTIONS } from 'Duck/components/player'; +import React from 'react'; import BottomBlock from '../BottomBlock'; import EventRow from './components/EventRow'; import { TYPES } from 'Types/session/event'; import { Icon } from 'UI'; import { Tooltip } from 'react-tippy'; import stl from './overviewPanel.module.css'; +import { connect } from 'react-redux'; interface Props { resourceList: any[]; exceptionsList: any[]; eventsList: any[]; endTime: number; + toggleBottomBlock: any; } function OverviewPanel(props: Props) { const { resourceList, exceptionsList, eventsList, endTime } = props; const clickRageList = React.useMemo(() => { return eventsList.filter((item: any) => item.type === TYPES.CLICKRAGE); }, [eventsList]); - - const containerRef = React.useRef(null); - const innerRef = React.createRef(); const scale = 100 / endTime; - const createEventClickHandler = (pointer: any) => (e: any) => { - console.log('here...'); + const createEventClickHandler = (pointer: any, type: any) => (e: any) => { e.stopPropagation(); Controls.jump(pointer.time); - // props.setTimelinePointer(pointer); + if (!type) { + return; + } + + props.toggleBottomBlock(type); }; - let width = 100; - const SPEED = 5; - const onWheel = (e: any) => { - e.preventDefault(); - e.stopPropagation(); - // console.log('e', e) - - // horizontal - if (e.deltaX != '-0') { - // e.preventDefault(); - console.log('e.deltaX', e.deltaX); - } - // Vertical - if (e.deltaY != '-0') { - console.log('e.deltaY', e.deltaY); - // e.preventDefault(); - const delta = e.deltaY; - if (delta > 0) { - width += SPEED; - } else { - width -= SPEED; - } - if (width < 100) { - width = 100; - } - - if (innerRef.current) { - innerRef.current.style.width = width + '%'; - if (containerRef.current) { - containerRef.current.style.left = (100 - width) / 2 + '%'; - } - } - } - }; - - useEffect(() => { - if (containerRef.current) { - containerRef.current.addEventListener('wheel', onWheel, { passive: false }); - } - - return () => { - if (containerRef.current) { - containerRef.current.removeEventListener('wheel', onWheel); - } - }; - }, []); - const renderNetworkElement = (item: any) => { return ( -
-
+
+
); @@ -108,8 +64,8 @@ function OverviewPanel(props: Props) { delay={0} position="top" > -
- +
+
); @@ -128,7 +84,7 @@ function OverviewPanel(props: Props) { delay={0} position="top" > -
+
@@ -141,8 +97,8 @@ function OverviewPanel(props: Props) { Overview -
-
+
+
@@ -156,12 +112,16 @@ function OverviewPanel(props: Props) { ); } -export default connectPlayer((state: any) => ({ - resourceList: state.resourceList.filter((r: any) => r.isRed() || r.isYellow()), - exceptionsList: state.exceptionsList, - eventsList: state.eventList, - endTime: state.endTime, -}))(OverviewPanel); +export default connect(null, { + toggleBottomBlock, +})( + connectPlayer((state: any) => ({ + resourceList: state.resourceList.filter((r: any) => r.isRed() || r.isYellow()), + exceptionsList: state.exceptionsList, + eventsList: state.eventList, + endTime: state.endTime, + }))(OverviewPanel) +); const VerticalPointerLine = connectPlayer((state: any) => ({ time: state.time, From 7b9cc0fca65bff717a10ac07f542c17435a68887 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 26 Jul 2022 17:05:55 +0200 Subject: [PATCH 008/168] fix(tracker): fix assist typings --- tracker/tracker-assist/src/Assist.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tracker/tracker-assist/src/Assist.ts b/tracker/tracker-assist/src/Assist.ts index a36c307db..44b90c848 100644 --- a/tracker/tracker-assist/src/Assist.ts +++ b/tracker/tracker-assist/src/Assist.ts @@ -14,7 +14,7 @@ import type { Options as ConfirmOptions } from './ConfirmWindow/defaults.js'; // TODO: fully specified strict check (everywhere) -type StartEndCallback = () => ((()=>{}) | void) +type StartEndCallback = () => ((() => void) | void) export interface Options { onAgentConnect: StartEndCallback, @@ -39,8 +39,8 @@ enum CallingState { }; -// TODO typing???? -type OptionalCallback = (()=>{}) | void +// TODO typing???? () => ((() => void) | void) +type OptionalCallback = (() => void) | void type Agent = { onDisconnect?: OptionalCallback, onControlReleased?: OptionalCallback, From 819aefb86efbf8f721f2a4a0cf86d01f289aa025 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 26 Jul 2022 17:06:48 +0200 Subject: [PATCH 009/168] fix(tracker): fix assist typings --- tracker/tracker-assist/src/Assist.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracker/tracker-assist/src/Assist.ts b/tracker/tracker-assist/src/Assist.ts index 44b90c848..43f342481 100644 --- a/tracker/tracker-assist/src/Assist.ts +++ b/tracker/tracker-assist/src/Assist.ts @@ -39,7 +39,7 @@ enum CallingState { }; -// TODO typing???? () => ((() => void) | void) +// TODO typing???? type OptionalCallback = (() => void) | void type Agent = { onDisconnect?: OptionalCallback, From 772261fef4ea9ea7b6f237b062dede3d54d670dc Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers <7943856+bschoenmaeckers@users.noreply.github.com> Date: Wed, 27 Jul 2022 18:40:45 +0200 Subject: [PATCH 010/168] fix(ui): Serve react index file for all page urls (#642) --- frontend/nginx.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 0138027f0..f5581606b 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -3,6 +3,9 @@ server { listen [::]:8080 default_server; root /var/www/openreplay; index index.html; + rewrite ^((?!.(js|css|png|svg|jpg|woff|woff2)).)*$ /index.html break; + proxy_intercept_errors on; # see frontend://nginx.org/en/docs/frontend/ngx_frontend_proxy_module.html#proxy_intercept_errors + error_page 404 =200 /index.html; location / { try_files $uri $uri/ =404; } From 976a7a5a036dbaf22596a5718b42d6fdc444f251 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Wed, 27 Jul 2022 18:48:58 +0200 Subject: [PATCH 011/168] feat(chalice): removed elasticsearch dependency --- api/chalicelib/core/log_tool_elasticsearch.py | 71 ++++++++++--------- api/requirements-alerts.txt | 1 - api/requirements.txt | 1 - ee/api/requirements-alerts.txt | 1 - ee/api/requirements-crons.txt | 1 - ee/api/requirements.txt | 1 - 6 files changed, 36 insertions(+), 40 deletions(-) diff --git a/api/chalicelib/core/log_tool_elasticsearch.py b/api/chalicelib/core/log_tool_elasticsearch.py index ba20636c4..90986f61d 100644 --- a/api/chalicelib/core/log_tool_elasticsearch.py +++ b/api/chalicelib/core/log_tool_elasticsearch.py @@ -1,7 +1,7 @@ # from elasticsearch import Elasticsearch, RequestsHttpConnection -from elasticsearch import Elasticsearch +# from elasticsearch import Elasticsearch from chalicelib.core import log_tools -import base64 +# import base64 import logging logging.getLogger('elasticsearch').level = logging.ERROR @@ -58,39 +58,40 @@ def add_edit(tenant_id, project_id, data): port=data["port"]) -def __get_es_client(host, port, api_key_id, api_key, use_ssl=False, timeout=15): - scheme = "http" if host.startswith("http") else "https" - host = host.replace("http://", "").replace("https://", "") - try: - args = { - "hosts": [{"host": host, "port": port, "scheme": scheme}], - "verify_certs": False, - # "ca_certs": False, - # "connection_class": RequestsHttpConnection, - "request_timeout": timeout, - "api_key": (api_key_id, api_key) - } - # if api_key_id is not None and len(api_key_id) > 0: - # # args["http_auth"] = (username, password) - # token = "ApiKey " + base64.b64encode(f"{api_key_id}:{api_key}".encode("utf-8")).decode("utf-8") - # args["headers"] = {"Authorization": token} - es = Elasticsearch( - **args - ) - r = es.ping() - if not r and not use_ssl: - return __get_es_client(host, port, api_key_id, api_key, use_ssl=True, timeout=timeout) - if not r: - return None - except Exception as err: - print("================exception connecting to ES host:") - print(err) - return None - return es +# def __get_es_client(host, port, api_key_id, api_key, use_ssl=False, timeout=15): +# scheme = "http" if host.startswith("http") else "https" +# host = host.replace("http://", "").replace("https://", "") +# try: +# args = { +# "hosts": [{"host": host, "port": port, "scheme": scheme}], +# "verify_certs": False, +# # "ca_certs": False, +# # "connection_class": RequestsHttpConnection, +# "request_timeout": timeout, +# "api_key": (api_key_id, api_key) +# } +# # if api_key_id is not None and len(api_key_id) > 0: +# # # args["http_auth"] = (username, password) +# # token = "ApiKey " + base64.b64encode(f"{api_key_id}:{api_key}".encode("utf-8")).decode("utf-8") +# # args["headers"] = {"Authorization": token} +# es = Elasticsearch( +# **args +# ) +# r = es.ping() +# if not r and not use_ssl: +# return __get_es_client(host, port, api_key_id, api_key, use_ssl=True, timeout=timeout) +# if not r: +# return None +# except Exception as err: +# print("================exception connecting to ES host:") +# print(err) +# return None +# return es def ping(tenant_id, host, port, apiKeyId, apiKey): - es = __get_es_client(host, port, apiKeyId, apiKey, timeout=3) - if es is None: - return {"state": False} - return {"state": es.ping()} + # es = __get_es_client(host, port, apiKeyId, apiKey, timeout=3) + # if es is None: + # return {"state": False} + # return {"state": es.ping()} + return {"state": True} diff --git a/api/requirements-alerts.txt b/api/requirements-alerts.txt index 81198b0f3..f70efdd13 100644 --- a/api/requirements-alerts.txt +++ b/api/requirements-alerts.txt @@ -3,7 +3,6 @@ urllib3==1.26.10 boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 -elasticsearch==8.3.1 jira==3.3.0 diff --git a/api/requirements.txt b/api/requirements.txt index 81198b0f3..f70efdd13 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -3,7 +3,6 @@ urllib3==1.26.10 boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 -elasticsearch==8.3.1 jira==3.3.0 diff --git a/ee/api/requirements-alerts.txt b/ee/api/requirements-alerts.txt index 66fa84713..07d31f369 100644 --- a/ee/api/requirements-alerts.txt +++ b/ee/api/requirements-alerts.txt @@ -3,7 +3,6 @@ urllib3==1.26.10 boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 -elasticsearch==8.3.1 jira==3.3.0 diff --git a/ee/api/requirements-crons.txt b/ee/api/requirements-crons.txt index 66fa84713..07d31f369 100644 --- a/ee/api/requirements-crons.txt +++ b/ee/api/requirements-crons.txt @@ -3,7 +3,6 @@ urllib3==1.26.10 boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 -elasticsearch==8.3.1 jira==3.3.0 diff --git a/ee/api/requirements.txt b/ee/api/requirements.txt index 5ce044904..2a9ae2e1a 100644 --- a/ee/api/requirements.txt +++ b/ee/api/requirements.txt @@ -3,7 +3,6 @@ urllib3==1.26.10 boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 -elasticsearch==8.3.1 jira==3.3.0 From 8cce90b8a7942f80998dd495cc37557bc8cdc8eb Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Thu, 28 Jul 2022 14:58:08 +0200 Subject: [PATCH 012/168] chore(helm): disable minio migration if s3 is enabled Signed-off-by: rjshrjndrn --- scripts/helmcharts/openreplay/templates/job.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/helmcharts/openreplay/templates/job.yaml b/scripts/helmcharts/openreplay/templates/job.yaml index 8925b83d8..c0d7f0a45 100644 --- a/scripts/helmcharts/openreplay/templates/job.yaml +++ b/scripts/helmcharts/openreplay/templates/job.yaml @@ -103,6 +103,7 @@ spec: mountPath: /opt/openreplay - name: dbmigrationscript mountPath: /opt/migrations/ + {{- if eq .Values.global.s3.endpoint "http://minio.db.svc.cluster.local:9000" }} - name: minio image: bitnami/minio:2020.10.9-debian-10-r6 env: @@ -128,6 +129,7 @@ spec: mountPath: /opt/openreplay - name: dbmigrationscript mountPath: /opt/migrations/ + {{- end}} {{- if .Values.global.enterpriseEditionLicense }} # Enterprise migration - name: clickhouse From f1e4d60ea8fcec43bd0ba762e628a90d7108e0e7 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 28 Jul 2022 16:34:44 +0200 Subject: [PATCH 013/168] DB improvements (#647) * feat(backend/db): updated ClickHouse library version from 1.5.4 to 2.2.0 * feat(backend/db): refactored ClickHouse connector * feat(backend/db): rewritten batch implementation for ClickHouse inserts * feat(backend/db): found and fix memory leak in db service --- backend/cmd/db/main.go | 11 +- backend/go.mod | 14 +- backend/go.sum | 52 +- ee/backend/internal/db/datasaver/stats.go | 2 +- ee/backend/pkg/db/clickhouse/bulk.go | 45 -- ee/backend/pkg/db/clickhouse/connector.go | 500 +++++++++++++++---- ee/backend/pkg/db/clickhouse/helpers.go | 34 -- ee/backend/pkg/db/clickhouse/messages-web.go | 243 --------- 8 files changed, 447 insertions(+), 454 deletions(-) delete mode 100644 ee/backend/pkg/db/clickhouse/bulk.go delete mode 100644 ee/backend/pkg/db/clickhouse/helpers.go delete mode 100644 ee/backend/pkg/db/clickhouse/messages-web.go diff --git a/backend/cmd/db/main.go b/backend/cmd/db/main.go index 2e962cb1b..1712b8a3f 100644 --- a/backend/cmd/db/main.go +++ b/backend/cmd/db/main.go @@ -122,11 +122,18 @@ func main() { os.Exit(0) case <-commitTick: // Send collected batches to db + start := time.Now() pg.CommitBatches() + pgDur := time.Now().Sub(start).Milliseconds() + + start = time.Now() if err := saver.CommitStats(); err != nil { log.Printf("Error on stats commit: %v", err) } - // TODO?: separate stats & regular messages + chDur := time.Now().Sub(start).Milliseconds() + log.Printf("commit duration(ms), pg: %d, ch: %d", pgDur, chDur) + + // TODO: use commit worker to save time each tick if err := consumer.Commit(); err != nil { log.Printf("Error on consumer commit: %v", err) } @@ -134,7 +141,7 @@ func main() { // Handle new message from queue err := consumer.ConsumeNext() if err != nil { - log.Fatalf("Error on consumption: %v", err) // TODO: is always fatal? + log.Fatalf("Error on consumption: %v", err) } } } diff --git a/backend/go.mod b/backend/go.mod index a15e23196..caaf1bf83 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -4,12 +4,12 @@ go 1.18 require ( cloud.google.com/go/logging v1.4.2 - github.com/ClickHouse/clickhouse-go v1.5.4 + github.com/ClickHouse/clickhouse-go/v2 v2.2.0 github.com/aws/aws-sdk-go v1.35.23 github.com/btcsuite/btcutil v1.0.2 github.com/elastic/go-elasticsearch/v7 v7.13.1 github.com/go-redis/redis v6.15.9+incompatible - github.com/google/uuid v1.1.2 + github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 github.com/jackc/pgconn v1.6.0 github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451 @@ -36,7 +36,6 @@ require ( cloud.google.com/go/storage v1.14.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect github.com/confluentinc/confluent-kafka-go v1.9.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -50,15 +49,19 @@ require ( github.com/jackc/pgproto3/v2 v2.0.2 // indirect github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8 // indirect github.com/jackc/pgtype v1.3.0 // indirect - github.com/jackc/puddle v1.1.0 // indirect + github.com/jackc/puddle v1.2.2-0.20220404125616-4e959849469a // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/klauspost/compress v1.11.9 // indirect + github.com/klauspost/compress v1.15.7 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/paulmach/orb v0.7.1 // indirect + github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/stretchr/testify v1.8.0 // indirect go.opencensus.io v0.23.0 // indirect go.opentelemetry.io/otel/sdk v1.7.0 // indirect go.opentelemetry.io/otel/trace v1.7.0 // indirect @@ -73,5 +76,4 @@ require ( google.golang.org/grpc v1.46.2 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index 433f2b895..6b76d1278 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -61,9 +61,11 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0= github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= +github.com/ClickHouse/clickhouse-go/v2 v2.2.0 h1:dj00TDKY+xwuTJdbpspCSmTLFyWzRJerTHwaBxut1C0= +github.com/ClickHouse/clickhouse-go/v2 v2.2.0/go.mod h1:8f2XZUi7XoeU+uPIytSi1cvx8fmJxi7vIgqpvYTF1+o= 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/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= @@ -79,7 +81,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk= github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= @@ -100,7 +101,6 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -151,6 +151,8 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -158,6 +160,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -230,8 +233,9 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe 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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -240,8 +244,10 @@ github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/Oth github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk= github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= 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/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= @@ -290,8 +296,9 @@ github.com/jackc/pgx/v4 v4.6.0 h1:Fh0O9GdlG4gYpjpwOqjdEodJUQM9jzN3Hdv7PN0xmm0= github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.0 h1:musOWczZC/rSbqut475Vfcczg7jJsdUQf0D6oKPLgNU= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +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/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -308,10 +315,11 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 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= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.11.9 h1:5OCMOdde1TCT2sookEuVeEZzA8bmRSFV3AwPDZAG8AA= -github.com/klauspost/compress v1.11.9/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.15.7 h1:7cgTQxJCU/vy+oP/E3B9RGbQTgbiVzIJWIKOLoAsPok= +github.com/klauspost/compress v1.15.7/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -339,6 +347,7 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -353,8 +362,12 @@ github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/oschwald/maxminddb-golang v1.7.0 h1:JmU4Q1WBv5Q+2KZy5xJI+98aUwTIrPPxZUkd5Cwr8Zc= github.com/oschwald/maxminddb-golang v1.7.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis= -github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= +github.com/paulmach/orb v0.7.1 h1:Zha++Z5OX/l168sqHK3k4z18LDvr+YAO/VjK0ReQ9rU= +github.com/paulmach/orb v0.7.1/go.mod h1:FWRlTgl88VI1RBx/MkrwWDRhQ96ctqMCh8boXhmqB/A= +github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= 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/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= @@ -393,8 +406,12 @@ github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThC 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= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= +github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -403,14 +420,19 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +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.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= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= +github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/ua-parser/uap-go v0.0.0-20200325213135-e1c09f13e2fe h1:aj/vX5epIlQQBEocKoM9nSAiNpakdQzElc8SaRFPu+I= @@ -420,6 +442,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -594,8 +617,10 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -649,6 +674,7 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -706,6 +732,7 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY 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= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -714,6 +741,7 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -927,8 +955,8 @@ 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 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= -gopkg.in/yaml.v3 v3.0.0/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= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ee/backend/internal/db/datasaver/stats.go b/ee/backend/internal/db/datasaver/stats.go index d5bd74f83..7fa2fb9d0 100644 --- a/ee/backend/internal/db/datasaver/stats.go +++ b/ee/backend/internal/db/datasaver/stats.go @@ -10,7 +10,7 @@ import ( . "openreplay/backend/pkg/messages" ) -var ch *clickhouse.Connector +var ch clickhouse.Connector var finalizeTicker <-chan time.Time func (si *Saver) InitStats() { diff --git a/ee/backend/pkg/db/clickhouse/bulk.go b/ee/backend/pkg/db/clickhouse/bulk.go deleted file mode 100644 index 121cdbbf0..000000000 --- a/ee/backend/pkg/db/clickhouse/bulk.go +++ /dev/null @@ -1,45 +0,0 @@ -package clickhouse - -import ( - "errors" - "database/sql" -) - -type bulk struct { - db *sql.DB - query string - tx *sql.Tx - stmt *sql.Stmt -} - -func newBulk(db *sql.DB, query string) *bulk { - return &bulk{ - db: db, - query: query, - } -} - -func (b *bulk) prepare() error { - var err error - b.tx, err = b.db.Begin() - if err != nil { - return err - } - b.stmt, err = b.tx.Prepare(b.query) - if err != nil { - return err - } - return nil -} - -func (b *bulk) commit() error { - return b.tx.Commit() -} - -func (b *bulk) exec(args ...interface{}) error { - if b.stmt == nil { - return errors.New("Bulk is not prepared.") - } - _, err := b.stmt.Exec(args...) - return err -} diff --git a/ee/backend/pkg/db/clickhouse/connector.go b/ee/backend/pkg/db/clickhouse/connector.go index cc0d20497..1fd6e5d1e 100644 --- a/ee/backend/pkg/db/clickhouse/connector.go +++ b/ee/backend/pkg/db/clickhouse/connector.go @@ -1,138 +1,416 @@ package clickhouse import ( - "database/sql" - _ "github.com/ClickHouse/clickhouse-go" + "context" + "errors" + "fmt" + "github.com/ClickHouse/clickhouse-go/v2" + "github.com/ClickHouse/clickhouse-go/v2/lib/driver" "log" + "openreplay/backend/pkg/db/types" + "openreplay/backend/pkg/hashid" + "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/url" + "strings" + "time" "openreplay/backend/pkg/license" ) -type Connector struct { - sessions *bulk - metadata *bulk // TODO: join sessions, sessions_metadata & sessions_ios - resources *bulk - pages *bulk - clicks *bulk - inputs *bulk - errors *bulk - performance *bulk - longtasks *bulk - db *sql.DB +var CONTEXT_MAP = map[uint64]string{0: "unknown", 1: "self", 2: "same-origin-ancestor", 3: "same-origin-descendant", 4: "same-origin", 5: "cross-origin-ancestor", 6: "cross-origin-descendant", 7: "cross-origin-unreachable", 8: "multiple-contexts"} +var CONTAINER_TYPE_MAP = map[uint64]string{0: "window", 1: "iframe", 2: "embed", 3: "object"} + +type Connector interface { + Prepare() error + Commit() error + FinaliseSessionsTable() error + InsertWebSession(session *types.Session) error + InsertWebResourceEvent(session *types.Session, msg *messages.ResourceEvent) error + InsertWebPageEvent(session *types.Session, msg *messages.PageEvent) error + InsertWebClickEvent(session *types.Session, msg *messages.ClickEvent) error + InsertWebInputEvent(session *types.Session, msg *messages.InputEvent) error + InsertWebErrorEvent(session *types.Session, msg *messages.ErrorEvent) error + InsertWebPerformanceTrackAggr(session *types.Session, msg *messages.PerformanceTrackAggr) error + InsertLongtask(session *types.Session, msg *messages.LongTask) error } -func NewConnector(url string) *Connector { +type connectorImpl struct { + conn driver.Conn + batches map[string]driver.Batch +} + +func NewConnector(url string) Connector { license.CheckLicense() - - db, err := sql.Open("clickhouse", url) + url = strings.TrimPrefix(url, "tcp://") + url = strings.TrimSuffix(url, "/default") + conn, err := clickhouse.Open(&clickhouse.Options{ + Addr: []string{url}, + Auth: clickhouse.Auth{ + Database: "default", + }, + MaxOpenConns: 20, + MaxIdleConns: 15, + ConnMaxLifetime: 3 * time.Minute, + Compression: &clickhouse.Compression{ + Method: clickhouse.CompressionLZ4, + }, + // Debug: true, + }) if err != nil { - log.Fatalln(err) + log.Fatal(err) } - return &Connector{ - db: db, - sessions: newBulk(db, ` - INSERT INTO sessions (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, duration, pages_count, events_count, errors_count, user_browser, user_browser_version) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `), - // TODO: join sessions, sessions_metadata & sessions_ios - metadata: newBulk(db, ` - INSERT INTO sessions_metadata (session_id, user_id, user_anonymous_id, metadata_1, metadata_2, metadata_3, metadata_4, metadata_5, metadata_6, metadata_7, metadata_8, metadata_9, metadata_10, datetime) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `), - resources: newBulk(db, ` - INSERT INTO resources (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, url, type, duration, ttfb, header_size, encoded_body_size, decoded_body_size, success) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `), - pages: newBulk(db, ` - INSERT INTO pages (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, url, request_start, response_start, response_end, dom_content_loaded_event_start, dom_content_loaded_event_end, load_event_start, load_event_end, first_paint, first_contentful_paint, speed_index, visually_complete, time_to_interactive) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `), - clicks: newBulk(db, ` - INSERT INTO clicks (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, label, hesitation_time) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `), - inputs: newBulk(db, ` - INSERT INTO inputs (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, label) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `), - errors: newBulk(db, ` - INSERT INTO errors (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, source, name, message, error_id) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `), - performance: newBulk(db, ` - INSERT INTO performance (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_total_js_heap_size, avg_total_js_heap_size, max_total_js_heap_size, min_used_js_heap_size, avg_used_js_heap_size, max_used_js_heap_size) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `), - longtasks: newBulk(db, ` - INSERT INTO longtasks (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, context, container_type, container_id, container_name, container_src) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `), + + c := &connectorImpl{ + conn: conn, + batches: make(map[string]driver.Batch, 9), } + return c } -func (conn *Connector) Prepare() error { - if err := conn.sessions.prepare(); err != nil { - return err +func (c *connectorImpl) newBatch(name, query string) error { + batch, err := c.conn.PrepareBatch(context.Background(), query) + if err != nil { + return fmt.Errorf("can't create new batch: %s", err) } - if err := conn.metadata.prepare(); err != nil { - return err + if _, ok := c.batches[name]; ok { + delete(c.batches, name) } - if err := conn.resources.prepare(); err != nil { - return err - } - if err := conn.pages.prepare(); err != nil { - return err - } - if err := conn.clicks.prepare(); err != nil { - return err - } - if err := conn.inputs.prepare(); err != nil { - return err - } - if err := conn.errors.prepare(); err != nil { - return err - } - if err := conn.performance.prepare(); err != nil { - return err - } - if err := conn.longtasks.prepare(); err != nil { - return err + c.batches[name] = batch + return nil +} + +var batches = map[string]string{ + "sessions": "INSERT INTO sessions (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, duration, pages_count, events_count, errors_count, user_browser, user_browser_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "metadata": "INSERT INTO sessions_metadata (session_id, user_id, user_anonymous_id, metadata_1, metadata_2, metadata_3, metadata_4, metadata_5, metadata_6, metadata_7, metadata_8, metadata_9, metadata_10, datetime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "resources": "INSERT INTO resources (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, url, type, duration, ttfb, header_size, encoded_body_size, decoded_body_size, success) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "pages": "INSERT INTO pages (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, url, request_start, response_start, response_end, dom_content_loaded_event_start, dom_content_loaded_event_end, load_event_start, load_event_end, first_paint, first_contentful_paint, speed_index, visually_complete, time_to_interactive) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "clicks": "INSERT INTO clicks (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, label, hesitation_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "inputs": "INSERT INTO inputs (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, label) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "errors": "INSERT INTO errors (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, source, name, message, error_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "performance": "INSERT INTO performance (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_total_js_heap_size, avg_total_js_heap_size, max_total_js_heap_size, min_used_js_heap_size, avg_used_js_heap_size, max_used_js_heap_size) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "longtasks": "INSERT INTO longtasks (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_country, datetime, context, container_type, container_id, container_name, container_src) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", +} + +func (c *connectorImpl) Prepare() error { + for table, query := range batches { + if err := c.newBatch(table, query); err != nil { + return fmt.Errorf("can't create %s batch: %s", table, err) + } } return nil } -func (conn *Connector) Commit() error { - if err := conn.sessions.commit(); err != nil { - return err - } - if err := conn.metadata.commit(); err != nil { - return err - } - if err := conn.resources.commit(); err != nil { - return err - } - if err := conn.pages.commit(); err != nil { - return err - } - if err := conn.clicks.commit(); err != nil { - return err - } - if err := conn.inputs.commit(); err != nil { - return err - } - if err := conn.errors.commit(); err != nil { - return err - } - if err := conn.performance.commit(); err != nil { - return err - } - if err := conn.longtasks.commit(); err != nil { - return err +func (c *connectorImpl) Commit() error { + for _, b := range c.batches { + if err := b.Send(); err != nil { + return fmt.Errorf("can't send batch: %s", err) + } } return nil } -func (conn *Connector) FinaliseSessionsTable() error { - _, err := conn.db.Exec("OPTIMIZE TABLE sessions FINAL") - return err +func (c *connectorImpl) FinaliseSessionsTable() error { + if err := c.conn.Exec(context.Background(), "OPTIMIZE TABLE sessions FINAL"); err != nil { + return fmt.Errorf("can't finalise sessions table: %s", err) + } + return nil +} + +func (c *connectorImpl) checkError(name string, err error) { + if err != clickhouse.ErrBatchAlreadySent { + if batchErr := c.newBatch(name, batches[name]); batchErr != nil { + log.Printf("can't create %s batch after failed append operation: %s", name, batchErr) + } + } +} + +func (c *connectorImpl) InsertWebSession(session *types.Session) error { + if session.Duration == nil { + return errors.New("trying to insert session with nil duration") + } + if err := c.batches["sessions"].Append( + session.SessionID, + session.ProjectID, + session.TrackerVersion, + nullableString(session.RevID), + session.UserUUID, + session.UserOS, + nullableString(session.UserOSVersion), + nullableString(session.UserDevice), + session.UserDeviceType, + session.UserCountry, + datetime(session.Timestamp), + uint32(*session.Duration), + uint16(session.PagesCount), + uint16(session.EventsCount), + uint16(session.ErrorsCount), + // Web unique columns + session.UserBrowser, + nullableString(session.UserBrowserVersion), + ); err != nil { + c.checkError("sessions", err) + return fmt.Errorf("can't append to sessions batch: %s", err) + } + if err := c.batches["metadata"].Append( + session.SessionID, + session.UserID, + session.UserAnonymousID, + session.Metadata1, + session.Metadata2, + session.Metadata3, + session.Metadata4, + session.Metadata5, + session.Metadata6, + session.Metadata7, + session.Metadata8, + session.Metadata9, + session.Metadata10, + datetime(session.Timestamp), + ); err != nil { + c.checkError("metadata", err) + return fmt.Errorf("can't append to metadata batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertWebResourceEvent(session *types.Session, msg *messages.ResourceEvent) error { + var method interface{} = url.EnsureMethod(msg.Method) + if method == "" { + method = nil + } + if err := c.batches["resources"].Append( + session.SessionID, + session.ProjectID, + session.TrackerVersion, + nullableString(session.RevID), + session.UserUUID, + session.UserOS, + nullableString(session.UserOSVersion), + session.UserBrowser, + nullableString(session.UserBrowserVersion), + nullableString(session.UserDevice), + session.UserDeviceType, + session.UserCountry, + datetime(msg.Timestamp), + url.DiscardURLQuery(msg.URL), + msg.Type, + nullableUint16(uint16(msg.Duration)), + nullableUint16(uint16(msg.TTFB)), + nullableUint16(uint16(msg.HeaderSize)), + nullableUint32(uint32(msg.EncodedBodySize)), + nullableUint32(uint32(msg.DecodedBodySize)), + msg.Success, + ); err != nil { + c.checkError("resources", err) + return fmt.Errorf("can't append to resources batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertWebPageEvent(session *types.Session, msg *messages.PageEvent) error { + if err := c.batches["pages"].Append( + session.SessionID, + session.ProjectID, + session.TrackerVersion, nullableString(session.RevID), + session.UserUUID, + session.UserOS, + nullableString(session.UserOSVersion), + session.UserBrowser, + nullableString(session.UserBrowserVersion), + nullableString(session.UserDevice), + session.UserDeviceType, + session.UserCountry, + datetime(msg.Timestamp), + url.DiscardURLQuery(msg.URL), + nullableUint16(uint16(msg.RequestStart)), + nullableUint16(uint16(msg.ResponseStart)), + nullableUint16(uint16(msg.ResponseEnd)), + nullableUint16(uint16(msg.DomContentLoadedEventStart)), + nullableUint16(uint16(msg.DomContentLoadedEventEnd)), + nullableUint16(uint16(msg.LoadEventStart)), + nullableUint16(uint16(msg.LoadEventEnd)), + nullableUint16(uint16(msg.FirstPaint)), + nullableUint16(uint16(msg.FirstContentfulPaint)), + nullableUint16(uint16(msg.SpeedIndex)), + nullableUint16(uint16(msg.VisuallyComplete)), + nullableUint16(uint16(msg.TimeToInteractive)), + ); err != nil { + c.checkError("pages", err) + return fmt.Errorf("can't append to pages batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertWebClickEvent(session *types.Session, msg *messages.ClickEvent) error { + if msg.Label == "" { + return nil + } + if err := c.batches["clicks"].Append( + session.SessionID, + session.ProjectID, + session.TrackerVersion, + nullableString(session.RevID), + session.UserUUID, + session.UserOS, + nullableString(session.UserOSVersion), + session.UserBrowser, + nullableString(session.UserBrowserVersion), + nullableString(session.UserDevice), + session.UserDeviceType, + session.UserCountry, + datetime(msg.Timestamp), + msg.Label, + nullableUint32(uint32(msg.HesitationTime)), + ); err != nil { + c.checkError("clicks", err) + return fmt.Errorf("can't append to clicks batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertWebInputEvent(session *types.Session, msg *messages.InputEvent) error { + if msg.Label == "" { + return nil + } + if err := c.batches["inputs"].Append( + session.SessionID, + session.ProjectID, + session.TrackerVersion, + nullableString(session.RevID), + session.UserUUID, + session.UserOS, + nullableString(session.UserOSVersion), + session.UserBrowser, + nullableString(session.UserBrowserVersion), + nullableString(session.UserDevice), + session.UserDeviceType, + session.UserCountry, + datetime(msg.Timestamp), + msg.Label, + ); err != nil { + c.checkError("inputs", err) + return fmt.Errorf("can't append to inputs batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertWebErrorEvent(session *types.Session, msg *messages.ErrorEvent) error { + if err := c.batches["errors"].Append( + session.SessionID, + session.ProjectID, + session.TrackerVersion, + nullableString(session.RevID), + session.UserUUID, + session.UserOS, + nullableString(session.UserOSVersion), + session.UserBrowser, + nullableString(session.UserBrowserVersion), + nullableString(session.UserDevice), + session.UserDeviceType, + session.UserCountry, + datetime(msg.Timestamp), + msg.Source, + nullableString(msg.Name), + msg.Message, + hashid.WebErrorID(session.ProjectID, msg), + ); err != nil { + c.checkError("errors", err) + return fmt.Errorf("can't append to errors batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertWebPerformanceTrackAggr(session *types.Session, msg *messages.PerformanceTrackAggr) error { + var timestamp uint64 = (msg.TimestampStart + msg.TimestampEnd) / 2 + if err := c.batches["performance"].Append( + session.SessionID, + session.ProjectID, + session.TrackerVersion, + nullableString(session.RevID), + session.UserUUID, + session.UserOS, + nullableString(session.UserOSVersion), + session.UserBrowser, + nullableString(session.UserBrowserVersion), + nullableString(session.UserDevice), + session.UserDeviceType, + session.UserCountry, + datetime(timestamp), + uint8(msg.MinFPS), + uint8(msg.AvgFPS), + uint8(msg.MaxFPS), + uint8(msg.MinCPU), + uint8(msg.AvgCPU), + uint8(msg.MaxCPU), + msg.MinTotalJSHeapSize, + msg.AvgTotalJSHeapSize, + msg.MaxTotalJSHeapSize, + msg.MinUsedJSHeapSize, + msg.AvgUsedJSHeapSize, + msg.MaxUsedJSHeapSize, + ); err != nil { + c.checkError("performance", err) + return fmt.Errorf("can't append to performance batch: %s", err) + } + return nil +} + +func (c *connectorImpl) InsertLongtask(session *types.Session, msg *messages.LongTask) error { + if err := c.batches["longtasks"].Append( + session.SessionID, + session.ProjectID, + session.TrackerVersion, + nullableString(session.RevID), + session.UserUUID, + session.UserOS, + nullableString(session.UserOSVersion), + session.UserBrowser, + nullableString(session.UserBrowserVersion), + nullableString(session.UserDevice), + session.UserDeviceType, + session.UserCountry, + datetime(msg.Timestamp), + CONTEXT_MAP[msg.Context], + CONTAINER_TYPE_MAP[msg.ContainerType], + msg.ContainerId, + msg.ContainerName, + msg.ContainerSrc, + ); err != nil { + c.checkError("longtasks", err) + return fmt.Errorf("can't append to longtasks batch: %s", err) + } + return nil +} + +func nullableUint16(v uint16) *uint16 { + var p *uint16 = nil + if v != 0 { + p = &v + } + return p +} + +func nullableUint32(v uint32) *uint32 { + var p *uint32 = nil + if v != 0 { + p = &v + } + return p +} + +func nullableString(v string) *string { + var p *string = nil + if v != "" { + p = &v + } + return p +} + +func datetime(timestamp uint64) time.Time { + t := time.Unix(int64(timestamp/1e3), 0) + // Temporal solution for not correct timestamps in performance messages + if t.Year() < 2022 || t.Year() > 2025 { + return time.Now() + } + return t } diff --git a/ee/backend/pkg/db/clickhouse/helpers.go b/ee/backend/pkg/db/clickhouse/helpers.go deleted file mode 100644 index 37e30518c..000000000 --- a/ee/backend/pkg/db/clickhouse/helpers.go +++ /dev/null @@ -1,34 +0,0 @@ -package clickhouse - -import ( - "time" -) - - -func nullableUint16(v uint16) *uint16 { - var p *uint16 = nil - if v != 0 { - p = &v - } - return p -} - -func nullableUint32(v uint32) *uint32 { - var p *uint32 = nil - if v != 0 { - p = &v - } - return p -} - -func nullableString(v string) *string { - var p *string = nil - if v != "" { - p = &v - } - return p -} - -func datetime(timestamp uint64) time.Time { - return time.Unix(int64(timestamp/1e3), 0) -} diff --git a/ee/backend/pkg/db/clickhouse/messages-web.go b/ee/backend/pkg/db/clickhouse/messages-web.go deleted file mode 100644 index adfa38655..000000000 --- a/ee/backend/pkg/db/clickhouse/messages-web.go +++ /dev/null @@ -1,243 +0,0 @@ -package clickhouse - -import ( - "errors" - - . "openreplay/backend/pkg/db/types" - "openreplay/backend/pkg/hashid" - . "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/url" -) - -func (conn *Connector) InsertWebSession(session *Session) error { - if session.Duration == nil { - return errors.New("Clickhouse: trying to insert session with ") - } - - if err := conn.sessions.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(session.Timestamp), - uint32(*session.Duration), - session.PagesCount, - session.EventsCount, - session.ErrorsCount, - // Web unique columns - session.UserBrowser, - nullableString(session.UserBrowserVersion), - ); err != nil { - return err - } - // TODO: join sessions, sessions_metadata & sessions_ios - return conn.metadata.exec( - session.SessionID, - session.UserID, - session.UserAnonymousID, - session.Metadata1, - session.Metadata2, - session.Metadata3, - session.Metadata4, - session.Metadata5, - session.Metadata6, - session.Metadata7, - session.Metadata8, - session.Metadata9, - session.Metadata10, - datetime(session.Timestamp), - ) -} - -func (conn *Connector) InsertWebResourceEvent(session *Session, msg *ResourceEvent) error { - // nullableString causes error "unexpected type *string" on Nullable Enum type - // (apparently, a clickhouse-go bug) https://github.com/ClickHouse/clickhouse-go/pull/204 - var method interface{} = url.EnsureMethod(msg.Method) - if method == "" { - method = nil - } - return conn.resources.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - session.UserBrowser, - nullableString(session.UserBrowserVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - url.DiscardURLQuery(msg.URL), - msg.Type, - nullableUint16(uint16(msg.Duration)), - nullableUint16(uint16(msg.TTFB)), - nullableUint16(uint16(msg.HeaderSize)), - nullableUint32(uint32(msg.EncodedBodySize)), - nullableUint32(uint32(msg.DecodedBodySize)), - msg.Success, - ) -} - -func (conn *Connector) InsertWebPageEvent(session *Session, msg *PageEvent) error { - return conn.pages.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - session.UserBrowser, - nullableString(session.UserBrowserVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - url.DiscardURLQuery(msg.URL), - nullableUint16(uint16(msg.RequestStart)), - nullableUint16(uint16(msg.ResponseStart)), - nullableUint16(uint16(msg.ResponseEnd)), - nullableUint16(uint16(msg.DomContentLoadedEventStart)), - nullableUint16(uint16(msg.DomContentLoadedEventEnd)), - nullableUint16(uint16(msg.LoadEventStart)), - nullableUint16(uint16(msg.LoadEventEnd)), - nullableUint16(uint16(msg.FirstPaint)), - nullableUint16(uint16(msg.FirstContentfulPaint)), - nullableUint16(uint16(msg.SpeedIndex)), - nullableUint16(uint16(msg.VisuallyComplete)), - nullableUint16(uint16(msg.TimeToInteractive)), - ) -} - -func (conn *Connector) InsertWebClickEvent(session *Session, msg *ClickEvent) error { - if msg.Label == "" { - return nil - } - return conn.clicks.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - session.UserBrowser, - nullableString(session.UserBrowserVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - msg.Label, - nullableUint32(uint32(msg.HesitationTime)), - ) -} - -func (conn *Connector) InsertWebInputEvent(session *Session, msg *InputEvent) error { - if msg.Label == "" { - return nil - } - return conn.inputs.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - session.UserBrowser, - nullableString(session.UserBrowserVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - msg.Label, - ) -} - -func (conn *Connector) InsertWebErrorEvent(session *Session, msg *ErrorEvent) error { - return conn.errors.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - session.UserBrowser, - nullableString(session.UserBrowserVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - msg.Source, - nullableString(msg.Name), - msg.Message, - hashid.WebErrorID(session.ProjectID, msg), - ) -} - -func (conn *Connector) InsertWebPerformanceTrackAggr(session *Session, msg *PerformanceTrackAggr) error { - var timestamp uint64 = (msg.TimestampStart + msg.TimestampEnd) / 2 - return conn.performance.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - session.UserBrowser, - nullableString(session.UserBrowserVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(timestamp), - uint8(msg.MinFPS), - uint8(msg.AvgFPS), - uint8(msg.MaxFPS), - uint8(msg.MinCPU), - uint8(msg.AvgCPU), - uint8(msg.MaxCPU), - msg.MinTotalJSHeapSize, - msg.AvgTotalJSHeapSize, - msg.MaxTotalJSHeapSize, - msg.MinUsedJSHeapSize, - msg.AvgUsedJSHeapSize, - msg.MaxUsedJSHeapSize, - ) -} - -// TODO: make enum message type -var CONTEXT_MAP = map[uint64]string{0: "unknown", 1: "self", 2: "same-origin-ancestor", 3: "same-origin-descendant", 4: "same-origin", 5: "cross-origin-ancestor", 6: "cross-origin-descendant", 7: "cross-origin-unreachable", 8: "multiple-contexts"} -var CONTAINER_TYPE_MAP = map[uint64]string{0: "window", 1: "iframe", 2: "embed", 3: "object"} - -func (conn *Connector) InsertLongtask(session *Session, msg *LongTask) error { - return conn.longtasks.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - session.UserBrowser, - nullableString(session.UserBrowserVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - CONTEXT_MAP[msg.Context], - CONTAINER_TYPE_MAP[msg.ContainerType], - msg.ContainerId, - msg.ContainerName, - msg.ContainerSrc, - ) -} From 959d54be7ef1a2dcf3f2f3441910e189b8ec5899 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Mon, 1 Aug 2022 18:05:38 +0200 Subject: [PATCH 014/168] feat(chalice): fixed update self-user feat(chalice): optimized /projects --- api/chalicelib/core/projects.py | 28 ++++++++++++++++++---------- api/routers/core.py | 8 -------- api/routers/core_dynamic.py | 8 ++++++++ ee/api/chalicelib/core/projects.py | 24 +++++++++++------------- ee/api/routers/core_dynamic.py | 8 ++++++++ 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/api/chalicelib/core/projects.py b/api/chalicelib/core/projects.py index 0b0bd963f..0893f6259 100644 --- a/api/chalicelib/core/projects.py +++ b/api/chalicelib/core/projects.py @@ -43,16 +43,24 @@ def __create(tenant_id, name): def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, stack_integrations=False): with pg_client.PostgresClient() as cur: - cur.execute(f"""\ - SELECT - s.project_id, s.name, s.project_key, s.save_request_payloads - {',s.gdpr' if gdpr else ''} - {',COALESCE((SELECT TRUE FROM public.sessions WHERE sessions.project_id = s.project_id LIMIT 1), FALSE) AS recorded' if recorded else ''} - {',stack_integrations.count>0 AS stack_integrations' if stack_integrations else ''} - FROM public.projects AS s - {'LEFT JOIN LATERAL (SELECT COUNT(*) AS count FROM public.integrations WHERE s.project_id = integrations.project_id LIMIT 1) AS stack_integrations ON TRUE' if stack_integrations else ''} - WHERE s.deleted_at IS NULL - ORDER BY s.project_id;""") + recorded_q = "" + if recorded: + recorded_q = """, COALESCE((SELECT TRUE + FROM public.sessions + WHERE sessions.project_id = s.project_id + AND sessions.start_ts >= (EXTRACT(EPOCH FROM s.created_at) * 1000 - 24 * 60 * 60 * 1000) + AND sessions.start_ts <= %(now)s + LIMIT 1), FALSE) AS recorded""" + query = cur.mogrify(f"""SELECT + s.project_id, s.name, s.project_key, s.save_request_payloads + {',s.gdpr' if gdpr else ''} + {recorded_q} + {',stack_integrations.count>0 AS stack_integrations' if stack_integrations else ''} + FROM public.projects AS s + {'LEFT JOIN LATERAL (SELECT COUNT(*) AS count FROM public.integrations WHERE s.project_id = integrations.project_id LIMIT 1) AS stack_integrations ON TRUE' if stack_integrations else ''} + WHERE s.deleted_at IS NULL + ORDER BY s.project_id;""", {"now": TimeUTC.now()}) + cur.execute(query) rows = cur.fetchall() if recording_state: project_ids = [f'({r["project_id"]})' for r in rows] diff --git a/api/routers/core.py b/api/routers/core.py index 2c3ff5b90..c69c7ff43 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -1110,14 +1110,6 @@ def generate_new_user_token(context: schemas.CurrentContext = Depends(OR_context return {"data": users.generate_new_api_key(user_id=context.user_id)} -@app.post('/account', tags=["account"]) -@app.put('/account', tags=["account"]) -def edit_account(data: schemas.EditUserSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - return users.edit(tenant_id=context.tenant_id, user_id_to_update=context.user_id, changes=data, - editor_id=context.user_id) - - @app.post('/account/password', tags=["account"]) @app.put('/account/password', tags=["account"]) def change_client_password(data: schemas.EditUserPasswordSchema = Body(...), diff --git a/api/routers/core_dynamic.py b/api/routers/core_dynamic.py index 594715bb6..32eb78e41 100644 --- a/api/routers/core_dynamic.py +++ b/api/routers/core_dynamic.py @@ -43,6 +43,14 @@ def get_account(context: schemas.CurrentContext = Depends(OR_context)): } +@app.post('/account', tags=["account"]) +@app.put('/account', tags=["account"]) +def edit_account(data: schemas.EditUserSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return users.edit(tenant_id=context.tenant_id, user_id_to_update=context.user_id, changes=data, + editor_id=context.user_id) + + @app.get('/projects/limit', tags=['projects']) def get_projects_limit(context: schemas.CurrentContext = Depends(OR_context)): return {"data": { diff --git a/ee/api/chalicelib/core/projects.py b/ee/api/chalicelib/core/projects.py index e6ef34760..6700173b5 100644 --- a/ee/api/chalicelib/core/projects.py +++ b/ee/api/chalicelib/core/projects.py @@ -52,30 +52,28 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st AND users.tenant_id = %(tenant_id)s AND (roles.all_projects OR roles_projects.project_id = s.project_id) ) AS role_project ON (TRUE)""" - pre_select = "" + recorded_q = "" if recorded: - pre_select = """WITH recorded_p AS (SELECT DISTINCT projects.project_id - FROM projects INNER JOIN sessions USING (project_id) - WHERE tenant_id =%(tenant_id)s - AND deleted_at IS NULL - AND duration > 0)""" - cur.execute( - cur.mogrify(f"""\ - {pre_select} + recorded_q = """, COALESCE((SELECT TRUE + FROM public.sessions + WHERE sessions.project_id = s.project_id + AND sessions.start_ts >= (EXTRACT(EPOCH FROM s.created_at) * 1000 - 24 * 60 * 60 * 1000) + AND sessions.start_ts <= %(now)s + LIMIT 1), FALSE) AS recorded""" + query = cur.mogrify(f"""\ SELECT s.project_id, s.name, s.project_key, s.save_request_payloads {',s.gdpr' if gdpr else ''} - {',EXISTS(SELECT 1 FROM recorded_p WHERE recorded_p.project_id = s.project_id) AS recorded' if recorded else ''} + {recorded_q} {',stack_integrations.count>0 AS stack_integrations' if stack_integrations else ''} FROM public.projects AS s - {'LEFT JOIN recorded_p USING (project_id)' if recorded else ''} {'LEFT JOIN LATERAL (SELECT COUNT(*) AS count FROM public.integrations WHERE s.project_id = integrations.project_id LIMIT 1) AS stack_integrations ON TRUE' if stack_integrations else ''} {role_query if user_id is not None else ""} WHERE s.tenant_id =%(tenant_id)s AND s.deleted_at IS NULL ORDER BY s.project_id;""", - {"tenant_id": tenant_id, "user_id": user_id}) - ) + {"tenant_id": tenant_id, "user_id": user_id, "now": TimeUTC.now()}) + cur.execute(query) rows = cur.fetchall() if recording_state: project_ids = [f'({r["project_id"]})' for r in rows] diff --git a/ee/api/routers/core_dynamic.py b/ee/api/routers/core_dynamic.py index 3c5c21905..6d8470444 100644 --- a/ee/api/routers/core_dynamic.py +++ b/ee/api/routers/core_dynamic.py @@ -46,6 +46,14 @@ def get_account(context: schemas.CurrentContext = Depends(OR_context)): } +@app.post('/account', tags=["account"]) +@app.put('/account', tags=["account"]) +def edit_account(data: schemas_ee.EditUserSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return users.edit(tenant_id=context.tenant_id, user_id_to_update=context.user_id, changes=data, + editor_id=context.user_id) + + @app.get('/projects/limit', tags=['projects']) def get_projects_limit(context: schemas.CurrentContext = Depends(OR_context)): return {"data": { From cc73278f9ad0dc933f63c85549cbf3f5c3207527 Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Mon, 1 Aug 2022 18:23:42 +0200 Subject: [PATCH 015/168] Revert "v1.7.0 ES change" --- api/chalicelib/core/log_tool_elasticsearch.py | 71 +++++++++---------- api/requirements-alerts.txt | 1 + api/requirements.txt | 1 + ee/api/requirements-alerts.txt | 1 + ee/api/requirements-crons.txt | 1 + ee/api/requirements.txt | 1 + 6 files changed, 40 insertions(+), 36 deletions(-) diff --git a/api/chalicelib/core/log_tool_elasticsearch.py b/api/chalicelib/core/log_tool_elasticsearch.py index 90986f61d..ba20636c4 100644 --- a/api/chalicelib/core/log_tool_elasticsearch.py +++ b/api/chalicelib/core/log_tool_elasticsearch.py @@ -1,7 +1,7 @@ # from elasticsearch import Elasticsearch, RequestsHttpConnection -# from elasticsearch import Elasticsearch +from elasticsearch import Elasticsearch from chalicelib.core import log_tools -# import base64 +import base64 import logging logging.getLogger('elasticsearch').level = logging.ERROR @@ -58,40 +58,39 @@ def add_edit(tenant_id, project_id, data): port=data["port"]) -# def __get_es_client(host, port, api_key_id, api_key, use_ssl=False, timeout=15): -# scheme = "http" if host.startswith("http") else "https" -# host = host.replace("http://", "").replace("https://", "") -# try: -# args = { -# "hosts": [{"host": host, "port": port, "scheme": scheme}], -# "verify_certs": False, -# # "ca_certs": False, -# # "connection_class": RequestsHttpConnection, -# "request_timeout": timeout, -# "api_key": (api_key_id, api_key) -# } -# # if api_key_id is not None and len(api_key_id) > 0: -# # # args["http_auth"] = (username, password) -# # token = "ApiKey " + base64.b64encode(f"{api_key_id}:{api_key}".encode("utf-8")).decode("utf-8") -# # args["headers"] = {"Authorization": token} -# es = Elasticsearch( -# **args -# ) -# r = es.ping() -# if not r and not use_ssl: -# return __get_es_client(host, port, api_key_id, api_key, use_ssl=True, timeout=timeout) -# if not r: -# return None -# except Exception as err: -# print("================exception connecting to ES host:") -# print(err) -# return None -# return es +def __get_es_client(host, port, api_key_id, api_key, use_ssl=False, timeout=15): + scheme = "http" if host.startswith("http") else "https" + host = host.replace("http://", "").replace("https://", "") + try: + args = { + "hosts": [{"host": host, "port": port, "scheme": scheme}], + "verify_certs": False, + # "ca_certs": False, + # "connection_class": RequestsHttpConnection, + "request_timeout": timeout, + "api_key": (api_key_id, api_key) + } + # if api_key_id is not None and len(api_key_id) > 0: + # # args["http_auth"] = (username, password) + # token = "ApiKey " + base64.b64encode(f"{api_key_id}:{api_key}".encode("utf-8")).decode("utf-8") + # args["headers"] = {"Authorization": token} + es = Elasticsearch( + **args + ) + r = es.ping() + if not r and not use_ssl: + return __get_es_client(host, port, api_key_id, api_key, use_ssl=True, timeout=timeout) + if not r: + return None + except Exception as err: + print("================exception connecting to ES host:") + print(err) + return None + return es def ping(tenant_id, host, port, apiKeyId, apiKey): - # es = __get_es_client(host, port, apiKeyId, apiKey, timeout=3) - # if es is None: - # return {"state": False} - # return {"state": es.ping()} - return {"state": True} + es = __get_es_client(host, port, apiKeyId, apiKey, timeout=3) + if es is None: + return {"state": False} + return {"state": es.ping()} diff --git a/api/requirements-alerts.txt b/api/requirements-alerts.txt index f70efdd13..81198b0f3 100644 --- a/api/requirements-alerts.txt +++ b/api/requirements-alerts.txt @@ -3,6 +3,7 @@ urllib3==1.26.10 boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 +elasticsearch==8.3.1 jira==3.3.0 diff --git a/api/requirements.txt b/api/requirements.txt index f70efdd13..81198b0f3 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -3,6 +3,7 @@ urllib3==1.26.10 boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 +elasticsearch==8.3.1 jira==3.3.0 diff --git a/ee/api/requirements-alerts.txt b/ee/api/requirements-alerts.txt index 07d31f369..66fa84713 100644 --- a/ee/api/requirements-alerts.txt +++ b/ee/api/requirements-alerts.txt @@ -3,6 +3,7 @@ urllib3==1.26.10 boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 +elasticsearch==8.3.1 jira==3.3.0 diff --git a/ee/api/requirements-crons.txt b/ee/api/requirements-crons.txt index 07d31f369..66fa84713 100644 --- a/ee/api/requirements-crons.txt +++ b/ee/api/requirements-crons.txt @@ -3,6 +3,7 @@ urllib3==1.26.10 boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 +elasticsearch==8.3.1 jira==3.3.0 diff --git a/ee/api/requirements.txt b/ee/api/requirements.txt index 2a9ae2e1a..5ce044904 100644 --- a/ee/api/requirements.txt +++ b/ee/api/requirements.txt @@ -3,6 +3,7 @@ urllib3==1.26.10 boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 +elasticsearch==8.3.1 jira==3.3.0 From dddda50e56b923a8c824a4f899909a65b7181ea3 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Thu, 7 Jul 2022 17:15:08 +0200 Subject: [PATCH 016/168] chore(helm): Adding PV support for deployments --- .../charts/alerts/templates/deployment.yaml | 8 ++++++++ .../helmcharts/openreplay/charts/alerts/values.yaml | 12 ++++++++++++ .../charts/assets/templates/deployment.yaml | 8 ++++++++ .../helmcharts/openreplay/charts/assets/values.yaml | 12 ++++++++++++ .../charts/assist/templates/deployment.yaml | 10 +++++++++- .../helmcharts/openreplay/charts/assist/values.yaml | 12 ++++++++++++ .../charts/chalice/templates/deployment.yaml | 8 ++++++++ .../helmcharts/openreplay/charts/chalice/values.yaml | 12 ++++++++++++ .../openreplay/charts/db/templates/deployment.yaml | 8 ++++++++ scripts/helmcharts/openreplay/charts/db/values.yaml | 12 ++++++++++++ .../charts/ender/templates/deployment.yaml | 8 ++++++++ .../helmcharts/openreplay/charts/ender/values.yaml | 12 ++++++++++++ .../openreplay/charts/http/templates/deployment.yaml | 8 ++++++++ .../helmcharts/openreplay/charts/http/values.yaml | 12 ++++++++++++ .../charts/integrations/templates/deployment.yaml | 8 ++++++++ .../openreplay/charts/integrations/values.yaml | 12 ++++++++++++ .../charts/peers/templates/deployment.yaml | 8 ++++++++ .../helmcharts/openreplay/charts/peers/values.yaml | 12 ++++++++++++ 18 files changed, 181 insertions(+), 1 deletion(-) diff --git a/scripts/helmcharts/openreplay/charts/alerts/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/alerts/templates/deployment.yaml index eac304df0..afb7aedc5 100644 --- a/scripts/helmcharts/openreplay/charts/alerts/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/alerts/templates/deployment.yaml @@ -96,8 +96,16 @@ spec: containerPort: {{ $val }} protocol: TCP {{- end }} + {{- with .Values.persistence.mounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.persistence.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/alerts/values.yaml b/scripts/helmcharts/openreplay/charts/alerts/values.yaml index 8efd36a80..4bcc516c8 100644 --- a/scripts/helmcharts/openreplay/charts/alerts/values.yaml +++ b/scripts/helmcharts/openreplay/charts/alerts/values.yaml @@ -99,3 +99,15 @@ nodeSelector: {} tolerations: [] affinity: {} + + +persistence: {} + # # Spec of spec.template.spec.containers[*].volumeMounts + # mounts: + # - name: kafka-ssl + # mountPath: /opt/kafka/ssl + # # Spec of spec.template.spec.volumes + # volumes: + # - name: kafka-ssl + # secret: + # secretName: kafka-ssl diff --git a/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml index ce0c41c99..5fbd084c0 100644 --- a/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml @@ -90,8 +90,16 @@ spec: containerPort: {{ $val }} protocol: TCP {{- end }} + {{- with .Values.persistence.mounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.persistence.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/assets/values.yaml b/scripts/helmcharts/openreplay/charts/assets/values.yaml index e590f1b3c..2597ed36c 100644 --- a/scripts/helmcharts/openreplay/charts/assets/values.yaml +++ b/scripts/helmcharts/openreplay/charts/assets/values.yaml @@ -96,3 +96,15 @@ nodeSelector: {} tolerations: [] affinity: {} + + +persistence: {} + # # Spec of spec.template.spec.containers[*].volumeMounts + # mounts: + # - name: kafka-ssl + # mountPath: /opt/kafka/ssl + # # Spec of spec.template.spec.volumes + # volumes: + # - name: kafka-ssl + # secret: + # secretName: kafka-ssl diff --git a/scripts/helmcharts/openreplay/charts/assist/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/assist/templates/deployment.yaml index 2a776ab58..ed4ec5d4a 100644 --- a/scripts/helmcharts/openreplay/charts/assist/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/assist/templates/deployment.yaml @@ -62,10 +62,18 @@ spec: {{- range $key, $val := .Values.service.ports }} - name: {{ $key }} containerPort: {{ $val }} - protocol: TCP {{- end }} + protocol: TCP + {{- with .Values.persistence.mounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.persistence.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/assist/values.yaml b/scripts/helmcharts/openreplay/charts/assist/values.yaml index 4ffd45a0d..4ffaf88e1 100644 --- a/scripts/helmcharts/openreplay/charts/assist/values.yaml +++ b/scripts/helmcharts/openreplay/charts/assist/values.yaml @@ -97,3 +97,15 @@ nodeSelector: {} tolerations: [] affinity: {} + + +persistence: {} + # # Spec of spec.template.spec.containers[*].volumeMounts + # mounts: + # - name: kafka-ssl + # mountPath: /opt/kafka/ssl + # # Spec of spec.template.spec.volumes + # volumes: + # - name: kafka-ssl + # secret: + # secretName: kafka-ssl diff --git a/scripts/helmcharts/openreplay/charts/chalice/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/chalice/templates/deployment.yaml index d2aafc35a..4491a82e4 100644 --- a/scripts/helmcharts/openreplay/charts/chalice/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/chalice/templates/deployment.yaml @@ -106,8 +106,16 @@ spec: containerPort: {{ $val }} protocol: TCP {{- end }} + {{- with .Values.persistence.mounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.persistence.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/chalice/values.yaml b/scripts/helmcharts/openreplay/charts/chalice/values.yaml index 99acdbf76..2c9d75040 100644 --- a/scripts/helmcharts/openreplay/charts/chalice/values.yaml +++ b/scripts/helmcharts/openreplay/charts/chalice/values.yaml @@ -120,3 +120,15 @@ healthCheck: initialDelaySeconds: 100 periodSeconds: 15 timeoutSeconds: 10 + + +persistence: {} + # # Spec of spec.template.spec.containers[*].volumeMounts + # mounts: + # - name: kafka-ssl + # mountPath: /opt/kafka/ssl + # # Spec of spec.template.spec.volumes + # volumes: + # - name: kafka-ssl + # secret: + # secretName: kafka-ssl diff --git a/scripts/helmcharts/openreplay/charts/db/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/db/templates/deployment.yaml index 7afbf0e7d..2c18179df 100644 --- a/scripts/helmcharts/openreplay/charts/db/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/db/templates/deployment.yaml @@ -62,8 +62,16 @@ spec: containerPort: {{ $val }} protocol: TCP {{- end }} + {{- with .Values.persistence.mounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.persistence.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/db/values.yaml b/scripts/helmcharts/openreplay/charts/db/values.yaml index 0da7ab913..7d375c594 100644 --- a/scripts/helmcharts/openreplay/charts/db/values.yaml +++ b/scripts/helmcharts/openreplay/charts/db/values.yaml @@ -98,3 +98,15 @@ nodeSelector: {} tolerations: [] affinity: {} + + +persistence: {} + # # Spec of spec.template.spec.containers[*].volumeMounts + # mounts: + # - name: kafka-ssl + # mountPath: /opt/kafka/ssl + # # Spec of spec.template.spec.volumes + # volumes: + # - name: kafka-ssl + # secret: + # secretName: kafka-ssl diff --git a/scripts/helmcharts/openreplay/charts/ender/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/ender/templates/deployment.yaml index 368c3ee29..a313415c8 100644 --- a/scripts/helmcharts/openreplay/charts/ender/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/ender/templates/deployment.yaml @@ -62,8 +62,16 @@ spec: containerPort: {{ $val }} protocol: TCP {{- end }} + {{- with .Values.persistence.mounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.persistence.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/ender/values.yaml b/scripts/helmcharts/openreplay/charts/ender/values.yaml index 2d3d2b65b..c751680d4 100644 --- a/scripts/helmcharts/openreplay/charts/ender/values.yaml +++ b/scripts/helmcharts/openreplay/charts/ender/values.yaml @@ -97,3 +97,15 @@ nodeSelector: {} tolerations: [] affinity: {} + + +persistence: {} + # # Spec of spec.template.spec.containers[*].volumeMounts + # mounts: + # - name: kafka-ssl + # mountPath: /opt/kafka/ssl + # # Spec of spec.template.spec.volumes + # volumes: + # - name: kafka-ssl + # secret: + # secretName: kafka-ssl diff --git a/scripts/helmcharts/openreplay/charts/http/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/http/templates/deployment.yaml index 44574d1f8..eaa5d7ed1 100644 --- a/scripts/helmcharts/openreplay/charts/http/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/http/templates/deployment.yaml @@ -88,8 +88,16 @@ spec: containerPort: {{ $val }} protocol: TCP {{- end }} + {{- with .Values.persistence.mounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.persistence.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/http/values.yaml b/scripts/helmcharts/openreplay/charts/http/values.yaml index 72a8acb7d..7a96d525d 100644 --- a/scripts/helmcharts/openreplay/charts/http/values.yaml +++ b/scripts/helmcharts/openreplay/charts/http/values.yaml @@ -97,3 +97,15 @@ nodeSelector: {} tolerations: [] affinity: {} + + +persistence: {} + # # Spec of spec.template.spec.containers[*].volumeMounts + # mounts: + # - name: kafka-ssl + # mountPath: /opt/kafka/ssl + # # Spec of spec.template.spec.volumes + # volumes: + # - name: kafka-ssl + # secret: + # secretName: kafka-ssl diff --git a/scripts/helmcharts/openreplay/charts/integrations/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/integrations/templates/deployment.yaml index 5e63e5153..e0f3fff60 100644 --- a/scripts/helmcharts/openreplay/charts/integrations/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/integrations/templates/deployment.yaml @@ -62,8 +62,16 @@ spec: containerPort: {{ $val }} protocol: TCP {{- end }} + {{- with .Values.persistence.mounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.persistence.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/integrations/values.yaml b/scripts/helmcharts/openreplay/charts/integrations/values.yaml index b9086900b..191ed7047 100644 --- a/scripts/helmcharts/openreplay/charts/integrations/values.yaml +++ b/scripts/helmcharts/openreplay/charts/integrations/values.yaml @@ -98,3 +98,15 @@ nodeSelector: {} tolerations: [] affinity: {} + + +persistence: {} + # # Spec of spec.template.spec.containers[*].volumeMounts + # mounts: + # - name: kafka-ssl + # mountPath: /opt/kafka/ssl + # # Spec of spec.template.spec.volumes + # volumes: + # - name: kafka-ssl + # secret: + # secretName: kafka-ssl diff --git a/scripts/helmcharts/openreplay/charts/peers/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/peers/templates/deployment.yaml index 6f1a379d8..ac673fd08 100644 --- a/scripts/helmcharts/openreplay/charts/peers/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/peers/templates/deployment.yaml @@ -54,8 +54,16 @@ spec: containerPort: {{ $val }} protocol: TCP {{- end }} + {{- with .Values.persistence.mounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.persistence.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/peers/values.yaml b/scripts/helmcharts/openreplay/charts/peers/values.yaml index 721c09db9..4643a75a7 100644 --- a/scripts/helmcharts/openreplay/charts/peers/values.yaml +++ b/scripts/helmcharts/openreplay/charts/peers/values.yaml @@ -93,3 +93,15 @@ nodeSelector: {} tolerations: [] affinity: {} + + +persistence: {} + # # Spec of spec.template.spec.containers[*].volumeMounts + # mounts: + # - name: kafka-ssl + # mountPath: /opt/kafka/ssl + # # Spec of spec.template.spec.volumes + # volumes: + # - name: kafka-ssl + # secret: + # secretName: kafka-ssl From eb488897f2720e5199c7e02f1c71bd79c322fb8f Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 1 Aug 2022 19:03:15 +0200 Subject: [PATCH 017/168] chore(nginx): removing ipv6, as some machines won't have support for that. Signed-off-by: rjshrjndrn --- frontend/nginx.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index f5581606b..84aac6601 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -1,6 +1,5 @@ server { listen 8080 default_server; - listen [::]:8080 default_server; root /var/www/openreplay; index index.html; rewrite ^((?!.(js|css|png|svg|jpg|woff|woff2)).)*$ /index.html break; From 78ab1e49c34aa0ccc3187752e4ac625d22425c3f Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 1 Aug 2022 19:04:30 +0200 Subject: [PATCH 018/168] fix(nginx): fixing base config Signed-off-by: rjshrjndrn --- frontend/nginx.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 84aac6601..b886096da 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -2,10 +2,10 @@ server { listen 8080 default_server; root /var/www/openreplay; index index.html; - rewrite ^((?!.(js|css|png|svg|jpg|woff|woff2)).)*$ /index.html break; - proxy_intercept_errors on; # see frontend://nginx.org/en/docs/frontend/ngx_frontend_proxy_module.html#proxy_intercept_errors - error_page 404 =200 /index.html; location / { try_files $uri $uri/ =404; + rewrite ^((?!.(js|css|png|svg|jpg|woff|woff2)).)*$ /index.html break; + proxy_intercept_errors on; # see frontend://nginx.org/en/docs/frontend/ngx_frontend_proxy_module.html#proxy_intercept_errors + error_page 404 =200 /index.html; } } From 898513dd0962251d55f70cab0d08928e440751eb Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 1 Aug 2022 19:11:34 +0200 Subject: [PATCH 019/168] chore(helm): heuristics mount persistence Signed-off-by: rjshrjndrn --- .../charts/heuristics/templates/deployment.yaml | 8 ++++++++ .../openreplay/charts/heuristics/values.yaml | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/scripts/helmcharts/openreplay/charts/heuristics/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/heuristics/templates/deployment.yaml index 995e8eac2..58059f58d 100644 --- a/scripts/helmcharts/openreplay/charts/heuristics/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/heuristics/templates/deployment.yaml @@ -60,8 +60,16 @@ spec: containerPort: {{ $val }} protocol: TCP {{- end }} + {{- with .Values.persistence.mounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.persistence.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/heuristics/values.yaml b/scripts/helmcharts/openreplay/charts/heuristics/values.yaml index 12d2346a3..ec8400866 100644 --- a/scripts/helmcharts/openreplay/charts/heuristics/values.yaml +++ b/scripts/helmcharts/openreplay/charts/heuristics/values.yaml @@ -97,3 +97,14 @@ nodeSelector: {} tolerations: [] affinity: {} + +persistence: {} + # # Spec of spec.template.spec.containers[*].volumeMounts + # mounts: + # - name: kafka-ssl + # mountPath: /opt/kafka/ssl + # # Spec of spec.template.spec.volumes + # volumes: + # - name: kafka-ssl + # secret: + # secretName: kafka-ssl From 7a07035771098d2cb9c5e189bf7f345a8460f539 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Wed, 3 Aug 2022 19:53:00 +0200 Subject: [PATCH 020/168] feat(backend): AWS_SKIP_SSL_VALIDATION env var --- backend/Dockerfile | 1 + backend/pkg/env/aws.go | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/backend/Dockerfile b/backend/Dockerfile index 28bedcb40..e83ec1802 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -46,6 +46,7 @@ ENV TZ=UTC \ AWS_REGION_WEB=eu-central-1 \ AWS_REGION_IOS=eu-west-1 \ AWS_REGION_ASSETS=eu-central-1 \ + AWS_SKIP_SSL_VALIDATION=false \ CACHE_ASSETS=true \ ASSETS_SIZE_LIMIT=6291456 \ ASSETS_HEADERS="{ \"Cookie\": \"ABv=3;\" }" \ diff --git a/backend/pkg/env/aws.go b/backend/pkg/env/aws.go index cb7445797..8292cb710 100644 --- a/backend/pkg/env/aws.go +++ b/backend/pkg/env/aws.go @@ -1,7 +1,9 @@ package env import ( + "crypto/tls" "log" + "net/http" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" @@ -20,6 +22,15 @@ func AWSSessionOnRegion(region string) *_session.Session { config.Endpoint = aws.String(AWS_ENDPOINT) config.DisableSSL = aws.Bool(true) config.S3ForcePathStyle = aws.Bool(true) + + AWS_SKIP_SSL_VALIDATION := Bool("AWS_SKIP_SSL_VALIDATION") + if !AWS_SKIP_SSL_VALIDATION { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + config.HTTPClient = client + } } aws_session, err := _session.NewSession(config) if err != nil { From 54d663606016909f17b12d42dbba7b81ad81fc5c Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Wed, 3 Aug 2022 20:22:27 +0200 Subject: [PATCH 021/168] fixup! feat(backend): AWS_SKIP_SSL_VALIDATION env var --- backend/pkg/env/aws.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/pkg/env/aws.go b/backend/pkg/env/aws.go index 8292cb710..e25a3a561 100644 --- a/backend/pkg/env/aws.go +++ b/backend/pkg/env/aws.go @@ -24,7 +24,7 @@ func AWSSessionOnRegion(region string) *_session.Session { config.S3ForcePathStyle = aws.Bool(true) AWS_SKIP_SSL_VALIDATION := Bool("AWS_SKIP_SSL_VALIDATION") - if !AWS_SKIP_SSL_VALIDATION { + if AWS_SKIP_SSL_VALIDATION { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } From c5208bf2c3dcfe59c17fbf7ef38df5a84c49ef6e Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Thu, 4 Aug 2022 18:58:26 +0200 Subject: [PATCH 022/168] feat(chalice): SMTP allow empty username --- api/chalicelib/utils/smtp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/chalicelib/utils/smtp.py b/api/chalicelib/utils/smtp.py index 3615ca71a..403f86e0f 100644 --- a/api/chalicelib/utils/smtp.py +++ b/api/chalicelib/utils/smtp.py @@ -30,7 +30,8 @@ class SMTPClient: self.server.starttls() # stmplib docs recommend calling ehlo() before & after starttls() self.server.ehlo() - self.server.login(user=config("EMAIL_USER"), password=config("EMAIL_PASSWORD")) + if len(config("EMAIL_USER", default="")) > 0 and len(config("EMAIL_PASSWORD", default="")) > 0: + self.server.login(user=config("EMAIL_USER"), password=config("EMAIL_PASSWORD")) return self.server def __exit__(self, *args): From bd28c4ea0f01c826036f2c9184e48c94b3bb06e1 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 4 Aug 2022 22:00:48 +0200 Subject: [PATCH 023/168] fix(ui) - network request operator value --- .../components/shared/Filters/FilterOperator/FilterOperator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/shared/Filters/FilterOperator/FilterOperator.tsx b/frontend/app/components/shared/Filters/FilterOperator/FilterOperator.tsx index 1446891e0..af977e975 100644 --- a/frontend/app/components/shared/Filters/FilterOperator/FilterOperator.tsx +++ b/frontend/app/components/shared/Filters/FilterOperator/FilterOperator.tsx @@ -79,7 +79,7 @@ function FilterOperator(props: Props) { placeholder="Select" isDisabled={isDisabled} value={value ? options.find((i: any) => i.value === value) : null} - onChange={({ value }: any) => onChange(null, { name: 'operator', value })} + onChange={({ value }: any) => onChange(null, { name: 'operator', value: value.value })} />
); From 7b6d55a4d49beffd11c9ea4afec05ca09b65d461 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 4 Aug 2022 22:07:09 +0200 Subject: [PATCH 024/168] fix(ui) - requestbody based on project settings --- frontend/app/components/shared/SessionSearch/SessionSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx index 66bd28a1b..bf0153057 100644 --- a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx +++ b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx @@ -89,6 +89,6 @@ function SessionSearch(props: Props) { } export default connect((state: any) => ({ - saveRequestPayloads: state.getIn(['site', 'active', 'saveRequestPayloads']), + saveRequestPayloads: state.getIn(['site', 'instance', 'saveRequestPayloads']), appliedFilter: state.getIn([ 'search', 'instance' ]), }), { edit, addFilter })(SessionSearch); From 4dd1431f01b4ed76cb3d8f4d49e6e788c9617c3f Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Fri, 5 Aug 2022 10:58:23 +0200 Subject: [PATCH 025/168] feat(chalice): fixed unkeyed connection issue --- api/chalicelib/utils/pg_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/chalicelib/utils/pg_client.py b/api/chalicelib/utils/pg_client.py index eda7747f8..014cfaeee 100644 --- a/api/chalicelib/utils/pg_client.py +++ b/api/chalicelib/utils/pg_client.py @@ -111,7 +111,7 @@ class PostgresClient: else: raise error finally: - if not self.long_query: + if not self.long_query and not self.unlimited_query: postgreSQL_pool.putconn(self.connection) From 7ecfd4bd5a76bed38a941cb9871d22736a33d2cb Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Fri, 5 Aug 2022 17:52:16 +0200 Subject: [PATCH 026/168] feat(chalice): fixed search sessions by any error --- api/chalicelib/core/sessions.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/api/chalicelib/core/sessions.py b/api/chalicelib/core/sessions.py index 738a5e3d9..c044a5819 100644 --- a/api/chalicelib/core/sessions.py +++ b/api/chalicelib/core/sessions.py @@ -712,13 +712,13 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr event.value, value_key=e_k)) elif event_type == events.event_type.ERROR.ui_type: event_from = event_from % f"{events.event_type.ERROR.table} AS main INNER JOIN public.errors AS main1 USING(error_id)" - event.source = tuple(event.source) + event.source = list(set(event.source)) if not is_any and event.value not in [None, "*", ""]: event_where.append( _multiple_conditions(f"(main1.message {op} %({e_k})s OR main1.name {op} %({e_k})s)", event.value, value_key=e_k)) if event.source[0] not in [None, "*", ""]: - event_where.append(_multiple_conditions(f"main1.source = %({s_k})s", event.value, value_key=s_k)) + event_where.append(_multiple_conditions(f"main1.source = %({s_k})s", event.source, value_key=s_k)) # ----- IOS @@ -877,7 +877,8 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr apply = True elif f.type == schemas.FetchFilterType._duration: event_where.append( - _multiple_conditions(f"main.duration {f.operator} %({e_k_f})s::integer", f.value, value_key=e_k_f)) + _multiple_conditions(f"main.duration {f.operator} %({e_k_f})s::integer", f.value, + value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._request_body: event_where.append( @@ -885,7 +886,8 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr apply = True elif f.type == schemas.FetchFilterType._response_body: event_where.append( - _multiple_conditions(f"main.response_body {op} %({e_k_f})s::text", f.value, value_key=e_k_f)) + _multiple_conditions(f"main.response_body {op} %({e_k_f})s::text", f.value, + value_key=e_k_f)) apply = True else: print(f"undefined FETCH filter: {f.type}") From 25e42fd6c533e34e7baf09c7bde44057c2b0294b Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 5 Aug 2022 19:12:10 +0200 Subject: [PATCH 027/168] fix(ui) - date range selection on switching the project --- .../app/components/BugFinder/BugFinder.js | 198 +++++++++--------- .../SessionList/SessionListHeader.js | 3 +- .../shared/Filters/FilterItem/FilterItem.tsx | 2 +- frontend/app/duck/search.js | 7 +- 4 files changed, 106 insertions(+), 104 deletions(-) diff --git a/frontend/app/components/BugFinder/BugFinder.js b/frontend/app/components/BugFinder/BugFinder.js index d3a63e49a..2c31a3ca8 100644 --- a/frontend/app/components/BugFinder/BugFinder.js +++ b/frontend/app/components/BugFinder/BugFinder.js @@ -2,14 +2,12 @@ import React from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; import withPageTitle from 'HOCs/withPageTitle'; -import { - fetchFavoriteList as fetchFavoriteSessionList -} from 'Duck/sessions'; +import { fetchFavoriteList as fetchFavoriteSessionList } from 'Duck/sessions'; import { applyFilter, clearEvents, addAttribute } from 'Duck/filters'; import { KEYS } from 'Types/filter/customFilter'; import SessionList from './SessionList'; import stl from './bugFinder.module.css'; -import withLocationHandlers from "HOCs/withLocationHandlers"; +import withLocationHandlers from 'HOCs/withLocationHandlers'; import { fetch as fetchFilterVariables } from 'Duck/sources'; import { fetchSources } from 'Duck/customField'; import { setActiveTab } from 'Duck/search'; @@ -21,113 +19,113 @@ import { clearSearch, fetchSessions, addFilterByKeyAndValue } from 'Duck/search' import { FilterKey } from 'Types/filter/filterType'; const weakEqual = (val1, val2) => { - if (!!val1 === false && !!val2 === false) return true; - if (!val1 !== !val2) return false; - return `${ val1 }` === `${ val2 }`; -} + if (!!val1 === false && !!val2 === false) return true; + if (!val1 !== !val2) return false; + return `${val1}` === `${val2}`; +}; const allowedQueryKeys = [ - 'userOs', - 'userId', - 'userBrowser', - 'userDevice', - 'userCountry', - 'startDate', - 'endDate', - 'minDuration', - 'maxDuration', - 'referrer', - 'sort', - 'order', + 'userOs', + 'userId', + 'userBrowser', + 'userDevice', + 'userCountry', + 'startDate', + 'endDate', + 'minDuration', + 'maxDuration', + 'referrer', + 'sort', + 'order', ]; @withLocationHandlers() -@connect(state => ({ - filter: state.getIn([ 'filters', 'appliedFilter' ]), - variables: state.getIn([ 'customFields', 'list' ]), - sources: state.getIn([ 'customFields', 'sources' ]), - filterValues: state.get('filterValues'), - favoriteList: state.getIn([ 'sessions', 'favoriteList' ]), - currentProjectId: state.getIn([ 'site', 'siteId' ]), - sites: state.getIn([ 'site', 'list' ]), - watchdogs: state.getIn(['watchdogs', 'list']), - activeFlow: state.getIn([ 'filters', 'activeFlow' ]), - sessions: state.getIn([ 'sessions', 'list' ]), -}), { - fetchFavoriteSessionList, - applyFilter, - addAttribute, - fetchFilterVariables, - fetchSources, - clearEvents, - setActiveTab, - clearSearch, - fetchSessions, - addFilterByKeyAndValue, -}) -@withPageTitle("Sessions - OpenReplay") +@connect( + (state) => ({ + filter: state.getIn(['filters', 'appliedFilter']), + variables: state.getIn(['customFields', 'list']), + sources: state.getIn(['customFields', 'sources']), + filterValues: state.get('filterValues'), + favoriteList: state.getIn(['sessions', 'favoriteList']), + currentProjectId: state.getIn(['site', 'siteId']), + sites: state.getIn(['site', 'list']), + watchdogs: state.getIn(['watchdogs', 'list']), + activeFlow: state.getIn(['filters', 'activeFlow']), + sessions: state.getIn(['sessions', 'list']), + }), + { + fetchFavoriteSessionList, + applyFilter, + addAttribute, + fetchFilterVariables, + fetchSources, + clearEvents, + setActiveTab, + clearSearch, + fetchSessions, + addFilterByKeyAndValue, + } +) +@withPageTitle('Sessions - OpenReplay') export default class BugFinder extends React.PureComponent { - state = {showRehydratePanel: false} - constructor(props) { - super(props); + state = { showRehydratePanel: false }; + constructor(props) { + super(props); - // TODO should cache the response - // props.fetchSources().then(() => { - // defaultFilters[6] = { - // category: 'Collaboration', - // type: 'CUSTOM', - // keys: this.props.sources.filter(({type}) => type === 'collaborationTool').map(({ label, key }) => ({ type: 'CUSTOM', source: key, label: label, key, icon: 'integrations/' + key, isFilter: false })).toJS() - // }; - // defaultFilters[7] = { - // category: 'Logging Tools', - // type: 'ERROR', - // keys: this.props.sources.filter(({type}) => type === 'logTool').map(({ label, key }) => ({ type: 'ERROR', source: key, label: label, key, icon: 'integrations/' + key, isFilter: false })).toJS() - // }; - // }); - if (props.sessions.size === 0) { - props.fetchSessions(); + // TODO should cache the response + // props.fetchSources().then(() => { + // defaultFilters[6] = { + // category: 'Collaboration', + // type: 'CUSTOM', + // keys: this.props.sources.filter(({type}) => type === 'collaborationTool').map(({ label, key }) => ({ type: 'CUSTOM', source: key, label: label, key, icon: 'integrations/' + key, isFilter: false })).toJS() + // }; + // defaultFilters[7] = { + // category: 'Logging Tools', + // type: 'ERROR', + // keys: this.props.sources.filter(({type}) => type === 'logTool').map(({ label, key }) => ({ type: 'ERROR', source: key, label: label, key, icon: 'integrations/' + key, isFilter: false })).toJS() + // }; + // }); + // if (props.sessions.size === 0) { + // props.fetchSessions(); + // } + + const queryFilter = this.props.query.all(allowedQueryKeys); + if (queryFilter.hasOwnProperty('userId')) { + props.addFilterByKeyAndValue(FilterKey.USERID, queryFilter.userId); + } else { + if (props.sessions.size === 0) { + props.fetchSessions(); + } + } } - const queryFilter = this.props.query.all(allowedQueryKeys); - if (queryFilter.hasOwnProperty('userId')) { - props.addFilterByKeyAndValue(FilterKey.USERID, queryFilter.userId); - } else { - if (props.sessions.size === 0) { - props.fetchSessions(); - } - } - } + toggleRehydratePanel = () => { + this.setState({ showRehydratePanel: !this.state.showRehydratePanel }); + }; - toggleRehydratePanel = () => { - this.setState({ showRehydratePanel: !this.state.showRehydratePanel }) - } + setActiveTab = (tab) => { + this.props.setActiveTab(tab); + }; - setActiveTab = tab => { - this.props.setActiveTab(tab); - } + render() { + const { showRehydratePanel } = this.state; - render() { - const { showRehydratePanel } = this.state; - - return ( -
-
-
- -
-
- -
- - + return ( +
+
+
+ +
+
+ +
+ + +
+ +
+
- -
-
-
- ); - } + ); + } } diff --git a/frontend/app/components/BugFinder/SessionList/SessionListHeader.js b/frontend/app/components/BugFinder/SessionList/SessionListHeader.js index 606ecaf67..afb135099 100644 --- a/frontend/app/components/BugFinder/SessionList/SessionListHeader.js +++ b/frontend/app/components/BugFinder/SessionList/SessionListHeader.js @@ -26,6 +26,7 @@ function SessionListHeader({ activeTab, count, applyFilter, filter }) { }, [label]); const { startDate, endDate, rangeValue } = filter; + console.log('startDate', startDate); const period = new Record({ start: startDate, end: endDate, rangeName: rangeValue, timezoneOffset: getTimeZoneOffset() }); const onDateChange = (e) => { @@ -40,7 +41,7 @@ function SessionListHeader({ activeTab, count, applyFilter, filter }) { const dateValues = period.toJSON(); dateValues.startDate = moment(dateValues.startDate).startOf('day').utcOffset(getTimeZoneOffset(), true).valueOf(); dateValues.endDate = moment(dateValues.endDate).endOf('day').utcOffset(getTimeZoneOffset(), true).valueOf(); - applyFilter(dateValues); + // applyFilter(dateValues); } }, [label]); diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index 3d82dae7c..223880b39 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -29,7 +29,7 @@ function FilterItem(props: Props) { }; const onOperatorChange = (e: any, { name, value }: any) => { - props.onUpdate({ ...filter, operator: value.value }); + props.onUpdate({ ...filter, operator: value }); }; const onSourceOperatorChange = (e: any, { name, value }: any) => { diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index 71361da14..2080e1f85 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -86,6 +86,8 @@ function reducer(state = initialState, action = {}) { return state.set('filterSearchList', groupedList); case APPLY_SAVED_SEARCH: return state.set('savedSearch', action.filter); + case CLEAR_SEARCH: + return state.set('savedSearch', new SavedFilter({})); case EDIT_SAVED_SEARCH: return state.mergeIn(['savedSearch'], action.instance); case UPDATE_CURRENT_PAGE: @@ -286,8 +288,9 @@ export function fetchFilterSearch(params) { } export const clearSearch = () => (dispatch, getState) => { - dispatch(applySavedSearch(new SavedFilter({}))); - dispatch(edit(new Filter({ filters: [] }))); + const filter = getState().getIn(['search', 'instance']); + // dispatch(applySavedSearch(new SavedFilter({}))); + dispatch(edit(new Filter({ startDate: filter.startDate, endDate: filter.endDate, rangeValue: filter.rangeValue, filters: [] }))); return dispatch({ type: CLEAR_SEARCH, }); From 4c65971eaff60b6fc9c34719a0c00b00974d5720 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 5 Aug 2022 19:13:04 +0200 Subject: [PATCH 028/168] change(ui) - hide fetch from filters --- frontend/app/types/filter/newFilter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index 9a87da2c6..452c4f1c9 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -11,7 +11,7 @@ export const filters = [ { key: FilterKey.INPUT, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Input', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/input', isEvent: true }, { key: FilterKey.LOCATION, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Path', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/location', isEvent: true }, { key: FilterKey.CUSTOM, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Custom Events', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/custom', isEvent: true }, - { key: FilterKey.REQUEST, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Fetch', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', isEvent: true }, + // { key: FilterKey.REQUEST, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Fetch', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', isEvent: true }, { key: FilterKey.FETCH, type: FilterType.SUB_FILTERS, category: FilterCategory.JAVASCRIPT, operator: 'is', label: 'Network Request', filters: [ { key: FilterKey.FETCH_URL, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with URL', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, { key: FilterKey.FETCH_STATUS_CODE, type: FilterType.NUMBER_MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with status code', operator: '=', operatorOptions: filterOptions.customOperators, icon: 'filters/fetch' }, From 2e009fae643adf95dc9f7852c17ace48eafc2c1b Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 5 Aug 2022 19:14:05 +0200 Subject: [PATCH 029/168] fix(ui) - fix typo --- .../components/Client/Users/components/UserForm/UserForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx b/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx index 477e42d8f..d1799cc6a 100644 --- a/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx +++ b/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx @@ -102,7 +102,7 @@ function UserForm(props: Props) { - + return ( +
+

{role.exists() ? 'Edit Role' : 'Create Role'}

+
+
+ + + + - - + + -
- -
-
All Projects
- - (Uncheck to select specific projects) - -
-
- { !role.allProjects && ( - <> - writeOption({ name: 'projects', value: value.value })} + value={null} + /> + {role.projects.size > 0 && ( +
+ {role.projects.map((p) => OptionLabel(projectsMap, p, onChangeProjects))} +
+ )} + + )} +
+ + + + writeOption({ name: 'permissions', value: value.value }) } - value={null} - /> - { role.permissions.size > 0 && ( -
- { role.permissions.map(p => ( - OptionLabel(permissionsMap, p, onChangePermissions) - )) }
- )} -
- - -
-
- - { role.exists() && ( - - )}
- { role.exists() && ( - - )} -
-
- ); -} + ); +}; -export default connect((state: any) => { - const role = state.getIn(['roles', 'instance']) - const projects = state.getIn([ 'site', 'list' ]) - return { - role, - projectOptions: projects.map((p: any) => ({ - key: p.get('id'), - value: p.get('id'), - label: p.get('name'), - // isDisabled: role.projects.includes(p.get('id')), - })).filter(({ value }: any) => !role.projects.includes(value)).toJS(), - permissions: state.getIn(['roles', 'permissions']).filter(({ value }: any) => !role.permissions.includes(value)) - .map(({ text, value }: any) => ({ label: text, value })).toJS(), - saving: state.getIn([ 'roles', 'saveRequest', 'loading' ]), - projectsMap: projects.reduce((acc: any, p: any) => { - acc[ p.get('id') ] = p.get('name') - return acc - } - , {}), - } -}, { edit, save })(RoleForm); +export default connect( + (state: any) => { + const role = state.getIn(['roles', 'instance']); + const projects = state.getIn(['site', 'list']); + return { + role, + projectOptions: projects + .map((p: any) => ({ + key: p.get('id'), + value: p.get('id'), + label: p.get('name'), + // isDisabled: role.projects.includes(p.get('id')), + })) + .filter(({ value }: any) => !role.projects.includes(value)) + .toJS(), + permissions: state + .getIn(['roles', 'permissions']) + .filter(({ value }: any) => !role.permissions.includes(value)) + .map(({ text, value }: any) => ({ label: text, value })) + .toJS(), + saving: state.getIn(['roles', 'saveRequest', 'loading']), + projectsMap: projects.reduce((acc: any, p: any) => { + acc[p.get('id')] = p.get('name'); + return acc; + }, {}), + }; + }, + { edit, save } +)(RoleForm); function OptionLabel(nameMap: any, p: any, onChangeOption: (e: any) => void) { - return
-
{nameMap[p]}
-
onChangeOption(p)}> - -
-
+ return ( +
+
{nameMap[p]}
+
onChangeOption(p)}> + +
+
+ ); } diff --git a/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx b/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx index 8cad11ae8..391e7ab93 100644 --- a/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx +++ b/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx @@ -1,64 +1,58 @@ -import React from 'react' -import { Icon, Link } from 'UI' -import stl from './roleItem.module.css' -import cn from 'classnames' +import React from 'react'; +import { Icon, Link, Button } from 'UI'; +import stl from './roleItem.module.css'; +import cn from 'classnames'; import { CLIENT_TABS, client as clientRoute } from 'App/routes'; - function PermisionLabel({ label }: any) { - return ( -
{ label }
- ); + return
{label}
; } function PermisionLabelLinked({ label, route }: any) { - return ( -
{ label }
- ); + return ( + +
{label}
+ + ); } interface Props { - role: any, - deleteHandler?: (role: any) => void, - editHandler?: (role: any) => void, - permissions: any, - isAdmin: boolean, - projects: any, + role: any; + deleteHandler?: (role: any) => void; + editHandler?: (role: any) => void; + permissions: any; + isAdmin: boolean; + projects: any; } function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions, projects }: Props) { - return ( -
-
- - { role.name } -
-
- {role.allProjects ? ( - - ) : ( - role.projects.map(p => ( - - )) - )} -
-
-
- {role.permissions.map((permission: any) => ( - - ))} -
- -
- {isAdmin && !!editHandler && -
editHandler(role) }> - + return ( +
+
+ + {role.name} +
+
+ {role.allProjects ? ( + + ) : ( + role.projects.map((p) => ) + )} +
+
+
+ {role.permissions.map((permission: any) => ( + + ))} +
+ +
+ {isAdmin && !!editHandler && ( +
- }
-
- -
- ); + ); } -export default RoleItem; \ No newline at end of file +export default RoleItem; diff --git a/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx b/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx index c6d04f2f4..0fe5fce65 100644 --- a/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx +++ b/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx @@ -16,7 +16,7 @@ function InstallButton(props: Props) { ); }; return ( - ); diff --git a/frontend/app/components/Client/Sites/Sites.js b/frontend/app/components/Client/Sites/Sites.js index 4158a57ea..ab5f5be25 100644 --- a/frontend/app/components/Client/Sites/Sites.js +++ b/frontend/app/components/Client/Sites/Sites.js @@ -130,5 +130,5 @@ function EditButton({ isAdmin, onClick }) { onClick(); showModal(); }; - return
-
- -
+
); From 76673c1078cf9cb903e0a2ff94723f191522bb8e Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 8 Aug 2022 12:08:50 +0200 Subject: [PATCH 033/168] change(ui) - link color --- frontend/app/styles/general.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/styles/general.css b/frontend/app/styles/general.css index 180abc3b0..05e1d31f3 100644 --- a/frontend/app/styles/general.css +++ b/frontend/app/styles/general.css @@ -254,7 +254,7 @@ p { } .link { - color: $blue !important; + color: $teal !important; cursor: pointer; &:hover { text-decoration: underline !important; From 8a8f1b734d6ba23eb7d89aedceba788deb19df57 Mon Sep 17 00:00:00 2001 From: Delirium Date: Mon, 8 Aug 2022 13:38:55 +0300 Subject: [PATCH 034/168] fix(ui): fix predefined metrics data fetch (#661) * fix(ui): fix predefined metrics data fetch * fix(ui): remove unused code --- .../ErrorsByOrigin/ErrorsByOrigin.tsx | 7 ++++--- .../components/WidgetChart/WidgetChart.tsx | 7 ++++--- frontend/app/mstore/dashboardStore.ts | 14 +++++++++----- frontend/app/mstore/index.tsx | 4 ++-- frontend/app/mstore/notificationStore.ts | 9 +++++++-- frontend/app/mstore/userStore.ts | 9 +++++++-- 6 files changed, 33 insertions(+), 17 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx index f50859051..e2a80c736 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx @@ -4,7 +4,7 @@ import { NoContent } from 'UI'; import { Styles } from '../../common'; import { BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + Legend, ResponsiveContainer, XAxis, YAxis } from 'recharts'; @@ -13,7 +13,8 @@ interface Props { metric?: any } function ErrorsByOrigin(props: Props) { - const { data, metric } = props; + const { metric } = props; + return ( { if (prevMetricRef.current && prevMetricRef.current.name !== metric.name) { - prevMetricRef.current = metric; - return + prevMetricRef.current = metric; + return }; prevMetricRef.current = metric; const timestmaps = drillDownPeriod.toTimestamps(); @@ -106,10 +106,11 @@ function WidgetChart(props: Props) { } if (metricType === 'predefined') { + const defaultMetric = metric.data.chart.length === 0 ? metricWithData : metric if (isOverviewWidget) { return } - return + return } if (metricType === 'timeseries') { diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 54fbe1972..5fe8c9eb3 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -19,7 +19,7 @@ import Session from "./types/session"; import Error from "./types/error"; import { FilterKey } from "Types/filter/filterType"; -export interface IDashboardSotre { +export interface IDashboardStore { dashboards: IDashboard[]; selectedDashboard: IDashboard | null; dashboardInstance: IDashboard; @@ -85,12 +85,12 @@ export interface IDashboardSotre { ): Promise; setPeriod(period: any): void; } -export default class DashboardStore implements IDashboardSotre { +export default class DashboardStore implements IDashboardStore { siteId: any = null; // Dashbaord / Widgets dashboards: Dashboard[] = []; selectedDashboard: Dashboard | null = null; - dashboardInstance: IDashboard = new Dashboard(); + dashboardInstance: Dashboard = new Dashboard(); selectedWidgets: IWidget[] = []; currentWidget: Widget = new Widget(); widgetCategories: any[] = []; @@ -214,7 +214,7 @@ export default class DashboardStore implements IDashboardSotre { } fetch(dashboardId: string): Promise { - this.fetchingDashboard = true; + this.setFetchingDashboard(true); return dashboardService .getDashboard(dashboardId) .then((response) => { @@ -223,10 +223,14 @@ export default class DashboardStore implements IDashboardSotre { }); }) .finally(() => { - this.fetchingDashboard = false; + this.setFetchingDashboard(false); }); } + setFetchingDashboard(value: boolean) { + this.fetchingDashboard = value; + } + save(dashboard: IDashboard): Promise { this.isSaving = true; const isCreating = !dashboard.dashboardId; diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx index c6ffeb460..445a174f3 100644 --- a/frontend/app/mstore/index.tsx +++ b/frontend/app/mstore/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import DashboardStore, { IDashboardSotre } from './dashboardStore'; +import DashboardStore, { IDashboardStore } from './dashboardStore'; import MetricStore, { IMetricStore } from './metricStore'; import UserStore from './userStore'; import RoleStore from './roleStore'; @@ -12,7 +12,7 @@ import NotificationStore from './notificationStore'; import ErrorStore from './errorStore'; export class RootStore { - dashboardStore: IDashboardSotre; + dashboardStore: IDashboardStore; metricStore: IMetricStore; funnelStore: FunnelStore; settingsStore: SettingsStore; diff --git a/frontend/app/mstore/notificationStore.ts b/frontend/app/mstore/notificationStore.ts index 13124ca62..76f63ac2e 100644 --- a/frontend/app/mstore/notificationStore.ts +++ b/frontend/app/mstore/notificationStore.ts @@ -18,6 +18,7 @@ export default class NotificationStore { fetchNotifications: action, ignoreAllNotifications: action, ignoreNotification: action, + setNotificationsCount: action, }); } @@ -74,15 +75,19 @@ export default class NotificationStore { }); } + setNotificationsCount(count: number) { + this.notificationsCount = count; + } + fetchNotificationsCount(): Promise { return new Promise((resolve, reject) => { userService.getNotificationsCount() .then((response: any) => { - this.notificationsCount = response.count; + this.setNotificationsCount(response.count); resolve(response); }).catch((error: any) => { reject(error); }); }); } -} \ No newline at end of file +} diff --git a/frontend/app/mstore/userStore.ts b/frontend/app/mstore/userStore.ts index 0a5c47f53..523ae04f7 100644 --- a/frontend/app/mstore/userStore.ts +++ b/frontend/app/mstore/userStore.ts @@ -23,6 +23,7 @@ export default class UserStore { updateUser: action, updateKey: action, initUser: action, + setLimits: action, }) } @@ -30,7 +31,7 @@ export default class UserStore { return new Promise((resolve, reject) => { userService.getLimits() .then((response: any) => { - this.limits = response; + this.setLimits(response); resolve(response); }).catch((error: any) => { reject(error); @@ -38,6 +39,10 @@ export default class UserStore { }); } + setLimits(limits: any) { + this.limits = limits; + } + initUser(user?: any ): Promise { return new Promise((resolve, reject) => { if (user) { @@ -175,4 +180,4 @@ export default class UserStore { return promise; } -} \ No newline at end of file +} From bb074ee36e184973ca2930d0b5b1817e7e54407f Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 8 Aug 2022 12:58:47 +0200 Subject: [PATCH 035/168] change(ui) - loader margin --- .../components/Client/Integrations/Integrations.tsx | 2 +- .../SlackChannelList/SlackChannelList.js | 6 +++++- .../app/components/Client/Integrations/SlackForm.tsx | 12 ++++++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend/app/components/Client/Integrations/Integrations.tsx b/frontend/app/components/Client/Integrations/Integrations.tsx index 92c050ea0..33d0520d4 100644 --- a/frontend/app/components/Client/Integrations/Integrations.tsx +++ b/frontend/app/components/Client/Integrations/Integrations.tsx @@ -80,7 +80,7 @@ function Integrations(props: Props) {

{cat.title}

{cat.isProject && (
-
+
{loading && cat.isProject && } diff --git a/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js b/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js index 07f1aa123..8d25b4454 100644 --- a/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js +++ b/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js @@ -27,7 +27,11 @@ function SlackChannelList(props) { show={list.size === 0} > {list.map((c) => ( -
onEdit(c)}> +
onEdit(c)} + >
{c.name}
{c.endpoint}
diff --git a/frontend/app/components/Client/Integrations/SlackForm.tsx b/frontend/app/components/Client/Integrations/SlackForm.tsx index 207f9b765..e04c02de6 100644 --- a/frontend/app/components/Client/Integrations/SlackForm.tsx +++ b/frontend/app/components/Client/Integrations/SlackForm.tsx @@ -24,16 +24,16 @@ const SlackForm = (props: Props) => { }, []); return ( -
-
-

Slack

- -
+
{active && ( -
+
setActive(false)} />
)} +
+

Slack

+ +
); }; From 334eb69edd8f5910d329f9f2e6299bc7dece767a Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 8 Aug 2022 14:26:21 +0200 Subject: [PATCH 036/168] change(ui) - slack listing and add button --- .../components/Client/Integrations/SlackForm.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/app/components/Client/Integrations/SlackForm.tsx b/frontend/app/components/Client/Integrations/SlackForm.tsx index e04c02de6..7d0cdc610 100644 --- a/frontend/app/components/Client/Integrations/SlackForm.tsx +++ b/frontend/app/components/Client/Integrations/SlackForm.tsx @@ -1,14 +1,16 @@ import React, { useEffect } from 'react'; import SlackChannelList from './SlackChannelList/SlackChannelList'; -import { fetchList } from 'Duck/integrations/slack'; +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 { onEdit: (integration: any) => void; istance: any; fetchList: any; + init: any; } const SlackForm = (props: Props) => { const { istance } = props; @@ -19,6 +21,11 @@ const SlackForm = (props: Props) => { setActive(true); }; + const onNew = () => { + setActive(true); + props.init({}); + } + useEffect(() => { props.fetchList(); }, []); @@ -31,7 +38,10 @@ const SlackForm = (props: Props) => {
)}
-

Slack

+
+

Slack

+
@@ -44,5 +54,5 @@ export default connect( (state: any) => ({ istance: state.getIn(['slack', 'instance']), }), - { fetchList } + { fetchList, init } )(SlackForm); From 6b7f66e94932e6445d0993e8f081504269ff6418 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 8 Aug 2022 17:23:53 +0200 Subject: [PATCH 037/168] feat(ui) - overview - errordetails modal --- .../Session_/OverviewPanel/OverviewPanel.tsx | 20 ++++- .../components/EventRow/EventRow.tsx | 2 +- .../FeatureSelection/FeatureSelection.tsx | 73 +++++++++++++++++ .../TimelineScale/TimelineScale.tsx | 14 ++++ .../components/TimelineScale/index.ts | 1 + .../app/components/ui/Checkbox/Checkbox.tsx | 25 +++--- .../ui/ErrorDetails/ErrorDetails.js | 65 --------------- .../ui/ErrorDetails/ErrorDetails.tsx | 79 +++++++++++++++++++ 8 files changed, 195 insertions(+), 84 deletions(-) create mode 100644 frontend/app/components/Session_/OverviewPanel/components/FeatureSelection/FeatureSelection.tsx create mode 100644 frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx create mode 100644 frontend/app/components/Session_/OverviewPanel/components/TimelineScale/index.ts delete mode 100644 frontend/app/components/ui/ErrorDetails/ErrorDetails.js create mode 100644 frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index 87dc542d6..a982e6fd8 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -4,10 +4,13 @@ import React from 'react'; import BottomBlock from '../BottomBlock'; import EventRow from './components/EventRow'; import { TYPES } from 'Types/session/event'; -import { Icon } from 'UI'; +import { Icon, Checkbox, ErrorDetails } from 'UI'; import { Tooltip } from 'react-tippy'; import stl from './overviewPanel.module.css'; import { connect } from 'react-redux'; +import TimelineScale from './components/TimelineScale'; +import FeatureSelection from './components/FeatureSelection/FeatureSelection'; +import { useModal } from 'App/components/Modal'; interface Props { resourceList: any[]; @@ -22,6 +25,8 @@ function OverviewPanel(props: Props) { return eventsList.filter((item: any) => item.type === TYPES.CLICKRAGE); }, [eventsList]); const scale = 100 / endTime; + const selectedFeatures = React.useMemo(() => ['NETWORK', 'ERRORS', 'EVENTS'], []); + const { showModal } = useModal(); const createEventClickHandler = (pointer: any, type: any) => (e: any) => { e.stopPropagation(); @@ -30,7 +35,10 @@ function OverviewPanel(props: Props) { return; } - props.toggleBottomBlock(type); + if (type === EXCEPTIONS) { + showModal(, { right: true }); + } + // props.toggleBottomBlock(type); }; const renderNetworkElement = (item: any) => { @@ -92,12 +100,16 @@ function OverviewPanel(props: Props) { }; return ( - + Overview +
+ {}} /> +
-
+
+
diff --git a/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx b/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx index b1fec7cec..f06003463 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx @@ -20,7 +20,7 @@ function EventRow(props: Props) { }) }, [list]); return ( -
+
{title}
{_list.map((item: any, index: number) => { diff --git a/frontend/app/components/Session_/OverviewPanel/components/FeatureSelection/FeatureSelection.tsx b/frontend/app/components/Session_/OverviewPanel/components/FeatureSelection/FeatureSelection.tsx new file mode 100644 index 000000000..c4bb3905e --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/FeatureSelection/FeatureSelection.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { Checkbox } from 'UI'; + +const NETWORK = 'NETWORK'; +const ERRORS = 'ERRORS'; +const EVENTS = 'EVENTS'; +const CLICKRAGE = 'CLICK RAGE'; +const PERFORMANCE = 'PERFORMANCE'; + +interface Props { + list: any[]; + updateList: any; +} +function FeatureSelection(props: Props) { + const { list } = props; + + return ( + + { + console.log(e); + }} + label={NETWORK} + /> + { + console.log(e); + }} + label={ERRORS} + /> + { + console.log(e); + }} + label={EVENTS} + /> + { + console.log(e); + }} + label={CLICKRAGE} + /> + { + console.log(e); + }} + label={PERFORMANCE} + /> + + ); +} + +export default FeatureSelection; diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx b/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx new file mode 100644 index 000000000..e0b4be0b7 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +interface Props { + +} +function TimelineScale(props: Props) { + return ( +
+ +
+ ); +} + +export default TimelineScale; \ No newline at end of file diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/index.ts b/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/index.ts new file mode 100644 index 000000000..9a2302a32 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/index.ts @@ -0,0 +1 @@ +export { default } from './TimelineScale'; \ No newline at end of file diff --git a/frontend/app/components/ui/Checkbox/Checkbox.tsx b/frontend/app/components/ui/Checkbox/Checkbox.tsx index 0781183b1..2b68ccc97 100644 --- a/frontend/app/components/ui/Checkbox/Checkbox.tsx +++ b/frontend/app/components/ui/Checkbox/Checkbox.tsx @@ -2,19 +2,16 @@ import React from 'react'; import cn from 'classnames'; interface Props { - classNam?: string; - label?: string; - [x: string]: any; + classNam?: string; + label?: string; + [x: string]: any; } export default (props: Props) => { - const { className = '', label = '', ...rest } = props; - return ( - - ) -}; \ No newline at end of file + const { className = '', label = '', ...rest } = props; + return ( + + ); +}; diff --git a/frontend/app/components/ui/ErrorDetails/ErrorDetails.js b/frontend/app/components/ui/ErrorDetails/ErrorDetails.js deleted file mode 100644 index 2a6afdd1e..000000000 --- a/frontend/app/components/ui/ErrorDetails/ErrorDetails.js +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useState } from 'react' -import ErrorFrame from '../ErrorFrame/ErrorFrame' -import cn from 'classnames'; -import { IconButton, Icon } from 'UI'; -import { connect } from 'react-redux'; - -const docLink = 'https://docs.openreplay.com/installation/upload-sourcemaps'; - -function ErrorDetails({ className, name = "Error", message, errorStack, sourcemapUploaded }) { - const [showRaw, setShowRaw] = useState(false) - const firstFunc = errorStack.first() && errorStack.first().function - - const openDocs = () => { - window.open(docLink, '_blank'); - } - - return ( -
- { !sourcemapUploaded && ( -
- -
Source maps must be uploaded to OpenReplay to be able to see stack traces. Learn more.
-
- ) } -
-

- Stacktrace -

-
- setShowRaw(false) } - label="FULL" - plain={!showRaw} - primaryText={!showRaw} - /> - setShowRaw(true) } - plain={showRaw} - label="RAW" - /> -
-
-
-
{ name }
-
{message}
-
- { showRaw && -
{name} : {firstFunc ? firstFunc : '?' }
- } - { errorStack.map((frame, i) => ( -
- -
- )) - } -
- ) -} - -ErrorDetails.displayName = "ErrorDetails"; -export default ErrorDetails; diff --git a/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx b/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx new file mode 100644 index 000000000..331535461 --- /dev/null +++ b/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx @@ -0,0 +1,79 @@ +import React, { useEffect, useState } from 'react'; +import ErrorFrame from '../ErrorFrame/ErrorFrame'; +import { fetchErrorStackList } from 'Duck/sessions'; +import { IconButton, Icon } from 'UI'; +import { connect } from 'react-redux'; + +const docLink = 'https://docs.openreplay.com/installation/upload-sourcemaps'; + +interface Props { + fetchErrorStackList: any; + sourcemapUploaded?: boolean; + errorStack?: any; + message?: string; + sessionId: string; + error: any; +} +function ErrorDetails(props: Props) { + const { error, sessionId, message = '', errorStack = [], sourcemapUploaded = false } = props; + const [showRaw, setShowRaw] = useState(false); + const firstFunc = errorStack.first() && errorStack.first().function; + + const openDocs = () => { + window.open(docLink, '_blank'); + }; + + useEffect(() => { + props.fetchErrorStackList(sessionId, error.errorId); + }, []); + + return ( +
+ {!sourcemapUploaded && ( +
+ +
+ Source maps must be uploaded to OpenReplay to be able to see stack traces.{' '} + + Learn more. + +
+
+ )} +
+

Stacktrace

+
+ setShowRaw(false)} label="FULL" plain={!showRaw} primaryText={!showRaw} /> + setShowRaw(true)} plain={showRaw} label="RAW" /> +
+
+
+
{error.name}
+
{message}
+
+ {showRaw && ( +
+ {error.name} : {firstFunc ? firstFunc : '?'} +
+ )} + ; + {errorStack.map((frame: any, i: any) => ( +
+ +
+ ))} +
+ ); +} + +ErrorDetails.displayName = 'ErrorDetails'; +export default connect( + (state: any) => ({ + errorStack: state.getIn(['sessions', 'errorStack']), + sessionId: state.getIn(['sessions', 'current', 'sessionId']), + }), + { fetchErrorStackList } +)(ErrorDetails); From b7685b0239395537b0c3568471434a4bcf145249 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Mon, 8 Aug 2022 17:57:12 +0200 Subject: [PATCH 038/168] feat(chalice): fixed sentry proxy endpoint --- api/routers/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/routers/core.py b/api/routers/core.py index c69c7ff43..d9a36493c 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -222,7 +222,7 @@ def delete_sentry(projectId: int, context: schemas.CurrentContext = Depends(OR_c @app.get('/{projectId}/integrations/sentry/events/{eventId}', tags=["integrations"]) -def proxy_sentry(projectId: int, eventId: int, context: schemas.CurrentContext = Depends(OR_context)): +def proxy_sentry(projectId: int, eventId: str, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_sentry.proxy_get(tenant_id=context.tenant_id, project_id=projectId, event_id=eventId)} From 210cfe8511c97b4cd13a53f2977fce30e0e4bd5f Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Mon, 8 Aug 2022 19:42:45 +0200 Subject: [PATCH 039/168] feat(chalice): PG-client unkeyed connexion --- api/Dockerfile.alerts | 2 +- api/chalicelib/utils/pg_client.py | 22 ++++++++++++++++++---- api/env.default | 1 + ee/api/Dockerfile.alerts | 2 +- ee/api/Dockerfile.crons | 3 ++- ee/api/env.default | 1 + 6 files changed, 24 insertions(+), 7 deletions(-) diff --git a/api/Dockerfile.alerts b/api/Dockerfile.alerts index c4614b3c1..fdc3a9a36 100644 --- a/api/Dockerfile.alerts +++ b/api/Dockerfile.alerts @@ -5,7 +5,7 @@ RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/al RUN apk add --no-cache build-base tini ARG envarg ENV APP_NAME=alerts \ - pg_minconn=2 \ + pg_minconn=1 \ pg_maxconn=10 \ ENTERPRISE_BUILD=${envarg} diff --git a/api/chalicelib/utils/pg_client.py b/api/chalicelib/utils/pg_client.py index 014cfaeee..66f06f3e5 100644 --- a/api/chalicelib/utils/pg_client.py +++ b/api/chalicelib/utils/pg_client.py @@ -44,6 +44,8 @@ RETRY = 0 def make_pool(): + if not config('PG_POOL', cast=bool, default=True): + return global postgreSQL_pool global RETRY if postgreSQL_pool is not None: @@ -68,7 +70,8 @@ def make_pool(): raise error -make_pool() +if config('PG_POOL', cast=bool, default=True): + make_pool() class PostgresClient: @@ -87,8 +90,14 @@ class PostgresClient: elif long_query: long_config = dict(_PG_CONFIG) long_config["application_name"] += "-LONG" - long_config["options"] = f"-c statement_timeout={config('pg_long_timeout', cast=int, default=5 * 60) * 1000}" + long_config["options"] = f"-c statement_timeout=" \ + f"{config('pg_long_timeout', cast=int, default=5 * 60) * 1000}" self.connection = psycopg2.connect(**long_config) + elif not config('PG_POOL', cast=bool, default=True): + single_config = dict(_PG_CONFIG) + single_config["application_name"] += "-NOPOOL" + single_config["options"] = f"-c statement_timeout={config('pg_timeout', cast=int, default=3 * 60) * 1000}" + self.connection = psycopg2.connect(**single_config) else: self.connection = postgreSQL_pool.getconn() @@ -105,13 +114,18 @@ class PostgresClient: self.connection.close() except Exception as error: print("Error while committing/closing PG-connection", error) - if str(error) == "connection already closed" and not self.long_query and not self.unlimited_query: + if str(error) == "connection already closed" \ + and not self.long_query \ + and not self.unlimited_query \ + and config('PG_POOL', cast=bool, default=True): print("Recreating the connexion pool") make_pool() else: raise error finally: - if not self.long_query and not self.unlimited_query: + if config('PG_POOL', cast=bool, default=True) \ + and not self.long_query \ + and not self.unlimited_query: postgreSQL_pool.putconn(self.connection) diff --git a/api/env.default b/api/env.default index aa14fc993..01ad50606 100644 --- a/api/env.default +++ b/api/env.default @@ -40,6 +40,7 @@ pg_minconn=20 pg_maxconn=50 PG_RETRY_MAX=50 PG_RETRY_INTERVAL=2 +PG_POOL=true put_S3_TTL=20 sentryURL= sessions_bucket=mobs diff --git a/ee/api/Dockerfile.alerts b/ee/api/Dockerfile.alerts index 785b0a5f9..86a5915cd 100644 --- a/ee/api/Dockerfile.alerts +++ b/ee/api/Dockerfile.alerts @@ -5,7 +5,7 @@ RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/al RUN apk add --no-cache build-base tini ARG envarg ENV APP_NAME=alerts \ - pg_minconn=2 \ + pg_minconn=1 \ pg_maxconn=10 \ ENTERPRISE_BUILD=${envarg} diff --git a/ee/api/Dockerfile.crons b/ee/api/Dockerfile.crons index 0647c6fc6..a139cea80 100644 --- a/ee/api/Dockerfile.crons +++ b/ee/api/Dockerfile.crons @@ -8,7 +8,8 @@ ENV APP_NAME=crons \ pg_minconn=2 \ pg_maxconn=10 \ ENTERPRISE_BUILD=${envarg} \ - ACTION="" + ACTION="" \ + PG_POOL=false WORKDIR /work_tmp COPY requirements-crons.txt /work_tmp/requirements.txt diff --git a/ee/api/env.default b/ee/api/env.default index 7687566d7..65f55a03c 100644 --- a/ee/api/env.default +++ b/ee/api/env.default @@ -49,6 +49,7 @@ pg_minconn=20 pg_maxconn=50 PG_RETRY_MAX=50 PG_RETRY_INTERVAL=2 +PG_POOL=true put_S3_TTL=20 sentryURL= sessions_bucket=mobs From 5387f2a57e6c33512d4f8b520e9ca36370f7cd93 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 9 Aug 2022 12:07:02 +0200 Subject: [PATCH 040/168] feat(ui) - overview - components --- .../Session_/OverviewPanel/OverviewPanel.tsx | 112 ++++-------------- .../TimelinePointer/TimelinePointer.tsx | 101 ++++++++++++++++ .../components/TimelinePointer/index.ts | 1 + .../VerticalPointerLine.tsx | 17 +++ .../components/VerticalPointerLine/index.ts | 1 + .../app/components/Session_/Player/Player.js | 4 +- 6 files changed, 146 insertions(+), 90 deletions(-) create mode 100644 frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx create mode 100644 frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/index.ts create mode 100644 frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/VerticalPointerLine.tsx create mode 100644 frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/index.ts diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index a982e6fd8..02421b6f9 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -1,16 +1,14 @@ -import { connectPlayer, Controls } from 'App/player'; +import { connectPlayer } from 'App/player'; import { toggleBottomBlock, NETWORK, EXCEPTIONS } from 'Duck/components/player'; import React from 'react'; import BottomBlock from '../BottomBlock'; import EventRow from './components/EventRow'; import { TYPES } from 'Types/session/event'; -import { Icon, Checkbox, ErrorDetails } from 'UI'; -import { Tooltip } from 'react-tippy'; -import stl from './overviewPanel.module.css'; import { connect } from 'react-redux'; import TimelineScale from './components/TimelineScale'; import FeatureSelection from './components/FeatureSelection/FeatureSelection'; -import { useModal } from 'App/components/Modal'; +import TimelinePointer from './components/TimelinePointer'; +import VerticalPointerLine from './components/VerticalPointerLine'; interface Props { resourceList: any[]; @@ -26,78 +24,6 @@ function OverviewPanel(props: Props) { }, [eventsList]); const scale = 100 / endTime; const selectedFeatures = React.useMemo(() => ['NETWORK', 'ERRORS', 'EVENTS'], []); - const { showModal } = useModal(); - - const createEventClickHandler = (pointer: any, type: any) => (e: any) => { - e.stopPropagation(); - Controls.jump(pointer.time); - if (!type) { - return; - } - - if (type === EXCEPTIONS) { - showModal(, { right: true }); - } - // props.toggleBottomBlock(type); - }; - - const renderNetworkElement = (item: any) => { - return ( - - {item.success ? 'Slow resource: ' : 'Missing resource:'} -
- {item.name} -
- } - delay={0} - position="top" - > -
-
-
- - ); - }; - - const renderClickRageElement = (item: any) => { - return ( - - {'Click Rage'} -
- } - delay={0} - position="top" - > -
- -
- - ); - }; - - const renderExceptionElement = (item: any) => { - return ( - - {'Exception'} -
- {item.message} -
- } - delay={0} - position="top" - > -
- -
- - ); - }; return ( @@ -112,11 +38,29 @@ function OverviewPanel(props: Props) {
- + } + />
- + } + />
- + } + />
@@ -134,11 +78,3 @@ export default connect(null, { endTime: state.endTime, }))(OverviewPanel) ); - -const VerticalPointerLine = connectPlayer((state: any) => ({ - time: state.time, - scale: 100 / state.endTime, -}))(({ time, scale }: any) => { - const left = time * scale; - return
; -}); diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx new file mode 100644 index 000000000..b74cd068d --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { connectPlayer, Controls } from 'App/player'; +import { toggleBottomBlock, NETWORK, EXCEPTIONS } from 'Duck/components/player'; +import { useModal } from 'App/components/Modal'; +import { Icon, ErrorDetails } from 'UI'; +import { Tooltip } from 'react-tippy'; +import { TYPES as EVENT_TYPES } from 'Types/session/event'; + +interface Props { + pointer: any; + type: any; +} +function TimelinePointer(props: Props) { + const { showModal } = useModal(); + const createEventClickHandler = (pointer: any, type: any) => (e: any) => { + e.stopPropagation(); + Controls.jump(pointer.time); + if (!type) { + return; + } + + if (type === EXCEPTIONS) { + showModal(, { right: true }); + } + // props.toggleBottomBlock(type); + }; + + const renderNetworkElement = (item: any) => { + return ( + + {item.success ? 'Slow resource: ' : 'Missing resource:'} +
+ {item.name} +
+ } + delay={0} + position="top" + > +
+
+
+ + ); + }; + + const renderClickRageElement = (item: any) => { + return ( + + {'Click Rage'} +
+ } + delay={0} + position="top" + > +
+ +
+ + ); + }; + + const renderExceptionElement = (item: any) => { + return ( + + {'Exception'} +
+ {item.message} +
+ } + delay={0} + position="top" + > +
+ +
+ + ); + }; + + const render = () => { + const { pointer, type } = props; + if (type === NETWORK) { + return renderNetworkElement(pointer); + } + if (type === EVENT_TYPES.CLICKRAGE) { + return renderClickRageElement(pointer); + } + if (type === EXCEPTIONS) { + return renderExceptionElement(pointer); + } + }; + return
{render()}
; +} + +export default TimelinePointer; diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/index.ts b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/index.ts new file mode 100644 index 000000000..e0f9399ff --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/index.ts @@ -0,0 +1 @@ +export { default } from './TimelinePointer' \ No newline at end of file diff --git a/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/VerticalPointerLine.tsx b/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/VerticalPointerLine.tsx new file mode 100644 index 000000000..b8997e111 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/VerticalPointerLine.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { connectPlayer } from 'App/player'; + +interface Props { + time: number; + scale: number; +} +function VerticalPointerLine(props: Props) { + const { time, scale } = props; + const left = time * scale; + return
; +} + +export default connectPlayer((state: any) => ({ + time: state.time, + scale: 100 / state.endTime, +}))(VerticalPointerLine); diff --git a/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/index.ts b/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/index.ts new file mode 100644 index 000000000..4a75fc048 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/index.ts @@ -0,0 +1 @@ +export { default } from './VerticalPointerLine' \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index 9a95121ac..ef69d5c2d 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -104,9 +104,9 @@ export default class Player extends React.PureComponent { ref={ this.screenWrapper } />
- { !fullscreen && !!bottomBlock && + { // !fullscreen && !!bottomBlock &&
- { bottomBlock === OVERVIEW && + { // bottomBlock === OVERVIEW && } { bottomBlock === CONSOLE && From e2a10c075170002d92740d8ce3f401b39aa9cda5 Mon Sep 17 00:00:00 2001 From: sylenien Date: Wed, 13 Jul 2022 15:46:32 +0200 Subject: [PATCH 041/168] feat(ui/tracker/player): handle multiple callers in one assist session feat(ui/tracker/player): small fixes feat(ui/tracker/player): fix incoming streams feat(tracker): some logs feat(tracker): fix types, fix stream binding feat(tracker): more stuff for multicall... feat(tracker): more stuff for multicall... feat(tracker): more stuff for multicall... feat(tracker): more stuff for multicall... feat(tracker): more stuff for multicall... feat(tracker): more stuff for multicall... feat(tracker): more stuff for multicall... feat(tracker): rm async feat(tracker): rewrite stuff feat(tracker): rewrite stuff feat(tracker): rewrite stuff feat(tracker): rewrite stuff feat(tracker): rewrite stuff feat(tracker): rewrite lstream feat(tracker): rewrite lstream feat(tracker): rewrite stuff feat(tracker): rewrite stuff feat(tracker): fix group call feat(tracker): fix group call feat(tracker): fix group call feat(tracker): fix group call feat(tracker): add text to ui feat(tracker): destroy calls obj on call end feat(tracker): rm unused prop fix(tracker-assist):simplify addRemoteStream logic fixup! fix(tracker-assist):simplify addRemoteStream logic refactor(tracker-assist): make multi-agents call logic more explicite => fixed few bugs --- .../Assist/ChatControls/ChatControls.tsx | 12 +- .../Assist/ChatWindow/ChatWindow.tsx | 27 +- .../AssistActions/AssistActions.tsx | 66 +++-- .../components/Session_/PlayerBlockHeader.js | 4 +- .../shared/SessionItem/SessionItem.tsx | 11 + .../managers/AssistManager.ts | 147 +++++---- .../managers/LocalStream.ts | 1 + frontend/app/player/singletone.js | 5 +- frontend/app/types/session/session.ts | 2 + frontend/package.json | 1 + tracker/tracker-assist/.eslintrc.cjs | 6 +- tracker/tracker-assist/package.json | 2 +- tracker/tracker-assist/src/Assist.ts | 279 ++++++++++-------- tracker/tracker-assist/src/CallWindow.ts | 118 ++++---- .../src/ConfirmWindow/ConfirmWindow.ts | 8 +- tracker/tracker-assist/src/LocalStream.ts | 2 + tracker/tracker-assist/src/Mouse.ts | 8 +- tracker/tracker-assist/src/RemoteControl.ts | 10 +- 18 files changed, 429 insertions(+), 280 deletions(-) diff --git a/frontend/app/components/Assist/ChatControls/ChatControls.tsx b/frontend/app/components/Assist/ChatControls/ChatControls.tsx index 42866ed4b..0b85dab09 100644 --- a/frontend/app/components/Assist/ChatControls/ChatControls.tsx +++ b/frontend/app/components/Assist/ChatControls/ChatControls.tsx @@ -9,9 +9,10 @@ interface Props { stream: LocalStream | null, endCall: () => void, videoEnabled: boolean, - setVideoEnabled: (boolean) => void + isPrestart?: boolean, + setVideoEnabled: (isEnabled: boolean) => void } -function ChatControls({ stream, endCall, videoEnabled, setVideoEnabled } : Props) { +function ChatControls({ stream, endCall, videoEnabled, setVideoEnabled, isPrestart } : Props) { const [audioEnabled, setAudioEnabled] = useState(true) const toggleAudio = () => { @@ -25,6 +26,13 @@ function ChatControls({ stream, endCall, videoEnabled, setVideoEnabled } : Props .then(setVideoEnabled) } + /** muting user if he is auto connected to the call */ + React.useEffect(() => { + if (isPrestart) { + audioEnabled && toggleAudio(); + } + }, []) + return (
diff --git a/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx b/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx index 8eb2a3620..5a6608fcf 100644 --- a/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx +++ b/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx @@ -1,5 +1,4 @@ -//@ts-nocheck -import React, { useState, FC, useEffect } from 'react' +import React, { useState, useEffect } from 'react' import VideoContainer from '../components/VideoContainer' import cn from 'classnames' import Counter from 'App/components/shared/SessionItem/Counter' @@ -8,23 +7,23 @@ import ChatControls from '../ChatControls/ChatControls' import Draggable from 'react-draggable'; import type { LocalStream } from 'Player/MessageDistributor/managers/LocalStream'; - export interface Props { - incomeStream: MediaStream | null, + incomeStream: MediaStream[] | null, localStream: LocalStream | null, - userId: String, + userId: string, + isPrestart?: boolean; endCall: () => void } -const ChatWindow: FC = function ChatWindow({ userId, incomeStream, localStream, endCall }) { +function ChatWindow({ userId, incomeStream, localStream, endCall, isPrestart }: Props) { const [localVideoEnabled, setLocalVideoEnabled] = useState(false) const [remoteVideoEnabled, setRemoteVideoEnabled] = useState(false) useEffect(() => { - if (!incomeStream) { return } + if (!incomeStream || incomeStream.length === 0) { return } const iid = setInterval(() => { - const settings = incomeStream.getVideoTracks()[0]?.getSettings() - const isDummyVideoTrack = !!settings ? (settings.width === 2 || settings.frameRate === 0) : true + const settings = incomeStream.map(stream => stream.getVideoTracks()[0]?.getSettings()).filter(Boolean) + const isDummyVideoTrack = settings.length > 0 ? (settings.every(s => s.width === 2 || s.frameRate === 0 || s.frameRate === undefined)) : true const shouldBeEnabled = !isDummyVideoTrack if (shouldBeEnabled !== localVideoEnabled) { setRemoteVideoEnabled(shouldBeEnabled) @@ -42,16 +41,20 @@ const ChatWindow: FC = function ChatWindow({ userId, incomeStream, localS style={{ width: '280px' }} >
-
Talking to {userId ? userId : 'Anonymous User'}
+
+ Talking to {userId ? userId : 'Anonymous User'} + {incomeStream && incomeStream.length > 2 ? ' (+ other agents in the call)' : ''} +
- + {!incomeStream &&
Error obtaining incoming streams
} + {incomeStream && incomeStream.map(stream => )}
- +
) diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index ba1a7cf0b..757beca54 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -1,11 +1,12 @@ import React, { useState, useEffect } from 'react'; import { Popup, Icon, Button, IconButton } from 'UI'; +import logger from 'App/logger'; import { connect } from 'react-redux'; import cn from 'classnames'; import { toggleChatWindow } from 'Duck/sessions'; import { connectPlayer } from 'Player/store'; import ChatWindow from '../../ChatWindow'; -import { callPeer, requestReleaseRemoteControl, toggleAnnotation } from 'Player'; +import { callPeer, setCallArgs, requestReleaseRemoteControl, toggleAnnotation } from 'Player'; import { CallingState, ConnectionStatus, RemoteControlStatus } from 'Player/MessageDistributor/managers/AssistManager'; import RequestLocalStream from 'Player/MessageDistributor/managers/LocalStream'; import type { LocalStream } from 'Player/MessageDistributor/managers/LocalStream'; @@ -14,15 +15,12 @@ import { toast } from 'react-toastify'; import { confirm } from 'UI'; import stl from './AassistActions.module.css'; -function onClose(stream) { - stream.getTracks().forEach((t) => t.stop()); -} - function onReject() { toast.info(`Call was rejected.`); } function onError(e) { + console.log(e) toast.error(typeof e === 'string' ? e : e.message); } @@ -35,6 +33,8 @@ interface Props { remoteControlStatus: RemoteControlStatus; hasPermission: boolean; isEnterprise: boolean; + isCallActive: boolean; + agentIds: string[]; } function AssistActions({ @@ -46,14 +46,21 @@ function AssistActions({ remoteControlStatus, hasPermission, isEnterprise, + isCallActive, + agentIds }: Props) { - const [incomeStream, setIncomeStream] = useState(null); + const [isPrestart, setPrestart] = useState(false); + const [incomeStream, setIncomeStream] = useState([]); const [localStream, setLocalStream] = useState(null); const [callObject, setCallObject] = useState<{ end: () => void } | null>(null); + const onCall = calling === CallingState.OnCall || calling === CallingState.Reconnecting; + const cannotCall = peerConnectionStatus !== ConnectionStatus.Connected || (isEnterprise && !hasPermission); + const remoteActive = remoteControlStatus === RemoteControlStatus.Enabled; + useEffect(() => { - return callObject?.end(); - }, []); + return callObject?.end() + }, []) useEffect(() => { if (peerConnectionStatus == ConnectionStatus.Disconnected) { @@ -61,15 +68,36 @@ function AssistActions({ } }, [peerConnectionStatus]); - function call() { - RequestLocalStream() - .then((lStream) => { - setLocalStream(lStream); - setCallObject(callPeer(lStream, setIncomeStream, lStream.stop.bind(lStream), onReject, onError)); - }) - .catch(onError); + const addIncomeStream = (stream: MediaStream) => { + console.log('new stream in component') + setIncomeStream(oldState => [...oldState, stream]); } + function call(agentIds?: string[]) { + RequestLocalStream().then(lStream => { + setLocalStream(lStream); + setCallArgs( + lStream, + addIncomeStream, + lStream.stop.bind(lStream), + onReject, + onError + ) + setCallObject(callPeer()); + if (agentIds) { + callPeer(agentIds) + } + }).catch(onError) + } + + React.useEffect(() => { + if (!onCall && isCallActive && agentIds) { + logger.log('joinig the party', agentIds) + setPrestart(true); + call(agentIds) + } + }, [agentIds, isCallActive]) + const confirmCall = async () => { if ( await confirm({ @@ -82,10 +110,6 @@ function AssistActions({ } }; - const onCall = calling === CallingState.OnCall || calling === CallingState.Reconnecting; - const cannotCall = peerConnectionStatus !== ConnectionStatus.Connected || (isEnterprise && !hasPermission); - const remoteActive = remoteControlStatus === RemoteControlStatus.Enabled; - return (
{(onCall || remoteActive) && ( @@ -123,7 +147,7 @@ function AssistActions({
- +
{onCall && callObject && ( - + )}
diff --git a/frontend/app/components/Session_/PlayerBlockHeader.js b/frontend/app/components/Session_/PlayerBlockHeader.js index f0576e419..f7afb2bdb 100644 --- a/frontend/app/components/Session_/PlayerBlockHeader.js +++ b/frontend/app/components/Session_/PlayerBlockHeader.js @@ -105,7 +105,7 @@ export default class PlayerBlockHeader extends React.PureComponent { const { hideBack } = this.state; - const { sessionId, userId, userNumericHash, live, metadata } = session; + const { sessionId, userId, userNumericHash, live, metadata, isCallActive, agentIds } = session; let _metaList = Object.keys(metadata) .filter((i) => metaList.includes(i)) .map((key) => { @@ -142,7 +142,7 @@ export default class PlayerBlockHeader extends React.PureComponent {
)} - {isAssist && } + {isAssist && }
{!isAssist && ( diff --git a/frontend/app/components/shared/SessionItem/SessionItem.tsx b/frontend/app/components/shared/SessionItem/SessionItem.tsx index fa886ab8b..83d2e6bb3 100644 --- a/frontend/app/components/shared/SessionItem/SessionItem.tsx +++ b/frontend/app/components/shared/SessionItem/SessionItem.tsx @@ -41,6 +41,8 @@ interface Props { userSessionsCount: number; issueTypes: []; active: boolean; + isCallActive?: boolean; + agentIds?: string[]; }; onUserClick?: (userId: string, userAnonymousId: string) => void; hasUserFilter?: boolean; @@ -168,6 +170,15 @@ function SessionItem(props: RouteComponentProps & Props) {
+ {live && session.isCallActive && session.agentIds.length > 0 ? ( +
+ +
+ ) : null} {isSessions && (
{isLastPlayed && ( diff --git a/frontend/app/player/MessageDistributor/managers/AssistManager.ts b/frontend/app/player/MessageDistributor/managers/AssistManager.ts index 2ba90311e..11d5e948c 100644 --- a/frontend/app/player/MessageDistributor/managers/AssistManager.ts +++ b/frontend/app/player/MessageDistributor/managers/AssistManager.ts @@ -136,7 +136,6 @@ export default class AssistManager { //agentInfo: JSON.stringify({}) } }) - //socket.onAny((...args) => console.log(...args)) socket.on("connect", () => { waitingForMessages = true this.setStatus(ConnectionStatus.WaitingMessages) // TODO: happens frequently on bad network @@ -146,7 +145,6 @@ export default class AssistManager { update({ calling: CallingState.NoCall }) }) socket.on('messages', messages => { - //console.log(messages.filter(m => m._id === 41 || m._id === 44)) jmr.append(messages) // as RawMessage[] if (waitingForMessages) { @@ -176,11 +174,11 @@ export default class AssistManager { this.setStatus(ConnectionStatus.Connected) }) - socket.on('UPDATE_SESSION', ({ active }) => { + socket.on('UPDATE_SESSION', (data) => { showDisconnectTimeout && clearTimeout(showDisconnectTimeout) !inactiveTimeout && this.setStatus(ConnectionStatus.Connected) - if (typeof active === "boolean") { - if (active) { + if (typeof data.active === "boolean") { + if (data.active) { inactiveTimeout && clearTimeout(inactiveTimeout) this.setStatus(ConnectionStatus.Connected) } else { @@ -305,7 +303,7 @@ export default class AssistManager { private _peer: Peer | null = null private connectionAttempts: number = 0 - private callConnection: MediaConnection | null = null + private callConnection: MediaConnection[] = [] private getPeer(): Promise { if (this._peer && !this._peer.disconnected) { return Promise.resolve(this._peer) } @@ -326,6 +324,32 @@ export default class AssistManager { }; } const peer = this._peer = new Peer(peerOpts) + peer.on('call', call => { + console.log('getting call from', call.peer) + call.answer(this.callArgs.localStream.stream) + this.callConnection.push(call) + + this.callArgs.localStream.onVideoTrack(vTrack => { + const sender = call.peerConnection.getSenders().find(s => s.track?.kind === "video") + if (!sender) { + console.warn("No video sender found") + return + } + sender.replaceTrack(vTrack) + }) + + call.on('stream', stream => { + this.callArgs && this.callArgs.onStream(stream) + }); + call.peerConnection.addEventListener("track", e => console.log('newtrack',e.track)) + + call.on("close", this.onRemoteCallEnd) + call.on("error", (e) => { + console.error("PeerJS error (on call):", e) + this.initiateCallEnd(); + this.callArgs && this.callArgs.onError && this.callArgs.onError(); + }); + }) peer.on('error', e => { if (e.type === 'disconnected') { return peer.reconnect() @@ -351,21 +375,21 @@ export default class AssistManager { private handleCallEnd() { this.callArgs && this.callArgs.onCallEnd() - this.callConnection && this.callConnection.close() + this.callConnection[0] && this.callConnection[0].close() update({ calling: CallingState.NoCall }) this.callArgs = null this.toggleAnnotation(false) } - private initiateCallEnd = () => { - this.socket?.emit("call_end") + private initiateCallEnd = async () => { + this.socket?.emit("call_end", store.getState().getIn([ 'user', 'account', 'name'])) this.handleCallEnd() } private onRemoteCallEnd = () => { if (getState().calling === CallingState.Requesting) { this.callArgs && this.callArgs.onReject() - this.callConnection && this.callConnection.close() + this.callConnection[0] && this.callConnection[0].close() update({ calling: CallingState.NoCall }) this.callArgs = null this.toggleAnnotation(false) @@ -379,15 +403,16 @@ export default class AssistManager { onStream: (s: MediaStream)=>void, onCallEnd: () => void, onReject: () => void, - onError?: ()=> void + onError?: ()=> void, } | null = null - call( + public setCallArgs( localStream: LocalStream, onStream: (s: MediaStream)=>void, onCallEnd: () => void, onReject: () => void, - onError?: ()=> void): { end: Function } { + onError?: ()=> void, + ) { this.callArgs = { localStream, onStream, @@ -395,12 +420,66 @@ export default class AssistManager { onReject, onError, } - this._call() + } + + public call(thirdPartyPeers?: string[]): { end: Function } { + if (thirdPartyPeers && thirdPartyPeers.length > 0) { + this.addPeerCall(thirdPartyPeers) + } else { + this._callSessionPeer() + } return { end: this.initiateCallEnd, } } + /** Connecting to the other agents that are already + * in the call with the user + */ + public addPeerCall(thirdPartyPeers: string[]) { + thirdPartyPeers.forEach(peer => this._peerConnection(peer)) + } + + /** Connecting to the app user */ + private _callSessionPeer() { + if (![CallingState.NoCall, CallingState.Reconnecting].includes(getState().calling)) { return } + update({ calling: CallingState.Connecting }) + this._peerConnection(this.peerID); + this.socket && this.socket.emit("_agent_name", store.getState().getIn([ 'user', 'account', 'name'])) + } + + private async _peerConnection(remotePeerId: string) { + try { + const peer = await this.getPeer(); + const call = peer.call(remotePeerId, this.callArgs.localStream.stream) + this.callConnection.push(call) + + this.callArgs.localStream.onVideoTrack(vTrack => { + const sender = call.peerConnection.getSenders().find(s => s.track?.kind === "video") + if (!sender) { + console.warn("No video sender found") + return + } + sender.replaceTrack(vTrack) + }) + + call.on('stream', stream => { + getState().calling !== CallingState.OnCall && update({ calling: CallingState.OnCall }) + this.callArgs && this.callArgs.onStream(stream) + }); + call.peerConnection.addEventListener("track", e => console.log('newtrack',e.track)) + + call.on("close", this.onRemoteCallEnd) + call.on("error", (e) => { + console.error("PeerJS error (on call):", e) + this.initiateCallEnd(); + this.callArgs && this.callArgs.onError && this.callArgs.onError(); + }); + } catch (e) { + console.error(e) + } + } + toggleAnnotation(enable?: boolean) { // if (getState().calling !== CallingState.OnCall) { return } if (typeof enable !== "boolean") { @@ -442,44 +521,6 @@ export default class AssistManager { private annot: AnnotationCanvas | null = null - private _call() { - if (![CallingState.NoCall, CallingState.Reconnecting].includes(getState().calling)) { return } - update({ calling: CallingState.Connecting }) - this.getPeer().then(peer => { - if (!this.callArgs) { return console.log("No call Args. Must not happen.") } - update({ calling: CallingState.Requesting }) - - // TODO: in a proper way - this.socket && this.socket.emit("_agent_name", store.getState().getIn([ 'user', 'account', 'name'])) - - const call = this.callConnection = peer.call(this.peerID, this.callArgs.localStream.stream) - this.callArgs.localStream.onVideoTrack(vTrack => { - const sender = call.peerConnection.getSenders().find(s => s.track?.kind === "video") - if (!sender) { - console.warn("No video sender found") - return - } - //logger.log("sender found:", sender) - sender.replaceTrack(vTrack) - }) - - call.on('stream', stream => { - update({ calling: CallingState.OnCall }) - this.callArgs && this.callArgs.onStream(stream) - }); - //call.peerConnection.addEventListener("track", e => console.log('newtrack',e.track)) - - call.on("close", this.onRemoteCallEnd) - call.on("error", (e) => { - console.error("PeerJS error (on call):", e) - this.initiateCallEnd(); - this.callArgs && this.callArgs.onError && this.callArgs.onError(); - }); - - }) - } - - /* ==== Cleaning ==== */ private cleaned: boolean = false clear() { @@ -502,5 +543,3 @@ export default class AssistManager { } } } - - diff --git a/frontend/app/player/MessageDistributor/managers/LocalStream.ts b/frontend/app/player/MessageDistributor/managers/LocalStream.ts index 63f01ad58..360033c7f 100644 --- a/frontend/app/player/MessageDistributor/managers/LocalStream.ts +++ b/frontend/app/player/MessageDistributor/managers/LocalStream.ts @@ -54,6 +54,7 @@ class _LocalStream { }) .catch(e => { // TODO: log + console.error(e) return false }) } diff --git a/frontend/app/player/singletone.js b/frontend/app/player/singletone.js index a0fe6ff26..808605793 100644 --- a/frontend/app/player/singletone.js +++ b/frontend/app/player/singletone.js @@ -2,7 +2,7 @@ import Player from './Player'; import { update, clean as cleanStore, getState } from './store'; import { clean as cleanLists } from './lists'; - +/** @type {Player} */ let instance = null; const initCheck = method => (...args) => { @@ -69,7 +69,10 @@ export const attach = initCheck((...args) => instance.attach(...args)); export const markElement = initCheck((...args) => instance.marker && instance.marker.mark(...args)); export const scale = initCheck(() => instance.scale()); export const toggleInspectorMode = initCheck((...args) => instance.toggleInspectorMode(...args)); +/** @type {Player.assistManager.call} */ export const callPeer = initCheck((...args) => instance.assistManager.call(...args)) +/** @type {Player.assistManager.setCallArgs} */ +export const setCallArgs = initCheck((...args) => instance.assistManager.setCallArgs(...args)) export const requestReleaseRemoteControl = initCheck((...args) => instance.assistManager.requestReleaseRemoteControl(...args)) export const markTargets = initCheck((...args) => instance.markTargets(...args)) export const activeTarget = initCheck((...args) => instance.activeTarget(...args)) diff --git a/frontend/app/types/session/session.ts b/frontend/app/types/session/session.ts index a4ed48fe6..5eadadf4b 100644 --- a/frontend/app/types/session/session.ts +++ b/frontend/app/types/session/session.ts @@ -79,6 +79,8 @@ export default Record({ isIOS: false, revId: '', userSessionsCount: 0, + agentIds: [], + isCallActive: false }, { fromJS:({ startTs=0, diff --git a/frontend/package.json b/frontend/package.json index bce5abbad..957c65c3a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -83,6 +83,7 @@ "@openreplay/sourcemap-uploader": "^3.0.0", "@types/react": "^18.0.9", "@types/react-dom": "^18.0.4", + "@types/react-redux": "^7.1.24", "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^5.24.0", "@typescript-eslint/parser": "^5.24.0", diff --git a/tracker/tracker-assist/.eslintrc.cjs b/tracker/tracker-assist/.eslintrc.cjs index 01d5c5bc0..4480aa99f 100644 --- a/tracker/tracker-assist/.eslintrc.cjs +++ b/tracker/tracker-assist/.eslintrc.cjs @@ -24,12 +24,14 @@ module.exports = { '@typescript-eslint/camelcase': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/unbound-method': 'off', - '@typescript-eslint/explicit-function-return-type': 'warn', '@typescript-eslint/prefer-readonly': 'warn', '@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unused-expressions': 'off', '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/restrict-plus-operands': 'warn', '@typescript-eslint/no-unsafe-return': 'warn', 'no-useless-escape': 'warn', @@ -38,9 +40,7 @@ module.exports = { '@typescript-eslint/no-useless-constructor': 'warn', '@typescript-eslint/no-this-alias': 'off', '@typescript-eslint/no-floating-promises': 'warn', - '@typescript-eslint/no-unsafe-argument': 'warn', 'no-unused-expressions': 'off', - '@typescript-eslint/no-unused-expressions': 'warn', '@typescript-eslint/no-useless-constructor': 'warn', 'semi': ["error", "never"], 'quotes': ["error", "single"], diff --git a/tracker/tracker-assist/package.json b/tracker/tracker-assist/package.json index 4efae850e..7849eb6ff 100644 --- a/tracker/tracker-assist/package.json +++ b/tracker/tracker-assist/package.json @@ -34,6 +34,7 @@ "@openreplay/tracker": "^3.5.3" }, "devDependencies": { + "@openreplay/tracker": "file:../tracker", "@typescript-eslint/eslint-plugin": "^5.30.0", "@typescript-eslint/parser": "^5.30.0", "eslint": "^7.8.0", @@ -42,7 +43,6 @@ "husky": "^8.0.1", "lint-staged": "^13.0.3", "prettier": "^2.7.1", - "@openreplay/tracker": "file:../tracker", "replace-in-files-cli": "^1.0.0", "typescript": "^4.6.0-dev.20211126" }, diff --git a/tracker/tracker-assist/src/Assist.ts b/tracker/tracker-assist/src/Assist.ts index 80023beaa..fa4b071d1 100644 --- a/tracker/tracker-assist/src/Assist.ts +++ b/tracker/tracker-assist/src/Assist.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import type { Socket, } from 'socket.io-client' import { connect, } from 'socket.io-client' -import Peer from 'peerjs' +import Peer, { MediaConnection, } from 'peerjs' import type { Properties, } from 'csstype' import { App, } from '@openreplay/tracker' -import RequestLocalStream from './LocalStream.js' +import RequestLocalStream, { LocalStream, } from './LocalStream.js' import RemoteControl from './RemoteControl.js' import CallWindow from './CallWindow.js' import AnnotationCanvas from './AnnotationCanvas.js' @@ -13,7 +13,7 @@ import ConfirmWindow from './ConfirmWindow/ConfirmWindow.js' import { callConfirmDefault, } from './ConfirmWindow/defaults.js' import type { Options as ConfirmOptions, } from './ConfirmWindow/defaults.js' -// TODO: fully specified strict check (everywhere) +// TODO: fully specified strict check with no-any (everywhere) type StartEndCallback = () => ((()=>Record) | void) @@ -45,7 +45,7 @@ type OptionalCallback = (()=>Record) | void type Agent = { onDisconnect?: OptionalCallback, onControlReleased?: OptionalCallback, - name?: string + //name?: string // } @@ -78,7 +78,7 @@ export default class Assist { ) if (document.hidden !== undefined) { - const sendActivityState = () => this.emit('UPDATE_SESSION', { active: !document.hidden, }) + const sendActivityState = (): void => this.emit('UPDATE_SESSION', { active: !document.hidden, }) app.attachEventListener( document, 'visibilitychange', @@ -111,7 +111,7 @@ export default class Assist { app.session.attachUpdateCallback(sessInfo => this.emit('UPDATE_SESSION', sessInfo)) } - private emit(ev: string, ...args) { + private emit(ev: string, ...args): void { this.socket && this.socket.emit(ev, ...args) } @@ -119,14 +119,17 @@ export default class Assist { return Object.keys(this.agents).length > 0 } - private notifyCallEnd() { - this.emit('call_end') + private readonly setCallingState = (newState: CallingState): void => { + this.callingState = newState } - private onRemoteCallEnd = () => {} private onStart() { const app = this.app - const peerID = `${app.getProjectKey()}-${app.getSessionID()}` + const sessionId = app.getSessionID() + if (!sessionId) { + return app.debug.error('No session ID') + } + const peerID = `${app.getProjectKey()}-${sessionId}` // SocketIO const socket = this.socket = connect(app.getHost(), { @@ -187,51 +190,65 @@ export default class Assist { socket.on('NEW_AGENT', (id: string, info) => { this.agents[id] = { - onDisconnect: this.options.onAgentConnect && this.options.onAgentConnect(), + onDisconnect: this.options.onAgentConnect?.(), ...info, // TODO } this.assistDemandedRestart = true this.app.stop() - this.app.start().then(() => { this.assistDemandedRestart = false }) + this.app.start().then(() => { this.assistDemandedRestart = false }).catch(e => app.debug.error(e)) }) socket.on('AGENTS_CONNECTED', (ids: string[]) => { ids.forEach(id =>{ this.agents[id] = { - onDisconnect: this.options.onAgentConnect && this.options.onAgentConnect(), + onDisconnect: this.options.onAgentConnect?.(), } }) this.assistDemandedRestart = true this.app.stop() - this.app.start().then(() => { this.assistDemandedRestart = false }) + this.app.start().then(() => { this.assistDemandedRestart = false }).catch(e => app.debug.error(e)) remoteControl.reconnect(ids) }) - let confirmCall:ConfirmWindow | null = null - socket.on('AGENT_DISCONNECTED', (id) => { remoteControl.releaseControl(id) - // close the call also - if (callingAgent === id) { - confirmCall?.remove() - this.onRemoteCallEnd() - } - - // @ts-ignore (wtf, typescript?!) - this.agents[id] && this.agents[id].onDisconnect != null && this.agents[id].onDisconnect() + this.agents[id]?.onDisconnect?.() delete this.agents[id] + + endAgentCall(id) }) socket.on('NO_AGENT', () => { + Object.values(this.agents).forEach(a => a.onDisconnect?.()) this.agents = {} }) - socket.on('call_end', () => this.onRemoteCallEnd()) // TODO: check if agent calling id + socket.on('call_end', (id) => { + if (!callingAgents.has(id)) { + app.debug.warn('Received call_end from unknown agent', id) + return + } + endAgentCall(id) + }) - // TODO: fix the code - let agentName = '' - let callingAgent = '' - socket.on('_agent_name',(id, name) => { agentName = name; callingAgent = id }) + socket.on('_agent_name', (id, name) => { + callingAgents.set(id, name) + updateCallerNames() + }) + const callingAgents: Map = new Map() // !! uses socket.io ID + // TODO: merge peerId & socket.io id (simplest way - send peerId with the name) + const calls: Record = {} // !! uses peerJS ID + const lStreams: Record = {} + // const callingPeers: Map = new Map() // Maybe + function endAgentCall(id: string) { + callingAgents.delete(id) + if (callingAgents.size === 0) { + handleCallEnd() + } else { + updateCallerNames() + //TODO: close() specific call and corresponding lStreams (after connecting peerId & socket.io id) + } + } // PeerJS call (todo: use native WebRTC) const peerOptions = { @@ -244,119 +261,147 @@ export default class Assist { peerOptions['config'] = this.options.config } const peer = this.peer = new Peer(peerID, peerOptions) - // app.debug.log('Peer created: ', peer) - // @ts-ignore + + // @ts-ignore (peerjs typing) peer.on('error', e => app.debug.warn('Peer error: ', e.type, e)) peer.on('disconnected', () => peer.reconnect()) - peer.on('call', (call) => { - app.debug.log('Call: ', call) - if (this.callingState !== CallingState.False) { - call.close() - //this.notifyCallEnd() // TODO: strictly connect calling peer with agent socket.id - app.debug.warn('Call closed instantly bacause line is busy. CallingState: ', this.callingState) - return - } - const setCallingState = (newState: CallingState) => { - if (newState === CallingState.True) { - sessionStorage.setItem(this.options.session_calling_peer_key, call.peer) - } else if (newState === CallingState.False) { - sessionStorage.removeItem(this.options.session_calling_peer_key) - } - this.callingState = newState + // Common for all incoming call requests + let callUI: CallWindow | null = null + function updateCallerNames() { + callUI?.setAssistentName(callingAgents) + } + // TODO: incapsulate + let callConfirmWindow: ConfirmWindow | null = null + let callConfirmAnswer: Promise | null = null + const closeCallConfirmWindow = () => { + if (callConfirmWindow) { + callConfirmWindow.remove() + callConfirmWindow = null + callConfirmAnswer = null } - + } + const requestCallConfirm = () => { + if (callConfirmAnswer) { // Already asking + return callConfirmAnswer + } + callConfirmWindow = new ConfirmWindow(callConfirmDefault(this.options.callConfirm || { + text: this.options.confirmText, + style: this.options.confirmStyle, + })) // TODO: reuse ? + return callConfirmAnswer = callConfirmWindow.mount().then(answer => { + closeCallConfirmWindow() + return answer + }) + } + let callEndCallback: ReturnType | null = null + const handleCallEnd = () => { // Completle stop and clear all calls + // Streams + Object.values(calls).forEach(call => call.close()) + Object.keys(calls).forEach(peerId => delete calls[peerId]) + Object.values(lStreams).forEach((stream) => { stream.stop() }) + Object.keys(lStreams).forEach((peerId: string) => { delete lStreams[peerId] }) + + // UI + closeCallConfirmWindow() + callUI?.remove() + annot?.remove() + callUI = null + annot = null + + this.emit('UPDATE_SESSION', { agentIds: [], isCallActive: false, }) + this.setCallingState(CallingState.False) + sessionStorage.removeItem(this.options.session_calling_peer_key) + callEndCallback?.() + } + const initiateCallEnd = () => { + this.emit('call_end') + handleCallEnd() + } + + peer.on('call', (call) => { + app.debug.log('Incoming call: ', call) let confirmAnswer: Promise - const callingPeer = sessionStorage.getItem(this.options.session_calling_peer_key) - if (callingPeer === call.peer) { + const callingPeerIds = JSON.parse(sessionStorage.getItem(this.options.session_calling_peer_key) || '[]') + if (callingPeerIds.includes(call.peer) || this.callingState === CallingState.True) { confirmAnswer = Promise.resolve(true) } else { - setCallingState(CallingState.Requesting) - confirmCall = new ConfirmWindow(callConfirmDefault(this.options.callConfirm || { - text: this.options.confirmText, - style: this.options.confirmStyle, - })) - confirmAnswer = confirmCall.mount() - this.playNotificationSound() - this.onRemoteCallEnd = () => { // if call cancelled by a caller before confirmation - app.debug.log('Received call_end during confirm window opened') - confirmCall?.remove() - setCallingState(CallingState.False) - call.close() - } + this.setCallingState(CallingState.Requesting) + confirmAnswer = requestCallConfirm() + this.playNotificationSound() // For every new agent during confirmation here + + // TODO: only one (latest) timeout setTimeout(() => { if (this.callingState !== CallingState.Requesting) { return } - call.close() - confirmCall?.remove() - this.notifyCallEnd() - setCallingState(CallingState.False) + initiateCallEnd() }, 30000) } - confirmAnswer.then(agreed => { + confirmAnswer.then(async agreed => { if (!agreed) { - call.close() - this.notifyCallEnd() - setCallingState(CallingState.False) + initiateCallEnd() + return + } + // Request local stream for the new connection + try { + // lStreams are reusable so fare we don't delete them in the `endAgentCall` + if (!lStreams[call.peer]) { + app.debug.log('starting new stream for', call.peer) + lStreams[call.peer] = await RequestLocalStream() + } + calls[call.peer] = call + } catch (e) { + app.debug.error('Audio mediadevice request error:', e) + initiateCallEnd() return } - const callUI = new CallWindow() - annot = new AnnotationCanvas() - annot.mount() - callUI.setAssistentName(agentName) - - const onCallEnd = this.options.onCallStart() - const handleCallEnd = () => { - app.debug.log('Handle Call End') - call.close() - callUI.remove() - annot && annot.remove() - annot = null - setCallingState(CallingState.False) - onCallEnd && onCallEnd() + // UI + if (!callUI) { + callUI = new CallWindow(app.debug.error) + // TODO: as constructor options + callUI.setCallEndAction(initiateCallEnd) + callUI.setLocalStreams(Object.values(lStreams)) } - const initiateCallEnd = () => { - this.notifyCallEnd() - handleCallEnd() + if (!annot) { + annot = new AnnotationCanvas() + annot.mount() } - this.onRemoteCallEnd = handleCallEnd call.on('error', e => { app.debug.warn('Call error:', e) initiateCallEnd() }) - - RequestLocalStream().then(lStream => { - call.on('stream', function(rStream) { - callUI.setRemoteStream(rStream) - const onInteraction = () => { // only if hidden? - callUI.playRemote() - document.removeEventListener('click', onInteraction) - } - document.addEventListener('click', onInteraction) - }) - - lStream.onVideoTrack(vTrack => { - const sender = call.peerConnection.getSenders().find(s => s.track?.kind === 'video') - if (!sender) { - app.debug.warn('No video sender found') - return - } - app.debug.log('sender found:', sender) - sender.replaceTrack(vTrack) - }) - - callUI.setCallEndAction(initiateCallEnd) - callUI.setLocalStream(lStream) - call.answer(lStream.stream) - setCallingState(CallingState.True) + call.on('stream', (rStream) => { + callUI?.addRemoteStream(rStream) + const onInteraction = () => { // do only if document.hidden ? + callUI?.playRemote() + document.removeEventListener('click', onInteraction) + } + document.addEventListener('click', onInteraction) }) - .catch(e => { - app.debug.warn('Audio mediadevice request error:', e) - initiateCallEnd() + + // remote video on/off/camera change + lStreams[call.peer].onVideoTrack(vTrack => { + const sender = call.peerConnection.getSenders().find(s => s.track?.kind === 'video') + if (!sender) { + app.debug.warn('No video sender found') + return + } + app.debug.log('sender found:', sender) + void sender.replaceTrack(vTrack) }) - }).catch() // in case of Confirm.remove() without any confirmation/decline + + call.answer(lStreams[call.peer].stream) + this.setCallingState(CallingState.True) + if (!callEndCallback) { callEndCallback = this.options.onCallStart?.() } + + const callingPeerIds = Object.keys(calls) + sessionStorage.setItem(this.options.session_calling_peer_key, JSON.stringify(callingPeerIds)) + this.emit('UPDATE_SESSION', { agentIds: callingPeerIds, isCallActive: true, }) + }).catch(reason => { // in case of Confirm.remove() without user answer (not a error) + app.debug.log(reason) + }) }) } diff --git a/tracker/tracker-assist/src/CallWindow.ts b/tracker/tracker-assist/src/CallWindow.ts index 8804ffa3e..c8f07e0ff 100644 --- a/tracker/tracker-assist/src/CallWindow.ts +++ b/tracker/tracker-assist/src/CallWindow.ts @@ -4,7 +4,7 @@ import attachDND from './dnd.js' const SS_START_TS_KEY = '__openreplay_assist_call_start_ts' export default class CallWindow { - private iframe: HTMLIFrameElement + private readonly iframe: HTMLIFrameElement private vRemote: HTMLVideoElement | null = null private vLocal: HTMLVideoElement | null = null private audioBtn: HTMLElement | null = null @@ -16,9 +16,9 @@ export default class CallWindow { private tsInterval: ReturnType - private load: Promise + private readonly load: Promise - constructor() { + constructor(private readonly logError: (...args: any[]) => void) { const iframe = this.iframe = document.createElement('iframe') Object.assign(iframe.style, { position: 'fixed', @@ -107,8 +107,8 @@ export default class CallWindow { private adjustIframeSize() { const doc = this.iframe.contentDocument if (!doc) { return } - this.iframe.style.height = doc.body.scrollHeight + 'px' - this.iframe.style.width = doc.body.scrollWidth + 'px' + this.iframe.style.height = `${doc.body.scrollHeight}px` + this.iframe.style.width = `${doc.body.scrollWidth}px` } setCallEndAction(endCall: () => void) { @@ -116,40 +116,46 @@ export default class CallWindow { if (this.endCallBtn) { this.endCallBtn.onclick = endCall } - }) + }).catch(e => this.logError(e)) } - private aRemote: HTMLAudioElement | null = null; private checkRemoteVideoInterval: ReturnType - setRemoteStream(rStream: MediaStream) { + private audioContainer: HTMLDivElement | null = null + addRemoteStream(rStream: MediaStream) { this.load.then(() => { + // Video if (this.vRemote && !this.vRemote.srcObject) { this.vRemote.srcObject = rStream if (this.vPlaceholder) { this.vPlaceholder.innerText = 'Video has been paused. Click anywhere to resume.' } - - // Hack for audio. Doesen't work inside the iframe because of some magical reasons (check if it is connected to autoplay?) - this.aRemote = document.createElement('audio') - this.aRemote.autoplay = true - this.aRemote.style.display = 'none' - this.aRemote.srcObject = rStream - document.body.appendChild(this.aRemote) + // Hack to determine if the remote video is enabled + // TODO: pass this info through socket + if (this.checkRemoteVideoInterval) { clearInterval(this.checkRemoteVideoInterval) } // just in case + let enabled = false + this.checkRemoteVideoInterval = setInterval(() => { + const settings = rStream.getVideoTracks()[0]?.getSettings() + const isDummyVideoTrack = !!settings && (settings.width === 2 || settings.frameRate === 0) + const shouldBeEnabled = !isDummyVideoTrack + if (enabled !== shouldBeEnabled) { + this.toggleRemoteVideoUI(enabled=shouldBeEnabled) + } + }, 1000) } - // Hack to determine if the remote video is enabled - if (this.checkRemoteVideoInterval) { clearInterval(this.checkRemoteVideoInterval) } // just in case - let enabled = false - this.checkRemoteVideoInterval = setInterval(() => { - const settings = rStream.getVideoTracks()[0]?.getSettings() - //console.log(settings) - const isDummyVideoTrack = !!settings && (settings.width === 2 || settings.frameRate === 0) - const shouldBeEnabled = !isDummyVideoTrack - if (enabled !== shouldBeEnabled) { - this.toggleRemoteVideoUI(enabled=shouldBeEnabled) - } - }, 1000) - }) + // Audio + if (!this.audioContainer) { + this.audioContainer = document.createElement('div') + document.body.appendChild(this.audioContainer) + } + // Hack for audio. Doesen't work inside the iframe + // because of some magical reasons (check if it is connected to autoplay?) + const audioEl = document.createElement('audio') + audioEl.autoplay = true + audioEl.style.display = 'none' + audioEl.srcObject = rStream + this.audioContainer.appendChild(audioEl) + }).catch(e => this.logError(e)) } toggleRemoteVideoUI(enable: boolean) { @@ -162,26 +168,27 @@ export default class CallWindow { } this.adjustIframeSize() } - }) + }).catch(e => this.logError(e)) } - private localStream: LocalStream | null = null; - - // TODO: on construction? - setLocalStream(lStream: LocalStream) { - this.localStream = lStream + private localStreams: LocalStream[] = [] + // !TODO: separate streams manipulation from ui + setLocalStreams(streams: LocalStream[]) { + this.localStreams = streams } playRemote() { this.vRemote && this.vRemote.play() } - setAssistentName(name: string) { + setAssistentName(callingAgents: Map) { this.load.then(() => { if (this.agentNameElem) { - this.agentNameElem.innerText = name + const nameString = Array.from(callingAgents.values()).join(', ') + const safeNames = nameString.length > 20 ? nameString.substring(0, 20) + '...' : nameString + this.agentNameElem.innerText = safeNames } - }) + }).catch(e => this.logError(e)) } @@ -195,7 +202,10 @@ export default class CallWindow { } private toggleAudio() { - const enabled = this.localStream?.toggleAudio() || false + let enabled = false + this.localStreams.forEach(stream => { + enabled = stream.toggleAudio() || false + }) this.toggleAudioUI(enabled) } @@ -211,30 +221,32 @@ export default class CallWindow { this.adjustIframeSize() } - private videoRequested = false private toggleVideo() { - this.localStream?.toggleVideo() - .then(enabled => { - this.toggleVideoUI(enabled) - this.load.then(() => { - if (this.vLocal && this.localStream && !this.vLocal.srcObject) { - this.vLocal.srcObject = this.localStream.stream - } - }) + this.localStreams.forEach(stream => { + stream.toggleVideo() + .then(enabled => { + this.toggleVideoUI(enabled) + this.load.then(() => { + if (this.vLocal && stream && !this.vLocal.srcObject) { + this.vLocal.srcObject = stream.stream + } + }).catch(e => this.logError(e)) + }).catch(e => this.logError(e)) }) } remove() { - this.localStream?.stop() clearInterval(this.tsInterval) clearInterval(this.checkRemoteVideoInterval) - if (this.iframe.parentElement) { - document.body.removeChild(this.iframe) + if (this.audioContainer && this.audioContainer.parentElement) { + this.audioContainer.parentElement.removeChild(this.audioContainer) + this.audioContainer = null } - if (this.aRemote && this.aRemote.parentElement) { - document.body.removeChild(this.aRemote) + if (this.iframe.parentElement) { + this.iframe.parentElement.removeChild(this.iframe) } sessionStorage.removeItem(SS_START_TS_KEY) + this.localStreams = [] } -} \ No newline at end of file +} diff --git a/tracker/tracker-assist/src/ConfirmWindow/ConfirmWindow.ts b/tracker/tracker-assist/src/ConfirmWindow/ConfirmWindow.ts index fd7209689..4de359ac9 100644 --- a/tracker/tracker-assist/src/ConfirmWindow/ConfirmWindow.ts +++ b/tracker/tracker-assist/src/ConfirmWindow/ConfirmWindow.ts @@ -110,17 +110,15 @@ export default class ConfirmWindow { this.wrapper = wrapper confirmBtn.onclick = () => { - this._remove() this.resolve(true) } declineBtn.onclick = () => { - this._remove() this.resolve(false) } } private resolve: (result: boolean) => void = () => {}; - private reject: () => void = () => {}; + private reject: (reason: string) => void = () => {}; mount(): Promise { document.body.appendChild(this.wrapper) @@ -135,10 +133,10 @@ export default class ConfirmWindow { if (!this.wrapper.parentElement) { return } - document.body.removeChild(this.wrapper) + this.wrapper.parentElement.removeChild(this.wrapper) } remove() { this._remove() - this.reject() + this.reject('no answer') } } diff --git a/tracker/tracker-assist/src/LocalStream.ts b/tracker/tracker-assist/src/LocalStream.ts index 7f233108a..78c9ccff8 100644 --- a/tracker/tracker-assist/src/LocalStream.ts +++ b/tracker/tracker-assist/src/LocalStream.ts @@ -22,6 +22,7 @@ export default function RequestLocalStream(): Promise { return navigator.mediaDevices.getUserMedia({ audio:true, }) .then(aStream => { const aTrack = aStream.getAudioTracks()[0] + if (!aTrack) { throw new Error('No audio tracks provided') } return new _LocalStream(aTrack) }) @@ -54,6 +55,7 @@ class _LocalStream { }) .catch(e => { // TODO: log + console.error(e) return false }) } diff --git a/tracker/tracker-assist/src/Mouse.ts b/tracker/tracker-assist/src/Mouse.ts index a6164e153..afb50a1f1 100644 --- a/tracker/tracker-assist/src/Mouse.ts +++ b/tracker/tracker-assist/src/Mouse.ts @@ -2,7 +2,7 @@ type XY = [number, number] export default class Mouse { - private mouse: HTMLDivElement + private readonly mouse: HTMLDivElement private position: [number,number] = [0,0,] constructor() { this.mouse = document.createElement('div') @@ -52,8 +52,8 @@ export default class Mouse { private readonly pScrEl = document.scrollingElement || document.documentElement // Is it always correct private lastScrEl: Element | 'window' | null = null - private resetLastScrEl = () => { this.lastScrEl = null } - private handleWScroll = e => { + private readonly resetLastScrEl = () => { this.lastScrEl = null } + private readonly handleWScroll = e => { if (e.target !== this.lastScrEl && this.lastScrEl !== 'window') { this.resetLastScrEl() @@ -111,4 +111,4 @@ export default class Mouse { window.removeEventListener('scroll', this.handleWScroll) window.removeEventListener('resize', this.resetLastScrEl) } -} \ No newline at end of file +} diff --git a/tracker/tracker-assist/src/RemoteControl.ts b/tracker/tracker-assist/src/RemoteControl.ts index 4cbc785f3..5cad2ac18 100644 --- a/tracker/tracker-assist/src/RemoteControl.ts +++ b/tracker/tracker-assist/src/RemoteControl.ts @@ -23,9 +23,9 @@ export default class RemoteControl { private agentID: string | null = null constructor( - private options: AssistOptions, - private onGrand: (sting?) => void, - private onRelease: (sting?) => void) {} + private readonly options: AssistOptions, + private readonly onGrand: (sting?) => void, + private readonly onRelease: (sting?) => void) {} reconnect(ids: string[]) { const storedID = sessionStorage.getItem(this.options.session_control_peer_key) @@ -56,7 +56,7 @@ export default class RemoteControl { } else { this.releaseControl(id) } - }).catch() + }).catch(e => console.error(e)) } grantControl = (id: string) => { this.agentID = id @@ -99,4 +99,4 @@ export default class RemoteControl { this.focused.innerText = value } } -} \ No newline at end of file +} From f791375d4280f18c2aa4ed09ed37d472e1198789 Mon Sep 17 00:00:00 2001 From: sylenien Date: Thu, 4 Aug 2022 16:20:06 +0200 Subject: [PATCH 042/168] fix(ui/player): refactor older files --- frontend/app/components/Session/LivePlayer.js | 22 +++++++++++-------- .../StatedScreen/StatedScreen.ts | 2 +- frontend/app/player/Player.ts | 17 +++++++------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/frontend/app/components/Session/LivePlayer.js b/frontend/app/components/Session/LivePlayer.js index 5793e2d52..b26e80923 100644 --- a/frontend/app/components/Session/LivePlayer.js +++ b/frontend/app/components/Session/LivePlayer.js @@ -16,13 +16,20 @@ import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; import PlayerBlock from '../Session_/PlayerBlock'; import styles from '../Session_/session.module.css'; - const InitLoader = connectPlayer(state => ({ loading: !state.initialized }))(Loader); - -function LivePlayer ({ session, toggleFullscreen, closeBottomBlock, fullscreen, jwt, loadingCredentials, assistCredendials, request, isEnterprise, hasErrors }) { +function LivePlayer ({ + session, + toggleFullscreen, + closeBottomBlock, + fullscreen, + loadingCredentials, + assistCredendials, + request, + isEnterprise, +}) { useEffect(() => { if (!loadingCredentials) { initPlayer(session, assistCredendials, true); @@ -47,7 +54,6 @@ function LivePlayer ({ session, toggleFullscreen, closeBottomBlock, fullscreen, } const [activeTab, setActiveTab] = useState(''); - return ( @@ -62,19 +68,17 @@ function LivePlayer ({ session, toggleFullscreen, closeBottomBlock, fullscreen, export default withRequest({ initialData: null, - endpoint: '/assist/credentials', - dataWrapper: data => data, - dataName: 'assistCredendials', + endpoint: '/assist/credentials', + dataWrapper: data => data, + dataName: 'assistCredendials', loadingName: 'loadingCredentials', })(withPermissions(['ASSIST_LIVE'], '', true)(connect( state => { return { session: state.getIn([ 'sessions', 'current' ]), showAssist: state.getIn([ 'sessions', 'showChatWindow' ]), - jwt: state.get('jwt'), fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]), isEnterprise: state.getIn([ 'user', 'account', 'edition' ]) === 'ee', - hasErrors: !!state.getIn([ 'sessions', 'errors' ]), } }, { toggleFullscreen, closeBottomBlock }, diff --git a/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts b/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts index 177419f35..29fead989 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts +++ b/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts @@ -81,7 +81,7 @@ export default class StatedScreen extends Screen { const { markedTargets } = getState(); if (markedTargets) { update({ - markedTargets: markedTargets.map(mt => ({ + markedTargets: markedTargets.map((mt: any) => ({ ...mt, boundingRect: this.calculateRelativeBoundingRect(mt.el), })), diff --git a/frontend/app/player/Player.ts b/frontend/app/player/Player.ts index 2b1d0b405..c7f37c0ca 100644 --- a/frontend/app/player/Player.ts +++ b/frontend/app/player/Player.ts @@ -1,11 +1,12 @@ import { goTo as listsGoTo } from './lists'; import { update, getState } from './store'; -import MessageDistributor, { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './MessageDistributor/MessageDistributor'; +import MessageDistributor, { INITIAL_STATE as SUPER_INITIAL_STATE } from './MessageDistributor/MessageDistributor'; const fps = 60; const performance = window.performance || { now: Date.now.bind(Date) }; const requestAnimationFrame = window.requestAnimationFrame || + // @ts-ignore window.webkitRequestAnimationFrame || // @ts-ignore window.mozRequestAnimationFrame || @@ -13,7 +14,7 @@ const requestAnimationFrame = window.oRequestAnimationFrame || // @ts-ignore window.msRequestAnimationFrame || - (callback => window.setTimeout(() => { callback(performance.now()); }, 1000 / fps)); + ((callback: (args: any) => void) => window.setTimeout(() => { callback(performance.now()); }, 1000 / fps)); const cancelAnimationFrame = window.cancelAnimationFrame || // @ts-ignore @@ -71,7 +72,7 @@ export default class Player extends MessageDistributor { let prevTime = getState().time; let animationPrevTime = performance.now(); - const nextFrame = (animationCurrentTime) => { + const nextFrame = (animationCurrentTime: number) => { const { speed, skip, @@ -91,7 +92,7 @@ export default class Player extends MessageDistributor { let time = prevTime + diffTime; - const skipInterval = skip && skipIntervals.find(si => si.contains(time)); // TODO: good skip by messages + const skipInterval = skip && skipIntervals.find((si: Node) => si.contains(time)); // TODO: good skip by messages if (skipInterval) time = skipInterval.end; const fmt = super.getFirstMessageTime(); @@ -151,7 +152,7 @@ export default class Player extends MessageDistributor { } } - jump(time = getState().time, index) { + jump(time = getState().time, index: number) { const { live } = getState(); if (live) return; @@ -176,7 +177,7 @@ export default class Player extends MessageDistributor { update({ skip }); } - toggleInspectorMode(flag, clickCallback) { + toggleInspectorMode(flag: boolean, clickCallback?: (args: any) => void) { if (typeof flag !== 'boolean') { const { inspectorMode } = getState(); flag = !inspectorMode; @@ -197,7 +198,7 @@ export default class Player extends MessageDistributor { this.setMarkedTargets(targets); } - activeTarget(index) { + activeTarget(index: number) { this.setActiveTarget(index); } @@ -219,7 +220,7 @@ export default class Player extends MessageDistributor { update({ autoplay }); } - toggleEvents(shouldShow = undefined) { + toggleEvents(shouldShow?: boolean) { const showEvents = shouldShow || !getState().showEvents; localStorage.setItem(SHOW_EVENTS_STORAGE_KEY, `${showEvents}`); update({ showEvents }); From 29599625a952a0c49a320e1d4c0f2e24c498a425 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 9 Aug 2022 12:31:08 +0200 Subject: [PATCH 043/168] fix(ui/tracker): fix muting stream button, cleanup --- .../Assist/components/AssistActions/AssistActions.tsx | 1 - .../app/player/MessageDistributor/managers/AssistManager.ts | 4 ++-- tracker/tracker-assist/package.json | 2 +- tracker/tracker-assist/src/Assist.ts | 5 +++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index 757beca54..90afa772a 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -69,7 +69,6 @@ function AssistActions({ }, [peerConnectionStatus]); const addIncomeStream = (stream: MediaStream) => { - console.log('new stream in component') setIncomeStream(oldState => [...oldState, stream]); } diff --git a/frontend/app/player/MessageDistributor/managers/AssistManager.ts b/frontend/app/player/MessageDistributor/managers/AssistManager.ts index 11d5e948c..d56bf3a39 100644 --- a/frontend/app/player/MessageDistributor/managers/AssistManager.ts +++ b/frontend/app/player/MessageDistributor/managers/AssistManager.ts @@ -341,7 +341,7 @@ export default class AssistManager { call.on('stream', stream => { this.callArgs && this.callArgs.onStream(stream) }); - call.peerConnection.addEventListener("track", e => console.log('newtrack',e.track)) + // call.peerConnection.addEventListener("track", e => console.log('newtrack',e.track)) call.on("close", this.onRemoteCallEnd) call.on("error", (e) => { @@ -467,7 +467,7 @@ export default class AssistManager { getState().calling !== CallingState.OnCall && update({ calling: CallingState.OnCall }) this.callArgs && this.callArgs.onStream(stream) }); - call.peerConnection.addEventListener("track", e => console.log('newtrack',e.track)) + // call.peerConnection.addEventListener("track", e => console.log('newtrack',e.track)) call.on("close", this.onRemoteCallEnd) call.on("error", (e) => { diff --git a/tracker/tracker-assist/package.json b/tracker/tracker-assist/package.json index 7849eb6ff..5e63e462e 100644 --- a/tracker/tracker-assist/package.json +++ b/tracker/tracker-assist/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker-assist", "description": "Tracker plugin for screen assistance through the WebRTC", - "version": "3.5.16", + "version": "3.5.17", "keywords": [ "WebRTC", "assistance", diff --git a/tracker/tracker-assist/src/Assist.ts b/tracker/tracker-assist/src/Assist.ts index fa4b071d1..a407271de 100644 --- a/tracker/tracker-assist/src/Assist.ts +++ b/tracker/tracker-assist/src/Assist.ts @@ -361,13 +361,14 @@ export default class Assist { callUI = new CallWindow(app.debug.error) // TODO: as constructor options callUI.setCallEndAction(initiateCallEnd) - callUI.setLocalStreams(Object.values(lStreams)) } if (!annot) { annot = new AnnotationCanvas() annot.mount() } - + // have to be updated + callUI.setLocalStreams(Object.values(lStreams)) + call.on('error', e => { app.debug.warn('Call error:', e) initiateCallEnd() From d6a4f420668e44bdee0348866f10e3a0fcd66052 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 9 Aug 2022 13:44:11 +0200 Subject: [PATCH 044/168] feat(sourcemaps): fixed mapping file permissions --- api/Dockerfile | 2 +- ee/api/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/Dockerfile b/api/Dockerfile index 0d949e25e..5ee6af463 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -20,7 +20,7 @@ RUN cd /work_tmp && npm install WORKDIR /work COPY . . -RUN mv env.default .env && mv /work_tmp/node_modules sourcemap-reader/. +RUN mv env.default .env && mv /work_tmp/node_modules sourcemap-reader/. && chmod 644 /mappings.wasm RUN adduser -u 1001 openreplay -D USER 1001 diff --git a/ee/api/Dockerfile b/ee/api/Dockerfile index 2500d2bfb..73aa4a6a4 100644 --- a/ee/api/Dockerfile +++ b/ee/api/Dockerfile @@ -18,7 +18,7 @@ RUN cd /work_tmp && npm install WORKDIR /work COPY . . -RUN mv env.default .env && mv /work_tmp/node_modules sourcemap-reader/. +RUN mv env.default .env && mv /work_tmp/node_modules sourcemap-reader/. && chmod 644 /mappings.wasm RUN adduser -u 1001 openreplay -D USER 1001 From 738217d979ee0436f50d8b1c1e5e5d28ca2d1a4e Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 9 Aug 2022 15:52:48 +0200 Subject: [PATCH 045/168] feat(chalice): fixed try payload validation for old data --- api/schemas.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/schemas.py b/api/schemas.py index 314a0f7d2..fdea7f439 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -560,6 +560,8 @@ class _SessionSearchEventRaw(__MixedSearchFilter): assert len(values["source"]) > 0 and isinstance(values["source"][0], int), \ f"source of type int if required for {PerformanceEventType.time_between_events}" else: + assert "source" in values, f"source is required for {values.get('type')}" + assert isinstance(values["source"], list), f"source of type list is required for {values.get('type')}" for c in values["source"]: assert isinstance(c, int), f"source value should be of type int for {values.get('type')}" elif values.get("type") == EventType.error and values.get("source") is None: From 8c9982124291e5379ddf845fece9c93bd3eb1746 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 9 Aug 2022 14:29:58 +0200 Subject: [PATCH 046/168] fix(ui) - metric filters metadata and performance filters --- .../shared/Filters/FilterItem/FilterItem.tsx | 2 +- frontend/app/mstore/types/filterItem.ts | 115 ++++++++++-------- 2 files changed, 65 insertions(+), 52 deletions(-) diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index 223880b39..c90428e96 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -73,7 +73,7 @@ function FilterItem(props: Props) { )} {/* Filter values */} - {!isSubFilter && ( + {!isSubFilter && filter.operatorOptions && ( <> { - this[key] = data[key] - }) + merge(data: any) { + Object.keys(data).forEach((key) => { + this[key] = data[key]; + }); } - fromJson(json, mainFilterKey = '') { - let _filter = filtersMap[json.type] || {} + fromJson(json: any, mainFilterKey = '') { + const isMetadata = json.type === FilterKey.METADATA; + let _filter: any = (isMetadata ? filtersMap[json.source] : filtersMap[json.type]) || {}; + if (mainFilterKey) { const mainFilter = filtersMap[mainFilterKey]; - const subFilterMap = {} - mainFilter.filters.forEach(option => { - subFilterMap[option.key] = option - }) - _filter = subFilterMap[json.type] + const subFilterMap = {}; + mainFilter.filters.forEach((option: any) => { + subFilterMap[option.key] = option; + }); + _filter = subFilterMap[json.type]; } - this.type = _filter.type - this.key = _filter.key - this.label = _filter.label - this.operatorOptions = _filter.operatorOptions - this.options = _filter.options - this.isEvent = _filter.isEvent + this.type = _filter.type; + this.key = _filter.key; + this.label = _filter.label; + this.operatorOptions = _filter.operatorOptions; + this.hasSource = _filter.hasSource; + this.category = _filter.category; + this.sourceOperatorOptions = _filter.sourceOperatorOptions; + this.options = _filter.options; + this.isEvent = _filter.isEvent; - this.value = json.value.length === 0 || !json.value ? [""] : json.value, - this.operator = json.operator - - this.filters = _filter.type === FilterType.SUB_FILTERS && json.filters ? json.filters.map(i => new FilterItem().fromJson(i, json.type)) : [] + (this.value = json.value.length === 0 || !json.value ? [''] : json.value), (this.operator = json.operator); + this.sourceOperator = json.sourceOperator; - this.completed = json.completed - this.dropped = json.dropped - return this + this.filters = + _filter.type === FilterType.SUB_FILTERS && json.filters ? json.filters.map((i: any) => new FilterItem().fromJson(i, json.type)) : []; + + this.completed = json.completed; + this.dropped = json.dropped; + return this; } - toJson() { + toJson(): any { + const isMetadata = this.category === FilterCategory.METADATA; const json = { - type: this.key, + type: isMetadata ? FilterKey.METADATA : this.key, isEvent: this.isEvent, value: this.value, operator: this.operator, - source: this.source, - filters: Array.isArray(this.filters) ? this.filters.map(i => i.toJson()) : [], - } - return json + source: isMetadata ? this.key : this.source, + sourceOperator: this.sourceOperator, + filters: Array.isArray(this.filters) ? this.filters.map((i) => i.toJson()) : [], + }; + return json; } -} \ No newline at end of file +} From 3df000bc6d96ed84e95456c1b29ae8526bad414e Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 9 Aug 2022 15:31:13 +0200 Subject: [PATCH 047/168] fix(ui) - filters source, fetch navigation tooltips --- .../Session_/TimeTable/TimeTable.tsx | 20 +++++++++++++++++-- .../shared/Filters/FilterItem/FilterItem.tsx | 2 +- frontend/app/components/ui/Button/Button.tsx | 8 ++++++-- frontend/app/mstore/types/filterItem.ts | 2 ++ 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/frontend/app/components/Session_/TimeTable/TimeTable.tsx b/frontend/app/components/Session_/TimeTable/TimeTable.tsx index 4a6f1140e..a2c7ef61c 100644 --- a/frontend/app/components/Session_/TimeTable/TimeTable.tsx +++ b/frontend/app/components/Session_/TimeTable/TimeTable.tsx @@ -227,8 +227,24 @@ export default class TimeTable extends React.PureComponent {
{navigation && (
- ); + + return tooltip ? {render()} : render(); }; diff --git a/frontend/app/mstore/types/filterItem.ts b/frontend/app/mstore/types/filterItem.ts index 3bfa3bf08..5a2a8eb6b 100644 --- a/frontend/app/mstore/types/filterItem.ts +++ b/frontend/app/mstore/types/filterItem.ts @@ -31,6 +31,7 @@ export default class FilterItem { filters: observable, isActive: observable, sourceOperator: observable, + category: observable, merge: action, }); @@ -77,6 +78,7 @@ export default class FilterItem { this.isEvent = _filter.isEvent; (this.value = json.value.length === 0 || !json.value ? [''] : json.value), (this.operator = json.operator); + this.source = json.source; this.sourceOperator = json.sourceOperator; this.filters = From dc46111893e5abbe013813f3597d6a6dcfa9d478 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 9 Aug 2022 16:16:03 +0200 Subject: [PATCH 048/168] change(ui) - show navigation only on errors --- frontend/app/components/Session_/Fetch/Fetch.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/app/components/Session_/Fetch/Fetch.js b/frontend/app/components/Session_/Fetch/Fetch.js index 2dff482c9..be2bcde2a 100644 --- a/frontend/app/components/Session_/Fetch/Fetch.js +++ b/frontend/app/components/Session_/Fetch/Fetch.js @@ -74,12 +74,13 @@ export default class Fetch extends React.PureComponent { if (currentIndex === 0) return; const newIndex = currentIndex - 1; this.setCurrent(filteredList[newIndex], newIndex); - this.setState({ showFetchDetails: true }); + this.setState({ showFetchDetails: true }); }; render() { const { listNow } = this.props; const { current, currentIndex, showFetchDetails, filteredList } = this.state; + const hasErrors = filteredList.some((r) => r.status >= 400); return ( - + {[ { label: 'Status', From e3a2b5260efec37b12f94808ed8fa719888974af Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 9 Aug 2022 17:36:38 +0200 Subject: [PATCH 049/168] fix(ui) - scrollToRow check for the element exist --- frontend/app/components/Session_/TimeTable/TimeTable.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/app/components/Session_/TimeTable/TimeTable.tsx b/frontend/app/components/Session_/TimeTable/TimeTable.tsx index a2c7ef61c..9ea553dc1 100644 --- a/frontend/app/components/Session_/TimeTable/TimeTable.tsx +++ b/frontend/app/components/Session_/TimeTable/TimeTable.tsx @@ -118,7 +118,9 @@ export default class TimeTable extends React.PureComponent { autoScroll = true; componentDidMount() { - this.scroller.current.scrollToRow(this.props.activeIndex); + if (this.scroller.current) { + this.scroller.current.scrollToRow(this.props.activeIndex); + } } componentDidUpdate(prevProps: any, prevState: any) { @@ -135,7 +137,7 @@ export default class TimeTable extends React.PureComponent { ...computeTimeLine(this.props.rows, this.state.firstVisibleRowIndex, this.visibleCount), }); } - if (this.props.activeIndex >= 0 && prevProps.activeIndex !== this.props.activeIndex) { + if (this.props.activeIndex >= 0 && prevProps.activeIndex !== this.props.activeIndex && this.scroller.current) { this.scroller.current.scrollToRow(this.props.activeIndex); } } From 36b4434254dcb1d97d17d42411df2977e9d2c6d3 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 9 Aug 2022 18:11:23 +0200 Subject: [PATCH 050/168] fix(ui) - assist sort options store --- .../shared/LiveSessionList/LiveSessionList.tsx | 17 ++++++++++------- frontend/app/duck/liveSearch.js | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx index f4bb1f45d..89aa0d9eb 100644 --- a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx @@ -39,16 +39,19 @@ function LiveSessionList(props: Props) { var timeoutId: any; const { filters } = filter; const hasUserFilter = filters.map((i: any) => i.key).includes(KEYS.USERID); - const sortOptions = [{ label: 'Newest', value: 'timestamp' }].concat(metaList - .map((i: any) => ({ - label: capitalize(i), - value: i, - })).toJS()); + const sortOptions = [{ label: 'Newest', value: 'timestamp' }].concat( + metaList + .map((i: any) => ({ + label: capitalize(i), + value: i, + })) + .toJS() + ); useEffect(() => { if (metaListLoading) return; const _filter = { ...filter }; - if (sortOptions[1]) { + if (sortOptions[1] && !filter.sort) { _filter.sort = sortOptions[1].value; } props.applyFilter(_filter); @@ -139,7 +142,7 @@ function LiveSessionList(props: Props) { -
+
Date: Tue, 9 Aug 2022 18:33:19 +0200 Subject: [PATCH 051/168] feat(assist): handle nested response --- utilities/utils/helper.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/utilities/utils/helper.js b/utilities/utils/helper.js index b012ccf6c..22fcd0fd5 100644 --- a/utilities/utils/helper.js +++ b/utilities/utils/helper.js @@ -174,6 +174,13 @@ const getValue = function (obj, key) { return undefined; } const sortPaginate = function (list, filters) { + if (typeof (list) === "object" && !Array.isArray(list)) { + for (const [key, value] of Object.entries(list)) { + list[key] = sortPaginate(value, filters); + } + return list + } + const total = list.length; list.sort((a, b) => { const tA = getValue(a, "timestamp"); From 6d7a0ea740021b95184f11c9025791b4c21dad83 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 9 Aug 2022 20:02:24 +0200 Subject: [PATCH 052/168] feat(alerts): debug --- api/chalicelib/core/alerts.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/chalicelib/core/alerts.py b/api/chalicelib/core/alerts.py index e5316ba06..55bd4a29e 100644 --- a/api/chalicelib/core/alerts.py +++ b/api/chalicelib/core/alerts.py @@ -138,7 +138,10 @@ def send_by_email(notification, destination): def send_by_email_batch(notifications_list): + if not helper.has_smtp(): + print("no SMTP configuration for email notifications") if notifications_list is None or len(notifications_list) == 0: + print("no email notifications") return for n in notifications_list: send_by_email(notification=n.get("notification"), destination=n.get("destination")) From b517903a1306b83fc1529d812d01373393304cc2 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 10 Aug 2022 11:31:33 +0200 Subject: [PATCH 053/168] change(ui) - removed session count from tab --- .../components/SessionHeader/SessionHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx index f0fe2b7d2..b2bbc1e65 100644 --- a/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx @@ -42,7 +42,7 @@ function SessionHeader(props: Props) { })} onClick={() => props.setActiveTab({ type: 'all' })} > - SESSIONS {listCount} + SESSIONS
Date: Wed, 10 Aug 2022 11:34:25 +0200 Subject: [PATCH 054/168] change(ui) - tags casing --- .../Alerts/DropdownChips/DropdownChips.js | 123 ++++++++---------- .../app/components/ui/TagBadge/TagBadge.js | 50 ++++--- .../ui/TagBadge/tagBadge.module.css | 2 +- 3 files changed, 79 insertions(+), 96 deletions(-) diff --git a/frontend/app/components/Alerts/DropdownChips/DropdownChips.js b/frontend/app/components/Alerts/DropdownChips/DropdownChips.js index 7a7e81ada..1f805057d 100644 --- a/frontend/app/components/Alerts/DropdownChips/DropdownChips.js +++ b/frontend/app/components/Alerts/DropdownChips/DropdownChips.js @@ -1,79 +1,66 @@ -import React from 'react' +import React from 'react'; import { Input, TagBadge } from 'UI'; import Select from 'Shared/Select'; -const DropdownChips = ({ - textFiled = false, - validate = null, - placeholder = '', - selected = [], - options = [], - badgeClassName = 'lowercase', - onChange = () => null, - ...props +const DropdownChips = ({ + textFiled = false, + validate = null, + placeholder = '', + selected = [], + options = [], + badgeClassName = 'lowercase', + onChange = () => null, + ...props }) => { - const onRemove = id => { - onChange(selected.filter(i => i !== id)) - } + const onRemove = (id) => { + onChange(selected.filter((i) => i !== id)); + }; - const onSelect = ({ value }) => { - const newSlected = selected.concat(value.value); - onChange(newSlected) - }; + const onSelect = ({ value }) => { + const newSlected = selected.concat(value.value); + onChange(newSlected); + }; - const onKeyPress = e => { - const val = e.target.value; - if (e.key !== 'Enter' || selected.includes(val)) return; - e.preventDefault(); - e.stopPropagation(); - if (validate && !validate(val)) return; + const onKeyPress = (e) => { + const val = e.target.value; + if (e.key !== 'Enter' || selected.includes(val)) return; + e.preventDefault(); + e.stopPropagation(); + if (validate && !validate(val)) return; - const newSlected = selected.concat(val); - e.target.value = ''; - onChange(newSlected); - } + const newSlected = selected.concat(val); + e.target.value = ''; + onChange(newSlected); + }; - const _options = options.filter(item => !selected.includes(item.value)) + const _options = options.filter((item) => !selected.includes(item.value)); + + const renderBadge = (item) => { + const val = typeof item === 'string' ? item : item.value; + const text = typeof item === 'string' ? item : item.label; + return onRemove(val)} outline={true} />; + }; - const renderBadge = item => { - const val = typeof item === 'string' ? item : item.value; - const text = typeof item === 'string' ? item : item.label; return ( - onRemove(val) } - outline={ true } - /> - ) - } +
+ {textFiled ? ( + + ) : ( + - ) : ( - setShowModal(true) } - onBlur={ () => setTimeout(setShowModal, 200, false) } - onChange={ onSearchChange } - placeholder={ 'Search sessions using any captured event (click, input, page, error...)'} - id="search" - type="search" - autoComplete="off" - className="hover:border-gray-medium" - /> + return ( +
+ setShowModal(true)} + onBlur={() => setTimeout(setShowModal, 200, false)} + onChange={onSearchChange} + placeholder={'Search sessions using any captured event (click, input, page, error...)'} + id="search" + type="search" + autoComplete="off" + className="hover:border-gray-medium text-lg placeholder-lg" + /> - { showModal && ( -
- + {showModal && ( +
+ +
+ )}
- )} -
- ); + ); } -export default connect((state: any) => ({ - filterSearchList: state.getIn([ 'search', 'filterSearchList' ]), - filterSearchListLive: state.getIn([ 'liveSearch', 'filterSearchList' ]), - filterList: state.getIn([ 'search', 'filterList' ]), - filterListLive: state.getIn([ 'search', 'filterListLive' ]), -}), { })(SessionSearchField); +export default connect( + (state: any) => ({ + filterSearchList: state.getIn(['search', 'filterSearchList']), + filterSearchListLive: state.getIn(['liveSearch', 'filterSearchList']), + filterList: state.getIn(['search', 'filterList']), + filterListLive: state.getIn(['search', 'filterListLive']), + }), + {} +)(SessionSearchField); From c6ec3cc7de302b8398e4579437a29cdb0d13a0ba Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 11 Aug 2022 12:00:14 +0200 Subject: [PATCH 066/168] change(ui) - no records yet message changes --- .../NoSessionsMessage/NoSessionsMessage.js | 85 +++++++++++-------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/frontend/app/components/shared/NoSessionsMessage/NoSessionsMessage.js b/frontend/app/components/shared/NoSessionsMessage/NoSessionsMessage.js index 01f8a66b2..79dc8e436 100644 --- a/frontend/app/components/shared/NoSessionsMessage/NoSessionsMessage.js +++ b/frontend/app/components/shared/NoSessionsMessage/NoSessionsMessage.js @@ -1,40 +1,55 @@ -import React from 'react' -import { Icon, Button } from 'UI' -import { connect } from 'react-redux' -import { onboarding as onboardingRoute } from 'App/routes' +import React from 'react'; +import { Icon, Button } from 'UI'; +import { connect } from 'react-redux'; +import { onboarding as onboardingRoute } from 'App/routes'; import { withRouter } from 'react-router-dom'; import * as routes from '../../../routes'; const withSiteId = routes.withSiteId; -const NoSessionsMessage= (props) => { - const { sites, match: { params: { siteId } } } = props; - const activeSite = sites.find(s => s.id == siteId); - const showNoSessions = !!activeSite && !activeSite.recorded; - return ( - <> - {showNoSessions && ( -
-
-
-
- -
-
- It takes a few minutes for first recordings to appear. All set but they are still not showing up? Check our troubleshooting section. -
- -
-
-
- )} - - ) -} +const NoSessionsMessage = (props) => { + const { + sites, + match: { + params: { siteId }, + }, + } = props; + const activeSite = sites.find((s) => s.id == siteId); + const showNoSessions = !!activeSite && !activeSite.recorded; + return ( + <> + {showNoSessions && ( +
+
+
+
+ +
+
+ It might take a few minutes for first recording to appear. + + Troubleshoot + + . +
+ +
+
+
+ )} + + ); +}; -export default connect(state => ({ - site: state.getIn([ 'site', 'siteId' ]), - sites: state.getIn([ 'site', 'list' ]) -}))(withRouter(NoSessionsMessage)) \ No newline at end of file +export default connect((state) => ({ + site: state.getIn(['site', 'siteId']), + sites: state.getIn(['site', 'list']), +}))(withRouter(NoSessionsMessage)); From be91fa3fb8ea76c62957057488d50993f6940c38 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 11 Aug 2022 12:01:07 +0200 Subject: [PATCH 067/168] change(ui) - session search filed placeholder text --- frontend/app/styles/general.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/app/styles/general.css b/frontend/app/styles/general.css index 05e1d31f3..f0b80a54d 100644 --- a/frontend/app/styles/general.css +++ b/frontend/app/styles/general.css @@ -143,6 +143,11 @@ font-size: 14px; } +.placeholder-lg::placeholder { + color: $gray-medium !important; + font-size: 16px; +} + .ui[class*="top fixed"].menu { background-color: white !important; border-bottom: solid thin #ddd !important; From c1bbeb61c031d3e5f1850f121ee345a10f1df228 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 11 Aug 2022 12:01:36 +0200 Subject: [PATCH 068/168] change(ui) - live list pagination --- .../LiveSessionList/LiveSessionList.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx index 8ad601039..b6e64c57c 100644 --- a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx @@ -151,18 +151,18 @@ function LiveSessionList(props: Props) { ))}
+ +
+ props.updateCurrentPage(page)} + limit={PER_PAGE} + debounceRequest={500} + /> +
- -
- props.updateCurrentPage(page)} - limit={PER_PAGE} - debounceRequest={500} - /> -
); From 4ebcff74e106c8ee8ba0d6fb282073901403869a Mon Sep 17 00:00:00 2001 From: Delirium Date: Thu, 11 Aug 2022 13:07:34 +0300 Subject: [PATCH 069/168] feat(ui): make ui able to load unprocessed session files (#652) * feat(ui): make ui able to load unprocessed session files * feat(ui): some lgos * feat(ui): connect api, rewrite old code * feat(ui): create testing ui functions * feat(ui/player): add ability to jump back in time for assist * feat(ui/player): rewrite for better readability * fix(ui/player): small refactor for better readability * fix(ui/player): fix private prop * fix(ui/player): add tooltip * feat(ui/player): create time calculating tooltip * fix(player): fix message timestamp * fix(ui/player): cleanup * fix(ui/player): handle errors for unprocessed files as well * fix(player): fix logged message * fix(player): code review fixes * fix(ui): fix circle color, fix button text * fix(tracker): code review * fix(player): small style fixes --- frontend/app/api_client.js | 1 + frontend/app/components/Session/LivePlayer.js | 2 +- frontend/app/components/Session/Session.js | 6 +- .../Session_/Player/Controls/Circle.tsx | 8 +- .../Session_/Player/Controls/Controls.js | 106 +++---- .../Player/Controls/CustomDragLayer.tsx | 2 +- .../Player/Controls/DraggableCircle.tsx | 8 +- .../Session_/Player/Controls/Time.js | 31 +- .../Session_/Player/Controls/TimeTooltip.tsx | 27 ++ .../Session_/Player/Controls/Timeline.js | 55 +++- .../Controls/components/PlayerControls.tsx | 111 ++++++++ .../Controls/components/TooltipContainer.tsx | 15 + .../Player/Controls/controls.module.css | 3 - .../Player/Controls/timeline.module.css | 32 +++ .../components/Session_/Player/Overlay.tsx | 10 +- .../Session_/Player/Overlay/AutoplayTimer.tsx | 14 +- frontend/app/components/Session_/Subheader.js | 63 ++-- .../shared/LiveTag/LiveTag.module.css | 14 +- .../app/components/shared/LiveTag/LiveTag.tsx | 4 +- frontend/app/duck/sessions.js | 12 + .../MessageDistributor/MessageDistributor.ts | 269 ++++++++++++------ .../managers/AssistManager.ts | 35 ++- .../MessageDistributor/managers/ListWalker.ts | 2 +- .../messages/MFileReader.ts | 8 +- .../messages/MStreamReader.ts | 2 +- .../MessageDistributor/messages/timed.ts | 2 +- .../MessageDistributor/network/loadFiles.ts | 63 ++-- frontend/app/player/Player.ts | 32 ++- frontend/app/player/singletone.js | 2 + 29 files changed, 671 insertions(+), 268 deletions(-) create mode 100644 frontend/app/components/Session_/Player/Controls/TimeTooltip.tsx create mode 100644 frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx create mode 100644 frontend/app/components/Session_/Player/Controls/components/TooltipContainer.tsx diff --git a/frontend/app/api_client.js b/frontend/app/api_client.js index 1f85d5af9..33f7ffe66 100644 --- a/frontend/app/api_client.js +++ b/frontend/app/api_client.js @@ -25,6 +25,7 @@ const siteIdRequiredPaths = [ '/custom_metrics', '/dashboards', '/metrics', + '/unprocessed', // '/custom_metrics/sessions', ]; diff --git a/frontend/app/components/Session/LivePlayer.js b/frontend/app/components/Session/LivePlayer.js index b26e80923..c142167f0 100644 --- a/frontend/app/components/Session/LivePlayer.js +++ b/frontend/app/components/Session/LivePlayer.js @@ -57,7 +57,7 @@ function LivePlayer ({ return ( - +
diff --git a/frontend/app/components/Session/Session.js b/frontend/app/components/Session/Session.js index 2d9bfa882..d6bf31a53 100644 --- a/frontend/app/components/Session/Session.js +++ b/frontend/app/components/Session/Session.js @@ -15,10 +15,10 @@ const SESSIONS_ROUTE = sessionsRoute(); function Session({ sessionId, loading, - hasErrors, + hasErrors, session, fetchSession, - fetchSlackList, + fetchSlackList, }) { usePageTitle("OpenReplay Session Player"); const [ initializing, setInitializing ] = useState(true) @@ -63,4 +63,4 @@ export default withPermissions(['SESSION_REPLAY'], '', true)(connect((state, pro }, { fetchSession, fetchSlackList, -})(Session)); \ No newline at end of file +})(Session)); diff --git a/frontend/app/components/Session_/Player/Controls/Circle.tsx b/frontend/app/components/Session_/Player/Controls/Circle.tsx index 274b38f8a..73e1e1bb1 100644 --- a/frontend/app/components/Session_/Player/Controls/Circle.tsx +++ b/frontend/app/components/Session_/Player/Controls/Circle.tsx @@ -1,16 +1,18 @@ import React, { memo, FC } from 'react'; import styles from './timeline.module.css'; +import cn from 'classnames'; interface Props { preview?: boolean; + isGreen?: boolean; } -export const Circle: FC = memo(function Box({ preview }) { +export const Circle: FC = memo(function Box({ preview, isGreen }) { return (
) }) -export default Circle; \ No newline at end of file +export default Circle; diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js index e92099393..73371c6e1 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ b/frontend/app/components/Session_/Player/Controls/Controls.js @@ -8,6 +8,11 @@ import { selectStorageListNow, } from 'Player/store'; import LiveTag from 'Shared/LiveTag'; +import { session as sessionRoute, withSiteId } from 'App/routes'; +import { + toggleTimetravel, + jumpToLive, +} from 'Player'; import { Icon } from 'UI'; import { toggleInspectorMode } from 'Player'; @@ -26,9 +31,10 @@ import { EXCEPTIONS, INSPECTOR, } from 'Duck/components/player'; -import { ReduxTime } from './Time'; +import { ReduxTime, AssistDuration } from './Time'; import Timeline from './Timeline'; import ControlButton from './ControlButton'; +import PlayerControls from './components/PlayerControls' import styles from './controls.module.css'; import { Tooltip } from 'react-tippy'; @@ -79,7 +85,6 @@ function getStorageName(type) { fullscreenDisabled: state.messagesLoading, logCount: state.logListNow.length, logRedCount: state.logRedCountNow, - // resourceCount: state.resourceCountNow, resourceRedCount: state.resourceRedCountNow, fetchRedCount: state.fetchRedCountNow, showStack: state.stackList.length > 0, @@ -97,6 +102,7 @@ function getStorageName(type) { exceptionsCount: state.exceptionsListNow.length, showExceptions: state.exceptionsList.length > 0, showLongtasks: state.longtasksList.length > 0, + liveTimeTravel: state.liveTimeTravel, })) @connect((state, props) => { const permissions = state.getIn([ 'user', 'account', 'permissions' ]) || []; @@ -129,7 +135,6 @@ export default class Controls extends React.Component { if ( nextProps.fullscreen !== this.props.fullscreen || nextProps.bottomBlock !== this.props.bottomBlock || - nextProps.endTime !== this.props.endTime || nextProps.live !== this.props.live || nextProps.livePlay !== this.props.livePlay || nextProps.playing !== this.props.playing || @@ -158,7 +163,8 @@ export default class Controls extends React.Component { nextProps.graphqlCount !== this.props.graphqlCount || nextProps.showExceptions !== this.props.showExceptions || nextProps.exceptionsCount !== this.props.exceptionsCount || - nextProps.showLongtasks !== this.props.showLongtasks + nextProps.showLongtasks !== this.props.showLongtasks || + nextProps.liveTimeTravel !== this.props.liveTimeTravel ) return true; return false; } @@ -206,7 +212,7 @@ export default class Controls extends React.Component { goLive =() => this.props.jump(this.props.endTime) renderPlayBtn = () => { - const { completed, playing, disabled } = this.props; + const { completed, playing } = this.props; let label; let icon; if (completed) { @@ -279,6 +285,9 @@ export default class Controls extends React.Component { fullscreen, inspectorMode, closedLive, + toggleSpeed, + toggleSkip, + liveTimeTravel, } = this.props; const toggleBottomTools = (blockName) => { @@ -290,75 +299,38 @@ export default class Controls extends React.Component { toggleBottomBlock(blockName); } } + return ( -
- { !live && } +
+ { !live || liveTimeTravel ? : null} { !fullscreen && -
+
- { !live && ( -
- { this.renderPlayBtn() } - { !live && ( -
- - / - -
- )} - -
- - {this.controlIcon("skip-forward-fill", 18, this.backTenSeconds, true, 'hover:bg-active-blue-border color-main h-full flex items-center')} - -
10s
- - {this.controlIcon("skip-forward-fill", 18, this.forthTenSeconds, false, 'hover:bg-active-blue-border color-main h-full flex items-center')} - -
- - {!live && -
- - - - - -
- } -
+ {!live && ( + )} { live && !closedLive && (
- - {'Elapsed'} - + livePlay ? null : jumpToLive()} /> +
+ + {!liveTimeTravel && ( +
+ See Past Activity +
+ )}
)}
diff --git a/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx b/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx index c72f03ce2..200c1c79f 100644 --- a/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx +++ b/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx @@ -95,4 +95,4 @@ const CustomDragLayer: FC = memo(function CustomDragLayer(props) { ); }) -export default CustomDragLayer; \ No newline at end of file +export default CustomDragLayer; diff --git a/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx b/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx index 385707879..0e1ee85a7 100644 --- a/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx +++ b/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx @@ -9,10 +9,12 @@ function getStyles( isDragging: boolean, ): CSSProperties { // const transform = `translate3d(${(left * 1161) / 100}px, -8px, 0)` + const leftPosition = left > 100 ? 100 : left + return { position: 'absolute', top: '-3px', - left: `${left}%`, + left: `${leftPosition}%`, // transform, // WebkitTransform: transform, // IE fallback: hide the real node using CSS when dragging @@ -59,9 +61,9 @@ const DraggableCircle: FC = memo(function DraggableCircle(props) { style={getStyles(left, isDragging)} role="DraggableBox" > - + 99} />
); }) -export default DraggableCircle \ No newline at end of file +export default DraggableCircle diff --git a/frontend/app/components/Session_/Player/Controls/Time.js b/frontend/app/components/Session_/Player/Controls/Time.js index b0e95c6f0..ca3c6ce4c 100644 --- a/frontend/app/components/Session_/Player/Controls/Time.js +++ b/frontend/app/components/Session_/Player/Controls/Time.js @@ -2,6 +2,7 @@ import React from 'react'; import { Duration } from 'luxon'; import { connectPlayer } from 'Player'; import styles from './time.module.css'; +import { Tooltip } from 'react-tippy'; const Time = ({ time, isCustom, format = 'm:ss', }) => (
@@ -11,13 +12,37 @@ const Time = ({ time, isCustom, format = 'm:ss', }) => ( Time.displayName = "Time"; - const ReduxTime = connectPlayer((state, { name, format }) => ({ time: state[ name ], format, }))(Time); +const AssistDurationCont = connectPlayer( + state => { + const assistStart = state.assistStart; + return { + assistStart, + } + } +)(({ assistStart }) => { + 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 = React.memo(AssistDurationCont); + ReduxTime.displayName = "ReduxTime"; -export default Time; -export { ReduxTime }; +export default React.memo(Time); +export { ReduxTime, AssistDuration }; diff --git a/frontend/app/components/Session_/Player/Controls/TimeTooltip.tsx b/frontend/app/components/Session_/Player/Controls/TimeTooltip.tsx new file mode 100644 index 000000000..fe22c4ea9 --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/TimeTooltip.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +// @ts-ignore +import { Duration } from 'luxon'; +import { connect } from 'react-redux'; +// @ts-ignore +import stl from './timeline.module.css'; + +function TimeTooltip({ time, offset, isVisible, liveTimeTravel }: { time: number; offset: number; isVisible: boolean, liveTimeTravel: boolean }) { + const duration = Duration.fromMillis(time).toFormat(`${liveTimeTravel ? '-' : ''}mm:ss`); + return ( +
+ {!time ? 'Loading' : duration} +
+ ); +} + +export default connect((state) => { + const { time = 0, offset = 0, isVisible } = state.getIn(['sessions', 'timeLineTooltip']); + return { time, offset, isVisible }; +})(TimeTooltip); diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js index 3acdb4c11..c177c14e0 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.js +++ b/frontend/app/components/Session_/Player/Controls/Timeline.js @@ -6,11 +6,12 @@ import { TimelinePointer, Icon } from 'UI'; import TimeTracker from './TimeTracker'; import stl from './timeline.module.css'; import { TYPES } from 'Types/session/event'; -import { setTimelinePointer } from 'Duck/sessions'; +import { setTimelinePointer, setTimelineHoverTime } from 'Duck/sessions'; import DraggableCircle from './DraggableCircle'; import CustomDragLayer from './CustomDragLayer'; import { debounce } from 'App/utils'; import { Tooltip } from 'react-tippy'; +import TooltipContainer from './components/TooltipContainer'; const BOUNDRY = 15 @@ -63,6 +64,7 @@ const getPointerIcon = (type) => { let deboucneJump = () => null; +let debounceTooltipChange = () => null; @connectPlayer(state => ({ playing: state.playing, time: state.time, @@ -86,16 +88,25 @@ let deboucneJump = () => null; state.getIn([ 'sessions', 'current', 'clickRageTime' ]), returningLocationTime: state.getIn([ 'sessions', 'current', 'returningLocation' ]) && state.getIn([ 'sessions', 'current', 'returningLocationTime' ]), -}), { setTimelinePointer }) + tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']) +}), { setTimelinePointer, setTimelineHoverTime }) export default class Timeline extends React.PureComponent { progressRef = React.createRef() + timelineRef = React.createRef() wasPlaying = false seekProgress = (e) => { + const time = this.getTime(e) + this.props.jump(time); + this.hideTimeTooltip() + } + + getTime = (e) => { const { endTime } = this.props; const p = e.nativeEvent.offsetX / e.target.offsetWidth; const time = Math.max(Math.round(p * endTime), 0); - this.props.jump(time); + + return time } createEventClickHandler = pointer => (e) => { @@ -109,6 +120,7 @@ export default class Timeline extends React.PureComponent { const skipToIssue = Controls.updateSkipToIssue(); const firstIssue = issues.get(0); deboucneJump = debounce(this.props.jump, 500); + debounceTooltipChange = debounce(this.props.setTimelineHoverTime, 50); if (firstIssue && skipToIssue) { this.props.jump(firstIssue.time); @@ -127,12 +139,33 @@ export default class Timeline extends React.PureComponent { const p = (offset.x - BOUNDRY) / this.progressRef.current.offsetWidth; const time = Math.max(Math.round(p * endTime), 0); deboucneJump(time); + this.hideTimeTooltip(); if (this.props.playing) { this.wasPlaying = true; this.props.pause(); } } + showTimeTooltip = (e) => { + if (e.target !== this.progressRef.current && e.target !== this.timelineRef.current) { + return this.props.tooltipVisible && this.hideTimeTooltip() + } + const time = this.getTime(e); + const { endTime, liveTimeTravel } = this.props; + + const timeLineTooltip = { + time: liveTimeTravel ? endTime - time : time, + offset: e.nativeEvent.offsetX, + isVisible: true + } + debounceTooltipChange(timeLineTooltip) + } + + hideTimeTooltip = () => { + const timeLineTooltip = { isVisible: false } + debounceTooltipChange(timeLineTooltip) + } + render() { const { events, @@ -140,14 +173,13 @@ export default class Timeline extends React.PureComponent { skipIntervals, disabled, endTime, - live, - logList, exceptionsList, resourceList, clickRageTime, stackList, fetchList, issues, + liveTimeTravel, } = this.props; const scale = 100 / endTime; @@ -155,17 +187,23 @@ export default class Timeline extends React.PureComponent { return (
+ + {/* custo color is live */} + { skip && skipIntervals.map(interval => (
)) } -
+
+ { events.map(e => (
)) } -
+
); } diff --git a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx new file mode 100644 index 000000000..be3ac24b3 --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx @@ -0,0 +1,111 @@ +import React from 'react' +import { Tooltip } from 'react-tippy'; +import { ReduxTime } from '../Time'; +import { Icon } from 'UI'; +import cn from 'classnames'; +// @ts-ignore +import styles from '../controls.module.css' + +interface Props { + live: boolean; + skip: boolean; + speed: number; + disabled: boolean; + playButton: JSX.Element; + backTenSeconds: () => void; + forthTenSeconds: () => void; + toggleSpeed: () => void; + toggleSkip: () => void; + controlIcon: (icon: string, size: number, action: () => void, isBackwards: boolean, additionalClasses: string) => JSX.Element; +} + +function PlayerControls(props: Props) { + const { + live, + skip, + speed, + disabled, + playButton, + backTenSeconds, + forthTenSeconds, + toggleSpeed, + toggleSkip, + controlIcon + } = props; + return ( +
+ {playButton} + {!live && ( +
+ {/* @ts-ignore */} + + / + {/* @ts-ignore */} + +
+ )} + +
+ {/* @ts-ignore */} + + {controlIcon( + "skip-forward-fill", + 18, + backTenSeconds, + true, + 'hover:bg-active-blue-border color-main h-full flex items-center' + )} + +
10s
+ {/* @ts-ignore */} + + {controlIcon( + "skip-forward-fill", + 18, + forthTenSeconds, + false, + 'hover:bg-active-blue-border color-main h-full flex items-center' + )} + +
+ + {!live && +
+ {/* @ts-ignore */} + + + + + +
+ } +
+ ) +} + +export default PlayerControls; diff --git a/frontend/app/components/Session_/Player/Controls/components/TooltipContainer.tsx b/frontend/app/components/Session_/Player/Controls/components/TooltipContainer.tsx new file mode 100644 index 000000000..2c90fcc1d --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/components/TooltipContainer.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import TimeTooltip from '../TimeTooltip'; +import store from 'App/store'; +import { Provider } from 'react-redux'; + +function TooltipContainer({ liveTimeTravel }: { liveTimeTravel: boolean }) { + + return ( + + + + ) +} + +export default React.memo(TooltipContainer); diff --git a/frontend/app/components/Session_/Player/Controls/controls.module.css b/frontend/app/components/Session_/Player/Controls/controls.module.css index 0b377a594..ba04b3396 100644 --- a/frontend/app/components/Session_/Player/Controls/controls.module.css +++ b/frontend/app/components/Session_/Player/Controls/controls.module.css @@ -18,9 +18,6 @@ height: 65px; padding-left: 30px; padding-right: 0; - &[data-is-live=true] { - padding: 0; - } } .buttonsLeft { diff --git a/frontend/app/components/Session_/Player/Controls/timeline.module.css b/frontend/app/components/Session_/Player/Controls/timeline.module.css index a5676d6b1..48217119d 100644 --- a/frontend/app/components/Session_/Player/Controls/timeline.module.css +++ b/frontend/app/components/Session_/Player/Controls/timeline.module.css @@ -21,14 +21,21 @@ } +.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; + } @@ -163,3 +170,28 @@ } } } + +.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/frontend/app/components/Session_/Player/Overlay.tsx b/frontend/app/components/Session_/Player/Overlay.tsx index 994608108..b067a3dd0 100644 --- a/frontend/app/components/Session_/Player/Overlay.tsx +++ b/frontend/app/components/Session_/Player/Overlay.tsx @@ -44,12 +44,6 @@ function Overlay({ togglePlay, closedLive }: Props) { - - // useEffect(() =>{ - // setTimeout(() => markTargets([{ selector: 'div', count:6}]), 5000) - // setTimeout(() => markTargets(null), 8000) - // },[]) - const showAutoplayTimer = !live && completed && autoplay && nextId const showPlayIconLayer = !live && !markedTargets && !inspectorMode && !loading && !showAutoplayTimer; const showLiveStatusText = live && liveStatusText && !loading; @@ -60,7 +54,7 @@ function Overlay({ { showLiveStatusText && } - { messagesLoading && } + { messagesLoading && } { showPlayIconLayer && } @@ -83,4 +77,4 @@ export default connectPlayer(state => ({ concetionStatus: state.peerConnectionStatus, markedTargets: state.markedTargets, activeTargetIndex: state.activeTargetIndex, -}))(Overlay); \ No newline at end of file +}))(Overlay); diff --git a/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.tsx b/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.tsx index ecf1cb7f0..a99633bb4 100644 --- a/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.tsx +++ b/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.tsx @@ -1,14 +1,19 @@ import React, { useEffect, useState } from 'react' import cn from 'classnames'; import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; import { Button, Link } from 'UI' import { session as sessionRoute, withSiteId } from 'App/routes' import stl from './AutoplayTimer.module.css'; import clsOv from './overlay.module.css'; -function AutoplayTimer({ nextId, siteId, history }) { - let timer +interface IProps extends RouteComponentProps { + nextId: number; + siteId: string; +} + +function AutoplayTimer({ nextId, siteId, history }: IProps) { + let timer: NodeJS.Timer const [cancelled, setCancelled] = useState(false); const [counter, setCounter] = useState(5); @@ -32,7 +37,7 @@ function AutoplayTimer({ nextId, siteId, history }) { } if (cancelled) - return '' + return null return (
@@ -50,7 +55,6 @@ function AutoplayTimer({ nextId, siteId, history }) { ) } - export default withRouter(connect(state => ({ siteId: state.getIn([ 'site', 'siteId' ]), nextId: parseInt(state.getIn([ 'sessions', 'nextId' ])), diff --git a/frontend/app/components/Session_/Subheader.js b/frontend/app/components/Session_/Subheader.js index 7dbde4535..c378386ee 100644 --- a/frontend/app/components/Session_/Subheader.js +++ b/frontend/app/components/Session_/Subheader.js @@ -12,7 +12,6 @@ function SubHeader(props) { const [isCopied, setCopied] = React.useState(false); const isAssist = window.location.pathname.includes('/assist/'); - if (isAssist) return null; const location = props.currentLocation && props.currentLocation.length > 60 ? `${props.currentLocation.slice(0, 60)}...` : props.currentLocation return ( @@ -39,37 +38,39 @@ function SubHeader(props) {
)} -
-
- {!isAssist && props.jiraConfig && props.jiraConfig.token && } + {!isAssist ? ( +
+
+ {props.jiraConfig && props.jiraConfig.token && } +
+
+ + + Share +
+ } + /> +
+
+ +
+
+ +
+
+
-
- - - Share -
- } - /> -
-
- -
-
- -
-
-
-
+ ) : null}
) } diff --git a/frontend/app/components/shared/LiveTag/LiveTag.module.css b/frontend/app/components/shared/LiveTag/LiveTag.module.css index cecf45bad..2914b0b76 100644 --- a/frontend/app/components/shared/LiveTag/LiveTag.module.css +++ b/frontend/app/components/shared/LiveTag/LiveTag.module.css @@ -8,26 +8,26 @@ cursor: pointer; user-select: none; height: 26px; - width: 56px; + padding: 4px 8px; border-radius: 3px; - background-color: $gray-light; + background-color: $main; display: flex; align-items: center; justify-content: center; - color: $gray-dark; + color: white; text-transform: uppercase; font-size: 10px; + font-weight: 600; letter-spacing: 1px; margin-right: 10px; & svg { - fill: $gray-dark; + fill: white; + opacity: .5; } &[data-is-live=true] { background-color: #42AE5E; - color: white; & svg { - fill: white; animation: fade 1s infinite; } } -} \ No newline at end of file +} diff --git a/frontend/app/components/shared/LiveTag/LiveTag.tsx b/frontend/app/components/shared/LiveTag/LiveTag.tsx index 36275783a..c29ae3d34 100644 --- a/frontend/app/components/shared/LiveTag/LiveTag.tsx +++ b/frontend/app/components/shared/LiveTag/LiveTag.tsx @@ -10,8 +10,8 @@ interface Props { function LiveTag({ isLive, onClick }: Props) { return ( ) } diff --git a/frontend/app/duck/sessions.js b/frontend/app/duck/sessions.js index e4a4ff7bd..d2556fce3 100644 --- a/frontend/app/duck/sessions.js +++ b/frontend/app/duck/sessions.js @@ -25,6 +25,8 @@ const SET_AUTOPLAY_VALUES = 'sessions/SET_AUTOPLAY_VALUES'; const TOGGLE_CHAT_WINDOW = 'sessions/TOGGLE_CHAT_WINDOW'; const SET_FUNNEL_PAGE_FLAG = 'sessions/SET_FUNNEL_PAGE_FLAG'; const SET_TIMELINE_POINTER = 'sessions/SET_TIMELINE_POINTER'; +const SET_TIMELINE_HOVER_POINTER = 'sessions/SET_TIMELINE_HOVER_POINTER'; + const SET_SESSION_PATH = 'sessions/SET_SESSION_PATH'; const LAST_PLAYED_SESSION_ID = `${name}/LAST_PLAYED_SESSION_ID`; const SET_ACTIVE_TAB = 'sessions/SET_ACTIVE_TAB'; @@ -61,6 +63,7 @@ const initialState = Map({ timelinePointer: null, sessionPath: {}, lastPlayedSessionId: null, + timeLineTooltip: { time: 0, offset: 0, isVisible: false } }); const reducer = (state = initialState, action = {}) => { @@ -187,6 +190,8 @@ const reducer = (state = initialState, action = {}) => { return state.set('funnelPage', action.funnelPage ? Map(action.funnelPage) : false); case SET_TIMELINE_POINTER: return state.set('timelinePointer', action.pointer); + case SET_TIMELINE_HOVER_POINTER: + return state.set('timeLineTooltip', action.timeLineTooltip); case SET_SESSION_PATH: return state.set('sessionPath', action.path); case LAST_PLAYED_SESSION_ID: @@ -350,6 +355,13 @@ export function setTimelinePointer(pointer) { }; } +export function setTimelineHoverTime(timeLineTooltip) { + return { + type: SET_TIMELINE_HOVER_POINTER, + timeLineTooltip + }; +} + export function setSessionPath(path) { return { type: SET_SESSION_PATH, diff --git a/frontend/app/player/MessageDistributor/MessageDistributor.ts b/frontend/app/player/MessageDistributor/MessageDistributor.ts index d9f2aac2b..cd48f54da 100644 --- a/frontend/app/player/MessageDistributor/MessageDistributor.ts +++ b/frontend/app/player/MessageDistributor/MessageDistributor.ts @@ -1,3 +1,4 @@ +// @ts-ignore import { Decoder } from "syncod"; import logger from 'App/logger'; @@ -5,7 +6,9 @@ import Resource, { TYPES } from 'Types/session/resource'; // MBTODO: player type import { TYPES as EVENT_TYPES } from 'Types/session/event'; import Log from 'Types/session/log'; -import { update } from '../store'; +import { update, getState } from '../store'; +import { toast } from 'react-toastify'; + import { init as initListsDepr, append as listAppend, @@ -24,7 +27,7 @@ import ActivityManager from './managers/ActivityManager'; import AssistManager from './managers/AssistManager'; import MFileReader from './messages/MFileReader'; -import loadFiles from './network/loadFiles'; +import { loadFiles, checkUnprocessedMobs } from './network/loadFiles'; import { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './StatedScreen/StatedScreen'; import { INITIAL_STATE as ASSIST_INITIAL_STATE, State as AssistState } from './managers/AssistManager'; @@ -70,29 +73,30 @@ import type { Timed } from './messages/timed'; export default class MessageDistributor extends StatedScreen { // TODO: consistent with the other data-lists - private readonly locationEventManager: ListWalker/**/ = new ListWalker(); - private readonly locationManager: ListWalker = new ListWalker(); - private readonly loadedLocationManager: ListWalker = new ListWalker(); - private readonly connectionInfoManger: ListWalker = new ListWalker(); - private readonly performanceTrackManager: PerformanceTrackManager = new PerformanceTrackManager(); - private readonly windowNodeCounter: WindowNodeCounter = new WindowNodeCounter(); - private readonly clickManager: ListWalker = new ListWalker(); + private locationEventManager: ListWalker/**/ = new ListWalker(); + private locationManager: ListWalker = new ListWalker(); + private loadedLocationManager: ListWalker = new ListWalker(); + private connectionInfoManger: ListWalker = new ListWalker(); + private performanceTrackManager: PerformanceTrackManager = new PerformanceTrackManager(); + private windowNodeCounter: WindowNodeCounter = new WindowNodeCounter(); + private clickManager: ListWalker = new ListWalker(); - private readonly resizeManager: ListWalker = new ListWalker([]); - private readonly pagesManager: PagesManager; - private readonly mouseMoveManager: MouseMoveManager; - private readonly assistManager: AssistManager; + private resizeManager: ListWalker = new ListWalker([]); + private pagesManager: PagesManager; + private mouseMoveManager: MouseMoveManager; + private assistManager: AssistManager; - private readonly scrollManager: ListWalker = new ListWalker(); + private scrollManager: ListWalker = new ListWalker(); private readonly decoder = new Decoder(); private readonly lists = initLists(); - private activirtManager: ActivityManager | null = null; + private activityManager: ActivityManager | null = null; - private readonly sessionStart: number; + private sessionStart: number; private navigationStartOffset: number = 0; private lastMessageTime: number = 0; + private lastRecordedMessageTime: number = 0; constructor(private readonly session: any /*Session*/, config: any, live: boolean) { super(); @@ -106,7 +110,7 @@ export default class MessageDistributor extends StatedScreen { initListsDepr({}) this.assistManager.connect(); } else { - this.activirtManager = new ActivityManager(this.session.duration.milliseconds); + this.activityManager = new ActivityManager(this.session.duration.milliseconds); /* == REFACTOR_ME == */ const eventList = this.session.events.toJSON(); initListsDepr({ @@ -115,12 +119,13 @@ export default class MessageDistributor extends StatedScreen { resource: this.session.resources.toJSON(), }); - eventList.forEach(e => { + // TODO: fix types for events, remove immutable js + eventList.forEach((e: Record) => { if (e.type === EVENT_TYPES.LOCATION) { //TODO type system this.locationEventManager.append(e); } }); - this.session.errors.forEach(e => { + this.session.errors.forEach((e: Record) => { this.lists.exceptions.append(e); }); /* === */ @@ -129,77 +134,160 @@ export default class MessageDistributor extends StatedScreen { } private waitingForFiles: boolean = false - private loadMessages(): void { + + private onFileSuccessRead() { + this.windowNodeCounter.reset() + + if (this.activityManager) { + this.activityManager.end() + update({ + skipIntervals: this.activityManager.list + }) + } + + this.waitingForFiles = false + this.setMessagesLoading(false) + } + + private readAndDistributeMessages(byteArray: Uint8Array, onReadCb?: (msg: Message) => void) { + const msgs: Array = [] + const reader = new MFileReader(new Uint8Array(), this.sessionStart) + + reader.append(byteArray) + let next: ReturnType + while (next = reader.next()) { + const [msg, index] = next + this.distributeMessage(msg, index) + msgs.push(msg) + onReadCb?.(msg) + } + + logger.info("Messages count: ", msgs.length, msgs) + + return msgs + } + + private processStateUpdates(msgs: Message[]) { + // @ts-ignore Hack for upet (TODO: fix ordering in one mutation in tracker(removes first)) + const headChildrenIds = msgs.filter(m => m.parentID === 1).map(m => m.id); + this.pagesManager.sortPages((m1, m2) => { + if (m1.time === m2.time) { + if (m1.tp === "remove_node" && m2.tp !== "remove_node") { + if (headChildrenIds.includes(m1.id)) { + return -1; + } + } else if (m2.tp === "remove_node" && m1.tp !== "remove_node") { + if (headChildrenIds.includes(m2.id)) { + return 1; + } + } else if (m2.tp === "remove_node" && m1.tp === "remove_node") { + const m1FromHead = headChildrenIds.includes(m1.id); + const m2FromHead = headChildrenIds.includes(m2.id); + if (m1FromHead && !m2FromHead) { + return -1; + } else if (m2FromHead && !m1FromHead) { + return 1; + } + } + } + return 0; + }) + + const stateToUpdate: {[key:string]: any} = { + performanceChartData: this.performanceTrackManager.chartData, + performanceAvaliability: this.performanceTrackManager.avaliability, + } + LIST_NAMES.forEach(key => { + stateToUpdate[ `${ key }List` ] = this.lists[ key ].list + }) + update(stateToUpdate) + this.setMessagesLoading(false) + } + + private loadMessages() { this.setMessagesLoading(true) this.waitingForFiles = true - const r = new MFileReader(new Uint8Array(), this.sessionStart) - const msgs: Array = [] + const onData = (byteArray: Uint8Array) => { + const msgs = this.readAndDistributeMessages(byteArray) + this.processStateUpdates(msgs) + } + loadFiles(this.session.mobsUrl, - b => { - r.append(b) - let next: ReturnType - while (next = r.next()) { - const [msg, index] = next - this.distributeMessage(msg, index) - msgs.push(msg) - } - - logger.info("Messages count: ", msgs.length, msgs) - - // @ts-ignore Hack for upet (TODO: fix ordering in one mutation in tracker(removes first)) - const headChildrenIds = msgs.filter(m => m.parentID === 1).map(m => m.id); - this.pagesManager.sortPages((m1, m2) => { - if (m1.time === m2.time) { - if (m1.tp === "remove_node" && m2.tp !== "remove_node") { - if (headChildrenIds.includes(m1.id)) { - return -1; - } - } else if (m2.tp === "remove_node" && m1.tp !== "remove_node") { - if (headChildrenIds.includes(m2.id)) { - return 1; - } - } else if (m2.tp === "remove_node" && m1.tp === "remove_node") { - const m1FromHead = headChildrenIds.includes(m1.id); - const m2FromHead = headChildrenIds.includes(m2.id); - if (m1FromHead && !m2FromHead) { - return -1; - } else if (m2FromHead && !m1FromHead) { - return 1; - } - } - } - return 0; - }) - - const stateToUpdate: {[key:string]: any} = { - performanceChartData: this.performanceTrackManager.chartData, - performanceAvaliability: this.performanceTrackManager.avaliability, - } - LIST_NAMES.forEach(key => { - stateToUpdate[ `${ key }List` ] = this.lists[ key ].list - }) - update(stateToUpdate) - this.setMessagesLoading(false) - } + onData ) - .then(() => { - this.windowNodeCounter.reset() - if (this.activirtManager) { - this.activirtManager.end() - update({ - skipIntervals: this.activirtManager.list + .then(() => this.onFileSuccessRead()) + .catch(async () => { + checkUnprocessedMobs(this.session.sessionId) + .then(file => file ? onData(file) : Promise.reject('No session file')) + .then(() => this.onFileSuccessRead()) + .catch((e) => { + logger.error(e) + update({ error: true }) + toast.error('Error getting a session replay file') + }) + .finally(() => { + this.waitingForFiles = false + this.setMessagesLoading(false) }) - } - this.waitingForFiles = false - this.setMessagesLoading(false) + }) - .catch(e => { - logger.error(e) - this.waitingForFiles = false - this.setMessagesLoading(false) + } + + public async reloadWithUnprocessedFile() { + // assist will pause and skip messages to prevent timestamp related errors + this.assistManager.toggleTimeTravelJump() + this.reloadMessageManagers() + + this.setMessagesLoading(true) + this.waitingForFiles = true + + const onData = (byteArray: Uint8Array) => { + const onReadCallback = () => this.setLastRecordedMessageTime(this.lastMessageTime) + const msgs = this.readAndDistributeMessages(byteArray, onReadCallback) + this.sessionStart = msgs[0].time + this.processStateUpdates(msgs) + } + + // unpausing assist + const unpauseAssist = () => { + this.assistManager.toggleTimeTravelJump() + update({ + liveTimeTravel: true, + }); + } + + try { + const unprocessedFile = await checkUnprocessedMobs(this.session.sessionId) + + Promise.resolve(onData(unprocessedFile)) + .then(() => this.onFileSuccessRead()) + .then(unpauseAssist) + } catch (unprocessedFilesError) { + logger.error(unprocessedFilesError) update({ error: true }) - }) + toast.error('Error getting a session replay file') + this.assistManager.toggleTimeTravelJump() + } finally { + this.waitingForFiles = false + this.setMessagesLoading(false) + } + } + + private reloadMessageManagers() { + this.locationEventManager = new ListWalker(); + this.locationManager = new ListWalker(); + this.loadedLocationManager = new ListWalker(); + this.connectionInfoManger = new ListWalker(); + this.clickManager = new ListWalker(); + this.scrollManager = new ListWalker(); + this.resizeManager = new ListWalker([]); + + this.performanceTrackManager = new PerformanceTrackManager() + this.windowNodeCounter = new WindowNodeCounter(); + this.pagesManager = new PagesManager(this, this.session.isMobile) + this.mouseMoveManager = new MouseMoveManager(this); + this.activityManager = new ActivityManager(this.session.duration.milliseconds); } move(t: number, index?: number): void { @@ -246,6 +334,7 @@ export default class MessageDistributor extends StatedScreen { LIST_NAMES.forEach(key => { const lastMsg = this.lists[key].moveGetLast(t, key === 'exceptions' ? undefined : index); if (lastMsg != null) { + // @ts-ignore TODO: fix types stateToUpdate[`${key}ListNow`] = this.lists[key].listNow; } }); @@ -279,10 +368,11 @@ export default class MessageDistributor extends StatedScreen { } } - private decodeMessage(msg, keys: Array) { + private decodeMessage(msg: any, keys: Array) { const decoded = {}; try { keys.forEach(key => { + // @ts-ignore TODO: types for decoder decoded[key] = this.decoder.decode(msg[key]); }); } catch (e) { @@ -294,7 +384,8 @@ export default class MessageDistributor extends StatedScreen { /* Binded */ distributeMessage(msg: Message, index: number): void { - this.lastMessageTime = Math.max(msg.time, this.lastMessageTime) + const lastMessageTime = Math.max(msg.time, this.lastMessageTime) + this.lastMessageTime = lastMessageTime if ([ "mouse_move", "mouse_click", @@ -304,7 +395,7 @@ export default class MessageDistributor extends StatedScreen { "set_viewport_size", "set_viewport_scroll", ].includes(msg.tp)) { - this.activirtManager?.updateAcctivity(msg.time); + this.activityManager?.updateAcctivity(msg.time); } //const index = i + index; //? let decoded; @@ -444,4 +535,12 @@ export default class MessageDistributor extends StatedScreen { update(INITIAL_STATE); this.assistManager.clear(); } + + public setLastRecordedMessageTime(time: number) { + this.lastRecordedMessageTime = time; + } + + public getLastRecordedMessageTime(): number { + return this.lastRecordedMessageTime; + } } diff --git a/frontend/app/player/MessageDistributor/managers/AssistManager.ts b/frontend/app/player/MessageDistributor/managers/AssistManager.ts index d56bf3a39..50a203e5f 100644 --- a/frontend/app/player/MessageDistributor/managers/AssistManager.ts +++ b/frontend/app/player/MessageDistributor/managers/AssistManager.ts @@ -56,10 +56,11 @@ export function getStatusText(status: ConnectionStatus): string { } export interface State { - calling: CallingState, - peerConnectionStatus: ConnectionStatus, - remoteControl: RemoteControlStatus, - annotating: boolean, + calling: CallingState; + peerConnectionStatus: ConnectionStatus; + remoteControl: RemoteControlStatus; + annotating: boolean; + assistStart: number; } export const INITIAL_STATE: State = { @@ -67,12 +68,16 @@ export const INITIAL_STATE: State = { peerConnectionStatus: ConnectionStatus.Connecting, remoteControl: RemoteControlStatus.Disabled, annotating: false, + assistStart: 0, } const MAX_RECONNECTION_COUNT = 4; export default class AssistManager { + private timeTravelJump = false; + private jumped = false; + constructor(private session: any, private md: MessageDistributor, private config: any) {} private setStatus(status: ConnectionStatus) { @@ -121,6 +126,10 @@ export default class AssistManager { let waitingForMessages = true let showDisconnectTimeout: ReturnType | undefined let inactiveTimeout: ReturnType | undefined + + const now = +new Date() + update({ assistStart: now }) + import('socket.io-client').then(({ default: io }) => { if (this.cleaned) { return } if (this.socket) { this.socket.close() } // TODO: single socket connection @@ -145,7 +154,7 @@ export default class AssistManager { update({ calling: CallingState.NoCall }) }) socket.on('messages', messages => { - jmr.append(messages) // as RawMessage[] + !this.timeTravelJump && jmr.append(messages) // as RawMessage[] if (waitingForMessages) { waitingForMessages = false // TODO: more explicit @@ -153,12 +162,21 @@ export default class AssistManager { // Call State if (getState().calling === CallingState.Reconnecting) { - this._call() // reconnecting call (todo improve code separation) + this._callSessionPeer() // reconnecting call (todo improve code separation) } } + if (this.timeTravelJump) { + return; + } + for (let msg = reader.readNext();msg !== null;msg = reader.readNext()) { //@ts-ignore + if (this.jumped) { + // @ts-ignore + msg.time = this.md.getLastRecordedMessageTime() + msg.time + } + // @ts-ignore TODO: fix msg types in generator this.md.distributeMessage(msg, msg._index) } }) @@ -521,6 +539,11 @@ export default class AssistManager { private annot: AnnotationCanvas | null = null + toggleTimeTravelJump() { + this.jumped = true; + this.timeTravelJump = !this.timeTravelJump; + } + /* ==== Cleaning ==== */ private cleaned: boolean = false clear() { diff --git a/frontend/app/player/MessageDistributor/managers/ListWalker.ts b/frontend/app/player/MessageDistributor/managers/ListWalker.ts index 9bae8203e..acf7b70aa 100644 --- a/frontend/app/player/MessageDistributor/managers/ListWalker.ts +++ b/frontend/app/player/MessageDistributor/managers/ListWalker.ts @@ -118,4 +118,4 @@ export default class ListWalker { } } -} \ No newline at end of file +} diff --git a/frontend/app/player/MessageDistributor/messages/MFileReader.ts b/frontend/app/player/MessageDistributor/messages/MFileReader.ts index 82d505716..9db9c2cff 100644 --- a/frontend/app/player/MessageDistributor/messages/MFileReader.ts +++ b/frontend/app/player/MessageDistributor/messages/MFileReader.ts @@ -8,9 +8,9 @@ import RawMessageReader from './RawMessageReader'; // which should be probably somehow incapsulated export default class MFileReader extends RawMessageReader { private pLastMessageID: number = 0 - private currentTime: number = 0 + private currentTime: number public error: boolean = false - constructor(data: Uint8Array, private readonly startTime: number) { + constructor(data: Uint8Array, private startTime?: number) { super(data) } @@ -60,6 +60,9 @@ export default class MFileReader extends RawMessageReader { } if (rMsg.tp === "timestamp") { + if (!this.startTime) { + this.startTime = rMsg.timestamp + } this.currentTime = rMsg.timestamp - this.startTime return this.next() } @@ -68,6 +71,7 @@ export default class MFileReader extends RawMessageReader { time: this.currentTime, _index: this.pLastMessageID, }) + return [msg, this.pLastMessageID] } } diff --git a/frontend/app/player/MessageDistributor/messages/MStreamReader.ts b/frontend/app/player/MessageDistributor/messages/MStreamReader.ts index 02e765a67..87ed89cf6 100644 --- a/frontend/app/player/MessageDistributor/messages/MStreamReader.ts +++ b/frontend/app/player/MessageDistributor/messages/MStreamReader.ts @@ -68,4 +68,4 @@ export default class MStreamReader { _index: this.idx++, }) } -} \ No newline at end of file +} diff --git a/frontend/app/player/MessageDistributor/messages/timed.ts b/frontend/app/player/MessageDistributor/messages/timed.ts index 2dd4cc707..143f6baec 100644 --- a/frontend/app/player/MessageDistributor/messages/timed.ts +++ b/frontend/app/player/MessageDistributor/messages/timed.ts @@ -1 +1 @@ -export interface Timed { readonly time: number }; +export interface Timed { time: number }; diff --git a/frontend/app/player/MessageDistributor/network/loadFiles.ts b/frontend/app/player/MessageDistributor/network/loadFiles.ts index 5bc8c580d..ff9e62de0 100644 --- a/frontend/app/player/MessageDistributor/network/loadFiles.ts +++ b/frontend/app/player/MessageDistributor/network/loadFiles.ts @@ -1,9 +1,16 @@ -const NO_NTH_FILE = "nnf" +import APIClient from 'App/api_client'; -export default function load( +const NO_NTH_FILE = "nnf" +const NO_UNPROCESSED_FILES = "nuf" + +const getUnprocessedFileLink = (sessionId: string) => '/unprocessed/' + sessionId + +type onDataCb = (data: Uint8Array) => void + +export const loadFiles = ( urls: string[], - onData: (ba: Uint8Array) => void, -): Promise { + onData: onDataCb, +): Promise => { const firstFileURL = urls[0] urls = urls.slice(1) if (!firstFileURL) { @@ -11,31 +18,16 @@ export default function load( } return window.fetch(firstFileURL) .then(r => { - if (r.status >= 400) { - throw new Error(`no start file. status code ${ r.status }`) - } - return r.arrayBuffer() + return processAPIStreamResponse(r, true) }) - .then(b => new Uint8Array(b)) .then(onData) .then(() => urls.reduce((p, url) => p.then(() => window.fetch(url) .then(r => { - return new Promise((res, rej) => { - if (r.status == 404) { - rej(NO_NTH_FILE) - return - } - if (r.status >= 400) { - rej(`Bad endfile status code ${r.status}`) - return - } - res(r.arrayBuffer()) - }) + return processAPIStreamResponse(r, false) }) - .then(b => new Uint8Array(b)) .then(onData) ), Promise.resolve(), @@ -48,3 +40,32 @@ export default function load( throw e }) } + +export const checkUnprocessedMobs = async (sessionId: string) => { + try { + const api = new APIClient() + const res = await api.fetch(getUnprocessedFileLink(sessionId)) + if (res.status >= 400) { + throw NO_UNPROCESSED_FILES + } + const byteArray = await processAPIStreamResponse(res, false) + return byteArray + } catch (e) { + throw e + } +} + +const processAPIStreamResponse = (response: Response, isFirstFile: boolean) => { + return new Promise((res, rej) => { + if (response.status === 404 && !isFirstFile) { + return rej(NO_NTH_FILE) + } + if (response.status >= 400) { + return rej( + isFirstFile ? `no start file. status code ${ response.status }` + : `Bad endfile status code ${response.status}` + ) + } + res(response.arrayBuffer()) + }).then(buffer => new Uint8Array(buffer)) +} diff --git a/frontend/app/player/Player.ts b/frontend/app/player/Player.ts index c7f37c0ca..4d4f40ed4 100644 --- a/frontend/app/player/Player.ts +++ b/frontend/app/player/Player.ts @@ -45,6 +45,7 @@ export const INITIAL_STATE = { inspectorMode: false, live: false, livePlay: false, + liveTimeTravel: false, } as const; @@ -53,7 +54,7 @@ export const INITIAL_NON_RESETABLE_STATE = { skipToIssue: initialSkipToIssue, autoplay: initialAutoplay, speed: initialSpeed, - showEvents: initialShowEvents + showEvents: initialShowEvents, } export default class Player extends MessageDistributor { @@ -118,9 +119,12 @@ export default class Player extends MessageDistributor { }); } - if (live && time > endTime) { + // throttle store updates + // TODO: make it possible to change frame rate + if (live && time - endTime > 100) { update({ endTime: time, + livePlay: endTime - time < 900 }); } this._setTime(time); @@ -153,20 +157,22 @@ export default class Player extends MessageDistributor { } jump(time = getState().time, index: number) { - const { live } = getState(); - if (live) return; + const { live, liveTimeTravel, endTime } = getState(); + if (live && !liveTimeTravel) return; if (getState().playing) { cancelAnimationFrame(this._animationFrameRequestId); // this._animationFrameRequestId = requestAnimationFrame(() => { this._setTime(time, index); this._startAnimation(); - update({ livePlay: time === getState().endTime }); + // throttilg the redux state update from each frame to nearly half a second + // which is better for performance and component rerenders + update({ livePlay: Math.abs(time - endTime) < 500 }); //}); } else { //this._animationFrameRequestId = requestAnimationFrame(() => { this._setTime(time, index); - update({ livePlay: time === getState().endTime }); + update({ livePlay: Math.abs(time - endTime) < 500 }); //}); } } @@ -246,6 +252,20 @@ export default class Player extends MessageDistributor { this._updateSpeed(Math.max(1, speed/2)); } + toggleTimetravel() { + if (!getState().liveTimeTravel) { + this.reloadWithUnprocessedFile() + this.play() + } + } + + jumpToLive() { + cancelAnimationFrame(this._animationFrameRequestId); + this._setTime(getState().endTime); + this._startAnimation(); + update({ livePlay: true }); +} + clean() { this.pause(); super.clean(); diff --git a/frontend/app/player/singletone.js b/frontend/app/player/singletone.js index 808605793..81d6a6138 100644 --- a/frontend/app/player/singletone.js +++ b/frontend/app/player/singletone.js @@ -77,6 +77,8 @@ export const requestReleaseRemoteControl = initCheck((...args) => instance.assis export const markTargets = initCheck((...args) => instance.markTargets(...args)) export const activeTarget = initCheck((...args) => instance.activeTarget(...args)) export const toggleAnnotation = initCheck((...args) => instance.assistManager.toggleAnnotation(...args)) +export const toggleTimetravel = initCheck((...args) => instance.toggleTimetravel(...args)) +export const jumpToLive = initCheck((...args) => instance.jumpToLive(...args)) export const Controls = { jump, From 28e1492eb818c088e00cb16616a57955d9d7add0 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 11 Aug 2022 12:10:07 +0200 Subject: [PATCH 070/168] change(ui) - alert no content message and other changes --- frontend/app/components/Alerts/AlertForm.js | 601 +++++++++--------- .../Alerts/AlertFormModal/AlertFormModal.tsx | 167 ++--- frontend/app/components/Alerts/Alerts.js | 173 ++--- frontend/app/components/Alerts/AlertsList.js | 99 +-- .../ui/NoContent/noContent.module.css | 4 +- frontend/app/types/alert.js | 2 +- 6 files changed, 524 insertions(+), 522 deletions(-) diff --git a/frontend/app/components/Alerts/AlertForm.js b/frontend/app/components/Alerts/AlertForm.js index 1701c8e0a..f5e4ee236 100644 --- a/frontend/app/components/Alerts/AlertForm.js +++ b/frontend/app/components/Alerts/AlertForm.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React, { useEffect } from 'react'; import { Button, Form, Input, SegmentSelection, Checkbox, Message, Link, Icon } from 'UI'; import { alertMetrics as metrics } from 'App/constants'; import { alertConditions as conditions } from 'App/constants'; @@ -9,333 +9,322 @@ import DropdownChips from './DropdownChips'; import { validateEmail } from 'App/validate'; import cn from 'classnames'; import { fetchTriggerOptions } from 'Duck/alerts'; -import Select from 'Shared/Select' +import Select from 'Shared/Select'; const thresholdOptions = [ - { label: '15 minutes', value: 15 }, - { label: '30 minutes', value: 30 }, - { label: '1 hour', value: 60 }, - { label: '2 hours', value: 120 }, - { label: '4 hours', value: 240 }, - { label: '1 day', value: 1440 }, + { label: '15 minutes', value: 15 }, + { label: '30 minutes', value: 30 }, + { label: '1 hour', value: 60 }, + { label: '2 hours', value: 120 }, + { label: '4 hours', value: 240 }, + { label: '1 day', value: 1440 }, ]; const changeOptions = [ - { label: 'change', value: 'change' }, - { label: '% change', value: 'percent' }, + { label: 'change', value: 'change' }, + { label: '% change', value: 'percent' }, ]; -const Circle = ({ text }) => ( -
{text}
-) +const Circle = ({ text }) =>
{text}
; const Section = ({ index, title, description, content }) => ( -
-
- -
- {title} - { description &&
{description}
} -
-
+
+
+ +
+ {title} + {description &&
{description}
} +
+
-
- {content} +
{content}
-
-) +); const integrationsRoute = client(CLIENT_TABS.INTEGRATIONS); -const AlertForm = props => { - const { instance, slackChannels, webhooks, loading, onDelete, deleting, triggerOptions, metricId, style={ width: '580px', height: '100vh' } } = props; - 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 onChangeOption = ({ checked, name }) => props.edit({ [ name ]: checked }) - // const onChangeCheck = (e) => { console.log(e) } +const AlertForm = (props) => { + const { + instance, + slackChannels, + webhooks, + loading, + onDelete, + deleting, + triggerOptions, + metricId, + style = { width: '580px', height: '100vh' }, + } = props; + 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 onChangeOption = ({ checked, name }) => props.edit({ [ name ]: checked }) + // const onChangeCheck = (e) => { console.log(e) } - useEffect(() => { - props.fetchTriggerOptions(); - }, []) + useEffect(() => { + props.fetchTriggerOptions(); + }, []); - const writeQueryOption = (e, { name, value }) => { - const { query } = instance; - props.edit({ query: { ...query, [name] : value } }); - } + const writeQueryOption = (e, { name, value }) => { + const { query } = instance; + props.edit({ query: { ...query, [name]: value } }); + }; - const writeQuery = ({ target: { value, name } }) => { - const { query } = instance; - props.edit({ query: { ...query, [name] : value } }); - } + const writeQuery = ({ target: { value, name } }) => { + const { query } = instance; + props.edit({ query: { ...query, [name]: value } }); + }; - const metric = (instance && instance.query.left) ? triggerOptions.find(i => i.value === instance.query.left) : null; - const unit = metric ? metric.unit : ''; - const isThreshold = instance.detectionMethod === 'threshold'; + const metric = instance && instance.query.left ? triggerOptions.find((i) => i.value === instance.query.left) : null; + const unit = metric ? metric.unit : ''; + const isThreshold = instance.detectionMethod === 'threshold'; - return ( -
props.onSubmit(instance)} id="alert-form"> -
- -
-
- props.edit({ [ name ]: value }) } - value={{ value: instance.detectionMethod }} - list={ [ - { name: 'Threshold', value: 'threshold' }, - { name: 'Change', value: 'change' }, - ]} - /> -
- {isThreshold && 'Eg. Alert me if memory.avg is greater than 500mb over the past 4 hours.'} - {!isThreshold && 'Eg. Alert me if % change of memory.avg is greater than 10% over the past 4 hours compared to the previous 4 hours.'} -
-
+ return ( + props.onSubmit(instance)} id="alert-form"> +
+ +
+
+ props.edit({ [name]: value })} + value={{ value: instance.detectionMethod }} + list={[ + { name: 'Threshold', value: 'threshold' }, + { name: 'Change', value: 'change' }, + ]} + /> +
+ {isThreshold && 'Eg. Alert me if memory.avg is greater than 500mb over the past 4 hours.'} + {!isThreshold && + 'Eg. Alert me if % change of memory.avg is greater than 10% over the past 4 hours compared to the previous 4 hours.'} +
+
+
+ } + /> + +
+ +
+ {!isThreshold && ( +
+ + i.value === instance.query.left)} + // onChange={ writeQueryOption } + onChange={({ value }) => writeQueryOption(null, { name: 'left', value: value.value })} + /> +
+ +
+ +
+ + {'test'} + + )} + {!unit && ( + + )} +
+
+ +
+ + writeOption(null, { name: 'previousPeriod', value })} + /> +
+ )} +
+ } + /> + +
+ +
+
+ + + +
+ + {instance.slack && ( +
+ +
+ props.edit({ slackInput: selected })} + /> +
+
+ )} + + {instance.email && ( +
+ +
+ props.edit({ emailInput: selected })} + /> +
+
+ )} + + {instance.webhook && ( +
+ + props.edit({ webhookInput: selected })} + /> +
+ )} +
+ } + />
- } - /> -
- -
- {!isThreshold && ( -
- - i.value === instance.query.left) } - // onChange={ writeQueryOption } - onChange={ ({ value }) => writeQueryOption(null, { name: 'left', value: value.value }) } - /> -
- -
- -
- - {'test'} - - )} - { !unit && ( - - )} +
+ {instance.exists() && ( + + )}
-
- -
- - writeOption(null, { name: 'previousPeriod', value }) } - /> -
- )}
- } - /> + + ); +}; -
- -
-
- - - -
- - { instance.slack && ( -
- -
- props.edit({ 'slackInput': selected })} - /> -
-
- )} - - {instance.email && ( -
- -
- props.edit({ 'emailInput': selected })} - /> -
-
- )} - - - {instance.webhook && ( -
- - props.edit({ 'webhookInput': selected })} - /> -
- )} -
- } - /> -
- - -
-
- -
- -
-
- {instance.exists() && ( - - )} -
-
- - ) -} - -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 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); diff --git a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx index 8869f3a02..dc4c9db15 100644 --- a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx +++ b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react'; import { SlideModal, IconButton } from 'UI'; import { init, edit, save, remove } from 'Duck/alerts'; import { fetchList as fetchWebhooks } from 'Duck/webhook'; @@ -9,93 +9,98 @@ import { EMAIL, SLACK, WEBHOOK } from 'App/constants/schedule'; import { confirm } from 'UI'; interface Props { - showModal?: boolean; - metricId?: number; - onClose?: () => void; - webhooks: any; - fetchWebhooks: Function; - save: Function; - remove: Function; - init: Function; - edit: Function; + showModal?: boolean; + metricId?: number; + onClose?: () => void; + webhooks: any; + fetchWebhooks: Function; + save: Function; + remove: Function; + init: Function; + edit: Function; } function AlertFormModal(props: Props) { - const { metricId = null, showModal = false, webhooks } = props; - const [showForm, setShowForm] = useState(false); + const { metricId = null, showModal = false, webhooks } = props; + const [showForm, setShowForm] = useState(false); - useEffect(() => { - props.fetchWebhooks(); - }, []) + useEffect(() => { + 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 = 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 saveAlert = instance => { - const wasUpdating = instance.exists(); - props.save(instance).then(() => { - if (!wasUpdating) { - toggleForm(null, false); - } - if (props.onClose) { - props.onClose(); - } - }) - } + const saveAlert = (instance) => { + const wasUpdating = instance.exists(); + props.save(instance).then(() => { + if (!wasUpdating) { + toggleForm(null, false); + } + if (props.onClose) { + props.onClose(); + } + }); + }; - 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 ( - - { 'Create Alert' } - {/* toggleForm({}, true) } - /> */} -
+ 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); + }); } - isDisplayed={ showModal } - onClose={props.onClose} - size="medium" - content={ showModal && - { + if (instance) { + props.init(instance); + } + return setShowForm(state ? state : !showForm); + }; + + return ( + + {'Create Alert'} +
+ } + isDisplayed={showModal} onClose={props.onClose} - onDelete={onDelete} - style={{ width: '580px', height: '100vh - 200px' }} - /> - } - /> - ); + size="medium" + content={ + showModal && ( + + ) + } + /> + ); } -export default connect(state => ({ - webhooks: state.getIn(['webhooks', 'list']), - instance: state.getIn(['alerts', 'instance']), -}), { init, edit, save, remove, fetchWebhooks, setShowAlerts })(AlertFormModal) \ No newline at end of file +export default connect( + (state) => ({ + webhooks: state.getIn(['webhooks', 'list']), + instance: state.getIn(['alerts', 'instance']), + }), + { init, edit, save, remove, fetchWebhooks, setShowAlerts } +)(AlertFormModal); diff --git a/frontend/app/components/Alerts/Alerts.js b/frontend/app/components/Alerts/Alerts.js index b24665a68..ed825abaf 100644 --- a/frontend/app/components/Alerts/Alerts.js +++ b/frontend/app/components/Alerts/Alerts.js @@ -10,95 +10,100 @@ 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); +const Alerts = (props) => { + const { webhooks, setShowAlerts } = props; + const [showForm, setShowForm] = useState(false); - useEffect(() => { - props.fetchWebhooks(); - }, []) + 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 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 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 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); - } + const toggleForm = (instance, state) => { + if (instance) { + props.init(instance); + } + return setShowForm(state ? state : !showForm); + }; - return ( -
- - { 'Alerts' } - toggleForm({}, true) } + return ( +
+ + {'Alerts'} + toggleForm({}, true)} /> +
+ } + isDisplayed={true} + onClose={() => { + toggleForm({}, false); + setShowAlerts(false); + }} + size="small" + content={ + { + toggleForm(alert, true); + }} + onClickCreate={() => toggleForm({}, true)} + /> + } + detailContent={ + showForm && ( + toggleForm({}, false)} + onDelete={onDelete} + /> + ) + } /> -
- } - isDisplayed={ true } - onClose={ () => { - toggleForm({}, false); - setShowAlerts(false); - } } - size="small" - content={ - { - toggleForm(alert, true) - }} - /> - } - detailContent={ - showForm && ( - toggleForm({}, false) } - onDelete={onDelete} - /> - ) - } - /> -
- ) -} +
+ ); +}; -export default connect(state => ({ - webhooks: state.getIn(['webhooks', 'list']), - instance: state.getIn(['alerts', 'instance']), -}), { init, edit, save, remove, fetchWebhooks, setShowAlerts })(Alerts) +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 index 21ea6448d..5a874e0fa 100644 --- a/frontend/app/components/Alerts/AlertsList.js +++ b/frontend/app/components/Alerts/AlertsList.js @@ -1,55 +1,58 @@ -import React, { useEffect, useState } from 'react' -import { Loader, NoContent, Input } from 'UI'; -import AlertItem from './AlertItem' +import React, { useEffect, useState } from 'react'; +import { Loader, NoContent, Input, Button } from 'UI'; +import AlertItem from './AlertItem'; import { fetchList, init } 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 AlertsList = (props) => { + const { loading, list, instance, onEdit } = props; + const [query, setQuery] = useState(''); - const filterRE = getRE(query, 'i'); - const _filteredList = list.filter(({ name, query: { left } }) => filterRE.test(name) || filterRE.test(left)); + useEffect(() => { + props.fetchList(); + }, []); - return ( -
-
- setQuery(value)} - /> -
- - -
- {_filteredList.map(a => ( -
- onEdit(a.toData())} - /> -
- ))} -
-
-
-
- ) -} + const filterRE = getRE(query, 'i'); + const _filteredList = list.filter(({ name, query: { left } }) => filterRE.test(name) || filterRE.test(left)); -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, init })(AlertsList) + return ( +
+
+ setQuery(value)} /> +
+ + +
Alerts helps your team stay up to date with the activity on your app.
+ +
+ } + size="small" + show={list.size === 0} + > +
+ {_filteredList.map((a) => ( +
+ onEdit(a.toData())} /> +
+ ))} +
+ + +
+ ); +}; + +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, init } +)(AlertsList); diff --git a/frontend/app/components/ui/NoContent/noContent.module.css b/frontend/app/components/ui/NoContent/noContent.module.css index 9b525dc26..dd78c8bbc 100644 --- a/frontend/app/components/ui/NoContent/noContent.module.css +++ b/frontend/app/components/ui/NoContent/noContent.module.css @@ -12,7 +12,7 @@ transition: all 0.2s; padding: 40px; - &.small { + /* &.small { & .title { font-size: 20px !important; } @@ -20,7 +20,7 @@ & .subtext { font-size: 16px; } - } + } */ } .title { diff --git a/frontend/app/types/alert.js b/frontend/app/types/alert.js index c16f6a87e..244047a45 100644 --- a/frontend/app/types/alert.js +++ b/frontend/app/types/alert.js @@ -12,7 +12,7 @@ conditions.forEach(c => { conditionsMap[c.value] = c }); export default Record({ alertId: '', projectId: undefined, - name: 'New Alert', + name: 'Untitled Alert', description: '', active: true, currentPeriod: 15, From a66a1341c4f02abcf570fa357cb6cc54df11c549 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 11 Aug 2022 13:46:24 +0200 Subject: [PATCH 071/168] change(ui) - no data message for network tab --- .../Session_/Network/NetworkContent.js | 473 +++++++++--------- 1 file changed, 232 insertions(+), 241 deletions(-) diff --git a/frontend/app/components/Session_/Network/NetworkContent.js b/frontend/app/components/Session_/Network/NetworkContent.js index 8e0183324..b6c54b5b4 100644 --- a/frontend/app/components/Session_/Network/NetworkContent.js +++ b/frontend/app/components/Session_/Network/NetworkContent.js @@ -1,7 +1,7 @@ import React from 'react'; import cn from 'classnames'; // import { connectPlayer } from 'Player'; -import { QuestionMarkHint, Popup, Tabs, Input } from 'UI'; +import { QuestionMarkHint, Popup, Tabs, Input, NoContent, Icon } from 'UI'; import { getRE } from 'App/utils'; import { TYPES } from 'Types/session/resource'; import { formatBytes } from 'App/utils'; @@ -21,270 +21,261 @@ const MEDIA = 'media'; const OTHER = 'other'; const TAB_TO_TYPE_MAP = { - [ XHR ]: TYPES.XHR, - [ JS ]: TYPES.JS, - [ CSS ]: TYPES.CSS, - [ IMG ]: TYPES.IMG, - [ MEDIA ]: TYPES.MEDIA, - [ OTHER ]: TYPES.OTHER -} -const TABS = [ ALL, XHR, JS, CSS, IMG, MEDIA, OTHER ].map(tab => ({ - text: tab, - key: tab, + [XHR]: TYPES.XHR, + [JS]: TYPES.JS, + [CSS]: TYPES.CSS, + [IMG]: TYPES.IMG, + [MEDIA]: TYPES.MEDIA, + [OTHER]: TYPES.OTHER, +}; +const TABS = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER].map((tab) => ({ + text: tab, + key: tab, })); -const DOM_LOADED_TIME_COLOR = "teal"; -const LOAD_TIME_COLOR = "red"; +const DOM_LOADED_TIME_COLOR = 'teal'; +const LOAD_TIME_COLOR = 'red'; -export function renderType(r) { - return ( - { r.type }
} > -
{ r.type }
- - ); +export function renderType(r) { + return ( + {r.type}
}> +
{r.type}
+ + ); } -export function renderName(r) { - return ( - { r.url }
} > -
{ r.name }
- - ); +export function renderName(r) { + return ( + {r.url}
}> +
{r.name}
+ + ); } const renderXHRText = () => ( - - {XHR} - - Use our Fetch plugin - {' to capture HTTP requests and responses, including status codes and bodies.'}
- We also provide support for GraphQL - {' for easy debugging of your queries.'} - - } - className="ml-1" - /> -
+ + {XHR} + + Use our{' '} + + Fetch plugin + + {' to capture HTTP requests and responses, including status codes and bodies.'}
+ We also provide{' '} + + support for GraphQL + + {' for easy debugging of your queries.'} + + } + className="ml-1" + /> +
); function renderSize(r) { - if (r.responseBodySize) return formatBytes(r.responseBodySize); - let triggerText; - let content; - if (r.decodedBodySize == null) { - triggerText = "x"; - content = "Not captured"; - } else { - const headerSize = r.headerSize || 0; - const encodedSize = r.encodedBodySize || 0; - const transferred = headerSize + encodedSize; - const showTransferred = r.headerSize != null; + if (r.responseBodySize) return formatBytes(r.responseBodySize); + let triggerText; + let content; + if (r.decodedBodySize == null) { + triggerText = 'x'; + content = 'Not captured'; + } else { + const headerSize = r.headerSize || 0; + const encodedSize = r.encodedBodySize || 0; + const transferred = headerSize + encodedSize; + const showTransferred = r.headerSize != null; - triggerText = formatBytes(r.decodedBodySize); - content = ( -
    - { showTransferred && -
  • {`${formatBytes( r.encodedBodySize + headerSize )} transfered over network`}
  • - } -
  • {`Resource size: ${formatBytes(r.decodedBodySize)} `}
  • -
+ triggerText = formatBytes(r.decodedBodySize); + content = ( +
    + {showTransferred &&
  • {`${formatBytes(r.encodedBodySize + headerSize)} transfered over network`}
  • } +
  • {`Resource size: ${formatBytes(r.decodedBodySize)} `}
  • +
+ ); + } + + return ( + +
{triggerText}
+
); - } - - return ( - -
{ triggerText }
-
- ); } export function renderDuration(r) { - if (!r.success) return 'x'; + if (!r.success) return 'x'; - const text = `${ Math.floor(r.duration) }ms`; - if (!r.isRed() && !r.isYellow()) return text; + const text = `${Math.floor(r.duration)}ms`; + if (!r.isRed() && !r.isYellow()) return text; - let tooltipText; - let className = "w-full h-full flex items-center "; - if (r.isYellow()) { - tooltipText = "Slower than average"; - className += "warn color-orange"; - } else { - tooltipText = "Much slower than average"; - className += "error color-red"; - } + let tooltipText; + let className = 'w-full h-full flex items-center '; + if (r.isYellow()) { + tooltipText = 'Slower than average'; + className += 'warn color-orange'; + } else { + tooltipText = 'Much slower than average'; + className += 'error color-red'; + } - return ( - -
{ text }
-
- ); + return ( + +
{text}
+
+ ); } export default class NetworkContent extends React.PureComponent { - state = { - filter: '', - activeTab: ALL, - } + state = { + filter: '', + activeTab: ALL, + }; - onTabClick = activeTab => this.setState({ activeTab }) - onFilterChange = ({ target: { value } }) => this.setState({ filter: value }) + onTabClick = (activeTab) => this.setState({ activeTab }); + onFilterChange = ({ target: { value } }) => this.setState({ filter: value }); - render() { - const { - location, - resources, - domContentLoadedTime, - loadTime, - domBuildingTime, - fetchPresented, - onRowClick, - isResult = false, - additionalHeight = 0, - resourcesSize, - transferredSize, - time, - currentIndex - } = this.props; - const { filter, activeTab } = this.state; - const filterRE = getRE(filter, 'i'); - let filtered = resources.filter(({ type, name }) => - filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ])); - const lastIndex = currentIndex || filtered.filter(item => item.time <= time).length - 1; + render() { + const { + location, + resources, + domContentLoadedTime, + loadTime, + domBuildingTime, + fetchPresented, + onRowClick, + isResult = false, + additionalHeight = 0, + resourcesSize, + transferredSize, + time, + currentIndex, + } = this.props; + const { filter, activeTab } = this.state; + const filterRE = getRE(filter, 'i'); + let filtered = resources.filter(({ type, name }) => filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[activeTab])); + const lastIndex = currentIndex || filtered.filter((item) => item.time <= time).length - 1; - const referenceLines = []; - if (domContentLoadedTime != null) { - referenceLines.push({ - time: domContentLoadedTime.time, - color: DOM_LOADED_TIME_COLOR, - }) + const referenceLines = []; + if (domContentLoadedTime != null) { + referenceLines.push({ + time: domContentLoadedTime.time, + color: DOM_LOADED_TIME_COLOR, + }); + } + if (loadTime != null) { + referenceLines.push({ + time: loadTime.time, + color: LOAD_TIME_COLOR, + }); + } + + let tabs = TABS; + if (!fetchPresented) { + tabs = TABS.map((tab) => + !isResult && tab.key === XHR + ? { + text: renderXHRText(), + key: XHR, + } + : tab + ); + } + + // const resourcesSize = filtered.reduce((sum, { decodedBodySize }) => sum + (decodedBodySize || 0), 0); + // const transferredSize = filtered + // .reduce((sum, { headerSize, encodedBodySize }) => sum + (headerSize || 0) + (encodedBodySize || 0), 0); + + return ( + + + +
+ Network + +
+ +
+ + + + 0} /> + 0} /> + + + + + + + No Data +
+ } + size="small" + show={filtered.length === 0} + > + + {[ + { + label: 'Status', + dataKey: 'status', + width: 70, + }, + { + label: 'Type', + dataKey: 'type', + width: 90, + render: renderType, + }, + { + label: 'Name', + width: 200, + render: renderName, + }, + { + label: 'Size', + width: 60, + render: renderSize, + }, + { + label: 'Time', + width: 80, + render: renderDuration, + }, + ]} + + + + + + ); } - if (loadTime != null) { - referenceLines.push({ - time: loadTime.time, - color: LOAD_TIME_COLOR, - }) - } - - let tabs = TABS; - if (!fetchPresented) { - tabs = TABS.map(tab => !isResult && tab.key === XHR - ? { - text: renderXHRText(), - key: XHR, - } - : tab - ); - } - - // const resourcesSize = filtered.reduce((sum, { decodedBodySize }) => sum + (decodedBodySize || 0), 0); - // const transferredSize = filtered - // .reduce((sum, { headerSize, encodedBodySize }) => sum + (headerSize || 0) + (encodedBodySize || 0), 0); - - return ( - - - -
- Network - -
- -
- - {/*
*/} - {/* */} - {/*
{ location }
*/} - {/*
*/} - {/*
*/} - - - 0 } - /> - 0 } - /> - - - - - - {[ - { - label: "Status", - dataKey: 'status', - width: 70, - }, { - label: "Type", - dataKey: 'type', - width: 90, - render: renderType, - }, { - label: "Name", - width: 200, - render: renderName, - }, - { - label: "Size", - width: 60, - render: renderSize, - }, - { - label: "Time", - width: 80, - render: renderDuration, - } - ]} - -
-
-
- ); - } } From ff8ef02c4c3c4a0831cf0010d5e73de6516fec6f Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 11 Aug 2022 13:46:38 +0200 Subject: [PATCH 072/168] change(ui) - no data message for console tab --- .../app/components/Session_/Console/ConsoleContent.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/app/components/Session_/Console/ConsoleContent.js b/frontend/app/components/Session_/Console/ConsoleContent.js index a2c084abd..29820de2e 100644 --- a/frontend/app/components/Session_/Console/ConsoleContent.js +++ b/frontend/app/components/Session_/Console/ConsoleContent.js @@ -86,7 +86,13 @@ export default class ConsoleContent extends React.PureComponent { /> - + + + No {activeTab === ALL ? 'Data' : activeTab.toLowerCase()}
} + size="small" + show={filtered.length === 0} + > {filtered.map((l, index) => (
Date: Thu, 11 Aug 2022 13:46:47 +0200 Subject: [PATCH 073/168] change(ui) - no data message for fetch tab --- frontend/app/components/Session_/Fetch/Fetch.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/app/components/Session_/Fetch/Fetch.js b/frontend/app/components/Session_/Fetch/Fetch.js index 2dff482c9..cdfbcc900 100644 --- a/frontend/app/components/Session_/Fetch/Fetch.js +++ b/frontend/app/components/Session_/Fetch/Fetch.js @@ -1,6 +1,6 @@ import React from 'react'; import { getRE } from 'App/utils'; -import { Label, NoContent, Input, SlideModal, CloseButton } from 'UI'; +import { Label, NoContent, Input, SlideModal, CloseButton, Icon } from 'UI'; import { connectPlayer, pause, jump } from 'Player'; // import Autoscroll from '../Autoscroll'; import BottomBlock from '../BottomBlock'; @@ -129,7 +129,17 @@ export default class Fetch extends React.PureComponent {
- + + + No Data +
+ } + // size="small" + show={filteredList.length === 0} + > + {/* */} {[ { From 04fd2ab0283f32d8e725df60aa3d650d95f0bfb0 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 11 Aug 2022 13:47:23 +0200 Subject: [PATCH 074/168] change(ui) - info circle change --- frontend/app/components/ui/SVG.tsx | 4 +--- frontend/app/svg/icons/info-circle.svg | 13 ++++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index a6df932b4..d5cd23f17 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -230,12 +230,11 @@ const SVG = (props: Props) => { case 'hourglass-start': return ; case 'id-card': return ; case 'image': return ; - case 'info-circle': return ; + case 'info-circle': return ; case 'info-square': return ; case 'info': return ; case 'inspect': return ; case 'integrations/assist': return ; - case 'integrations/aws': return ; case 'integrations/bugsnag-text': return ; case 'integrations/bugsnag': return ; case 'integrations/cloudwatch-text': return ; @@ -244,7 +243,6 @@ const SVG = (props: Props) => { case 'integrations/elasticsearch-text': return ; case 'integrations/elasticsearch': return ; case 'integrations/github': return ; - case 'integrations/google-cloud': return ; case 'integrations/graphql': return ; case 'integrations/jira-text': return ; case 'integrations/jira': return ; diff --git a/frontend/app/svg/icons/info-circle.svg b/frontend/app/svg/icons/info-circle.svg index dfb82474d..1dccdda5d 100644 --- a/frontend/app/svg/icons/info-circle.svg +++ b/frontend/app/svg/icons/info-circle.svg @@ -1,4 +1,11 @@ - - - + + + + + + + + + + \ No newline at end of file From 5b92e0bee545c3fcbae7d7cfdbf04cb3635153e0 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 11 Aug 2022 14:04:55 +0200 Subject: [PATCH 075/168] change(ui) - session list tags bg and other changes, button hover bg --- .../components/SessionHeader/SessionHeader.tsx | 2 +- .../components/SessionTags/SessionTags.tsx | 9 +++++---- frontend/app/components/ui/Button/Button.tsx | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx index b2bbc1e65..3777763ca 100644 --- a/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx @@ -33,7 +33,7 @@ function SessionHeader(props: Props) { }; return ( -
+
diff --git a/frontend/app/components/ui/Button/Button.tsx b/frontend/app/components/ui/Button/Button.tsx index bc71be3a0..d3e3acd15 100644 --- a/frontend/app/components/ui/Button/Button.tsx +++ b/frontend/app/components/ui/Button/Button.tsx @@ -30,7 +30,7 @@ export default (props: Props) => { let classes = ['relative flex items-center h-10 px-3 rounded tracking-wide whitespace-nowrap']; if (variant === 'default') { - classes.push('bg-white hover:bg-gray-lightest border border-gray-light'); + classes.push('bg-white hover:bg-gray-light border border-gray-light'); } if (variant === 'primary') { @@ -38,7 +38,7 @@ export default (props: Props) => { } if (variant === 'text') { - classes.push('bg-transparent color-gray-dark hover:bg-gray-lightest hover:color-gray-dark'); + classes.push('bg-transparent color-gray-dark hover:bg-gray-light hover:color-gray-dark'); } if (variant === 'text-primary') { From 8805c84735969c97df7f2a629566c0968304035f Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 11 Aug 2022 15:58:33 +0200 Subject: [PATCH 076/168] feat(ui) - overview - error details modal --- .../app/components/Errors/Error/ErrorInfo.js | 149 ++++++------- .../components/Errors/Error/MainSection.js | 206 ++++++++---------- .../Session_/Exceptions/Exceptions.js | 2 +- .../Session_/OverviewPanel/OverviewPanel.tsx | 4 +- .../components/EventRow/EventRow.tsx | 8 +- .../TimelinePointer/TimelinePointer.tsx | 44 ++-- .../Session_/Player/Controls/Timeline.js | 2 +- .../ui/ErrorDetails/ErrorDetails.tsx | 11 +- 8 files changed, 201 insertions(+), 225 deletions(-) diff --git a/frontend/app/components/Errors/Error/ErrorInfo.js b/frontend/app/components/Errors/Error/ErrorInfo.js index b1d18ab45..8407826de 100644 --- a/frontend/app/components/Errors/Error/ErrorInfo.js +++ b/frontend/app/components/Errors/Error/ErrorInfo.js @@ -2,82 +2,77 @@ import React from 'react'; import { connect } from 'react-redux'; import withSiteIdRouter from 'HOCs/withSiteIdRouter'; import { errors as errorsRoute, error as errorRoute } from 'App/routes'; -import { NoContent , Loader, IconButton, Icon, Popup, BackLink, } from 'UI'; +import { NoContent, Loader, IconButton, Icon, Popup, BackLink } from 'UI'; import { fetch, fetchTrace } from 'Duck/errors'; import MainSection from './MainSection'; import SideSection from './SideSection'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; -@connect(state =>({ - errorIdInStore: state.getIn(["errors", "instance"]).errorId, - loading: state.getIn([ "errors", "fetch", "loading" ]) || state.getIn([ "errors", "fetchTrace", "loading" ]), - errorOnFetch: state.getIn(["errors", "fetch", "errors"]) || state.getIn([ "errors", "fetchTrace", "errors" ]), -}), { - fetch, - fetchTrace, -}) +@connect( + (state) => ({ + errorIdInStore: state.getIn(['errors', 'instance']).errorId, + loading: state.getIn(['errors', 'fetch', 'loading']) || state.getIn(['errors', 'fetchTrace', 'loading']), + errorOnFetch: state.getIn(['errors', 'fetch', 'errors']) || state.getIn(['errors', 'fetchTrace', 'errors']), + }), + { + fetch, + fetchTrace, + } +) @withSiteIdRouter export default class ErrorInfo extends React.PureComponent { - ensureInstance() { - const { errorId, loading, errorOnFetch } = this.props; - if (!loading && - this.props.errorIdInStore !== errorId && - errorId != null) { - this.props.fetch(errorId); - this.props.fetchTrace(errorId) - } - } - componentDidMount() { - this.ensureInstance(); - } - componentDidUpdate() { - this.ensureInstance(); - } - next = () => { - const { list, errorId } = this.props; - const curIndex = list.findIndex(e => e.errorId === errorId); - const next = list.get(curIndex + 1); - if (next != null) { - this.props.history.push(errorRoute(next.errorId)) - } - } - prev = () => { - const { list, errorId } = this.props; - const curIndex = list.findIndex(e => e.errorId === errorId); - const prev = list.get(curIndex - 1); - if (prev != null) { - this.props.history.push(errorRoute(prev.errorId)) - } - - } - render() { - const { - loading, - errorIdInStore, - list, - errorId, - } = this.props; + ensureInstance() { + const { errorId, loading, errorOnFetch } = this.props; + if (!loading && this.props.errorIdInStore !== errorId && errorId != null) { + this.props.fetch(errorId); + this.props.fetchTrace(errorId); + } + } + componentDidMount() { + this.ensureInstance(); + } + componentDidUpdate() { + this.ensureInstance(); + } + next = () => { + const { list, errorId } = this.props; + const curIndex = list.findIndex((e) => e.errorId === errorId); + const next = list.get(curIndex + 1); + if (next != null) { + this.props.history.push(errorRoute(next.errorId)); + } + }; + prev = () => { + const { list, errorId } = this.props; + const curIndex = list.findIndex((e) => e.errorId === errorId); + const prev = list.get(curIndex - 1); + if (prev != null) { + this.props.history.push(errorRoute(prev.errorId)); + } + }; + render() { + const { loading, errorIdInStore, list, errorId } = this.props; - let nextDisabled = true, - prevDisabled = true; - if (list.size > 0) { - nextDisabled = loading || list.last().errorId === errorId; - prevDisabled = loading || list.first().errorId === errorId; - } + let nextDisabled = true, + prevDisabled = true; + if (list.size > 0) { + nextDisabled = loading || list.last().errorId === errorId; + prevDisabled = loading || list.first().errorId === errorId; + } - return ( - - -
No Error Found!
-
- } - subtext="Please try to find existing one." - // animatedIcon="no-results" - show={ !loading && errorIdInStore == null } - > - {/*
+ return ( + + +
No Error Found!
+
+ } + subtext="Please try to find existing one." + // animatedIcon="no-results" + show={!loading && errorIdInStore == null} + > + {/*
@@ -111,13 +106,13 @@ export default class ErrorInfo extends React.PureComponent {
*/} -
- - - - -
- - ); - } -} \ No newline at end of file +
+ + + + +
+ + ); + } +} diff --git a/frontend/app/components/Errors/Error/MainSection.js b/frontend/app/components/Errors/Error/MainSection.js index 4a81ca062..534f417f8 100644 --- a/frontend/app/components/Errors/Error/MainSection.js +++ b/frontend/app/components/Errors/Error/MainSection.js @@ -5,105 +5,89 @@ import withSiteIdRouter from 'HOCs/withSiteIdRouter'; import { ErrorDetails, IconButton, 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 { UNRESOLVED, RESOLVED, IGNORED } from 'Types/errorInfo'; import { addFilterByKeyAndValue } from 'Duck/search'; -import { resolve,unresolve,ignore, toggleFavorite } from "Duck/errors"; +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 SharePopup from 'Shared/SharePopup'; import { FilterKey } from 'Types/filter/filterType'; import SessionBar from './SessionBar'; @withSiteIdRouter -@connect(state => ({ - error: state.getIn([ "errors", "instance" ]), - trace: state.getIn([ "errors", "instanceTrace" ]), - sourcemapUploaded: state.getIn([ "errors", "sourcemapUploaded" ]), - resolveToggleLoading: state.getIn(["errors", "resolve", "loading"]) || - state.getIn(["errors", "unresolve", "loading"]), - ignoreLoading: state.getIn([ "errors", "ignore", "loading" ]), - toggleFavoriteLoading: state.getIn([ "errors", "toggleFavorite", "loading" ]), - traceLoading: state.getIn([ "errors", "fetchTrace", "loading"]), -}),{ - resolve, - unresolve, - ignore, - toggleFavorite, - addFilterByKeyAndValue, -}) +@connect( + (state) => ({ + error: state.getIn(['errors', 'instance']), + trace: state.getIn(['errors', 'instanceTrace']), + sourcemapUploaded: state.getIn(['errors', 'sourcemapUploaded']), + resolveToggleLoading: state.getIn(['errors', 'resolve', 'loading']) || state.getIn(['errors', 'unresolve', 'loading']), + ignoreLoading: state.getIn(['errors', 'ignore', 'loading']), + toggleFavoriteLoading: state.getIn(['errors', 'toggleFavorite', 'loading']), + traceLoading: state.getIn(['errors', 'fetchTrace', 'loading']), + }), + { + resolve, + unresolve, + ignore, + toggleFavorite, + addFilterByKeyAndValue, + } +) export default class MainSection extends React.PureComponent { - resolve = () => { - const { error } = this.props; - this.props.resolve(error.errorId) - } + resolve = () => { + const { error } = this.props; + this.props.resolve(error.errorId); + }; - unresolve = () => { - const { error } = this.props; - this.props.unresolve(error.errorId) - } + unresolve = () => { + const { error } = this.props; + this.props.unresolve(error.errorId); + }; - ignore = () => { - const { error } = this.props; - this.props.ignore(error.errorId) - } - bookmark = () => { - const { error } = this.props; - this.props.toggleFavorite(error.errorId); - } + ignore = () => { + const { error } = this.props; + this.props.ignore(error.errorId); + }; + bookmark = () => { + const { error } = this.props; + this.props.toggleFavorite(error.errorId); + }; - findSessions = () => { - this.props.addFilterByKeyAndValue(FilterKey.ERROR, this.props.error.message); - this.props.history.push(sessionsRoute()); - } + findSessions = () => { + this.props.addFilterByKeyAndValue(FilterKey.ERROR, this.props.error.message); + this.props.history.push(sessionsRoute()); + }; - render() { - const { - error, - trace, - sourcemapUploaded, - ignoreLoading, - resolveToggleLoading, - toggleFavoriteLoading, - className, - traceLoading, - } = this.props; + render() { + const { error, trace, sourcemapUploaded, ignoreLoading, resolveToggleLoading, toggleFavoriteLoading, className, traceLoading } = this.props; - return ( -
-
- -
-
- { error.message } -
-
-
-
-
Over the past 30 days
-
-
- -
+ return ( +
+
+ +
+
+ {error.message} +
+
+
+
+
Over the past 30 days
+
+
+
- {/* + {/*
{ error.status === UNRESOLVED ?
*/} - -
-

Last session with this error

- { resentOrDate(error.lastOccurrence) } - - -
- -
- - - -
- -
- ); - } -} \ No newline at end of file + +
+

Last session with this error

+ {resentOrDate(error.lastOccurrence)} + + +
+ +
+ + + +
+
+ ); + } +} diff --git a/frontend/app/components/Session_/Exceptions/Exceptions.js b/frontend/app/components/Session_/Exceptions/Exceptions.js index 0cdd9d01e..2f3b83483 100644 --- a/frontend/app/components/Session_/Exceptions/Exceptions.js +++ b/frontend/app/components/Session_/Exceptions/Exceptions.js @@ -64,7 +64,7 @@ export default class Exceptions extends React.PureComponent { show={ !loading && errorStack.size === 0 } title="Nothing found!" > - +
diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index 53ba3ddf5..b5b9b91cd 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -36,10 +36,10 @@ function OverviewPanel(props: Props) { }; return ( - + Overview -
+
diff --git a/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx b/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx index 0447fcaa9..cff0c6182 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx @@ -10,7 +10,7 @@ interface Props { endTime?: number; renderElement?: (item: any) => React.ReactNode; } -function EventRow(props: Props) { +const EventRow = React.memo((props: Props) => { const { title, className, list = [], endTime = 0 } = props; const scale = 100 / endTime; const _list = React.useMemo(() => { @@ -22,8 +22,8 @@ function EventRow(props: Props) { }) }, [list]); return ( -
-
{title}
+
+
{title}
{_list.map((item: any, index: number) => { return ( @@ -36,7 +36,7 @@ function EventRow(props: Props) {
); -} +}); export default connectPlayer((state: any) => ({ endTime: state.endTime, diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx index a9d58f4a2..c3cf175dd 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx @@ -2,16 +2,17 @@ import React from 'react'; import { connectPlayer, Controls } from 'App/player'; import { toggleBottomBlock, NETWORK, EXCEPTIONS, PERFORMANCE } from 'Duck/components/player'; import { useModal } from 'App/components/Modal'; -import { Icon, ErrorDetails } from 'UI'; +import { Icon, ErrorDetails, Popup } from 'UI'; import { Tooltip } from 'react-tippy'; import { TYPES as EVENT_TYPES } from 'Types/session/event'; import StackEventModal from '../StackEventModal'; +import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal'; interface Props { pointer: any; type: any; } -function TimelinePointer(props: Props) { +const TimelinePointer = React.memo((props: Props) => { const { showModal, hideModal } = useModal(); const createEventClickHandler = (pointer: any, type: any) => (e: any) => { e.stopPropagation(); @@ -20,8 +21,8 @@ function TimelinePointer(props: Props) { return; } - if (type === 'EXCEPTIONS') { - showModal(, { right: true }); + if (type === 'ERRORS') { + showModal(, { right: true }); } if (type === 'EVENT') { @@ -32,8 +33,8 @@ function TimelinePointer(props: Props) { const renderNetworkElement = (item: any) => { return ( - {item.success ? 'Slow resource: ' : 'Missing resource:'}
@@ -46,14 +47,14 @@ function TimelinePointer(props: Props) {
- + ); }; const renderClickRageElement = (item: any) => { return ( - {'Click Rage'}
@@ -64,14 +65,14 @@ function TimelinePointer(props: Props) {
-
+ ); }; const renderStackEventElement = (item: any) => { return ( - {'Stack Event'}
@@ -82,15 +83,14 @@ function TimelinePointer(props: Props) {
{/* */}
- + ); }; const renderPerformanceElement = (item: any) => { - console.log('item', item) return ( - {item.name}
@@ -101,14 +101,14 @@ function TimelinePointer(props: Props) {
{/* */}
- + ); }; const renderExceptionElement = (item: any) => { return ( - {'Exception'}
@@ -118,10 +118,10 @@ function TimelinePointer(props: Props) { delay={0} position="top" > -
+
- + ); }; @@ -145,6 +145,6 @@ function TimelinePointer(props: Props) { } }; return
{render()}
; -} +}); export default TimelinePointer; diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js index 91603fbf5..0899f1d2a 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.js +++ b/frontend/app/components/Session_/Player/Controls/Timeline.js @@ -12,7 +12,7 @@ import CustomDragLayer from './CustomDragLayer'; import { debounce } from 'App/utils'; import { Tooltip } from 'react-tippy'; -const BOUNDRY = 15; +const BOUNDRY = 0; function getTimelinePosition(value, scale) { const pos = value * scale; diff --git a/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx b/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx index 331535461..047c29f0a 100644 --- a/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx +++ b/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import ErrorFrame from '../ErrorFrame/ErrorFrame'; import { fetchErrorStackList } from 'Duck/sessions'; -import { IconButton, Icon } from 'UI'; +import { Button, Icon } from 'UI'; import { connect } from 'react-redux'; const docLink = 'https://docs.openreplay.com/installation/upload-sourcemaps'; @@ -46,8 +46,12 @@ function ErrorDetails(props: Props) {

Stacktrace

- setShowRaw(false)} label="FULL" plain={!showRaw} primaryText={!showRaw} /> - setShowRaw(true)} plain={showRaw} label="RAW" /> + +
@@ -59,7 +63,6 @@ function ErrorDetails(props: Props) { {error.name} : {firstFunc ? firstFunc : '?'}
)} - ; {errorStack.map((frame: any, i: any) => (
From 843c41971d0fab15398a3d8347edefd36b83a53f Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Thu, 11 Aug 2022 15:19:59 +0100 Subject: [PATCH 077/168] feat(chalice): SMTP change --- api/chalicelib/utils/smtp.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/api/chalicelib/utils/smtp.py b/api/chalicelib/utils/smtp.py index 403f86e0f..ce0b20655 100644 --- a/api/chalicelib/utils/smtp.py +++ b/api/chalicelib/utils/smtp.py @@ -1,5 +1,8 @@ import smtplib +from smtplib import SMTPAuthenticationError + from decouple import config +from starlette.exceptions import HTTPException class EmptySMTP: @@ -31,7 +34,10 @@ class SMTPClient: # stmplib docs recommend calling ehlo() before & after starttls() self.server.ehlo() if len(config("EMAIL_USER", default="")) > 0 and len(config("EMAIL_PASSWORD", default="")) > 0: - self.server.login(user=config("EMAIL_USER"), password=config("EMAIL_PASSWORD")) + try: + self.server.login(user=config("EMAIL_USER"), password=config("EMAIL_PASSWORD")) + except SMTPAuthenticationError: + raise HTTPException(401, "SMTP Authentication Error") return self.server def __exit__(self, *args): From 930e502f2521fee9616d0dc14ebc956df9fadf77 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 11 Aug 2022 17:09:28 +0200 Subject: [PATCH 078/168] feat(ui) - overview - vertical line follow and jump --- .../Session_/OverviewPanel/OverviewPanel.tsx | 41 ++++--- .../OverviewPanelContainer.tsx | 44 +++++++ .../OverviewPanelContainer/index.ts | 1 + .../components/VerticalLine/VerticalLine.tsx | 15 +++ .../components/VerticalLine/index.ts | 1 + .../VerticalPointerLine.tsx | 3 +- .../Session_/Player/Controls/Timeline.js | 112 +++++++++--------- .../ui/ErrorDetails/ErrorDetails.tsx | 2 +- 8 files changed, 144 insertions(+), 75 deletions(-) create mode 100644 frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx create mode 100644 frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/index.ts create mode 100644 frontend/app/components/Session_/OverviewPanel/components/VerticalLine/VerticalLine.tsx create mode 100644 frontend/app/components/Session_/OverviewPanel/components/VerticalLine/index.ts diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index b5b9b91cd..26fda0a30 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -10,6 +10,8 @@ import FeatureSelection from './components/FeatureSelection/FeatureSelection'; import TimelinePointer from './components/TimelinePointer'; import VerticalPointerLine from './components/VerticalPointerLine'; import cn from 'classnames'; +import VerticalLine from './components/VerticalLine'; +import OverviewPanelContainer from './components/OverviewPanelContainer'; interface Props { resourceList: any[]; @@ -21,22 +23,24 @@ interface Props { } function OverviewPanel(props: Props) { const { resourceList, exceptionsList, eventsList, stackEventList, issuesList } = props; + const clickRageList = React.useMemo(() => { return eventsList.filter((item: any) => item.type === TYPES.CLICKRAGE); }, [eventsList]); - // const scale = 100 / endTime; const [selectedFeatures, setSelectedFeatures] = React.useState(['PERFORMANCE', 'ERRORS', 'EVENTS']); - const resources: any = { - NETWORK: resourceList, - ERRORS: exceptionsList, - EVENTS: stackEventList, - CLICKRAGE: clickRageList, - PERFORMANCE: issuesList, - }; + const resources: any = React.useMemo(() => { + return { + NETWORK: resourceList, + ERRORS: exceptionsList, + EVENTS: stackEventList, + CLICKRAGE: clickRageList, + PERFORMANCE: issuesList, + }; + }, [resourceList, exceptionsList, stackEventList, clickRageList, issuesList]); return ( - + Overview
@@ -44,9 +48,9 @@ function OverviewPanel(props: Props) {
-
+ -
+
{selectedFeatures.map((feature: any, index: number) => (
@@ -59,17 +63,20 @@ function OverviewPanel(props: Props) {
))}
-
+
); } -export default connect((state: any) => ({ - issuesList: state.getIn(['sessions', 'current', 'issues']), -}), { - toggleBottomBlock, -})( +export default connect( + (state: any) => ({ + issuesList: state.getIn(['sessions', 'current', 'issues']), + }), + { + toggleBottomBlock, + } +)( connectPlayer((state: any) => ({ resourceList: state.resourceList.filter((r: any) => r.isRed() || r.isYellow()), exceptionsList: state.exceptionsList, diff --git a/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx b/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx new file mode 100644 index 000000000..979e8bb53 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import VerticalLine from '../VerticalLine'; +import { connectPlayer, Controls } from 'App/player'; + +interface Props { + children: React.ReactNode; + endTime: number; +} + +function OverviewPanelContainer(props: Props) { + const { endTime } = props; + const [mouseX, setMouseX] = React.useState(0); + const [mouseIn, setMouseIn] = React.useState(false); + const onClickTrack = (e: any) => { + const p = e.nativeEvent.offsetX / e.target.offsetWidth; + const time = Math.max(Math.round(p * endTime), 0); + Controls.jump(time); + }; + + const onMouseMoveCapture = (e: any) => { + if (!mouseIn) { + return; + } + const p = e.nativeEvent.offsetX / e.target.offsetWidth; + setMouseX(p * 100); + }; + + return ( +
setMouseIn(true)} + // onMouseOut={() => setMouseIn(false)} + > + {mouseIn && } +
{props.children}
+
+ ); +} + +export default connectPlayer((state: any) => ({ + endTime: state.endTime, +}))(OverviewPanelContainer); diff --git a/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/index.ts b/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/index.ts new file mode 100644 index 000000000..788665588 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/index.ts @@ -0,0 +1 @@ +export { default } from './OverviewPanelContainer'; \ No newline at end of file diff --git a/frontend/app/components/Session_/OverviewPanel/components/VerticalLine/VerticalLine.tsx b/frontend/app/components/Session_/OverviewPanel/components/VerticalLine/VerticalLine.tsx new file mode 100644 index 000000000..43a536f13 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/VerticalLine/VerticalLine.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import cn from 'classnames'; + +interface Props { + left: number; + className?: string; + height?: string; + width?: string; +} +function VerticalLine(props: Props) { + const { left, className = 'border-gray-dark', height = '221px', width = '1px' } = props; + return
; +} + +export default VerticalLine; diff --git a/frontend/app/components/Session_/OverviewPanel/components/VerticalLine/index.ts b/frontend/app/components/Session_/OverviewPanel/components/VerticalLine/index.ts new file mode 100644 index 000000000..423077b49 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/VerticalLine/index.ts @@ -0,0 +1 @@ +export { default } from './VerticalLine' \ No newline at end of file diff --git a/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/VerticalPointerLine.tsx b/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/VerticalPointerLine.tsx index b8997e111..8db015447 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/VerticalPointerLine.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/VerticalPointerLine.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { connectPlayer } from 'App/player'; +import VerticalLine from '../VerticalLine'; interface Props { time: number; @@ -8,7 +9,7 @@ interface Props { function VerticalPointerLine(props: Props) { const { time, scale } = props; const left = time * scale; - return
; + return ; } export default connectPlayer((state: any) => ({ diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js index 0899f1d2a..e6879671d 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.js +++ b/frontend/app/components/Session_/Player/Controls/Timeline.js @@ -1,8 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; -import cn from 'classnames'; +// import cn from 'classnames'; import { connectPlayer, Controls } from 'Player'; -import { TimelinePointer, Icon } from 'UI'; +// import { TimelinePointer, Icon } from 'UI'; import TimeTracker from './TimeTracker'; import stl from './timeline.module.css'; import { TYPES } from 'Types/session/event'; @@ -10,7 +10,7 @@ import { setTimelinePointer } from 'Duck/sessions'; import DraggableCircle from './DraggableCircle'; import CustomDragLayer from './CustomDragLayer'; import { debounce } from 'App/utils'; -import { Tooltip } from 'react-tippy'; +// import { Tooltip } from 'react-tippy'; const BOUNDRY = 0; @@ -20,46 +20,46 @@ function getTimelinePosition(value, scale) { return pos > 100 ? 100 : pos; } -const getPointerIcon = (type) => { - // exception, - switch (type) { - case 'fetch': - return 'funnel/file-earmark-minus-fill'; - case 'exception': - return 'funnel/exclamation-circle-fill'; - case 'log': - return 'funnel/exclamation-circle-fill'; - case 'stack': - return 'funnel/patch-exclamation-fill'; - case 'resource': - return 'funnel/file-earmark-minus-fill'; +// const getPointerIcon = (type) => { +// // exception, +// switch (type) { +// case 'fetch': +// return 'funnel/file-earmark-minus-fill'; +// case 'exception': +// return 'funnel/exclamation-circle-fill'; +// case 'log': +// return 'funnel/exclamation-circle-fill'; +// case 'stack': +// return 'funnel/patch-exclamation-fill'; +// case 'resource': +// return 'funnel/file-earmark-minus-fill'; - case 'dead_click': - return 'funnel/dizzy'; - case 'click_rage': - return 'funnel/dizzy'; - case 'excessive_scrolling': - return 'funnel/mouse'; - case 'bad_request': - return 'funnel/file-medical-alt'; - case 'missing_resource': - return 'funnel/file-earmark-minus-fill'; - case 'memory': - return 'funnel/sd-card'; - case 'cpu': - return 'funnel/microchip'; - case 'slow_resource': - return 'funnel/hourglass-top'; - case 'slow_page_load': - return 'funnel/hourglass-top'; - case 'crash': - return 'funnel/file-exclamation'; - case 'js_exception': - return 'funnel/exclamation-circle-fill'; - } +// case 'dead_click': +// return 'funnel/dizzy'; +// case 'click_rage': +// return 'funnel/dizzy'; +// case 'excessive_scrolling': +// return 'funnel/mouse'; +// case 'bad_request': +// return 'funnel/file-medical-alt'; +// case 'missing_resource': +// return 'funnel/file-earmark-minus-fill'; +// case 'memory': +// return 'funnel/sd-card'; +// case 'cpu': +// return 'funnel/microchip'; +// case 'slow_resource': +// return 'funnel/hourglass-top'; +// case 'slow_page_load': +// return 'funnel/hourglass-top'; +// case 'crash': +// return 'funnel/file-exclamation'; +// case 'js_exception': +// return 'funnel/exclamation-circle-fill'; +// } - return 'info'; -}; +// return 'info'; +// }; let deboucneJump = () => null; @connectPlayer((state) => ({ @@ -73,11 +73,11 @@ let deboucneJump = () => null; disabled: state.cssLoading || state.messagesLoading || state.markedTargets, endTime: state.endTime, live: state.live, - logList: state.logList, - exceptionsList: state.exceptionsList, - resourceList: state.resourceList, - stackList: state.stackList, - fetchList: state.fetchList, + // logList: state.logList, + // exceptionsList: state.exceptionsList, + // resourceList: state.resourceList, + // stackList: state.stackList, + // fetchList: state.fetchList, })) @connect( (state) => ({ @@ -141,14 +141,14 @@ export default class Timeline extends React.PureComponent { skipIntervals, disabled, endTime, - live, - logList, - exceptionsList, - resourceList, - clickRageTime, - stackList, - fetchList, - issues, + // live, + // logList, + // exceptionsList, + // resourceList, + // clickRageTime, + // stackList, + // fetchList, + // issues, } = this.props; const scale = 100 / endTime; @@ -175,9 +175,9 @@ export default class Timeline extends React.PureComponent { /> ))}
- {/* {events.map((e) => ( + {events.map((e) => (
- ))} */} + ))} {/* {issues.map((iss) => (
+
{!sourcemapUploaded && (
Date: Thu, 11 Aug 2022 17:51:10 +0200 Subject: [PATCH 079/168] feat(ui) - overview - stack event modal --- .../StackEventModal/StackEventModal.tsx | 22 +++++- .../components/JsonViewer/JsonViewer.js | 15 +++++ .../components/JsonViewer/index.ts | 1 + .../components/Sentry/Sentry.js | 67 +++++++++++++++++++ .../components/Sentry/index.ts | 1 + .../components/Sentry/sentry.module.css | 47 +++++++++++++ 6 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/JsonViewer/JsonViewer.js create mode 100644 frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/JsonViewer/index.ts create mode 100644 frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/Sentry.js create mode 100644 frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/index.ts create mode 100644 frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/sentry.module.css diff --git a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx index 07d163dbe..76490900a 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx @@ -1,12 +1,30 @@ import React from 'react'; +import JsonViewer from './components/JsonViewer'; +import Sentry from './components/Sentry'; +import { OPENREPLAY, SENTRY, DATADOG, STACKDRIVER } from 'Types/session/stackEvent'; interface Props { event: any; } function StackEventModal(props: Props) { + const renderPopupContent = () => { + const { + event: { source, payload, name }, + } = props; + switch (source) { + case SENTRY: + return ; + case DATADOG: + return ; + case STACKDRIVER: + return ; + default: + return ; + } + }; return ( -
- Content +
+ {renderPopupContent()}
); } diff --git a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/JsonViewer/JsonViewer.js b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/JsonViewer/JsonViewer.js new file mode 100644 index 000000000..985191896 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/JsonViewer/JsonViewer.js @@ -0,0 +1,15 @@ +import React from 'react'; +import { Icon, JSONTree } from 'UI'; + +export default class JsonViewer extends React.PureComponent { + render() { + const { data, title, icon } = this.props; + return ( +
+ +

{title}

+ +
+ ); + } +} diff --git a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/JsonViewer/index.ts b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/JsonViewer/index.ts new file mode 100644 index 000000000..155729246 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/JsonViewer/index.ts @@ -0,0 +1 @@ +export { default } from './JsonViewer'; \ No newline at end of file diff --git a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/Sentry.js b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/Sentry.js new file mode 100644 index 000000000..0e1ea0747 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/Sentry.js @@ -0,0 +1,67 @@ +import React from 'react'; +import { getIn, get } from 'immutable'; +import cn from 'classnames'; +import { withRequest } from 'HOCs'; +import { Loader, Icon, JSONTree } from 'UI'; +import { Accordion } from 'semantic-ui-react'; +import stl from './sentry.module.css'; + +@withRequest({ + endpoint: (props) => `/integrations/sentry/events/${props.event.id}`, + dataName: 'detailedEvent', + loadOnInitialize: true, +}) +export default class SentryEventInfo extends React.PureComponent { + makePanelsFromStackTrace(stacktrace) { + return get(stacktrace, 'frames', []).map(({ filename, function: method, lineNo, context = [] }) => ({ + key: `${filename}_${method}_${lineNo}`, + title: { + content: ( + + {filename} + {' in '} + {method} + {' at line '} + {lineNo} + + ), + }, + content: { + content: ( +
    + {context.map(([ctxLineNo, codeText]) => ( +
  1. {codeText}
  2. + ))} +
+ ), + }, + })); + } + + renderBody() { + const { detailedEvent, requestError, event } = this.props; + + const exceptionEntry = get(detailedEvent, ['entries'], []).find(({ type }) => type === 'exception'); + const stacktraces = getIn(exceptionEntry, ['data', 'values']); + if (!stacktraces) { + return ; + } + return stacktraces.map(({ type, value, stacktrace }) => ( +
+
{type}
+

{value}

+ +
+ )); + } + + render() { + const { open, toggleOpen, loading } = this.props; + return ( +
+ + {this.renderBody()} +
+ ); + } +} diff --git a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/index.ts b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/index.ts new file mode 100644 index 000000000..534162c8b --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/index.ts @@ -0,0 +1 @@ +export { default } from './Sentry'; \ No newline at end of file diff --git a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/sentry.module.css b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/sentry.module.css new file mode 100644 index 000000000..75956a074 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/sentry.module.css @@ -0,0 +1,47 @@ + +.wrapper { + padding: 20px 40px 30px; +} +.icon { + margin-left: -5px; +} +.stacktrace { + & h6 { + display: flex; + align-items: center; + font-size: 17px; + padding-top: 7px; + margin-bottom: 10px; + } + & p { + font-family: 'Menlo', 'monaco', 'consolas', monospace; + } +} + + +.accordionTitle { + font-weight: 100; + & > b { + font-weight: 700; + } +} + +.lineList { + list-style-position: inside; + list-style-type: decimal-leading-zero; + background: $gray-lightest; +} + +.codeLine { + font-family: 'Menlo', 'monaco', 'consolas', monospace; + line-height: 24px; + font-size: 12px; + white-space: pre-wrap; + word-wrap: break-word; + min-height: 24px; + padding: 0 25px; + &.highlighted { + background: $red; + color: $white; + } +} \ No newline at end of file From d582ba59bf45aa97425d5fb6caacc865b3173409 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Thu, 11 Aug 2022 17:36:13 +0100 Subject: [PATCH 080/168] feat(alerts): debug --- api/chalicelib/core/alerts.py | 4 ++-- api/chalicelib/utils/email_handler.py | 14 ++++++++------ api/chalicelib/utils/email_helper.py | 2 +- api/chalicelib/utils/pg_client.py | 13 +++++++------ api/chalicelib/utils/smtp.py | 3 ++- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/api/chalicelib/core/alerts.py b/api/chalicelib/core/alerts.py index 55bd4a29e..f851751ba 100644 --- a/api/chalicelib/core/alerts.py +++ b/api/chalicelib/core/alerts.py @@ -139,9 +139,9 @@ def send_by_email(notification, destination): def send_by_email_batch(notifications_list): if not helper.has_smtp(): - print("no SMTP configuration for email notifications") + logging.info("no SMTP configuration for email notifications") if notifications_list is None or len(notifications_list) == 0: - print("no email notifications") + logging.info("no email notifications") return for n in notifications_list: send_by_email(notification=n.get("notification"), destination=n.get("destination")) diff --git a/api/chalicelib/utils/email_handler.py b/api/chalicelib/utils/email_handler.py index 66b8a3afd..b3c7d9984 100644 --- a/api/chalicelib/utils/email_handler.py +++ b/api/chalicelib/utils/email_handler.py @@ -1,13 +1,15 @@ import base64 +import logging import re from email.header import Header from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from chalicelib.utils import helper, smtp from decouple import config +from chalicelib.utils import smtp + def __get_subject(subject): return subject @@ -64,11 +66,11 @@ def send_html(BODY_HTML, SUBJECT, recipient, bcc=None): if bcc is not None and len(bcc) > 0: r += [bcc] try: - print(f"Email sending to: {r}") + logging.info(f"Email sending to: {r}") s.sendmail(msg['FROM'], r, msg.as_string().encode('ascii')) except Exception as e: - print("!!! Email error!") - print(e) + logging.error("!!! Email error!") + logging.error(e) def send_text(recipients, text, subject): @@ -82,8 +84,8 @@ def send_text(recipients, text, subject): try: s.sendmail(msg['FROM'], recipients, msg.as_string().encode('ascii')) except Exception as e: - print("!! Text-email failed: " + subject), - print(e) + logging.error("!! Text-email failed: " + subject), + logging.error(e) def __escape_text_html(text): diff --git a/api/chalicelib/utils/email_helper.py b/api/chalicelib/utils/email_helper.py index 72072c924..2c5eb02e2 100644 --- a/api/chalicelib/utils/email_helper.py +++ b/api/chalicelib/utils/email_helper.py @@ -1,5 +1,5 @@ from chalicelib.utils.TimeUTC import TimeUTC -from chalicelib.utils.email_handler import __get_html_from_file, send_html, __escape_text_html +from chalicelib.utils.email_handler import __get_html_from_file, send_html def send_team_invitation(recipient, client_id, sender_name, invitation_link): diff --git a/api/chalicelib/utils/pg_client.py b/api/chalicelib/utils/pg_client.py index 66f06f3e5..753298328 100644 --- a/api/chalicelib/utils/pg_client.py +++ b/api/chalicelib/utils/pg_client.py @@ -1,3 +1,4 @@ +import logging import time from threading import Semaphore @@ -52,18 +53,18 @@ def make_pool(): try: postgreSQL_pool.closeall() except (Exception, psycopg2.DatabaseError) as error: - print("Error while closing all connexions to PostgreSQL", error) + logging.error("Error while closing all connexions to PostgreSQL", error) try: postgreSQL_pool = ORThreadedConnectionPool(config("pg_minconn", cast=int, default=20), config("pg_maxconn", cast=int, default=80), **PG_CONFIG) if (postgreSQL_pool): - print("Connection pool created successfully") + logging.info("Connection pool created successfully") except (Exception, psycopg2.DatabaseError) as error: - print("Error while connecting to PostgreSQL", error) + logging.error("Error while connecting to PostgreSQL", error) if RETRY < RETRY_MAX: RETRY += 1 - print(f"waiting for {RETRY_INTERVAL}s before retry n°{RETRY}") + logging.info(f"waiting for {RETRY_INTERVAL}s before retry n°{RETRY}") time.sleep(RETRY_INTERVAL) make_pool() else: @@ -113,12 +114,12 @@ class PostgresClient: if self.long_query or self.unlimited_query: self.connection.close() except Exception as error: - print("Error while committing/closing PG-connection", error) + logging.error("Error while committing/closing PG-connection", error) if str(error) == "connection already closed" \ and not self.long_query \ and not self.unlimited_query \ and config('PG_POOL', cast=bool, default=True): - print("Recreating the connexion pool") + logging.info("Recreating the connexion pool") make_pool() else: raise error diff --git a/api/chalicelib/utils/smtp.py b/api/chalicelib/utils/smtp.py index ce0b20655..63e1621fb 100644 --- a/api/chalicelib/utils/smtp.py +++ b/api/chalicelib/utils/smtp.py @@ -1,3 +1,4 @@ +import logging import smtplib from smtplib import SMTPAuthenticationError @@ -7,7 +8,7 @@ from starlette.exceptions import HTTPException class EmptySMTP: def sendmail(self, from_addr, to_addrs, msg, mail_options=(), rcpt_options=()): - print("!! CANNOT SEND EMAIL, NO VALID SMTP CONFIGURATION FOUND") + logging.error("!! CANNOT SEND EMAIL, NO VALID SMTP CONFIGURATION FOUND") class SMTPClient: From 641294be0c699d8bfd5aaca9620c48eee3e186fd Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Thu, 11 Aug 2022 17:58:19 +0100 Subject: [PATCH 081/168] feat(chalice): pg_client logging --- api/chalicelib/utils/pg_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/chalicelib/utils/pg_client.py b/api/chalicelib/utils/pg_client.py index 753298328..c4149f49d 100644 --- a/api/chalicelib/utils/pg_client.py +++ b/api/chalicelib/utils/pg_client.py @@ -7,6 +7,9 @@ import psycopg2.extras from decouple import config from psycopg2 import pool +logging.basicConfig(level=config("LOGLEVEL", default=logging.INFO)) +logging.getLogger('apscheduler').setLevel(config("LOGLEVEL", default=logging.INFO)) + _PG_CONFIG = {"host": config("pg_host"), "database": config("pg_dbname"), "user": config("pg_user"), From 289c5c96112b28fcba674ba957cda0b48dfc398d Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 12 Aug 2022 11:39:36 +0200 Subject: [PATCH 082/168] change(ui) - no content changes for widget sessions, user sessions and active sessions --- .../components/SessionList/SessionList.tsx | 17 ++- .../WidgetSessions/WidgetSessions.tsx | 105 ++++++------------ .../EventsBlock/Metadata/SessionList.js | 87 ++++++++------- 3 files changed, 93 insertions(+), 116 deletions(-) diff --git a/frontend/app/components/Assist/components/SessionList/SessionList.tsx b/frontend/app/components/Assist/components/SessionList/SessionList.tsx index dc8b54025..6de55c0d1 100644 --- a/frontend/app/components/Assist/components/SessionList/SessionList.tsx +++ b/frontend/app/components/Assist/components/SessionList/SessionList.tsx @@ -4,6 +4,7 @@ import { fetchLiveList } from 'Duck/sessions'; import { Loader, NoContent, Label } from 'UI'; import SessionItem from 'Shared/SessionItem'; import { useModal } from 'App/components/Modal'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; interface Props { loading: boolean; @@ -24,14 +25,26 @@ function SessionList(props: Props) { return (
-
+
{props.userId}'s Live Sessions{' '}
- + + +
+
No live sessions found.
+
+ } + >
{props.list.map((session: any) => (
diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index 66a4654e3..e3de553d2 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -1,21 +1,21 @@ -import React, { useEffect, useState } from "react"; -import { NoContent, Loader, Pagination } from "UI"; -import Select from "Shared/Select"; -import cn from "classnames"; -import { useStore } from "App/mstore"; -import SessionItem from "Shared/SessionItem"; -import { observer, useObserver } from "mobx-react-lite"; -import { DateTime } from "luxon"; -import { debounce } from "App/utils"; -import useIsMounted from "App/hooks/useIsMounted"; -import AnimatedSVG, { ICONS } from "Shared/AnimatedSVG/AnimatedSVG"; +import React, { useEffect, useState } from 'react'; +import { NoContent, Loader, Pagination } from 'UI'; +import Select from 'Shared/Select'; +import cn from 'classnames'; +import { useStore } from 'App/mstore'; +import SessionItem from 'Shared/SessionItem'; +import { observer, useObserver } from 'mobx-react-lite'; +import { DateTime } from 'luxon'; +import { debounce } from 'App/utils'; +import useIsMounted from 'App/hooks/useIsMounted'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; interface Props { className?: string; } function WidgetSessions(props: Props) { - const { className = "" } = props; - const [activeSeries, setActiveSeries] = useState("all"); + const { className = '' } = props; + const [activeSeries, setActiveSeries] = useState('all'); const [data, setData] = useState([]); const isMounted = useIsMounted(); const [loading, setLoading] = useState(false); @@ -23,15 +23,9 @@ function WidgetSessions(props: Props) { const { dashboardStore, metricStore } = useStore(); const filter = useObserver(() => dashboardStore.drillDownFilter); const widget: any = useObserver(() => metricStore.instance); - const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat( - "LLL dd, yyyy HH:mm" - ); - const endTime = DateTime.fromMillis(filter.endTimestamp).toFormat( - "LLL dd, yyyy HH:mm" - ); - const [seriesOptions, setSeriesOptions] = useState([ - { label: "All", value: "all" }, - ]); + const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat('LLL dd, yyyy HH:mm'); + const endTime = DateTime.fromMillis(filter.endTimestamp).toFormat('LLL dd, yyyy HH:mm'); + const [seriesOptions, setSeriesOptions] = useState([{ label: 'All', value: 'all' }]); const writeOption = ({ value }: any) => setActiveSeries(value.value); useEffect(() => { @@ -40,7 +34,7 @@ function WidgetSessions(props: Props) { label: item.seriesName, value: item.seriesId, })); - setSeriesOptions([{ label: "All", value: "all" }, ...seriesOptions]); + setSeriesOptions([{ label: 'All', value: 'all' }, ...seriesOptions]); }, [data]); const fetchSessions = (metricId: any, filter: any) => { @@ -55,10 +49,7 @@ function WidgetSessions(props: Props) { setLoading(false); }); }; - const debounceRequest: any = React.useCallback( - debounce(fetchSessions, 1000), - [] - ); + const debounceRequest: any = React.useCallback(debounce(fetchSessions, 1000), []); const depsString = JSON.stringify(widget.series); useEffect(() => { @@ -68,13 +59,7 @@ function WidgetSessions(props: Props) { page: metricStore.sessionsPage, limit: metricStore.sessionsPageSize, }); - }, [ - filter.startTimestamp, - filter.endTimestamp, - filter.filters, - depsString, - metricStore.sessionsPage, - ]); + }, [filter.startTimestamp, filter.endTimestamp, filter.filters, depsString, metricStore.sessionsPage]); return useObserver(() => (
@@ -82,28 +67,15 @@ function WidgetSessions(props: Props) {

Sessions

- between{" "} - - {startTime} - {" "} - and{" "} - - {endTime} - {" "} + between {startTime} and{' '} + {endTime}{' '}
- {widget.metricType !== "table" && ( + {widget.metricType !== 'table' && (
- - Filter by Series - -
)}
@@ -112,14 +84,10 @@ function WidgetSessions(props: Props) { - -
- No recordings found -
+
+ +
+
No relevant sessions found for the selected time period.
} show={filteredSessions.sessions.length === 0} @@ -134,13 +102,8 @@ function WidgetSessions(props: Props) {
- metricStore.updateKey("sessionsPage", page) - } + totalPages={Math.ceil(filteredSessions.total / metricStore.sessionsPageSize)} + onPageChange={(page: any) => metricStore.updateKey('sessionsPage', page)} limit={metricStore.sessionsPageSize} debounceRequest={500} /> @@ -155,13 +118,9 @@ function WidgetSessions(props: Props) { const getListSessionsBySeries = (data: any, seriesId: any) => { const arr: any = { sessions: [], total: 0 }; data.forEach((element: any) => { - if (seriesId === "all") { + if (seriesId === 'all') { const sessionIds = arr.sessions.map((i: any) => i.sessionId); - arr.sessions.push( - ...element.sessions.filter( - (i: any) => !sessionIds.includes(i.sessionId) - ) - ); + arr.sessions.push(...element.sessions.filter((i: any) => !sessionIds.includes(i.sessionId))); arr.total = element.total; } else { if (element.seriesId === seriesId) { diff --git a/frontend/app/components/Session_/EventsBlock/Metadata/SessionList.js b/frontend/app/components/Session_/EventsBlock/Metadata/SessionList.js index bc0868933..fe414a30d 100644 --- a/frontend/app/components/Session_/EventsBlock/Metadata/SessionList.js +++ b/frontend/app/components/Session_/EventsBlock/Metadata/SessionList.js @@ -4,52 +4,57 @@ 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' ]) +@connect((state) => ({ + currentSessionId: state.getIn(['sessions', 'current', 'sessionId']), })) class SessionList extends React.PureComponent { - render() { - const { - similarSessions, - loading, - currentSessionId, - } = this.props; + render() { + const { similarSessions, loading, currentSessionId } = this.props; - const similarSessionWithoutCurrent = similarSessions.map(({sessions, ...rest}) => { - return { - ...rest, - sessions: sessions.map(Session).filter(({ sessionId }) => sessionId !== currentSessionId) - } - }).filter(site => site.sessions.length > 0); - - return ( - - -
- { similarSessionWithoutCurrent.map(site => ( -
-
- - { site.name } -
-
- { site.sessions.map(session => ( -
- + const similarSessionWithoutCurrent = similarSessions + .map(({ sessions, ...rest }) => { + return { + ...rest, + sessions: sessions.map(Session).filter(({ sessionId }) => sessionId !== currentSessionId), + }; + }) + .filter((site) => site.sessions.length > 0); + + return ( + + + +
+
No sessions found.
+
+ } + > +
+ {similarSessionWithoutCurrent.map((site) => ( +
+
+ + {site.name} +
+
+ {site.sessions.map((session) => ( +
+ +
+ ))} +
+
+ ))}
- )) } -
-
- )) } -
- - - ); - } + + + ); + } } export default SessionList; From d90b222fa36034af0a67204945855ab1e9b2f51a Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 12 Aug 2022 11:49:04 +0200 Subject: [PATCH 083/168] change(ui) - header logo changes --- frontend/app/components/Header/Header.js | 4 ++-- frontend/app/components/Header/header.module.css | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 9726e83ee..1b5b97c22 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -66,8 +66,8 @@ const Header = (props) => { return (
-
-
+
+
v{window.env.VERSION}
diff --git a/frontend/app/components/Header/header.module.css b/frontend/app/components/Header/header.module.css index 8eba021a9..9852b7436 100644 --- a/frontend/app/components/Header/header.module.css +++ b/frontend/app/components/Header/header.module.css @@ -9,7 +9,7 @@ $height: 50px; display: flex; justify-content: space-between; border-bottom: solid thin $gray-light; - padding: 0 15px; + /* padding: 0 15px; */ background: $white; z-index: $header; } From 796f0ea66bf657d1d4153acd0aa3cee1b20859bb Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 12 Aug 2022 11:49:15 +0200 Subject: [PATCH 084/168] change(ui) - header logo changes --- frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx b/frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx index dc70cdb4c..114cba1e9 100644 --- a/frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx +++ b/frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx @@ -40,7 +40,7 @@ function AnimatedSVG(props: Props) { case ICONS.EMPTY_STATE: return ; case ICONS.LOGO_SMALL: - return ; + return ; case ICONS.NO_RESULTS: return ; case ICONS.SIGNAL_GREEN: From bf3bb7a8403a2b85311488ad09e6d93047fe0649 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 12 Aug 2022 11:51:02 +0200 Subject: [PATCH 085/168] change(ui) - no content padding --- frontend/app/components/ui/NoContent/noContent.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/ui/NoContent/noContent.module.css b/frontend/app/components/ui/NoContent/noContent.module.css index dd78c8bbc..91c29e579 100644 --- a/frontend/app/components/ui/NoContent/noContent.module.css +++ b/frontend/app/components/ui/NoContent/noContent.module.css @@ -31,7 +31,7 @@ .subtext { font-size: 14px; - margin-bottom: 20px; + /* margin-bottom: 20px; */ } From 5658aaaea254fe4887ec59476b0ff636904293e3 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 12 Aug 2022 12:02:23 +0200 Subject: [PATCH 086/168] change(ui) - live list header --- .../LiveSessionList/LiveSessionList.tsx | 47 ++++++++++--------- .../SortOrderButton/SortOrderButton.tsx | 4 +- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx index b6e64c57c..97866a270 100644 --- a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx @@ -14,6 +14,7 @@ import { capitalize } from 'App/utils'; import LiveSessionReloadButton from 'Shared/LiveSessionReloadButton'; import cn from 'classnames'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import { numberWithCommas } from 'App/utils'; const AUTOREFRESH_INTERVAL = 0.5 * 60 * 1000; const PER_PAGE = 10; @@ -83,33 +84,33 @@ function LiveSessionList(props: Props) { return (
-
-
-

- Live Sessions - {total} -

+
+
+
+

+ Live Sessions + {numberWithCommas(total)} +

- props.applyFilter({ ...filter })} /> -
-
-
- Sort By -
- i.value === filter.sort) || sortOptions[0]} + /> +
+ props.applyFilter({ order: state })} sortOrder={filter.order} /> +
-
-
onChange('asc')} > @@ -23,7 +23,7 @@ export default React.memo(function SortOrderButton(props: Props) {
onChange('desc')} > From 6c1c2295cb23b7a1938f718d20be6dacf274334c Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 9 Aug 2022 16:16:52 +0200 Subject: [PATCH 087/168] fix(ui): small ui changes for dashboards and metrics --- .../MetricListItem/MetricListItem.tsx | 13 +++---- .../components/MetricsList/MetricsList.tsx | 17 +++++---- .../MetricsSearch/MetricsSearch.tsx | 4 +-- .../components/MetricsView/MetricsView.tsx | 10 ++++-- .../components/WidgetForm/WidgetForm.tsx | 33 ++++------------- .../WidgetPreview/WidgetPreview.tsx | 36 ++++++++++++++++--- frontend/app/components/ui/SVG.tsx | 4 ++- frontend/app/svg/icons/columns-gap-filled.svg | 3 ++ frontend/app/svg/icons/info-circle-fill.svg | 3 ++ frontend/app/utils.ts | 2 +- 10 files changed, 72 insertions(+), 53 deletions(-) create mode 100644 frontend/app/svg/icons/columns-gap-filled.svg create mode 100644 frontend/app/svg/icons/info-circle-fill.svg diff --git a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx index 492a41bd5..38af42702 100644 --- a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx +++ b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx @@ -51,8 +51,8 @@ function MetricTypeIcon({ type }: any) { return ( -
- +
+
) @@ -63,7 +63,7 @@ function MetricListItem(props: Props) { return ( -
+
{/*
@@ -76,17 +76,14 @@ function MetricListItem(props: Props) {
{/*
*/} -
- -
{metric.owner}
-
+
{metric.isPublic ? 'Team' : 'Private'}
-
{metric.lastModified && checkForRecent(metric.lastModified, 'LLL dd, yyyy, hh:mm a')}
+
{metric.lastModified && checkForRecent(metric.lastModified, 'LLL dd, yyyy, hh:mm a')}
); } diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index 3cc6dff40..983d70603 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -27,6 +27,7 @@ function MetricsList(props: Props) { metricStore.updateKey('sessionsPage', 1); }, []) + console.log(list, list.length) return useObserver(() => ( } > -
-
-
Metric
+
+
+
Title
{/*
Type
*/} -
Dashboards
Owner
-
Visibility
-
Last Modified
+
Visibility
+
Last Modified
{sliceListPerPage(list, metricStore.page - 1, metricStore.pageSize).map((metric: any) => ( @@ -54,7 +54,10 @@ function MetricsList(props: Props) { ))}
-
+
+
+ Showing {Math.min(list.length, metricStore.pageSize)} out of {list.length} Sessions +
@@ -31,4 +31,4 @@ function MetricsSearch(props) { )); } -export default MetricsSearch; \ No newline at end of file +export default MetricsSearch; diff --git a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx index a8c1d96c4..216d1369b 100644 --- a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx +++ b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx @@ -18,17 +18,21 @@ function MetricsView(props: Props) { metricStore.fetchList(); }, []); return useObserver(() => ( -
-
+
+
{metricsCount}
-
+
+
+ + Create custom Metrics to capture key interactions and track KPIs. +
)); diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index 88e0a59b4..40e80a7c9 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { metricTypes, metricOf, issueOptions } from 'App/constants/filterOptions'; import { FilterKey } from 'Types/filter/filterType'; import { useStore } from 'App/mstore'; @@ -8,7 +8,6 @@ import FilterSeries from '../FilterSeries'; import { confirm, Popup } from 'UI'; import Select from 'Shared/Select' import { withSiteId, dashboardMetricDetails, metricDetails } from 'App/routes' -import DashboardSelectionModal from '../DashboardSelectionModal/DashboardSelectionModal'; interface Props { history: any; @@ -17,8 +16,8 @@ interface Props { } function WidgetForm(props: Props) { - const [showDashboardSelectionModal, setShowDashboardSelectionModal] = useState(false); - const { history, match: { params: { siteId, dashboardId, metricId } } } = props; + + const { history, match: { params: { siteId, dashboardId } } } = props; const { metricStore, dashboardStore } = useStore(); const dashboards = dashboardStore.dashboards; const isSaving = useObserver(() => metricStore.isSaving); @@ -201,31 +200,13 @@ function WidgetForm(props: Props) {
{metric.exists() && ( - <> - - - + )}
- { canAddToDashboard && ( - setShowDashboardSelectionModal(false)} - /> - )}
)); } diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx index bde05f398..3cc02fd76 100644 --- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx @@ -2,19 +2,22 @@ import React from 'react'; import cn from 'classnames'; import WidgetWrapper from '../WidgetWrapper'; import { useStore } from 'App/mstore'; -import { SegmentSelection } from 'UI'; +import { SegmentSelection, Button, Icon } from 'UI'; import { useObserver } from 'mobx-react-lite'; import SelectDateRange from 'Shared/SelectDateRange'; import { FilterKey } from 'Types/filter/filterType'; import WidgetDateRange from '../WidgetDateRange/WidgetDateRange'; // import Period, { LAST_24_HOURS, LAST_30_DAYS } from 'Types/app/period'; +import DashboardSelectionModal from '../DashboardSelectionModal/DashboardSelectionModal'; interface Props { className?: string; } function WidgetPreview(props: Props) { + const [showDashboardSelectionModal, setShowDashboardSelectionModal] = React.useState(false); const { className = '' } = props; const { metricStore, dashboardStore } = useStore(); + const dashboards = dashboardStore.dashboards; const metric: any = useObserver(() => metricStore.instance); const isTimeSeries = metric.metricType === 'timeseries'; const isTable = metric.metricType === 'table'; @@ -53,9 +56,12 @@ function WidgetPreview(props: Props) { } } + const canAddToDashboard = metric.exists() && dashboards.length > 0; + return useObserver(() => ( -
-
+ <> +
+

{getWidgetTitle()}

@@ -99,13 +105,33 @@ function WidgetPreview(props: Props) { )}
+ {/* add to dashboard */} + {metric.exists() && ( + + )}
-
+
+ { canAddToDashboard && ( + setShowDashboardSelectionModal(false)} + /> + )} + )); } -export default WidgetPreview; \ No newline at end of file +export default WidgetPreview; diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index d5cd23f17..dd207816e 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -111,6 +111,7 @@ const SVG = (props: Props) => { case 'cog': return ; case 'cogs': return ; case 'collection': return ; + case 'columns-gap-filled': return ; case 'columns-gap': return ; case 'console/error': return ; case 'console/exception': return ; @@ -230,7 +231,8 @@ const SVG = (props: Props) => { case 'hourglass-start': return ; case 'id-card': return ; case 'image': return ; - case 'info-circle': return ; + case 'info-circle-fill': return ; + case 'info-circle': return ; case 'info-square': return ; case 'info': return ; case 'inspect': return ; diff --git a/frontend/app/svg/icons/columns-gap-filled.svg b/frontend/app/svg/icons/columns-gap-filled.svg new file mode 100644 index 000000000..4bb29842d --- /dev/null +++ b/frontend/app/svg/icons/columns-gap-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/app/svg/icons/info-circle-fill.svg b/frontend/app/svg/icons/info-circle-fill.svg new file mode 100644 index 000000000..9af7ae43b --- /dev/null +++ b/frontend/app/svg/icons/info-circle-fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/app/utils.ts b/frontend/app/utils.ts index 9444e121e..142016d2b 100644 --- a/frontend/app/utils.ts +++ b/frontend/app/utils.ts @@ -347,4 +347,4 @@ export const compareJsonObjects = (obj1: any, obj2: any) => { export const getInitials = (name: any) => { const names = name.split(' '); return names.slice(0, 2).map((n: any) => n[0]).join(''); -} \ No newline at end of file +} From 0f3f883a2761e18780076edc7758a048d33a1224 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 9 Aug 2022 16:23:48 +0200 Subject: [PATCH 088/168] fix(ui): small ui changes for dashboards and metrics --- .../components/Dashboard/components/MetricsList/MetricsList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index 983d70603..2171822c9 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -56,7 +56,7 @@ function MetricsList(props: Props) {
- Showing {Math.min(list.length, metricStore.pageSize)} out of {list.length} Sessions + Showing {Math.min(list.length, metricStore.pageSize)} out of {list.length} metrics
Date: Wed, 10 Aug 2022 10:51:49 +0200 Subject: [PATCH 089/168] fix(ui): change dashboards overview --- .../DashboardList/DashboardList.tsx | 73 +++++++++++ .../DashboardList/DashboardListItem.tsx | 55 +++++++++ .../DashboardList/DashboardSearch.tsx | 36 ++++++ .../DashboardList/DashboardsView.tsx | 28 +++++ .../components/DashboardList/index.ts | 1 + .../DashboardRouter/DashboardRouter.tsx | 5 +- .../DashboardSideMenu/DashboardSideMenu.tsx | 113 ++++-------------- .../DashboardView/DashboardView.tsx | 24 ++-- .../components/MetricsList/MetricsList.tsx | 15 +-- .../MetricsSearch/MetricsSearch.tsx | 2 +- .../app/components/ui/NoContent/NoContent.tsx | 2 +- .../ui/SideMenuitem/SideMenuitem.js | 4 +- frontend/app/mstore/dashboardStore.ts | 12 ++ frontend/app/mstore/types/dashboard.ts | 9 +- frontend/app/utils.ts | 4 +- 15 files changed, 261 insertions(+), 122 deletions(-) create mode 100644 frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/index.ts diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx new file mode 100644 index 000000000..93649babf --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -0,0 +1,73 @@ +import { observer } from 'mobx-react-lite'; +import React from 'react'; +import { NoContent, Pagination } from 'UI'; +import { useStore } from 'App/mstore'; +import { getRE } from 'App/utils'; +import { sliceListPerPage } from 'App/utils'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import DashboardListItem from './DashboardListItem'; + +const filterList = >(list: T[], searchQuery: string): T[] => { + const filterRE = getRE(searchQuery, 'i'); + console.log(filterRE) + let _list = list.filter((w: T) => { + console.log(w.name, w.owner, w.description, filterRE.test(w.name)) + return filterRE.test(w.name) || filterRE.test(w.owner) || filterRE.test(w.description); + }); + return _list +} + +function DashboardList() { + const { dashboardStore } = useStore(); + const [shownDashboards, setDashboards] = React.useState([]); + const dashboards = dashboardStore.dashboards; + const dashboardsSearch = dashboardStore.dashboardsSearch; + + React.useEffect(() => { + setDashboards(filterList(dashboards, dashboardsSearch)) + }, [dashboardsSearch]) + + const list = dashboardsSearch !== '' ? shownDashboards : dashboards; + const lenth = list.length; + + return ( + + +
No data available.
+
+ } + > +
+
+
Title
+
Visibility
+
Created
+
+ + {sliceListPerPage(list, dashboardStore.page - 1, dashboardStore.pageSize).map((dashboard: any) => ( + + + + ))} +
+ +
+
+ Showing {Math.min(list.length, dashboardStore.pageSize)} out of {list.length} Dashboards +
+ dashboardStore.updateKey('page', page)} + limit={dashboardStore.pageSize} + debounceRequest={100} + /> +
+ + ); +} + +export default observer(DashboardList); diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx new file mode 100644 index 000000000..8a40c2139 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Icon } from 'UI'; +import { connect } from 'react-redux'; +import{ IDashboard } from "App/mstore/types/dashboard"; +import { checkForRecent } from 'App/date'; +import { withSiteId, dashboardSelected } from 'App/routes'; +import { useStore } from 'App/mstore'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +interface Props extends RouteComponentProps { + dashboard: IDashboard; + siteId: string; +} + +function DashboardListItem(props: Props) { + const { dashboard, siteId, history } = props; + const { dashboardStore } = useStore(); + + const onItemClick = () => { + dashboardStore.selectDashboardById(dashboard.dashboardId); + const path = withSiteId(dashboardSelected(dashboard.dashboardId), siteId); + history.push(path); + }; + return ( + <> +
+
+
+
+ +
+
+ {dashboard.name} +
+
+
+ {/*
*/} +
+
+ + {dashboard.isPublic ? 'Team' : 'Private'} +
+
+
+ {checkForRecent(dashboard.createdAt, 'LLL dd, yyyy, hh:mm a')} +
+
+
+ {dashboard.description} +
+ + ); +} + +export default connect(state => ({ siteId: state.getIn([ 'site', 'siteId' ]) }))(withRouter(DashboardListItem)) diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx new file mode 100644 index 000000000..a3b13f1d3 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx @@ -0,0 +1,36 @@ +import React, { useEffect, useState } from 'react'; +import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore'; +import { Icon } from 'UI'; +import { debounce } from 'App/utils'; + +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 ( +
+ + +
+ ); +} + +export default observer(DashboardSearch); diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx new file mode 100644 index 000000000..5ebe9e150 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Button, PageTitle, Icon, Link } from 'UI'; +import withPageTitle from 'HOCs/withPageTitle'; +import DashboardList from './DashboardList'; +import DashboardSearch from './DashboardSearch'; + +function DashboardsView() { + return ( +
+
+
+ +
+ +
+ +
+
+
+ + A dashboard is a custom visualization using your OpenReplay data. +
+ +
+ ); +} + +export default withPageTitle('Dashboards - OpenReplay')(DashboardsView); diff --git a/frontend/app/components/Dashboard/components/DashboardList/index.ts b/frontend/app/components/Dashboard/components/DashboardList/index.ts new file mode 100644 index 000000000..61e485dc9 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/index.ts @@ -0,0 +1 @@ +export { default } from './DashboardsView'; diff --git a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx index 4df856619..a7e71ffbd 100644 --- a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx +++ b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx @@ -16,6 +16,7 @@ import DashboardView from '../DashboardView'; import MetricsView from '../MetricsView'; import WidgetView from '../WidgetView'; import WidgetSubDetailsView from '../WidgetSubDetailsView'; +import DashboardsView from '../DashboardList'; function DashboardViewSelected({ siteId, dashboardId }) { return ( @@ -44,8 +45,8 @@ function DashboardRouter(props: Props) { - - + + diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx index 00b462bbd..2c4a4091e 100644 --- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx @@ -1,113 +1,40 @@ -//@ts-nocheck -import { useObserver } from 'mobx-react-lite'; import React from 'react'; -import { SideMenuitem, SideMenuHeader, Icon, Popup, Button } from 'UI'; -import { useStore } from 'App/mstore'; -import { withRouter } from 'react-router-dom'; -import { withSiteId, dashboardSelected, metrics } from 'App/routes'; -import { useModal } from 'App/components/Modal'; -import DashbaordListModal from '../DashbaordListModal'; -import DashboardModal from '../DashboardModal'; -import cn from 'classnames'; +import { SideMenuitem, SideMenuHeader } from 'UI'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { withSiteId, metrics, dashboard } from 'App/routes'; import { connect } from 'react-redux'; import { compose } from 'redux' import { setShowAlerts } from 'Duck/dashboard'; -// import stl from 'Shared/MainSearchBar/mainSearchBar.module.css'; -const SHOW_COUNT = 8; - -interface Props { +interface Props extends RouteComponentProps { siteId: string history: any setShowAlerts: (show: boolean) => void } -function DashboardSideMenu(props: RouteComponentProps) { +function DashboardSideMenu(props: Props) { const { history, siteId, setShowAlerts } = props; - const { hideModal, showModal } = useModal(); - const { dashboardStore } = useStore(); - const dashboardId = useObserver(() => dashboardStore.selectedDashboard?.dashboardId); - const dashboardsPicked = useObserver(() => dashboardStore.dashboards.slice(0, SHOW_COUNT)); - const remainingDashboardsCount = dashboardStore.dashboards.length - SHOW_COUNT; const isMetric = history.location.pathname.includes('metrics'); + const isDashboards = history.location.pathname.includes('dashboard'); - const redirect = (path) => { + const redirect = (path: string) => { history.push(path); } - const onItemClick = (dashboard) => { - dashboardStore.selectDashboardById(dashboard.dashboardId); - const path = withSiteId(dashboardSelected(dashboard.dashboardId), parseInt(siteId)); - history.push(path); - }; - - const onAddDashboardClick = (e) => { - dashboardStore.initDashboard(); - showModal(, { right: true }) - } - - const togglePinned = (dashboard, e) => { - e.stopPropagation(); - dashboardStore.updatePinned(dashboard.dashboardId); - } - - return useObserver(() => ( + return (
- <> - - Create - - - } + text="Preferences" /> - {dashboardsPicked.map((item: any) => ( - onItemClick(item)} - className="group" - leading = {( -
- {item.isPublic && ( - -
-
- )} - {item.isPinned &&
} - {!item.isPinned && ( - -
togglePinned(item, e)} - > - -
-
- )} -
- )} - /> - ))} -
- {remainingDashboardsCount > 0 && ( -
showModal(, {})} - > - {remainingDashboardsCount} More -
- )} -
+
+ redirect(withSiteId(dashboard(), siteId))} + /> +
) { />
- )); + ); } export default compose( withRouter, connect(null, { setShowAlerts }), -)(DashboardSideMenu) as React.FunctionComponent> +)(DashboardSideMenu) diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index 108d961a5..e3aa0c2f2 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -15,8 +15,10 @@ import withPageTitle from "HOCs/withPageTitle"; import withReport from "App/components/hocs/withReport"; import DashboardOptions from "../DashboardOptions"; import SelectDateRange from "Shared/SelectDateRange"; +// @ts-ignore import DashboardIcon from "../../../../svg/dashboard-icn.svg"; import { Tooltip } from "react-tippy"; +import Breadcrumb from 'Shared/Breadcrumb'; interface IProps { siteId: string; @@ -69,6 +71,10 @@ function DashboardView(props: Props) { dashboardStore.selectDefaultDashboard(); }, [siteId]) + useEffect(() => { + dashboardStore.selectDashboardById(dashboardId); + }, [dashboardId]) + const onAddWidgets = () => { dashboardStore.initDashboard(dashboard); showModal( @@ -138,15 +144,6 @@ function DashboardView(props: Props) {
} size="small" - subtext={ - - } >
setShowEditModal(false)} focusTitle={focusTitle} /> +
metricStore.metrics); const metricsSearch = useObserver(() => metricStore.metricsSearch); - const filterList = (list) => { + const filterList = >(list: T[]): T[] => { const filterRE = getRE(metricsSearch, 'i'); - let _list = list.filter(w => { - const dashbaordNames = w.dashboards.map(d => d.name).join(' '); + let _list = list.filter((w: T) => { + const dashbaordNames = w.dashboards.map((d: any) => d.name).join(' '); return filterRE.test(w.name) || filterRE.test(w.metricType) || filterRE.test(w.owner) || filterRE.test(dashbaordNames); }); return _list } - const list: any = metricsSearch !== '' ? filterList(metrics) : metrics; + const list = metricsSearch !== '' ? filterList(metrics) : metrics; const lenth = list.length; useEffect(() => { metricStore.updateKey('sessionsPage', 1); }, []) - console.log(list, list.length) return useObserver(() => ( - +
No data available.
} @@ -41,7 +39,6 @@ function MetricsList(props: Props) {
Title
- {/*
Type
*/}
Owner
Visibility
Last Modified
diff --git a/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx b/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx index 582851842..cf27661d9 100644 --- a/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx +++ b/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx @@ -12,7 +12,7 @@ function MetricsSearch(props) { debounceUpdate = debounce((key, value) => metricStore.updateKey(key, value), 500); }, []) - const write = ({ target: { name, value } }) => { + const write = ({ target: { value } }) => { setQuery(value); debounceUpdate('metricsSearch', value); } diff --git a/frontend/app/components/ui/NoContent/NoContent.tsx b/frontend/app/components/ui/NoContent/NoContent.tsx index 10a7e72e7..ae26731be 100644 --- a/frontend/app/components/ui/NoContent/NoContent.tsx +++ b/frontend/app/components/ui/NoContent/NoContent.tsx @@ -7,7 +7,7 @@ interface Props { subtext?: any; icon?: string; iconSize?: number; - size?: number; + size?: string; show?: boolean; children?: any; image?: any; diff --git a/frontend/app/components/ui/SideMenuitem/SideMenuitem.js b/frontend/app/components/ui/SideMenuitem/SideMenuitem.js index ffbd31cc4..c9dd32b46 100644 --- a/frontend/app/components/ui/SideMenuitem/SideMenuitem.js +++ b/frontend/app/components/ui/SideMenuitem/SideMenuitem.js @@ -7,13 +7,13 @@ function SideMenuitem({ iconBg = false, iconColor = "gray-dark", iconSize = 18, - className, + className = '', iconName = null, title, active = false, disabled = false, onClick, - deleteHandler, + deleteHandler = null, leading = null, ...props }) { diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 54fbe1972..232d4da4f 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -46,6 +46,11 @@ export interface IDashboardSotre { showAlertModal: boolean; + page: number + pageSize: number + dashboardsSearch: string + sort: any + selectWidgetsByCategory: (category: string) => void; toggleAllSelectedWidgets: (isSelected: boolean) => void; removeSelectedWidgetByCategory(category: string): void; @@ -115,6 +120,12 @@ export default class DashboardStore implements IDashboardSotre { sessionsLoading: boolean = false; showAlertModal: boolean = false; + // Pagination + page: number = 1 + pageSize: number = 15 + dashboardsSearch: string = '' + sort: any = {} + constructor() { makeAutoObservable(this); @@ -183,6 +194,7 @@ export default class DashboardStore implements IDashboardSotre { } updateKey(key: any, value: any) { + console.log(key, value) this[key] = value; } diff --git a/frontend/app/mstore/types/dashboard.ts b/frontend/app/mstore/types/dashboard.ts index 4c7ea801e..e172198ff 100644 --- a/frontend/app/mstore/types/dashboard.ts +++ b/frontend/app/mstore/types/dashboard.ts @@ -2,6 +2,7 @@ import { makeAutoObservable, observable, action, runInAction } from "mobx" import Widget, { IWidget } from "./widget" import { dashboardService } from "App/services" import { toast } from 'react-toastify'; +import { DateTime } from 'luxon'; export interface IDashboard { dashboardId: any @@ -14,6 +15,7 @@ export interface IDashboard { isPinned: boolean currentWidget: IWidget config: any + createdAt: Date update(data: any): void toJson(): any @@ -44,6 +46,7 @@ export default class Dashboard implements IDashboard { isPinned: boolean = false currentWidget: IWidget = new Widget() config: any = {} + createdAt: Date = new Date() constructor() { makeAutoObservable(this) @@ -63,8 +66,7 @@ export default class Dashboard implements IDashboard { dashboardId: this.dashboardId, name: this.name, isPublic: this.isPublic, - // widgets: this.widgets.map(w => w.toJson()) - // widgets: this.widgets + createdAt: this.createdAt, metrics: this.metrics, description: this.description, } @@ -77,7 +79,8 @@ export default class Dashboard implements IDashboard { this.description = json.description this.isPublic = json.isPublic this.isPinned = json.isPinned - this.widgets = json.widgets ? json.widgets.map(w => new Widget().fromJson(w)).sort((a, b) => a.position - b.position) : [] + this.createdAt = DateTime.fromMillis(new Date(json.createdAt).getTime()) + this.widgets = json.widgets ? json.widgets.map((w: Widget) => new Widget().fromJson(w)).sort((a: Widget, b: Widget) => a.position - b.position) : [] }) return this } diff --git a/frontend/app/utils.ts b/frontend/app/utils.ts index 142016d2b..d0bfb9b56 100644 --- a/frontend/app/utils.ts +++ b/frontend/app/utils.ts @@ -239,10 +239,10 @@ export const isGreaterOrEqualVersion = (version, compareTo) => { return major > majorC || (major === majorC && minor > minorC) || (major === majorC && minor === minorC && patch >= patchC); }; -export const sliceListPerPage = (list, page, perPage = 10) => { +export const sliceListPerPage = >(list: T, page: number, perPage = 10): T => { const start = page * perPage; const end = start + perPage; - return list.slice(start, end); + return list.slice(start, end) as T; }; export const positionOfTheNumber = (min, max, value, length) => { From 58a42c72d557a92fdf85fa7ced3358a0d5333b9c Mon Sep 17 00:00:00 2001 From: sylenien Date: Wed, 10 Aug 2022 11:06:36 +0200 Subject: [PATCH 090/168] fix(ui): add typing to filter function --- .../components/DashboardList/DashboardList.tsx | 14 ++------------ .../components/MetricsList/MetricsList.tsx | 18 +++++++++--------- frontend/app/utils.ts | 15 ++++++++++++++- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index 93649babf..5ce208da8 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -2,21 +2,11 @@ import { observer } from 'mobx-react-lite'; import React from 'react'; import { NoContent, Pagination } from 'UI'; import { useStore } from 'App/mstore'; -import { getRE } from 'App/utils'; +import { filterList } from 'App/utils'; import { sliceListPerPage } from 'App/utils'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import DashboardListItem from './DashboardListItem'; -const filterList = >(list: T[], searchQuery: string): T[] => { - const filterRE = getRE(searchQuery, 'i'); - console.log(filterRE) - let _list = list.filter((w: T) => { - console.log(w.name, w.owner, w.description, filterRE.test(w.name)) - return filterRE.test(w.name) || filterRE.test(w.owner) || filterRE.test(w.description); - }); - return _list -} - function DashboardList() { const { dashboardStore } = useStore(); const [shownDashboards, setDashboards] = React.useState([]); @@ -24,7 +14,7 @@ function DashboardList() { const dashboardsSearch = dashboardStore.dashboardsSearch; React.useEffect(() => { - setDashboards(filterList(dashboards, dashboardsSearch)) + setDashboards(filterList(dashboards, dashboardsSearch, ['name', 'owner', 'description'])) }, [dashboardsSearch]) const list = dashboardsSearch !== '' ? shownDashboards : dashboards; diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index 2b236564e..166be1b40 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -2,24 +2,24 @@ import { useObserver } from 'mobx-react-lite'; import React, { useEffect } from 'react'; import { NoContent, Pagination } from 'UI'; import { useStore } from 'App/mstore'; -import { getRE } from 'App/utils'; +import { getRE, filterList } from 'App/utils'; import MetricListItem from '../MetricListItem'; import { sliceListPerPage } from 'App/utils'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import { IWidget } from 'App/mstore/types/widget'; function MetricsList() { const { metricStore } = useStore(); const metrics = useObserver(() => metricStore.metrics); const metricsSearch = useObserver(() => metricStore.metricsSearch); - const filterList = >(list: T[]): T[] => { - const filterRE = getRE(metricsSearch, 'i'); - let _list = list.filter((w: T) => { - const dashbaordNames = w.dashboards.map((d: any) => d.name).join(' '); - return filterRE.test(w.name) || filterRE.test(w.metricType) || filterRE.test(w.owner) || filterRE.test(dashbaordNames); - }); - return _list + + const filterByDashboard = (item: IWidget, searchRE: RegExp) => { + const dashboardsStr = item.dashboards.map((d: any) => d.name).join(' ') + return searchRE.test(dashboardsStr) } - const list = metricsSearch !== '' ? filterList(metrics) : metrics; + const list = metricsSearch !== '' + ? filterList(metrics, metricsSearch, ['name', 'metricType', 'owner'], filterByDashboard) + : metrics; const lenth = list.length; useEffect(() => { diff --git a/frontend/app/utils.ts b/frontend/app/utils.ts index d0bfb9b56..7694f245a 100644 --- a/frontend/app/utils.ts +++ b/frontend/app/utils.ts @@ -53,7 +53,7 @@ export const cutURL = (url, prefix = '.../') => `${prefix + url.split('/').slice export const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -export function getRE(string, options) { +export function getRE(string: string, options: string) { let re; try { re = new RegExp(string, options); @@ -63,6 +63,19 @@ export function getRE(string, options) { return re; } +export const filterList = >( + list: T[], + searchQuery: string, + testKeys: string[], + searchCb?: (listItem: T, query: string | RegExp +) => boolean): T[] => { + const filterRE = getRE(searchQuery, 'i'); + let _list = list.filter((listItem: T) => { + return testKeys.some((key) => filterRE.test(listItem[key]) || searchCb?.(listItem, filterRE)); + }); + return _list + } + export const getStateColor = (state) => { switch (state) { case 'passed': From 7806c158068a8a5dde47d67341cfe9b81c98a947 Mon Sep 17 00:00:00 2001 From: sylenien Date: Wed, 10 Aug 2022 15:39:03 +0200 Subject: [PATCH 091/168] fix(ui): add new metric selection modals, fix dashboard view --- .../app/components/Dashboard/NewDashboard.tsx | 20 +- .../DashboardList/DashboardsView.tsx | 23 +- .../DashboardMetricSelection.tsx | 11 +- .../DashboardModal/DashboardModal.tsx | 9 +- .../DashboardRouter/DashboardRouter.tsx | 12 +- .../DashboardView/DashboardView.tsx | 228 ++++++++---------- .../DashboardWidgetGrid/AddMetric.tsx | 98 ++++++++ .../AddMetricContainer.tsx | 83 +++++++ .../AddPredefinedMetric.tsx | 152 ++++++++++++ .../DashboardWidgetGrid.tsx | 11 +- .../components/MetricsView/MetricsView.tsx | 3 - .../app/components/Header/SiteDropdown.js | 3 +- frontend/app/components/ui/Button/Button.tsx | 1 + frontend/app/mstore/dashboardStore.ts | 15 +- 14 files changed, 503 insertions(+), 166 deletions(-) create mode 100644 frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx diff --git a/frontend/app/components/Dashboard/NewDashboard.tsx b/frontend/app/components/Dashboard/NewDashboard.tsx index 89af30897..af0e1ed88 100644 --- a/frontend/app/components/Dashboard/NewDashboard.tsx +++ b/frontend/app/components/Dashboard/NewDashboard.tsx @@ -9,12 +9,20 @@ import cn from 'classnames'; import { withSiteId } from 'App/routes'; import withPermissions from 'HOCs/withPermissions' -function NewDashboard(props: RouteComponentProps<{}>) { - const { history, match: { params: { siteId, dashboardId, metricId } } } = props; +interface RouterProps { + siteId: string; + dashboardId: string; + metricId: string; +} + +function NewDashboard(props: RouteComponentProps) { + const { history, match: { params: { siteId, dashboardId } } } = props; const { dashboardStore } = useStore(); const loading = useObserver(() => dashboardStore.isLoading); const isMetricDetails = history.location.pathname.includes('/metrics/') || history.location.pathname.includes('/metric/'); + const isDashboardDetails = history.location.pathname.includes('/dashboard/') + const shouldHideMenu = isMetricDetails || isDashboardDetails; useEffect(() => { dashboardStore.fetchList().then((resp) => { if (parseInt(dashboardId) > 0) { @@ -33,16 +41,16 @@ function NewDashboard(props: RouteComponentProps<{}>) { return useObserver(() => (
-
+
- +
diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx index 5ebe9e150..2564cea2b 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx @@ -1,17 +1,32 @@ import React from 'react'; -import { Button, PageTitle, Icon, Link } from 'UI'; +import { Button, PageTitle, Icon } from 'UI'; import withPageTitle from 'HOCs/withPageTitle'; +import { useStore } from 'App/mstore'; +import { withSiteId } from 'App/routes'; + import DashboardList from './DashboardList'; import DashboardSearch from './DashboardSearch'; - -function DashboardsView() { + +function DashboardsView({ history, siteId }: { history: any, siteId: string }) { + const { dashboardStore } = useStore(); + + const onAddDashboardClick = () => { + dashboardStore.initDashboard(); + dashboardStore + .save(dashboardStore.dashboardInstance) + .then(async (syncedDashboard) => { + dashboardStore.selectDashboardById(syncedDashboard.dashboardId); + history.push(withSiteId(`/dashboard/${syncedDashboard.dashboardId}`, siteId)) + }) + } + return (
- +
diff --git a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx b/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx index d612efe0b..cca7c51c1 100644 --- a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx +++ b/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx @@ -6,9 +6,16 @@ import cn from 'classnames'; import { useStore } from 'App/mstore'; import { Loader } from 'UI'; -function WidgetCategoryItem({ category, isSelected, onClick, selectedWidgetIds }) { +interface IWiProps { + category: Record + onClick: (category: Record) => void + isSelected: boolean + selectedWidgetIds: string[] +} + +export function WidgetCategoryItem({ category, isSelected, onClick, selectedWidgetIds }: IWiProps) { const selectedCategoryWidgetsCount = useObserver(() => { - return category.widgets.filter(widget => selectedWidgetIds.includes(widget.metricId)).length; + return category.widgets.filter((widget: any) => selectedWidgetIds.includes(widget.metricId)).length; }); return (
void; } -function DashboardModal(props) { +function DashboardModal(props: Props) { const { history, siteId, dashboardId } = props; const { dashboardStore } = useStore(); const selectedWidgetsCount = useObserver(() => dashboardStore.selectedWidgets.length); const { hideModal } = useModal(); - const loadingTemplates = useObserver(() => dashboardStore.loadingTemplates); const dashboard = useObserver(() => dashboardStore.dashboardInstance); const loading = useObserver(() => dashboardStore.isSaving); diff --git a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx index a7e71ffbd..f18415f27 100644 --- a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx +++ b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Switch, Route } from 'react-router'; -import { withRouter } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; import { metrics, @@ -18,18 +18,18 @@ import WidgetView from '../WidgetView'; import WidgetSubDetailsView from '../WidgetSubDetailsView'; import DashboardsView from '../DashboardList'; -function DashboardViewSelected({ siteId, dashboardId }) { +function DashboardViewSelected({ siteId, dashboardId }: { siteId: string, dashboardId: string }) { return ( ) } -interface Props { - history: any +interface Props extends RouteComponentProps { match: any } function DashboardRouter(props: Props) { - const { match: { params: { siteId, dashboardId, metricId } } } = props; + const { match: { params: { siteId, dashboardId } }, history } = props; + return (
@@ -46,7 +46,7 @@ function DashboardRouter(props: Props) { - + diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index e3aa0c2f2..b81cef7b9 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from "react"; import { observer } from "mobx-react-lite"; import { useStore } from "App/mstore"; -import { Button, PageTitle, Loader, NoContent } from "UI"; +import { Button, PageTitle, Loader } from "UI"; import { withSiteId } from "App/routes"; import withModal from "App/components/Modal/withModal"; import DashboardWidgetGrid from "../DashboardWidgetGrid"; @@ -15,8 +15,6 @@ import withPageTitle from "HOCs/withPageTitle"; import withReport from "App/components/hocs/withReport"; import DashboardOptions from "../DashboardOptions"; import SelectDateRange from "Shared/SelectDateRange"; -// @ts-ignore -import DashboardIcon from "../../../../svg/dashboard-icn.svg"; import { Tooltip } from "react-tippy"; import Breadcrumb from 'Shared/Breadcrumb'; @@ -31,23 +29,18 @@ type Props = IProps & RouteComponentProps; function DashboardView(props: Props) { const { siteId, dashboardId } = props; const { dashboardStore } = useStore(); + const { showModal } = useModal(); + const [focusTitle, setFocusedInput] = React.useState(true); const [showEditModal, setShowEditModal] = React.useState(false); - const { showModal } = useModal(); const showAlertModal = dashboardStore.showAlertModal; const loading = dashboardStore.fetchingDashboard; - const dashboards = dashboardStore.dashboards; const dashboard: any = dashboardStore.selectedDashboard; const period = dashboardStore.period; const queryParams = new URLSearchParams(props.location.search); - useEffect(() => { - if (!dashboard || !dashboard.dashboardId) return; - dashboardStore.fetch(dashboard.dashboardId); - }, [dashboard]); - const trimQuery = () => { if (!queryParams.has("modal")) return; queryParams.delete("modal"); @@ -60,21 +53,24 @@ function DashboardView(props: Props) { }; useEffect(() => { - if (!dashboardId || (!dashboard && dashboardStore.dashboards.length > 0)) dashboardStore.selectDefaultDashboard(); - if (queryParams.has("modal")) { onAddWidgets(); trimQuery(); } }, []); - useEffect(() => { - dashboardStore.selectDefaultDashboard(); - }, [siteId]) useEffect(() => { - dashboardStore.selectDashboardById(dashboardId); + const isExists = dashboardStore.getDashboardById(dashboardId); + if (!isExists) { + props.history.push(withSiteId(`/dashboard`, siteId)) + } }, [dashboardId]) + useEffect(() => { + if (!dashboard || !dashboard.dashboardId) return; + dashboardStore.fetch(dashboard.dashboardId); + }, [dashboard]); + const onAddWidgets = () => { dashboardStore.initDashboard(dashboard); showModal( @@ -87,11 +83,6 @@ function DashboardView(props: Props) { ); }; - const onAddDashboardClick = () => { - dashboardStore.initDashboard(); - showModal(, { right: true }) - } - const onEdit = (isTitle: boolean) => { dashboardStore.initDashboard(dashboard); setFocusedInput(isTitle); @@ -107,131 +98,104 @@ function DashboardView(props: Props) { }) ) { dashboardStore.deleteDashboard(dashboard).then(() => { - dashboardStore.selectDefaultDashboard().then( - ({ dashboardId }) => { - props.history.push( - withSiteId(`/dashboard/${dashboardId}`, siteId) - ); - }, - () => { - props.history.push(withSiteId("/dashboard", siteId)); - } - ); + props.history.push(withSiteId(`/dashboard`, siteId)); }); } }; + if (!dashboard) return null; + return ( - - + setShowEditModal(false)} + focusTitle={focusTitle} + /> + +
+
+ + {dashboard?.name} + + } + onDoubleClick={() => onEdit(true)} + className="mr-3 select-none hover:border-dotted hover:border-b border-gray-medium cursor-pointer" + actionButton={ + + } /> - - Gather and analyze
important metrics in one - place. -
- } - size="small" - > -
- setShowEditModal(false)} - focusTitle={focusTitle} - /> - -
-
- - {dashboard?.name} - - } - onDoubleClick={() => onEdit(true)} - className="mr-3 select-none hover:border-dotted hover:border-b border-gray-medium cursor-pointer" - actionButton={ - +
+
+ + dashboardStore.setPeriod(period) } + right={true} />
-
-
- - dashboardStore.setPeriod(period) - } - right={true} - /> -
-
-
- -
+
+
+
-
-

- {dashboard?.description} -

-
- - - dashboardStore.updateKey("showAlertModal", false) - } - />
- +
+

onEdit(false)} + > + {dashboard?.description || "Describe the purpose of this dashboard"} +

+
+ + + dashboardStore.updateKey("showAlertModal", false) + } + /> +
); } diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx new file mode 100644 index 000000000..ccdc033fe --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { Button } from 'UI'; +import WidgetWrapper from 'App/components/Dashboard/components/WidgetWrapper'; +import { useStore } from 'App/mstore'; +import { useModal } from 'App/components/Modal'; +import { dashboardMetricCreate, withSiteId } from 'App/routes'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +interface IProps extends RouteComponentProps { + metrics: any[]; + siteId: string; + title: string; + description: string; +} + +function AddMetric({ metrics, history, siteId, title, description }: IProps) { + const { dashboardStore } = useStore(); + const { hideModal } = useModal(); + + const dashboard = dashboardStore.selectedDashboard; + const selectedWidgetIds = dashboardStore.selectedWidgets.map((widget: any) => widget.metricId); + const queryParams = new URLSearchParams(location.search); + + const onSave = () => { + if (selectedWidgetIds.length === 0) return; + dashboardStore + .save(dashboard) + .then(async (syncedDashboard) => { + if (dashboard.exists()) { + await dashboardStore.fetch(dashboard.dashboardId); + } + dashboardStore.selectDashboardById(syncedDashboard.dashboardId); + }) + .then(hideModal); + }; + + const onCreateNew = () => { + const path = withSiteId(dashboardMetricCreate(dashboard.dashboardId), siteId); + if (!queryParams.has('modal')) history.push('?modal=addMetric'); + history.push(path); + hideModal(); + }; + + return ( +
+
+
+
+

{title}

+
{description}
+
+ {title.includes('Custom') ? ( +
+ + + Create new + +
+ ) : ( +
+ Don’t find the one you need? + + + Create custom metric + +
+ )} +
+ +
+ {metrics.map((metric: any) => ( + dashboardStore.toggleWidgetSelection(metric)} + /> + ))} +
+ +
+
+ {'Selected '} + {selectedWidgetIds.length} + {' out of '} + {metrics.length} +
+ +
+
+
+ ); +} + +export default withRouter(observer(AddMetric)); diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx new file mode 100644 index 000000000..c796ecc5f --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { Icon } from 'UI'; +import { useModal } from 'App/components/Modal'; +import { useStore } from 'App/mstore'; +import AddMetric from './AddMetric'; +import AddPredefinedMetric from './AddPredefinedMetric'; + +interface AddMetricButtonProps { + iconName: string; + title: string; + description: string; + onClick: () => void; +} + +function AddMetricButton({ iconName, title, description, onClick }: AddMetricButtonProps) { + return ( +
+
+ +
+
{title}
+
{description}
+
+ ); +} + +function AddMetricContainer({ siteId }: any) { + const { showModal } = useModal(); + const [categories, setCategories] = React.useState[]>([]); + const { dashboardStore } = useStore(); + + React.useEffect(() => { + dashboardStore?.fetchTemplates(true).then((cats) => setCategories(cats)); + }, []); + + const onAddCustomMetrics = () => { + dashboardStore.initDashboard(dashboardStore.selectedDashboard); + showModal( + category.name === 'custom')?.widgets} + />, + { right: true } + ); + }; + + const onAddPredefinedMetrics = () => { + dashboardStore.initDashboard(dashboardStore.selectedDashboard); + showModal( + category.name !== 'custom')} + />, + { right: true } + ); + }; + return ( +
+ + +
+ ); +} + +export default observer(AddMetricContainer); diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx new file mode 100644 index 000000000..13cd1047b --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx @@ -0,0 +1,152 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { Button } from 'UI'; +import WidgetWrapper from 'App/components/Dashboard/components/WidgetWrapper'; +import { useStore } from 'App/mstore'; +import { useModal } from 'App/components/Modal'; +import { dashboardMetricCreate, withSiteId } from 'App/routes'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { WidgetCategoryItem } from 'App/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection'; + +interface IProps extends RouteComponentProps { + categories: Record[]; + siteId: string; + title: string; + description: string; +} + +function AddPredefinedMetric({ categories, history, siteId, title, description }: IProps) { + const { dashboardStore } = useStore(); + const { hideModal } = useModal(); + const [allCheck, setAllCheck] = React.useState(false); + const [activeCategory, setActiveCategory] = React.useState>(); + + const scrollContainer = React.useRef(null); + + const dashboard = dashboardStore.selectedDashboard; + const selectedWidgetIds = dashboardStore.selectedWidgets.map((widget: any) => widget.metricId); + const queryParams = new URLSearchParams(location.search); + const totalMetricCount = categories.reduce((acc, category) => acc + category.widgets.length, 0); + + React.useEffect(() => { + dashboardStore?.fetchTemplates(true).then((categories) => { + const defaultCategory = categories.filter((category: any) => category.name !== 'custom')[0]; + setActiveCategory(defaultCategory); + }); + }, []); + + React.useEffect(() => { + if (scrollContainer.current) { + scrollContainer.current.scrollTop = 0; + } + }, [activeCategory, scrollContainer.current]); + + const handleWidgetCategoryClick = (category: any) => { + setActiveCategory(category); + setAllCheck(false); + }; + + const onSave = () => { + if (selectedWidgetIds.length === 0) return; + dashboardStore + .save(dashboard) + .then(async (syncedDashboard) => { + if (dashboard.exists()) { + await dashboardStore.fetch(dashboard.dashboardId); + } + dashboardStore.selectDashboardById(syncedDashboard.dashboardId); + }) + .then(hideModal); + }; + + const onCreateNew = () => { + const path = withSiteId(dashboardMetricCreate(dashboard.dashboardId), siteId); + if (!queryParams.has('modal')) history.push('?modal=addMetric'); + history.push(path); + hideModal(); + }; + + const toggleAllMetrics = ({ target: { checked } }: any) => { + setAllCheck(checked); + if (checked) { + dashboardStore.selectWidgetsByCategory(activeCategory.name); + } else { + dashboardStore.removeSelectedWidgetByCategory(activeCategory); + } + }; + + return ( +
+
+
+
+

{title}

+
{description}
+
+ {title.includes('Custom') ? ( +
+ + + Create new + +
+ ) : ( +
+ Don’t find the one you need? + + + Create custom metric + +
+ )} +
+ +
+
+
+ {activeCategory && + categories.map((category) => ( + + ))} +
+
+ +
+ {activeCategory && + activeCategory.widgets.map((metric: any) => ( + dashboardStore.toggleWidgetSelection(metric)} + /> + ))} +
+
+ +
+
+ {'Selected '} + {selectedWidgetIds.length} + {' out of '} + {totalMetricCount} +
+ +
+
+
+ ); +} + +export default withRouter(observer(AddPredefinedMetric)); diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx index 442ee46e6..7750c8241 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { useStore } from 'App/mstore'; import WidgetWrapper from '../WidgetWrapper'; -import { NoContent, Button, Loader } from 'UI'; +import { NoContent, Loader } from 'UI'; import { useObserver } from 'mobx-react-lite'; +import AddMetricContainer from './AddMetricContainer' interface Props { siteId: string, @@ -18,16 +19,14 @@ function DashboardWidgetGrid(props: Props) { const list: any = useObserver(() => dashboard?.widgets); return useObserver(() => ( + // @ts-ignore -

Metrics helps you visualize trends from sessions captured by OpenReplay

- -
+ } >
diff --git a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx index 216d1369b..288b9e1f0 100644 --- a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx +++ b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx @@ -10,9 +10,7 @@ interface Props{ siteId: number; } function MetricsView(props: Props) { - const { siteId } = props; const { metricStore } = useStore(); - const metricsCount = useObserver(() => metricStore.metrics.length); React.useEffect(() => { metricStore.fetchList(); @@ -22,7 +20,6 @@ function MetricsView(props: Props) {
- {metricsCount}
diff --git a/frontend/app/components/Header/SiteDropdown.js b/frontend/app/components/Header/SiteDropdown.js index 87fe2a0c2..7f89eee82 100644 --- a/frontend/app/components/Header/SiteDropdown.js +++ b/frontend/app/components/Header/SiteDropdown.js @@ -54,7 +54,8 @@ export default class SiteDropdown extends React.PureComponent { this.props.clearSearchLive(); mstore.initClient(); - }; + mstore.dashboardStore.selectDefaultDashboard(); + } render() { const { diff --git a/frontend/app/components/ui/Button/Button.tsx b/frontend/app/components/ui/Button/Button.tsx index d3e3acd15..eb9396682 100644 --- a/frontend/app/components/ui/Button/Button.tsx +++ b/frontend/app/components/ui/Button/Button.tsx @@ -8,6 +8,7 @@ interface Props { onClick?: () => void; disabled?: boolean; type?: 'button' | 'submit' | 'reset'; + variant?: 'default' | 'primary' | 'text' | 'text-primary' | 'text-red' | 'outline' loading?: boolean; icon?: string; rounded?: boolean; diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 232d4da4f..8abef7ea8 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -53,7 +53,7 @@ export interface IDashboardSotre { selectWidgetsByCategory: (category: string) => void; toggleAllSelectedWidgets: (isSelected: boolean) => void; - removeSelectedWidgetByCategory(category: string): void; + removeSelectedWidgetByCategory(category: Record): void; toggleWidgetSelection(widget: IWidget): void; initDashboard(dashboard?: IDashboard): void; @@ -72,6 +72,7 @@ export interface IDashboardSotre { getDashboardCount(): void; updateDashboard(dashboard: IDashboard): void; selectDashboardById(dashboardId: string): void; + getDashboardById(dashboardId: string): boolean; setSiteId(siteId: any): void; selectDefaultDashboard(): Promise; @@ -372,6 +373,18 @@ export default class DashboardStore implements IDashboardSotre { 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; }; From a9819589403a3a1114167f594a096a1d8fa6a918 Mon Sep 17 00:00:00 2001 From: sylenien Date: Wed, 10 Aug 2022 16:48:59 +0200 Subject: [PATCH 092/168] fix(ui): minor style fixes --- frontend/app/components/Dashboard/NewDashboard.tsx | 7 ------- .../Dashboard/components/DashboardList/DashboardList.tsx | 2 +- .../components/DashboardList/DashboardListItem.tsx | 2 +- .../Dashboard/components/DashboardList/DashboardsView.tsx | 4 ++-- .../Dashboard/components/MetricListItem/MetricListItem.tsx | 6 +----- .../Dashboard/components/MetricsList/MetricsList.tsx | 2 +- .../Dashboard/components/MetricsView/MetricsView.tsx | 4 ++-- 7 files changed, 8 insertions(+), 19 deletions(-) diff --git a/frontend/app/components/Dashboard/NewDashboard.tsx b/frontend/app/components/Dashboard/NewDashboard.tsx index af0e1ed88..2f9496591 100644 --- a/frontend/app/components/Dashboard/NewDashboard.tsx +++ b/frontend/app/components/Dashboard/NewDashboard.tsx @@ -29,13 +29,6 @@ function NewDashboard(props: RouteComponentProps) { dashboardStore.selectDashboardById(dashboardId); } }); - if (!dashboardId && location.pathname.includes('dashboard')) { - dashboardStore.selectDefaultDashboard().then(({ dashboardId }) => { - props.history.push(withSiteId(`/dashboard/${dashboardId}`, siteId)); - }, () => { - props.history.push(withSiteId('/dashboard', siteId)); - }) - } }, [siteId]); return useObserver(() => ( diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index 5ce208da8..466103dc8 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -31,7 +31,7 @@ function DashboardList() { } >
-
+
Title
Visibility
Created
diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx index 8a40c2139..5e82ac2b6 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx @@ -23,7 +23,7 @@ function DashboardListItem(props: Props) { }; return ( <> -
+
diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx index 2564cea2b..10bbfc50d 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx @@ -22,7 +22,7 @@ function DashboardsView({ history, siteId }: { history: any, siteId: string }) { return (
-
+
@@ -31,7 +31,7 @@ function DashboardsView({ history, siteId }: { history: any, siteId: string }) {
-
+
A dashboard is a custom visualization using your OpenReplay data.
diff --git a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx index 38af42702..52747a86c 100644 --- a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx +++ b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx @@ -63,19 +63,15 @@ function MetricListItem(props: Props) { return ( -
+
- {/*
- -
*/} {metric.name}
- {/*
*/}
{metric.owner}
diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index 166be1b40..8c453688e 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -37,7 +37,7 @@ function MetricsList() { } >
-
+
Title
Owner
Visibility
diff --git a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx index 288b9e1f0..abdf2e5e2 100644 --- a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx +++ b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx @@ -17,7 +17,7 @@ function MetricsView(props: Props) { }, []); return useObserver(() => (
-
+
@@ -26,7 +26,7 @@ function MetricsView(props: Props) {
-
+
Create custom Metrics to capture key interactions and track KPIs.
From a70058cf5d8f2ecb2c29afa6dd7598d8dd1be98d Mon Sep 17 00:00:00 2001 From: sylenien Date: Wed, 10 Aug 2022 17:59:54 +0200 Subject: [PATCH 093/168] fix(ui): fix crashes --- .../components/DashboardList/DashboardListItem.tsx | 2 +- .../Dashboard/components/DashboardWidgetGrid/AddMetric.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx index 5e82ac2b6..1fffe9f30 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx @@ -51,5 +51,5 @@ function DashboardListItem(props: Props) { ); } - +// @ts-ignore export default connect(state => ({ siteId: state.getIn([ 'site', 'siteId' ]) }))(withRouter(DashboardListItem)) diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx index ccdc033fe..af46de0c2 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx @@ -67,7 +67,7 @@ function AddMetric({ metrics, history, siteId, title, description }: IProps) {
- {metrics.map((metric: any) => ( + {metrics ? metrics.map((metric: any) => ( dashboardStore.toggleWidgetSelection(metric)} /> - ))} + )) : ( +
No custom metrics created.
+ )}
From 73c45dae3e1e3dc0546f2553636bfd0a929027bd Mon Sep 17 00:00:00 2001 From: sylenien Date: Thu, 11 Aug 2022 13:50:07 +0200 Subject: [PATCH 094/168] fix(player): add border to dashboards, fix font size and tooltip --- .../DashboardList/DashboardsView.tsx | 8 +++--- .../MetricListItem/MetricListItem.tsx | 27 +++++++------------ .../components/MetricsView/MetricsView.tsx | 8 +++--- 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx index 10bbfc50d..75b4aaf4e 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx @@ -21,18 +21,18 @@ function DashboardsView({ history, siteId }: { history: any, siteId: string }) { } return ( -
+
-
+
-
- +
+ A dashboard is a custom visualization using your OpenReplay data.
diff --git a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx index 52747a86c..18f580851 100644 --- a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx +++ b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { Icon, NoContent, Label, Link, Pagination, Popup } from 'UI'; -import { checkForRecent, formatDateTimeDefault, convertTimestampToUtcTimestamp } from 'App/date'; -import { getIcon } from 'react-toastify/dist/components'; +import { Icon, Link, Popup } from 'UI'; +import { checkForRecent } from 'App/date'; +import { Tooltip } from 'react-tippy' interface Props { metric: any; @@ -25,19 +25,6 @@ function DashboardLink({ dashboards}: any) { } function MetricTypeIcon({ type }: any) { - const PopupWrapper = (props: any) => { - return ( - {type}
} - position="top center" - on="hover" - hideOnScroll={true} - > - {props.children} - - ); - } - const getIcon = () => { switch (type) { case 'funnel': @@ -50,11 +37,15 @@ function MetricTypeIcon({ type }: any) { } return ( - + {type}
} + position="top" + arrow + >
- + ) } diff --git a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx index abdf2e5e2..afe0dd337 100644 --- a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx +++ b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx @@ -16,18 +16,18 @@ function MetricsView(props: Props) { metricStore.fetchList(); }, []); return useObserver(() => ( -
+
-
+
-
- +
+ Create custom Metrics to capture key interactions and track KPIs.
From 3f14d4b766b363969a1cf35d4e16936722fff061 Mon Sep 17 00:00:00 2001 From: sylenien Date: Thu, 11 Aug 2022 15:58:23 +0200 Subject: [PATCH 095/168] fix(player): change card colors and borders, fix icons --- .../DashboardView/DashboardView.tsx | 135 +++++++----------- .../AddMetricContainer.tsx | 35 +++-- .../DashboardWidgetGrid.tsx | 5 +- .../components/WidgetName/WidgetName.tsx | 7 +- frontend/app/components/ui/SVG.tsx | 2 + frontend/app/mstore/types/dashboard.ts | 2 +- frontend/app/mstore/types/widget.ts | 2 +- frontend/app/svg/icons/bar-pencil.svg | 16 +++ frontend/app/svg/icons/grid-check.svg | 3 + frontend/app/theme/colors.js | 13 ++ 10 files changed, 121 insertions(+), 99 deletions(-) create mode 100644 frontend/app/svg/icons/bar-pencil.svg create mode 100644 frontend/app/svg/icons/grid-check.svg diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index b81cef7b9..e75fe6823 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -1,21 +1,21 @@ -import React, { useEffect } from "react"; -import { observer } from "mobx-react-lite"; -import { useStore } from "App/mstore"; -import { Button, PageTitle, Loader } from "UI"; -import { withSiteId } from "App/routes"; -import withModal from "App/components/Modal/withModal"; -import DashboardWidgetGrid from "../DashboardWidgetGrid"; -import { confirm } from "UI"; -import { withRouter, RouteComponentProps } from "react-router-dom"; -import { useModal } from "App/components/Modal"; -import DashboardModal from "../DashboardModal"; -import DashboardEditModal from "../DashboardEditModal"; -import AlertFormModal from "App/components/Alerts/AlertFormModal"; -import withPageTitle from "HOCs/withPageTitle"; -import withReport from "App/components/hocs/withReport"; -import DashboardOptions from "../DashboardOptions"; -import SelectDateRange from "Shared/SelectDateRange"; -import { Tooltip } from "react-tippy"; +import React, { useEffect } from 'react'; +import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore'; +import { Button, PageTitle, Loader } from 'UI'; +import { withSiteId } from 'App/routes'; +import withModal from 'App/components/Modal/withModal'; +import DashboardWidgetGrid from '../DashboardWidgetGrid'; +import { confirm } from 'UI'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { useModal } from 'App/components/Modal'; +import DashboardModal from '../DashboardModal'; +import DashboardEditModal from '../DashboardEditModal'; +import AlertFormModal from 'App/components/Alerts/AlertFormModal'; +import withPageTitle from 'HOCs/withPageTitle'; +import withReport from 'App/components/hocs/withReport'; +import DashboardOptions from '../DashboardOptions'; +import SelectDateRange from 'Shared/SelectDateRange'; +import { Tooltip } from 'react-tippy'; import Breadcrumb from 'Shared/Breadcrumb'; interface IProps { @@ -42,18 +42,18 @@ function DashboardView(props: Props) { const queryParams = new URLSearchParams(props.location.search); const trimQuery = () => { - if (!queryParams.has("modal")) return; - queryParams.delete("modal"); + if (!queryParams.has('modal')) return; + queryParams.delete('modal'); props.history.replace({ search: queryParams.toString(), }); }; const pushQuery = () => { - if (!queryParams.has("modal")) props.history.push("?modal=addMetric"); + if (!queryParams.has('modal')) props.history.push('?modal=addMetric'); }; useEffect(() => { - if (queryParams.has("modal")) { + if (queryParams.has('modal')) { onAddWidgets(); trimQuery(); } @@ -62,9 +62,9 @@ function DashboardView(props: Props) { useEffect(() => { const isExists = dashboardStore.getDashboardById(dashboardId); if (!isExists) { - props.history.push(withSiteId(`/dashboard`, siteId)) + props.history.push(withSiteId(`/dashboard`, siteId)); } - }, [dashboardId]) + }, [dashboardId]); useEffect(() => { if (!dashboard || !dashboard.dashboardId) return; @@ -73,14 +73,7 @@ function DashboardView(props: Props) { const onAddWidgets = () => { dashboardStore.initDashboard(dashboard); - showModal( - , - { right: true } - ); + showModal(, { right: true }); }; const onEdit = (isTitle: boolean) => { @@ -92,8 +85,8 @@ function DashboardView(props: Props) { const onDelete = async () => { if ( await confirm({ - header: "Confirm", - confirmButton: "Yes, delete", + header: 'Confirm', + confirmButton: 'Yes, delete', confirmation: `Are you sure you want to permanently delete this Dashboard?`, }) ) { @@ -107,60 +100,41 @@ function DashboardView(props: Props) { return ( -
- setShowEditModal(false)} - focusTitle={focusTitle} - /> +
+ setShowEditModal(false)} focusTitle={focusTitle} />
+ // @ts-ignore + {dashboard?.name} } onDoubleClick={() => onEdit(true)} - className="mr-3 select-none hover:border-dotted hover:border-b border-gray-medium cursor-pointer" + className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer" actionButton={ - } />
-
-
+
+
- dashboardStore.setPeriod(period) - } + onChange={(period: any) => dashboardStore.setPeriod(period)} right={true} />
@@ -176,30 +150,21 @@ function DashboardView(props: Props) {
-

onEdit(false)} - > - {dashboard?.description || "Describe the purpose of this dashboard"} -

+ {/* @ts-ignore */} + +

onEdit(false)} + > + {dashboard?.description || 'Describe the purpose of this dashboard'} +

+
- - - dashboardStore.updateKey("showAlertModal", false) - } - /> + + dashboardStore.updateKey('showAlertModal', false)} />
); } -export default withPageTitle("Dashboards - OpenReplay")( - withReport(withRouter(withModal(observer(DashboardView)))) -); +export default withPageTitle('Dashboards - OpenReplay')(withReport(withRouter(withModal(observer(DashboardView))))); diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx index c796ecc5f..7cba9ac52 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx @@ -5,25 +5,41 @@ import { useModal } from 'App/components/Modal'; import { useStore } from 'App/mstore'; import AddMetric from './AddMetric'; import AddPredefinedMetric from './AddPredefinedMetric'; +import cn from 'classnames'; interface AddMetricButtonProps { iconName: string; title: string; description: string; + isPremade?: boolean; onClick: () => void; } -function AddMetricButton({ iconName, title, description, onClick }: AddMetricButtonProps) { +function AddMetricButton({ iconName, title, description, onClick, isPremade }: AddMetricButtonProps) { return (
-
- +
+
-
{title}
-
{description}
+
{title}
+
{description}
); } @@ -63,17 +79,18 @@ function AddMetricContainer({ siteId }: any) { ); }; return ( -
+
diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx index 7750c8241..74c1d5c3d 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx @@ -24,9 +24,9 @@ function DashboardWidgetGrid(props: Props) { Build your dashboard} subtext={ - +
} >
@@ -41,6 +41,7 @@ function DashboardWidgetGrid(props: Props) { isWidget={true} /> ))} +
diff --git a/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx b/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx index 67be4930d..d0d673df2 100644 --- a/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx +++ b/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx @@ -68,7 +68,12 @@ function WidgetName(props: Props) {
setEditing(true)} - className={cn("text-2xl h-8 flex items-center border-transparent", canEdit && 'cursor-pointer select-none hover:border-dotted hover:border-b border-gray-medium')} + className={ + cn( + "text-2xl h-8 flex items-center border-transparent", + canEdit && 'cursor-pointer select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium' + ) + } > { name }
diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index dd207816e..4a6582789 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -64,6 +64,7 @@ const SVG = (props: Props) => { case 'avatar/icn_wild_bore': return ; case 'ban': return ; case 'bar-chart-line': return ; + case 'bar-pencil': return ; case 'bell-plus': return ; case 'bell': return ; case 'binoculars': return ; @@ -221,6 +222,7 @@ const SVG = (props: Props) => { case 'github': return ; case 'graph-up-arrow': return ; case 'grid-3x3': return ; + case 'grid-check': return ; case 'grip-horizontal': return ; case 'hash': return ; case 'hdd-stack': return ; diff --git a/frontend/app/mstore/types/dashboard.ts b/frontend/app/mstore/types/dashboard.ts index e172198ff..5b8c40b17 100644 --- a/frontend/app/mstore/types/dashboard.ts +++ b/frontend/app/mstore/types/dashboard.ts @@ -37,7 +37,7 @@ export interface IDashboard { export default class Dashboard implements IDashboard { public static get ID_KEY():string { return "dashboardId" } dashboardId: any = undefined - name: string = "New Dashboard" + name: string = "Untitled Dashboard" description: string = "" isPublic: boolean = true widgets: IWidget[] = [] diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index 10e1e68f4..3d824c68f 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -60,7 +60,7 @@ export default class Widget implements IWidget { public static get ID_KEY():string { return "metricId" } metricId: any = undefined widgetId: any = undefined - name: string = "New Metric" + name: string = "Untitled Metric" // metricType: string = "timeseries" metricType: string = "timeseries" metricOf: string = "sessionCount" diff --git a/frontend/app/svg/icons/bar-pencil.svg b/frontend/app/svg/icons/bar-pencil.svg new file mode 100644 index 000000000..1b9916e25 --- /dev/null +++ b/frontend/app/svg/icons/bar-pencil.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/frontend/app/svg/icons/grid-check.svg b/frontend/app/svg/icons/grid-check.svg new file mode 100644 index 000000000..3e899f840 --- /dev/null +++ b/frontend/app/svg/icons/grid-check.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/app/theme/colors.js b/frontend/app/theme/colors.js index 51be4289d..297e044bd 100644 --- a/frontend/app/theme/colors.js +++ b/frontend/app/theme/colors.js @@ -43,5 +43,18 @@ module.exports = { 'gray-light-shade': '#EEEEEE', 'primary': '#3490dc', 'transparent': 'transparent', + }, + + // actual theme colors - use this for new components + figmaColors: { + 'accent-secondary': 'rgba(62, 170, 175, 1)', + 'main': 'rgba(57, 78, 255, 1)', + 'primary-outlined-hover-background': 'rgba(62, 170, 175, 0.08)', + 'primary-outlined-resting-border': 'rgba(62, 170, 175, 0.5)', + 'secondary-outlined-hover-background': 'rgba(63, 81, 181, 0.08)', + 'secondary-outlined-resting-border': 'rgba(63, 81, 181, 0.5)', + 'text-disabled': 'rgba(0,0,0, 0.38)', + 'text-primary': 'rgba(0,0,0, 0.87)', + 'outlined-border': 'rgba(0,0,0, 0.23)', } } From 3cbd5e1154767237b0722055893f03af6b924702 Mon Sep 17 00:00:00 2001 From: sylenien Date: Thu, 11 Aug 2022 16:09:25 +0200 Subject: [PATCH 096/168] fix(player): fix shadow clipping --- .../components/DashboardWidgetGrid/AddMetricContainer.tsx | 2 +- .../components/DashboardWidgetGrid/AddPredefinedMetric.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx index 7cba9ac52..d3caf987a 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx @@ -79,7 +79,7 @@ function AddMetricContainer({ siteId }: any) { ); }; return ( -
+
{activeCategory && From c9338fd1160e7f134a834e86736600cf38bc70d2 Mon Sep 17 00:00:00 2001 From: sylenien Date: Thu, 11 Aug 2022 16:40:40 +0200 Subject: [PATCH 097/168] fix(ui): fix grid rows height --- .../Dashboard/components/DashboardWidgetGrid/AddMetric.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx index af46de0c2..a07c57016 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx @@ -66,7 +66,7 @@ function AddMetric({ metrics, history, siteId, title, description }: IProps) { )}
-
+
{metrics ? metrics.map((metric: any) => ( Date: Thu, 11 Aug 2022 16:43:48 +0200 Subject: [PATCH 098/168] fix(ui): fix predef modal height --- .../components/DashboardWidgetGrid/AddPredefinedMetric.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx index 0e4b726d2..5be789a2d 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx @@ -99,7 +99,7 @@ function AddPredefinedMetric({ categories, history, siteId, title, description } )}
-
+
{activeCategory && From 901539f27d413af3e5df01ccf3b74248d2afe054 Mon Sep 17 00:00:00 2001 From: sylenien Date: Thu, 11 Aug 2022 17:50:10 +0200 Subject: [PATCH 099/168] fix(ui): fix colors for metric picker --- .../DashboardList/DashboardList.tsx | 2 +- .../DashboardList/DashboardListItem.tsx | 28 ++++++-------- .../DashboardView/DashboardView.module.css | 5 +++ .../DashboardView/DashboardView.tsx | 23 ++++++++++-- .../DashboardWidgetGrid/AddMetric.tsx | 19 +++------- .../AddMetricContainer.tsx | 37 ++++++++++++------- .../AddPredefinedMetric.tsx | 23 ++++-------- .../OutsideClickDetectingDiv.js | 8 ++-- frontend/app/styles/general.css | 5 ++- 9 files changed, 80 insertions(+), 70 deletions(-) create mode 100644 frontend/app/components/Dashboard/components/DashboardView/DashboardView.module.css diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index 466103dc8..4aa5a43e5 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -31,7 +31,7 @@ function DashboardList() { } >
-
+
Title
Visibility
Created
diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx index 1fffe9f30..057b22d9a 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx @@ -1,15 +1,15 @@ import React from 'react'; import { Icon } from 'UI'; import { connect } from 'react-redux'; -import{ IDashboard } from "App/mstore/types/dashboard"; +import { IDashboard } from 'App/mstore/types/dashboard'; import { checkForRecent } from 'App/date'; import { withSiteId, dashboardSelected } from 'App/routes'; import { useStore } from 'App/mstore'; import { withRouter, RouteComponentProps } from 'react-router-dom'; interface Props extends RouteComponentProps { - dashboard: IDashboard; - siteId: string; + dashboard: IDashboard; + siteId: string; } function DashboardListItem(props: Props) { @@ -22,34 +22,28 @@ function DashboardListItem(props: Props) { history.push(path); }; return ( - <> +
-
+
-
- {dashboard.name} -
+
{dashboard.name}
{/*
*/}
- + {dashboard.isPublic ? 'Team' : 'Private'}
-
- {checkForRecent(dashboard.createdAt, 'LLL dd, yyyy, hh:mm a')} -
+
{checkForRecent(dashboard.createdAt, 'LLL dd, yyyy, hh:mm a')}
-
- {dashboard.description} -
- + {dashboard.description ?
{dashboard.description}
: null} +
); } // @ts-ignore -export default connect(state => ({ siteId: state.getIn([ 'site', 'siteId' ]) }))(withRouter(DashboardListItem)) +export default connect((state) => ({ siteId: state.getIn(['site', 'siteId']) }))(withRouter(DashboardListItem)); diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.module.css b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.module.css new file mode 100644 index 000000000..42045607f --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.module.css @@ -0,0 +1,5 @@ +.tooltipContainer { + & > tippy-popper > tippy-tooltip { + padding: 0!important; + } +} diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index e75fe6823..8c6d7f54b 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -17,6 +17,10 @@ import DashboardOptions from '../DashboardOptions'; import SelectDateRange from 'Shared/SelectDateRange'; import { Tooltip } from 'react-tippy'; import Breadcrumb from 'Shared/Breadcrumb'; +import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; +import AddMetricContainer from '../DashboardWidgetGrid/AddMetricContainer'; +// @ts-ignore +import stl from './DashboardView.module.css'; interface IProps { siteId: string; @@ -27,6 +31,7 @@ interface IProps { type Props = IProps & RouteComponentProps; function DashboardView(props: Props) { + const [isTooltipShown, setTooltip] = React.useState(false); const { siteId, dashboardId } = props; const { dashboardStore } = useStore(); const { showModal } = useModal(); @@ -123,9 +128,21 @@ function DashboardView(props: Props) { onDoubleClick={() => onEdit(true)} className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer" actionButton={ - + setTooltip(false)}> + {/* @ts-ignore */} +
} + > + + + } />
diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx index a07c57016..472d98d0f 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx @@ -50,20 +50,11 @@ function AddMetric({ metrics, history, siteId, title, description }: IProps) {

{title}

{description}
- {title.includes('Custom') ? ( -
- - + Create new - -
- ) : ( -
- Don’t find the one you need? - - + Create custom metric - -
- )} + + +
diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx index d3caf987a..30f48f58e 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx @@ -12,39 +12,42 @@ interface AddMetricButtonProps { title: string; description: string; isPremade?: boolean; + isPopup?: boolean; onClick: () => void; } -function AddMetricButton({ iconName, title, description, onClick, isPremade }: AddMetricButtonProps) { +function AddMetricButton({ iconName, title, description, onClick, isPremade, isPopup }: AddMetricButtonProps) { return (
- + +
+
+
{title}
+
+ {description} +
-
{title}
-
{description}
); } -function AddMetricContainer({ siteId }: any) { +function AddMetricContainer({ siteId, isPopup }: any) { const { showModal } = useModal(); const [categories, setCategories] = React.useState[]>([]); const { dashboardStore } = useStore(); @@ -78,20 +81,26 @@ function AddMetricContainer({ siteId }: any) { { right: true } ); }; + + const classes = isPopup + ? 'bg-white border rounded p-4 grid grid-rows-2 gap-4' + : 'bg-white border border-dashed hover:!border-gray-medium rounded p-8 grid grid-cols-2 gap-8'; return ( -
+
); diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx index 5be789a2d..c27646b2f 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx @@ -83,23 +83,16 @@ function AddPredefinedMetric({ categories, history, siteId, title, description }

{title}

{description}
- {title.includes('Custom') ? ( -
- - + Create new - -
- ) : ( -
- Don’t find the one you need? - - + Create custom metric - -
- )} + +
+ Don’t find the one you need? + +
-
+
{activeCategory && diff --git a/frontend/app/components/shared/OutsideClickDetectingDiv/OutsideClickDetectingDiv.js b/frontend/app/components/shared/OutsideClickDetectingDiv/OutsideClickDetectingDiv.js index 954566480..a6ec97c40 100644 --- a/frontend/app/components/shared/OutsideClickDetectingDiv/OutsideClickDetectingDiv.js +++ b/frontend/app/components/shared/OutsideClickDetectingDiv/OutsideClickDetectingDiv.js @@ -29,8 +29,7 @@ function handleClickOutside(e) { document.addEventListener('click', handleClickOutside); - -export default React.memo(function OutsideClickDetectingDiv({ onClickOutside, children, ...props}) { +function OutsideClickDetectingDiv({ onClickOutside, children, ...props}) { const ref = useRef(null); useLayoutEffect(() => { function handleClickOutside(event) { @@ -44,7 +43,6 @@ export default React.memo(function OutsideClickDetectingDiv({ onClickOutside, ch }, [ ref ]); return
{children}
; -}); - - +} +export default React.memo(OutsideClickDetectingDiv); diff --git a/frontend/app/styles/general.css b/frontend/app/styles/general.css index f0b80a54d..dd84006a2 100644 --- a/frontend/app/styles/general.css +++ b/frontend/app/styles/general.css @@ -278,6 +278,9 @@ p { .tippy-tooltip.openreplay-theme .tippy-backdrop { background-color: $tealx; } +.tippy-tooltip[data-theme~='nopadding'], .nopadding-theme { + padding: 0!important +} @media print { .no-print { @@ -302,4 +305,4 @@ p { .recharts-legend-item-text { white-space: nowrap !important; -} \ No newline at end of file +} From 3136ce5a2baefef07e70d24938bfd2b18f25ec26 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 12 Aug 2022 10:57:28 +0200 Subject: [PATCH 100/168] fix(ui): fix empty metric text, fix metric min height, fix rows etc --- .../BreakdownOfLoadedResources.js | 1 + .../Widgets/CallWithErrors/CallWithErrors.js | 1 + .../Widgets/CallsErrors4xx/CallsErrors4xx.js | 1 + .../Widgets/CallsErrors5xx/CallsErrors5xx.js | 1 + .../Dashboard/Widgets/CpuLoad/CpuLoad.js | 1 + .../Dashboard/Widgets/Crashes/Crashes.js | 1 + .../CustomMetricPieChart.tsx | 2 +- .../CustomMetricTable/CustomMetricTable.tsx | 4 ++-- .../CustomMetricWidget/CustomMetricWidget.tsx | 1 + .../Widgets/DomBuildingTime/DomBuildingTime.js | 2 ++ .../Widgets/ErrorsByOrigin/ErrorsByOrigin.js | 1 + .../Widgets/ErrorsByType/ErrorsByType.js | 1 + .../Widgets/ErrorsPerDomain/ErrorsPerDomain.js | 1 + .../components/Dashboard/Widgets/FPS/FPS.js | 1 + .../LastFrustrations/LastFrustrations.js | 1 + .../MemoryConsumption/MemoryConsumption.js | 1 + .../MostImpactfulErrors/MostImpactfulErrors.js | 1 + .../BreakdownOfLoadedResources.tsx | 3 ++- .../PredefinedWidgets/CPULoad/CPULoad.tsx | 3 ++- .../CallWithErrors/CallWithErrors.tsx | 1 + .../CallsErrors4xx/CallsErrors4xx.tsx | 3 ++- .../CallsErrors5xx/CallsErrors5xx.tsx | 3 ++- .../PredefinedWidgets/Crashes/Crashes.tsx | 3 ++- .../DomBuildingTime/DomBuildingTime.tsx | 3 ++- .../ErrorsByOrigin/ErrorsByOrigin.tsx | 3 ++- .../ErrorsByType/ErrorsByType.tsx | 3 ++- .../ErrorsPerDomain/ErrorsPerDomain.tsx | 3 ++- .../Widgets/PredefinedWidgets/FPS/FPS.tsx | 3 ++- .../MemoryConsumption/MemoryConsumption.tsx | 3 ++- .../ResourceLoadedVsResponseEnd.tsx | 1 + .../ResourceLoadedVsVisuallyComplete.tsx | 3 ++- .../ResourceLoadingTime.tsx | 3 ++- .../ResponseTime/ResponseTime.tsx | 3 ++- .../ResponseTimeDistribution.tsx | 3 ++- .../SessionsAffectedByJSErrors.tsx | 3 ++- .../SessionsImpactedBySlowRequests.tsx | 3 ++- .../SessionsPerBrowser/SessionsPerBrowser.tsx | 3 ++- .../SlowestDomains/SlowestDomains.tsx | 1 + .../SpeedIndexByLocation.tsx | 2 +- .../TimeToRender/TimeToRender.tsx | 3 ++- .../ResourceLoadedVsResponseEnd.js | 1 + .../ResourceLoadedVsVisuallyComplete.js | 1 + .../ResourceLoadingTime/ResourceLoadingTime.js | 2 ++ .../Widgets/ResponseTime/ResponseTime.js | 1 + .../ResponseTimeDistribution.js | 1 + .../SessionsAffectedByJSErrors.js | 1 + .../SessionsImpactedBySlowRequests.js | 1 + .../SessionsPerBrowser/SessionsPerBrowser.js | 3 ++- .../Widgets/SlowestDomains/SlowestDomains.js | 1 + .../Widgets/SlowestImages/SlowestImages.js | 1 + .../SlowestResources/SlowestResources.js | 1 + .../Widgets/TimeToRender/TimeToRender.js | 1 + .../Dashboard/Widgets/TopDomains/TopDomains.js | 1 + .../components/DashboardView/DashboardView.tsx | 18 ++++++++---------- .../DashboardWidgetGrid/AddMetric.tsx | 2 +- .../DashboardWidgetGrid/AddMetricContainer.tsx | 2 +- .../AddPredefinedMetric.tsx | 16 ++++++++-------- .../components/WidgetChart/WidgetChart.tsx | 2 +- .../app/components/Errors/Error/SideSection.js | 4 +--- .../Funnels/FunnelWidget/FunnelWidget.tsx | 2 +- .../components/Session/IOSPlayer/Crashes.js | 3 ++- .../app/components/Session/IOSPlayer/Logs.js | 3 ++- .../Session_/Exceptions/Exceptions.js | 3 ++- .../app/components/Session_/GraphQL/GraphQL.js | 1 + .../components/Session_/LongTasks/LongTasks.js | 1 + .../Session_/TimeTable/TimeTable.tsx | 2 +- frontend/app/dateRange.js | 2 +- frontend/app/types/app/period.js | 2 +- 68 files changed, 108 insertions(+), 55 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js b/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js index 10ab766a7..99ffc931b 100644 --- a/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js +++ b/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js @@ -30,6 +30,7 @@ export default class BreakdownOfLoadedResources extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js b/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js index 8bf8b90c2..4b3cadef6 100644 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js +++ b/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js @@ -64,6 +64,7 @@ export default class CallWithErrors extends React.PureComponent {
diff --git a/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js b/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js index a0e0d05a0..3c655da5f 100644 --- a/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js +++ b/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js @@ -37,6 +37,7 @@ export default class CallsErrors5xx extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js b/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js index 0579480fb..ee448dac2 100644 --- a/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js +++ b/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js @@ -27,6 +27,7 @@ export default class CpuLoad extends React.PureComponent {
diff --git a/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js b/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js index 16f96a07c..576c9c13f 100644 --- a/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js +++ b/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js @@ -30,6 +30,7 @@ export default class Crashes extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx index 76b8697c1..0765d7940 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx @@ -34,7 +34,7 @@ function CustomMetricPieChart(props: Props) { } } return ( - + - +
+
diff --git a/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js b/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js index 27cc682ff..970bfdbad 100644 --- a/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js +++ b/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js @@ -44,6 +44,7 @@ export default class DomBuildingTime extends React.PureComponent { return ( @@ -60,6 +61,7 @@ export default class DomBuildingTime extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js b/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js index 399908f74..d77bac5f4 100644 --- a/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js +++ b/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js @@ -29,6 +29,7 @@ export default class ErrorsByOrigin extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js b/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js index 3bca2406c..4421a3fbb 100644 --- a/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js +++ b/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js @@ -31,6 +31,7 @@ export default class ErrorsByType extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js index 68752c46b..11af3f7d7 100644 --- a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js +++ b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js @@ -15,6 +15,7 @@ export default class ErrorsPerDomain extends React.PureComponent {
diff --git a/frontend/app/components/Dashboard/Widgets/FPS/FPS.js b/frontend/app/components/Dashboard/Widgets/FPS/FPS.js index d91379188..843cea3db 100644 --- a/frontend/app/components/Dashboard/Widgets/FPS/FPS.js +++ b/frontend/app/components/Dashboard/Widgets/FPS/FPS.js @@ -26,6 +26,7 @@ export default class FPS extends React.PureComponent { return ( diff --git a/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js b/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js index 23f5731d9..fcd36d98e 100644 --- a/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js +++ b/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js @@ -12,6 +12,7 @@ export default class LastFeedbacks extends React.PureComponent { { sessions.map(({ diff --git a/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js b/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js index 839db02bc..14ed08d93 100644 --- a/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js +++ b/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js @@ -26,6 +26,7 @@ export default class MemoryConsumption extends React.PureComponent {
diff --git a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js index 6f2d300a1..a86e23220 100644 --- a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js +++ b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js @@ -48,6 +48,7 @@ export default class MostImpactfulErrors extends React.PureComponent {
@@ -46,4 +47,4 @@ function BreakdownOfLoadedResources(props: Props) { ); } -export default BreakdownOfLoadedResources; \ No newline at end of file +export default BreakdownOfLoadedResources; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx index 53356bf0d..311b481c6 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx @@ -19,6 +19,7 @@ function CPULoad(props: Props) { return ( @@ -54,4 +55,4 @@ function CPULoad(props: Props) { ); } -export default CPULoad; \ No newline at end of file +export default CPULoad; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx index 45673614f..d85512786 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx @@ -61,6 +61,7 @@ function CallWithErrors(props: Props) { diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx index afaaeb37d..ce62bedff 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx @@ -16,6 +16,7 @@ function CallsErrors4xx(props: Props) { return ( @@ -46,4 +47,4 @@ function CallsErrors4xx(props: Props) { ); } -export default CallsErrors4xx; \ No newline at end of file +export default CallsErrors4xx; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx index cc87d5c26..9792e9fce 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx @@ -16,6 +16,7 @@ function CallsErrors5xx(props: Props) { return ( @@ -46,4 +47,4 @@ function CallsErrors5xx(props: Props) { ); } -export default CallsErrors5xx; \ No newline at end of file +export default CallsErrors5xx; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx index 0fa472db9..69a7c2f6b 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx @@ -18,6 +18,7 @@ function Crashes(props: Props) { return ( @@ -52,4 +53,4 @@ function Crashes(props: Props) { ); } -export default Crashes; \ No newline at end of file +export default Crashes; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx index f14dc5cd7..7eea40a8a 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx @@ -33,6 +33,7 @@ function DomBuildingTime(props: Props) { return ( <> @@ -87,4 +88,4 @@ export default withRequest({ requestName: "fetchOptions", endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', method: 'GET' -})(DomBuildingTime) \ No newline at end of file +})(DomBuildingTime) diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx index f50859051..dded3aef0 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx @@ -17,6 +17,7 @@ function ErrorsByOrigin(props: Props) { return ( @@ -49,4 +50,4 @@ function ErrorsByOrigin(props: Props) { ); } -export default ErrorsByOrigin; \ No newline at end of file +export default ErrorsByOrigin; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx index 8d01941c8..f3d4a6f0b 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx @@ -16,6 +16,7 @@ function ErrorsByType(props: Props) { return ( @@ -48,4 +49,4 @@ function ErrorsByType(props: Props) { ); } -export default ErrorsByType; \ No newline at end of file +export default ErrorsByType; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx index fab8ced65..d58effb2b 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx @@ -17,6 +17,7 @@ function ErrorsPerDomain(props: Props) { size="small" show={ metric.data.chart.length === 0 } style={{ height: '240px'}} + title="No recordings found" >
{metric.data.chart.map((item, i) => @@ -34,4 +35,4 @@ function ErrorsPerDomain(props: Props) { ); } -export default ErrorsPerDomain; \ No newline at end of file +export default ErrorsPerDomain; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx index e246d3c3f..baea7156d 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx @@ -19,6 +19,7 @@ function FPS(props: Props) { return ( <> @@ -57,4 +58,4 @@ function FPS(props: Props) { ); } -export default FPS; \ No newline at end of file +export default FPS; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx index 80e1f4d9c..4889f05f7 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx @@ -22,6 +22,7 @@ function MemoryConsumption(props: Props) { <>
@@ -60,4 +61,4 @@ function MemoryConsumption(props: Props) { ); } -export default MemoryConsumption; \ No newline at end of file +export default MemoryConsumption; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.tsx index 4bd0c2b52..74ae4c997 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.tsx @@ -17,6 +17,7 @@ function ResourceLoadedVsResponseEnd(props: Props) { <>
@@ -119,4 +120,4 @@ export default withRequest({ requestName: "fetchOptions", endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', method: 'GET' -})(ResourceLoadingTime) \ No newline at end of file +})(ResourceLoadingTime) diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx index 0d6587386..6ed001ff1 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx @@ -34,6 +34,7 @@ function ResponseTime(props: Props) { return ( <> @@ -88,4 +89,4 @@ export default withRequest({ requestName: "fetchOptions", endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', method: 'GET' -})(ResponseTime) \ No newline at end of file +})(ResponseTime) diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx index 5190157ae..8021b9f7d 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx @@ -49,6 +49,7 @@ function ResponseTimeDistribution(props: Props) { return ( @@ -125,4 +126,4 @@ function ResponseTimeDistribution(props: Props) { ); } -export default ResponseTimeDistribution; \ No newline at end of file +export default ResponseTimeDistribution; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx index 55434e2a9..495ac5126 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx @@ -15,6 +15,7 @@ function SessionsAffectedByJSErrors(props: Props) { const { data, metric } = props; return ( @@ -52,4 +53,4 @@ function SessionsImpactedBySlowRequests(props: Props) { ); } -export default SessionsImpactedBySlowRequests; \ No newline at end of file +export default SessionsImpactedBySlowRequests; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/SessionsPerBrowser.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/SessionsPerBrowser.tsx index 6b155364d..48db4f67c 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/SessionsPerBrowser.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/SessionsPerBrowser.tsx @@ -19,6 +19,7 @@ function SessionsPerBrowser(props: Props) { return (
@@ -38,4 +39,4 @@ function SessionsPerBrowser(props: Props) { ); } -export default SessionsPerBrowser; \ No newline at end of file +export default SessionsPerBrowser; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx index c6adbeff6..1291305ed 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx @@ -16,6 +16,7 @@ function SlowestDomains(props: Props) { size="small" show={ metric.data.chart.length === 0 } style={{ maxHeight: '240px' }} + title="No recordings found" >
{metric.data.chart.map((item, i) => diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx index e87a66b24..6661623c2 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx @@ -63,7 +63,7 @@ function SpeedIndexByLocation(props: Props) { }; return ( - +
diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx index 7fceb853d..94334ee5d 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx @@ -35,6 +35,7 @@ function TimeToRender(props: Props) { <>
@@ -88,4 +89,4 @@ export default withRequest({ requestName: "fetchOptions", endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', method: 'GET' -})(TimeToRender) \ No newline at end of file +})(TimeToRender) diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js index c30375aa7..d04a5cef5 100644 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js +++ b/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js @@ -28,6 +28,7 @@ export default class ResourceLoadedVsResponseEnd extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js index 262312f1b..8f95a3479 100644 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js +++ b/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js @@ -66,6 +66,7 @@ export default class ResourceLoadingTime extends React.PureComponent {
@@ -96,6 +97,7 @@ export default class ResourceLoadingTime extends React.PureComponent {
diff --git a/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js b/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js index 747247872..057122195 100644 --- a/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js +++ b/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js @@ -36,6 +36,7 @@ export default class SessionsAffectedByJSErrors extends React.PureComponent {
{data.chart.map((item, i) => @@ -40,4 +41,4 @@ export default class SessionsPerBrowser extends React.PureComponent { ); } -} \ 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 index b31b93891..9f85ae412 100644 --- a/frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js +++ b/frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js @@ -16,6 +16,7 @@ export default class ResponseTime extends React.PureComponent {
{data.partition && data.partition.map((item, i) => diff --git a/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js b/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js index 87cf5478f..7bfc0cfd9 100644 --- a/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js +++ b/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js @@ -41,6 +41,7 @@ export default class SlowestImages extends React.PureComponent {
diff --git a/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js b/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js index 956174cba..1ac489588 100644 --- a/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js +++ b/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js @@ -59,6 +59,7 @@ export default class TimeToRender extends React.PureComponent { -
+
onEdit(true)} className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer" actionButton={ - setTooltip(false)}> - {/* @ts-ignore */} + /* @ts-ignore */
} > - - } />
@@ -166,7 +164,7 @@ function DashboardView(props: Props) {
-
+
{/* @ts-ignore */}

-
+
{metrics ? metrics.map((metric: any) => ( {description}
-
- Don’t find the one you need? - -
+

-
+
{activeCategory && categories.map((category) => ( {activeCategory && activeCategory.widgets.map((metric: any) => ( diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 31e73fda3..7a6df145f 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -179,7 +179,7 @@ function WidgetChart(props: Props) { } return ( - {renderChart()} +
{renderChart()}
); } diff --git a/frontend/app/components/Errors/Error/SideSection.js b/frontend/app/components/Errors/Error/SideSection.js index 6c1702c78..da6e5803b 100644 --- a/frontend/app/components/Errors/Error/SideSection.js +++ b/frontend/app/components/Errors/Error/SideSection.js @@ -87,7 +87,7 @@ export default class SideSection extends React.PureComponent {

Overview

( - +
{ !isWidget && ( stages.map((filter: any, index: any) => ( diff --git a/frontend/app/components/Session/IOSPlayer/Crashes.js b/frontend/app/components/Session/IOSPlayer/Crashes.js index 9fe3d9ad0..015dece96 100644 --- a/frontend/app/components/Session/IOSPlayer/Crashes.js +++ b/frontend/app/components/Session/IOSPlayer/Crashes.js @@ -31,6 +31,7 @@ function Crashes({ player }) { @@ -48,4 +49,4 @@ function Crashes({ player }) { ); } -export default observer(Crashes); \ No newline at end of file +export default observer(Crashes); diff --git a/frontend/app/components/Session/IOSPlayer/Logs.js b/frontend/app/components/Session/IOSPlayer/Logs.js index 2469a7e1a..e9fe033d7 100644 --- a/frontend/app/components/Session/IOSPlayer/Logs.js +++ b/frontend/app/components/Session/IOSPlayer/Logs.js @@ -45,6 +45,7 @@ function Logs({ player }) { { filtered.map(log => @@ -57,4 +58,4 @@ function Logs({ player }) { ); } -export default observer(Logs); \ No newline at end of file +export default observer(Logs); diff --git a/frontend/app/components/Session_/Exceptions/Exceptions.js b/frontend/app/components/Session_/Exceptions/Exceptions.js index e08145ad7..b2d57457a 100644 --- a/frontend/app/components/Session_/Exceptions/Exceptions.js +++ b/frontend/app/components/Session_/Exceptions/Exceptions.js @@ -97,6 +97,7 @@ export default class Exceptions extends React.PureComponent { { filtered.map(e => ( @@ -115,4 +116,4 @@ export default class Exceptions extends React.PureComponent { ); } -} \ No newline at end of file +} diff --git a/frontend/app/components/Session_/GraphQL/GraphQL.js b/frontend/app/components/Session_/GraphQL/GraphQL.js index 2d3a112e4..cb7b46b7f 100644 --- a/frontend/app/components/Session_/GraphQL/GraphQL.js +++ b/frontend/app/components/Session_/GraphQL/GraphQL.js @@ -100,6 +100,7 @@ export default class GraphQL extends React.PureComponent { {
- +
{timeColumns.map((_, index) => ( diff --git a/frontend/app/dateRange.js b/frontend/app/dateRange.js index 70f674665..77f2c3d2e 100644 --- a/frontend/app/dateRange.js +++ b/frontend/app/dateRange.js @@ -9,7 +9,7 @@ export const CUSTOM_RANGE = "CUSTOM_RANGE"; const DATE_RANGE_LABELS = { // LAST_30_MINUTES: '30 Minutes', // TODAY: 'Today', - LAST_24_HOURS: "Last 24 Hours", + LAST_24_HOURS: "Past 24 Hours", // YESTERDAY: 'Yesterday', LAST_7_DAYS: "Past 7 Days", LAST_30_DAYS: "Past 30 Days", diff --git a/frontend/app/types/app/period.js b/frontend/app/types/app/period.js index da69519b0..8ab9869e0 100644 --- a/frontend/app/types/app/period.js +++ b/frontend/app/types/app/period.js @@ -18,7 +18,7 @@ const RANGE_LABELS = { [LAST_30_MINUTES]: "Last 30 Minutes", [TODAY]: "Today", [YESTERDAY]: "Yesterday", - [LAST_24_HOURS]: "Last 24 Hours", + [LAST_24_HOURS]: "Past 24 Hours", [LAST_7_DAYS]: "Last 7 Days", [LAST_30_DAYS]: "Last 30 Days", [THIS_MONTH]: "This Month", From db16f2b89bd8c90e04872ff1e771d8cad68f7082 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 12 Aug 2022 11:25:13 +0200 Subject: [PATCH 101/168] fix(ui): metric rows, paddings, dashboard nav link --- .../DashboardList/DashboardList.tsx | 2 +- .../DashboardList/DashboardListItem.tsx | 4 +- .../DashboardList/DashboardsView.tsx | 4 +- .../MetricListItem/MetricListItem.tsx | 43 ++++++++----------- .../components/MetricsList/MetricsList.tsx | 4 +- .../components/MetricsView/MetricsView.tsx | 6 +-- frontend/app/components/Header/Header.js | 5 +++ 7 files changed, 32 insertions(+), 36 deletions(-) diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index 4aa5a43e5..466103dc8 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -31,7 +31,7 @@ function DashboardList() { } >
-
+
Title
Visibility
Created
diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx index 057b22d9a..61aeb15b9 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx @@ -22,14 +22,14 @@ function DashboardListItem(props: Props) { history.push(path); }; return ( -
+
-
{dashboard.name}
+
{dashboard.name}
{/*
*/} diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx index 75b4aaf4e..c9b98a745 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx @@ -21,10 +21,10 @@ function DashboardsView({ history, siteId }: { history: any, siteId: string }) { } return ( -
+
- +
diff --git a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx index 18f580851..23bcedb2e 100644 --- a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx +++ b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx @@ -1,27 +1,13 @@ import React from 'react'; -import { Icon, Link, Popup } from 'UI'; +import { Icon, Link } from 'UI'; import { checkForRecent } from 'App/date'; import { Tooltip } from 'react-tippy' +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { withSiteId } from 'App/routes'; -interface Props { +interface Props extends RouteComponentProps { metric: any; -} - -function DashboardLink({ dashboards}: any) { - return ( - dashboards.map((dashboard: any) => ( - - -
-
- -
- {dashboard.name} -
- -
- )) - ); + siteId: string; } function MetricTypeIcon({ type }: any) { @@ -49,18 +35,23 @@ function MetricTypeIcon({ type }: any) { ) } -function MetricListItem(props: Props) { - const { metric } = props; - + +function MetricListItem(props: Props) { + const { metric, history, siteId } = props; + + const onItemClick = () => { + const path = withSiteId(`/metrics/${metric.metricId}`, siteId); + history.push(path); + }; return ( -
+
- +
{metric.name} - +
{metric.owner}
@@ -75,4 +66,4 @@ function MetricListItem(props: Props) { ); } -export default MetricListItem; +export default withRouter(MetricListItem); diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index 8c453688e..dea6c4a4f 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -8,7 +8,7 @@ import { sliceListPerPage } from 'App/utils'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import { IWidget } from 'App/mstore/types/widget'; -function MetricsList() { +function MetricsList({ siteId }: { siteId: string }) { const { metricStore } = useStore(); const metrics = useObserver(() => metricStore.metrics); const metricsSearch = useObserver(() => metricStore.metricsSearch); @@ -46,7 +46,7 @@ function MetricsList() { {sliceListPerPage(list, metricStore.page - 1, metricStore.pageSize).map((metric: any) => ( - + ))}
diff --git a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx index afe0dd337..85cefe70d 100644 --- a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx +++ b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx @@ -9,14 +9,14 @@ import { useObserver } from 'mobx-react-lite'; interface Props{ siteId: number; } -function MetricsView(props: Props) { +function MetricsView({ siteId }: Props) { const { metricStore } = useStore(); React.useEffect(() => { metricStore.fetchList(); }, []); return useObserver(() => ( -
+
@@ -30,7 +30,7 @@ function MetricsView(props: Props) { Create custom Metrics to capture key interactions and track KPIs.
- +
)); } diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 1b5b97c22..f06c7321e 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -4,6 +4,7 @@ import { NavLink, withRouter } from 'react-router-dom'; import cn from 'classnames'; import { sessions, + metrics, assist, client, dashboard, @@ -27,6 +28,7 @@ import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; const DASHBOARD_PATH = dashboard(); +const METRICS_PATH = metrics(); const SESSIONS_PATH = sessions(); const ASSIST_PATH = assist(); const CLIENT_PATH = client(CLIENT_DEFAULT_TAB); @@ -94,6 +96,9 @@ const Header = (props) => { to={ withSiteId(DASHBOARD_PATH, siteId) } className={ styles.nav } activeClassName={ styles.active } + isActive={ (_, location) => { + return location.pathname.includes(DASHBOARD_PATH) || location.pathname.includes(METRICS_PATH); + }} > { 'Dashboards' } From 2d87da1b996da4ec9e9650a51deb4fbde913d961 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 12 Aug 2022 11:54:14 +0200 Subject: [PATCH 102/168] fix(ui): fix for breadcrumb hover, empty metric icon etc --- .../CustomMetricTableErrors.tsx | 10 ++++------ .../components/DashboardList/DashboardList.tsx | 7 +++---- .../Dashboard/components/MetricsList/MetricsList.tsx | 9 ++++----- .../components/WidgetPreview/WidgetPreview.tsx | 2 +- .../app/components/shared/Breadcrumb/Breadcrumb.tsx | 2 +- frontend/app/components/ui/SVG.tsx | 4 +++- frontend/app/svg/icons/no-dashboard.svg | 12 ++++++++++++ frontend/app/svg/icons/no-metrics.svg | 12 ++++++++++++ 8 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 frontend/app/svg/icons/no-dashboard.svg create mode 100644 frontend/app/svg/icons/no-metrics.svg diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx index 55fcb29eb..1add0810c 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx @@ -1,11 +1,10 @@ import React, { useEffect } from "react"; -import { Pagination, NoContent } from "UI"; +import { Pagination, NoContent, Icon } from "UI"; import ErrorListItem from "App/components/Dashboard/components/Errors/ErrorListItem"; import { withRouter, RouteComponentProps } from "react-router-dom"; import { useModal } from "App/components/Modal"; import ErrorDetailsModal from "App/components/Dashboard/components/Errors/ErrorDetailsModal"; import { useStore } from "App/mstore"; -import { overPastString } from "App/dateRange"; interface Props { metric: any; data: any; @@ -18,7 +17,6 @@ function CustomMetricTableErrors(props: RouteComponentProps & Props) { const errorId = new URLSearchParams(props.location.search).get("errorId"); const { showModal, hideModal } = useModal(); const { dashboardStore } = useStore(); - const period = dashboardStore.period; const onErrorClick = (e: any, error: any) => { e.stopPropagation(); @@ -46,9 +44,9 @@ function CustomMetricTableErrors(props: RouteComponentProps & Props) { return ( No errors found in the selected time range
} + show={!data.errors || data.errors.length === 0} + size="small" >
{data.errors && diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index 466103dc8..6498bcf6a 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -1,10 +1,9 @@ import { observer } from 'mobx-react-lite'; import React from 'react'; -import { NoContent, Pagination } from 'UI'; +import { NoContent, Pagination, Icon } from 'UI'; import { useStore } from 'App/mstore'; import { filterList } from 'App/utils'; import { sliceListPerPage } from 'App/utils'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import DashboardListItem from './DashboardListItem'; function DashboardList() { @@ -25,8 +24,8 @@ function DashboardList() { show={lenth === 0} title={
- -
No data available.
+ +
You haven't created any dashboards yet
} > diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index dea6c4a4f..438ea09ce 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -1,11 +1,10 @@ import { useObserver } from 'mobx-react-lite'; import React, { useEffect } from 'react'; -import { NoContent, Pagination } from 'UI'; +import { NoContent, Pagination, Icon } from 'UI'; import { useStore } from 'App/mstore'; -import { getRE, filterList } from 'App/utils'; +import { filterList } from 'App/utils'; import MetricListItem from '../MetricListItem'; import { sliceListPerPage } from 'App/utils'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import { IWidget } from 'App/mstore/types/widget'; function MetricsList({ siteId }: { siteId: string }) { @@ -31,8 +30,8 @@ function MetricsList({ siteId }: { siteId: string }) { show={lenth === 0} title={
- -
No data available.
+ +
You haven't created any metrics yet
} > diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx index 3cc02fd76..9e5465382 100644 --- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx @@ -61,7 +61,7 @@ function WidgetPreview(props: Props) { return useObserver(() => ( <>
-
+

{getWidgetTitle()}

diff --git a/frontend/app/components/shared/Breadcrumb/Breadcrumb.tsx b/frontend/app/components/shared/Breadcrumb/Breadcrumb.tsx index 35c0ea45f..7a48dedf5 100644 --- a/frontend/app/components/shared/Breadcrumb/Breadcrumb.tsx +++ b/frontend/app/components/shared/Breadcrumb/Breadcrumb.tsx @@ -16,7 +16,7 @@ function Breadcrumb(props: Props) { ); } return ( -
+
{index === 0 && } {item.label} diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index 4a6582789..732c7d403 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -234,7 +234,7 @@ const SVG = (props: Props) => { case 'id-card': return ; case 'image': return ; case 'info-circle-fill': return ; - case 'info-circle': return ; + case 'info-circle': return ; case 'info-square': return ; case 'info': return ; case 'inspect': return ; @@ -285,7 +285,9 @@ const SVG = (props: Props) => { case 'mobile': return ; case 'mouse-alt': return ; case 'next1': return ; + case 'no-dashboard': return ; case 'no-metrics-chart': return ; + case 'no-metrics': return ; case 'os/android': return ; case 'os/chrome_os': return ; case 'os/fedora': return ; diff --git a/frontend/app/svg/icons/no-dashboard.svg b/frontend/app/svg/icons/no-dashboard.svg new file mode 100644 index 000000000..2e849b192 --- /dev/null +++ b/frontend/app/svg/icons/no-dashboard.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/frontend/app/svg/icons/no-metrics.svg b/frontend/app/svg/icons/no-metrics.svg new file mode 100644 index 000000000..6809e0f79 --- /dev/null +++ b/frontend/app/svg/icons/no-metrics.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + From ee065619ba856625ab3411515a6ecc5ffba013e1 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 12 Aug 2022 12:25:41 +0200 Subject: [PATCH 103/168] fix(ui): fix metric creation title, sessions list --- .../WidgetPreview/WidgetPreview.tsx | 24 +++---------------- .../WidgetSessions/WidgetSessions.tsx | 4 ++-- .../components/WidgetView/WidgetView.tsx | 2 +- .../WidgetWrapper/WidgetWrapper.tsx | 3 ++- 4 files changed, 8 insertions(+), 25 deletions(-) diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx index 9e5465382..98bb11a1b 100644 --- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx @@ -4,7 +4,6 @@ import WidgetWrapper from '../WidgetWrapper'; import { useStore } from 'App/mstore'; import { SegmentSelection, Button, Icon } from 'UI'; import { useObserver } from 'mobx-react-lite'; -import SelectDateRange from 'Shared/SelectDateRange'; import { FilterKey } from 'Types/filter/filterType'; import WidgetDateRange from '../WidgetDateRange/WidgetDateRange'; // import Period, { LAST_24_HOURS, LAST_30_DAYS } from 'Types/app/period'; @@ -12,6 +11,7 @@ import DashboardSelectionModal from '../DashboardSelectionModal/DashboardSelecti interface Props { className?: string; + name: string; } function WidgetPreview(props: Props) { const [showDashboardSelectionModal, setShowDashboardSelectionModal] = React.useState(false); @@ -38,24 +38,6 @@ function WidgetPreview(props: Props) { // }) // } - const getWidgetTitle = () => { - if (isTimeSeries) { - return 'Time Series'; - } else if (isTable) { - if (metric.metricOf === FilterKey.SESSIONS) { - // return 'Table of Sessions'; - return
Sessions {metric.data.total}
; - } else if (metric.metricOf === FilterKey.ERRORS) { - // return 'Table of Errors'; - return
Errors {metric.data.total}
; - } else { - return 'Table'; - } - } else if (metric.metricType === 'funnel') { - return 'Funnel'; - } - } - const canAddToDashboard = metric.exists() && dashboards.length > 0; return useObserver(() => ( @@ -63,7 +45,7 @@ function WidgetPreview(props: Props) {

- {getWidgetTitle()} + {props.name}

{isTimeSeries && ( @@ -120,7 +102,7 @@ function WidgetPreview(props: Props) {
- +
{ canAddToDashboard && ( diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index e3de553d2..d1e10d10a 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -62,7 +62,7 @@ function WidgetSessions(props: Props) { }, [filter.startTimestamp, filter.endTimestamp, filter.filters, depsString, metricStore.sessionsPage]); return useObserver(() => ( -
+

Sessions

@@ -80,7 +80,7 @@ function WidgetSessions(props: Props) { )}
-
+
}
- + {widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && ( <> {(widget.metricType === 'table' || widget.metricType === 'timeseries') && } diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index a9b6e2046..6354af350 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -25,6 +25,7 @@ interface Props { history?: any onClick?: () => void; isWidget?: boolean; + hideName?: boolean; } function WidgetWrapper(props: Props & RouteComponentProps) { const { dashboardStore } = useStore(); @@ -112,7 +113,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) {
-
{widget.name}
+ {!props.hideName ?
{widget.name}
: null} {isWidget && (
{!isPredefined && isTimeSeries && ( From 5bf2d1008ba0cd6f222fe2a08d92b00ca67e2e14 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 12 Aug 2022 13:20:34 +0200 Subject: [PATCH 104/168] fix(ui): fix Icon typing --- frontend/app/components/ui/Icon/Icon.tsx | 6 +++--- frontend/app/components/ui/SVG.tsx | 7 ++++++- frontend/app/svg/icons/funnel-new.svg | 3 +++ frontend/app/svg/icons/graph-up.svg | 3 +++ frontend/app/svg/icons/table-new.svg | 3 +++ frontend/scripts/icons.ts | 4 +++- 6 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 frontend/app/svg/icons/funnel-new.svg create mode 100644 frontend/app/svg/icons/graph-up.svg create mode 100644 frontend/app/svg/icons/table-new.svg diff --git a/frontend/app/components/ui/Icon/Icon.tsx b/frontend/app/components/ui/Icon/Icon.tsx index 745d6412d..74e91e1ed 100644 --- a/frontend/app/components/ui/Icon/Icon.tsx +++ b/frontend/app/components/ui/Icon/Icon.tsx @@ -1,10 +1,10 @@ import React from 'react'; import cn from 'classnames'; -import SVG from 'UI/SVG'; +import SVG, { IconNames } from 'UI/SVG'; import styles from './icon.module.css'; -interface IProps { - name: string +interface IProps { + name: IconNames size?: number | string height?: number width?: number diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index 732c7d403..1ed4ed218 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -1,8 +1,10 @@ import React from 'react'; +export type IconNames = 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-clockwise' | 'arrow-down' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | '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-plus' | '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' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-square-quote' | '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' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | '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' | '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' | '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' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-3x3' | 'grid-check' | '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/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-ul' | 'list' | 'lock-alt' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | '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' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus' | 'prev1' | 'puzzle-piece' | 'question-circle' | 'quote-left' | 'quote-right' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | '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' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; + interface Props { - name: string; + name: IconNames; size?: number | string; width?: number | string; height?: number | string; @@ -217,10 +219,12 @@ const SVG = (props: Props) => { case 'funnel/patch-exclamation-fill': return ; case 'funnel/sd-card': return ; case 'funnel-fill': return ; + case 'funnel-new': return ; case 'funnel': return ; case 'geo-alt-fill-custom': return ; case 'github': return ; case 'graph-up-arrow': return ; + case 'graph-up': return ; case 'grid-3x3': return ; case 'grid-check': return ; case 'grip-horizontal': return ; @@ -347,6 +351,7 @@ const SVG = (props: Props) => { case 'stopwatch': return ; case 'store': return ; case 'sync-alt': return ; + case 'table-new': return ; case 'table': return ; case 'tablet-android': return ; case 'tachometer-slow': return ; diff --git a/frontend/app/svg/icons/funnel-new.svg b/frontend/app/svg/icons/funnel-new.svg new file mode 100644 index 000000000..36cb3c15c --- /dev/null +++ b/frontend/app/svg/icons/funnel-new.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/app/svg/icons/graph-up.svg b/frontend/app/svg/icons/graph-up.svg new file mode 100644 index 000000000..7b12d457e --- /dev/null +++ b/frontend/app/svg/icons/graph-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/app/svg/icons/table-new.svg b/frontend/app/svg/icons/table-new.svg new file mode 100644 index 000000000..702e7a05b --- /dev/null +++ b/frontend/app/svg/icons/table-new.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/scripts/icons.ts b/frontend/scripts/icons.ts index a89a8c3a3..4ad35692d 100644 --- a/frontend/scripts/icons.ts +++ b/frontend/scripts/icons.ts @@ -68,8 +68,10 @@ const plugins = (removeFill = true) => { fs.writeFileSync(`${UI_DIRNAME}/SVG.tsx`, ` import React from 'react'; +export type IconNames = ${icons.map(icon => "'"+ icon.slice(0, -4) + "'").join(' | ')}; + interface Props { - name: string; + name: IconNames; size?: number | string; width?: number | string; height?: number | string; From 50f82c2fd98a655f8569907aac88a9c45e8c71e1 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 12 Aug 2022 16:17:44 +0200 Subject: [PATCH 105/168] fix(player): fix filereader buffer --- .../app/player/MessageDistributor/MessageDistributor.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/app/player/MessageDistributor/MessageDistributor.ts b/frontend/app/player/MessageDistributor/MessageDistributor.ts index cd48f54da..2d73081fb 100644 --- a/frontend/app/player/MessageDistributor/MessageDistributor.ts +++ b/frontend/app/player/MessageDistributor/MessageDistributor.ts @@ -92,6 +92,7 @@ export default class MessageDistributor extends StatedScreen { private readonly lists = initLists(); private activityManager: ActivityManager | null = null; + private fileReader: MFileReader; private sessionStart: number; private navigationStartOffset: number = 0; @@ -151,11 +152,13 @@ export default class MessageDistributor extends StatedScreen { private readAndDistributeMessages(byteArray: Uint8Array, onReadCb?: (msg: Message) => void) { const msgs: Array = [] - const reader = new MFileReader(new Uint8Array(), this.sessionStart) + if (!this.fileReader) { + this.fileReader = new MFileReader(new Uint8Array(), this.sessionStart) + } - reader.append(byteArray) + this.fileReader.append(byteArray) let next: ReturnType - while (next = reader.next()) { + while (next = this.fileReader.next()) { const [msg, index] = next this.distributeMessage(msg, index) msgs.push(msg) From a0a09f293e061cf3ec8ecc1935f23f291afd155a Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 12 Aug 2022 17:53:24 +0200 Subject: [PATCH 106/168] fix(ui): fix button focusing and nocontent icon size --- frontend/.prettierrc | 4 +- .../DashboardList/DashboardList.tsx | 4 +- .../components/MetricsList/MetricsList.tsx | 4 +- .../Session_/Player/Controls/Controls.js | 5 +- .../Controls/components/PlayerControls.tsx | 128 +++++++++++------- 5 files changed, 86 insertions(+), 59 deletions(-) diff --git a/frontend/.prettierrc b/frontend/.prettierrc index 761a3e639..4c38cc4c4 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -1,6 +1,6 @@ { - "tabWidth": 4, + "tabWidth": 2, "useTabs": false, - "printWidth": 150, + "printWidth": 100, "singleQuote": true } diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index 6498bcf6a..4a0049df3 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -24,8 +24,8 @@ function DashboardList() { show={lenth === 0} title={
- -
You haven't created any dashboards yet
+ +
You haven't created any dashboards yet
} > diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index 438ea09ce..e64026822 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -30,8 +30,8 @@ function MetricsList({ siteId }: { siteId: string }) { show={lenth === 0} title={
- -
You haven't created any metrics yet
+ +
You haven't created any metrics yet
} > diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js index 73371c6e1..951e2eead 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ b/frontend/app/components/Session_/Player/Controls/Controls.js @@ -8,7 +8,6 @@ import { selectStorageListNow, } from 'Player/store'; import LiveTag from 'Shared/LiveTag'; -import { session as sessionRoute, withSiteId } from 'App/routes'; import { toggleTimetravel, jumpToLive, @@ -31,7 +30,7 @@ import { EXCEPTIONS, INSPECTOR, } from 'Duck/components/player'; -import { ReduxTime, AssistDuration } from './Time'; +import { AssistDuration } from './Time'; import Timeline from './Timeline'; import ControlButton from './ControlButton'; import PlayerControls from './components/PlayerControls' @@ -121,7 +120,6 @@ function getStorageName(type) { toggleBottomBlock, }) export default class Controls extends React.Component { - componentDidMount() { document.addEventListener('keydown', this.onKeyDown); } @@ -318,6 +316,7 @@ export default class Controls extends React.Component { toggleSkip={toggleSkip} playButton={this.renderPlayBtn()} controlIcon={this.controlIcon} + ref={this.speedRef} /> )} diff --git a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx index be3ac24b3..1361c6c7b 100644 --- a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx @@ -1,10 +1,10 @@ -import React from 'react' +import React from 'react'; import { Tooltip } from 'react-tippy'; import { ReduxTime } from '../Time'; import { Icon } from 'UI'; import cn from 'classnames'; // @ts-ignore -import styles from '../controls.module.css' +import styles from '../controls.module.css'; interface Props { live: boolean; @@ -16,22 +16,51 @@ interface Props { forthTenSeconds: () => void; toggleSpeed: () => void; toggleSkip: () => void; - controlIcon: (icon: string, size: number, action: () => void, isBackwards: boolean, additionalClasses: string) => JSX.Element; + controlIcon: ( + icon: string, + size: number, + action: () => void, + isBackwards: boolean, + additionalClasses: string + ) => JSX.Element; } function PlayerControls(props: Props) { - const { - live, - skip, - speed, - disabled, - playButton, - backTenSeconds, - forthTenSeconds, - toggleSpeed, - toggleSkip, - controlIcon + const { + live, + skip, + speed, + disabled, + playButton, + backTenSeconds, + forthTenSeconds, + toggleSpeed, + toggleSkip, + controlIcon, } = props; + + const speedRef = React.useRef(null); + const arrowBackRef = React.useRef(null); + const arrowForwardRef = React.useRef(null); + + React.useEffect(() => { + const handleKeyboard = (e: KeyboardEvent) => { + if (e.key === 'ArrowRight') { + arrowForwardRef.current.focus(); + } + if (e.key === 'ArrowLeft') { + arrowBackRef.current.focus(); + } + if (e.key === 'ArrowDown') { + speedRef.current.focus(); + } + if (e.key === 'ArrowUp') { + speedRef.current.focus(); + } + }; + document.addEventListener('keydown', handleKeyboard); + return () => document.removeEventListener('keydown', handleKeyboard); + }, [speedRef, arrowBackRef, arrowForwardRef]); return (
{playButton} @@ -47,46 +76,41 @@ function PlayerControls(props: Props) {
{/* @ts-ignore */} - - {controlIcon( - "skip-forward-fill", - 18, - backTenSeconds, - true, - 'hover:bg-active-blue-border color-main h-full flex items-center' - )} + + -
10s
+
+ 10s +
{/* @ts-ignore */} - - {controlIcon( - "skip-forward-fill", - 18, - forthTenSeconds, - false, - 'hover:bg-active-blue-border color-main h-full flex items-center' - )} + +
- {!live && -
+ {!live && ( +
{/* @ts-ignore */} - +
- } + )}
- ) + ); } export default PlayerControls; From c8bede6108abcc6f6069fb7866365fbf2d2f922a Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 16 Aug 2022 09:58:11 +0200 Subject: [PATCH 107/168] fix(ui): remove logs --- .../components/Assist/components/AssistActions/AssistActions.tsx | 1 - frontend/app/mstore/dashboardStore.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index 90afa772a..0e24e10f9 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -91,7 +91,6 @@ function AssistActions({ React.useEffect(() => { if (!onCall && isCallActive && agentIds) { - logger.log('joinig the party', agentIds) setPrestart(true); call(agentIds) } diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 8abef7ea8..617a54fa2 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -195,7 +195,6 @@ export default class DashboardStore implements IDashboardSotre { } updateKey(key: any, value: any) { - console.log(key, value) this[key] = value; } From c05a418a93aeff8e1c94b41c511c8ce82f2b3d77 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 16 Aug 2022 11:25:25 +0200 Subject: [PATCH 108/168] feat(ui): change metric type selector component --- .../components/WidgetForm/WidgetForm.tsx | 24 ++++++++++++++----- .../ui/SegmentSelection/SegmentSelection.js | 3 ++- .../segmentSelection.module.css | 20 +++++++++++++++- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index 40e80a7c9..685da85a5 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -3,7 +3,7 @@ import { metricTypes, metricOf, issueOptions } from 'App/constants/filterOptions import { FilterKey } from 'Types/filter/filterType'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; -import { Button, Icon } from 'UI' +import { Button, Icon, SegmentSelection } from 'UI' import FilterSeries from '../FilterSeries'; import { confirm, Popup } from 'UI'; import Select from 'Shared/Select' @@ -15,6 +15,12 @@ interface Props { onDelete: () => void; } +const metricIcons = { + timeseries: 'graph-up', + table: 'table', + funnel: 'funnel', +} + function WidgetForm(props: Props) { const { history, match: { params: { siteId, dashboardId } } } = props; @@ -64,13 +70,15 @@ function WidgetForm(props: Props) { metricStore.merge(obj); }; + const onSelect = (_: any, option: Record) => writeOption({ value: { value: option.value }, name: option.name}) + const onSave = () => { const wasCreating = !metric.exists() metricStore.save(metric, dashboardId) .then((metric: any) => { if (wasCreating) { if (parseInt(dashboardId) > 0) { - history.replace(withSiteId(dashboardMetricDetails(parseInt(dashboardId), metric.metricId), siteId)); + history.replace(withSiteId(dashboardMetricDetails(dashboardId, metric.metricId), siteId)); } else { history.replace(withSiteId(metricDetails(metric.metricId), siteId)); } @@ -93,11 +101,15 @@ function WidgetForm(props: Props) {
-
No errors found in the selected time range} + title={
No data for the selected time period
} show={!data.errors || data.errors.length === 0} size="small" > diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx index c5aa85e0f..ffb489b11 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx @@ -3,7 +3,7 @@ import React from "react"; import SessionItem from "Shared/SessionItem"; import { Pagination, NoContent } from "UI"; import { useStore } from "App/mstore"; -import { overPastString } from "App/dateRange"; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; interface Props { metric: any; @@ -26,7 +26,13 @@ function CustomMetricTableSessions(props: Props) { data.sessions.length === 0 } size="small" - title={`No sessions found ${overPastString(period)}`} + title={ +
+ +
+
No relevant sessions found for the selected time period.
+
+ } >
{data.sessions && From f7c0cf11fb4d978e9c605b8b0b6e193180b1cf56 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 14:49:20 +0200 Subject: [PATCH 111/168] feat(ui) - overview - toggle button and other optimizations --- frontend/app/components/Header/Header.js | 1 + frontend/app/components/Session/LivePlayer.js | 2 +- frontend/app/components/Session/WebPlayer.js | 2 +- .../Session_/EventsBlock/EventsBlock.js | 2 +- .../Session_/OverviewPanel/OverviewPanel.tsx | 92 +- .../components/EventRow/EventRow.tsx | 55 +- .../OverviewPanelContainer.tsx | 32 +- .../PerformanceGraph/PerformanceGraph.tsx | 83 ++ .../components/PerformanceGraph/index.ts | 1 + .../TimelinePointer/TimelinePointer.tsx | 4 +- .../TimelineScale/TimelineScale.tsx | 60 +- .../Session_/Player/Controls/Controls.js | 872 +++++++++--------- .../Session_/Player/Controls/Timeline.js | 733 ++++++++------- .../Controls/components/PlayerControls.tsx | 155 ++-- .../app/components/Session_/Player/Player.js | 4 +- .../shared/XRayButton/XRayButton.tsx | 18 + .../app/components/shared/XRayButton/index.ts | 1 + .../shared/XRayButton/xrayButton.module.css | 17 + frontend/app/utils.ts | 6 + 19 files changed, 1151 insertions(+), 989 deletions(-) create mode 100644 frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx create mode 100644 frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/index.ts create mode 100644 frontend/app/components/shared/XRayButton/XRayButton.tsx create mode 100644 frontend/app/components/shared/XRayButton/index.ts create mode 100644 frontend/app/components/shared/XRayButton/xrayButton.module.css diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 1b5b97c22..7212c35c3 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -122,6 +122,7 @@ const Header = (props) => {
    +
diff --git a/frontend/app/components/Session/LivePlayer.js b/frontend/app/components/Session/LivePlayer.js index c142167f0..0c07b134b 100644 --- a/frontend/app/components/Session/LivePlayer.js +++ b/frontend/app/components/Session/LivePlayer.js @@ -49,7 +49,7 @@ function LivePlayer ({ }, []) const TABS = { - EVENTS: 'Events', + EVENTS: 'User Actions', HEATMAPS: 'Click Map', } const [activeTab, setActiveTab] = useState(''); diff --git a/frontend/app/components/Session/WebPlayer.js b/frontend/app/components/Session/WebPlayer.js index 1285ac9b1..cef4d4bae 100644 --- a/frontend/app/components/Session/WebPlayer.js +++ b/frontend/app/components/Session/WebPlayer.js @@ -14,7 +14,7 @@ import styles from '../Session_/session.module.css'; import { countDaysFrom } from 'App/date'; const TABS = { - EVENTS: 'Events', + EVENTS: 'User Actions', HEATMAPS: 'Click Map', }; diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.js b/frontend/app/components/Session_/EventsBlock/EventsBlock.js index 1bc4419d4..e690ce3cc 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.js +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.js @@ -197,7 +197,7 @@ export default class EventsBlock extends React.PureComponent { setActiveTab={setActiveTab} value={query} header={ -
User Events { events.size }
+
User Actions { events.size }
} /> diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index 26fda0a30..4cadd1fad 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -1,6 +1,6 @@ import { connectPlayer } from 'App/player'; import { toggleBottomBlock } from 'Duck/components/player'; -import React from 'react'; +import React, { useEffect } from 'react'; import BottomBlock from '../BottomBlock'; import EventRow from './components/EventRow'; import { TYPES } from 'Types/session/event'; @@ -10,7 +10,7 @@ import FeatureSelection from './components/FeatureSelection/FeatureSelection'; import TimelinePointer from './components/TimelinePointer'; import VerticalPointerLine from './components/VerticalPointerLine'; import cn from 'classnames'; -import VerticalLine from './components/VerticalLine'; +// import VerticalLine from './components/VerticalLine'; import OverviewPanelContainer from './components/OverviewPanelContainer'; interface Props { @@ -20,52 +20,67 @@ interface Props { toggleBottomBlock: any; stackEventList: any[]; issuesList: any[]; + performanceChartData: any; + endTime: number; } function OverviewPanel(props: Props) { - const { resourceList, exceptionsList, eventsList, stackEventList, issuesList } = props; - - const clickRageList = React.useMemo(() => { - return eventsList.filter((item: any) => item.type === TYPES.CLICKRAGE); - }, [eventsList]); + const [dataLoaded, setDataLoaded] = React.useState(false); const [selectedFeatures, setSelectedFeatures] = React.useState(['PERFORMANCE', 'ERRORS', 'EVENTS']); const resources: any = React.useMemo(() => { + const { resourceList, exceptionsList, eventsList, stackEventList, issuesList, performanceChartData } = props; return { NETWORK: resourceList, ERRORS: exceptionsList, EVENTS: stackEventList, - CLICKRAGE: clickRageList, - PERFORMANCE: issuesList, + CLICKRAGE: eventsList.filter((item: any) => item.type === TYPES.CLICKRAGE), + PERFORMANCE: performanceChartData, }; - }, [resourceList, exceptionsList, stackEventList, clickRageList, issuesList]); + }, [dataLoaded]); + + useEffect(() => { + if (dataLoaded) { + return; + } + + if (props.resourceList.length > 0) { + setDataLoaded(true); + } + }, [props.resourceList]); return ( - - - Overview -
- -
-
- - - -
- - {selectedFeatures.map((feature: any, index: number) => ( -
- } - /> + dataLoaded && ( + + + + X-RAY +
+ +
+
+ + + +
+ + {selectedFeatures.map((feature: any, index: number) => ( +
+ } + endTime={props.endTime} + /> +
+ ))}
- ))} -
- - - + + + + + ) ); } @@ -82,5 +97,12 @@ export default connect( exceptionsList: state.exceptionsList, eventsList: state.eventList, stackEventList: state.stackList, + performanceChartData: state.performanceChartData, + endTime: state.endTime, + // endTime: 30000000, }))(OverviewPanel) ); + +const Wrapper = React.memo((props: any) => { + return
{props.children}
; +}); diff --git a/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx b/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx index cff0c6182..ee7bcb857 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx @@ -1,43 +1,48 @@ import React from 'react'; -import cn from 'classnames' +import cn from 'classnames'; import { getTimelinePosition } from 'App/utils'; import { connectPlayer } from 'App/player'; - +import PerformanceGraph from '../PerformanceGraph'; interface Props { list?: any[]; title: string; className?: string; endTime?: number; renderElement?: (item: any) => React.ReactNode; + isGraph?: boolean; } const EventRow = React.memo((props: Props) => { - const { title, className, list = [], endTime = 0 } = props; + const { title, className, list = [], endTime = 0, isGraph = false } = props; const scale = 100 / endTime; - const _list = React.useMemo(() => { - return list.map((item: any, _index: number) => { - return { - ...item.toJS(), - left: getTimelinePosition(item.time, scale), - } - }) - }, [list]); + const _list = + !isGraph && + React.useMemo(() => { + return list.map((item: any, _index: number) => { + return { + ...item.toJS(), + left: getTimelinePosition(item.time, scale), + }; + }); + }, [list]); + return ( -
-
{title}
-
- {_list.map((item: any, index: number) => { - return ( -
- {props.renderElement ? props.renderElement(item) : null} -
- ) - } - )} +
+
{title}
+
+ {isGraph ? ( + + ) : ( + _list.map((item: any, index: number) => { + return ( +
+ {props.renderElement ? props.renderElement(item) : null} +
+ ); + }) + )}
); }); -export default connectPlayer((state: any) => ({ - endTime: state.endTime, -}))(EventRow); \ No newline at end of file +export default EventRow; diff --git a/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx b/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx index 979e8bb53..5a898c67e 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx @@ -7,29 +7,31 @@ interface Props { endTime: number; } -function OverviewPanelContainer(props: Props) { +const OverviewPanelContainer = React.memo((props: Props) => { const { endTime } = props; const [mouseX, setMouseX] = React.useState(0); const [mouseIn, setMouseIn] = React.useState(false); const onClickTrack = (e: any) => { const p = e.nativeEvent.offsetX / e.target.offsetWidth; const time = Math.max(Math.round(p * endTime), 0); - Controls.jump(time); + if (time) { + Controls.jump(time); + } }; - const onMouseMoveCapture = (e: any) => { - if (!mouseIn) { - return; - } - const p = e.nativeEvent.offsetX / e.target.offsetWidth; - setMouseX(p * 100); - }; + // const onMouseMoveCapture = (e: any) => { + // if (!mouseIn) { + // return; + // } + // const p = e.nativeEvent.offsetX / e.target.offsetWidth; + // setMouseX(p * 100); + // }; return (
setMouseIn(true)} // onMouseOut={() => setMouseIn(false)} > @@ -37,8 +39,10 @@ function OverviewPanelContainer(props: Props) {
{props.children}
); -} +}); -export default connectPlayer((state: any) => ({ - endTime: state.endTime, -}))(OverviewPanelContainer); +export default OverviewPanelContainer; + +// export default connectPlayer((state: any) => ({ +// endTime: state.endTime, +// }))(OverviewPanelContainer); diff --git a/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx new file mode 100644 index 000000000..28193cd10 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { connectPlayer } from 'App/player'; +import { AreaChart, Area, Tooltip, ResponsiveContainer } from 'recharts'; + +interface Props { + list: any; +} +const PerformanceGraph = React.memo((props: Props) => { + const { list } = props; + + const finalValues = React.useMemo(() => { + const cpuMax = list.reduce((acc: number, item: any) => { + return Math.max(acc, item.cpu); + }, 0); + const cpuMin = list.reduce((acc: number, item: any) => { + return Math.min(acc, item.cpu); + }, Infinity); + + const memoryMin = list.reduce((acc: number, item: any) => { + return Math.min(acc, item.usedHeap); + }, Infinity); + const memoryMax = list.reduce((acc: number, item: any) => { + return Math.max(acc, item.usedHeap); + }, 0); + + const convertToPercentage = (val: number, max: number, min: number) => { + return ((val - min) / (max - min)) * 100; + }; + const cpuValues = list.map((item: any) => convertToPercentage(item.cpu, cpuMax, cpuMin)); + const memoryValues = list.map((item: any) => convertToPercentage(item.usedHeap, memoryMax, memoryMin)); + const mergeArraysWithMaxNumber = (arr1: any[], arr2: any[]) => { + const maxLength = Math.max(arr1.length, arr2.length); + const result = []; + for (let i = 0; i < maxLength; i++) { + const num = Math.round(Math.max(arr1[i] || 0, arr2[i] || 0)); + result.push(num > 60 ? num : 1); + } + return result; + }; + const finalValues = mergeArraysWithMaxNumber(cpuValues, memoryValues); + return finalValues; + }, []); + + const data = list.map((item: any, index: number) => { + return { + time: item.time, + cpu: finalValues[index], + }; + }); + + return ( + + + + + + + + + {/* */} + + + + ); +}); + +export default PerformanceGraph; diff --git a/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/index.ts b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/index.ts new file mode 100644 index 000000000..2c5c88675 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/index.ts @@ -0,0 +1 @@ +export { default } from './PerformanceGraph'; \ No newline at end of file diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx index c3cf175dd..6e45b5e99 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx @@ -45,7 +45,7 @@ const TimelinePointer = React.memo((props: Props) => { position="top" >
-
+
); @@ -92,7 +92,7 @@ const TimelinePointer = React.memo((props: Props) => { - {item.name} + {item.type}
} delay={0} diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx b/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx index e0b4be0b7..3b7fc453e 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx @@ -1,14 +1,60 @@ import React from 'react'; +import { connectPlayer } from 'App/player'; +import { millisToMinutesAndSeconds } from 'App/utils'; interface Props { - + endTime: number; } function TimelineScale(props: Props) { - return ( -
- -
- ); + const { endTime } = props; + const scaleRef = React.useRef(null); + const gap = 60; + + const drawScale = (container: any) => { + const width = container.offsetWidth; + const part = Math.round(width / gap); + container.replaceChildren(); + for (var i = 0; i < part; i++) { + const txt = millisToMinutesAndSeconds(i * (endTime / part)); + const el = document.createElement('div'); + // el.style.height = '10px'; + // el.style.width = '1px'; + // el.style.backgroundColor = '#ccc'; + el.style.position = 'absolute'; + el.style.left = `${i * gap}px`; + el.style.paddingTop = '1px'; + el.style.opacity = '0.8'; + el.innerHTML = txt + ''; + el.style.fontSize = '12px'; + el.style.color = 'white'; + + container.appendChild(el); + } + }; + + React.useEffect(() => { + if (!scaleRef.current) { + return; + } + + drawScale(scaleRef.current); + + // const resize = () => drawScale(scaleRef.current); + + // window.addEventListener('resize', resize); + // return () => { + // window.removeEventListener('resize', resize); + // }; + }, [scaleRef]); + return ( +
+ {/*
*/} +
+ ); } -export default TimelineScale; \ No newline at end of file +export default TimelineScale; + +// export default connectPlayer((state: any) => ({ +// endTime: state.endTime, +// }))(TimelineScale); diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js index e2197e6b3..ec0fa4ab3 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ b/frontend/app/components/Session_/Player/Controls/Controls.js @@ -1,344 +1,342 @@ import React from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; -import { - connectPlayer, - STORAGE_TYPES, - selectStorageType, - selectStorageListNow, -} from 'Player/store'; +import { connectPlayer, STORAGE_TYPES, selectStorageType, selectStorageListNow } from 'Player/store'; import LiveTag from 'Shared/LiveTag'; -import { session as sessionRoute, withSiteId } from 'App/routes'; -import { - toggleTimetravel, - jumpToLive, -} from 'Player'; +import { toggleTimetravel, jumpToLive } from 'Player'; -import { Icon } from 'UI'; +import { Icon, Button } from 'UI'; import { toggleInspectorMode } from 'Player'; import { - fullscreenOn, - fullscreenOff, - toggleBottomBlock, - OVERVIEW, - CONSOLE, - NETWORK, - STACKEVENTS, - STORAGE, - PROFILER, - PERFORMANCE, - GRAPHQL, - FETCH, - EXCEPTIONS, - INSPECTOR, + fullscreenOn, + fullscreenOff, + toggleBottomBlock, + OVERVIEW, + CONSOLE, + NETWORK, + STACKEVENTS, + STORAGE, + PROFILER, + PERFORMANCE, + GRAPHQL, + FETCH, + EXCEPTIONS, + INSPECTOR, } from 'Duck/components/player'; import { ReduxTime, AssistDuration } from './Time'; import Timeline from './Timeline'; import ControlButton from './ControlButton'; -import PlayerControls from './components/PlayerControls' +import PlayerControls from './components/PlayerControls'; import styles from './controls.module.css'; import { Tooltip } from 'react-tippy'; - +import XRayButton from 'Shared/XRayButton'; function getStorageIconName(type) { - switch(type) { - case STORAGE_TYPES.REDUX: - return "vendors/redux"; - case STORAGE_TYPES.MOBX: - return "vendors/mobx" - case STORAGE_TYPES.VUEX: - return "vendors/vuex"; - case STORAGE_TYPES.NGRX: - return "vendors/ngrx"; - case STORAGE_TYPES.NONE: - return "store" - } + switch (type) { + case STORAGE_TYPES.REDUX: + return 'vendors/redux'; + case STORAGE_TYPES.MOBX: + return 'vendors/mobx'; + case STORAGE_TYPES.VUEX: + return 'vendors/vuex'; + case STORAGE_TYPES.NGRX: + return 'vendors/ngrx'; + case STORAGE_TYPES.NONE: + return 'store'; + } } function getStorageName(type) { - switch(type) { - case STORAGE_TYPES.REDUX: - return "REDUX"; - case STORAGE_TYPES.MOBX: - return "MOBX"; - case STORAGE_TYPES.VUEX: - return "VUEX"; - case STORAGE_TYPES.NGRX: - return "NGRX"; - case STORAGE_TYPES.NONE: - return "STATE"; - } + switch (type) { + case STORAGE_TYPES.REDUX: + return 'REDUX'; + case STORAGE_TYPES.MOBX: + return 'MOBX'; + case STORAGE_TYPES.VUEX: + return 'VUEX'; + case STORAGE_TYPES.NGRX: + return 'NGRX'; + case STORAGE_TYPES.NONE: + return 'STATE'; + } } -@connectPlayer(state => ({ - time: state.time, - endTime: state.endTime, - live: state.live, - livePlay: state.livePlay, - playing: state.playing, - completed: state.completed, - skip: state.skip, - skipToIssue: state.skipToIssue, - speed: state.speed, - disabled: state.cssLoading || state.messagesLoading || state.inspectorMode || state.markedTargets, - inspectorMode: state.inspectorMode, - fullscreenDisabled: state.messagesLoading, - logCount: state.logListNow.length, - logRedCount: state.logRedCountNow, - resourceRedCount: state.resourceRedCountNow, - fetchRedCount: state.fetchRedCountNow, - showStack: state.stackList.length > 0, - stackCount: state.stackListNow.length, - stackRedCount: state.stackRedCountNow, - profilesCount: state.profilesListNow.length, - storageCount: selectStorageListNow(state).length, - storageType: selectStorageType(state), - showStorage: selectStorageType(state) !== STORAGE_TYPES.NONE, - showProfiler: state.profilesList.length > 0, - showGraphql: state.graphqlList.length > 0, - showFetch: state.fetchCount > 0, - fetchCount: state.fetchCountNow, - graphqlCount: state.graphqlListNow.length, - exceptionsCount: state.exceptionsListNow.length, - showExceptions: state.exceptionsList.length > 0, - showLongtasks: state.longtasksList.length > 0, - liveTimeTravel: state.liveTimeTravel, +@connectPlayer((state) => ({ + time: state.time, + endTime: state.endTime, + live: state.live, + livePlay: state.livePlay, + playing: state.playing, + completed: state.completed, + skip: state.skip, + skipToIssue: state.skipToIssue, + speed: state.speed, + disabled: state.cssLoading || state.messagesLoading || state.inspectorMode || state.markedTargets, + inspectorMode: state.inspectorMode, + fullscreenDisabled: state.messagesLoading, + logCount: state.logListNow.length, + logRedCount: state.logRedCountNow, + resourceRedCount: state.resourceRedCountNow, + fetchRedCount: state.fetchRedCountNow, + showStack: state.stackList.length > 0, + stackCount: state.stackListNow.length, + stackRedCount: state.stackRedCountNow, + profilesCount: state.profilesListNow.length, + storageCount: selectStorageListNow(state).length, + storageType: selectStorageType(state), + showStorage: selectStorageType(state) !== STORAGE_TYPES.NONE, + showProfiler: state.profilesList.length > 0, + showGraphql: state.graphqlList.length > 0, + showFetch: state.fetchCount > 0, + fetchCount: state.fetchCountNow, + graphqlCount: state.graphqlListNow.length, + exceptionsCount: state.exceptionsListNow.length, + showExceptions: state.exceptionsList.length > 0, + showLongtasks: state.longtasksList.length > 0, + liveTimeTravel: state.liveTimeTravel, })) -@connect((state, props) => { - const permissions = state.getIn([ 'user', 'account', 'permissions' ]) || []; - const isEnterprise = state.getIn([ 'user', 'account', 'edition' ]) === 'ee'; - return { - disabled: props.disabled || (isEnterprise && !permissions.includes('DEV_TOOLS')), - fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]), - bottomBlock: state.getIn([ 'components', 'player', 'bottomBlock' ]), - showStorage: props.showStorage || !state.getIn(['components', 'player', 'hiddenHints', 'storage']), - showStack: props.showStack || !state.getIn(['components', 'player', 'hiddenHints', 'stack']), - closedLive: !!state.getIn([ 'sessions', 'errors' ]) || !state.getIn([ 'sessions', 'current', 'live' ]), - } -}, { - fullscreenOn, - fullscreenOff, - toggleBottomBlock, -}) +@connect( + (state, props) => { + const permissions = state.getIn(['user', 'account', 'permissions']) || []; + const isEnterprise = state.getIn(['user', 'account', 'edition']) === 'ee'; + return { + disabled: props.disabled || (isEnterprise && !permissions.includes('DEV_TOOLS')), + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), + showStorage: props.showStorage || !state.getIn(['components', 'player', 'hiddenHints', 'storage']), + showStack: props.showStack || !state.getIn(['components', 'player', 'hiddenHints', 'stack']), + closedLive: !!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current', 'live']), + }; + }, + { + fullscreenOn, + fullscreenOff, + toggleBottomBlock, + } +) export default class Controls extends React.Component { - - componentDidMount() { - document.addEventListener('keydown', this.onKeyDown); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.onKeyDown); - //this.props.toggleInspectorMode(false); - } - - shouldComponentUpdate(nextProps) { - if ( - nextProps.fullscreen !== this.props.fullscreen || - nextProps.bottomBlock !== this.props.bottomBlock || - nextProps.live !== this.props.live || - nextProps.livePlay !== this.props.livePlay || - nextProps.playing !== this.props.playing || - nextProps.completed !== this.props.completed || - nextProps.skip !== this.props.skip || - nextProps.skipToIssue !== this.props.skipToIssue || - nextProps.speed !== this.props.speed || - nextProps.disabled !== this.props.disabled || - nextProps.fullscreenDisabled !== this.props.fullscreenDisabled || - // nextProps.inspectorMode !== this.props.inspectorMode || - nextProps.logCount !== this.props.logCount || - nextProps.logRedCount !== this.props.logRedCount || - nextProps.resourceRedCount !== this.props.resourceRedCount || - nextProps.fetchRedCount !== this.props.fetchRedCount || - nextProps.showStack !== this.props.showStack || - nextProps.stackCount !== this.props.stackCount || - nextProps.stackRedCount !== this.props.stackRedCount || - nextProps.profilesCount !== this.props.profilesCount || - nextProps.storageCount !== this.props.storageCount || - nextProps.storageType !== this.props.storageType || - nextProps.showStorage !== this.props.showStorage || - nextProps.showProfiler !== this.props.showProfiler || - nextProps.showGraphql !== this.props.showGraphql || - nextProps.showFetch !== this.props.showFetch || - nextProps.fetchCount !== this.props.fetchCount || - nextProps.graphqlCount !== this.props.graphqlCount || - nextProps.showExceptions !== this.props.showExceptions || - nextProps.exceptionsCount !== this.props.exceptionsCount || - nextProps.showLongtasks !== this.props.showLongtasks || - nextProps.liveTimeTravel !== this.props.liveTimeTravel - ) return true; - return false; - } - - onKeyDown = (e) => { - if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { - return; + componentDidMount() { + document.addEventListener('keydown', this.onKeyDown); } - if (this.props.inspectorMode) { - if (e.key === 'Esc' || e.key === 'Escape') { - toggleInspectorMode(false); - } + + componentWillUnmount() { + document.removeEventListener('keydown', this.onKeyDown); + //this.props.toggleInspectorMode(false); + } + + shouldComponentUpdate(nextProps) { + if ( + nextProps.fullscreen !== this.props.fullscreen || + nextProps.bottomBlock !== this.props.bottomBlock || + nextProps.live !== this.props.live || + nextProps.livePlay !== this.props.livePlay || + nextProps.playing !== this.props.playing || + nextProps.completed !== this.props.completed || + nextProps.skip !== this.props.skip || + nextProps.skipToIssue !== this.props.skipToIssue || + nextProps.speed !== this.props.speed || + nextProps.disabled !== this.props.disabled || + nextProps.fullscreenDisabled !== this.props.fullscreenDisabled || + // nextProps.inspectorMode !== this.props.inspectorMode || + nextProps.logCount !== this.props.logCount || + nextProps.logRedCount !== this.props.logRedCount || + nextProps.resourceRedCount !== this.props.resourceRedCount || + nextProps.fetchRedCount !== this.props.fetchRedCount || + nextProps.showStack !== this.props.showStack || + nextProps.stackCount !== this.props.stackCount || + nextProps.stackRedCount !== this.props.stackRedCount || + nextProps.profilesCount !== this.props.profilesCount || + nextProps.storageCount !== this.props.storageCount || + nextProps.storageType !== this.props.storageType || + nextProps.showStorage !== this.props.showStorage || + nextProps.showProfiler !== this.props.showProfiler || + nextProps.showGraphql !== this.props.showGraphql || + nextProps.showFetch !== this.props.showFetch || + nextProps.fetchCount !== this.props.fetchCount || + nextProps.graphqlCount !== this.props.graphqlCount || + nextProps.showExceptions !== this.props.showExceptions || + nextProps.exceptionsCount !== this.props.exceptionsCount || + nextProps.showLongtasks !== this.props.showLongtasks || + nextProps.liveTimeTravel !== this.props.liveTimeTravel + ) + return true; + return false; + } + + onKeyDown = (e) => { + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { + return; + } + if (this.props.inspectorMode) { + if (e.key === 'Esc' || e.key === 'Escape') { + toggleInspectorMode(false); + } + } + // if (e.key === ' ') { + // document.activeElement.blur(); + // this.props.togglePlay(); + // } + if (e.key === 'Esc' || e.key === 'Escape') { + this.props.fullscreenOff(); + } + if (e.key === 'ArrowRight') { + this.forthTenSeconds(); + } + if (e.key === 'ArrowLeft') { + this.backTenSeconds(); + } + if (e.key === 'ArrowDown') { + this.props.speedDown(); + } + if (e.key === 'ArrowUp') { + this.props.speedUp(); + } }; - // if (e.key === ' ') { - // document.activeElement.blur(); - // this.props.togglePlay(); - // } - if (e.key === 'Esc' || e.key === 'Escape') { - this.props.fullscreenOff(); - } - if (e.key === "ArrowRight") { - this.forthTenSeconds(); - } - if (e.key === "ArrowLeft") { - this.backTenSeconds(); - } - if (e.key === "ArrowDown") { - this.props.speedDown(); - } - if (e.key === "ArrowUp") { - this.props.speedUp(); - } - } - forthTenSeconds = () => { - const { time, endTime, jump } = this.props; - jump(Math.min(endTime, time + 1e4)) - } + forthTenSeconds = () => { + const { time, endTime, jump } = this.props; + jump(Math.min(endTime, time + 1e4)); + }; - backTenSeconds = () => { //shouldComponentUpdate - const { time, jump } = this.props; - jump(Math.max(0, time - 1e4)); - } + backTenSeconds = () => { + //shouldComponentUpdate + const { time, jump } = this.props; + jump(Math.max(0, time - 1e4)); + }; - goLive =() => this.props.jump(this.props.endTime) + goLive = () => this.props.jump(this.props.endTime); - renderPlayBtn = () => { - const { completed, playing } = this.props; - let label; - let icon; - if (completed) { - icon = 'arrow-clockwise'; - label = 'Replay this session' - } else if (playing) { - icon = 'pause-fill'; - label = 'Pause'; - } else { - icon = 'play-fill-new'; - label = 'Pause'; - label = 'Play' - } + renderPlayBtn = () => { + const { completed, playing } = this.props; + let label; + let icon; + if (completed) { + icon = 'arrow-clockwise'; + label = 'Replay this session'; + } else if (playing) { + icon = 'pause-fill'; + label = 'Pause'; + } else { + icon = 'play-fill-new'; + label = 'Pause'; + label = 'Play'; + } - return ( - -
- -
-
- ) - } - - controlIcon = (icon, size, action, isBackwards, additionalClasses) => -
- -
- - render() { - const { - bottomBlock, - toggleBottomBlock, - live, - livePlay, - skip, - speed, - disabled, - logCount, - logRedCount, - resourceRedCount, - fetchRedCount, - showStack, - stackCount, - stackRedCount, - profilesCount, - storageCount, - showStorage, - storageType, - showProfiler, - showGraphql, - showFetch, - fetchCount, - graphqlCount, - exceptionsCount, - showExceptions, - fullscreen, - inspectorMode, - closedLive, - toggleSpeed, - toggleSkip, - liveTimeTravel, - } = this.props; - - const toggleBottomTools = (blockName) => { - if (blockName === INSPECTOR) { - toggleInspectorMode(); - bottomBlock && toggleBottomBlock(); - } else { - toggleInspectorMode(false); - toggleBottomBlock(blockName); - } - } - - return ( -
- { !live || liveTimeTravel ? : null} - { !fullscreen && -
-
- {!live && ( - - )} - - { live && !closedLive && ( -
- livePlay ? null : jumpToLive()} /> -
- - {!liveTimeTravel && ( -
- See Past Activity -
- )} + return ( + +
+
- )} -
+ + ); + }; -
- { !live &&
} - {/* ! TEMP DISABLED ! + controlIcon = (icon, size, action, isBackwards, additionalClasses) => ( +
+ +
+ ); + + render() { + const { + bottomBlock, + toggleBottomBlock, + live, + livePlay, + skip, + speed, + disabled, + logCount, + logRedCount, + resourceRedCount, + fetchRedCount, + showStack, + stackCount, + stackRedCount, + profilesCount, + storageCount, + showStorage, + storageType, + showProfiler, + showGraphql, + showFetch, + fetchCount, + graphqlCount, + exceptionsCount, + showExceptions, + fullscreen, + inspectorMode, + closedLive, + toggleSpeed, + toggleSkip, + liveTimeTravel, + } = this.props; + + const toggleBottomTools = (blockName) => { + if (blockName === INSPECTOR) { + toggleInspectorMode(); + bottomBlock && toggleBottomBlock(); + } else { + toggleInspectorMode(false); + toggleBottomBlock(blockName); + } + }; + + return ( +
+ {!live || liveTimeTravel ? ( + + ) : null} + {!fullscreen && ( +
+
+ {!live && ( + <> + + {/* */} +
+ toggleBottomTools(OVERVIEW)} /> + + )} + + {live && !closedLive && ( +
+ (livePlay ? null : jumpToLive())} /> +
+ +
+ + {!liveTimeTravel && ( +
+ See Past Activity +
+ )} +
+ )} +
+ +
+ {/* { !live &&
} */} + {/* ! TEMP DISABLED ! {!live && ( )} */} - toggleBottomTools(OVERVIEW) } active={ bottomBlock === OVERVIEW && !inspectorMode} @@ -360,132 +358,132 @@ export default class Controls extends React.Component { // count={ logCount } // hasErrors={ logRedCount > 0 } containerClassName="mx-2" - /> - toggleBottomTools(CONSOLE) } - active={ bottomBlock === CONSOLE && !inspectorMode} - label="CONSOLE" - noIcon - labelClassName="!text-base font-semibold" - count={ logCount } - hasErrors={ logRedCount > 0 } - containerClassName="mx-2" - /> - { !live && - toggleBottomTools(NETWORK) } - active={ bottomBlock === NETWORK && !inspectorMode } - label="NETWORK" - hasErrors={ resourceRedCount > 0 } - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - } - {!live && - toggleBottomTools(PERFORMANCE) } - active={ bottomBlock === PERFORMANCE && !inspectorMode } - label="PERFORMANCE" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - } - {showFetch && - toggleBottomTools(FETCH) } - active={ bottomBlock === FETCH && !inspectorMode } - hasErrors={ fetchRedCount > 0 } - count={ fetchCount } - label="FETCH" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - } - { !live && showGraphql && - toggleBottomTools(GRAPHQL) } - active={ bottomBlock === GRAPHQL && !inspectorMode } - count={ graphqlCount } - label="GRAPHQL" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - } - { !live && showStorage && - toggleBottomTools(STORAGE) } - active={ bottomBlock === STORAGE && !inspectorMode } - count={ storageCount } - label={ getStorageName(storageType) } - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - } - { showExceptions && - toggleBottomTools(EXCEPTIONS) } - active={ bottomBlock === EXCEPTIONS && !inspectorMode } - label="EXCEPTIONS" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - count={ exceptionsCount } - hasErrors={ exceptionsCount > 0 } - /> - } - { !live && showStack && - toggleBottomTools(STACKEVENTS) } - active={ bottomBlock === STACKEVENTS && !inspectorMode } - label="EVENTS" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - count={ stackCount } - hasErrors={ stackRedCount > 0 } - /> - } - { !live && showProfiler && - toggleBottomTools(PROFILER) } - active={ bottomBlock === PROFILER && !inspectorMode } - count={ profilesCount } - label="PROFILER" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - } - { !live &&
} - { !live && ( - - {this.controlIcon("arrows-angle-extend", 18, this.props.fullscreenOn, false, "rounded hover:bg-gray-light-shade color-gray-medium")} - - ) - } + /> */} + toggleBottomTools(CONSOLE)} + active={bottomBlock === CONSOLE && !inspectorMode} + label="CONSOLE" + noIcon + labelClassName="!text-base font-semibold" + count={logCount} + hasErrors={logRedCount > 0} + containerClassName="mx-2" + /> + {!live && ( + toggleBottomTools(NETWORK)} + active={bottomBlock === NETWORK && !inspectorMode} + label="NETWORK" + hasErrors={resourceRedCount > 0} + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live && ( + toggleBottomTools(PERFORMANCE)} + active={bottomBlock === PERFORMANCE && !inspectorMode} + label="PERFORMANCE" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {showFetch && ( + toggleBottomTools(FETCH)} + active={bottomBlock === FETCH && !inspectorMode} + hasErrors={fetchRedCount > 0} + count={fetchCount} + label="FETCH" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live && showGraphql && ( + toggleBottomTools(GRAPHQL)} + active={bottomBlock === GRAPHQL && !inspectorMode} + count={graphqlCount} + label="GRAPHQL" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live && showStorage && ( + toggleBottomTools(STORAGE)} + active={bottomBlock === STORAGE && !inspectorMode} + count={storageCount} + label={getStorageName(storageType)} + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {showExceptions && ( + toggleBottomTools(EXCEPTIONS)} + active={bottomBlock === EXCEPTIONS && !inspectorMode} + label="EXCEPTIONS" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + count={exceptionsCount} + hasErrors={exceptionsCount > 0} + /> + )} + {!live && showStack && ( + toggleBottomTools(STACKEVENTS)} + active={bottomBlock === STACKEVENTS && !inspectorMode} + label="EVENTS" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + count={stackCount} + hasErrors={stackRedCount > 0} + /> + )} + {!live && showProfiler && ( + toggleBottomTools(PROFILER)} + active={bottomBlock === PROFILER && !inspectorMode} + count={profilesCount} + label="PROFILER" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live &&
} + {!live && ( + + {this.controlIcon( + 'arrows-angle-extend', + 18, + this.props.fullscreenOn, + false, + 'rounded hover:bg-gray-light-shade color-gray-medium' + )} + + )} +
+
+ )}
-
- } -
- ); - } + ); + } } diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js index c177c14e0..5ae8d67d2 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.js +++ b/frontend/app/components/Session_/Player/Controls/Timeline.js @@ -13,393 +13,392 @@ import { debounce } from 'App/utils'; import { Tooltip } from 'react-tippy'; import TooltipContainer from './components/TooltipContainer'; -const BOUNDRY = 15 +const BOUNDRY = 0; function getTimelinePosition(value, scale) { - const pos = value * scale; + const pos = value * scale; - return pos > 100 ? 100 : pos; + return pos > 100 ? 100 : pos; } const getPointerIcon = (type) => { - // exception, - switch(type) { - case 'fetch': - return 'funnel/file-earmark-minus-fill'; - case 'exception': - return 'funnel/exclamation-circle-fill'; - case 'log': - return 'funnel/exclamation-circle-fill'; - case 'stack': - return 'funnel/patch-exclamation-fill'; - case 'resource': - return 'funnel/file-earmark-minus-fill'; + // exception, + switch (type) { + case 'fetch': + return 'funnel/file-earmark-minus-fill'; + case 'exception': + return 'funnel/exclamation-circle-fill'; + case 'log': + return 'funnel/exclamation-circle-fill'; + case 'stack': + return 'funnel/patch-exclamation-fill'; + case 'resource': + return 'funnel/file-earmark-minus-fill'; - case 'dead_click': - return 'funnel/dizzy'; - case 'click_rage': - return 'funnel/dizzy'; - case 'excessive_scrolling': - return 'funnel/mouse'; - case 'bad_request': - return 'funnel/file-medical-alt'; - case 'missing_resource': - return 'funnel/file-earmark-minus-fill'; - case 'memory': - return 'funnel/sd-card'; - case 'cpu': - return 'funnel/microchip'; - case 'slow_resource': - return 'funnel/hourglass-top'; - case 'slow_page_load': - return 'funnel/hourglass-top'; - case 'crash': - return 'funnel/file-exclamation'; - case 'js_exception': - return 'funnel/exclamation-circle-fill'; - } - - return 'info'; -} + case 'dead_click': + return 'funnel/dizzy'; + case 'click_rage': + return 'funnel/dizzy'; + case 'excessive_scrolling': + return 'funnel/mouse'; + case 'bad_request': + return 'funnel/file-medical-alt'; + case 'missing_resource': + return 'funnel/file-earmark-minus-fill'; + case 'memory': + return 'funnel/sd-card'; + case 'cpu': + return 'funnel/microchip'; + case 'slow_resource': + return 'funnel/hourglass-top'; + case 'slow_page_load': + return 'funnel/hourglass-top'; + case 'crash': + return 'funnel/file-exclamation'; + case 'js_exception': + return 'funnel/exclamation-circle-fill'; + } + return 'info'; +}; let deboucneJump = () => null; let debounceTooltipChange = () => null; -@connectPlayer(state => ({ - playing: state.playing, - time: state.time, - skipIntervals: state.skipIntervals, - events: state.eventList, - skip: state.skip, - // not updating properly rn - // skipToIssue: state.skipToIssue, - disabled: state.cssLoading || state.messagesLoading || state.markedTargets, - endTime: state.endTime, - live: state.live, - logList: state.logList, - exceptionsList: state.exceptionsList, - resourceList: state.resourceList, - stackList: state.stackList, - fetchList: state.fetchList, +@connectPlayer((state) => ({ + playing: state.playing, + time: state.time, + skipIntervals: state.skipIntervals, + events: state.eventList, + skip: state.skip, + // not updating properly rn + // skipToIssue: state.skipToIssue, + disabled: state.cssLoading || state.messagesLoading || state.markedTargets, + endTime: state.endTime, + live: state.live, + logList: state.logList, + exceptionsList: state.exceptionsList, + resourceList: state.resourceList, + stackList: state.stackList, + fetchList: state.fetchList, })) -@connect(state => ({ - issues: state.getIn([ 'sessions', 'current', 'issues' ]), - clickRageTime: state.getIn([ 'sessions', 'current', 'clickRage' ]) && - state.getIn([ 'sessions', 'current', 'clickRageTime' ]), - returningLocationTime: state.getIn([ 'sessions', 'current', 'returningLocation' ]) && - state.getIn([ 'sessions', 'current', 'returningLocationTime' ]), - tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']) -}), { setTimelinePointer, setTimelineHoverTime }) +@connect( + (state) => ({ + issues: state.getIn(['sessions', 'current', 'issues']), + clickRageTime: state.getIn(['sessions', 'current', 'clickRage']) && state.getIn(['sessions', 'current', 'clickRageTime']), + returningLocationTime: + state.getIn(['sessions', 'current', 'returningLocation']) && state.getIn(['sessions', 'current', 'returningLocationTime']), + tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']), + }), + { setTimelinePointer, setTimelineHoverTime } +) export default class Timeline extends React.PureComponent { - progressRef = React.createRef() - timelineRef = React.createRef() - wasPlaying = false + progressRef = React.createRef(); + timelineRef = React.createRef(); + wasPlaying = false; - seekProgress = (e) => { - const time = this.getTime(e) - this.props.jump(time); - this.hideTimeTooltip() - } + seekProgress = (e) => { + const time = this.getTime(e); + this.props.jump(time); + this.hideTimeTooltip(); + }; - getTime = (e) => { - const { endTime } = this.props; - const p = e.nativeEvent.offsetX / e.target.offsetWidth; - const time = Math.max(Math.round(p * endTime), 0); + getTime = (e) => { + const { endTime } = this.props; + const p = e.nativeEvent.offsetX / e.target.offsetWidth; + const time = Math.max(Math.round(p * endTime), 0); - return time - } + return time; + }; - createEventClickHandler = pointer => (e) => { - e.stopPropagation(); - this.props.jump(pointer.time); - this.props.setTimelinePointer(pointer); - } + createEventClickHandler = (pointer) => (e) => { + e.stopPropagation(); + this.props.jump(pointer.time); + this.props.setTimelinePointer(pointer); + }; - componentDidMount() { - const { issues } = this.props; - const skipToIssue = Controls.updateSkipToIssue(); - const firstIssue = issues.get(0); - deboucneJump = debounce(this.props.jump, 500); - debounceTooltipChange = debounce(this.props.setTimelineHoverTime, 50); + componentDidMount() { + const { issues } = this.props; + const skipToIssue = Controls.updateSkipToIssue(); + const firstIssue = issues.get(0); + deboucneJump = debounce(this.props.jump, 500); + debounceTooltipChange = debounce(this.props.setTimelineHoverTime, 50); - if (firstIssue && skipToIssue) { - this.props.jump(firstIssue.time); + if (firstIssue && skipToIssue) { + this.props.jump(firstIssue.time); + } } - } - onDragEnd = () => { - if (this.wasPlaying) { - this.props.togglePlay(); + onDragEnd = () => { + if (this.wasPlaying) { + this.props.togglePlay(); + } + }; + + onDrag = (offset) => { + const { endTime } = this.props; + + const p = (offset.x - BOUNDRY) / this.progressRef.current.offsetWidth; + const time = Math.max(Math.round(p * endTime), 0); + deboucneJump(time); + this.hideTimeTooltip(); + if (this.props.playing) { + this.wasPlaying = true; + this.props.pause(); + } + }; + + showTimeTooltip = (e) => { + if (e.target !== this.progressRef.current && e.target !== this.timelineRef.current) { + return this.props.tooltipVisible && this.hideTimeTooltip(); + } + const time = this.getTime(e); + const { endTime, liveTimeTravel } = this.props; + + const timeLineTooltip = { + time: liveTimeTravel ? endTime - time : time, + offset: e.nativeEvent.offsetX, + isVisible: true, + }; + debounceTooltipChange(timeLineTooltip); + }; + + hideTimeTooltip = () => { + const timeLineTooltip = { isVisible: false }; + debounceTooltipChange(timeLineTooltip); + }; + + render() { + const { + events, + skip, + skipIntervals, + disabled, + endTime, + exceptionsList, + resourceList, + clickRageTime, + stackList, + fetchList, + issues, + liveTimeTravel, + } = this.props; + + const scale = 100 / endTime; + + return ( +
+
+ + {/* custo color is live */} + + + + + {skip && + skipIntervals.map((interval) => ( +
+ ))} +
+ + {events.map((e) => ( +
+ ))} + {/* {issues.map((iss) => ( +
+ + {iss.name} +
+ } + > + + +
+ ))} + {events + .filter((e) => e.type === TYPES.CLICKRAGE) + .map((e) => ( +
+ + {'Click Rage'} +
+ } + > + + +
+ ))} + {typeof clickRageTime === 'number' && ( +
+ + {'Click Rage'} +
+ } + > + + +
+ )} + {exceptionsList.map((e) => ( +
+ + {'Exception'} +
+ {e.message} +
+ } + > + + +
+ ))} + {resourceList + .filter((r) => r.isRed() || r.isYellow()) + .map((r) => ( +
+ + {r.success ? 'Slow resource: ' : 'Missing resource:'} +
+ {r.name} +
+ } + > + + +
+ ))} + {fetchList + .filter((e) => e.isRed()) + .map((e) => ( +
+ + Failed Fetch +
+ {e.name} +
+ } + > + + +
+ ))} + {stackList + .filter((e) => e.isRed()) + .map((e) => ( +
+ + Stack Event +
+ {e.name} +
+ } + > + + +
+ ))} */} +
+
+ ); } - } - - onDrag = (offset) => { - const { endTime } = this.props; - - const p = (offset.x - BOUNDRY) / this.progressRef.current.offsetWidth; - const time = Math.max(Math.round(p * endTime), 0); - deboucneJump(time); - this.hideTimeTooltip(); - if (this.props.playing) { - this.wasPlaying = true; - this.props.pause(); - } - } - - showTimeTooltip = (e) => { - if (e.target !== this.progressRef.current && e.target !== this.timelineRef.current) { - return this.props.tooltipVisible && this.hideTimeTooltip() - } - const time = this.getTime(e); - const { endTime, liveTimeTravel } = this.props; - - const timeLineTooltip = { - time: liveTimeTravel ? endTime - time : time, - offset: e.nativeEvent.offsetX, - isVisible: true - } - debounceTooltipChange(timeLineTooltip) - } - - hideTimeTooltip = () => { - const timeLineTooltip = { isVisible: false } - debounceTooltipChange(timeLineTooltip) - } - - render() { - const { - events, - skip, - skipIntervals, - disabled, - endTime, - exceptionsList, - resourceList, - clickRageTime, - stackList, - fetchList, - issues, - liveTimeTravel, - } = this.props; - - const scale = 100 / endTime; - - return ( -
-
- - {/* custo color is live */} - - - - - { skip && skipIntervals.map(interval => - (
)) - } -
- - { events.map(e => ( -
- )) - } - { - issues.map(iss => ( -
- - { iss.name } -
- } - > - - -
- )) - } - { events.filter(e => e.type === TYPES.CLICKRAGE).map(e => ( -
- - { "Click Rage" } -
- } - > - - -
- ))} - {typeof clickRageTime === 'number' && -
- - { "Click Rage" } -
- } - > - - -
- } - { exceptionsList - .map(e => ( -
- - { "Exception" } -
- { e.message } -
- } - > - - -
- )) - } - { resourceList - .filter(r => r.isRed() || r.isYellow()) - .map(r => ( -
- - { r.success ? "Slow resource: " : "Missing resource:" } -
- { r.name } -
- } - > - - -
- )) - } - { fetchList - .filter(e => e.isRed()) - .map(e => ( -
- - Failed Fetch -
- { e.name } -
- } - > - - -
- )) - } - { stackList - .filter(e => e.isRed()) - .map(e => ( -
- - Stack Event -
- { e.name } -
- } - > - - -
- )) - } -
-
- ); - } } diff --git a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx index be3ac24b3..5b07012df 100644 --- a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx @@ -1,111 +1,72 @@ -import React from 'react' +import React from 'react'; import { Tooltip } from 'react-tippy'; import { ReduxTime } from '../Time'; import { Icon } from 'UI'; import cn from 'classnames'; // @ts-ignore -import styles from '../controls.module.css' +import styles from '../controls.module.css'; interface Props { - live: boolean; - skip: boolean; - speed: number; - disabled: boolean; - playButton: JSX.Element; - backTenSeconds: () => void; - forthTenSeconds: () => void; - toggleSpeed: () => void; - toggleSkip: () => void; - controlIcon: (icon: string, size: number, action: () => void, isBackwards: boolean, additionalClasses: string) => JSX.Element; + live: boolean; + skip: boolean; + speed: number; + disabled: boolean; + playButton: JSX.Element; + backTenSeconds: () => void; + forthTenSeconds: () => void; + toggleSpeed: () => void; + toggleSkip: () => void; + controlIcon: (icon: string, size: number, action: () => void, isBackwards: boolean, additionalClasses: string) => JSX.Element; } function PlayerControls(props: Props) { - const { - live, - skip, - speed, - disabled, - playButton, - backTenSeconds, - forthTenSeconds, - toggleSpeed, - toggleSkip, - controlIcon - } = props; - return ( -
- {playButton} - {!live && ( -
- {/* @ts-ignore */} - - / - {/* @ts-ignore */} - + const { live, skip, speed, disabled, playButton, backTenSeconds, forthTenSeconds, toggleSpeed, toggleSkip, controlIcon } = props; + return ( +
+ {playButton} + {!live && ( +
+ {/* @ts-ignore */} + + / + {/* @ts-ignore */} + +
+ )} + +
+ {/* @ts-ignore */} + + {controlIcon('skip-forward-fill', 18, backTenSeconds, true, 'hover:bg-active-blue-border color-main h-full flex items-center')} + +
10s
+ {/* @ts-ignore */} + + {controlIcon('skip-forward-fill', 18, forthTenSeconds, false, 'hover:bg-active-blue-border color-main h-full flex items-center')} + +
+ + {!live && ( +
+ {/* @ts-ignore */} + + + + + +
+ )}
- )} - -
- {/* @ts-ignore */} - - {controlIcon( - "skip-forward-fill", - 18, - backTenSeconds, - true, - 'hover:bg-active-blue-border color-main h-full flex items-center' - )} - -
10s
- {/* @ts-ignore */} - - {controlIcon( - "skip-forward-fill", - 18, - forthTenSeconds, - false, - 'hover:bg-active-blue-border color-main h-full flex items-center' - )} - -
- - {!live && -
- {/* @ts-ignore */} - - - - - -
- } -
- ) + ); } export default PlayerControls; diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index ef69d5c2d..9a95121ac 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -104,9 +104,9 @@ export default class Player extends React.PureComponent { ref={ this.screenWrapper } />
- { // !fullscreen && !!bottomBlock && + { !fullscreen && !!bottomBlock &&
- { // bottomBlock === OVERVIEW && + { bottomBlock === OVERVIEW && } { bottomBlock === CONSOLE && diff --git a/frontend/app/components/shared/XRayButton/XRayButton.tsx b/frontend/app/components/shared/XRayButton/XRayButton.tsx new file mode 100644 index 000000000..2d66ed283 --- /dev/null +++ b/frontend/app/components/shared/XRayButton/XRayButton.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import stl from './xrayButton.module.css'; +import cn from 'classnames'; + +interface Props { + onClick?: () => void; + isActive?: boolean; +} +function XRayButton(props: Props) { + const { isActive } = props; + return ( + + ); +} + +export default XRayButton; diff --git a/frontend/app/components/shared/XRayButton/index.ts b/frontend/app/components/shared/XRayButton/index.ts new file mode 100644 index 000000000..45f067067 --- /dev/null +++ b/frontend/app/components/shared/XRayButton/index.ts @@ -0,0 +1 @@ +export { default } from './XRayButton'; \ No newline at end of file diff --git a/frontend/app/components/shared/XRayButton/xrayButton.module.css b/frontend/app/components/shared/XRayButton/xrayButton.module.css new file mode 100644 index 000000000..3b3f76ca4 --- /dev/null +++ b/frontend/app/components/shared/XRayButton/xrayButton.module.css @@ -0,0 +1,17 @@ +.wrapper { + text-align: center; + padding: 4px 14px; + border: none; + border-radius: 6px; + font-weight: 500; + + &.default { + color: white; + background: linear-gradient(90deg, rgba(57, 78, 255, 0.87) 0%, rgba(62, 170, 175, 0.87) 100%); + } + + &.active { + background: rgba(63, 81, 181, 0.08); + color: $gray-darkest; + } +} diff --git a/frontend/app/utils.ts b/frontend/app/utils.ts index 517ab5e72..1e738024c 100644 --- a/frontend/app/utils.ts +++ b/frontend/app/utils.ts @@ -356,3 +356,9 @@ export function getTimelinePosition(value: any, scale: any) { const pos = value * scale; return pos > 100 ? 100 : pos; } + +export function millisToMinutesAndSeconds(millis: any) { + const minutes = Math.floor(millis / 60000); + const seconds: any = ((millis % 60000) / 1000).toFixed(0); + return minutes + 'm' + (seconds < 10 ? '0' : '') + seconds + 's'; +} \ No newline at end of file From 32450c49de649a36abb45a8d6598d128689be4ce Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 15:06:51 +0200 Subject: [PATCH 112/168] change(ui) - player controls margin --- .../Player/Controls/components/PlayerControls.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx index 9f813ce93..4a8276482 100644 --- a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx @@ -83,7 +83,10 @@ function PlayerControls(props: Props) {
{/* @ts-ignore */} -
{/* @ts-ignore */} -
{!live && ( -
+
{/* @ts-ignore */}
- - - ) +
+ + + No data for the selected time period +
+ } + > +
+ + + ); } export default CustomMetricTable; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx index 83cd55b35..dbc3c5504 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx @@ -44,9 +44,10 @@ function CustomMetricTableErrors(props: RouteComponentProps & Props) { return ( No data for the selected time period} - show={!data.errors || data.errors.length === 0} - size="small" + title={
No data for the selected time period
} + show={!data.errors || data.errors.length === 0} + size="small" + style={{ minHeight: 220 }} >
{data.errors && From dcb6c9ed4ef834b01dce8fdaa320276aa16bacdc Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 17:48:48 +0200 Subject: [PATCH 132/168] change(ui) - add btn and other changes --- .../Client/CustomFields/CustomFields.js | 20 ++++++++----------- .../CustomFields/customFields.module.css | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/frontend/app/components/Client/CustomFields/CustomFields.js b/frontend/app/components/Client/CustomFields/CustomFields.js index 1d8c2b2cd..89962de64 100644 --- a/frontend/app/components/Client/CustomFields/CustomFields.js +++ b/frontend/app/components/Client/CustomFields/CustomFields.js @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; import withPageTitle from 'HOCs/withPageTitle'; -import { Button, Loader, NoContent, TextLink } from 'UI'; +import { Button, Loader, NoContent, Icon } from 'UI'; import { init, fetchList, save, remove } from 'Duck/customField'; import SiteDropdown from 'Shared/SiteDropdown'; import styles from './customFields.module.css'; @@ -71,25 +71,21 @@ function CustomFields(props) {
- +
+
+ + See additonal user information in sessions. + Learn more
- + {/*
*/}
None added yet
-
} size="small" diff --git a/frontend/app/components/Client/CustomFields/customFields.module.css b/frontend/app/components/Client/CustomFields/customFields.module.css index 8636473a7..89e5e9914 100644 --- a/frontend/app/components/Client/CustomFields/customFields.module.css +++ b/frontend/app/components/Client/CustomFields/customFields.module.css @@ -1,7 +1,7 @@ .tabHeader { display: flex; align-items: center; - margin-bottom: 25px; + /* margin-bottom: 25px; */ & .tabTitle { margin: 0 15px 0 0; From 8fca8f2ebf6ca15bb44a763968810c1d87fffe33 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 17:49:00 +0200 Subject: [PATCH 133/168] change(ui) - add btn and other changes --- .../app/components/Client/Webhooks/Webhooks.js | 15 +++++++++------ .../Client/Webhooks/webhooks.module.css | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/frontend/app/components/Client/Webhooks/Webhooks.js b/frontend/app/components/Client/Webhooks/Webhooks.js index 58350809f..42f73d799 100644 --- a/frontend/app/components/Client/Webhooks/Webhooks.js +++ b/frontend/app/components/Client/Webhooks/Webhooks.js @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import cn from 'classnames'; import withPageTitle from 'HOCs/withPageTitle'; -import { Button, Loader, NoContent } from 'UI'; +import { Button, Loader, NoContent, Icon } from 'UI'; import { init, fetchList, remove } from 'Duck/webhook'; import WebhookForm from './WebhookForm'; import ListItem from './ListItem'; @@ -45,18 +45,21 @@ function Webhooks(props) {

{'Webhooks'}

- +
+ +
+ + Leverage webhooks to push OpenReplay data to other systems.
- +
None added yet
-
} size="small" diff --git a/frontend/app/components/Client/Webhooks/webhooks.module.css b/frontend/app/components/Client/Webhooks/webhooks.module.css index 718a256f3..dbd8c241b 100644 --- a/frontend/app/components/Client/Webhooks/webhooks.module.css +++ b/frontend/app/components/Client/Webhooks/webhooks.module.css @@ -3,7 +3,7 @@ .tabHeader { display: flex; align-items: center; - margin-bottom: 25px; + /* margin-bottom: 25px; */ & .tabTitle { margin: 0 15px 0 0; From c89025faa17a6fedf643e039a064b228ec742140 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 17:49:10 +0200 Subject: [PATCH 134/168] change(ui) - add btn and other changes --- .../components/Client/Sites/AddProjectButton/AddUserButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Client/Sites/AddProjectButton/AddUserButton.tsx b/frontend/app/components/Client/Sites/AddProjectButton/AddUserButton.tsx index e938b391f..5934dfe52 100644 --- a/frontend/app/components/Client/Sites/AddProjectButton/AddUserButton.tsx +++ b/frontend/app/components/Client/Sites/AddProjectButton/AddUserButton.tsx @@ -22,7 +22,7 @@ function AddProjectButton({ isAdmin = false, init = () => {} }: any) { }; return ( - + ); } From e464f99b12336ea95c8a705a990df26e57092ccb Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 17:51:02 +0200 Subject: [PATCH 135/168] change(ui) - no data icon --- .../app/components/Client/Audit/AuditList/AuditList.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/app/components/Client/Audit/AuditList/AuditList.tsx b/frontend/app/components/Client/Audit/AuditList/AuditList.tsx index 41f413f1e..45475923a 100644 --- a/frontend/app/components/Client/Audit/AuditList/AuditList.tsx +++ b/frontend/app/components/Client/Audit/AuditList/AuditList.tsx @@ -35,13 +35,14 @@ function AuditList(props: Props) { return useObserver(() => ( - -
No data available.
+ +
No data available
} + size="small" + show={list.length === 0} >
Name
From 6396921d760dadc6c7cede3c99f46e8a90d89b3c Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 17:57:02 +0200 Subject: [PATCH 136/168] change(ui) - users list no data icon and message --- .../Users/components/AddUserButton/AddUserButton.tsx | 7 ++++--- .../Client/Users/components/UserList/UserList.tsx | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/app/components/Client/Users/components/AddUserButton/AddUserButton.tsx b/frontend/app/components/Client/Users/components/AddUserButton/AddUserButton.tsx index 6907a7b12..df2777e83 100644 --- a/frontend/app/components/Client/Users/components/AddUserButton/AddUserButton.tsx +++ b/frontend/app/components/Client/Users/components/AddUserButton/AddUserButton.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Popup, IconButton } from 'UI'; +import { Popup, IconButton, Button } from 'UI'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; @@ -14,7 +14,8 @@ function AddUserButton({ isAdmin = false, onClick }: any ) { - Add + {/* + /> */} ); } diff --git a/frontend/app/components/Client/Users/components/UserList/UserList.tsx b/frontend/app/components/Client/Users/components/UserList/UserList.tsx index 5f805b386..54acae74a 100644 --- a/frontend/app/components/Client/Users/components/UserList/UserList.tsx +++ b/frontend/app/components/Client/Users/components/UserList/UserList.tsx @@ -44,13 +44,14 @@ function UserList(props: Props) { return useObserver(() => ( - -
No data available.
+ +
No matching results.
} + size="small" + show={!loading && length === 0} >
From cf5d0d8abeb6ddec41cec176b5d92cb2a95204bd Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 17:57:28 +0200 Subject: [PATCH 137/168] change(ui) - dashboard and metrics no data font size --- .../Dashboard/components/DashboardList/DashboardList.tsx | 2 +- .../components/Dashboard/components/MetricsList/MetricsList.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index dd0ebb1ad..3a9ff8696 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -25,7 +25,7 @@ function DashboardList() { title={
-
You haven't created any dashboards yet
+
You haven't created any dashboards yet
} > diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index 9a6646ad2..0833c3557 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -31,7 +31,7 @@ function MetricsList({ siteId }: { siteId: string }) { title={
-
You haven't created any metrics yet
+
You haven't created any metrics yet
} > From 9e7e132e34fac76c80ed0847ced106637ef28d3d Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 17:57:52 +0200 Subject: [PATCH 138/168] change(ui) - add button and other changes --- frontend/app/components/Client/Roles/Roles.tsx | 17 ++++------------- frontend/app/components/Client/Sites/Sites.js | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/frontend/app/components/Client/Roles/Roles.tsx b/frontend/app/components/Client/Roles/Roles.tsx index 6ed98ac06..7c978086b 100644 --- a/frontend/app/components/Client/Roles/Roles.tsx +++ b/frontend/app/components/Client/Roles/Roles.tsx @@ -1,6 +1,6 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect } from 'react'; import cn from 'classnames'; -import { Loader, IconButton, Popup, NoContent, SlideModal } from 'UI'; +import { Loader, Popup, NoContent, Button } from 'UI'; import { connect } from 'react-redux'; import stl from './roles.module.css'; import RoleForm from './components/RoleForm'; @@ -78,26 +78,17 @@ function Roles(props: Props) { return ( - {/* } - onClose={closeModal} - /> */}

Roles and Access

-
- setShowmModal(true)} /> -
+
- +
diff --git a/frontend/app/components/Client/Sites/Sites.js b/frontend/app/components/Client/Sites/Sites.js index ab5f5be25..86bbddd66 100644 --- a/frontend/app/components/Client/Sites/Sites.js +++ b/frontend/app/components/Client/Sites/Sites.js @@ -1,7 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; import withPageTitle from 'HOCs/withPageTitle'; -import { Loader, Button, Popup, TextLink } from 'UI'; +import { Loader, Button, Popup, TextLink, NoContent } from 'UI'; import { init, remove, fetchGDPR } from 'Duck/site'; import { RED, YELLOW, GREEN, STATUS_COLOR_MAP } from 'Types/site'; import stl from './sites.module.css'; @@ -13,6 +13,7 @@ import InstallButton from './InstallButton'; import ProjectKey from './ProjectKey'; import { useModal } from 'App/components/Modal'; import { getInitials } from 'App/utils'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; const STATUS_MESSAGE_MAP = { [RED]: ' There seems to be an issue (please verify your installation)', @@ -74,8 +75,18 @@ class Sites extends React.PureComponent { this.setState({ searchQuery: value })} />
- +
+ + +
No matching results.
+
+ } + size="small" + show={!loading && filteredSites.size === 0} + >
Project Name
Key
@@ -115,6 +126,7 @@ class Sites extends React.PureComponent {
))} +
From f287ad4745af190e6d1c0ac42c0fe41a37eee637 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 17:58:12 +0200 Subject: [PATCH 139/168] change(ui) - nodata icons --- frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx b/frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx index df2fffce8..08b69ee33 100644 --- a/frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx +++ b/frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx @@ -13,6 +13,7 @@ import NoSessionsInVault from '../../../svg/ca-no-sessions-in-vault.svg'; import NoWebhooks from '../../../svg/ca-no-webhooks.svg'; import NoMetadata from '../../../svg/ca-no-metadata.svg'; import NoIssues from '../../../svg/ca-no-issues.svg'; +import NoAuditTrail from '../../../svg/ca-no-audit-trail.svg'; export enum ICONS { DASHBOARD_ICON = 'dashboard-icn', @@ -30,6 +31,7 @@ export enum ICONS { NO_METADATA = 'ca-no-metadata', NO_SESSIONS_IN_VAULT = 'ca-no-sessions-in-vault', NO_ISSUES = 'ca-no-issues', + NO_AUDIT_TRAIL = 'ca-no-audit-trail', } interface Props { @@ -68,6 +70,8 @@ function AnimatedSVG(props: Props) { return ; case ICONS.NO_ISSUES: return ; + case ICONS.NO_AUDIT_TRAIL: + return ; default: return null; } From 6e266744d122b73bbf88db18a9a57a5dff6d41f6 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 17:58:19 +0200 Subject: [PATCH 140/168] change(ui) - nodata icons --- frontend/app/svg/ca-no-audit-trail.svg | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 frontend/app/svg/ca-no-audit-trail.svg diff --git a/frontend/app/svg/ca-no-audit-trail.svg b/frontend/app/svg/ca-no-audit-trail.svg new file mode 100644 index 000000000..52a701f25 --- /dev/null +++ b/frontend/app/svg/ca-no-audit-trail.svg @@ -0,0 +1,4 @@ + + + + From f119269c9fbdb2d353a0687fe6e2a0908f4508d0 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 18:00:25 +0200 Subject: [PATCH 141/168] change(ui) - add btn spacing --- frontend/app/components/ui/PageTitle/PageTitle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/ui/PageTitle/PageTitle.tsx b/frontend/app/components/ui/PageTitle/PageTitle.tsx index c1500a5af..50047ac03 100644 --- a/frontend/app/components/ui/PageTitle/PageTitle.tsx +++ b/frontend/app/components/ui/PageTitle/PageTitle.tsx @@ -17,7 +17,7 @@ function PageTitle({ title, actionButton = null, subTitle = '', className = '',

{title}

- { actionButton && actionButton} + { actionButton &&
{actionButton}
} {subTitle &&

{subTitle}

} From 1f17b7dfa0c5dfd405bb5079c17e48e60679a460 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 16 Aug 2022 18:08:10 +0200 Subject: [PATCH 142/168] change(ui) - alerts laoder --- .../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 4d748687d..b76666962 100644 --- a/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx +++ b/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx @@ -37,7 +37,7 @@ function AlertTriggersModal(props: Props) { { count > 0 && (