From 130775a968efc528056cbba8b84192fb4d25ee63 Mon Sep 17 00:00:00 2001 From: Eric Chan Date: Wed, 13 Jul 2022 11:41:43 -0400 Subject: [PATCH 001/245] avoid accessing localStorage and sessionStorage before override --- tracker/tracker/src/main/app/index.ts | 34 +++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index be03a968c..81b99aae6 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -23,8 +23,8 @@ export interface StartOptions { } interface OnStartInfo { - sessionID: string, - sessionToken: string, + sessionID: string, + sessionToken: string, userUUID: string, } const CANCELED = "canceled" as const @@ -60,8 +60,8 @@ type AppOptions = { __is_snippet: boolean; __debug_report_edp: string | null; __debug__?: LoggerOptions; - localStorage: Storage; - sessionStorage: Storage; + localStorage: Storage | null; + sessionStorage: Storage | null; // @deprecated onStart?: StartCallback; @@ -117,8 +117,8 @@ export default class App { verbose: false, __is_snippet: false, __debug_report_edp: null, - localStorage: window.localStorage, - sessionStorage: window.sessionStorage, + localStorage: null, + sessionStorage: null, }, options, ); @@ -140,8 +140,8 @@ export default class App { Object.entries(metadata).forEach(([key, value]) => this.send(new Metadata(key, value))) } }) - this.localStorage = this.options.localStorage; - this.sessionStorage = this.options.sessionStorage; + this.localStorage = this.options.localStorage ?? window.localStorage; + this.sessionStorage = this.options.sessionStorage ?? window.sessionStorage; if (sessionToken != null) { this.sessionStorage.setItem(this.options.session_token_key, sessionToken); @@ -175,7 +175,7 @@ export default class App { this.attachEventListener(document.body, 'mouseleave', alertWorker, false, false); // TODO: stop session after inactivity timeout (make configurable) this.attachEventListener(document, 'visibilitychange', alertWorker, false); - } catch (e) { + } catch (e) { this._debug("worker_start", e); } } @@ -197,9 +197,9 @@ export default class App { send(message: Message, urgent = false): void { if (this.activityState === ActivityState.NotActive) { return } this.messages.push(message); - // TODO: commit on start if there were `urgent` sends; + // TODO: commit on start if there were `urgent` sends; // Clearify where urgent can be used for; - // Clearify workflow for each type of message in case it was sent before start + // Clearify workflow for each type of message in case it was sent before start // (like Fetch before start; maybe add an option "preCapture: boolean" or sth alike) if (this.activityState === ActivityState.Active && urgent) { this.commit(); @@ -339,8 +339,8 @@ export default class App { if (!this.worker) { return Promise.resolve(UnsuccessfulStart("No worker found: perhaps, CSP is not set.")) } - if (this.activityState !== ActivityState.NotActive) { - return Promise.resolve(UnsuccessfulStart("OpenReplay: trying to call `start()` on the instance that has been started already.")) + if (this.activityState !== ActivityState.NotActive) { + return Promise.resolve(UnsuccessfulStart("OpenReplay: trying to call `start()` on the instance that has been started already.")) } this.activityState = ActivityState.Starting; @@ -364,7 +364,7 @@ export default class App { this.worker.postMessage(startWorkerMsg) this.session.update({ // TODO: transparent "session" module logic AND explicit internal api for plugins. - // "updating" with old metadata in order to trigger session's UpdateCallbacks. + // "updating" with old metadata in order to trigger session's UpdateCallbacks. // (for the case of internal .start() calls, like on "restart" webworker signal or assistent connection in tracker-assist ) metadata: startOpts.metadata || this.session.getInfo().metadata, userID: startOpts.userID, @@ -391,7 +391,7 @@ export default class App { if (r.status === 200) { return r.json() } else { - return r.text().then(text => text === CANCELED + return r.text().then(text => text === CANCELED ? Promise.reject(CANCELED) : Promise.reject(`Server error: ${r.status}. ${text}`) ); @@ -418,7 +418,7 @@ export default class App { this.worker.postMessage(startWorkerMsg) this.activityState = ActivityState.Active - + const onStartInfo = { sessionToken: token, userUUID, sessionID }; this.startCallbacks.forEach((cb) => cb(onStartInfo)); // TODO: start as early as possible (before receiving the token) @@ -432,7 +432,7 @@ export default class App { } return SuccessfulStart(onStartInfo) }) - .catch(reason => { + .catch(reason => { this.sessionStorage.removeItem(this.options.session_token_key) this.stop() if (reason === CANCELED) { return UnsuccessfulStart(CANCELED) } From fbbd69732e4f8d9b047e2254f4c5a903eabf4d66 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 20 Jul 2022 18:16:02 +0200 Subject: [PATCH 002/245] 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 003/245] 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 004/245] 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 005/245] 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 006/245] 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: Fri, 22 Jul 2022 11:13:13 -0400 Subject: [PATCH 008/245] add comment about #490, #637 --- tracker/tracker/src/main/app/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index 81b99aae6..f7b2ddb0b 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -140,6 +140,8 @@ export default class App { Object.entries(metadata).forEach(([key, value]) => this.send(new Metadata(key, value))) } }) + + // window.localStorage and window.sessionStorage should only be accessed if required, see #490, #637 this.localStorage = this.options.localStorage ?? window.localStorage; this.sessionStorage = this.options.sessionStorage ?? window.sessionStorage; From fa314fed5725924bc3ef19927f22201a17d1fa9b Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 25 Jul 2022 13:50:18 +0200 Subject: [PATCH 009/245] 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 010/245] 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 011/245] 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 012/245] 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 013/245] 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 014/245] 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 015/245] 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 b66923a468d30d86222e1b21cf27565461bba3ff Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Thu, 28 Jul 2022 14:58:08 +0200 Subject: [PATCH 016/245] 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 b36a0a09ea2568b872380f9970e1b5a6d93aaea7 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 1 Aug 2022 17:47:09 +0200 Subject: [PATCH 017/245] feat(ui) - session list - tabs and settings --- .../SessionHeader/SessionHeader.tsx | 38 ++++++++++++++++--- .../SessionSettingButton.tsx | 20 ++++++++++ .../components/SessionSettingButton/index.ts | 1 + frontend/app/types/session/issue.js | 14 +++---- 4 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/SessionSettingButton.tsx create mode 100644 frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/index.ts diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx index 9efcf7e6e..a3af10ff4 100644 --- a/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx @@ -6,14 +6,25 @@ import SelectDateRange from 'Shared/SelectDateRange'; import SessionTags from '../SessionTags'; import { connect } from 'react-redux'; import SessionSort from '../SessionSort'; +import cn from 'classnames'; +import { setActiveTab } from 'Duck/search'; +import SessionSettingButton from '../SessionSettingButton'; interface Props { listCount: number; filter: any; + isBookmark: any; + isEnterprise: boolean; applyFilter: (filter: any) => void; + setActiveTab: (tab: any) => void; } function SessionHeader(props: Props) { - const { listCount, filter: { startDate, endDate, rangeValue } } = props; + const { + listCount, + filter: { startDate, endDate, rangeValue }, + isBookmark, + isEnterprise, + } = props; const period = Period({ start: startDate, end: endDate, rangeName: rangeValue }); const onDateChange = (e: any) => { @@ -23,17 +34,30 @@ function SessionHeader(props: Props) { return (
-
-
- Sessions {listCount} +
+
+
props.setActiveTab({ type: 'all' })} + > + Sessions {listCount} +
+
props.setActiveTab({ type: 'bookmark' })} + > + {`${isEnterprise ? 'Vault' : 'Bookmarks'}`} +
-
+ {!isBookmark && } +
+
); @@ -43,6 +67,8 @@ export default connect( (state: any) => ({ filter: state.getIn(['search', 'instance']), listCount: numberWithCommas(state.getIn(['sessions', 'total'])), + isBookmark: state.getIn(['search', 'activeTab', 'type']) === 'bookmark', + isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee', }), - { applyFilter } + { applyFilter, setActiveTab } )(SessionHeader); diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/SessionSettingButton.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/SessionSettingButton.tsx new file mode 100644 index 000000000..897a59754 --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/SessionSettingButton.tsx @@ -0,0 +1,20 @@ +import { useModal } from 'App/components/Modal'; +import React from 'react'; +import SessionSettings from 'Shared/SessionSettings'; +import { Button } from 'UI'; + +function SessionSettingButton(props: any) { + const { showModal } = useModal(); + + const handleClick = () => { + showModal(, { right: true }); + }; + + return ( +
+
+ ); +} + +export default SessionSettingButton; diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/index.ts b/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/index.ts new file mode 100644 index 000000000..dffed3cc0 --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/index.ts @@ -0,0 +1 @@ +export { default } from './SessionSettingButton'; \ No newline at end of file diff --git a/frontend/app/types/session/issue.js b/frontend/app/types/session/issue.js index 673467fd2..d2afff190 100644 --- a/frontend/app/types/session/issue.js +++ b/frontend/app/types/session/issue.js @@ -8,13 +8,13 @@ export const issues_types = List([ { 'type': 'click_rage', 'visible': true, 'order': 2, 'name': 'Click Rage', 'icon': 'funnel/emoji-angry' }, { 'type': 'crash', 'visible': true, 'order': 3, 'name': 'Crashes', 'icon': 'funnel/file-earmark-break' }, { 'type': 'memory', 'visible': true, 'order': 4, 'name': 'High Memory', 'icon': 'funnel/sd-card' }, - { 'type': 'vault', 'visible': true, 'order': 5, 'name': 'Vault', 'icon': 'safe' }, - { 'type': 'bookmark', 'visible': true, 'order': 5, 'name': 'Bookmarks', 'icon': 'safe' }, - { 'type': 'bad_request', 'visible': true, 'order': 1, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' }, - { 'type': 'missing_resource', 'visible': true, 'order': 2, 'name': 'Missing Images', 'icon': 'funnel/image' }, - { 'type': 'dead_click', 'visible': true, 'order': 4, 'name': 'Dead Clicks', 'icon': 'funnel/dizzy' }, - { 'type': 'cpu', 'visible': true, 'order': 6, 'name': 'High CPU', 'icon': 'funnel/cpu' }, - { 'type': 'custom', 'visible': false, 'order': 8, 'name': 'Custom', 'icon': 'funnel/exclamation-circle' } + // { 'type': 'vault', 'visible': true, 'order': 5, 'name': 'Vault', 'icon': 'safe' }, + // { 'type': 'bookmark', 'visible': true, 'order': 5, 'name': 'Bookmarks', 'icon': 'safe' }, + // { 'type': 'bad_request', 'visible': true, 'order': 1, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' }, + // { 'type': 'missing_resource', 'visible': true, 'order': 2, 'name': 'Missing Images', 'icon': 'funnel/image' }, + // { 'type': 'dead_click', 'visible': true, 'order': 4, 'name': 'Dead Clicks', 'icon': 'funnel/dizzy' }, + // { 'type': 'cpu', 'visible': true, 'order': 6, 'name': 'High CPU', 'icon': 'funnel/cpu' }, + // { 'type': 'custom', 'visible': false, 'order': 8, 'name': 'Custom', 'icon': 'funnel/exclamation-circle' } ]).map(Watchdog) export const issues_types_map = {} From a0344608386e474a892672735e2e9cc2b1fe5499 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 1 Aug 2022 18:01:16 +0200 Subject: [PATCH 018/245] feat(ui) - session list - tabs and settings --- .../SessionListContainer/SessionListContainer.tsx | 6 +++--- .../components/SessionHeader/SessionHeader.tsx | 14 +++++++++----- .../components/SessionList/SessionList.tsx | 5 ++--- frontend/app/theme/colors.js | 1 + 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx b/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx index 156f845c4..0a13707c7 100644 --- a/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx +++ b/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx @@ -2,12 +2,12 @@ import React from 'react'; import SessionList from './components/SessionList'; import SessionHeader from './components/SessionHeader'; -interface Props {} -function SessionListContainer(props: Props) { +function SessionListContainer() { return (
-
+
+
diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx index a3af10ff4..f0fe2b7d2 100644 --- a/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx @@ -33,20 +33,24 @@ function SessionHeader(props: Props) { }; return ( -
+
props.setActiveTab({ type: 'all' })} > - Sessions {listCount} + SESSIONS {listCount}
props.setActiveTab({ type: 'bookmark' })} > - {`${isEnterprise ? 'Vault' : 'Bookmarks'}`} + {`${isEnterprise ? 'VAULT' : 'BOOKMARKS'}`}
diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx index 60d1aec6c..b5a475073 100644 --- a/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx @@ -60,7 +60,7 @@ function SessionList(props: Props) { show={!loading && list.size === 0} > {list.map((session: any) => ( - +
-
- +
))} diff --git a/frontend/app/theme/colors.js b/frontend/app/theme/colors.js index 97c65d0c5..51be4289d 100644 --- a/frontend/app/theme/colors.js +++ b/frontend/app/theme/colors.js @@ -42,5 +42,6 @@ module.exports = { default: '#DDDDDD', 'gray-light-shade': '#EEEEEE', 'primary': '#3490dc', + 'transparent': 'transparent', } } From 959d54be7ef1a2dcf3f2f3441910e189b8ec5899 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Mon, 1 Aug 2022 18:05:38 +0200 Subject: [PATCH 019/245] 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 020/245] 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 021/245] 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 f51519bc8656a4288881b81f9584c590dc2da7a9 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Thu, 7 Jul 2022 17:15:08 +0200 Subject: [PATCH 022/245] 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 023/245] 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 024/245] 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 76acafdd125129f4ec35fbf007b9c73c470df2fe Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 1 Aug 2022 19:04:30 +0200 Subject: [PATCH 025/245] fix(nginx): fixing base config Signed-off-by: rjshrjndrn --- frontend/nginx.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 0138027f0..0fc356092 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -5,5 +5,8 @@ server { index 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 26c8d769b78f18320e0446bd598b6fb144647ff4 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 1 Aug 2022 19:03:15 +0200 Subject: [PATCH 026/245] 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 0fc356092..b886096da 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; location / { From 898513dd0962251d55f70cab0d08928e440751eb Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 1 Aug 2022 19:11:34 +0200 Subject: [PATCH 027/245] 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 7dedafa7b70676e2d4f643c6c8a80f237a0f7ec4 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 1 Aug 2022 19:11:34 +0200 Subject: [PATCH 028/245] 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 4794a513eda31577bc132584e677164620ed9864 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 2 Aug 2022 11:29:49 +0200 Subject: [PATCH 029/245] feat(ui) - session list - tabs and settings --- .../components/SessionTags/SessionTags.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionTags/SessionTags.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionTags/SessionTags.tsx index 1c8f5c926..4d49ab83b 100644 --- a/frontend/app/components/shared/SessionListContainer/components/SessionTags/SessionTags.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/SessionTags/SessionTags.tsx @@ -54,12 +54,11 @@ function TagItem({ isActive, onClick, label, icon = '', disabled = false }: any) onClick={onClick} className={cn('transition group rounded ml-2 px-2 py-1 flex items-center uppercase text-sm hover:bg-teal hover:text-white', { 'bg-teal text-white': isActive, - 'bg-active-blue color-teal': !isActive, 'disabled': disabled, })} > - {icon && } - {label} + {icon && } + {label}
); From 01ec7865b7d083afe8ba928528be1f67ce74e0ca Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 2 Aug 2022 16:00:39 +0200 Subject: [PATCH 030/245] feat(chalice): EFS sessions --- api/.gitignore | 3 ++- api/app.py | 3 ++- api/chalicelib/core/assist.py | 10 +++++++++- api/env.default | 3 ++- api/routers/core.py | 15 +++++++++++++-- ee/api/app.py | 4 +++- ee/api/env.default | 1 + 7 files changed, 32 insertions(+), 7 deletions(-) diff --git a/api/.gitignore b/api/.gitignore index 6a46fedcb..68797b56a 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -174,4 +174,5 @@ logs*.txt SUBNETS.json ./chalicelib/.configs -README/* \ No newline at end of file +README/* +.local \ No newline at end of file diff --git a/api/app.py b/api/app.py index 4fd042d1a..cf00c747b 100644 --- a/api/app.py +++ b/api/app.py @@ -4,6 +4,7 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from decouple import config from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware +from fastapi.middleware.gzip import GZipMiddleware from starlette.responses import StreamingResponse from chalicelib.utils import helper @@ -14,7 +15,7 @@ from routers.crons import core_dynamic_crons from routers.subs import dashboard, insights, metrics, v1_api app = FastAPI(root_path="/api", docs_url=config("docs_url", default=""), redoc_url=config("redoc_url", default="")) - +app.add_middleware(GZipMiddleware, minimum_size=1000) @app.middleware('http') async def or_middleware(request: Request, call_next): diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py index 6fc8bcd90..e382fe348 100644 --- a/api/chalicelib/core/assist.py +++ b/api/chalicelib/core/assist.py @@ -1,6 +1,6 @@ import requests from decouple import config - +from os.path import exists import schemas from chalicelib.core import projects @@ -158,3 +158,11 @@ def autocomplete(project_id, q: str, key: str = None): def get_ice_servers(): return config("iceServers") if config("iceServers", default=None) is not None \ and len(config("iceServers")) > 0 else None + + +def get_raw_mob_by_id(project_id, session_id): + path_to_file = config("FS_DIR") + "/" + str(session_id) + + if exists(path_to_file): + return path_to_file + return None diff --git a/api/env.default b/api/env.default index aa14fc993..c99e1dc05 100644 --- a/api/env.default +++ b/api/env.default @@ -47,4 +47,5 @@ sessions_region=us-east-1 sourcemaps_bucket=sourcemaps sourcemaps_reader=http://127.0.0.1:9000/sourcemaps stage=default-foss -version_number=1.4.0 \ No newline at end of file +version_number=1.4.0 +FS_DIR=/mnt/efs \ No newline at end of file diff --git a/api/routers/core.py b/api/routers/core.py index f9629d8bb..816ffbd4a 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -2,6 +2,7 @@ from typing import Union, Optional from decouple import config from fastapi import Depends, Body, BackgroundTasks, HTTPException +from fastapi.responses import FileResponse from starlette import status import schemas @@ -183,8 +184,8 @@ def session_top_filter_values(projectId: int, context: schemas.CurrentContext = @app.get('/{projectId}/integrations', tags=["integrations"]) def get_integrations_status(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): data = integrations_global.get_global_integrations_status(tenant_id=context.tenant_id, - user_id=context.user_id, - project_id=projectId) + user_id=context.user_id, + project_id=projectId) return {"data": data} @@ -895,6 +896,16 @@ def get_live_session(projectId: int, sessionId: str, background_tasks: Backgroun return {'data': data} +@app.get('/{projectId}/assist/sessions/{sessionId}/replay', tags=["assist"]) +def get_live_session_replay_file(projectId: int, sessionId: str, + context: schemas.CurrentContext = Depends(OR_context)): + path = assist.get_raw_mob_by_id(project_id=projectId, session_id=sessionId) + if path is None: + return {"errors": ["Replay file not found"]} + + return FileResponse(path=path, media_type="application/octet-stream") + + @app.post('/{projectId}/heatmaps/url', tags=["heatmaps"]) def get_heatmaps_by_url(projectId: int, data: schemas.GetHeatmapPayloadSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): diff --git a/ee/api/app.py b/ee/api/app.py index 1e12e6015..9f2f9a306 100644 --- a/ee/api/app.py +++ b/ee/api/app.py @@ -5,18 +5,20 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from decouple import config from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware +from fastapi.middleware.gzip import GZipMiddleware from starlette import status from starlette.responses import StreamingResponse, JSONResponse from chalicelib.utils import helper from chalicelib.utils import pg_client from routers import core, core_dynamic, ee, saml -from routers.subs import v1_api from routers.crons import core_crons from routers.crons import core_dynamic_crons from routers.subs import dashboard, insights, metrics, v1_api_ee +from routers.subs import v1_api app = FastAPI(root_path="/api", docs_url=config("docs_url", default=""), redoc_url=config("redoc_url", default="")) +app.add_middleware(GZipMiddleware, minimum_size=1000) @app.middleware('http') diff --git a/ee/api/env.default b/ee/api/env.default index 7687566d7..70941ab99 100644 --- a/ee/api/env.default +++ b/ee/api/env.default @@ -57,3 +57,4 @@ sourcemaps_bucket=sourcemaps sourcemaps_reader=http://127.0.0.1:9000/sourcemaps stage=default-ee version_number=1.0.0 +FS_DIR=/mnt/efs \ No newline at end of file From 7af26795ace6ac23d44380c976166bf4e8d83c86 Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Tue, 2 Aug 2022 16:01:44 +0200 Subject: [PATCH 031/245] feat(chalice): EFS sessions (#651) --- api/.gitignore | 3 ++- api/app.py | 3 ++- api/chalicelib/core/assist.py | 10 +++++++++- api/env.default | 3 ++- api/routers/core.py | 15 +++++++++++++-- ee/api/app.py | 4 +++- ee/api/env.default | 1 + 7 files changed, 32 insertions(+), 7 deletions(-) diff --git a/api/.gitignore b/api/.gitignore index 6a46fedcb..68797b56a 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -174,4 +174,5 @@ logs*.txt SUBNETS.json ./chalicelib/.configs -README/* \ No newline at end of file +README/* +.local \ No newline at end of file diff --git a/api/app.py b/api/app.py index 4fd042d1a..cf00c747b 100644 --- a/api/app.py +++ b/api/app.py @@ -4,6 +4,7 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from decouple import config from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware +from fastapi.middleware.gzip import GZipMiddleware from starlette.responses import StreamingResponse from chalicelib.utils import helper @@ -14,7 +15,7 @@ from routers.crons import core_dynamic_crons from routers.subs import dashboard, insights, metrics, v1_api app = FastAPI(root_path="/api", docs_url=config("docs_url", default=""), redoc_url=config("redoc_url", default="")) - +app.add_middleware(GZipMiddleware, minimum_size=1000) @app.middleware('http') async def or_middleware(request: Request, call_next): diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py index 6fc8bcd90..e382fe348 100644 --- a/api/chalicelib/core/assist.py +++ b/api/chalicelib/core/assist.py @@ -1,6 +1,6 @@ import requests from decouple import config - +from os.path import exists import schemas from chalicelib.core import projects @@ -158,3 +158,11 @@ def autocomplete(project_id, q: str, key: str = None): def get_ice_servers(): return config("iceServers") if config("iceServers", default=None) is not None \ and len(config("iceServers")) > 0 else None + + +def get_raw_mob_by_id(project_id, session_id): + path_to_file = config("FS_DIR") + "/" + str(session_id) + + if exists(path_to_file): + return path_to_file + return None diff --git a/api/env.default b/api/env.default index aa14fc993..c99e1dc05 100644 --- a/api/env.default +++ b/api/env.default @@ -47,4 +47,5 @@ sessions_region=us-east-1 sourcemaps_bucket=sourcemaps sourcemaps_reader=http://127.0.0.1:9000/sourcemaps stage=default-foss -version_number=1.4.0 \ No newline at end of file +version_number=1.4.0 +FS_DIR=/mnt/efs \ No newline at end of file diff --git a/api/routers/core.py b/api/routers/core.py index f9629d8bb..816ffbd4a 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -2,6 +2,7 @@ from typing import Union, Optional from decouple import config from fastapi import Depends, Body, BackgroundTasks, HTTPException +from fastapi.responses import FileResponse from starlette import status import schemas @@ -183,8 +184,8 @@ def session_top_filter_values(projectId: int, context: schemas.CurrentContext = @app.get('/{projectId}/integrations', tags=["integrations"]) def get_integrations_status(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): data = integrations_global.get_global_integrations_status(tenant_id=context.tenant_id, - user_id=context.user_id, - project_id=projectId) + user_id=context.user_id, + project_id=projectId) return {"data": data} @@ -895,6 +896,16 @@ def get_live_session(projectId: int, sessionId: str, background_tasks: Backgroun return {'data': data} +@app.get('/{projectId}/assist/sessions/{sessionId}/replay', tags=["assist"]) +def get_live_session_replay_file(projectId: int, sessionId: str, + context: schemas.CurrentContext = Depends(OR_context)): + path = assist.get_raw_mob_by_id(project_id=projectId, session_id=sessionId) + if path is None: + return {"errors": ["Replay file not found"]} + + return FileResponse(path=path, media_type="application/octet-stream") + + @app.post('/{projectId}/heatmaps/url', tags=["heatmaps"]) def get_heatmaps_by_url(projectId: int, data: schemas.GetHeatmapPayloadSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): diff --git a/ee/api/app.py b/ee/api/app.py index 1e12e6015..9f2f9a306 100644 --- a/ee/api/app.py +++ b/ee/api/app.py @@ -5,18 +5,20 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from decouple import config from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware +from fastapi.middleware.gzip import GZipMiddleware from starlette import status from starlette.responses import StreamingResponse, JSONResponse from chalicelib.utils import helper from chalicelib.utils import pg_client from routers import core, core_dynamic, ee, saml -from routers.subs import v1_api from routers.crons import core_crons from routers.crons import core_dynamic_crons from routers.subs import dashboard, insights, metrics, v1_api_ee +from routers.subs import v1_api app = FastAPI(root_path="/api", docs_url=config("docs_url", default=""), redoc_url=config("redoc_url", default="")) +app.add_middleware(GZipMiddleware, minimum_size=1000) @app.middleware('http') diff --git a/ee/api/env.default b/ee/api/env.default index 7687566d7..70941ab99 100644 --- a/ee/api/env.default +++ b/ee/api/env.default @@ -57,3 +57,4 @@ sourcemaps_bucket=sourcemaps sourcemaps_reader=http://127.0.0.1:9000/sourcemaps stage=default-ee version_number=1.0.0 +FS_DIR=/mnt/efs \ No newline at end of file From e867c18300d3dff9518e47b4dd0da1097d467a09 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 2 Aug 2022 17:08:32 +0200 Subject: [PATCH 032/245] feat(chalice): changed EFS endpoint --- api/routers/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/routers/core.py b/api/routers/core.py index 816ffbd4a..91e07e493 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -896,6 +896,7 @@ def get_live_session(projectId: int, sessionId: str, background_tasks: Backgroun return {'data': data} +@app.get('/{projectId}/unprocessed/{sessionId}', tags=["assist"]) @app.get('/{projectId}/assist/sessions/{sessionId}/replay', tags=["assist"]) def get_live_session_replay_file(projectId: int, sessionId: str, context: schemas.CurrentContext = Depends(OR_context)): From 4ddbe3884519e8c0a2a28386925963034092ebb3 Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Tue, 2 Aug 2022 17:09:47 +0200 Subject: [PATCH 033/245] Api dev v1.8.0 (#653) * feat(chalice): EFS sessions * feat(chalice): changed EFS endpoint --- api/routers/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/routers/core.py b/api/routers/core.py index 816ffbd4a..91e07e493 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -896,6 +896,7 @@ def get_live_session(projectId: int, sessionId: str, background_tasks: Backgroun return {'data': data} +@app.get('/{projectId}/unprocessed/{sessionId}', tags=["assist"]) @app.get('/{projectId}/assist/sessions/{sessionId}/replay', tags=["assist"]) def get_live_session_replay_file(projectId: int, sessionId: str, context: schemas.CurrentContext = Depends(OR_context)): From f1e1c420ff4bc66e978ff7342fdeac5ccfe8e3f6 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Tue, 2 Aug 2022 17:49:21 +0200 Subject: [PATCH 034/245] chore(helm): chalice mount efs --- .../openreplay/charts/chalice/values.yaml | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/scripts/helmcharts/openreplay/charts/chalice/values.yaml b/scripts/helmcharts/openreplay/charts/chalice/values.yaml index 2c9d75040..29e036522 100644 --- a/scripts/helmcharts/openreplay/charts/chalice/values.yaml +++ b/scripts/helmcharts/openreplay/charts/chalice/values.yaml @@ -122,13 +122,14 @@ healthCheck: 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 +persistence: + # Spec of spec.template.spec.containers[*].volumeMounts + mounts: + - mountPath: /mnt/efs + name: datadir + # Spec of spec.template.spec.volumes + volumes: + - hostPath: + path: /openreplay/storage/nfs + type: DirectoryOrCreate + name: datadir From 60c6f127521d6cd7e33ed1e4124381692d7c0fa2 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 2 Aug 2022 20:13:51 +0200 Subject: [PATCH 035/245] fix(tracker-fetch):3.5.10-beta.0: fix SSR; wrap custom fetch from options --- tracker/tracker-fetch/package.json | 2 +- tracker/tracker-fetch/src/index.ts | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tracker/tracker-fetch/package.json b/tracker/tracker-fetch/package.json index c13b1a28b..08716fb52 100644 --- a/tracker/tracker-fetch/package.json +++ b/tracker/tracker-fetch/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker-fetch", "description": "Tracker plugin for fetch requests recording ", - "version": "3.5.3", + "version": "3.5.10-beta.0", "keywords": [ "fetch", "logging", diff --git a/tracker/tracker-fetch/src/index.ts b/tracker/tracker-fetch/src/index.ts index 922913923..7a5ae6016 100644 --- a/tracker/tracker-fetch/src/index.ts +++ b/tracker/tracker-fetch/src/index.ts @@ -18,24 +18,33 @@ interface RequestResponseData { response: ResponseData } +type WindowFetch = typeof fetch export interface Options { + fetch: WindowFetch, sessionTokenHeader?: string failuresOnly: boolean overrideGlobal: boolean ignoreHeaders: Array | boolean sanitiser?: (RequestResponseData) => RequestResponseData | null + // Depricated requestSanitizer?: any responseSanitizer?: any } -export default function(opts: Partial = {}) { +export default function(opts: Partial = {}): (app: App | null) => WindowFetch | null { + if (typeof window === 'undefined') { + // not in browser (SSR) + return () => opts.fetch || null + } + const options: Options = Object.assign( { overrideGlobal: false, failuresOnly: false, ignoreHeaders: [ 'Cookie', 'Set-Cookie', 'Authorization' ], + fetch: window.fetch, }, opts, ); @@ -43,10 +52,9 @@ export default function(opts: Partial = {}) { console.warn("OpenReplay fetch plugin: `requestSanitizer` and `responseSanitizer` options are depricated. Please, use `sanitiser` instead (check out documentation at https://docs.openreplay.com/plugins/fetch).") } - const origFetch = window.fetch return (app: App | null) => { if (app === null) { - return origFetch + return options.fetch } const ihOpt = options.ignoreHeaders @@ -56,7 +64,7 @@ export default function(opts: Partial = {}) { const fetch = async (input: RequestInfo, init: RequestInit = {}) => { if (typeof input !== 'string') { - return origFetch(input, init); + return options.fetch(input, init); } if (options.sessionTokenHeader) { const sessionToken = app.getSessionToken(); @@ -74,7 +82,7 @@ export default function(opts: Partial = {}) { } } const startTime = performance.now(); - const response = await origFetch(input, init); + const response = await options.fetch(input, init); const duration = performance.now() - startTime; if (options.failuresOnly && response.status < 400) { return response From a7183590bc7ac56b40f5c560a8f8a9e49f70e798 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Wed, 3 Aug 2022 16:33:24 +0200 Subject: [PATCH 036/245] feat(chalice): jira fix feat(chalice): jira upgrade feat(chalice): github fix --- api/requirements-alerts.txt | 2 +- api/requirements.txt | 2 +- api/routers/core.py | 6 ++---- api/routers/core_dynamic.py | 12 ------------ api/schemas.py | 8 +++++--- ee/api/requirements-alerts.txt | 2 +- ee/api/requirements-crons.txt | 2 +- ee/api/requirements.txt | 2 +- ee/api/routers/core_dynamic.py | 12 ------------ 9 files changed, 12 insertions(+), 36 deletions(-) diff --git a/api/requirements-alerts.txt b/api/requirements-alerts.txt index 81198b0f3..788c58767 100644 --- a/api/requirements-alerts.txt +++ b/api/requirements-alerts.txt @@ -4,7 +4,7 @@ boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 elasticsearch==8.3.1 -jira==3.3.0 +jira==3.3.1 diff --git a/api/requirements.txt b/api/requirements.txt index 81198b0f3..788c58767 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -4,7 +4,7 @@ boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 elasticsearch==8.3.1 -jira==3.3.0 +jira==3.3.1 diff --git a/api/routers/core.py b/api/routers/core.py index 91e07e493..a675a8b0d 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -443,27 +443,25 @@ def get_integration_status(context: schemas.CurrentContext = Depends(OR_context) @app.post('/integrations/jira', tags=["integrations"]) @app.put('/integrations/jira', tags=["integrations"]) -def add_edit_jira_cloud(data: schemas.JiraGithubSchema = Body(...), +def add_edit_jira_cloud(data: schemas.JiraSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): error, integration = integrations_manager.get_integration(tool=integration_jira_cloud.PROVIDER, tenant_id=context.tenant_id, user_id=context.user_id) if error is not None and integration is None: return error - data.provider = integration_jira_cloud.PROVIDER return {"data": integration.add_edit(data=data.dict())} @app.post('/integrations/github', tags=["integrations"]) @app.put('/integrations/github', tags=["integrations"]) -def add_edit_github(data: schemas.JiraGithubSchema = Body(...), +def add_edit_github(data: schemas.GithubSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): error, integration = integrations_manager.get_integration(tool=integration_github.PROVIDER, tenant_id=context.tenant_id, user_id=context.user_id) if error is not None: return error - data.provider = integration_github.PROVIDER return {"data": integration.add_edit(data=data.dict())} diff --git a/api/routers/core_dynamic.py b/api/routers/core_dynamic.py index 594715bb6..bddb0ae4d 100644 --- a/api/routers/core_dynamic.py +++ b/api/routers/core_dynamic.py @@ -87,18 +87,6 @@ def edit_slack_integration(integrationId: int, data: schemas.EditSlackSchema = B changes={"name": data.name, "endpoint": data.url})} -# this endpoint supports both jira & github based on `provider` attribute -@app.post('/integrations/issues', tags=["integrations"]) -def add_edit_jira_cloud_github(data: schemas.JiraGithubSchema, - context: schemas.CurrentContext = Depends(OR_context)): - provider = data.provider.upper() - error, integration = integrations_manager.get_integration(tool=provider, tenant_id=context.tenant_id, - user_id=context.user_id) - if error is not None: - return error - return {"data": integration.add_edit(data=data.dict())} - - @app.post('/client/members', tags=["client"]) @app.put('/client/members', tags=["client"]) def add_member(background_tasks: BackgroundTasks, data: schemas.CreateMemberSchema = Body(...), diff --git a/api/schemas.py b/api/schemas.py index 81d2bffab..2289c03cb 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -100,10 +100,12 @@ class NotificationsViewSchema(BaseModel): endTimestamp: Optional[int] = Field(default=None) -class JiraGithubSchema(BaseModel): - provider: str = Field(...) - username: str = Field(...) +class GithubSchema(BaseModel): token: str = Field(...) + + +class JiraSchema(GithubSchema): + username: str = Field(...) url: HttpUrl = Field(...) @validator('url') diff --git a/ee/api/requirements-alerts.txt b/ee/api/requirements-alerts.txt index 66fa84713..906189999 100644 --- a/ee/api/requirements-alerts.txt +++ b/ee/api/requirements-alerts.txt @@ -4,7 +4,7 @@ boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 elasticsearch==8.3.1 -jira==3.3.0 +jira==3.3.1 diff --git a/ee/api/requirements-crons.txt b/ee/api/requirements-crons.txt index 66fa84713..906189999 100644 --- a/ee/api/requirements-crons.txt +++ b/ee/api/requirements-crons.txt @@ -4,7 +4,7 @@ boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 elasticsearch==8.3.1 -jira==3.3.0 +jira==3.3.1 diff --git a/ee/api/requirements.txt b/ee/api/requirements.txt index 5ce044904..0a8ca819e 100644 --- a/ee/api/requirements.txt +++ b/ee/api/requirements.txt @@ -4,7 +4,7 @@ boto3==1.24.26 pyjwt==2.4.0 psycopg2-binary==2.9.3 elasticsearch==8.3.1 -jira==3.3.0 +jira==3.3.1 diff --git a/ee/api/routers/core_dynamic.py b/ee/api/routers/core_dynamic.py index 3c5c21905..9d09198a6 100644 --- a/ee/api/routers/core_dynamic.py +++ b/ee/api/routers/core_dynamic.py @@ -90,18 +90,6 @@ def edit_slack_integration(integrationId: int, data: schemas.EditSlackSchema = B changes={"name": data.name, "endpoint": data.url})} -# this endpoint supports both jira & github based on `provider` attribute -@app.post('/integrations/issues', tags=["integrations"]) -def add_edit_jira_cloud_github(data: schemas.JiraGithubSchema, - context: schemas.CurrentContext = Depends(OR_context)): - provider = data.provider.upper() - error, integration = integrations_manager.get_integration(tool=provider, tenant_id=context.tenant_id, - user_id=context.user_id) - if error is not None: - return error - return {"data": integration.add_edit(data=data.dict())} - - @app.post('/client/members', tags=["client"]) @app.put('/client/members', tags=["client"]) def add_member(background_tasks: BackgroundTasks, data: schemas_ee.CreateMemberSchema = Body(...), From 54d7c1a72c6da09addf5d4f4bdf2d1d9c4b93f30 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Wed, 3 Aug 2022 16:58:10 +0200 Subject: [PATCH 037/245] chore(docker): removing edge busybox, as the main repo udpated Signed-off-by: rjshrjndrn --- api/Dockerfile | 2 -- api/Dockerfile.alerts | 1 - backend/Dockerfile | 1 - ee/api/Dockerfile | 2 -- ee/api/Dockerfile.alerts | 1 - ee/api/Dockerfile.crons | 1 - ee/utilities/Dockerfile | 1 - frontend/Dockerfile | 1 - peers/Dockerfile | 1 - scripts/dockerfiles/alpine/Dockerfile | 1 - scripts/dockerfiles/ingress-controller/Dockerfile | 1 - scripts/dockerfiles/nginx/Dockerfile | 1 - utilities/Dockerfile | 1 - 13 files changed, 15 deletions(-) diff --git a/api/Dockerfile b/api/Dockerfile index a6077fd13..20dfe9b86 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,9 +1,7 @@ FROM python:3.10-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache build-base nodejs npm tini -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main ARG envarg # Add Tini # Startup daemon diff --git a/api/Dockerfile.alerts b/api/Dockerfile.alerts index c4614b3c1..dbb0c581d 100644 --- a/api/Dockerfile.alerts +++ b/api/Dockerfile.alerts @@ -1,7 +1,6 @@ FROM python:3.10-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache build-base tini ARG envarg ENV APP_NAME=alerts \ diff --git a/backend/Dockerfile b/backend/Dockerfile index fe8f9e7e4..4941a47cb 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -19,7 +19,6 @@ RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o service -tags musl openrep FROM alpine AS entrypoint -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache ca-certificates RUN adduser -u 1001 openreplay -D diff --git a/ee/api/Dockerfile b/ee/api/Dockerfile index 2a2aef8f0..2e04fa330 100644 --- a/ee/api/Dockerfile +++ b/ee/api/Dockerfile @@ -1,9 +1,7 @@ FROM python:3.10-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache build-base libressl libffi-dev libressl-dev libxslt-dev libxml2-dev xmlsec-dev xmlsec nodejs npm tini -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main ARG envarg ENV SOURCE_MAP_VERSION=0.7.4 \ APP_NAME=chalice \ diff --git a/ee/api/Dockerfile.alerts b/ee/api/Dockerfile.alerts index 785b0a5f9..351fce661 100644 --- a/ee/api/Dockerfile.alerts +++ b/ee/api/Dockerfile.alerts @@ -1,7 +1,6 @@ FROM python:3.10-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache build-base tini ARG envarg ENV APP_NAME=alerts \ diff --git a/ee/api/Dockerfile.crons b/ee/api/Dockerfile.crons index 0647c6fc6..96b9e6453 100644 --- a/ee/api/Dockerfile.crons +++ b/ee/api/Dockerfile.crons @@ -1,7 +1,6 @@ FROM python:3.10-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache build-base tini ARG envarg ENV APP_NAME=crons \ diff --git a/ee/utilities/Dockerfile b/ee/utilities/Dockerfile index 2de6197a2..b4592048d 100644 --- a/ee/utilities/Dockerfile +++ b/ee/utilities/Dockerfile @@ -1,6 +1,5 @@ FROM node:18-alpine LABEL Maintainer="KRAIEM Taha Yassine" -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache tini git libc6-compat && ln -s /lib/libc.musl-x86_64.so.1 /lib/ld-linux-x86-64.so.2 ARG envarg diff --git a/frontend/Dockerfile b/frontend/Dockerfile index bfa86857d..5e6c9b3b0 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -14,7 +14,6 @@ COPY nginx.conf /etc/nginx/conf.d/default.conf # Default step in docker build FROM nginx:alpine LABEL maintainer=Rajesh -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main COPY --from=builder /work/public /var/www/openreplay COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/peers/Dockerfile b/peers/Dockerfile index b05fdee3a..c22e33f37 100644 --- a/peers/Dockerfile +++ b/peers/Dockerfile @@ -1,7 +1,6 @@ FROM node:18-alpine LABEL Maintainer="KRAIEM Taha Yassine" RUN apk add --no-cache tini -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main ARG envarg ENV ENTERPRISE_BUILD=${envarg} diff --git a/scripts/dockerfiles/alpine/Dockerfile b/scripts/dockerfiles/alpine/Dockerfile index db49d3c3d..f3f03a617 100644 --- a/scripts/dockerfiles/alpine/Dockerfile +++ b/scripts/dockerfiles/alpine/Dockerfile @@ -1,3 +1,2 @@ FROM alpine # Fix busybox vulnerability -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main diff --git a/scripts/dockerfiles/ingress-controller/Dockerfile b/scripts/dockerfiles/ingress-controller/Dockerfile index 85f58d272..8572bca26 100644 --- a/scripts/dockerfiles/ingress-controller/Dockerfile +++ b/scripts/dockerfiles/ingress-controller/Dockerfile @@ -1,5 +1,4 @@ from k8s.gcr.io/ingress-nginx/controller:v1.3.0 # Fix critical vulnerability user 0 -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main user 101 diff --git a/scripts/dockerfiles/nginx/Dockerfile b/scripts/dockerfiles/nginx/Dockerfile index caf6b283b..d09f15770 100644 --- a/scripts/dockerfiles/nginx/Dockerfile +++ b/scripts/dockerfiles/nginx/Dockerfile @@ -173,7 +173,6 @@ STOPSIGNAL SIGQUIT # Openreplay Custom configs -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main # Adding prometheus monitoring support ADD https://raw.githubusercontent.com/knyar/nginx-lua-prometheus/master/prometheus.lua /usr/local/openresty/lualib/ ADD https://raw.githubusercontent.com/knyar/nginx-lua-prometheus/master/prometheus_keys.lua /usr/local/openresty/lualib/ diff --git a/utilities/Dockerfile b/utilities/Dockerfile index 2de6197a2..b4592048d 100644 --- a/utilities/Dockerfile +++ b/utilities/Dockerfile @@ -1,6 +1,5 @@ FROM node:18-alpine LABEL Maintainer="KRAIEM Taha Yassine" -RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache tini git libc6-compat && ln -s /lib/libc.musl-x86_64.so.1 /lib/ld-linux-x86-64.so.2 ARG envarg From c1ad198a65a3344abee0dc72354f512298c939a5 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Wed, 3 Aug 2022 17:36:33 +0200 Subject: [PATCH 038/245] feat(chalice): jira single status feat(chalice): github single status --- api/routers/core.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/api/routers/core.py b/api/routers/core.py index a675a8b0d..2263a30bd 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -441,6 +441,26 @@ def get_integration_status(context: schemas.CurrentContext = Depends(OR_context) return {"data": integration.get_obfuscated()} +@app.get('/integrations/jira', tags=["integrations"]) +def get_integration_status_jira(context: schemas.CurrentContext = Depends(OR_context)): + error, integration = integrations_manager.get_integration(tenant_id=context.tenant_id, + user_id=context.user_id, + tool=integration_jira_cloud.PROVIDER) + if error is not None and integration is None: + return error + return {"data": integration.get_obfuscated()} + + +@app.get('/integrations/github', tags=["integrations"]) +def get_integration_status_github(context: schemas.CurrentContext = Depends(OR_context)): + error, integration = integrations_manager.get_integration(tenant_id=context.tenant_id, + user_id=context.user_id, + tool=integration_github.PROVIDER) + if error is not None and integration is None: + return error + return {"data": integration.get_obfuscated()} + + @app.post('/integrations/jira', tags=["integrations"]) @app.put('/integrations/jira', tags=["integrations"]) def add_edit_jira_cloud(data: schemas.JiraSchema = Body(...), From 247aa530f5d1770b32f74e31dc9fdb9e799d15b9 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Wed, 3 Aug 2022 18:42:28 +0200 Subject: [PATCH 039/245] feat(chalice): jira timeout --- api/chalicelib/utils/jira_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/chalicelib/utils/jira_client.py b/api/chalicelib/utils/jira_client.py index b1734660c..f03e304e6 100644 --- a/api/chalicelib/utils/jira_client.py +++ b/api/chalicelib/utils/jira_client.py @@ -18,7 +18,7 @@ class JiraManager: self._config = {"JIRA_PROJECT_ID": project_id, "JIRA_URL": url, "JIRA_USERNAME": username, "JIRA_PASSWORD": password} try: - self._jira = JIRA(url, basic_auth=(username, password), logging=True, max_retries=1) + self._jira = JIRA(url, basic_auth=(username, password), logging=True, max_retries=1, timeout=3) except Exception as e: print("!!! JIRA AUTH ERROR") print(e) From 644c86fb9def88b93dd973163d3c865380836b17 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Wed, 3 Aug 2022 18:58:50 +0200 Subject: [PATCH 040/245] feat(chalice): jira max_retries --- api/chalicelib/utils/jira_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/chalicelib/utils/jira_client.py b/api/chalicelib/utils/jira_client.py index f03e304e6..4306cfab2 100644 --- a/api/chalicelib/utils/jira_client.py +++ b/api/chalicelib/utils/jira_client.py @@ -18,7 +18,7 @@ class JiraManager: self._config = {"JIRA_PROJECT_ID": project_id, "JIRA_URL": url, "JIRA_USERNAME": username, "JIRA_PASSWORD": password} try: - self._jira = JIRA(url, basic_auth=(username, password), logging=True, max_retries=1, timeout=3) + self._jira = JIRA(url, basic_auth=(username, password), logging=True, max_retries=0, timeout=3) except Exception as e: print("!!! JIRA AUTH ERROR") print(e) From 5a32244db0b430d79470905ff8773f5f3c9fab42 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Wed, 3 Aug 2022 19:53:00 +0200 Subject: [PATCH 041/245] 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 4941a47cb..c2375d800 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -45,6 +45,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 44d176f9779a50d78beb02c1764a780372a8debb Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Wed, 3 Aug 2022 20:22:27 +0200 Subject: [PATCH 042/245] 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 7a07035771098d2cb9c5e189bf7f345a8460f539 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Wed, 3 Aug 2022 19:53:00 +0200 Subject: [PATCH 043/245] 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 044/245] 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 0c0dd30a73fb4b66fdd0983bcdd97ea7f35c7a2d Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 4 Aug 2022 12:21:25 +0200 Subject: [PATCH 045/245] Preferences - UI and API improvements (#654) * fix(tracker): fix assist typings * fix(tracker): fix assist typings * change(ui) - preferences - removed old * change(ui) - preferences - wip * change(ui) - preferences - list * change(ui) - right box mardings * change(ui) - preferences - integration item paddings * change(ui) - preferences - integration icons * change(ui) - preferences - integration icons * change(ui) - preferences - integration - check status * change(ui) - preferences - integration - check status * change(ui) - preferences - metadata - move the delete button inside the modal * change(ui) - preferences - webhooks - modal and delete btn changes * change(ui) - preferences - modalContext updates * change(ui) - input field forward refs * change(ui) - metadata - modal * change(ui) - metadata - set deleting item to null * change(ui) - integrations * change(ui) - hoc withcopy * change(ui) - projects * change(ui) - users list modal * change(ui) - projects remove border for the last * change(ui) - integrations new api changes * change(ui) - github and jira changes * change(ui) - github and jira changes Co-authored-by: sylenien --- frontend/app/assets/integrations/aws.svg | 5 + frontend/app/assets/integrations/bugsnag.svg | 5 +- .../app/assets/integrations/google-cloud.svg | 6 + frontend/app/assets/integrations/newrelic.svg | 13 +- frontend/app/assets/integrations/rollbar.svg | 26 +- frontend/app/components/Client/Client.js | 2 +- .../Client/CustomFields/CustomFieldForm.js | 113 ++++---- .../Client/CustomFields/CustomFields.js | 217 +++++++-------- .../Client/CustomFields/ListItem.js | 29 +- .../Integrations/AssistDoc/AssistDoc.js | 79 +++--- .../Client/Integrations/AxiosDoc/AxiosDoc.js | 73 ++--- .../Integrations/BugsnagForm/BugsnagForm.js | 47 ++-- .../CloudwatchForm/CloudwatchForm.js | 69 ++--- .../Client/Integrations/DatadogForm.js | 43 +-- .../Client/Integrations/ElasticsearchForm.js | 139 +++++----- .../Client/Integrations/FetchDoc/FetchDoc.js | 70 ++--- .../Client/Integrations/GithubForm.js | 45 +-- .../Integrations/GraphQLDoc/GraphQLDoc.js | 73 ++--- .../Client/Integrations/IntegrationForm.js | 261 +++++++++--------- .../Client/Integrations/IntegrationItem.js | 27 -- .../Client/Integrations/IntegrationItem.tsx | 39 +++ .../{Integrations.js => Integrations.js_} | 0 .../Client/Integrations/Integrations.tsx | 166 +++++++++++ .../Client/Integrations/JiraForm/JiraForm.js | 62 +++-- .../Client/Integrations/MobxDoc/MobxDoc.js | 73 ++--- .../Integrations/NewrelicForm/NewrelicForm.js | 52 ++-- .../Client/Integrations/NgRxDoc/NgRxDoc.js | 70 ++--- .../Integrations/ProfilerDoc/ProfilerDoc.js | 70 ++--- .../Client/Integrations/ReduxDoc/ReduxDoc.js | 66 ++--- .../Client/Integrations/RollbarForm.js | 36 +-- .../Client/Integrations/SentryForm.js | 50 ++-- .../Integrations/SlackAddForm/SlackAddForm.js | 176 ++++++------ .../SlackChannelList/SlackChannelList.js | 80 +++--- .../Client/Integrations/SlackForm.js | 15 - .../Client/Integrations/SlackForm.tsx | 48 ++++ .../Client/Integrations/StackdriverForm.js | 43 +-- .../SumoLogicForm/SumoLogicForm.js | 48 ++-- .../Client/Integrations/VueDoc/VueDoc.js | 70 ++--- .../Integrations/_IntegrationItem .js_old | 42 --- .../Integrations/integrationItem.module.css | 2 +- .../Client/Notifications/Notifications.js | 80 +++--- .../Client/PreferencesMenu/PreferencesMenu.js | 34 +-- .../Sites/AddProjectButton/AddUserButton.tsx | 22 +- .../Sites/InstallButton/InstallButton.tsx | 25 ++ .../Client/Sites/InstallButton/index.ts | 1 + .../components/Client/Sites/NewSiteForm.js | 211 +++++++------- .../components/Client/Sites/ProjectKey.tsx | 8 + frontend/app/components/Client/Sites/Sites.js | 137 +++------ .../app/components/Client/Users/UsersView.tsx | 30 +- .../Users/components/UserList/UserList.tsx | 22 +- .../components/Client/Webhooks/ListItem.js | 29 +- .../components/Client/Webhooks/WebhookForm.js | 147 +++++----- .../components/Client/Webhooks/Webhooks.js | 139 +++++----- frontend/app/components/Modal/Modal.tsx | 22 +- .../app/components/Modal/ModalOverlay.tsx | 12 +- frontend/app/components/Modal/index.tsx | 95 ++++--- frontend/app/components/hocs/index.js | 3 +- frontend/app/components/hocs/withCopy.tsx | 27 ++ frontend/app/components/hocs/withRequest.js | 120 ++++---- .../TrackingCodeModal/TrackingCodeModal.js | 110 ++++---- frontend/app/components/ui/Form/Form.tsx | 27 +- frontend/app/components/ui/Input/Input.tsx | 6 +- frontend/app/components/ui/SVG.tsx | 8 +- .../app/components/ui/TextLink/TextLink.js | 30 +- frontend/app/components/ui/Toggler/Toggler.js | 32 +-- frontend/app/duck/integrations/actions.js | 56 ++-- frontend/app/duck/integrations/index.js | 32 ++- .../app/duck/integrations/integrations.js | 40 +++ frontend/app/duck/integrations/reducer.js | 82 +++--- frontend/app/duck/integrations/slack.js | 103 ++++--- .../app/svg/icons/integrations/bugsnag.svg | 5 +- .../app/svg/icons/integrations/newrelic.svg | 13 +- .../app/svg/icons/integrations/rollbar.svg | 26 +- .../app/types/integrations/githubConfig.js | 75 ++--- frontend/app/types/integrations/jiraConfig.js | 83 +++--- frontend/app/utils.ts | 5 + tracker/tracker-assist/src/Assist.ts | 8 +- 77 files changed, 2391 insertions(+), 2064 deletions(-) create mode 100644 frontend/app/assets/integrations/aws.svg create mode 100644 frontend/app/assets/integrations/google-cloud.svg delete mode 100644 frontend/app/components/Client/Integrations/IntegrationItem.js create mode 100644 frontend/app/components/Client/Integrations/IntegrationItem.tsx rename frontend/app/components/Client/Integrations/{Integrations.js => Integrations.js_} (100%) create mode 100644 frontend/app/components/Client/Integrations/Integrations.tsx delete mode 100644 frontend/app/components/Client/Integrations/SlackForm.js create mode 100644 frontend/app/components/Client/Integrations/SlackForm.tsx delete mode 100644 frontend/app/components/Client/Integrations/_IntegrationItem .js_old create mode 100644 frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx create mode 100644 frontend/app/components/Client/Sites/InstallButton/index.ts create mode 100644 frontend/app/components/Client/Sites/ProjectKey.tsx create mode 100644 frontend/app/components/hocs/withCopy.tsx create mode 100644 frontend/app/duck/integrations/integrations.js diff --git a/frontend/app/assets/integrations/aws.svg b/frontend/app/assets/integrations/aws.svg new file mode 100644 index 000000000..c18fbdab2 --- /dev/null +++ b/frontend/app/assets/integrations/aws.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/app/assets/integrations/bugsnag.svg b/frontend/app/assets/integrations/bugsnag.svg index 26a3a13b8..cc97e195b 100644 --- a/frontend/app/assets/integrations/bugsnag.svg +++ b/frontend/app/assets/integrations/bugsnag.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + diff --git a/frontend/app/assets/integrations/google-cloud.svg b/frontend/app/assets/integrations/google-cloud.svg new file mode 100644 index 000000000..93f614043 --- /dev/null +++ b/frontend/app/assets/integrations/google-cloud.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/app/assets/integrations/newrelic.svg b/frontend/app/assets/integrations/newrelic.svg index cc4aea514..061e7e0a3 100644 --- a/frontend/app/assets/integrations/newrelic.svg +++ b/frontend/app/assets/integrations/newrelic.svg @@ -1 +1,12 @@ -NewRelic-logo-square \ No newline at end of file + + + + + + + + + + + + diff --git a/frontend/app/assets/integrations/rollbar.svg b/frontend/app/assets/integrations/rollbar.svg index 2f6538118..0d183182b 100644 --- a/frontend/app/assets/integrations/rollbar.svg +++ b/frontend/app/assets/integrations/rollbar.svg @@ -1,20 +1,10 @@ - - - - -rollbar-logo-color-vertical - - - - - + + + + + + + + diff --git a/frontend/app/components/Client/Client.js b/frontend/app/components/Client/Client.js index adae9f536..94af41ec1 100644 --- a/frontend/app/components/Client/Client.js +++ b/frontend/app/components/Client/Client.js @@ -52,7 +52,7 @@ export default class Client extends React.PureComponent {
-
+
{ activeTab && this.renderActiveTab() }
diff --git a/frontend/app/components/Client/CustomFields/CustomFieldForm.js b/frontend/app/components/Client/CustomFields/CustomFieldForm.js index 76ee849d5..adc9ac884 100644 --- a/frontend/app/components/Client/CustomFields/CustomFieldForm.js +++ b/frontend/app/components/Client/CustomFields/CustomFieldForm.js @@ -4,59 +4,74 @@ import { edit, save } from 'Duck/customField'; import { Form, Input, Button, Message } from 'UI'; import styles from './customFieldForm.module.css'; -@connect(state => ({ - field: state.getIn(['customFields', 'instance']), - saving: state.getIn(['customFields', 'saveRequest', 'loading']), - errors: state.getIn([ 'customFields', 'saveRequest', 'errors' ]), -}), { - edit, - save, -}) +@connect( + (state) => ({ + field: state.getIn(['customFields', 'instance']), + saving: state.getIn(['customFields', 'saveRequest', 'loading']), + errors: state.getIn(['customFields', 'saveRequest', 'errors']), + }), + { + edit, + save, + } +) class CustomFieldForm extends React.PureComponent { - setFocus = () => this.focusElement.focus(); - onChangeSelect = (event, { name, value }) => this.props.edit({ [ name ]: value }); - write = ({ target: { value, name } }) => this.props.edit({ [ name ]: value }); + setFocus = () => this.focusElement.focus(); + onChangeSelect = (event, { name, value }) => this.props.edit({ [name]: value }); + write = ({ target: { value, name } }) => this.props.edit({ [name]: value }); - render() { - const { field, errors} = this.props; - const exists = field.exists(); - return ( -
- - - { this.focusElement = ref; } } - name="key" - value={ field.key } - onChange={ this.write } - placeholder="Field Name" - /> - + render() { + const { field, errors } = this.props; + const exists = field.exists(); + return ( +
+

{exists ? 'Update' : 'Add'} Metadata Field

+ + + + { + this.focusElement = ref; + }} + name="key" + value={field.key} + onChange={this.write} + placeholder="Field Name" + /> + - { errors && -
- { errors.map(error => { error }) } -
- } + {errors && ( +
+ {errors.map((error) => ( + + {error} + + ))} +
+ )} - - - - ); - } +
+
+ + +
+ + +
+ +
+ ); + } } export default CustomFieldForm; diff --git a/frontend/app/components/Client/CustomFields/CustomFields.js b/frontend/app/components/Client/CustomFields/CustomFields.js index 4c3d0bbc8..d5abbb2c6 100644 --- a/frontend/app/components/Client/CustomFields/CustomFields.js +++ b/frontend/app/components/Client/CustomFields/CustomFields.js @@ -1,8 +1,8 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; import withPageTitle from 'HOCs/withPageTitle'; -import { IconButton, SlideModal, Loader, NoContent, Icon, TextLink } from 'UI'; +import { Button, Loader, NoContent, TextLink } from 'UI'; import { init, fetchList, save, remove } from 'Duck/customField'; import SiteDropdown from 'Shared/SiteDropdown'; import styles from './customFields.module.css'; @@ -10,121 +10,118 @@ import CustomFieldForm from './CustomFieldForm'; import ListItem from './ListItem'; import { confirm } from 'UI'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import { useModal } from 'App/components/Modal'; -@connect(state => ({ - fields: state.getIn(['customFields', 'list']).sortBy(i => i.index), - field: state.getIn(['customFields', 'instance']), - loading: state.getIn(['customFields', 'fetchRequest', 'loading']), - sites: state.getIn([ 'site', 'list' ]), - errors: state.getIn([ 'customFields', 'saveRequest', 'errors' ]), -}), { - init, - fetchList, - save, - remove, -}) -@withPageTitle('Metadata - OpenReplay Preferences') -class CustomFields extends React.Component { - state = { showModal: false, currentSite: this.props.sites.get(0), deletingItem: null }; +function CustomFields(props) { + const [currentSite, setCurrentSite] = React.useState(props.sites.get(0)); + const [deletingItem, setDeletingItem] = React.useState(null); + const { showModal, hideModal } = useModal(); - componentWillMount() { - const activeSite = this.props.sites.get(0); - if (!activeSite) return; - - this.props.fetchList(activeSite.id); - } + useEffect(() => { + const activeSite = props.sites.get(0); + if (!activeSite) return; - save = (field) => { - const { currentSite } = this.state; - this.props.save(currentSite.id, field).then(() => { - const { errors } = this.props; - if (!errors || errors.size === 0) { - return this.closeModal(); - } - }); - }; + props.fetchList(activeSite.id); + }, []); - closeModal = () => this.setState({ showModal: false }); - init = (field) => { - this.props.init(field); - this.setState({ showModal: true }); - } - - onChangeSelect = ({ value }) => { - const site = this.props.sites.find(s => s.id === value.value); - this.setState({ currentSite: site }) - this.props.fetchList(site.id); - } - - removeMetadata = async (field) => { - if (await confirm({ - header: 'Metadata', - confirmation: `Are you sure you want to remove?` - })) { - const { currentSite } = this.state; - this.setState({ deletingItem: field.index }); - this.props.remove(currentSite.id, field.index) - .then(() => this.setState({ deletingItem: null })); - } - } - - render() { - const { fields, field, loading } = this.props; - const { showModal, currentSite, deletingItem } = this.state; - return ( -
- } - onClose={ this.closeModal } - /> -
-

{ 'Metadata' }

-
- -
- this.init() } /> - -
- - - - -
No data available.
-
+ const save = (field) => { + props.save(currentSite.id, field).then(() => { + const { errors } = props; + if (!errors || errors.size === 0) { + hideModal(); } - size="small" - show={ fields.size === 0 } - // animatedIcon="empty-state" - > -
- { fields.filter(i => i.index).map(field => ( - this.removeMetadata(field) } + }); + }; + + const init = (field) => { + props.init(field); + showModal( removeMetadata(field)} />); + }; + + const onChangeSelect = ({ value }) => { + const site = props.sites.find((s) => s.id === value.value); + setCurrentSite(site); + props.fetchList(site.id); + }; + + const removeMetadata = async (field) => { + if ( + await confirm({ + header: 'Metadata', + confirmation: `Are you sure you want to remove?`, + }) + ) { + setDeletingItem(field.index); + props + .remove(currentSite.id, field.index) + .then(() => { + hideModal(); + }) + .finally(() => { + setDeletingItem(null); + }); + } + }; + + const { fields, loading } = props; + return ( +
+
+

{'Metadata'}

+
+ +
+
- - -
+ + + + +
No data available.
+
+ } + size="small" + show={fields.size === 0} + > +
+ {fields + .filter((i) => i.index) + .map((field) => ( + removeMetadata(field) } + /> + ))} +
+ + +
); - } } -export default CustomFields; +export default connect( + (state) => ({ + fields: state.getIn(['customFields', 'list']).sortBy((i) => i.index), + field: state.getIn(['customFields', 'instance']), + loading: state.getIn(['customFields', 'fetchRequest', 'loading']), + sites: state.getIn(['site', 'list']), + errors: state.getIn(['customFields', 'saveRequest', 'errors']), + }), + { + init, + fetchList, + save, + remove, + } +)(withPageTitle('Metadata - OpenReplay Preferences')(CustomFields)); diff --git a/frontend/app/components/Client/CustomFields/ListItem.js b/frontend/app/components/Client/CustomFields/ListItem.js index ef806fc93..03ba8b640 100644 --- a/frontend/app/components/Client/CustomFields/ListItem.js +++ b/frontend/app/components/Client/CustomFields/ListItem.js @@ -1,22 +1,25 @@ import React from 'react'; -import cn from 'classnames' +import cn from 'classnames'; import { Icon } from 'UI'; import styles from './listItem.module.css'; -const ListItem = ({ field, onEdit, onDelete, disabled }) => { - return ( -
field.index != 0 && onEdit(field) } > - { field.key } -
-
{ e.stopPropagation(); onDelete(field) } }> +const ListItem = ({ field, onEdit, disabled }) => { + return ( +
field.index != 0 && onEdit(field)} + > + {field.key} +
+ {/*
{ e.stopPropagation(); onDelete(field) } }> +
*/} +
+ +
+
-
- -
-
-
- ); + ); }; export default ListItem; diff --git a/frontend/app/components/Client/Integrations/AssistDoc/AssistDoc.js b/frontend/app/components/Client/Integrations/AssistDoc/AssistDoc.js index 4cf2d0e7f..83319959a 100644 --- a/frontend/app/components/Client/Integrations/AssistDoc/AssistDoc.js +++ b/frontend/app/components/Client/Integrations/AssistDoc/AssistDoc.js @@ -1,59 +1,56 @@ import React from 'react'; -import Highlight from 'react-highlight' +import Highlight from 'react-highlight'; import DocLink from 'Shared/DocLink/DocLink'; -import AssistScript from './AssistScript' -import AssistNpm from './AssistNpm' +import AssistScript from './AssistScript'; +import AssistNpm from './AssistNpm'; import { Tabs } from 'UI'; import { useState } from 'react'; -const NPM = 'NPM' -const SCRIPT = 'SCRIPT' +const NPM = 'NPM'; +const SCRIPT = 'SCRIPT'; const TABS = [ - { key: SCRIPT, text: SCRIPT }, - { key: NPM, text: NPM }, -] + { key: SCRIPT, text: SCRIPT }, + { key: NPM, text: NPM }, +]; const AssistDoc = (props) => { - const { projectKey } = props; - const [activeTab, setActiveTab] = useState(SCRIPT) - + const { projectKey } = props; + const [activeTab, setActiveTab] = useState(SCRIPT); - const renderActiveTab = () => { - switch (activeTab) { - case SCRIPT: - return - case NPM: - return - } - return null; - } + const renderActiveTab = () => { + switch (activeTab) { + case SCRIPT: + return ; + case NPM: + return ; + } + return null; + }; + return ( +
+

Assist

+
+
+ OpenReplay Assist allows you to support your users by seeing their live screen and instantly hopping on call (WebRTC) with them + without requiring any 3rd-party screen sharing software. +
- return ( -
-
OpenReplay Assist allows you to support your users by seeing their live screen and instantly hopping on call (WebRTC) with them without requiring any 3rd-party screen sharing software.
+
Installation
+ {`npm i @openreplay/tracker-assist`} +
-
Installation
- - {`npm i @openreplay/tracker-assist`} - -
+
Usage
+ setActiveTab(tab)} /> -
Usage
- setActiveTab(tab) } - /> +
{renderActiveTab()}
-
- { renderActiveTab() } -
- - -
- ) + +
+
+ ); }; -AssistDoc.displayName = "AssistDoc"; +AssistDoc.displayName = 'AssistDoc'; export default AssistDoc; diff --git a/frontend/app/components/Client/Integrations/AxiosDoc/AxiosDoc.js b/frontend/app/components/Client/Integrations/AxiosDoc/AxiosDoc.js index 8fe32cfd0..9b624bbe4 100644 --- a/frontend/app/components/Client/Integrations/AxiosDoc/AxiosDoc.js +++ b/frontend/app/components/Client/Integrations/AxiosDoc/AxiosDoc.js @@ -1,40 +1,46 @@ import React from 'react'; -import Highlight from 'react-highlight' -import ToggleContent from 'Shared/ToggleContent' +import Highlight from 'react-highlight'; +import ToggleContent from 'Shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; const AxiosDoc = (props) => { - const { projectKey } = props; - return ( -
-
This plugin allows you to capture axios requests and inspect them later on while replaying session recordings. This is very useful for understanding and fixing issues.
- -
Installation
- - {`npm i @openreplay/tracker-axios`} - - -
Usage
-

