diff --git a/README.md b/README.md index 32421d883..dd248aea5 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ OpenReplay can be deployed anywhere. Follow our step-by-step guides for deployin ## OpenReplay Cloud -For those who want to simply use OpenReplay as a service, [sign up](https://asayer.io/register.html) for a free account on our cloud offering. +For those who want to simply use OpenReplay as a service, [sign up](https://app.openreplay.com/signup) for a free account on our cloud offering. ## Community Support diff --git a/codeql-analysis.yml b/codeql-analysis.yml new file mode 100644 index 000000000..a79a63767 --- /dev/null +++ b/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ dev ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ dev ] + schedule: + - cron: '30 23 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go', 'javascript', 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/frontend/app/components/BugFinder/BugFinder.js b/frontend/app/components/BugFinder/BugFinder.js index 3aa234b26..bac9ea8e6 100644 --- a/frontend/app/components/BugFinder/BugFinder.js +++ b/frontend/app/components/BugFinder/BugFinder.js @@ -25,10 +25,9 @@ import { LAST_7_DAYS } from 'Types/app/period'; import { resetFunnel } from 'Duck/funnels'; import { resetFunnelFilters } from 'Duck/funnelFilters' import NoSessionsMessage from '../shared/NoSessionsMessage'; +import TrackerUpdateMessage from '../shared/TrackerUpdateMessage'; import LiveSessionList from './LiveSessionList' -const AUTOREFRESH_INTERVAL = 10 * 60 * 1000; - const weakEqual = (val1, val2) => { if (!!val1 === false && !!val2 === false) return true; if (!val1 !== !val2) return false; @@ -37,7 +36,6 @@ const weakEqual = (val1, val2) => { @withLocationHandlers() @connect(state => ({ - shouldAutorefresh: state.getIn([ 'filters', 'appliedFilter', 'events' ]).size === 0, filter: state.getIn([ 'filters', 'appliedFilter' ]), showLive: state.getIn([ 'user', 'account', 'appearance', 'sessionsLive' ]), variables: state.getIn([ 'customFields', 'list' ]), @@ -93,12 +91,6 @@ export default class BugFinder extends React.PureComponent { this.props.resetFunnel(); this.props.resetFunnelFilters(); - this.autorefreshIntervalId = setInterval(() => { - if (this.props.shouldAutorefresh) { - props.applyFilter(); - } - }, AUTOREFRESH_INTERVAL); - props.fetchFunnelsList(LAST_7_DAYS) } @@ -129,10 +121,6 @@ export default class BugFinder extends React.PureComponent { }.bind(this)); } - componentWillUnmount() { - clearInterval(this.autorefreshIntervalId); - } - setActiveTab = tab => { this.props.setActiveTab(tab); } @@ -151,6 +139,7 @@ export default class BugFinder extends React.PureComponent { />
+
, fetchList: (params) => void, + applyFilter: () => void, filters: List } function LiveSessionList(props: Props) { const { loading, list, filters } = props; + var timeoutId; useEffect(() => { props.fetchList(filters.toJS()); + timeout(); + return () => { + clearTimeout(timeoutId) + } }, []) + const timeout = () => { + timeoutId = setTimeout(() => { + props.fetchList(filters.toJS()); + timeout(); + }, AUTOREFRESH_INTERVAL); + } + return (
+ See how to {'enable Assist'} if you haven't yet done so. + + } image={} show={ !loading && list && list.size === 0} > diff --git a/frontend/app/components/BugFinder/SessionList/SessionList.js b/frontend/app/components/BugFinder/SessionList/SessionList.js index 41375a6af..0aa4b9afb 100644 --- a/frontend/app/components/BugFinder/SessionList/SessionList.js +++ b/frontend/app/components/BugFinder/SessionList/SessionList.js @@ -4,12 +4,14 @@ import { applyFilter, addAttribute, addEvent } from 'Duck/filters'; import SessionItem from 'Shared/SessionItem'; import SessionListHeader from './SessionListHeader'; import { KEYS } from 'Types/filter/customFilter'; -import styles from './sessionList.css'; const ALL = 'all'; const PER_PAGE = 10; +const AUTOREFRESH_INTERVAL = 3 * 60 * 1000; +var timeoutId; @connect(state => ({ + shouldAutorefresh: state.getIn([ 'filters', 'appliedFilter', 'events' ]).size === 0, savedFilters: state.getIn([ 'filters', 'list' ]), loading: state.getIn([ 'sessions', 'loading' ]), activeTab: state.getIn([ 'sessions', 'activeTab' ]), @@ -27,6 +29,7 @@ export default class SessionList extends React.PureComponent { } constructor(props) { super(props); + this.timeout(); } componentDidUpdate(prevProps) { @@ -47,6 +50,15 @@ export default class SessionList extends React.PureComponent { this.props.applyFilter() } + timeout = () => { + timeoutId = setTimeout(function () { + if (this.props.shouldAutorefresh) { + this.props.applyFilter(); + } + this.timeout(); + }.bind(this), AUTOREFRESH_INTERVAL); + } + getNoContentMessage = activeTab => { let str = "No recordings found"; if (activeTab.type !== 'all') { @@ -57,6 +69,10 @@ export default class SessionList extends React.PureComponent { return str + '!'; } + componentWillUnmount() { + clearTimeout(timeoutId) + } + renderActiveTabContent(list) { const { loading, diff --git a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js index 129862b29..48cb9c0f8 100644 --- a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js +++ b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js @@ -7,6 +7,7 @@ import { fetchWatchdogStatus } from 'Duck/watchdogs'; import { setActiveFlow, clearEvents } from 'Duck/filters'; import { setActiveTab } from 'Duck/sessions'; import { issues_types } from 'Types/session/issue' +import NewBadge from 'Shared/NewBadge'; function SessionsMenu(props) { const { @@ -75,11 +76,15 @@ function SessionsMenu(props) {
+
Assist
+
{ }
+
} iconName="person" active={activeTab.type === 'live'} onClick={() => onMenuItemClick({ name: 'Assist', type: 'live' })} /> +
diff --git a/frontend/app/components/Client/Integrations/AssistDoc/AssistDoc.js b/frontend/app/components/Client/Integrations/AssistDoc/AssistDoc.js index 01d3e7ad2..a0aef3cad 100644 --- a/frontend/app/components/Client/Integrations/AssistDoc/AssistDoc.js +++ b/frontend/app/components/Client/Integrations/AssistDoc/AssistDoc.js @@ -1,60 +1,53 @@ import Highlight from 'react-highlight' import ToggleContent from 'Shared/ToggleContent' import DocLink from 'Shared/DocLink/DocLink'; +import AssistScript from './AssistScript' +import AssistNpm from './AssistNpm' +import { Tabs } from 'UI'; +import { useState } from 'react'; + +const NPM = 'NPM' +const SCRIPT = 'SCRIPT' +const TABS = [ + { key: SCRIPT, text: SCRIPT }, + { key: NPM, text: NPM }, +] const AssistDoc = (props) => { + const { projectKey } = props; + const [activeTab, setActiveTab] = useState(SCRIPT) + + + const renderActiveTab = () => { + switch (activeTab) { + case SCRIPT: + return + case NPM: + return + } + return null; + } + + 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`} - -
Usage
-

Initialize the tracker then load the @openreplay/tracker-assist plugin.

-
+
Usage
- - {`import Tracker from '@openreplay/tracker'; -import trackerAssist from '@openreplay/tracker-assist'; -const tracker = new Tracker({ - projectKey: PROJECT_KEY, -}); -tracker.start(); -tracker.use(trackerAssist(options)); // check the list of available options below`} - - } - second={ - - {`import OpenReplay from '@openreplay/tracker/cjs'; -import trackerFetch from '@openreplay/tracker-assist/cjs'; -const tracker = new OpenReplay({ - projectKey: PROJECT_KEY -}); -const trackerAssist = tracker.use(trackerAssist(options)); // check the list of available options below -//... -function MyApp() { - useEffect(() => { // use componentDidMount in case of React Class Component - tracker.start(); - }, []) -//... -}`} - - } + setActiveTab(tab) } /> -
Options
- - {`trackerAssist({ - confirmText: string; -})`} - +
+ { renderActiveTab() } +
diff --git a/frontend/app/components/Client/Integrations/AssistDoc/AssistNpm.tsx b/frontend/app/components/Client/Integrations/AssistDoc/AssistNpm.tsx new file mode 100644 index 000000000..28c12bd30 --- /dev/null +++ b/frontend/app/components/Client/Integrations/AssistDoc/AssistNpm.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import Highlight from 'react-highlight' +import ToggleContent from 'Shared/ToggleContent' + +function AssistNpm(props) { + return ( +
+

Initialize the tracker then load the @openreplay/tracker-assist plugin.

+ +
Usage
+ + {`import Tracker from '@openreplay/tracker'; +import trackerAssist from '@openreplay/tracker-assist'; +const tracker = new Tracker({ + projectKey: '${props.projectKey}', +}); +tracker.start(); +tracker.use(trackerAssist(options)); // check the list of available options below`} + + } + second={ + + {`import OpenReplay from '@openreplay/tracker/cjs'; +import trackerFetch from '@openreplay/tracker-assist/cjs'; +const tracker = new OpenReplay({ + projectKey: '${props.projectKey}' +}); +const trackerAssist = tracker.use(trackerAssist(options)); // check the list of available options below +//... +function MyApp() { + useEffect(() => { // use componentDidMount in case of React Class Component + tracker.start(); + }, []) +//... +}`} + + } + /> + +
Options
+ + {`trackerAssist({ + confirmText: string; +})`} + +
+ ); +} + +export default AssistNpm; \ No newline at end of file diff --git a/frontend/app/components/Client/Integrations/AssistDoc/AssistScript.tsx b/frontend/app/components/Client/Integrations/AssistDoc/AssistScript.tsx new file mode 100644 index 000000000..932926a1a --- /dev/null +++ b/frontend/app/components/Client/Integrations/AssistDoc/AssistScript.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import Highlight from 'react-highlight' + +function AssistScript(props) { + return ( +
+

If your OpenReplay tracker is set up using the JS snippet, then simply replace the .../openreplay.js occurrence with .../openreplay-assist.js. Below is an example of how the script should like after the change:

+
+ + + {` +`} + +
+ ); +} + +export default AssistScript; \ No newline at end of file diff --git a/frontend/app/components/Client/Integrations/FetchDoc/FetchDoc.js b/frontend/app/components/Client/Integrations/FetchDoc/FetchDoc.js index d8238c21f..8ba8e590d 100644 --- a/frontend/app/components/Client/Integrations/FetchDoc/FetchDoc.js +++ b/frontend/app/components/Client/Integrations/FetchDoc/FetchDoc.js @@ -3,6 +3,7 @@ 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.
@@ -18,14 +19,14 @@ const FetchDoc = (props) => {
Usage
{`import tracker from '@openreplay/tracker'; import trackerFetch from '@openreplay/tracker-fetch'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); tracker.start(); //... @@ -40,7 +41,7 @@ fetch('https://api.openreplay.com/').then(response => console.log(response.json( import trackerFetch from '@openreplay/tracker-fetch/cjs'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); //... function SomeFunctionalComponent() { diff --git a/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js b/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js index b1b648f99..7a964de3a 100644 --- a/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js +++ b/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js @@ -3,6 +3,7 @@ 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.

@@ -19,14 +20,14 @@ const GraphQLDoc = (props) => {
{`import OpenReplay from '@openreplay/tracker'; import trackerGraphQL from '@openreplay/tracker-graphql'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY, + projectKey: '${projectKey}' }); tracker.start(); //... @@ -39,7 +40,7 @@ export const recordGraphQL = tracker.use(trackerGraphQL());`} import trackerGraphQL from '@openreplay/tracker-graphql/cjs'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); //... function SomeFunctionalComponent() { diff --git a/frontend/app/components/Client/Integrations/Integrations.js b/frontend/app/components/Client/Integrations/Integrations.js index 05036788e..f34050055 100644 --- a/frontend/app/components/Client/Integrations/Integrations.js +++ b/frontend/app/components/Client/Integrations/Integrations.js @@ -75,7 +75,7 @@ const TITLE = { [ ASSIST ] : 'Assist', } -const DOCS = [REDUX, VUE, GRAPHQL, NGRX, FETCH, MOBX, PROFILER] +const DOCS = [REDUX, VUE, GRAPHQL, NGRX, FETCH, MOBX, PROFILER, ASSIST] const integrations = [ 'sentry', 'datadog', 'stackdriver', 'rollbar', 'newrelic', 'bugsnag', 'cloudwatch', 'elasticsearch', 'sumologic', 'issues' ]; @@ -87,12 +87,14 @@ const integrations = [ 'sentry', 'datadog', 'stackdriver', 'rollbar', 'newrelic' state.getIn([ name, 'list' ]).size > 0; props.loading = props.loading || state.getIn([ name, 'fetchRequest', 'loading']); }) + const site = state.getIn([ 'site', 'instance' ]); return { ...props, issues: state.getIn([ 'issues', 'list']).first() || {}, slackChannelListExists: state.getIn([ 'slack', 'list' ]).size > 0, tenantId: state.getIn([ 'user', 'client', 'tenantId' ]), - jwt: state.get('jwt') + jwt: state.get('jwt'), + projectKey: site ? site.projectKey : '' }; }, { fetchList, @@ -142,7 +144,9 @@ export default class Integrations extends React.PureComponent { } } - renderModalContent() { + renderModalContent() { + const { projectKey } = this.props; + switch (this.state.modalContent) { case SENTRY: return ; @@ -172,21 +176,21 @@ export default class Integrations extends React.PureComponent { case JIRA: return ; case REDUX: - return + return case VUE: - return + return case GRAPHQL: - return + return case NGRX: - return + return case FETCH: - return + return case MOBX: - return + return case PROFILER: - return + return case ASSIST: - return + return default: return null; } diff --git a/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js b/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js index 7c4b3233b..79166da56 100644 --- a/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js +++ b/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js @@ -3,6 +3,7 @@ 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.
@@ -18,14 +19,14 @@ const MobxDoc = (props) => {
Usage
{`import OpenReplay from '@openreplay/tracker'; import trackerMobX from '@openreplay/tracker-mobx'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); tracker.use(trackerMobX()); // check list of available options below tracker.start();`} @@ -37,7 +38,7 @@ tracker.start();`} import trackerMobX from '@openreplay/tracker-mobx/cjs'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); tracker.use(trackerMobX()); // check list of available options below //... diff --git a/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js b/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js index 092e93a7d..9aa441c7e 100644 --- a/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js +++ b/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js @@ -3,6 +3,7 @@ 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.
@@ -18,7 +19,7 @@ const NgRxDoc = (props) => {
Usage
{`import { StoreModule } from '@ngrx/store'; @@ -27,7 +28,7 @@ import OpenReplay from '@openreplay/tracker'; import trackerNgRx from '@openreplay/tracker-ngrx'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); tracker.start(); //... @@ -47,7 +48,7 @@ import OpenReplay from '@openreplay/tracker/cjs'; import trackerNgRx from '@openreplay/tracker-ngrx/cjs'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); //... function SomeFunctionalComponent() { diff --git a/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js b/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js index e61f968b9..f00b98815 100644 --- a/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js +++ b/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js @@ -3,6 +3,7 @@ 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.
@@ -18,14 +19,14 @@ const ProfilerDoc = (props) => {
Usage
{`import OpenReplay from '@openreplay/tracker'; import trackerProfiler from '@openreplay/tracker-profiler'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); tracker.start(); //... @@ -42,7 +43,7 @@ const fn = profiler('call_name')(() => { import trackerProfiler from '@openreplay/tracker-profiler/cjs'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); //... function SomeFunctionalComponent() { diff --git a/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js b/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js index acc083a97..c4fa51240 100644 --- a/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js +++ b/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js @@ -3,6 +3,7 @@ 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.
@@ -17,7 +18,7 @@ const ReduxDoc = (props) => {

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

{`import { applyMiddleware, createStore } from 'redux'; @@ -25,7 +26,7 @@ import OpenReplay from '@openreplay/tracker'; import trackerRedux from '@openreplay/tracker-redux'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); tracker.start(); //... @@ -42,7 +43,7 @@ import OpenReplay from '@openreplay/tracker/cjs'; import trackerRedux from '@openreplay/tracker-redux/cjs'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); //... function SomeFunctionalComponent() { diff --git a/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js b/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js index 14a7e435a..b6682411c 100644 --- a/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js +++ b/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js @@ -3,6 +3,7 @@ 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.
@@ -18,7 +19,7 @@ const VueDoc = (props) => { {`import Vuex from 'vuex' @@ -26,7 +27,7 @@ import OpenReplay from '@openreplay/tracker'; import trackerVuex from '@openreplay/tracker-vuex'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); tracker.start(); //... @@ -43,7 +44,7 @@ import OpenReplay from '@openreplay/tracker/cjs'; import trackerVuex from '@openreplay/tracker-vuex/cjs'; //... const tracker = new OpenReplay({ - projectKey: PROJECT_KEY + projectKey: '${projectKey}' }); //... function SomeFunctionalComponent() { diff --git a/frontend/app/components/Client/ManageUsers/ManageUsers.js b/frontend/app/components/Client/ManageUsers/ManageUsers.js index 6d291ed1d..0d49d5319 100644 --- a/frontend/app/components/Client/ManageUsers/ManageUsers.js +++ b/frontend/app/components/Client/ManageUsers/ManageUsers.js @@ -27,7 +27,7 @@ const LIMIT_WARNING = 'You have reached users limit.'; fetchList, generateInviteLink }) -@withPageTitle('Manage Users - OpenReplay Preferences') +@withPageTitle('Users - OpenReplay Preferences') class ManageUsers extends React.PureComponent { state = { showModal: false, remaining: this.props.account.limits.teamMember.remaining, invited: false } @@ -50,7 +50,7 @@ class ManageUsers extends React.PureComponent { deleteHandler = async (user) => { if (await confirm({ - header: 'Manage Users', + header: 'Users', confirmation: `Are you sure you want to remove this user?` })) { this.props.deleteMember(user.id).then(() => { diff --git a/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js b/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js index f2a59dd15..4a1ff3f1d 100644 --- a/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js +++ b/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js @@ -72,7 +72,7 @@ function PreferencesMenu({ activeTab, appearance, history }) {
setTab(CLIENT_TABS.MANAGE_USERS) } /> diff --git a/frontend/app/components/Client/Sites/NewSiteForm.js b/frontend/app/components/Client/Sites/NewSiteForm.js index a9306048c..011ed8b01 100644 --- a/frontend/app/components/Client/Sites/NewSiteForm.js +++ b/frontend/app/components/Client/Sites/NewSiteForm.js @@ -2,6 +2,7 @@ import { connect } from 'react-redux'; import { Input, Button, Label } from 'UI'; import { save, edit, update , fetchList } from 'Duck/site'; import { pushNewSite, setSiteId } from 'Duck/user'; +import { withRouter } from 'react-router-dom'; import styles from './siteForm.css'; @connect(state => ({ @@ -17,6 +18,7 @@ import styles from './siteForm.css'; fetchList, setSiteId }) +@withRouter export default class NewSiteForm extends React.PureComponent { state = { existsError: false, @@ -24,7 +26,7 @@ export default class NewSiteForm extends React.PureComponent { onSubmit = e => { e.preventDefault(); - const { site, siteList } = this.props; + const { site, siteList, location: { pathname } } = this.props; if (!site.exists() && siteList.some(({ name }) => name === site.name)) { return this.setState({ existsError: true }); } @@ -39,20 +41,21 @@ export default class NewSiteForm extends React.PureComponent { const site = sites.last(); this.props.pushNewSite(site) - this.props.setSiteId(site.id) + if (!pathname.includes('/client')) { + this.props.setSiteId(site.id) + } this.props.onClose(null, site) }); } } edit = ({ target: { name, value } }) => { - if (value.includes(' ')) return; // TODO: site validation this.setState({ existsError: false }); this.props.edit({ [ name ]: value }); } render() { - const { site, loading, onClose } = this.props; + const { site, loading } = this.props; return (
diff --git a/frontend/app/components/Header/OnboardingExplore/featureItem.css b/frontend/app/components/Header/OnboardingExplore/featureItem.css index b0fe2dbb9..70a0f3d05 100644 --- a/frontend/app/components/Header/OnboardingExplore/featureItem.css +++ b/frontend/app/components/Header/OnboardingExplore/featureItem.css @@ -34,6 +34,7 @@ .activeLink { cursor: pointer; pointer-events: default; + text-decoration: underline; & label { color: #000000 !important; text-decoration: underline; diff --git a/frontend/app/components/Onboarding/components/OnboardingTabs/OnboardingTabs.js b/frontend/app/components/Onboarding/components/OnboardingTabs/OnboardingTabs.js index 6ec2db234..fd05da5b6 100644 --- a/frontend/app/components/Onboarding/components/OnboardingTabs/OnboardingTabs.js +++ b/frontend/app/components/Onboarding/components/OnboardingTabs/OnboardingTabs.js @@ -22,7 +22,6 @@ class TrackingCodeModal extends React.PureComponent { } renderActiveTab = () => { - console.log('rendering...') switch (this.state.activeTab) { case PROJECT: return diff --git a/frontend/app/components/Session/RightBlock.tsx b/frontend/app/components/Session/RightBlock.tsx index be88b9e8e..3eb802f68 100644 --- a/frontend/app/components/Session/RightBlock.tsx +++ b/frontend/app/components/Session/RightBlock.tsx @@ -4,9 +4,10 @@ import PageInsightsPanel from '../Session_/PageInsightsPanel/PageInsightsPanel' import { Controls as PlayerControls } from 'Player'; import { Tabs } from 'UI'; import { connectPlayer } from 'Player'; +import NewBadge from 'Shared/NewBadge'; const EVENTS = 'Events'; -const HEATMAPS = 'Heatmaps'; +const HEATMAPS = 'Click Map'; const TABS = [ EVENTS, HEATMAPS ].map(tab => ({ text: tab, key: tab })); @@ -29,12 +30,15 @@ export default function RightBlock() { } return (
- setActiveTab(tab) } - border={ true } - /> +
+ setActiveTab(tab) } + border={ true } + /> +
{ }
+
{ renderActiveTab(activeTab) } diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.js b/frontend/app/components/Session_/EventsBlock/EventsBlock.js index 53a83aaa9..0190ed23c 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.js +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.js @@ -42,7 +42,9 @@ export default class EventsBlock extends React.PureComponent { this.props.setEventFilter({ query: value, filter }) setTimeout(() => { - this.scroller.current.scrollToRow(0); + if (!this.scroller.current) return; + + this.scroller.current.scrollToRow(0); }, 100) } @@ -54,7 +56,9 @@ export default class EventsBlock extends React.PureComponent { this.scroller.current.forceUpdateGrid(); setTimeout(() => { - this.scroller.current.scrollToRow(0); + if (!this.scroller.current) return; + + this.scroller.current.scrollToRow(0); }, 100) } @@ -176,6 +180,7 @@ export default class EventsBlock extends React.PureComponent { userNumericHash, userDisplayName, userId, + revId, userAnonymousId }, filteredEvents @@ -191,6 +196,7 @@ export default class EventsBlock extends React.PureComponent { userNumericHash={userNumericHash} userDisplayName={userDisplayName} userId={userId} + revId={revId} userAnonymousId={userAnonymousId} /> diff --git a/frontend/app/components/Session_/EventsBlock/UserCard/UserCard.js b/frontend/app/components/Session_/EventsBlock/UserCard/UserCard.js index ecd072498..c2b138044 100644 --- a/frontend/app/components/Session_/EventsBlock/UserCard/UserCard.js +++ b/frontend/app/components/Session_/EventsBlock/UserCard/UserCard.js @@ -6,7 +6,7 @@ import Metadata from '../Metadata' import { withRequest } from 'HOCs' import SessionList from '../Metadata/SessionList' -function UserCard({ className, userNumericHash, userDisplayName, similarSessions, userId, userAnonymousId, request, loading }) { +function UserCard({ className, userNumericHash, userDisplayName, similarSessions, userId, userAnonymousId, request, loading, revId }) { const [showUserSessions, setShowUserSessions] = useState(false) const hasUserDetails = !!userId || !!userAnonymousId; @@ -29,6 +29,11 @@ function UserCard({ className, userNumericHash, userDisplayName, similarSessions
+ {revId && ( +
+ Rev ID: {revId} +
+ )}
diff --git a/frontend/app/components/Session_/Exceptions/Exceptions.js b/frontend/app/components/Session_/Exceptions/Exceptions.js index 9cd646fe1..6a2c75426 100644 --- a/frontend/app/components/Session_/Exceptions/Exceptions.js +++ b/frontend/app/components/Session_/Exceptions/Exceptions.js @@ -14,7 +14,7 @@ import BottomBlock from '../BottomBlock'; @connect(state => ({ session: state.getIn([ 'sessions', 'current' ]), errorStack: state.getIn([ 'sessions', 'errorStack' ]), - sourceMapUploaded: state.getIn([ 'sessions', 'sourceMapUploaded' ]), + sourcemapUploaded: state.getIn([ 'sessions', 'sourcemapUploaded' ]), loading: state.getIn([ 'sessions', 'fetchErrorStackList', 'loading' ]) }), { fetchErrorStackList }) export default class Exceptions extends React.PureComponent { @@ -33,7 +33,7 @@ export default class Exceptions extends React.PureComponent { closeModal = () => this.setState({ currentError: null}) render() { - const { exceptions, loading, errorStack, sourceMapUploaded } = this.props; + const { exceptions, loading, errorStack, sourcemapUploaded } = this.props; const { filter, currentError } = this.state; const filterRE = getRE(filter, 'i'); @@ -63,7 +63,7 @@ export default class Exceptions extends React.PureComponent { show={ !loading && errorStack.size === 0 } title="Nothing found!" > - +
diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js index 848e17153..266cf1cd1 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ b/frontend/app/components/Session_/Player/Controls/Controls.js @@ -399,7 +399,7 @@ export default class Controls extends React.Component { icon="tachometer-slow" /> } - { !live && showLongtasks && + {/* { !live && showLongtasks && toggleBottomBlock(LONGTASKS) } @@ -407,7 +407,7 @@ export default class Controls extends React.Component { label="Long Tasks" icon="business-time" /> - } + } */}
{ !live && diff --git a/frontend/app/components/Signup/SignupForm/SignupForm.js b/frontend/app/components/Signup/SignupForm/SignupForm.js index 7df2822ee..c12c3dcc8 100644 --- a/frontend/app/components/Signup/SignupForm/SignupForm.js +++ b/frontend/app/components/Signup/SignupForm/SignupForm.js @@ -26,11 +26,24 @@ export default class SignupForm extends React.Component { email: '', projectName: '', organizationName: '', + reload: false, }; + static getDerivedStateFromProps(props, state) { + if (props.errors && props.errors.size > 0 && state.reload) { + recaptchaRef.current.reset(); + return { + reload: false + } + } + return null; + } + + handleSubmit = (token) => { const { tenantId, fullname, password, email, projectName, organizationName, auth } = this.state; this.props.signup({ tenantId, fullname, password, email, projectName, organizationName, auth, 'g-recaptcha-response': token }) + this.setState({ reload: true }) } write = ({ target: { value, name } }) => this.setState({ [ name ]: value }) diff --git a/frontend/app/components/shared/TrackerUpdateMessage/TrackerUpdateMessage.js b/frontend/app/components/shared/TrackerUpdateMessage/TrackerUpdateMessage.js new file mode 100644 index 000000000..985d1c5fc --- /dev/null +++ b/frontend/app/components/shared/TrackerUpdateMessage/TrackerUpdateMessage.js @@ -0,0 +1,40 @@ +import React from 'react' +import { Icon } from 'UI' +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom'; +import { onboarding as onboardingRoute } from 'App/routes' +import { withSiteId } from 'App/routes'; + +const TrackerUpdateMessage= (props) => { + // const { site } = props; + const { site, sites, match: { params: { siteId } } } = props; + const activeSite = sites.find(s => s.id == siteId); + const hasSessions = !!activeSite && !activeSite.recorded; + const needUpdate = !hasSessions && site.trackerVersion !== window.ENV.TRACKER_VERSION; + return needUpdate ? ( + <> + {( +
+
+
+
+ +
+
+ Please props.history.push(withSiteId(onboardingRoute('installing'), siteId))}>update your tracker (Asayer) to the latest OpenReplay version ({window.ENV.TRACKER_VERSION}) to benefit from all new features we recently shipped. +
+
+
+
+ )} + + ) : '' +} + +export default connect(state => ({ + site: state.getIn([ 'site', 'instance' ]), + sites: state.getIn([ 'site', 'list' ]) +}))(withRouter(TrackerUpdateMessage)) \ No newline at end of file diff --git a/frontend/app/components/shared/TrackerUpdateMessage/index.js b/frontend/app/components/shared/TrackerUpdateMessage/index.js new file mode 100644 index 000000000..b9f95895d --- /dev/null +++ b/frontend/app/components/shared/TrackerUpdateMessage/index.js @@ -0,0 +1 @@ +export { default } from './TrackerUpdateMessage' \ No newline at end of file diff --git a/frontend/app/components/ui/SavedSearchList/SavedSearchList.js b/frontend/app/components/ui/SavedSearchList/SavedSearchList.js index 3bbd8d05b..fc82f1ef0 100644 --- a/frontend/app/components/ui/SavedSearchList/SavedSearchList.js +++ b/frontend/app/components/ui/SavedSearchList/SavedSearchList.js @@ -19,7 +19,6 @@ import { funnel as funnelRoute, withSiteId } from 'App/routes'; import Event, { TYPES } from 'Types/filter/event'; import FunnelMenuItem from 'Components/Funnels/FunnelMenuItem'; import FunnelSaveModal from 'Components/Funnels/FunnelSaveModal'; -import NewBadge from 'Shared/NewBadge'; import { blink as setBlink } from 'Duck/funnels'; const DEFAULT_VISIBLE = 3; @@ -98,7 +97,6 @@ class SavedSearchList extends React.Component { onClick={ this.createHandler } /> )} -
{ }
{ funnels.size === 0 && diff --git a/frontend/app/player/MessageDistributor/managers/AssistManager.ts b/frontend/app/player/MessageDistributor/managers/AssistManager.ts index 8f8b16e6c..ea2121464 100644 --- a/frontend/app/player/MessageDistributor/managers/AssistManager.ts +++ b/frontend/app/player/MessageDistributor/managers/AssistManager.ts @@ -161,7 +161,6 @@ export default class AssistManager { if (['peer-unavailable', 'network', 'webrtc'].includes(e.type)) { if (this.peer && this.connectionAttempts++ < MAX_RECONNECTION_COUNT) { this.setStatus(ConnectionStatus.Connecting); - console.log("peerunavailable") this.connectToPeer(); } else { this.setStatus(ConnectionStatus.Disconnected); @@ -175,7 +174,6 @@ export default class AssistManager { peer.on("open", () => { if (this.peeropened) { return; } this.peeropened = true; - console.log('peeropen') this.connectToPeer(); }); }); @@ -186,11 +184,9 @@ export default class AssistManager { if (!this.peer) { return; } this.setStatus(ConnectionStatus.Connecting); const id = this.peerID; - console.log("trying to connect to", id) const conn = this.peer.connect(id, { serialization: 'json', reliable: true}); conn.on('open', () => { window.addEventListener("beforeunload", ()=>conn.open &&conn.send("unload")); - console.log("peer connected") let i = 0; let firstMessage = true; @@ -252,7 +248,6 @@ export default class AssistManager { const onDataClose = () => { this.initiateCallEnd(); this.setStatus(ConnectionStatus.Connecting); - console.log('closed peer conn. Reconnecting...') this.connectToPeer(); } @@ -263,7 +258,6 @@ export default class AssistManager { // }, 3000); conn.on('close', onDataClose);// Does it work ? conn.on("error", (e) => { - console.log("PeerJS connection error", e); this.setStatus(ConnectionStatus.Error); }) } @@ -290,12 +284,10 @@ export default class AssistManager { private notifyCallEnd() { const dataConn = this.dataConnection; if (dataConn) { - console.log("notifyCallEnd send") dataConn.send("call_end"); } } private initiateCallEnd = () => { - console.log('initiateCallEnd') this.forceCallEnd(); this.notifyCallEnd(); this.onCallEnd?.(); @@ -349,7 +341,6 @@ export default class AssistManager { if (!this.peer || getState().calling !== CallingState.False) { return null; } update({ calling: CallingState.Requesting }); - console.log('calling...') const call = this.peer.call(this.peerID, localStream); call.on('stream', stream => { @@ -393,12 +384,10 @@ export default class AssistManager { } clear() { - console.log('clearing', this.peerID) this.initiateCallEnd(); this.dataCheckIntervalID && clearInterval(this.dataCheckIntervalID); if (this.peer) { this.peer.connections[this.peerID]?.forEach(c => c.open && c.close()); - console.log("destroying peer...") this.peer.disconnect(); this.peer.destroy(); this.peer = null; diff --git a/frontend/app/types/filter/customFilter.js b/frontend/app/types/filter/customFilter.js index 950c1b61a..ea7d82af2 100644 --- a/frontend/app/types/filter/customFilter.js +++ b/frontend/app/types/filter/customFilter.js @@ -1,7 +1,7 @@ import Record from 'Types/Record'; import Target from 'Types/target'; import { camelCased } from 'App/utils'; -// import { getEventIcon } from 'Types/filter'; +import { getEventIcon } from 'Types/filter'; const CLICK = 'CLICK'; const INPUT = 'INPUT'; @@ -105,6 +105,6 @@ export default Record({ operator: event.operator || getOperatorDefault(event.type), // value: target ? target.label : event.value, value: typeof value === 'string' ? [value] : value, - icon: 'filters/metadata' + icon: event.type ? getEventIcon(event.type) : 'filters/metadata' }), }) diff --git a/frontend/app/types/filter/filter.js b/frontend/app/types/filter/filter.js index feaa44a20..42637a2fa 100644 --- a/frontend/app/types/filter/filter.js +++ b/frontend/app/types/filter/filter.js @@ -124,6 +124,7 @@ export const getEventIcon = (filter) => { type = type || key; if (type === KEYS.USER_COUNTRY) return 'map-marker-alt'; if (type === KEYS.USER_BROWSER) return 'window'; + if (type === KEYS.USERBROWSER) return 'window'; if (type === KEYS.PLATFORM) return 'window'; if (type === TYPES.CLICK) return 'filters/click'; diff --git a/frontend/app/types/session/session.js b/frontend/app/types/session/session.js index 3926c1901..1fabc79a6 100644 --- a/frontend/app/types/session/session.js +++ b/frontend/app/types/session/session.js @@ -75,6 +75,7 @@ export default Record({ crashes: [], socket: null, isIOS: false, + revId: '' }, { fromJS:({ startTs=0, diff --git a/scripts/helm/README.md b/scripts/helm/README.md index f4a842546..b3d618665 100644 --- a/scripts/helm/README.md +++ b/scripts/helm/README.md @@ -30,11 +30,11 @@ Installation components are separated by namespaces. **Scripts:** - **install.sh** - Installs OpenReplay in a single node machine, for trial runs / demo. + Installs OpenReplay in a single node machine. This script is a wrapper around the `install.sh` with [k3s](https://k3s.io/) as kubernetes distro. - Note: As of now this script support only ubuntu, as we've to install some packages to enable `NFS`. + Note: As of now this script support only Ubuntu, as we've to install some packages to enable `NFS`. - **kube-install.sh:** diff --git a/scripts/helm/app/storage.yaml b/scripts/helm/app/storage.yaml index 836deab4a..39cca7f6b 100644 --- a/scripts/helm/app/storage.yaml +++ b/scripts/helm/app/storage.yaml @@ -43,3 +43,4 @@ env: KAFKA_SERVERS: kafka.db.svc.cluster.local:9092 KAFKA_USE_SSL: false LICENSE_KEY: "" + FS_CLEAN_HRS: 24 diff --git a/scripts/helm/nginx-ingress/nginx-ingress/README.md b/scripts/helm/nginx-ingress/nginx-ingress/README.md index 76c878b5a..89797d6e4 100644 --- a/scripts/helm/nginx-ingress/nginx-ingress/README.md +++ b/scripts/helm/nginx-ingress/nginx-ingress/README.md @@ -4,10 +4,10 @@ This is the frontend of the OpenReplay web app (internet). ## Endpoints -- /streaming -> ios-proxy - /api -> chalice -- /http -> http - / -> frontend (in minio) - /assets -> sessions-assets bucket in minio - /minio -> minio api endpoint - /ingest -> events ingestor +- /assist -> live sessions and webRTC +- /grafana -> monitoring (Enterprise Edition only) \ No newline at end of file diff --git a/scripts/helm/nginx-ingress/nginx-ingress/templates/configmap.yaml b/scripts/helm/nginx-ingress/nginx-ingress/templates/configmap.yaml index fff5ac641..84cc6337d 100644 --- a/scripts/helm/nginx-ingress/nginx-ingress/templates/configmap.yaml +++ b/scripts/helm/nginx-ingress/nginx-ingress/templates/configmap.yaml @@ -52,14 +52,6 @@ data: proxy_set_header Host $host; proxy_pass $target; } - location /streaming/ { - set $target http://ios-proxy-openreplay.app.svc.cluster.local; rewrite ^/streaming/(.*) /$1 break; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; - proxy_set_header Host $host; - proxy_pass $target; - } location /api/ { rewrite ^/api/(.*) /$1 break; proxy_http_version 1.1;