Merge pull request #1102 from openreplay/dev

recent dev changes
This commit is contained in:
Rajesh Rajendran 2023-04-04 16:27:12 +02:00 committed by GitHub
commit 74e3c95bf2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 124 additions and 136 deletions

View file

@ -18,41 +18,45 @@ import Roles from './Roles';
@withRouter
export default class Client extends React.PureComponent {
constructor(props){
constructor(props) {
super(props);
}
}
setTab = (tab) => {
this.props.history.push(clientRoute(tab));
}
};
renderActiveTab = () => (
<Switch>
<Route exact strict path={ clientRoute(CLIENT_TABS.PROFILE) } component={ ProfileSettings } />
<Route exact strict path={ clientRoute(CLIENT_TABS.INTEGRATIONS) } component={ Integrations } />
<Route exact strict path={ clientRoute(CLIENT_TABS.MANAGE_USERS) } component={ UserView } />
<Route exact strict path={ clientRoute(CLIENT_TABS.SITES) } component={ Sites } />
<Route exact strict path={ clientRoute(CLIENT_TABS.CUSTOM_FIELDS) } component={ CustomFields } />
<Route exact strict path={ clientRoute(CLIENT_TABS.WEBHOOKS) } component={ Webhooks } />
<Route exact strict path={ clientRoute(CLIENT_TABS.NOTIFICATIONS) } component={ Notifications } />
<Route exact strict path={ clientRoute(CLIENT_TABS.MANAGE_ROLES) } component={ Roles } />
<Route exact strict path={ clientRoute(CLIENT_TABS.AUDIT) } component={ AuditView } />
<Redirect to={ clientRoute(CLIENT_TABS.PROFILE) } />
<Route exact strict path={clientRoute(CLIENT_TABS.PROFILE)} component={ProfileSettings} />
<Route exact strict path={clientRoute(CLIENT_TABS.INTEGRATIONS)} component={Integrations} />
<Route exact strict path={clientRoute(CLIENT_TABS.MANAGE_USERS)} component={UserView} />
<Route exact strict path={clientRoute(CLIENT_TABS.SITES)} component={Sites} />
<Route exact strict path={clientRoute(CLIENT_TABS.CUSTOM_FIELDS)} component={CustomFields} />
<Route exact strict path={clientRoute(CLIENT_TABS.WEBHOOKS)} component={Webhooks} />
<Route exact strict path={clientRoute(CLIENT_TABS.NOTIFICATIONS)} component={Notifications} />
<Route exact strict path={clientRoute(CLIENT_TABS.MANAGE_ROLES)} component={Roles} />
<Route exact strict path={clientRoute(CLIENT_TABS.AUDIT)} component={AuditView} />
<Redirect to={clientRoute(CLIENT_TABS.PROFILE)} />
</Switch>
)
);
render() {
const { match: { params: { activeTab } } } = this.props;
const {
match: {
params: { activeTab },
},
} = this.props;
return (
<div className={ cn(styles.wrapper, 'page-margin container-90') }>
<div className={ styles.main }>
<div className={ styles.tabMenu }>
<div className={cn('page-margin container-90 flex relative')}>
<div className={styles.tabMenu}>
<PreferencesMenu activeTab={activeTab} />
</div>
<div className="bg-white w-full rounded-lg mx-4 my-8 border">
{ activeTab && this.renderActiveTab() }
<div className={cn('side-menu-margined w-full')}>
<div className="bg-white w-full rounded-lg mx-auto mb-8 border" style={{ maxWidth: '1300px'}}>
{activeTab && this.renderActiveTab()}
</div>
</div>
</div>
</div>
);
}

View file