Initialize the @openreplay/tracker package as usual then load the axios plugin. Note that OpenReplay axios plugin requires axios@^0.21.2 as a peer dependency.

-
+ const { projectKey } = props; + return ( +
+

Axios

+
+
+ This plugin allows you to capture axios requests and inspect them later on while replaying session recordings. This is very useful + for understanding and fixing issues. +
-
Usage
- - {`import tracker from '@openreplay/tracker'; +
Installation
+ {`npm i @openreplay/tracker-axios`} + +
Usage
+

+ Initialize the @openreplay/tracker package as usual then load the axios plugin. Note that OpenReplay axios plugin requires + axios@^0.21.2 as a peer dependency. +

+
+ +
Usage
+ + {`import tracker from '@openreplay/tracker'; import trackerAxios from '@openreplay/tracker-axios'; const tracker = new OpenReplay({ projectKey: '${projectKey}' }); tracker.use(trackerAxios(options)); // check list of available options below tracker.start();`} - - } - second={ - - {`import OpenReplay from '@openreplay/tracker/cjs'; + + } + second={ + + {`import OpenReplay from '@openreplay/tracker/cjs'; import trackerAxios from '@openreplay/tracker-axios/cjs'; const tracker = new OpenReplay({ projectKey: '${projectKey}' @@ -47,15 +53,16 @@ function MyApp() { }, []) //... }`} - - } - /> + + } + /> - -
- ) + +
+
+ ); }; -AxiosDoc.displayName = "AxiosDoc"; +AxiosDoc.displayName = 'AxiosDoc'; export default AxiosDoc; diff --git a/frontend/app/components/Client/Integrations/BugsnagForm/BugsnagForm.js b/frontend/app/components/Client/Integrations/BugsnagForm/BugsnagForm.js index b1aba5a30..15d8ddef1 100644 --- a/frontend/app/components/Client/Integrations/BugsnagForm/BugsnagForm.js +++ b/frontend/app/components/Client/Integrations/BugsnagForm/BugsnagForm.js @@ -1,32 +1,35 @@ import React from 'react'; import { tokenRE } from 'Types/integrations/bugsnagConfig'; -import IntegrationForm from '../IntegrationForm'; +import IntegrationForm from '../IntegrationForm'; import ProjectListDropdown from './ProjectListDropdown'; import DocLink from 'Shared/DocLink/DocLink'; const BugsnagForm = (props) => ( - <> -
-
How to integrate Bugsnag with OpenReplay and see backend errors alongside session recordings.
- +
+

Bugsnag

+
+
How to integrate Bugsnag with OpenReplay and see backend errors alongside session recordings.
+ +
+ tokenRE.test(config.authorizationToken), + component: ProjectListDropdown, + }, + ]} + />
- tokenRE.test(config.authorizationToken), - component: ProjectListDropdown, - } - ]} - /> - ); -BugsnagForm.displayName = "BugsnagForm"; +BugsnagForm.displayName = 'BugsnagForm'; -export default BugsnagForm; \ No newline at end of file +export default BugsnagForm; diff --git a/frontend/app/components/Client/Integrations/CloudwatchForm/CloudwatchForm.js b/frontend/app/components/Client/Integrations/CloudwatchForm/CloudwatchForm.js index 482167c72..bd9604b01 100644 --- a/frontend/app/components/Client/Integrations/CloudwatchForm/CloudwatchForm.js +++ b/frontend/app/components/Client/Integrations/CloudwatchForm/CloudwatchForm.js @@ -1,43 +1,48 @@ import React from 'react'; import { ACCESS_KEY_ID_LENGTH, SECRET_ACCESS_KEY_LENGTH } from 'Types/integrations/cloudwatchConfig'; -import IntegrationForm from '../IntegrationForm'; +import IntegrationForm from '../IntegrationForm'; import LogGroupDropdown from './LogGroupDropdown'; import RegionDropdown from './RegionDropdown'; import DocLink from 'Shared/DocLink/DocLink'; const CloudwatchForm = (props) => ( - <> -
-
How to integrate CloudWatch with OpenReplay and see backend errors alongside session replays.
- +
+

Cloud Watch

+
+
How to integrate CloudWatch with OpenReplay and see backend errors alongside session replays.
+ +
+ + config.awsSecretAccessKey.length === SECRET_ACCESS_KEY_LENGTH && + config.region !== '' && + config.awsAccessKeyId.length === ACCESS_KEY_ID_LENGTH, + }, + ]} + />
- - config.awsSecretAccessKey.length === SECRET_ACCESS_KEY_LENGTH && - config.region !== '' && - config.awsAccessKeyId.length === ACCESS_KEY_ID_LENGTH - } - ]} - /> - ); -CloudwatchForm.displayName = "CloudwatchForm"; +CloudwatchForm.displayName = 'CloudwatchForm'; -export default CloudwatchForm; \ No newline at end of file +export default CloudwatchForm; diff --git a/frontend/app/components/Client/Integrations/DatadogForm.js b/frontend/app/components/Client/Integrations/DatadogForm.js index 76ca0734d..46360259c 100644 --- a/frontend/app/components/Client/Integrations/DatadogForm.js +++ b/frontend/app/components/Client/Integrations/DatadogForm.js @@ -1,29 +1,32 @@ import React from 'react'; -import IntegrationForm from './IntegrationForm'; +import IntegrationForm from './IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; const DatadogForm = (props) => ( - <> -
-
How to integrate Datadog with OpenReplay and see backend errors alongside session recordings.
- +
+

