Merge branch 'main' of github.com:openreplay/openreplay into dev

This commit is contained in:
Shekar Siri 2022-01-17 17:50:04 +05:30
commit 319852e56e
35 changed files with 434 additions and 341 deletions

View file

@ -24,6 +24,7 @@ import * as routes from './routes';
import { OB_DEFAULT_TAB } from 'App/routes';
import Signup from './components/Signup/Signup';
import { fetchTenants } from 'Duck/user';
import { setSessionPath } from 'Duck/sessions';
const BugFinder = withSiteIdUpdater(BugFinderPure);
const Dashboard = withSiteIdUpdater(DashboardPure);
@ -73,9 +74,12 @@ const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB);
onboarding: state.getIn([ 'user', 'onboarding' ])
};
}, {
fetchUserInfo, fetchTenants
fetchUserInfo, fetchTenants, setSessionPath
})
class Router extends React.Component {
state = {
destinationPath: null
}
constructor(props) {
super(props);
if (props.isLoggedIn) {
@ -85,10 +89,23 @@ class Router extends React.Component {
props.fetchTenants();
}
componentDidUpdate(prevProps) {
componentDidMount() {
const { isLoggedIn, location } = this.props;
if (!isLoggedIn) {
this.setState({ destinationPath: location.pathname });
}
}
componentDidUpdate(prevProps, prevState) {
this.props.setSessionPath(prevProps.location.pathname)
if (prevProps.email !== this.props.email && !this.props.email) {
this.props.fetchTenants();
}
if (!prevProps.isLoggedIn && this.props.isLoggedIn && this.state.destinationPath !== routes.login()) {
this.props.history.push(this.state.destinationPath);
this.setState({ destinationPath: null });
}
}
render() {

View file

@ -0,0 +1,38 @@
import React, { useEffect, useState } from 'react';
import { SlideModal, Icon } from 'UI';
import SessionList from '../SessionList';
import stl from './assistTabs.css'
interface Props {
userId: any,
}
const AssistTabs = (props: Props) => {
const [showMenu, setShowMenu] = useState(false)
return (
<div className="relative mr-4">
<div className="flex items-center">
<div
className={stl.btnLink}
onClick={() => setShowMenu(!showMenu)}
>
More Live Sessions
</div>
<span className="mx-3 color-gray-medium">by</span>
<div className="flex items-center">
<Icon name="user-alt" color="gray-darkest" />
<div className="ml-2">{props.userId}</div>
</div>
</div>
<SlideModal
title={ <div>Live Sessions by {props.userId}</div> }
isDisplayed={ showMenu }
content={ showMenu && <SessionList /> }
onClose={ () => setShowMenu(false) }
/>
</div>
);
};
export default AssistTabs;

View file

@ -0,0 +1,5 @@
.btnLink {
cursor: pointer;
color: $green;
text-decoration: underline;
}

View file

@ -0,0 +1 @@
export { default } from './AssistTabs';

View file

@ -0,0 +1,40 @@
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { fetchLiveList } from 'Duck/sessions';
import { Loader, NoContent } from 'UI';
import SessionItem from 'Shared/SessionItem';
interface Props {
loading: boolean,
list: any,
session: any,
fetchLiveList: () => void,
}
function SessionList(props: Props) {
useEffect(() => {
props.fetchLiveList();
}, [])
return (
<Loader loading={props.loading}>
<NoContent
show={ !props.loading && (props.list.size === 0 )}
title="No live sessions."
>
<div className="p-4">
{ props.list.map(session => <SessionItem key={ session.sessionId } session={ session } />) }
</div>
</NoContent>
</Loader>
);
}
export default connect(state => {
const session = state.getIn([ 'sessions', 'current' ]);
return {
session,
list: state.getIn(['sessions', 'liveSessions'])
.filter(i => i.userId === session.userId && i.sessionId !== session.sessionId),
loading: state.getIn([ 'sessions', 'fetchLiveListRequest', 'loading' ]),
}
}, { fetchLiveList })(SessionList);

View file

@ -0,0 +1 @@
export { default } from './SessionList';

View file

@ -119,7 +119,7 @@ export default connect(state => ({
activeFlow: state.getIn([ 'filters', 'activeFlow' ]),
captureRate: state.getIn(['watchdogs', 'captureRate']),
filters: state.getIn([ 'filters', 'appliedFilter' ]),
sessionsLoading: state.getIn([ 'sessions', 'loading' ]),
sessionsLoading: state.getIn([ 'sessions', 'fetchLiveListRequest', 'loading' ]),
}), {
fetchWatchdogStatus, setActiveFlow, clearEvents, setActiveTab, fetchSessionList
})(SessionsMenu);

View file

@ -175,7 +175,7 @@ export default class Dashboard extends React.PureComponent {
<div className="page-margin container-90">
<div className="side-menu">
<SideMenuSection
title="Sessions"
title="Metrics"
onItemClick={this.onMenuItemClick}
items={menuList}
/>

View file

@ -2,7 +2,7 @@ import cn from 'classnames';
import { connect } from 'react-redux';
import { Set, List as ImmutableList } from "immutable";
import { NoContent, Loader, Checkbox, LoadMoreButton, IconButton, Input, DropdownPlain } from 'UI';
import { merge, resolve,unresolve,ignore } from "Duck/errors";
import { merge, resolve, unresolve, ignore, updateCurrentPage } from "Duck/errors";
import { applyFilter } from 'Duck/filters';
import { IGNORED, RESOLVED, UNRESOLVED } from 'Types/errorInfo';
import SortDropdown from 'Components/BugFinder/Filters/SortDropdown';
@ -30,18 +30,19 @@ const sortOptions = Object.entries(sortOptionsMap)
state.getIn(["errors", "unresolve", "loading"]),
ignoreLoading: state.getIn([ "errors", "ignore", "loading" ]),
mergeLoading: state.getIn([ "errors", "merge", "loading" ]),
currentPage: state.getIn(["errors", "currentPage"]),
}), {
merge,
resolve,
unresolve,
ignore,
applyFilter
applyFilter,
updateCurrentPage,
})
export default class List extends React.PureComponent {
state = {
checkedAll: false,
checkedIds: Set(),
showPages: 1,
sort: {}
}
@ -106,7 +107,7 @@ export default class List extends React.PureComponent {
this.applyToAllChecked(this.props.ignore);
}
addPage = () => this.setState({ showPages: this.state.showPages + 1 })
addPage = () => this.props.updateCurrentPage(this.props.currentPage + 1)
writeOption = (e, { name, value }) => {
const [ sort, order ] = value.split('-');
@ -123,16 +124,16 @@ export default class List extends React.PureComponent {
resolveToggleLoading,
mergeLoading,
onFilterChange,
currentPage,
} = this.props;
const {
checkedAll,
checkedIds,
showPages,
sort
} = this.state;
const someLoading = loading || ignoreLoading || resolveToggleLoading || mergeLoading;
const currentCheckedIds = this.currentCheckedIds();
const displayedCount = Math.min(showPages * PER_PAGE, list.size);
const displayedCount = Math.min(currentPage * PER_PAGE, list.size);
let _list = sort.sort ? list.sortBy(i => i[sort.sort]) : list;
_list = sort.order === 'desc' ? _list.reverse() : _list;

View file

@ -31,17 +31,19 @@ const InitLoader = connectPlayer(state => ({
}))(Loader);
function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, loadingCredentials, assistCredendials, request }) {
function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, loadingCredentials, assistCredendials, request, isEnterprise, hasSessionsPath }) {
useEffect(() => {
if (!loadingCredentials) {
initPlayer(session, jwt, assistCredendials);
initPlayer(session, jwt, assistCredendials, !hasSessionsPath && session.live);
}
return () => cleanPlayer()
}, [ session.sessionId, loadingCredentials, assistCredendials ]);
// LAYOUT (TODO: local layout state - useContext or something..)
useEffect(() => {
request();
if (isEnterprise) {
request();
}
return () => {
toggleFullscreen(false);
closeBottomBlock();
@ -60,7 +62,7 @@ function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, l
</InitLoader>
</PlayerProvider>
);
}
};
export default withRequest({
initialData: null,
@ -69,11 +71,17 @@ export default withRequest({
dataName: 'assistCredendials',
loadingName: 'loadingCredentials',
})(withPermissions(['SESSION_REPLAY', 'ASSIST_LIVE'], '', true)(connect(
state => ({
session: state.getIn([ 'sessions', 'current' ]),
showAssist: state.getIn([ 'sessions', 'showChatWindow' ]),
jwt: state.get('jwt'),
fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]),
}),
state => {
const isAssist = state.getIn(['sessions', 'activeTab']).type === 'live';
const hasSessioPath = state.getIn([ 'sessions', 'sessionPath' ]).includes('/sessions');
return {
session: state.getIn([ 'sessions', 'current' ]),
showAssist: state.getIn([ 'sessions', 'showChatWindow' ]),
jwt: state.get('jwt'),
fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]),
hasSessionsPath: hasSessioPath && !isAssist,
isEnterprise: state.getIn([ 'user', 'client', 'edition' ]) === 'ee',
}
},
{ toggleFullscreen, closeBottomBlock },
)(WebPlayer)));
)(WebPlayer)));