@ -9,7 +9,7 @@ import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
function DashboardList() {
const { dashboardStore } = useStore();
const list = dashboardStore.filteredList;
const dashboardsSearch = dashboardStore.dashboardsSearch;
const dashboardsSearch = dashboardStore.filter.query;
const lenth = list.length;
return (
@ -18,17 +18,11 @@ function DashboardList() {
title={
<div className="flex flex-col items-center justify-center">
<AnimatedSVG name={ICONS.NO_DASHBOARDS} size={180} />
<div className="text-center my-4">
{dashboardsSearch !== '' ? (
'No matching results'
) : (
<div>
<div>You haven't created any dashboards yet</div>
<div className="text-sm color-gray-medium font-normal">
A Dashboard is a collection of Cards that can be shared across teams.
</div>
</div>
)}
<div className="text-center mt-4">
{dashboardsSearch !== '' ? 'No matching results' : "You haven't created any dashboards yet"}
</div>
<div className="text-sm color-gray-medium font-normal">
A Dashboard is a collection of Cards that can be shared across teams.
</div>
</div>
}

View file

@ -5,9 +5,9 @@ import SaveFilterButton from 'Shared/SaveFilterButton';
import { connect } from 'react-redux';
import { Button } from 'UI';
import { edit, addFilter, fetchSessions, updateFilter } from 'Duck/search';
import SessionSearchQueryParamHandler from 'Shared/SessionSearchQueryParamHandler';
import { debounce } from 'App/utils';
import useSessionSearchQueryHandler from 'App/hooks/useSessionSearchQueryHandler';
let debounceFetch: any = () => {}
@ -24,6 +24,9 @@ function SessionSearch(props: Props) {
const { appliedFilter, saveRequestPayloads = false, metaLoading } = props;
const hasEvents = appliedFilter.filters.filter((i: any) => i.isEvent).size > 0;
const hasFilters = appliedFilter.filters.filter((i: any) => !i.isEvent).size > 0;
useSessionSearchQueryHandler({ appliedFilter, applyFilter: props.updateFilter });
useEffect(() => {
debounceFetch = debounce(() => props.fetchSessions(), 500);
}, [])
@ -71,7 +74,6 @@ function SessionSearch(props: Props) {
return !metaLoading && (
<>
<SessionSearchQueryParamHandler />
{hasEvents || hasFilters ? (
<div className="border bg-white rounded mt-4">
<div className="p-5">

View file

@ -15,7 +15,6 @@ interface Props {
fetchFilterSearch: (query: any) => void;
addFilterByKeyAndValue: (key: string, value: string) => void;
liveAddFilterByKeyAndValue: (key: string, value: string) => void;
filterSearchList: any;
liveFetchFilterSearch: any;
}
function SessionSearchField(props: Props) {

View file

@ -1,39 +0,0 @@
import React, { useEffect } from 'react';
import { useHistory } from 'react-router';
import { connect } from 'react-redux';
import { addFilterByKeyAndValue, addFilter } from 'Duck/search';
import { updateFilter } from 'Duck/search';
import { createUrlQuery, getFiltersFromQuery } from 'App/utils/search';
interface Props {
appliedFilter: any;
updateFilter: any;
addFilterByKeyAndValue: typeof addFilterByKeyAndValue;
addFilter: typeof addFilter;
}
const SessionSearchQueryParamHandler = (props: Props) => {
const { appliedFilter } = props;
const history = useHistory();
const applyFilterFromQuery = () => {
const filter = getFiltersFromQuery(history.location.search, appliedFilter);
props.updateFilter(filter, true, false);
};
const generateUrlQuery = () => {
const search: any = createUrlQuery(appliedFilter);
history.replace({ search });
};
useEffect(applyFilterFromQuery, []);
useEffect(generateUrlQuery, [appliedFilter]);
return <></>;
};
export default connect(
(state: any) => ({
appliedFilter: state.getIn(['search', 'instance']),
}),
{ addFilterByKeyAndValue, addFilter, updateFilter }
)(SessionSearchQueryParamHandler);

View file

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

View file

@ -0,0 +1,35 @@
import { useEffect } from 'react';
import { useHistory } from 'react-router';
import { createUrlQuery, getFiltersFromQuery } from 'App/utils/search';
interface Props {
appliedFilter: any;
applyFilter: any;
}
const useSessionSearchQueryHandler = (props: Props) => {
const { appliedFilter, applyFilter } = props;
const history = useHistory();
useEffect(() => {
const applyFilterFromQuery = () => {
const filter = getFiltersFromQuery(history.location.search, appliedFilter);
applyFilter(filter, true, false);
};
applyFilterFromQuery();
}, []);
useEffect(() => {
const generateUrlQuery = () => {
const search: any = createUrlQuery(appliedFilter);
history.replace({ search });
};
generateUrlQuery();
}, [appliedFilter]);
return null;
};
export default useSessionSearchQueryHandler;

View file

@ -4,7 +4,7 @@ import type Screen from '../../Screen/Screen';
import type { Message, SetNodeScroll } from '../../messages';
import { MType } from '../../messages';
import ListWalker from '../../../common/ListWalker';
import StylesManager, { rewriteNodeStyleSheet } from './StylesManager';
import StylesManager from './StylesManager';
import FocusManager from './FocusManager';
import SelectionManager from './SelectionManager';
import type { StyleElement } from './VirtualDOM';
@ -289,11 +289,6 @@ export default class DOMManager extends ListWalker<Message> {
vn = this.vTexts.get(msg.id)
if (!vn) { logger.error("SetCssData: Node not found", msg); return }
vn.setData(msg.data)
if (vn.node instanceof HTMLStyleElement) {
doc = this.screen.document
// TODO: move to message parsing
doc && rewriteNodeStyleSheet(doc, vn.node)
}
if (msg.tp === MType.SetCssData) { // Styles in priority (do we need inlines as well?)
vn.applyChanges()
}

View file

@ -2,8 +2,7 @@ import logger from 'App/logger';
import type { SetNodeFocus } from '../../messages';
import type { VElement } from './VirtualDOM';
import ListWalker from '../../../common/ListWalker';
const FOCUS_CLASS = "-openreplay-focus"
import { FOCUS_CLASSNAME } from '../../messages/rewriter/constants'
export default class FocusManager extends ListWalker<SetNodeFocus> {
constructor(private readonly vElements: Map<number, VElement>) {super()}
@ -11,7 +10,7 @@ export default class FocusManager extends ListWalker<SetNodeFocus> {
move(t: number) {
const msg = this.moveGetLast(t)
if (!msg) {return}
this.focused?.classList.remove(FOCUS_CLASS)
this.focused?.classList.remove(FOCUS_CLASSNAME)
if (msg.id === -1) {
this.focused = null
return
@ -19,7 +18,7 @@ export default class FocusManager extends ListWalker<SetNodeFocus> {
const vn = this.vElements.get(msg.id)
if (!vn) { logger.error("Node not found", msg); return }
this.focused = vn.node
this.focused.classList.add(FOCUS_CLASS)
this.focused.classList.add(FOCUS_CLASSNAME)
}
}

View file

@ -1,19 +1,18 @@
import type Screen from '../../Screen/Screen';
import type { CssInsertRule, CssDeleteRule } from '../../messages';
import { replaceCSSPseudoclasses } from '../../messages/rewriter/rewriteMessage'
type CSSRuleMessage = CssInsertRule | CssDeleteRule;
const HOVER_CN = "-openreplay-hover";
const HOVER_SELECTOR = `.${HOVER_CN}`;
// Doesn't work with css files (hasOwnProperty)
export function rewriteNodeStyleSheet(doc: Document, node: HTMLLinkElement | HTMLStyleElement) {
// Doesn't work with css files (hasOwnProperty returns false)
// TODO: recheck and remove if true
function rewriteNodeStyleSheet(doc: Document, node: HTMLLinkElement | HTMLStyleElement) {
const ss = Object.values(doc.styleSheets).find(s => s.ownerNode === node);
if (!ss || !ss.hasOwnProperty('rules')) { return; }
for(let i = 0; i < ss.rules.length; i++){
const r = ss.rules[i]
if (r instanceof CSSStyleRule) {
r.selectorText = r.selectorText.replace('/\:hover/g', HOVER_SELECTOR)
r.selectorText = replaceCSSPseudoclasses(r.selectorText)
}
}
}
@ -29,7 +28,7 @@ export default class StylesManager {
this.linkLoadingCount = 0;
this.linkLoadPromises = [];
//cancel all promises? tothinkaboutit
//cancel all promises? thinkaboutit
}
setStyleHandlers(node: HTMLLinkElement, value: string): void {
@ -44,6 +43,7 @@ export default class StylesManager {
}
timeoutId = setTimeout(addSkipAndResolve, 4000);
// It would be better to make it more relyable with addEventListener
node.onload = () => {
const doc = this.screen.document;
doc && rewriteNodeStyleSheet(doc, node);

View file

@ -1,20 +1,14 @@
import type Screen from '../Screen/Screen'
import type { MouseMove } from "../messages";
import { HOVER_CLASSNAME } from '../messages/rewriter/constants'
import ListWalker from '../../common/ListWalker'
const HOVER_CLASS = "-openreplay-hover";
const HOVER_CLASS_DEPR = "-asayer-hover";
export default class MouseMoveManager extends ListWalker<MouseMove> {
private hoverElements: Array<Element> = []
constructor(private screen: Screen) {super()}
// private getCursorTarget() {
// return this.screen.getElementFromInternalPoint(this.current)
// }
private getCursorTargets() {
return this.screen.getElementsFromInternalPoint(this.current)
}
@ -25,12 +19,10 @@ export default class MouseMoveManager extends ListWalker<MouseMove> {
const diffRemove = this.hoverElements.filter(elem => !curHoverElements.includes(elem))
this.hoverElements = curHoverElements
diffAdd.forEach(elem => {
elem.classList.add(HOVER_CLASS)
elem.classList.add(HOVER_CLASS_DEPR)
elem.classList.add(HOVER_CLASSNAME)
})
diffRemove.forEach(elem => {
elem.classList.remove(HOVER_CLASS)
elem.classList.remove(HOVER_CLASS_DEPR)
elem.classList.remove(HOVER_CLASSNAME)
})
}

View file

@ -2,7 +2,7 @@ import type { RawMessage } from './raw.gen'
import type { TrackerMessage } from './tracker.gen'
import translate from './tracker.gen'
import { TP_MAP } from './tracker-legacy.gen'
import resolveURL from './urlBasedResolver'
import rewriteMessage from './rewriter/rewriteMessage'
function legacyTranslate(msg: any): RawMessage | null {
@ -30,7 +30,7 @@ export default class JSONRawMessageReader {
if (!rawMsg) {
return this.readMessage()
}
return resolveURL(rawMsg)
return rewriteMessage(rawMsg)
}
}

View file

@ -2,7 +2,7 @@ import type { Message } from './message.gen';
import type { RawMessage } from './raw.gen';
import { MType } from './raw.gen';
import RawMessageReader from './RawMessageReader.gen';
import resolveURL from './urlBasedResolver'
import rewriteMessage from './rewriter/rewriteMessage'
import Logger from 'App/logger'
// TODO: composition instead of inheritance
@ -77,7 +77,7 @@ export default class MFileReader extends RawMessageReader {
}
const index = this.getLastMessageID()
const msg = Object.assign(resolveURL(rMsg), {
const msg = Object.assign(rewriteMessage(rMsg), {
time: this.currentTime,
_index: index,
})

View file

@ -0,0 +1,2 @@
export const HOVER_CLASSNAME = "-openreplay-hover"
export const FOCUS_CLASSNAME = "-openreplay-focus"

View file

@ -10,24 +10,31 @@ import type {
RawAdoptedSsInsertRule,
RawAdoptedSsReplaceURLBased,
RawAdoptedSsReplace,
} from './raw.gen'
import { MType } from './raw.gen'
} from '../raw.gen'
import { MType } from '../raw.gen'
import { resolveURL, resolveCSS } from './urlResolve'
import { HOVER_CLASSNAME, FOCUS_CLASSNAME } from './constants'
// type PickMessage<T extends MType> = Extract<RawMessage, { tp: T }>;
// type ResolversMap = {
// [Key in MType]: (event: PickMessage<Key>) => RawMessage
// }
const HOVER_SELECTOR = `.${HOVER_CLASSNAME}`
const FOCUS_SELECTOR = `.${FOCUS_CLASSNAME}`
export function replaceCSSPseudoclasses(cssText: string): string {
return cssText
.replace('/\:hover/g', HOVER_SELECTOR)
.replace('/\:focus/g', FOCUS_SELECTOR)
}
function rewriteCSS(baseURL: string, cssText: string): string {
return replaceCSSPseudoclasses(resolveCSS(baseURL, cssText))
}
// TODO: commonURLBased logic for feilds
const resolvers = {
// TODO: common logic for URL fields in all the ...URLBased messages
const REWRITERS = {
[MType.SetNodeAttributeURLBased]: (msg: RawSetNodeAttributeURLBased): RawSetNodeAttribute =>
({
...msg,
value: msg.name === 'src' || msg.name === 'href'
? resolveURL(msg.baseURL, msg.value)
: (msg.name === 'style'
? resolveCSS(msg.baseURL, msg.value)
? rewriteCSS(msg.baseURL, msg.value)
: msg.value
),
tp: MType.SetNodeAttribute,
@ -35,35 +42,35 @@ const resolvers = {
[MType.SetCssDataURLBased]: (msg: RawSetCssDataURLBased): RawSetCssData =>
({
...msg,
data: resolveCSS(msg.baseURL, msg.data),
data: rewriteCSS(msg.baseURL, msg.data),
tp: MType.SetCssData,
}),
[MType.CssInsertRuleURLBased]: (msg: RawCssInsertRuleURLBased): RawCssInsertRule =>
({
...msg,
rule: resolveCSS(msg.baseURL, msg.rule),
rule: rewriteCSS(msg.baseURL, msg.rule),
tp: MType.CssInsertRule,
}),
[MType.AdoptedSsInsertRuleURLBased]: (msg: RawAdoptedSsInsertRuleURLBased): RawAdoptedSsInsertRule =>
({
...msg,
rule: resolveCSS(msg.baseURL, msg.rule),
rule: rewriteCSS(msg.baseURL, msg.rule),
tp: MType.AdoptedSsInsertRule,
}),
[MType.AdoptedSsReplaceURLBased]: (msg: RawAdoptedSsReplaceURLBased): RawAdoptedSsReplace =>
({
...msg,
text: resolveCSS(msg.baseURL, msg.text),
text: rewriteCSS(msg.baseURL, msg.text),
tp: MType.AdoptedSsReplace,
}),
} as const
export default function resolve(msg: RawMessage): RawMessage {
// @ts-ignore --- any idea?
if (resolvers[msg.tp]) {
export default function rewriteMessage(msg: RawMessage): RawMessage {
// @ts-ignore --- any idea for correct typing?
if (REWRITERS[msg.tp]) {
// @ts-ignore
return resolvers[msg.tp](msg)
return REWRITERS[msg.tp](msg)
}
return msg
}

View file

@ -35,7 +35,7 @@ message 92, 'IOSMetadata' do
string 'Value'
end
message 93, 'IOSCustomEvent', :seq_index => true, :replayer => true do
message 93, 'IOSCustomEvent', :replayer => true do
uint 'Timestamp'
uint 'Length'
string 'Name'
@ -63,7 +63,7 @@ message 96, 'IOSScreenChanges', :replayer => true do
uint 'Height'
end
message 97, 'IOSCrash', :seq_index => true do
message 97, 'IOSCrash' do
uint 'Timestamp'
uint 'Length'
string 'Name'
@ -71,7 +71,7 @@ message 97, 'IOSCrash', :seq_index => true do
string 'Stacktrace'
end
message 98, 'IOSScreenEnter', :seq_index => true do
message 98, 'IOSScreenEnter' do
uint 'Timestamp'
uint 'Length'
string 'Title'
@ -85,7 +85,7 @@ message 99, 'IOSScreenLeave' do
string 'ViewName'
end
message 100, 'IOSClickEvent', :seq_index => true, :replayer => true do
message 100, 'IOSClickEvent', :replayer => true do
uint 'Timestamp'
uint 'Length'
string 'Label'
@ -93,7 +93,7 @@ message 100, 'IOSClickEvent', :seq_index => true, :replayer => true do
uint 'Y'
end
message 101, 'IOSInputEvent', :seq_index => true do
message 101, 'IOSInputEvent' do
uint 'Timestamp'
uint 'Length'
string 'Value'
@ -115,7 +115,7 @@ Name/Value may be :
"mainThreadCPU": Possible values (0 .. 100)
"memoryUsage": Used memory in bytes
=end
message 102, 'IOSPerformanceEvent', :replayer => true, :seq_index => true do
message 102, 'IOSPerformanceEvent', :replayer => true do
uint 'Timestamp'
uint 'Length'
string 'Name'
@ -135,7 +135,7 @@ message 104, 'IOSInternalError' do
string 'Content'
end
message 105, 'IOSNetworkCall', :replayer => true, :seq_index => true do
message 105, 'IOSNetworkCall', :replayer => true do
uint 'Timestamp'
uint 'Length'
uint 'Duration'
@ -163,7 +163,7 @@ message 110, 'IOSPerformanceAggregated', :swift => false do
uint 'MaxBattery'
end
message 111, 'IOSIssueEvent', :seq_index => true do
message 111, 'IOSIssueEvent' do
uint 'Timestamp'
string 'Type'
string 'ContextString'

View file

@ -84,14 +84,13 @@ end
$context = :web
class Message
attr_reader :id, :name, :tracker, :replayer, :swift, :seq_index, :attributes, :context
def initialize(name:, id:, tracker: $context == :web, replayer: $context == :web, swift: $context == :ios, seq_index: false, &block)
attr_reader :id, :name, :tracker, :replayer, :swift, :attributes, :context
def initialize(name:, id:, tracker: $context == :web, replayer: $context == :web, swift: $context == :ios, &block)
@id = id
@name = name
@tracker = tracker
@replayer = replayer
@swift = swift
@seq_index = seq_index
@context = $context
@attributes = []
# opts.each { |key, value| send "#{key}=", value }