Datadog

+
+
How to integrate Datadog with OpenReplay and see backend errors alongside session recordings.
+ +
+
- - ); -DatadogForm.displayName = "DatadogForm"; +DatadogForm.displayName = 'DatadogForm'; export default DatadogForm; diff --git a/frontend/app/components/Client/Integrations/ElasticsearchForm.js b/frontend/app/components/Client/Integrations/ElasticsearchForm.js index 271ccefe1..ad33b6302 100644 --- a/frontend/app/components/Client/Integrations/ElasticsearchForm.js +++ b/frontend/app/components/Client/Integrations/ElasticsearchForm.js @@ -1,75 +1,88 @@ import React from 'react'; import { connect } from 'react-redux'; -import IntegrationForm from './IntegrationForm'; +import IntegrationForm from './IntegrationForm'; import { withRequest } from 'HOCs'; import { edit } from 'Duck/integrations/actions'; import DocLink from 'Shared/DocLink/DocLink'; -@connect(state => ({ - config: state.getIn([ 'elasticsearch', 'instance' ]) -}), { edit }) +@connect( + (state) => ({ + config: state.getIn(['elasticsearch', 'instance']), + }), + { edit } +) @withRequest({ - dataName: "isValid", - initialData: false, - dataWrapper: data => data.state, - requestName: "validateConfig", - endpoint: '/integrations/elasticsearch/test', - method: 'POST', + dataName: 'isValid', + initialData: false, + dataWrapper: (data) => data.state, + requestName: 'validateConfig', + endpoint: '/integrations/elasticsearch/test', + method: 'POST', }) export default class ElasticsearchForm extends React.PureComponent { - componentWillReceiveProps(newProps) { - const { config: { host, port, apiKeyId, apiKey } } = this.props; - const { loading, config } = newProps; - const valuesChanged = host !== config.host || port !== config.port || apiKeyId !== config.apiKeyId || apiKey !== config.apiKey; - if (!loading && valuesChanged && newProps.config.validateKeys() && newProps) { - this.validateConfig(newProps); + componentWillReceiveProps(newProps) { + const { + config: { host, port, apiKeyId, apiKey }, + } = this.props; + const { loading, config } = newProps; + const valuesChanged = host !== config.host || port !== config.port || apiKeyId !== config.apiKeyId || apiKey !== config.apiKey; + if (!loading && valuesChanged && newProps.config.validateKeys() && newProps) { + this.validateConfig(newProps); + } } - } - validateConfig = (newProps) => { - const { config } = newProps; - this.props.validateConfig({ - host: config.host, - port: config.port, - apiKeyId: config.apiKeyId, - apiKey: config.apiKey, - }).then((res) => { - const { isValid } = this.props; - this.props.edit('elasticsearch', { isValid: isValid }) - }); - } + validateConfig = (newProps) => { + const { config } = newProps; + this.props + .validateConfig({ + host: config.host, + port: config.port, + apiKeyId: config.apiKeyId, + apiKey: config.apiKey, + }) + .then((res) => { + const { isValid } = this.props; + this.props.edit('elasticsearch', { isValid: isValid }); + }); + }; - render() { - const props = this.props; - return ( - <> -
-
How to integrate Elasticsearch with OpenReplay and see backend errors alongside session recordings.
- -
- - - ) - } -}; + render() { + const props = this.props; + return ( +
+

Elasticsearch

+
+
How to integrate Elasticsearch with OpenReplay and see backend errors alongside session recordings.
+ +
+ +
+ ); + } +} diff --git a/frontend/app/components/Client/Integrations/FetchDoc/FetchDoc.js b/frontend/app/components/Client/Integrations/FetchDoc/FetchDoc.js index 8d9bbd5b9..b4b8b537d 100644 --- a/frontend/app/components/Client/Integrations/FetchDoc/FetchDoc.js +++ b/frontend/app/components/Client/Integrations/FetchDoc/FetchDoc.js @@ -1,29 +1,32 @@ import React from 'react'; -import Highlight from 'react-highlight' -import ToggleContent from 'Shared/ToggleContent' +import Highlight from 'react-highlight'; +import ToggleContent from 'Shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; const FetchDoc = (props) => { - const { projectKey } = props; - return ( -
-
This plugin allows you to capture fetch payloads and inspect them later on while replaying session recordings. This is very useful for understanding and fixing issues.
- -
Installation
- - {`npm i @openreplay/tracker-fetch --save`} - - -
Usage
-