View file

@ -20,6 +20,7 @@ function Session({
session,
fetchSession,
fetchSlackList,
hasSessionsPath
}) {
usePageTitle("OpenReplay Session Player");
useEffect(() => {
@ -34,7 +35,7 @@ function Session({
return () => {
if (!session.exists()) return;
}
},[ sessionId ]);
},[ sessionId, hasSessionsPath ]);
return (
<NoContent
@ -50,7 +51,7 @@ function Session({
<Loader className="flex-1" loading={ loading || sessionId !== session.sessionId }>
{ session.isIOS
? <IOSPlayer session={session} />
: (session.live ? <LivePlayer /> : <WebPlayer />)
: (session.live && !hasSessionsPath ? <LivePlayer /> : <WebPlayer />)
}
</Loader>
</NoContent>
@ -59,11 +60,14 @@ function Session({
export default withPermissions(['SESSION_REPLAY'], '', true)(connect((state, props) => {
const { match: { params: { sessionId } } } = props;
const isAssist = state.getIn(['sessions', 'activeTab']).type === 'live';
const hasSessiosPath = state.getIn([ 'sessions', 'sessionPath' ]).includes('/sessions');
return {
sessionId,
loading: state.getIn([ 'sessions', 'loading' ]),
hasErrors: !!state.getIn([ 'sessions', 'errors' ]),
session: state.getIn([ 'sessions', 'current' ]),
hasSessionsPath: hasSessiosPath && !isAssist,
};
}, {
fetchSession,

View file

@ -1,56 +1,89 @@
//import cn from 'classnames';
import { getRE } from 'App/utils';
import { Label, NoContent, Input, SlideModal, CloseButton } from 'UI';
import { connectPlayer, pause } from 'Player';
import Autoscroll from '../Autoscroll';
import { connectPlayer, pause, jump } from 'Player';
// import Autoscroll from '../Autoscroll';
import BottomBlock from '../BottomBlock';
import TimeTable from '../TimeTable';
import FetchDetails from './FetchDetails';
import { renderName, renderDuration } from '../Network';
import { connect } from 'react-redux';
import { setTimelinePointer } from 'Duck/sessions';
@connectPlayer(state => ({
list: state.fetchList,
}))
@connect(state => ({
timelinePointer: state.getIn(['sessions', 'timelinePointer']),
}), { setTimelinePointer })
export default class Fetch extends React.PureComponent {
state = {
filter: "",
filteredList: this.props.list,
current: null,
currentIndex: 0,
showFetchDetails: false,
hasNextError: false,
hasPreviousError: false,
}
onFilterChange = (e, { value }) => this.setState({ filter: value })
onFilterChange = (e, { value }) => {
const { list } = this.props;
const filterRE = getRE(value, 'i');
const filtered = list
.filter((r) => filterRE.test(r.name) || filterRE.test(r.url) || filterRE.test(r.method) || filterRE.test(r.status));
this.setState({ filter: value, filteredList: value ? filtered : list, currentIndex: 0 });
}
setCurrent = (item, index) => {
pause()
jump(item.time)
this.setState({ current: item, currentIndex: index });
}
closeModal = () => this.setState({ current: null})
onRowClick = (item, index) => {
pause()
this.setState({ current: item, currentIndex: index, showFetchDetails: true });
this.props.setTimelinePointer(null);
}
closeModal = () => this.setState({ current: null, showFetchDetails: false });
nextClickHander = () => {
const { list } = this.props;
const { currentIndex } = this.state;
// const { list } = this.props;
const { currentIndex, filteredList } = this.state;
if (currentIndex === list.length - 1) return;
if (currentIndex === filteredList.length - 1) return;
const newIndex = currentIndex + 1;
this.setCurrent(list[newIndex], newIndex);
this.setCurrent(filteredList[newIndex], newIndex);
this.setState({ showFetchDetails: true });
}
prevClickHander = () => {
const { list } = this.props;
const { currentIndex } = this.state;
// const { list } = this.props;
const { currentIndex, filteredList } = this.state;
if (currentIndex === 0) return;
const newIndex = currentIndex - 1;
this.setCurrent(list[newIndex], newIndex);
this.setCurrent(filteredList[newIndex], newIndex);
this.setState({ showFetchDetails: true });
}
static getDerivedStateFromProps(nextProps, prevState) {
const { filteredList } = prevState;
if (nextProps.timelinePointer) {
let activeItem = filteredList.find((r) => r.time >= nextProps.timelinePointer.time);
activeItem = activeItem || filteredList[filteredList.length - 1];
return {
current: activeItem,
currentIndex: filteredList.indexOf(activeItem),
};
}
}
render() {
const { list } = this.props;
const { filter, current, currentIndex } = this.state;
const filterRE = getRE(filter, 'i');
const filtered = list
.filter((r) => filterRE.test(r.name) || filterRE.test(r.url) || filterRE.test(r.method) || filterRE.test(r.status));
// const { list } = this.props;
const { current, currentIndex, showFetchDetails, filteredList } = this.state;
return (
<React.Fragment>
<SlideModal
@ -73,14 +106,14 @@ export default class Fetch extends React.PureComponent {
</div>
</div>
}
isDisplayed={ current != null }
content={ current &&
isDisplayed={ current != null && showFetchDetails }
content={ current && showFetchDetails &&
<FetchDetails
resource={ current }
nextClick={this.nextClickHander}
prevClick={this.prevClickHander}
first={currentIndex === 0}
last={currentIndex === filtered.length - 1}
last={currentIndex === filteredList.length - 1}
/>
}
onClose={ this.closeModal }
@ -88,25 +121,31 @@ export default class Fetch extends React.PureComponent {
<BottomBlock>
<BottomBlock.Header>
<h4 className="text-lg">Fetch</h4>
<Input
className="input-small"
placeholder="Filter"
icon="search"
iconPosition="left"
name="filter"
onChange={ this.onFilterChange }
/>
<div className="flex items-center">
{/* <div className="flex items-center mr-3 text-sm uppercase">
<div className="p-2 cursor-pointer" onClick={this.goToPrevError}>Prev</div>
<div className="p-2 cursor-pointer" onClick={this.goToNextError}>Next</div>
</div> */}
<Input
className="input-small"
placeholder="Filter"
icon="search"
iconPosition="left"
name="filter"
onChange={ this.onFilterChange }
/>
</div>
</BottomBlock.Header>
<BottomBlock.Content>
<NoContent
size="small"
show={ filtered.length === 0}
show={ filteredList.length === 0}
>
<TimeTable
rows={ filtered }
onRowClick={ this.setCurrent }
rows={ filteredList }
onRowClick={ this.onRowClick }
hoverable
// navigation
navigation
activeIndex={currentIndex}
>
{[
@ -120,7 +159,7 @@ export default class Fetch extends React.PureComponent {
width: 60,
}, {
label: "Name",
width: 130,
width: 180,
render: renderName,
},
{

View file

@ -3,14 +3,10 @@ import { connectPlayer, jump, pause } from 'Player';
import { QuestionMarkHint, Popup, Tabs, Input } from 'UI';
import { getRE } from 'App/utils';
import { TYPES } from 'Types/session/resource';
import { formatBytes } from 'App/utils';
import { formatMs } from 'App/date';
import TimeTable from '../TimeTable';
import BottomBlock from '../BottomBlock';
import InfoLine from '../BottomBlock/InfoLine';
import stl from './network.css';
import NetworkContent from './NetworkContent';
import { connect } from 'react-redux';
import { setTimelinePointer } from 'Duck/sessions';
const ALL = 'ALL';
const XHR = 'xhr';
@ -28,73 +24,24 @@ const TAB_TO_TYPE_MAP = {
[ MEDIA ]: TYPES.MEDIA,
[ OTHER ]: TYPES.OTHER
}
const TABS = [ ALL, XHR, JS, CSS, IMG, MEDIA, OTHER ].map(tab => ({
text: tab,
key: tab,
}));
const DOM_LOADED_TIME_COLOR = "teal";
const LOAD_TIME_COLOR = "red";
export function renderName(r) {
return (
<Popup
trigger={ <div className={ stl.popupNameTrigger }>{ r.name }</div> }
content={ <div className={ stl.popupNameContent }>{ r.url }</div> }
size="mini"
position="right center"
/>
);
}
const renderXHRText = () => (
<span className="flex items-center">
{XHR}
<QuestionMarkHint
onHover
content={
<>
Use our <a className="color-teal underline" target="_blank" href="https://docs.openreplay.com/plugins/fetch">Fetch plugin</a>
{' to capture HTTP requests and responses, including status codes and bodies.'} <br/>
We also provide <a className="color-teal underline" target="_blank" href="https://docs.openreplay.com/plugins/graphql">support for GraphQL</a>
{' for easy debugging of your queries.'}
</>
}
className="ml-1"
/>
</span>
);
function renderSize(r) {
let triggerText;
let content;
if (r.decodedBodySize == null) {
triggerText = "x";
content = "Not captured";
} else {
const headerSize = r.headerSize || 0;
const encodedSize = r.encodedBodySize || 0;
const transferred = headerSize + encodedSize;
const showTransferred = r.headerSize != null;
triggerText = formatBytes(r.decodedBodySize);
content = (
<ul>
{ showTransferred &&
<li>{`${formatBytes( r.encodedBodySize + headerSize )} transfered over network`}</li>
}
<li>{`Resource size: ${formatBytes(r.decodedBodySize)} `}</li>
</ul>
);
}
return (
<Popup
trigger={ <div>{ triggerText }</div> }
content={ content }
size="mini"
position="right center"
/>
<div className="flex w-full relative items-center">
<Popup
trigger={ <div className={ stl.popupNameTrigger }>{ r.name }</div> }
content={ <div className={ stl.popupNameContent }>{ r.url }</div> }
size="mini"
position="right center"
/>
<div
className="absolute right-0 text-xs uppercase p-2 color-gray-500 hover:color-black"
onClick={ (e) => {
e.stopPropagation();
jump(r.time)
}}
>Jump</div>
</div>
);
}
@ -130,14 +77,18 @@ export function renderDuration(r) {
resources: state.resourceList,
domContentLoadedTime: state.domContentLoadedTime,
loadTime: state.loadTime,
time: state.time,
// time: state.time,
playing: state.playing,
domBuildingTime: state.domBuildingTime,
fetchPresented: state.fetchList.length > 0,
}))
@connect(state => ({
timelinePointer: state.getIn(['sessions', 'timelinePointer']),
}), { setTimelinePointer })
export default class Network extends React.PureComponent {
state = {
filter: '',
filteredList: this.props.resources,
activeTab: ALL,
currentIndex: 0
}
@ -146,10 +97,29 @@ export default class Network extends React.PureComponent {
pause();
jump(e.time);
this.setState({ currentIndex: index })
this.props.setTimelinePointer(null);
}
onTabClick = activeTab => this.setState({ activeTab })
onFilterChange = (e, { value }) => this.setState({ filter: value })
onFilterChange = (e, { value }) => {
const { resources } = this.props;
const filterRE = getRE(value, 'i');
const filtered = resources.filter(({ type, name }) =>
filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ]));
this.setState({ filter: value, filteredList: value ? filtered : resources, currentIndex: 0 });
}
static getDerivedStateFromProps(nextProps, prevState) {
const { filteredList } = prevState;
if (nextProps.timelinePointer) {
const activeItem = filteredList.find((r) => r.time >= nextProps.timelinePointer.time);
return {
currentIndex: activeItem ? filteredList.indexOf(activeItem) : filteredList.length - 1,
};
}
}
render() {
const {
@ -159,50 +129,23 @@ export default class Network extends React.PureComponent {
loadTime,
domBuildingTime,
fetchPresented,
time,
// time,
playing
} = this.props;
const { filter, activeTab, currentIndex } = this.state;
const filterRE = getRE(filter, 'i');
let filtered = resources.filter(({ type, name }) =>
filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ]));
// const referenceLines = [];
// if (domContentLoadedTime != null) {
// referenceLines.push({
// time: domContentLoadedTime,
// color: DOM_LOADED_TIME_COLOR,
// })
// }
// if (loadTime != null) {
// referenceLines.push({
// time: loadTime,
// color: LOAD_TIME_COLOR,
// })
// }
//
// let tabs = TABS;
// if (!fetchPresented) {
// tabs = TABS.map(tab => tab.key === XHR
// ? {
// text: renderXHRText(),
// key: XHR,
// }
// : tab
// );
// }
const resourcesSize = filtered.reduce((sum, { decodedBodySize }) => sum + (decodedBodySize || 0), 0);
const transferredSize = filtered
const { filter, activeTab, currentIndex, filteredList } = this.state;
// const filterRE = getRE(filter, 'i');
// let filtered = resources.filter(({ type, name }) =>
// filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ]));
const resourcesSize = filteredList.reduce((sum, { decodedBodySize }) => sum + (decodedBodySize || 0), 0);
const transferredSize = filteredList
.reduce((sum, { headerSize, encodedBodySize }) => sum + (headerSize || 0) + (encodedBodySize || 0), 0);
return (
<React.Fragment>
<NetworkContent
// {...this.props }
time = { time }
// time = { time }
location = { location }
resources = { resources }
resources = { filteredList }
domContentLoadedTime = { domContentLoadedTime }
loadTime = { loadTime }
domBuildingTime = { domBuildingTime }
@ -210,91 +153,8 @@ export default class Network extends React.PureComponent {
resourcesSize={resourcesSize}
transferredSize={transferredSize}
onRowClick={ this.onRowClick }
currentIndex={playing ? null : currentIndex}
currentIndex={currentIndex}
/>
{/* <BottomBlock>
<BottomBlock.Header>
<Tabs
className="uppercase"
tabs={ tabs }
active={ activeTab }
onClick={ this.onTabClick }
border={ false }
/>
<Input
className="input-small"
placeholder="Filter by Name"
icon="search"
iconPosition="left"
name="filter"
onChange={ this.onFilterChange }
/>
</BottomBlock.Header>
<BottomBlock.Content>
<InfoLine>
<InfoLine.Point label={ filtered.length } value=" requests" />
<InfoLine.Point
label={ formatBytes(transferredSize) }
value="transferred"
display={ transferredSize > 0 }
/>
<InfoLine.Point
label={ formatBytes(resourcesSize) }
value="resources"
display={ resourcesSize > 0 }
/>
<InfoLine.Point
label="DOM Building Time"
value={ formatMs(domBuildingTime)}
display={ domBuildingTime != null }
/>
<InfoLine.Point
label="DOMContentLoaded"
value={ formatMs(domContentLoadedTime)}
display={ domContentLoadedTime != null }
dotColor={ DOM_LOADED_TIME_COLOR }
/>
<InfoLine.Point
label="Load"
value={ formatMs(loadTime)}
display={ loadTime != null }
dotColor={ LOAD_TIME_COLOR }
/>
</InfoLine>
<TimeTable
rows={ filtered }
referenceLines={referenceLines}
renderPopup
navigation
>
{[
{
label: "Status",
dataKey: 'status',
width: 70,
}, {
label: "Type",
dataKey: 'type',
width: 60,
}, {
label: "Name",
width: 130,
render: renderName,
},
{
label: "Size",
width: 60,
render: renderSize,
},
{
label: "Time",
width: 80,
render: renderDuration,
}
]}
</TimeTable>
</BottomBlock.Content>
</BottomBlock> */}
</React.Fragment>
);
}

View file

@ -22,7 +22,7 @@
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 100%;
max-width: 80%;
width: fit-content;
}
.popupNameContent {

View file

@ -7,6 +7,7 @@ import TimeTracker from './TimeTracker';
import { ReduxTime } from './Time';
import stl from './timeline.css';
import { TYPES } from 'Types/session/event';
import { setTimelinePointer } from 'Duck/sessions';
const getPointerIcon = (type) => {
// exception,
@ -69,7 +70,7 @@ const getPointerIcon = (type) => {
state.getIn([ 'sessions', 'current', 'clickRageTime' ]),
returningLocationTime: state.getIn([ 'sessions', 'current', 'returningLocation' ]) &&
state.getIn([ 'sessions', 'current', 'returningLocationTime' ]),
}))
}), { setTimelinePointer })
export default class Timeline extends React.PureComponent {
seekProgress = (e) => {
const { endTime } = this.props;
@ -78,9 +79,10 @@ export default class Timeline extends React.PureComponent {
this.props.jump(time);
}
createEventClickHandler = time => (e) => {
createEventClickHandler = pointer => (e) => {
e.stopPropagation();
this.props.jump(time)
this.props.jump(pointer.time);
this.props.setTimelinePointer(pointer);
}
componentDidMount() {
@ -144,7 +146,7 @@ export default class Timeline extends React.PureComponent {
//width: `${ 2000 * scale }%`
} }
className={ stl.clickRage }
onClick={ this.createEventClickHandler(iss.time) }
onClick={ this.createEventClickHandler(iss) }
>
<TimelinePointer
icon={iss.icon}
@ -165,7 +167,7 @@ export default class Timeline extends React.PureComponent {
//width: `${ 2000 * scale }%`
} }
className={ stl.clickRage }
onClick={ this.createEventClickHandler(e.time) }
onClick={ this.createEventClickHandler(e) }
>
<TimelinePointer
icon={getPointerIcon('click_rage')}
@ -259,7 +261,7 @@ export default class Timeline extends React.PureComponent {
key={ e.key }
className={ cn(stl.markup, stl.error) }
style={ { left: `${ e.time * scale }%`, top: '-30px' } }
onClick={ this.createEventClickHandler(e.time) }
onClick={ this.createEventClickHandler(e) }
>
<TimelinePointer
icon={getPointerIcon('exception')}
@ -305,7 +307,7 @@ export default class Timeline extends React.PureComponent {
//[ stl.info ]: !l.isYellow() && !l.isRed(),
}) }
style={ { left: `${ l.time * scale }%`, top: '-30px' } }
onClick={ this.createEventClickHandler(l.time) }
onClick={ this.createEventClickHandler(l) }
>
<TimelinePointer
icon={getPointerIcon('log')}
@ -360,7 +362,7 @@ export default class Timeline extends React.PureComponent {
[ stl.warning ]: r.isYellow(),
}) }
style={ { left: `${ r.time * scale }%`, top: '-30px' } }
onClick={ this.createEventClickHandler(r.time) }
onClick={ this.createEventClickHandler(r) }
>
<TimelinePointer
icon={getPointerIcon('resource')}
@ -407,7 +409,7 @@ export default class Timeline extends React.PureComponent {
key={ e.key }
className={ cn(stl.markup, stl.error) }
style={ { left: `${ e.time * scale }%`, top: '-30px' } }
onClick={ this.createEventClickHandler(e.time) }
onClick={ this.createEventClickHandler(e) }
>
<TimelinePointer
icon={getPointerIcon('fetch')}
@ -448,7 +450,7 @@ export default class Timeline extends React.PureComponent {
key={ e.key }
className={ cn(stl.markup, stl.error) }
style={ { left: `${ e.time * scale }%`, top: '-30px' } }
onClick={ this.createEventClickHandler(e.time) }
onClick={ this.createEventClickHandler(e) }
>
<TimelinePointer
icon={getPointerIcon('stack')}

View file

@ -4,17 +4,18 @@ import { browserIcon, osIcon, deviceTypeIcon } from 'App/iconNames';
import { formatTimeOrDate } from 'App/date';
import { sessions as sessionsRoute, funnel as funnelRoute, funnelIssue as funnelIssueRoute, withSiteId } from 'App/routes';
import { Icon, CountryFlag, IconButton, BackLink } from 'UI';
import { toggleFavorite } from 'Duck/sessions';
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
import cn from 'classnames';
import { connectPlayer } from 'Player';
import HeaderInfo from './HeaderInfo';
import SharePopup from '../shared/SharePopup/SharePopup';
import { fetchList as fetchListIntegration } from 'Duck/integrations/actions';
import cls from './playerBlockHeader.css';
import stl from './playerBlockHeader.css';
import Issues from './Issues/Issues';
import Autoplay from './Autoplay';
import AssistActions from '../Assist/components/AssistActions';
import AssistTabs from '../Assist/components/AssistTabs';
const SESSIONS_ROUTE = sessionsRoute();
@ -27,18 +28,24 @@ function capitalise(str) {
live: state.live,
loading: state.cssLoading || state.messagesLoading,
}))
@connect((state, props) => ({
session: state.getIn([ 'sessions', 'current' ]),
loading: state.getIn([ 'sessions', 'toggleFavoriteRequest', 'loading' ]),
disabled: state.getIn([ 'components', 'targetDefiner', 'inspectorMode' ]) || props.loading,
jiraConfig: state.getIn([ 'issues', 'list' ]).first(),
issuesFetched: state.getIn([ 'issues', 'issuesFetched' ]),
local: state.getIn(['sessions', 'timezone']),
funnelRef: state.getIn(['funnels', 'navRef']),
siteId: state.getIn([ 'user', 'siteId' ]),
funnelPage: state.getIn(['sessions', 'funnelPage']),
}), {
toggleFavorite, fetchListIntegration
@connect((state, props) => {
const isAssist = state.getIn(['sessions', 'activeTab']).type === 'live';
const hasSessioPath = state.getIn([ 'sessions', 'sessionPath' ]).includes('/sessions');
return {
session: state.getIn([ 'sessions', 'current' ]),
sessionPath: state.getIn([ 'sessions', 'sessionPath' ]),
loading: state.getIn([ 'sessions', 'toggleFavoriteRequest', 'loading' ]),
disabled: state.getIn([ 'components', 'targetDefiner', 'inspectorMode' ]) || props.loading,
jiraConfig: state.getIn([ 'issues', 'list' ]).first(),
issuesFetched: state.getIn([ 'issues', 'issuesFetched' ]),
local: state.getIn(['sessions', 'timezone']),
funnelRef: state.getIn(['funnels', 'navRef']),
siteId: state.getIn([ 'user', 'siteId' ]),
funnelPage: state.getIn(['sessions', 'funnelPage']),
hasSessionsPath: hasSessioPath && !isAssist,
}
}, {
toggleFavorite, fetchListIntegration, setSessionPath
})
@withRouter
export default class PlayerBlockHeader extends React.PureComponent {
@ -54,16 +61,22 @@ export default class PlayerBlockHeader extends React.PureComponent {
);
backHandler = () => {
const { history, siteId, funnelPage } = this.props;
const funnelId = funnelPage && funnelPage.get('funnelId');
const issueId = funnelPage && funnelPage.get('issueId');
if (funnelId || issueId) {
if (issueId) {
history.push(withSiteId(funnelIssueRoute(funnelId, issueId), siteId))
} else
history.push(withSiteId(funnelRoute(funnelId), siteId));
} else
const { history, siteId, funnelPage, sessionPath } = this.props;
// alert(sessionPath)
if (sessionPath === history.location.pathname) {
history.push(withSiteId(SESSIONS_ROUTE), siteId);
} else {
history.push(sessionPath ? sessionPath : withSiteId(SESSIONS_ROUTE, siteId));
}
// const funnelId = funnelPage && funnelPage.get('funnelId');
// const issueId = funnelPage && funnelPage.get('issueId');
// if (funnelId || issueId) {
// if (issueId) {
// history.push(withSiteId(funnelIssueRoute(funnelId, issueId), siteId))
// } else
// history.push(withSiteId(funnelRoute(funnelId), siteId));
// } else
// history.push(withSiteId(SESSIONS_ROUTE), siteId);
}
toggleFavorite = () => {
@ -86,21 +99,24 @@ export default class PlayerBlockHeader extends React.PureComponent {
userDevice,
userBrowserVersion,
userDeviceType,
live,
},
loading,
live,
// live,
disabled,
jiraConfig,
fullscreen,
hasSessionsPath
} = this.props;
const { history, siteId } = this.props;
// const { history, siteId } = this.props;
const _live = live && !hasSessionsPath;
return (
<div className={ cn(cls.header, "flex justify-between", { "hidden" : fullscreen}) }>
<div className={ cn(stl.header, "flex justify-between", { "hidden" : fullscreen}) }>
<div className="flex w-full">
<BackLink onClick={this.backHandler} label="Back" />
<div className={ cls.divider } />
<div className={ stl.divider } />
<div className="mx-4 flex items-center">
<CountryFlag country={ userCountry } />
@ -115,11 +131,17 @@ export default class PlayerBlockHeader extends React.PureComponent {
<HeaderInfo icon={ osIcon(userOs) } label={ userOs } />
<div className='ml-auto flex items-center'>
{ live && <AssistActions isLive userId={userId} /> }
{ !live && (
{ live && hasSessionsPath && (
<div className={stl.liveSwitchButton} onClick={() => this.props.setSessionPath('')}>
This Session is Now Continuing Live
</div>
)}
{ _live && <AssistTabs userId={userId} />}
{ _live && <AssistActions isLive userId={userId} /> }
{ !_live && (
<>
<Autoplay />
<div className={ cls.divider } />
<div className={ stl.divider } />
<IconButton
className="mr-2"
tooltip="Bookmark"
@ -143,7 +165,7 @@ export default class PlayerBlockHeader extends React.PureComponent {
/>
</>
)}
{ !live && jiraConfig && jiraConfig.token && <Issues sessionId={ sessionId } /> }
{ !_live && jiraConfig && jiraConfig.token && <Issues sessionId={ sessionId } /> }
</div>
</div>
</div>

View file

@ -135,7 +135,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
...computeTimeLine(this.props.rows, this.state.firstVisibleRowIndex, this.visibleCount),
});
}
if (this.props.activeIndex && prevProps.activeIndex !== this.props.activeIndex && this.scroller.current != null) {
if (this.props.activeIndex >= 0 && prevProps.activeIndex !== this.props.activeIndex && this.scroller.current != null) {
this.scroller.current.scrollToRow(this.props.activeIndex);
}
}
@ -168,7 +168,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
<div
style={ rowStyle }
key={ key }
className={ cn('border-b border-color-gray-light-shade', stl.row, { [ stl.hoverable ]: hoverable, "error color-red": !!row.isRed && row.isRed(), 'cursor-pointer' : typeof onRowClick === "function", [stl.activeRow] : activeIndex === index, 'color-white' : activeIndex === index }) }
className={ cn('border-b border-color-gray-light-shade', stl.row, { [ stl.hoverable ]: hoverable, "error color-red": !!row.isRed && row.isRed(), 'cursor-pointer' : typeof onRowClick === "function", [stl.activeRow] : activeIndex === index }) }
onClick={ typeof onRowClick === "function" ? () => onRowClick(row, index) : null }
id="table-row"
>
@ -223,7 +223,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
navigation=false,
referenceLines = [],
additionalHeight = 0,
activeIndex
activeIndex,
} = this.props;
const {
timewidth,
@ -247,7 +247,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
return (
<div className={ cn(className, "relative") }>
{ navigation &&
<div className={ cn(autoscrollStl.navButtons, "flex items-center") } style={{ top: '-33px', right: '30px' }} >
<div className={ cn(autoscrollStl.navButtons, "flex items-center") } >
<IconButton
size="small"
icon="chevron-up"

View file

@ -99,8 +99,6 @@ $offset: 10px;
}
}
.activeRow {
background-color: $teal;
background-color: rgba(54, 108, 217, 0.1);
}

View file

@ -12,8 +12,8 @@
.navButtons {
position: absolute;
right: 40px;
top: 10px;
right: 260px;
top: -34px;
}

View file

@ -12,3 +12,15 @@
background-color: $gray-light;
}
.liveSwitchButton {
cursor: pointer;
padding: 3px 8px;
border: solid thin $green;
color: $green;
border-radius: 3px;
margin-right: 10px;
&:hover {
background-color: $green;
color: white;
}
}

View file

@ -3,7 +3,7 @@ import { Duration } from 'luxon';
interface Props {
startTime: any,
className: string
className?: string
}
function Counter({ startTime, className }: Props) {

View file

@ -10,21 +10,33 @@ import {
TextEllipsis
} from 'UI';
import { deviceTypeIcon } from 'App/iconNames';
import { toggleFavorite } from 'Duck/sessions';
import { session as sessionRoute } from 'App/routes';
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
import { session as sessionRoute, withSiteId } from 'App/routes';
import { durationFormatted, formatTimeOrDate } from 'App/date';
import stl from './sessionItem.css';
import LiveTag from 'Shared/LiveTag';
import Bookmark from 'Shared/Bookmark';
import Counter from './Counter'
import { withRouter } from 'react-router-dom';
const Label = ({ label = '', color = 'color-gray-medium'}) => (
<div className={ cn('font-light text-sm', color)}>{label}</div>
)
@connect(state => ({
timezone: state.getIn(['sessions', 'timezone'])
}), { toggleFavorite })
timezone: state.getIn(['sessions', 'timezone']),
isAssist: state.getIn(['sessions', 'activeTab']).type === 'live',
siteId: state.getIn([ 'user', 'siteId' ]),
}), { toggleFavorite, setSessionPath })
@withRouter
export default class SessionItem extends React.PureComponent {
replaySession = () => {
const { history, session: { sessionId }, siteId, isAssist } = this.props;
if (!isAssist) {
this.props.setSessionPath(history.location.pathname)
}
history.push(withSiteId(sessionRoute(sessionId), siteId))
}
// eslint-disable-next-line complexity
render() {
const {
@ -110,9 +122,9 @@ export default class SessionItem extends React.PureComponent {
</div>
<div className={ stl.playLink } id="play-button" data-viewed={ viewed }>
<Link to={ sessionRoute(sessionId) }>
<div onClick={this.replaySession}>
<Icon name={ viewed ? 'play-fill' : 'play-circle-light' } size="30" color="teal" />
</Link>
</div>
</div>
</div>
</div>

View file

@ -92,7 +92,7 @@
display: flex;
align-items: center;
transition: all 0.2s;
/* opacity: 0; */
cursor: pointer;
&[data-viewed=true] {
opacity: 1;
}

View file

@ -7,6 +7,7 @@
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
height: 36px;
font-size: 14px;

View file

@ -17,6 +17,7 @@ const IGNORE = "errors/IGNORE";
const MERGE = "errors/MERGE";
const TOGGLE_FAVORITE = "errors/TOGGLE_FAVORITE";
const FETCH_TRACE = "errors/FETCH_TRACE";
const UPDATE_CURRENT_PAGE = "errors/UPDATE_CURRENT_PAGE";
function chartWrapper(chart = []) {
return chart.map(point => ({ ...point, count: Math.max(point.count, 0) }));
@ -33,7 +34,8 @@ const initialState = Map({
instance: ErrorInfo(),
instanceTrace: List(),
stats: Map(),
sourcemapUploaded: true
sourcemapUploaded: true,
currentPage: 1,
});
@ -67,7 +69,8 @@ function reducer(state = initialState, action = {}) {
return state.update("list", list => list.filter(e => !ids.includes(e.errorId)));
case success(FETCH_NEW_ERRORS_COUNT):
return state.set('stats', action.data);
case UPDATE_CURRENT_PAGE:
return state.set('currentPage', action.page);
}
return state;
}
@ -166,3 +169,9 @@ export function fetchNewErrorsCount(params = {}) {
}
}
export function updateCurrentPage(page) {
return {
type: 'errors/UPDATE_CURRENT_PAGE',
page,
};
}

View file

@ -119,7 +119,6 @@ const reducer = (state = initialState, action = {}) => {
let stages = [];
if (action.isRefresh) {
const activeStages = state.get('activeStages');
console.log('test', activeStages);
const oldInsights = state.get('insights');
const lastStage = action.data.stages[action.data.stages.length - 1]
const lastStageIndex = activeStages.toJS()[1];

View file

@ -8,7 +8,6 @@ import { getRE } from 'App/utils';
import { LAST_7_DAYS } from 'Types/app/period';
import { getDateRangeFromValue } from 'App/dateRange';
const INIT = 'sessions/INIT';
const FETCH_LIST = new RequestTypes('sessions/FETCH_LIST');
@ -25,6 +24,8 @@ const SET_EVENT_QUERY = 'sessions/SET_EVENT_QUERY';
const SET_AUTOPLAY_VALUES = 'sessions/SET_AUTOPLAY_VALUES';
const TOGGLE_CHAT_WINDOW = 'sessions/TOGGLE_CHAT_WINDOW';
const SET_FUNNEL_PAGE_FLAG = 'sessions/SET_FUNNEL_PAGE_FLAG';
const SET_TIMELINE_POINTER = 'sessions/SET_TIMELINE_POINTER';
const SET_SESSION_PATH = 'sessions/SET_SESSION_PATH';
const SET_ACTIVE_TAB = 'sessions/SET_ACTIVE_TAB';
@ -57,6 +58,8 @@ const initialState = Map({
insightFilters: defaultDateFilters,
host: '',
funnelPage: Map(),
timelinePointer: null,
sessionPath: '',
});
const reducer = (state = initialState, action = {}) => {
@ -242,13 +245,18 @@ const reducer = (state = initialState, action = {}) => {
return state.set('insights', List(action.data).sort((a, b) => b.count - a.count));
case SET_FUNNEL_PAGE_FLAG:
return state.set('funnelPage', action.funnelPage ? Map(action.funnelPage) : false);
case SET_TIMELINE_POINTER:
return state.set('timelinePointer', action.pointer);
case SET_SESSION_PATH:
return state.set('sessionPath', action.path);
default:
return state;
}
};
export default withRequestState({
_: [ FETCH, FETCH_LIST, FETCH_LIVE_LIST ],
_: [ FETCH, FETCH_LIST ],
fetchLiveListRequest: FETCH_LIVE_LIST,
fetchFavoriteListRequest: FETCH_FAVORITE_LIST,
toggleFavoriteRequest: TOGGLE_FAVORITE,
fetchErrorStackList: FETCH_ERROR_STACK,
@ -262,10 +270,10 @@ function init(session) {
}
}
export const fetchList = (params = {}, clear = false) => (dispatch, getState) => {
export const fetchList = (params = {}, clear = false, live = false) => (dispatch, getState) => {
const activeTab = getState().getIn([ 'sessions', 'activeTab' ]);
return dispatch(activeTab && activeTab.type === 'live' ? {
return dispatch((activeTab && activeTab.type === 'live' || live )? {
types: FETCH_LIVE_LIST.toArray(),
call: client => client.post('/assist/sessions', params),
} : {
@ -376,3 +384,16 @@ export function setFunnelPage(funnelPage) {
}
}
export function setTimelinePointer(pointer) {
return {
type: SET_TIMELINE_POINTER,
pointer
}
}
export function setSessionPath(path) {
return {
type: SET_SESSION_PATH,
path
}
}

View file

@ -109,7 +109,7 @@ export default class MessageDistributor extends StatedScreen {
private navigationStartOffset: number = 0;
private lastMessageTime: number = 0;
constructor(private readonly session: any /*Session*/, jwt: string, config) {
constructor(private readonly session: any /*Session*/, jwt: string, config, live: boolean) {
super();
this.pagesManager = new PagesManager(this, this.session.isMobile)
this.mouseManager = new MouseManager(this);
@ -117,7 +117,7 @@ export default class MessageDistributor extends StatedScreen {
this.sessionStart = this.session.startedAt;
if (this.session.live) {
if (live) {
// const sockUrl = `wss://live.openreplay.com/1/${ this.session.siteId }/${ this.session.sessionId }/${ jwt }`;
// this.subscribeOnMessages(sockUrl);
initListsDepr({})

View file

@ -94,13 +94,14 @@ export default class AssistManager {
return;
}
this.setStatus(ConnectionStatus.Connecting)
// @ts-ignore
const urlObject = new URL(window.ENV.API_EDP)
import('peerjs').then(({ default: Peer }) => {
if (this.closed) {return}
const _config = {
// @ts-ignore
host: new URL(window.ENV.API_EDP).host,
host: urlObject.hostname,
path: '/assist',
port: location.protocol === 'https:' ? 443 : 80,
port: urlObject.port === "" ? (location.protocol === 'https:' ? 443 : 80 ): parseInt(urlObject.port),
}
if (this.config) {

View file

@ -28,11 +28,11 @@ document.addEventListener("visibilitychange", function() {
}
});
export function init(session, jwt, config) {
const live = session.live;
export function init(session, jwt, config, live = false) {
// const live = session.live;
const endTime = !live && session.duration.valueOf();
instance = new Player(session, jwt, config);
instance = new Player(session, jwt, config, live);
update({
initialized: true,
live,

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 204.7 200.9" xmlns="http://www.w3.org/2000/svg">
<title>Fedora logo (2021)</title>
<path d="m102.7 1.987c-55.41 0-100.3 44.21-100.4 98.79h-0.01773v76.47h0.01773c0.02659 12.38 10.22 22.4 22.8 22.4h77.58c55.42-0.0349 100.3-44.24 100.3-98.79 8e-5 -54.58-44.91-98.79-100.4-98.79zm20.39 40.68c16.85 0 32.76 12.7 32.76 30.23 0 1.625 0.01 3.252-0.26 5.095-0.4668 4.662-4.794 8.012-9.505 7.355-4.711-0.6649-7.909-5.07-7.037-9.679 0.0799-0.526 0.1083-1.352 0.1083-2.772 0-9.938-8.257-13.77-16.06-13.77-7.805 0-14.84 6.462-14.85 13.77 0.1349 8.455 0 16.84 0 25.29l14.49-0.1067c11.31-0.2306 11.44 16.54 0.1305 16.46l-14.61 0.1067c-0.0354 6.801 0.0532 5.571 0.0178 8.996 0 0 0.1225 8.318-0.1296 14.62-1.749 18.52-17.76 33.32-37 33.32-20.4 0-37.2-16.41-37.2-36.54 0.6124-20.7 17.38-36.99 38.5-36.8l11.78-0.08737v16.43l-11.78 0.1066h-0.06216c-11.6 0.3382-21.55 8.1-21.74 20.34 0 11.15 9.148 20.08 20.5 20.08 11.34 0 20.42-8.124 20.42-20.06l-0.01772-62.23c0.0058-1.155 0.04435-2.073 0.1731-3.347 1.914-15.22 15.74-26.82 31.39-26.82z" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -34,8 +34,8 @@ export default Record({
startDate,
endDate,
sort: undefined,
order: undefined,
sort: 'startTs',
order: 'desc',
viewed: undefined,
consoleLogCount: undefined,

View file

@ -88,6 +88,7 @@ export default Record({
...session
}) => {
const duration = Duration.fromMillis(session.duration < 1000 ? 1000 : session.duration);
const durationSeconds = duration.valueOf();
const startedAt = +startTs;
const userDevice = session.userDevice || session.userDeviceType || 'Other';
@ -96,7 +97,7 @@ export default Record({
const events = List(session.events)
.map(e => SessionEvent({ ...e, time: e.timestamp - startedAt }))
.filter(({ type }) => type !== TYPES.CONSOLE);
.filter(({ type, time }) => type !== TYPES.CONSOLE && time <= durationSeconds);
let resources = List(session.resources)
.map(Resource);

View file

@ -1,14 +1,7 @@
- Initialize databases
- we've to pass the --wait flag, else the db installation won't be complete. and it'll break the db init.
- collate all dbs required
- How to distinguish b/w enterprise and community
- Or fist only community then enterprise
- install db migration
- have to have another helm chart with low hook value for higher prioriry
- install app
- customize values.yaml file
## Installation
helm upgrade --install databases ./databases -n db --create-namespace --wait -f ./values.yaml --atomic
helm upgrade --install openreplay ./openreplay -n app --create-namespace --wait -f ./values.yaml --atomic

View file

@ -1,4 +1,4 @@
## Licenses (as of October 28, 2021)
## Licenses (as of January 16, 2022)
Below is the list of dependencies used in OpenReplay software. Licenses may change between versions, so please keep this up to date with every new library you use.
@ -28,8 +28,16 @@ Below is the list of dependencies used in OpenReplay software. Licenses may chan
| pyjwt | MIT | Python |
| jsbeautifier | MIT | Python |
| psycopg2-binary | LGPL | Python |
| pytz | MIT | Python |
| fastapi | MIT | Python |
| uvicorn | BSD | Python |
| python-decouple | MIT | Python |
| pydantic | MIT | Python |
| apscheduler | MIT | Python |
| python-multipart | Apache | Python |
| elasticsearch-py | Apache2 | Python |
| jira | BSD2 | Python |
| clickhouse-driver | MIT | Python |
| python3-saml | MIT | Python |
| kubernetes | Apache2 | Python |
| chalice | Apache2 | Python |
| pandas | BSD3 | Python |
@ -80,11 +88,6 @@ Below is the list of dependencies used in OpenReplay software. Licenses may chan
| source-map | BSD3 | JavaScript |
| aws-sdk | Apache2 | JavaScript |
| serverless | MIT | JavaScript |
| schedule | MIT | Python |
| croniter | MIT | Python |
| lib/pq | MIT | Go |
| peerjs | MIT | JavaScript |
| antonmedv/finder | MIT | JavaScript |
| elasticsearch-py | Apache2 | Python |
| sentry-python | BSD2 | Python |
| jira | BSD2 | Python |