Use the provided fetch method from the plugin instead of the one built-in.

-
+ const { projectKey } = props; + return ( +
+

Fetch

+
+
+ This plugin allows you to capture fetch payloads and inspect them later on while replaying session recordings. This is very useful + for understanding and fixing issues. +
-
Usage
- - {`import tracker from '@openreplay/tracker'; +
Installation
+ {`npm i @openreplay/tracker-fetch --save`} + +
Usage
+

Use the provided fetch method from the plugin instead of the one built-in.

+
+ +
Usage
+ + {`import tracker from '@openreplay/tracker'; import trackerFetch from '@openreplay/tracker-fetch'; //... const tracker = new OpenReplay({ @@ -34,11 +37,11 @@ tracker.start(); export const fetch = tracker.use(trackerFetch()); // check list of available options below //... fetch('https://api.openreplay.com/').then(response => console.log(response.json()));`} - - } - second={ - - {`import OpenReplay from '@openreplay/tracker/cjs'; + + } + second={ + + {`import OpenReplay from '@openreplay/tracker/cjs'; import trackerFetch from '@openreplay/tracker-fetch/cjs'; //... const tracker = new OpenReplay({ @@ -54,15 +57,16 @@ export const fetch = tracker.use(trackerFetch()); // check list of avai //... fetch('https://api.openreplay.com/').then(response => console.log(response.json())); }`} - - } - /> + + } + /> - -
- ) + +
+
+ ); }; -FetchDoc.displayName = "FetchDoc"; +FetchDoc.displayName = 'FetchDoc'; export default FetchDoc; diff --git a/frontend/app/components/Client/Integrations/GithubForm.js b/frontend/app/components/Client/Integrations/GithubForm.js index 586ab3093..7d140732b 100644 --- a/frontend/app/components/Client/Integrations/GithubForm.js +++ b/frontend/app/components/Client/Integrations/GithubForm.js @@ -1,30 +1,31 @@ import React from 'react'; -import IntegrationForm from './IntegrationForm'; +import IntegrationForm from './IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; const GithubForm = (props) => ( - <> -
-
Integrate GitHub with OpenReplay and create issues directly from the recording page.
-
- -
+
+

Github

+
+
Integrate GitHub with OpenReplay and create issues directly from the recording page.
+
+ +
+
+
- - ); -GithubForm.displayName = "GithubForm"; +GithubForm.displayName = 'GithubForm'; -export default GithubForm; \ No newline at end of file +export default GithubForm; diff --git a/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js b/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js index a9150bc44..36e883f25 100644 --- a/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js +++ b/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js @@ -1,30 +1,36 @@ import React from 'react'; -import Highlight from 'react-highlight' +import Highlight from 'react-highlight'; import DocLink from 'Shared/DocLink/DocLink'; import ToggleContent from 'Shared/ToggleContent'; const GraphQLDoc = (props) => { - const { projectKey } = props; - return ( -
-

This plugin allows you to capture GraphQL requests and inspect them later on while replaying session recordings. This is very useful for understanding and fixing issues.

-

GraphQL plugin is compatible with Apollo and Relay implementations.

- -
Installation
- - {`npm i @openreplay/tracker-graphql --save`} - - -
Usage
-

The plugin call will return the function, which receives four variables operationKind, operationName, variables and result. It returns result without changes.

- -
+ const { projectKey } = props; + return ( +
+

GraphQL

+
+

+ This plugin allows you to capture GraphQL requests and inspect them later on while replaying session recordings. This is very + useful for understanding and fixing issues. +

+

GraphQL plugin is compatible with Apollo and Relay implementations.

- - {`import OpenReplay from '@openreplay/tracker'; +
Installation
+ {`npm i @openreplay/tracker-graphql --save`} + +
Usage
+

+ The plugin call will return the function, which receives four variables operationKind, operationName, variables and result. It + returns result without changes. +

+ +
+ + + {`import OpenReplay from '@openreplay/tracker'; import trackerGraphQL from '@openreplay/tracker-graphql'; //... const tracker = new OpenReplay({ @@ -33,11 +39,11 @@ const tracker = new OpenReplay({ tracker.start(); //... export const recordGraphQL = tracker.use(trackerGraphQL());`} - - } - second={ - - {`import OpenReplay from '@openreplay/tracker/cjs'; + + } + second={ + + {`import OpenReplay from '@openreplay/tracker/cjs'; import trackerGraphQL from '@openreplay/tracker-graphql/cjs'; //... const tracker = new OpenReplay({ @@ -51,15 +57,16 @@ function SomeFunctionalComponent() { } //... export const recordGraphQL = tracker.use(trackerGraphQL());`} - - } - /> + + } + /> - -
- ) + +
+
+ ); }; -GraphQLDoc.displayName = "GraphQLDoc"; +GraphQLDoc.displayName = 'GraphQLDoc'; export default GraphQLDoc; diff --git a/frontend/app/components/Client/Integrations/IntegrationForm.js b/frontend/app/components/Client/Integrations/IntegrationForm.js index aeb28fe31..ad6689f3b 100644 --- a/frontend/app/components/Client/Integrations/IntegrationForm.js +++ b/frontend/app/components/Client/Integrations/IntegrationForm.js @@ -1,144 +1,147 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Input, Form, Button, Checkbox } from 'UI'; +import { Input, Form, Button, Checkbox, Loader } from 'UI'; import SiteDropdown from 'Shared/SiteDropdown'; import { save, init, edit, remove, fetchList } from 'Duck/integrations/actions'; +import { fetchIntegrationList } from 'Duck/integrations/integrations'; -@connect((state, { name, customPath }) => ({ - sites: state.getIn([ 'site', 'list' ]), - initialSiteId: state.getIn([ 'site', 'siteId' ]), - list: state.getIn([ name, 'list' ]), - config: state.getIn([ name, 'instance']), - saving: state.getIn([ customPath || name, 'saveRequest', 'loading']), - removing: state.getIn([ name, 'removeRequest', 'loading']), -}), { - save, - init, - edit, - remove, - fetchList -}) +@connect( + (state, { name, customPath }) => ({ + sites: state.getIn(['site', 'list']), + initialSiteId: state.getIn(['site', 'siteId']), + list: state.getIn([name, 'list']), + config: state.getIn([name, 'instance']), + loading: state.getIn([name, 'fetchRequest', 'loading']), + saving: state.getIn([customPath || name, 'saveRequest', 'loading']), + removing: state.getIn([name, 'removeRequest', 'loading']), + siteId: state.getIn(['integrations', 'siteId']), + }), + { + save, + init, + edit, + remove, + fetchList, + fetchIntegrationList, + } +) export default class IntegrationForm extends React.PureComponent { - constructor(props) { - super(props); - const currentSiteId = this.props.initialSiteId; - this.state = { currentSiteId }; - this.init(currentSiteId); - } - - write = ({ target: { value, name: key, type, checked } }) => { - if (type === 'checkbox') - this.props.edit(this.props.name, { [ key ]: checked }) - else - this.props.edit(this.props.name, { [ key ]: value }) - }; + constructor(props) { + super(props); + // const currentSiteId = this.props.initialSiteId; + // this.state = { currentSiteId }; + // this.init(currentSiteId); + } - onChangeSelect = ({ value }) => { - const { sites, list, name } = this.props; - const site = sites.find(s => s.id === value.value); - this.setState({ currentSiteId: site.id }) - this.init(value.value); - } + write = ({ target: { value, name: key, type, checked } }) => { + if (type === 'checkbox') this.props.edit(this.props.name, { [key]: checked }); + else this.props.edit(this.props.name, { [key]: value }); + }; - init = (siteId) => { - const { list, name } = this.props; - const config = (parseInt(siteId) > 0) ? list.find(s => s.projectId === siteId) : undefined; - this.props.init(name, config ? config : list.first()); - } + // onChangeSelect = ({ value }) => { + // const { sites, list, name } = this.props; + // const site = sites.find((s) => s.id === value.value); + // this.setState({ currentSiteId: site.id }); + // this.init(value.value); + // }; - save = () => { - const { config, name, customPath } = this.props; - const isExists = config.exists(); - const { currentSiteId } = this.state; - const { ignoreProject } = this.props; - this.props.save(customPath || name, (!ignoreProject ? currentSiteId : null), config) - .then(() => { - this.props.fetchList(name) - this.props.onClose(); - if (isExists) return; - }); - } + // init = (siteId) => { + // const { list, name } = this.props; + // const config = parseInt(siteId) > 0 ? list.find((s) => s.projectId === siteId) : undefined; + // this.props.init(name, config ? config : list.first()); + // }; - remove = () => { - const { name, config, ignoreProject } = this.props; - this.props.remove(name, !ignoreProject ? config.projectId : null).then(function() { - this.props.onClose(); - this.props.fetchList(name) - }.bind(this)); - } + save = () => { + const { config, name, customPath, ignoreProject } = this.props; + const isExists = config.exists(); + // const { currentSiteId } = this.state; + this.props.save(customPath || name, !ignoreProject ? this.props.siteId : null, config).then(() => { + // this.props.fetchList(name); + this.props.onClose(); + if (isExists) return; + }); + }; - render() { - const { config, saving, removing, formFields, name, loading, ignoreProject } = this.props; - const { currentSiteId } = this.state; + remove = () => { + const { name, config, ignoreProject } = this.props; + this.props.remove(name, !ignoreProject ? config.projectId : null).then( + function () { + this.props.onClose(); + this.props.fetchList(name); + }.bind(this) + ); + }; - return ( -
-
- {!ignoreProject && - - - - - } + render() { + const { config, saving, removing, formFields, name, loading, ignoreProject } = this.props; + // const { currentSiteId } = this.state; - { formFields.map(({ - key, - label, - placeholder=label, - component: Component = 'input', - type = "text", - checkIfDisplayed, - autoFocus=false - }) => (typeof checkIfDisplayed !== 'function' || checkIfDisplayed(config)) && - ((type === 'checkbox') ? - - - - : - - - - - ) - )} - - + return ( + +
+ + {/* {!ignoreProject && ( + + + + + )} */} - {config.exists() && ( - - )} - -
- ); - } + {formFields.map( + ({ + key, + label, + placeholder = label, + component: Component = 'input', + type = 'text', + checkIfDisplayed, + autoFocus = false, + }) => + (typeof checkIfDisplayed !== 'function' || checkIfDisplayed(config)) && + (type === 'checkbox' ? ( + + + + ) : ( + + + + + )) + )} + + + + {config.exists() && ( + + )} + +
+ + ); + } } diff --git a/frontend/app/components/Client/Integrations/IntegrationItem.js b/frontend/app/components/Client/Integrations/IntegrationItem.js deleted file mode 100644 index b0bfa258a..000000000 --- a/frontend/app/components/Client/Integrations/IntegrationItem.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { Icon } from 'UI'; -import stl from './integrationItem.module.css'; - -const onDocLinkClick = (e, link) => { - e.stopPropagation(); - window.open(link, '_blank'); -} - -const IntegrationItem = ({ - deleteHandler = null, icon, url = null, title = '', description = '', onClick = null, dockLink = '', integrated = false -}) => { - return ( -
onClick(e, url) }> - {integrated && ( -
- -
- )} - integration -

{ title }

-
- ) -}; - -export default IntegrationItem; diff --git a/frontend/app/components/Client/Integrations/IntegrationItem.tsx b/frontend/app/components/Client/Integrations/IntegrationItem.tsx new file mode 100644 index 000000000..7e36adaef --- /dev/null +++ b/frontend/app/components/Client/Integrations/IntegrationItem.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import cn from 'classnames'; +import { Icon, Popup } from 'UI'; +import stl from './integrationItem.module.css'; +import { connect } from 'react-redux'; + +interface Props { + integration: any; + onClick?: (e: React.MouseEvent) => void; + integrated?: boolean; + hide?: boolean; +} + +const IntegrationItem = (props: Props) => { + const { integration, integrated, hide = false } = props; + return hide ? <> : ( +
props.onClick(e)}> + {integrated && ( +
+ + + +
+ )} + integration +
+

{integration.title}

+

{integration.subtitle && integration.subtitle}

+
+
+ ); +}; + +export default connect((state: any, props: Props) => { + const list = state.getIn([props.integration.slug, 'list']) || []; + return { + // integrated: props.integration.slug === 'issues' ? !!(list.first() && list.first().token) : list.size > 0, + }; +})(IntegrationItem); diff --git a/frontend/app/components/Client/Integrations/Integrations.js b/frontend/app/components/Client/Integrations/Integrations.js_ similarity index 100% rename from frontend/app/components/Client/Integrations/Integrations.js rename to frontend/app/components/Client/Integrations/Integrations.js_ diff --git a/frontend/app/components/Client/Integrations/Integrations.tsx b/frontend/app/components/Client/Integrations/Integrations.tsx new file mode 100644 index 000000000..d43079d6d --- /dev/null +++ b/frontend/app/components/Client/Integrations/Integrations.tsx @@ -0,0 +1,166 @@ +import { useModal } from 'App/components/Modal'; +import React, { useEffect } from 'react'; +import BugsnagForm from './BugsnagForm'; +import CloudwatchForm from './CloudwatchForm'; +import DatadogForm from './DatadogForm'; +import ElasticsearchForm from './ElasticsearchForm'; +import GithubForm from './GithubForm'; +import IntegrationItem from './IntegrationItem'; +import JiraForm from './JiraForm'; +import NewrelicForm from './NewrelicForm'; +import RollbarForm from './RollbarForm'; +import SentryForm from './SentryForm'; +import SlackForm from './SlackForm'; +import StackdriverForm from './StackdriverForm'; +import SumoLogicForm from './SumoLogicForm'; +import { fetch, init } from 'Duck/integrations/actions'; +import { fetchIntegrationList, setSiteId } from 'Duck/integrations/integrations'; +import { connect } from 'react-redux'; +import SiteDropdown from 'Shared/SiteDropdown'; +import ReduxDoc from './ReduxDoc'; +import VueDoc from './VueDoc'; +import GraphQLDoc from './GraphQLDoc'; +import NgRxDoc from './NgRxDoc'; +import MobxDoc from './MobxDoc'; +import FetchDoc from './FetchDoc'; +import ProfilerDoc from './ProfilerDoc'; +import AxiosDoc from './AxiosDoc'; +import AssistDoc from './AssistDoc'; + +interface Props { + fetch: (name: string, siteId: string) => void; + init: () => void; + fetchIntegrationList: (siteId: any) => void; + integratedList: any; + initialSiteId: string; + setSiteId: (siteId: string) => void; + siteId: string; +} +function Integrations(props: Props) { + const { initialSiteId } = props; + const { showModal } = useModal(); + const [loading, setLoading] = React.useState(true); + const [integratedList, setIntegratedList] = React.useState([]); + // const [siteId, setSiteId] = React.useState(props.siteId || initialSiteId); + + useEffect(() => { + const list = props.integratedList.filter((item: any) => item.integrated).map((item: any) => item.name); + setIntegratedList(list); + }, [props.integratedList]); + + useEffect(() => { + if (!props.siteId) { + props.setSiteId(initialSiteId); + props.fetchIntegrationList(initialSiteId); + } else { + props.fetchIntegrationList(props.siteId); + } + }, []); + + useEffect(() => { + if (loading) { + return; + } + }, [loading]); + + const onClick = (integration: any) => { + if (integration.slug) { + props.fetch(integration.slug, props.siteId); + } + showModal(integration.component, { right: true }); + }; + + const onChangeSelect = ({ value }: any) => { + props.setSiteId(value.value); + props.fetchIntegrationList(value.value); + }; + + return ( +
+ {integrations.map((cat: any) => ( +
+
+

{cat.title}

+ {cat.isProject && ( +
+ +
+ )} +
+
{cat.description}
+ +
+ {cat.integrations.map((integration: any) => ( + onClick(integration)} + hide={(integration.slug === 'github' && integratedList.includes('jira') || integration.slug === 'jira' && integratedList.includes('github'))} + /> + ))} +
+
+ ))} +
+ ); +} + +export default connect( + (state: any) => ({ + initialSiteId: state.getIn(['site', 'siteId']), + integratedList: state.getIn(['integrations', 'list']), + siteId: state.getIn(['integrations', 'siteId']), + }), + { fetch, init, fetchIntegrationList, setSiteId } +)(Integrations); + +const integrations = [ + { + title: 'Issue Reporting and Collaborations', + description: 'Seamlessly report issues or share issues with your team right from OpenReplay.', + integrations: [ + { title: 'Jira', slug: 'jira', category: 'Errors', icon: 'integrations/jira', component: }, + { title: 'Github', slug: 'github', category: 'Errors', icon: 'integrations/github', component: }, + { title: 'Slack', category: 'Errors', icon: 'integrations/slack', component: }, + ], + }, + { + title: 'Backend Logging', + isProject: true, + description: 'Sync your backend errors with sessions replays and see what happened front-to-back.', + integrations: [ + { title: 'Sentry', slug: 'sentry', icon: 'integrations/sentry', component: }, + { title: 'Datadog', slug: 'datadog', icon: 'integrations/datadog', component: }, + { title: 'Rollbar', slug: 'rollbar', icon: 'integrations/rollbar', component: }, + { title: 'Elasticsearch', slug: 'elasticsearch', icon: 'integrations/elasticsearch', component: }, + { title: 'Datadog', slug: 'datadog', icon: 'integrations/datadog', component: }, + { title: 'Sumo Logic', slug: 'sumologic', icon: 'integrations/sumologic', component: }, + { + title: 'Google Cloud', + slug: 'stackdriver', + subtitle: '(Stackdriver)', + icon: 'integrations/google-cloud', + component: , + }, + { title: 'AWS', slug: 'cloudwatch', subtitle: '(CloudWatch)', icon: 'integrations/aws', component: }, + { title: 'Newrelic', slug: 'newrelic', icon: 'integrations/newrelic', component: }, + ], + }, + { + title: 'Plugins', + description: + "Reproduce issues as if they happened in your own browser. Plugins help capture your application's store, HTTP requeets, GraphQL queries, and more.", + integrations: [ + { title: 'Redux', slug: '', icon: 'integrations/redux', component: }, + { title: 'VueX', slug: '', icon: 'integrations/vuejs', component: }, + { title: 'GraphQL', slug: '', icon: 'integrations/graphql', component: }, + { title: 'NgRx', slug: '', icon: 'integrations/ngrx', component: }, + { title: 'MobX', slug: '', icon: 'integrations/mobx', component: }, + { title: 'Fetch', slug: '', icon: 'integrations/openreplay', component: }, + { title: 'Profiler', slug: '', icon: 'integrations/openreplay', component: }, + { title: 'Axios', slug: '', icon: 'integrations/openreplay', component: }, + { title: 'Assist', slug: '', icon: 'integrations/openreplay', component: }, + ], + }, +]; diff --git a/frontend/app/components/Client/Integrations/JiraForm/JiraForm.js b/frontend/app/components/Client/Integrations/JiraForm/JiraForm.js index dc4585872..b17bbc460 100644 --- a/frontend/app/components/Client/Integrations/JiraForm/JiraForm.js +++ b/frontend/app/components/Client/Integrations/JiraForm/JiraForm.js @@ -1,37 +1,41 @@ import React from 'react'; -import IntegrationForm from '../IntegrationForm'; +import IntegrationForm from '../IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; const JiraForm = (props) => ( - <> -
-
How to integrate Jira Cloud with OpenReplay.
-
- -
+
+

Jira

+
+
How to integrate Jira Cloud with OpenReplay.
+
+ +
+
+
- - ); -JiraForm.displayName = "JiraForm"; +JiraForm.displayName = 'JiraForm'; -export default JiraForm; \ No newline at end of file +export default JiraForm; diff --git a/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js b/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js index 320e1a742..bbe36d45b 100644 --- a/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js +++ b/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js @@ -1,29 +1,35 @@ import React from 'react'; -import Highlight from 'react-highlight' -import ToggleContent from 'Shared/ToggleContent' +import Highlight from 'react-highlight'; +import ToggleContent from 'Shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; const MobxDoc = (props) => { - const { projectKey } = props; - return ( -
-
This plugin allows you to capture MobX events and inspect them later on while replaying session recordings. This is very useful for understanding and fixing issues.
- -
Installation
- - {`npm i @openreplay/tracker-mobx --save`} - - -
Usage
-

Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put the generated middleware into your Redux chain.

-
+ const { projectKey } = props; + return ( +
+

MobX

+
+
+ This plugin allows you to capture MobX events and inspect them later on while replaying session recordings. This is very useful + for understanding and fixing issues. +
-
Usage
- - {`import OpenReplay from '@openreplay/tracker'; +
Installation
+ {`npm i @openreplay/tracker-mobx --save`} + +
Usage
+

+ Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put the generated middleware into your Redux + chain. +

+
+ +
Usage
+ + {`import OpenReplay from '@openreplay/tracker'; import trackerMobX from '@openreplay/tracker-mobx'; //... const tracker = new OpenReplay({ @@ -31,11 +37,11 @@ const tracker = new OpenReplay({ }); tracker.use(trackerMobX()); // check list of available options below tracker.start();`} - - } - second={ - - {`import OpenReplay from '@openreplay/tracker/cjs'; + + } + second={ + + {`import OpenReplay from '@openreplay/tracker/cjs'; import trackerMobX from '@openreplay/tracker-mobx/cjs'; //... const tracker = new OpenReplay({ @@ -48,15 +54,16 @@ function SomeFunctionalComponent() { tracker.start(); }, []) }`} - - } - /> + + } + /> - -
- ) + +
+
+ ); }; -MobxDoc.displayName = "MobxDoc"; +MobxDoc.displayName = 'MobxDoc'; export default MobxDoc; diff --git a/frontend/app/components/Client/Integrations/NewrelicForm/NewrelicForm.js b/frontend/app/components/Client/Integrations/NewrelicForm/NewrelicForm.js index d7ce557e8..670656583 100644 --- a/frontend/app/components/Client/Integrations/NewrelicForm/NewrelicForm.js +++ b/frontend/app/components/Client/Integrations/NewrelicForm/NewrelicForm.js @@ -1,32 +1,36 @@ import React from 'react'; -import IntegrationForm from '../IntegrationForm'; +import IntegrationForm from '../IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; const NewrelicForm = (props) => ( - <> -
-
How to integrate NewRelic with OpenReplay and see backend errors alongside session recordings.
- +
+

New Relic

+
+
How to integrate NewRelic with OpenReplay and see backend errors alongside session recordings.
+ +
+
- - ); -NewrelicForm.displayName = "NewrelicForm"; +NewrelicForm.displayName = 'NewrelicForm'; -export default NewrelicForm; \ No newline at end of file +export default NewrelicForm; diff --git a/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js b/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js index 385b0d4e4..956e4f57e 100644 --- a/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js +++ b/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js @@ -1,29 +1,32 @@ import React from 'react'; -import Highlight from 'react-highlight' -import ToggleContent from 'Shared/ToggleContent' +import Highlight from 'react-highlight'; +import ToggleContent from 'Shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; const NgRxDoc = (props) => { - const { projectKey } = props; - return ( -
-
This plugin allows you to capture NgRx actions/state and inspect them later on while replaying session recordings. This is very useful for understanding and fixing issues.
- -
Installation
- - {`npm i @openreplay/tracker-ngrx --save`} - - -
Usage
-

Add the generated meta-reducer into your imports. See NgRx documentation for more details.

-
+ const { projectKey } = props; + return ( +
+

NgRx

+
+
+ This plugin allows you to capture NgRx actions/state and inspect them later on while replaying session recordings. This is very + useful for understanding and fixing issues. +
-
Usage
- - {`import { StoreModule } from '@ngrx/store'; +
Installation
+ {`npm i @openreplay/tracker-ngrx --save`} + +
Usage
+

Add the generated meta-reducer into your imports. See NgRx documentation for more details.

+
+ +
Usage
+ + {`import { StoreModule } from '@ngrx/store'; import { reducers } from './reducers'; import OpenReplay from '@openreplay/tracker'; import trackerNgRx from '@openreplay/tracker-ngrx'; @@ -39,11 +42,11 @@ const metaReducers = [tracker.use(trackerNgRx())]; // check list of ava imports: [StoreModule.forRoot(reducers, { metaReducers })] }) export class AppModule {}`} - - } - second={ - - {`import { StoreModule } from '@ngrx/store'; + + } + second={ + + {`import { StoreModule } from '@ngrx/store'; import { reducers } from './reducers'; import OpenReplay from '@openreplay/tracker/cjs'; import trackerNgRx from '@openreplay/tracker-ngrx/cjs'; @@ -64,15 +67,16 @@ const metaReducers = [tracker.use(trackerNgRx())]; // check list of ava }) export class AppModule {} }`} - - } - /> + + } + /> - -
- ) + +
+
+ ); }; -NgRxDoc.displayName = "NgRxDoc"; +NgRxDoc.displayName = 'NgRxDoc'; export default NgRxDoc; diff --git a/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js b/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js index 9cada092b..f5ffab724 100644 --- a/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js +++ b/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js @@ -1,29 +1,32 @@ import React from 'react'; -import Highlight from 'react-highlight' -import ToggleContent from 'Shared/ToggleContent' +import Highlight from 'react-highlight'; +import ToggleContent from 'Shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; const ProfilerDoc = (props) => { - const { projectKey } = props; - return ( -
-
The profiler plugin allows you to measure your JS functions' performance and capture both arguments and result for each function call.
- -
Installation
- - {`npm i @openreplay/tracker-profiler --save`} - - -
Usage
-

Initialize the tracker and load the plugin into it. Then decorate any function inside your code with the generated function.

-
+ const { projectKey } = props; + return ( +
+

Profiler

+
+
+ The profiler plugin allows you to measure your JS functions' performance and capture both arguments and result for each function + call. +
-
Usage
- - {`import OpenReplay from '@openreplay/tracker'; +
Installation
+ {`npm i @openreplay/tracker-profiler --save`} + +
Usage
+

Initialize the tracker and load the plugin into it. Then decorate any function inside your code with the generated function.

+
+ +
Usage
+ + {`import OpenReplay from '@openreplay/tracker'; import trackerProfiler from '@openreplay/tracker-profiler'; //... const tracker = new OpenReplay({ @@ -36,11 +39,11 @@ export const profiler = tracker.use(trackerProfiler()); const fn = profiler('call_name')(() => { //... }, thisArg); // thisArg is optional`} - - } - second={ - - {`import OpenReplay from '@openreplay/tracker/cjs'; + + } + second={ + + {`import OpenReplay from '@openreplay/tracker/cjs'; import trackerProfiler from '@openreplay/tracker-profiler/cjs'; //... const tracker = new OpenReplay({ @@ -58,15 +61,16 @@ const fn = profiler('call_name')(() => { //... }, thisArg); // thisArg is optional }`} - - } - /> + + } + /> - -
- ) + +
+
+ ); }; -ProfilerDoc.displayName = "ProfilerDoc"; +ProfilerDoc.displayName = 'ProfilerDoc'; export default ProfilerDoc; diff --git a/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js b/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js index 8e3b12432..e16eecbba 100644 --- a/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js +++ b/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js @@ -1,28 +1,31 @@ import React from 'react'; -import Highlight from 'react-highlight' +import Highlight from 'react-highlight'; import ToggleContent from '../../../shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; const ReduxDoc = (props) => { - const { projectKey } = props; - return ( -
-
This plugin allows you to capture Redux actions/state and inspect them later on while replaying session recordings. This is very useful for understanding and fixing issues.
- -
Installation
- - {`npm i @openreplay/tracker-redux --save`} - - + const { projectKey } = props; + return ( +
+

Redux

-
Usage
-

Initialize the tracker then put the generated middleware into your Redux chain.

-
- - {`import { applyMiddleware, createStore } from 'redux'; +
+
+ This plugin allows you to capture Redux actions/state and inspect them later on while replaying session recordings. This is very + useful for understanding and fixing issues. +
+ +
Installation
+ {`npm i @openreplay/tracker-redux --save`} + +
Usage
+

Initialize the tracker then put the generated middleware into your Redux chain.

+
+ + {`import { applyMiddleware, createStore } from 'redux'; import OpenReplay from '@openreplay/tracker'; import trackerRedux from '@openreplay/tracker-redux'; //... @@ -35,11 +38,11 @@ const store = createStore( reducer, applyMiddleware(tracker.use(trackerRedux())) // check list of available options below );`} - - } - second={ - - {`import { applyMiddleware, createStore } from 'redux'; + + } + second={ + + {`import { applyMiddleware, createStore } from 'redux'; import OpenReplay from '@openreplay/tracker/cjs'; import trackerRedux from '@openreplay/tracker-redux/cjs'; //... @@ -57,15 +60,16 @@ const store = createStore( applyMiddleware(tracker.use(trackerRedux())) // check list of available options below ); }`} - - } - /> + + } + /> - -
- ) + +
+
+ ); }; -ReduxDoc.displayName = "ReduxDoc"; +ReduxDoc.displayName = 'ReduxDoc'; export default ReduxDoc; diff --git a/frontend/app/components/Client/Integrations/RollbarForm.js b/frontend/app/components/Client/Integrations/RollbarForm.js index 3b8830423..441819323 100644 --- a/frontend/app/components/Client/Integrations/RollbarForm.js +++ b/frontend/app/components/Client/Integrations/RollbarForm.js @@ -1,25 +1,27 @@ import React from 'react'; -import IntegrationForm from './IntegrationForm'; +import IntegrationForm from './IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; const RollbarForm = (props) => ( - <> -
-
How to integrate Rollbar with OpenReplay and see backend errors alongside session replays.
- +
+

Rollbar

+
+
How to integrate Rollbar with OpenReplay and see backend errors alongside session replays.
+ +
+
- - ); -RollbarForm.displayName = "RollbarForm"; +RollbarForm.displayName = 'RollbarForm'; -export default RollbarForm; \ No newline at end of file +export default RollbarForm; diff --git a/frontend/app/components/Client/Integrations/SentryForm.js b/frontend/app/components/Client/Integrations/SentryForm.js index fd7bf1f11..bd119ba31 100644 --- a/frontend/app/components/Client/Integrations/SentryForm.js +++ b/frontend/app/components/Client/Integrations/SentryForm.js @@ -1,31 +1,35 @@ import React from 'react'; -import IntegrationForm from './IntegrationForm'; +import IntegrationForm from './IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; const SentryForm = (props) => ( - <> -
-
How to integrate Sentry with OpenReplay and see backend errors alongside session recordings.
- +
+

Sentry

+
+
How to integrate Sentry with OpenReplay and see backend errors alongside session recordings.
+ +
+
- - ); -SentryForm.displayName = "SentryForm"; +SentryForm.displayName = 'SentryForm'; -export default SentryForm; \ No newline at end of file +export default SentryForm; diff --git a/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js b/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js index 8e1bb121e..f018da3e5 100644 --- a/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js +++ b/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js @@ -1,101 +1,91 @@ -import React from 'react' -import { connect } from 'react-redux' -import { edit, save, init, update } from 'Duck/integrations/slack' -import { Form, Input, Button, Message } from 'UI' +import React from 'react'; +import { connect } from 'react-redux'; +import { edit, save, init, update } from 'Duck/integrations/slack'; +import { Form, Input, Button, Message } from 'UI'; import { confirm } from 'UI'; -import { remove } from 'Duck/integrations/slack' +import { remove } from 'Duck/integrations/slack'; class SlackAddForm extends React.PureComponent { - componentWillUnmount() { - this.props.init({}); - } - - save = () => { - const instance = this.props.instance; - if(instance.exists()) { - this.props.update(this.props.instance) - } else { - this.props.save(this.props.instance) - } - } - - remove = async (id) => { - if (await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to permanently delete this channel?` - })) { - this.props.remove(id); + componentWillUnmount() { + this.props.init({}); } - } - write = ({ target: { name, value } }) => this.props.edit({ [ name ]: value }); - - render() { - const { instance, saving, errors, onClose } = this.props; - return ( -
-
- - - - - - - - -
-
- - - -
- - -
-
- - { errors && -
- { errors.map(error => { error }) } -
+ save = () => { + const instance = this.props.instance; + if (instance.exists()) { + this.props.update(this.props.instance); + } else { + this.props.save(this.props.instance); } -
- ) - } + }; + + remove = async (id) => { + if ( + await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to permanently delete this channel?`, + }) + ) { + this.props.remove(id); + } + }; + + write = ({ target: { name, value } }) => this.props.edit({ [name]: value }); + + render() { + const { instance, saving, errors, onClose } = this.props; + return ( +
+
+ + + + + + + + +
+
+ + + +
+ + +
+
+ + {errors && ( +
+ {errors.map((error) => ( + + {error} + + ))} +
+ )} +
+ ); + } } -export default connect(state => ({ - instance: state.getIn(['slack', 'instance']), - saving: state.getIn(['slack', 'saveRequest', 'loading']), - errors: state.getIn([ 'slack', 'saveRequest', 'errors' ]), -}), { edit, save, init, remove, update })(SlackAddForm) \ No newline at end of file +export default connect( + (state) => ({ + instance: state.getIn(['slack', 'instance']), + saving: state.getIn(['slack', 'saveRequest', 'loading']), + errors: state.getIn(['slack', 'saveRequest', 'errors']), + }), + { edit, save, init, remove, update } +)(SlackAddForm); diff --git a/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js b/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js index f78527204..07f1aa123 100644 --- a/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js +++ b/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js @@ -1,49 +1,47 @@ -import React from 'react' -import { connect } from 'react-redux' +import React from 'react'; +import { connect } from 'react-redux'; import { NoContent } from 'UI'; -import { remove, edit } from 'Duck/integrations/slack' +import { remove, edit, init } from 'Duck/integrations/slack'; import DocLink from 'Shared/DocLink/DocLink'; function SlackChannelList(props) { - const { list } = props; + const { list } = props; - const onEdit = (instance) => { - props.edit(instance) - props.onEdit() - } + const onEdit = (instance) => { + props.edit(instance); + props.onEdit(); + }; - return ( -
- -
Integrate Slack with OpenReplay and share insights with the rest of the team, directly from the recording page.
- {/* */} - -
- } - size="small" - show={ list.size === 0 } - > - {list.map(c => ( -
onEdit(c)} - > -
-
{c.name}
-
- {c.endpoint} -
-
-
- ))} - -
- ) + return ( +
+ +
+ Integrate Slack with OpenReplay and share insights with the rest of the team, directly from the recording page. +
+ +
+ } + size="small" + show={list.size === 0} + > + {list.map((c) => ( +
onEdit(c)}> +
+
{c.name}
+
{c.endpoint}
+
+
+ ))} + +
+ ); } -export default connect(state => ({ - list: state.getIn(['slack', 'list']) -}), { remove, edit })(SlackChannelList) +export default connect( + (state) => ({ + list: state.getIn(['slack', 'list']), + }), + { remove, edit, init } +)(SlackChannelList); diff --git a/frontend/app/components/Client/Integrations/SlackForm.js b/frontend/app/components/Client/Integrations/SlackForm.js deleted file mode 100644 index 986af20ab..000000000 --- a/frontend/app/components/Client/Integrations/SlackForm.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import SlackChannelList from './SlackChannelList/SlackChannelList'; - -const SlackForm = (props) => { - const { onEdit } = props; - return ( - <> - - - ) -} - -SlackForm.displayName = "SlackForm"; - -export default SlackForm; \ No newline at end of file diff --git a/frontend/app/components/Client/Integrations/SlackForm.tsx b/frontend/app/components/Client/Integrations/SlackForm.tsx new file mode 100644 index 000000000..207f9b765 --- /dev/null +++ b/frontend/app/components/Client/Integrations/SlackForm.tsx @@ -0,0 +1,48 @@ +import React, { useEffect } from 'react'; +import SlackChannelList from './SlackChannelList/SlackChannelList'; +import { fetchList } from 'Duck/integrations/slack'; +import { connect } from 'react-redux'; +import SlackAddForm from './SlackAddForm'; +import { useModal } from 'App/components/Modal'; + +interface Props { + onEdit: (integration: any) => void; + istance: any; + fetchList: any; +} +const SlackForm = (props: Props) => { + const { istance } = props; + const { hideModal } = useModal(); + const [active, setActive] = React.useState(false); + + const onEdit = () => { + setActive(true); + }; + + useEffect(() => { + props.fetchList(); + }, []); + + return ( +
+
+

Slack

+ +
+ {active && ( +
+ setActive(false)} /> +
+ )} +
+ ); +}; + +SlackForm.displayName = 'SlackForm'; + +export default connect( + (state: any) => ({ + istance: state.getIn(['slack', 'instance']), + }), + { fetchList } +)(SlackForm); diff --git a/frontend/app/components/Client/Integrations/StackdriverForm.js b/frontend/app/components/Client/Integrations/StackdriverForm.js index b8e29fa3c..ce137bd99 100644 --- a/frontend/app/components/Client/Integrations/StackdriverForm.js +++ b/frontend/app/components/Client/Integrations/StackdriverForm.js @@ -1,29 +1,32 @@ import React from 'react'; -import IntegrationForm from './IntegrationForm'; +import IntegrationForm from './IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; const StackdriverForm = (props) => ( - <> -
-
How to integrate Stackdriver with OpenReplay and see backend errors alongside session recordings.
- +
+

Stackdriver

+
+
How to integrate Stackdriver with OpenReplay and see backend errors alongside session recordings.
+ +
+
- - ); -StackdriverForm.displayName = "StackdriverForm"; +StackdriverForm.displayName = 'StackdriverForm'; export default StackdriverForm; diff --git a/frontend/app/components/Client/Integrations/SumoLogicForm/SumoLogicForm.js b/frontend/app/components/Client/Integrations/SumoLogicForm/SumoLogicForm.js index 0a807edb6..6aea9fe6e 100644 --- a/frontend/app/components/Client/Integrations/SumoLogicForm/SumoLogicForm.js +++ b/frontend/app/components/Client/Integrations/SumoLogicForm/SumoLogicForm.js @@ -4,30 +4,34 @@ import RegionDropdown from './RegionDropdown'; import DocLink from 'Shared/DocLink/DocLink'; const SumoLogicForm = (props) => ( - <> -
-
How to integrate SumoLogic with OpenReplay and see backend errors alongside session recordings.
- +
+

Sumologic

+
+
How to integrate SumoLogic with OpenReplay and see backend errors alongside session recordings.
+ +
+
- - ); -SumoLogicForm.displayName = "SumoLogicForm"; +SumoLogicForm.displayName = 'SumoLogicForm'; export default SumoLogicForm; diff --git a/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js b/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js index e00d1c0ad..cece7c01e 100644 --- a/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js +++ b/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js @@ -1,29 +1,34 @@ import React from 'react'; -import Highlight from 'react-highlight' +import Highlight from 'react-highlight'; import ToggleContent from '../../../shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; const VueDoc = (props) => { - const { projectKey } = props; - return ( -
-
This plugin allows you to capture VueX mutations/state and inspect them later on while replaying session recordings. This is very useful for understanding and fixing issues.
- -
Installation
- - {`npm i @openreplay/tracker-vuex --save`} - - -
Usage
-

Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put the generated plugin into your plugins field of your store.

-
+ const { projectKey } = props; + return ( +
+

VueX

+
+
+ This plugin allows you to capture VueX mutations/state and inspect them later on while replaying session recordings. This is very + useful for understanding and fixing issues. +
- - - {`import Vuex from 'vuex' +
Installation
+ {`npm i @openreplay/tracker-vuex --save`} + +
Usage
+

+ Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put the generated plugin into your plugins + field of your store. +

+
+ + + {`import Vuex from 'vuex' import OpenReplay from '@openreplay/tracker'; import trackerVuex from '@openreplay/tracker-vuex'; //... @@ -36,11 +41,11 @@ const store = new Vuex.Store({ //... plugins: [tracker.use(trackerVuex())] // check list of available options below });`} - - } - second={ - - {`import Vuex from 'vuex' + + } + second={ + + {`import Vuex from 'vuex' import OpenReplay from '@openreplay/tracker/cjs'; import trackerVuex from '@openreplay/tracker-vuex/cjs'; //... @@ -58,15 +63,16 @@ const store = new Vuex.Store({ plugins: [tracker.use(trackerVuex())] // check list of available options below }); }`} - - } - /> + + } + /> - -
- ) + +
+
+ ); }; -VueDoc.displayName = "VueDoc"; +VueDoc.displayName = 'VueDoc'; export default VueDoc; diff --git a/frontend/app/components/Client/Integrations/_IntegrationItem .js_old b/frontend/app/components/Client/Integrations/_IntegrationItem .js_old deleted file mode 100644 index 962135633..000000000 --- a/frontend/app/components/Client/Integrations/_IntegrationItem .js_old +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { Icon } from 'UI'; -import styles from './integrationItem.module.css'; - -const onDocLinkClick = (e, link) => { - e.stopPropagation(); - window.open(link, '_blank'); -} - -const IntegrationItem = ({ - deleteHandler = null, icon, url = null, title = '', description = '', onClick = null, dockLink = '', integrated = false -}) => { - return ( -
onClick(e, url) }> - -

{ title }

-

{ description }

-
-
- {deleteHandler && ( -
- - { 'Remove' } -
- )} - { dockLink && ( -
onDocLinkClick(e, dockLink) }> - - { 'Documentation' } -
- )} -
- - { 'Integrated' } -
-
-
- ) -}; - -export default IntegrationItem; diff --git a/frontend/app/components/Client/Integrations/integrationItem.module.css b/frontend/app/components/Client/Integrations/integrationItem.module.css index 94ab26726..fca162909 100644 --- a/frontend/app/components/Client/Integrations/integrationItem.module.css +++ b/frontend/app/components/Client/Integrations/integrationItem.module.css @@ -9,7 +9,7 @@ display: flex; flex-direction: column; align-items: center; - justify-content: center; + justify-content: flex-start; /* min-height: 250px; */ /* min-width: 260px; */ /* max-width: 300px; */ diff --git a/frontend/app/components/Client/Notifications/Notifications.js b/frontend/app/components/Client/Notifications/Notifications.js index 15d6b9b4d..d01b12456 100644 --- a/frontend/app/components/Client/Notifications/Notifications.js +++ b/frontend/app/components/Client/Notifications/Notifications.js @@ -1,46 +1,50 @@ -import React, { useEffect } from 'react' -import cn from 'classnames' -import stl from './notifications.module.css' -import { Checkbox } from 'UI' -import { connect } from 'react-redux' -import { withRequest } from 'HOCs' -import { fetch as fetchConfig, edit as editConfig, save as saveConfig } from 'Duck/config' +import React, { useEffect } from 'react'; +import cn from 'classnames'; +import stl from './notifications.module.css'; +import { Checkbox, Toggler } from 'UI'; +import { connect } from 'react-redux'; +import { withRequest } from 'HOCs'; +import { fetch as fetchConfig, edit as editConfig, save as saveConfig } from 'Duck/config'; import withPageTitle from 'HOCs/withPageTitle'; function Notifications(props) { - const { config } = props; + const { config } = props; - useEffect(() => { - props.fetchConfig(); - }, []) + useEffect(() => { + props.fetchConfig(); + }, []); - const onChange = () => { - const _config = { 'weeklyReport' : !config.weeklyReport }; - props.editConfig(_config); - props.saveConfig(_config) - } + const onChange = () => { + const _config = { weeklyReport: !config.weeklyReport }; + props.editConfig(_config); + props.saveConfig(_config); + }; - return ( -
-
- {

{ 'Notifications' }

} -
-
- - -
-
- ) + return ( +
+
{

{'Notifications'}

}
+
+
Weekly project summary
+
Receive wekly report for each project on email.
+ + {/* */} + {/* */} +
+
+ ); } -export default connect(state => ({ - config: state.getIn(['config', 'options']) -}), { fetchConfig, editConfig, saveConfig })(withPageTitle('Notifications - OpenReplay Preferences')(Notifications)); +export default connect( + (state) => ({ + config: state.getIn(['config', 'options']), + }), + { fetchConfig, editConfig, saveConfig } +)(withPageTitle('Notifications - OpenReplay Preferences')(Notifications)); diff --git a/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js b/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js index 820fe14e4..8314e521a 100644 --- a/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js +++ b/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js @@ -13,14 +13,14 @@ function PreferencesMenu({ account, activeTab, history, isEnterprise }) { }; return ( -
+
Preferences
-
+
-
+
-
+
{ -
+
} -
+
{isEnterprise && isAdmin && ( -
+
+
- setTab(CLIENT_TABS.MANAGE_USERS)} - /> -
+
+ setTab(CLIENT_TABS.MANAGE_USERS)} + /> +
)} -
+
{} }: any) { const { userStore } = useStore(); + const { showModal, hideModal } = useModal(); const limtis = useObserver(() => userStore.limits); const canAddProject = useObserver(() => isAdmin && (limtis.projects === -1 || limtis.projects > 0)); + + const onClick = () => { + init(); + showModal(, { right: true }); + }; return ( - {/* */} ); } -export default AddProjectButton; +export default connect(null, { init, remove, fetchGDPR })(AddProjectButton); diff --git a/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx b/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx new file mode 100644 index 000000000..c6d04f2f4 --- /dev/null +++ b/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx @@ -0,0 +1,25 @@ +import { useModal } from 'App/components/Modal'; +import React from 'react'; +import TrackingCodeModal from 'Shared/TrackingCodeModal'; +import { Button } from 'UI'; + +interface Props { + site: any; +} +function InstallButton(props: Props) { + const { site } = props; + const { showModal, hideModal } = useModal(); + const onClick = () => { + showModal( + , + { right: true } + ); + }; + return ( + + ); +} + +export default InstallButton; diff --git a/frontend/app/components/Client/Sites/InstallButton/index.ts b/frontend/app/components/Client/Sites/InstallButton/index.ts new file mode 100644 index 000000000..c64b2ff6c --- /dev/null +++ b/frontend/app/components/Client/Sites/InstallButton/index.ts @@ -0,0 +1 @@ +export { default } from './InstallButton' \ No newline at end of file diff --git a/frontend/app/components/Client/Sites/NewSiteForm.js b/frontend/app/components/Client/Sites/NewSiteForm.js index c6633b73b..0a9dc81c7 100644 --- a/frontend/app/components/Client/Sites/NewSiteForm.js +++ b/frontend/app/components/Client/Sites/NewSiteForm.js @@ -1,121 +1,122 @@ import React from 'react'; import { connect } from 'react-redux'; import { Form, Input, Button, Icon } from 'UI'; -import { save, edit, update , fetchList, remove } from 'Duck/site'; +import { save, edit, update, fetchList, remove } from 'Duck/site'; import { pushNewSite } from 'Duck/user'; import { setSiteId } from 'Duck/site'; import { withRouter } from 'react-router-dom'; import styles from './siteForm.module.css'; import { confirm } from 'UI'; -@connect(state => ({ - site: state.getIn([ 'site', 'instance' ]), - sites: state.getIn([ 'site', 'list' ]), - siteList: state.getIn([ 'site', 'list' ]), - loading: state.getIn([ 'site', 'save', 'loading' ]) || state.getIn([ 'site', 'remove', 'loading' ]), -}), { - save, - remove, - edit, - update, - pushNewSite, - fetchList, - setSiteId -}) +@connect( + (state) => ({ + site: state.getIn(['site', 'instance']), + sites: state.getIn(['site', 'list']), + siteList: state.getIn(['site', 'list']), + loading: state.getIn(['site', 'save', 'loading']) || state.getIn(['site', 'remove', 'loading']), + }), + { + save, + remove, + edit, + update, + pushNewSite, + fetchList, + setSiteId, + } +) @withRouter export default class NewSiteForm extends React.PureComponent { - state = { - existsError: false, - } + state = { + existsError: false, + }; - componentDidMount() { - const { location: { pathname }, match: { params: { siteId } } } = this.props; - if (pathname.includes('onboarding')) { - this.props.setSiteId(siteId); - } - } + componentDidMount() { + const { + location: { pathname }, + match: { + params: { siteId }, + }, + } = this.props; + if (pathname.includes('onboarding')) { + this.props.setSiteId(siteId); + } + } - onSubmit = e => { - e.preventDefault(); - const { site, siteList, location: { pathname } } = this.props; - if (!site.exists() && siteList.some(({ name }) => name === site.name)) { - return this.setState({ existsError: true }); - } - if (site.exists()) { - this.props.update(this.props.site, this.props.site.id).then(() => { - this.props.onClose(null) - this.props.fetchList(); - }) - } else { - this.props.save(this.props.site).then(() => { - this.props.fetchList().then(() => { - const { sites } = this.props; - const site = sites.last(); - if (!pathname.includes('/client')) { - this.props.setSiteId(site.get('id')) - } - this.props.onClose(null, site) - }) - - // this.props.pushNewSite(site) - }); - } - } + onSubmit = (e) => { + e.preventDefault(); + const { + site, + siteList, + location: { pathname }, + } = this.props; + if (!site.exists() && siteList.some(({ name }) => name === site.name)) { + return this.setState({ existsError: true }); + } + if (site.exists()) { + this.props.update(this.props.site, this.props.site.id).then(() => { + this.props.onClose(null); + this.props.fetchList(); + }); + } else { + this.props.save(this.props.site).then(() => { + this.props.fetchList().then(() => { + const { sites } = this.props; + const site = sites.last(); + if (!pathname.includes('/client')) { + this.props.setSiteId(site.get('id')); + } + this.props.onClose(null, site); + }); - remove = async (site) => { - if (await confirm({ - header: 'Projects', - confirmation: `Are you sure you want to delete this Project? We won't be able to record anymore sessions.` - })) { - this.props.remove(site.id).then(() => { - this.props.onClose(null) - }); - } - }; + // this.props.pushNewSite(site) + }); + } + }; - edit = ({ target: { name, value } }) => { - this.setState({ existsError: false }); - this.props.edit({ [ name ]: value }); - } + remove = async (site) => { + if ( + await confirm({ + header: 'Projects', + confirmation: `Are you sure you want to delete this Project? We won't be able to record anymore sessions.`, + }) + ) { + this.props.remove(site.id).then(() => { + this.props.onClose(null); + }); + } + }; - render() { - const { site, loading } = this.props; - return ( -
-
- - - - -
- - {site.exists() && ( - - )} -
- { this.state.existsError && -
- { "Site exists already. Please choose another one." } -
- } -
-
- ); - } -} \ No newline at end of file + edit = ({ target: { name, value } }) => { + this.setState({ existsError: false }); + this.props.edit({ [name]: value }); + }; + + render() { + const { site, loading } = this.props; + return ( +
+

{site.exists() ? 'Edit Project' : 'New Project'}

+
+
+ + + + +
+ + {site.exists() && ( + + )} +
+ {this.state.existsError &&
{'Site exists already. Please choose another one.'}
} +
+
+
+ ); + } +} diff --git a/frontend/app/components/Client/Sites/ProjectKey.tsx b/frontend/app/components/Client/Sites/ProjectKey.tsx new file mode 100644 index 000000000..d53b336f8 --- /dev/null +++ b/frontend/app/components/Client/Sites/ProjectKey.tsx @@ -0,0 +1,8 @@ +import { withCopy } from 'HOCs'; +import React from 'react'; + +function ProjectKey({ value, tooltip }: any) { + return
{value}
; +} + +export default withCopy(ProjectKey); diff --git a/frontend/app/components/Client/Sites/Sites.js b/frontend/app/components/Client/Sites/Sites.js index 1c96c0b3c..4158a57ea 100644 --- a/frontend/app/components/Client/Sites/Sites.js +++ b/frontend/app/components/Client/Sites/Sites.js @@ -1,18 +1,18 @@ import React from 'react'; import { connect } from 'react-redux'; -import cn from 'classnames'; import withPageTitle from 'HOCs/withPageTitle'; -import { Loader, SlideModal, Icon, Button, Popup, TextLink } from 'UI'; +import { Loader, Button, Popup, TextLink } 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'; import NewSiteForm from './NewSiteForm'; -import GDPRForm from './GDPRForm'; -import TrackingCodeModal from 'Shared/TrackingCodeModal'; -import BlockedIps from './BlockedIps'; import { confirm, PageTitle } from 'UI'; import SiteSearch from './SiteSearch'; import AddProjectButton from './AddProjectButton'; +import InstallButton from './InstallButton'; +import ProjectKey from './ProjectKey'; +import { useModal } from 'App/components/Modal'; +import { getInitials } from 'App/utils'; const STATUS_MESSAGE_MAP = { [RED]: ' There seems to be an issue (please verify your installation)', @@ -20,11 +20,7 @@ const STATUS_MESSAGE_MAP = { [GREEN]: 'All good!', }; -const BLOCKED_IPS = 'BLOCKED_IPS'; -const NONE = 'NONE'; - const NEW_SITE_FORM = 'NEW_SITE_FORM'; -const GDPR_FORM = 'GDPR_FORM'; @connect( (state) => ({ @@ -43,20 +39,9 @@ const GDPR_FORM = 'GDPR_FORM'; @withPageTitle('Projects - OpenReplay Preferences') class Sites extends React.PureComponent { state = { - showTrackingCode: false, - modalContent: NONE, - detailContent: NONE, searchQuery: '', }; - toggleBlockedIp = () => { - this.setState({ - detailContent: this.state.detailContent === BLOCKED_IPS ? NONE : BLOCKED_IPS, - }); - }; - - closeModal = () => this.setState({ modalContent: NONE, detailContent: NONE }); - edit = (site) => { this.props.init(site); this.setState({ modalContent: NEW_SITE_FORM }); @@ -73,128 +58,59 @@ class Sites extends React.PureComponent { } }; - showGDPRForm = (site) => { - this.props.init(site); - this.setState({ modalContent: GDPR_FORM }); - }; - - showNewSiteForm = () => { - this.props.init(); - this.setState({ modalContent: NEW_SITE_FORM }); - }; - - showTrackingCode = (site) => { - this.props.init(site); - this.setState({ showTrackingCode: true }); - }; - - getModalTitle() { - switch (this.state.modalContent) { - case NEW_SITE_FORM: - return this.props.site.exists() ? 'Update Project' : 'New Project'; - case GDPR_FORM: - return 'Project Settings'; - default: - return ''; - } - } - - renderModalContent() { - switch (this.state.modalContent) { - case NEW_SITE_FORM: - return ; - case GDPR_FORM: - return ; - default: - return null; - } - } - - renderModalDetailContent() { - switch (this.state.detailContent) { - case BLOCKED_IPS: - return ; - default: - return null; - } - } - render() { - const { loading, sites, site, user, account } = this.props; - const { modalContent, showTrackingCode } = this.state; + const { loading, sites, user } = this.props; const isAdmin = user.admin || user.superAdmin; const filteredSites = sites.filter((site) => site.name.toLowerCase().includes(this.state.searchQuery.toLowerCase())); return ( - this.setState({ showTrackingCode: false })} - site={site} - /> -
- Projects
} - actionButton={} - /> + Projects
} actionButton={} />
- + this.setState({ searchQuery: value })} />
-
Name
+
Project Name
Key
{filteredSites.map((_site) => (
- -
- + +
+
+
+ {getInitials(_site.name)} +
{_site.host}
- {_site.projectKey} +
- +
- + this.props.init(_site)} />
@@ -207,3 +123,12 @@ class Sites extends React.PureComponent { } export default Sites; + +function EditButton({ isAdmin, onClick }) { + const { showModal, hideModal } = useModal(); + const _onClick = () => { + onClick(); + showModal(); + }; + return - { webhook.exists() && ( - - )} - - ); - } +
+
+ + {webhook.exists() && } +
+ {webhook.exists() && } +
+ +
+ ); + } } export default WebhookForm; diff --git a/frontend/app/components/Client/Webhooks/Webhooks.js b/frontend/app/components/Client/Webhooks/Webhooks.js index eb5306aa6..076ed0587 100644 --- a/frontend/app/components/Client/Webhooks/Webhooks.js +++ b/frontend/app/components/Client/Webhooks/Webhooks.js @@ -1,8 +1,8 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import cn from 'classnames'; import withPageTitle from 'HOCs/withPageTitle'; -import { IconButton, SlideModal, Loader, NoContent } from 'UI'; +import { Button, Loader, NoContent } from 'UI'; import { init, fetchList, remove } from 'Duck/webhook'; import WebhookForm from './WebhookForm'; import ListItem from './ListItem'; @@ -10,87 +10,74 @@ import styles from './webhooks.module.css'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import { confirm } from 'UI'; import { toast } from 'react-toastify'; +import { useModal } from 'App/components/Modal'; -@connect(state => ({ - webhooks: state.getIn(['webhooks', 'list']), - loading: state.getIn(['webhooks', 'loading']), -}), { - init, - fetchList, - remove, -}) -@withPageTitle('Webhooks - OpenReplay Preferences') -class Webhooks extends React.PureComponent { - state = { showModal: false }; +function Webhooks(props) { + const { webhooks, loading } = props; + const { showModal, hideModal } = useModal(); - componentWillMount() { - this.props.fetchList(); - } + const noSlackWebhooks = webhooks.filter((hook) => hook.type !== 'slack'); + useEffect(() => { + props.fetchList(); + }, []); - closeModal = () => this.setState({ showModal: false }); - init = (v) => { - this.props.init(v); - this.setState({ showModal: true }); - } + const init = (v) => { + props.init(v); + showModal(); + }; - removeWebhook = async (id) => { - if (await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to remove this webhook?` - })) { - this.props.remove(id).then(() => { - toast.success('Webhook removed successfully'); - }); - } - } + const removeWebhook = async (id) => { + if ( + await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to remove this webhook?`, + }) + ) { + props.remove(id).then(() => { + toast.success('Webhook removed successfully'); + }); + hideModal(); + } + }; - render() { - const { webhooks, loading } = this.props; - const { showModal } = this.state; - - const noSlackWebhooks = webhooks.filter(hook => hook.type !== 'slack'); return ( -
- } - onClose={ this.closeModal } - /> -
-

{ 'Webhooks' }

- this.init() } /> -
- - - - -
No webhooks available.
-
- } - size="small" - show={ noSlackWebhooks.size === 0 } - // animatedIcon="no-results" - > -
- { noSlackWebhooks.map(webhook => ( - this.init(webhook) } - onDelete={ () => this.removeWebhook(webhook.webhookId) } - /> - ))} +
+
+

{'Webhooks'}

+
- - -
+ + + + +
No webhooks available.
+
+ } + size="small" + show={noSlackWebhooks.size === 0} + > +
+ {noSlackWebhooks.map((webhook) => ( + init(webhook)} /> + ))} +
+ + +
); - } } -export default Webhooks; \ No newline at end of file +export default connect( + (state) => ({ + webhooks: state.getIn(['webhooks', 'list']), + loading: state.getIn(['webhooks', 'loading']), + }), + { + init, + fetchList, + remove, + } +)(withPageTitle('Webhooks - OpenReplay Preferences')(Webhooks)); diff --git a/frontend/app/components/Modal/Modal.tsx b/frontend/app/components/Modal/Modal.tsx index d14f6411a..9dc622a18 100644 --- a/frontend/app/components/Modal/Modal.tsx +++ b/frontend/app/components/Modal/Modal.tsx @@ -3,14 +3,14 @@ import ReactDOM from 'react-dom'; import ModalOverlay from './ModalOverlay'; export default function Modal({ component, props, hideModal }: any) { - return component ? ReactDOM.createPortal( - - {component} - , - document.querySelector("#modal-root"), - ) : <>; -} \ No newline at end of file + return component ? ( + ReactDOM.createPortal( + + {component} + , + document.querySelector('#modal-root') + ) + ) : ( + <> + ); +} diff --git a/frontend/app/components/Modal/ModalOverlay.tsx b/frontend/app/components/Modal/ModalOverlay.tsx index 398e27f2f..5b2a9edab 100644 --- a/frontend/app/components/Modal/ModalOverlay.tsx +++ b/frontend/app/components/Modal/ModalOverlay.tsx @@ -1,18 +1,14 @@ import React from 'react'; -import stl from './ModalOverlay.module.css' +import stl from './ModalOverlay.module.css'; import cn from 'classnames'; function ModalOverlay({ hideModal, children, left = false, right = false }: any) { return (
-
-
{children}
+
+
{children}
); } -export default ModalOverlay; \ No newline at end of file +export default ModalOverlay; diff --git a/frontend/app/components/Modal/index.tsx b/frontend/app/components/Modal/index.tsx index 04e2acd91..920cb2d14 100644 --- a/frontend/app/components/Modal/index.tsx +++ b/frontend/app/components/Modal/index.tsx @@ -3,60 +3,59 @@ import React, { Component, createContext } from 'react'; import Modal from './Modal'; const ModalContext = createContext({ - component: null, - props: { - right: false, - onClose: () => {}, - }, - showModal: (component: any, props: any) => {}, - hideModal: () => {} + component: null, + props: { + right: true, + onClose: () => {}, + }, + showModal: (component: any, props: any) => {}, + hideModal: () => {}, }); export class ModalProvider extends Component { - - handleKeyDown = (e: any) => { - if (e.keyCode === 27) { - this.hideModal(); - } - } - - showModal = (component, props = { }) => { - this.setState({ - component, - props - }); - document.addEventListener('keydown', this.handleKeyDown); - document.querySelector("body").style.overflow = 'hidden'; - }; - - hideModal = () => { - document.removeEventListener('keydown', this.handleKeyDown); - document.querySelector("body").style.overflow = 'visible'; - const { props } = this.state; - if (props.onClose) { - props.onClose(); + handleKeyDown = (e: any) => { + if (e.keyCode === 27) { + this.hideModal(); + } }; - this.setState({ - component: null, - props: {} - }); - } - state = { - component: null, - props: {}, - showModal: this.showModal, - hideModal: this.hideModal - }; + showModal = (component, props = { right: true }) => { + this.setState({ + component, + props, + }); + document.addEventListener('keydown', this.handleKeyDown); + document.querySelector('body').style.overflow = 'hidden'; + }; - render() { - return ( - - - {this.props.children} - - ); - } + hideModal = () => { + document.removeEventListener('keydown', this.handleKeyDown); + document.querySelector('body').style.overflow = 'visible'; + const { props } = this.state; + if (props.onClose) { + props.onClose(); + } + this.setState({ + component: null, + props: {}, + }); + }; + + state = { + component: null, + props: {}, + showModal: this.showModal, + hideModal: this.hideModal, + }; + + render() { + return ( + + + {this.props.children} + + ); + } } export const ModalConsumer = ModalContext.Consumer; diff --git a/frontend/app/components/hocs/index.js b/frontend/app/components/hocs/index.js index 444ad0180..5f08b86f0 100644 --- a/frontend/app/components/hocs/index.js +++ b/frontend/app/components/hocs/index.js @@ -1,2 +1,3 @@ export { default as withRequest } from './withRequest'; -export { default as withToggle } from './withToggle'; \ No newline at end of file +export { default as withToggle } from './withToggle'; +export { default as withCopy } from './withCopy' \ No newline at end of file diff --git a/frontend/app/components/hocs/withCopy.tsx b/frontend/app/components/hocs/withCopy.tsx new file mode 100644 index 000000000..2b3a9d541 --- /dev/null +++ b/frontend/app/components/hocs/withCopy.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import copy from 'copy-to-clipboard'; +import { Tooltip } from 'react-tippy'; + +const withCopy = (WrappedComponent: React.ComponentType) => { + const ComponentWithCopy = (props: any) => { + const [copied, setCopied] = React.useState(false); + const { value, tooltip } = props; + const copyToClipboard = (text: string) => { + copy(text); + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 1000); + }; + return ( +
copyToClipboard(value)} className="w-fit"> + + + +
+ ); + }; + return ComponentWithCopy; +}; + +export default withCopy; diff --git a/frontend/app/components/hocs/withRequest.js b/frontend/app/components/hocs/withRequest.js index 80dfaccf3..992b0ce4e 100644 --- a/frontend/app/components/hocs/withRequest.js +++ b/frontend/app/components/hocs/withRequest.js @@ -2,66 +2,66 @@ import React from 'react'; import APIClient from 'App/api_client'; export default ({ - initialData = null, - endpoint = '', - method = 'GET', - requestName = "request", - loadingName = "loading", - errorName = "requestError", - dataName = "data", - dataWrapper = data => data, - loadOnInitialize = false, - resetBeforeRequest = false, // Probably use handler? -}) => BaseComponent => class extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - data: typeof initialData === 'function' ? initialData(props) : initialData, - loading: loadOnInitialize, - error: false, - }; - if (loadOnInitialize) { - this.request(); - } - } + initialData = null, + endpoint = '', + method = 'GET', + requestName = 'request', + loadingName = 'loading', + errorName = 'requestError', + dataName = 'data', + dataWrapper = (data) => data, + loadOnInitialize = false, + resetBeforeRequest = false, // Probably use handler? + }) => + (BaseComponent) => + class extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + data: typeof initialData === 'function' ? initialData(props) : initialData, + loading: loadOnInitialize, + error: false, + }; + if (loadOnInitialize) { + this.request(); + } + } - request = (params, edpParams) => { - this.setState({ - loading: true, - error: false, - data: resetBeforeRequest - ? (typeof initialData === 'function' ? initialData(this.props) : initialData) - : this.state.data, - }); - const edp = typeof endpoint === 'function' - ? endpoint(this.props, edpParams) - : endpoint; - return new APIClient()[ method.toLowerCase() ](edp, params) - .then(response => response.json()) - .then(({ errors, data }) => { - if (errors) { - return this.setError(); - } - this.setState({ - data: dataWrapper(data, this.state.data), - loading: false, - }); - }) - .catch(this.setError); - } + request = (params, edpParams) => { + this.setState({ + loading: true, + error: false, + data: resetBeforeRequest ? (typeof initialData === 'function' ? initialData(this.props) : initialData) : this.state.data, + }); + const edp = typeof endpoint === 'function' ? endpoint(this.props, edpParams) : endpoint; + return new APIClient() + [method.toLowerCase()](edp, params) + .then((response) => response.json()) + .then(({ errors, data }) => { + if (errors) { + return this.setError(); + } + this.setState({ + data: dataWrapper(data, this.state.data), + loading: false, + }); + }) + .catch(this.setError); + }; - setError = () => this.setState({ - loading: false, - error: true, - }) + setError = () => + this.setState({ + loading: false, + error: true, + }); - render() { - const ownProps = { - [ requestName ]: this.request, - [ loadingName ]: this.state.loading, - [ dataName ]: this.state.data, - [ errorName ]: this.state.error, - }; - return - } -} \ No newline at end of file + render() { + const ownProps = { + [requestName]: this.request, + [loadingName]: this.state.loading, + [dataName]: this.state.data, + [errorName]: this.state.error, + }; + return ; + } + }; diff --git a/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js b/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js index cd8c23707..586cc8742 100644 --- a/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js +++ b/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js @@ -3,65 +3,79 @@ import { Modal, Icon, Tabs } from 'UI'; import styles from './trackingCodeModal.module.css'; import { editGDPR, saveGDPR } from 'Duck/site'; import { connect } from 'react-redux'; -import ProjectCodeSnippet from './ProjectCodeSnippet'; +import ProjectCodeSnippet from './ProjectCodeSnippet'; import InstallDocs from './InstallDocs'; import cn from 'classnames'; const PROJECT = 'Using Script'; const DOCUMENTATION = 'Using NPM'; const TABS = [ - { key: DOCUMENTATION, text: DOCUMENTATION }, - { key: PROJECT, text: PROJECT }, + { key: DOCUMENTATION, text: DOCUMENTATION }, + { key: PROJECT, text: PROJECT }, ]; class TrackingCodeModal extends React.PureComponent { - state = { copied: false, changed: false, activeTab: DOCUMENTATION }; + state = { copied: false, changed: false, activeTab: DOCUMENTATION }; - setActiveTab = (tab) => { - this.setState({ activeTab: tab }); - } + setActiveTab = (tab) => { + this.setState({ activeTab: tab }); + }; - renderActiveTab = () => { - const { site } = this.props; - switch (this.state.activeTab) { - case PROJECT: - return ; - case DOCUMENTATION: - return ; + renderActiveTab = () => { + const { site } = this.props; + switch (this.state.activeTab) { + case PROJECT: + return ; + case DOCUMENTATION: + return ; + } + return null; + }; + + render() { + const { site, displayed, onClose, title = '', subTitle } = this.props; + const { activeTab } = this.state; + return ( +
+

+ {title} {subTitle && {subTitle}} +

+ +
+ +
{this.renderActiveTab()}
+
+
+ // displayed && + // + // + //
{ title } { subTitle && {subTitle}}
+ //
+ // + //
+ //
+ // + // + //
+ // { this.renderActiveTab() } + //
+ //
+ //
+ ); } - return null; - } - - render() { - const { site, displayed, onClose, title = '', subTitle } = this.props; - const { activeTab } = this.state; - return ( - displayed && - - -
{ title } { subTitle && {subTitle}}
-
- -
-
- - -
- { this.renderActiveTab() } -
-
-
- ); - } } -export default connect(state => ({ - site: state.getIn([ 'site', 'instance' ]), - gdpr: state.getIn([ 'site', 'instance', 'gdpr' ]), - saving: state.getIn([ 'site', 'saveGDPR', 'loading' ]), -}), { - editGDPR, saveGDPR -})(TrackingCodeModal); \ No newline at end of file +export default connect( + (state) => ({ + site: state.getIn(['site', 'instance']), + gdpr: state.getIn(['site', 'instance', 'gdpr']), + saving: state.getIn(['site', 'saveGDPR', 'loading']), + }), + { + editGDPR, + saveGDPR, + } +)(TrackingCodeModal); diff --git a/frontend/app/components/ui/Form/Form.tsx b/frontend/app/components/ui/Form/Form.tsx index c9ab7c036..a85af0b23 100644 --- a/frontend/app/components/ui/Form/Form.tsx +++ b/frontend/app/components/ui/Form/Form.tsx @@ -2,16 +2,15 @@ import React from 'react'; interface Props { children: React.ReactNode; - onSubmit?: any - [x: string]: any + onSubmit?: any; + [x: string]: any; } - interface FormFieldProps { children: React.ReactNode; - [x: string]: any + [x: string]: any; } -function FormField (props: FormFieldProps) { +function FormField(props: FormFieldProps) { const { children, ...rest } = props; return (
@@ -20,16 +19,18 @@ function FormField (props: FormFieldProps) { ); } - function Form(props: Props) { const { children, ...rest } = props; return ( -
{ - e.preventDefault(); - if (props.onSubmit) { - props.onSubmit(e); - } - }}> + { + e.preventDefault(); + if (props.onSubmit) { + props.onSubmit(e); + } + }} + > {children}
); @@ -37,4 +38,4 @@ function Form(props: Props) { Form.Field = FormField; -export default Form; \ No newline at end of file +export default Form; diff --git a/frontend/app/components/ui/Input/Input.tsx b/frontend/app/components/ui/Input/Input.tsx index 1897ece13..1c36f7a8a 100644 --- a/frontend/app/components/ui/Input/Input.tsx +++ b/frontend/app/components/ui/Input/Input.tsx @@ -11,13 +11,14 @@ interface Props { rows?: number; [x: string]: any; } -function Input(props: Props) { +const Input = React.forwardRef((props: Props, ref: any) => { const { className = '', leadingButton = '', wrapperClassName = '', icon = '', type = 'text', rows = 4, ...rest } = props; return (
{icon && } {type === 'textarea' ? (