Merge remote-tracking branch 'origin/dev' into api-v1.11.0
This commit is contained in:
commit
6a67007cfc
22 changed files with 75 additions and 63 deletions
|
|
@ -167,12 +167,14 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
|
||||
values = {**values, **sh.multi_values(helper.values_for_operator(value=s["value"], op=s["operator"]),
|
||||
value_key=f"value{i + 1}")}
|
||||
if sh.is_negation_operator(op) and i > 0:
|
||||
if sh.is_negation_operator(s["operator"]) and i > 0:
|
||||
op = sh.reverse_sql_operator(op)
|
||||
main_condition = "left_not.session_id ISNULL"
|
||||
extra_from.append(f"""LEFT JOIN LATERAL (SELECT session_id
|
||||
FROM {next_table} AS s_main
|
||||
WHERE s_main.{next_col_name} {op} %(value{i + 1})s
|
||||
WHERE
|
||||
{sh.multi_conditions(f"s_main.{next_col_name} {op} %(value{i + 1})s",
|
||||
values=s["value"], value_key=f"value{i + 1}")}
|
||||
AND s_main.timestamp >= T{i}.stage{i}_timestamp
|
||||
AND s_main.session_id = T1.session_id) AS left_not ON (TRUE)""")
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -173,12 +173,14 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
|
||||
values = {**values, **sh.multi_values(helper.values_for_operator(value=s["value"], op=s["operator"]),
|
||||
value_key=f"value{i + 1}")}
|
||||
if sh.is_negation_operator(op) and i > 0:
|
||||
if sh.is_negation_operator(s["operator"]) and i > 0:
|
||||
op = sh.reverse_sql_operator(op)
|
||||
main_condition = "left_not.session_id ISNULL"
|
||||
extra_from.append(f"""LEFT JOIN LATERAL (SELECT session_id
|
||||
FROM {next_table} AS s_main
|
||||
WHERE s_main.{next_col_name} {op} %(value{i + 1})s
|
||||
WHERE
|
||||
{sh.multi_conditions(f"s_main.{next_col_name} {op} %(value{i + 1})s",
|
||||
values=s["value"], value_key=f"value{i + 1}")}
|
||||
AND s_main.timestamp >= T{i}.stage{i}_timestamp
|
||||
AND s_main.session_id = T1.session_id) AS left_not ON (TRUE)""")
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -173,12 +173,14 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
|
||||
values = {**values, **sh.multi_values(helper.values_for_operator(value=s["value"], op=s["operator"]),
|
||||
value_key=f"value{i + 1}")}
|
||||
if sh.is_negation_operator(op) and i > 0:
|
||||
if sh.is_negation_operator(s["operator"]) and i > 0:
|
||||
op = sh.reverse_sql_operator(op)
|
||||
main_condition = "left_not.session_id ISNULL"
|
||||
extra_from.append(f"""LEFT JOIN LATERAL (SELECT session_id
|
||||
FROM {next_table} AS s_main
|
||||
WHERE s_main.{next_col_name} {op} %(value{i + 1})s
|
||||
WHERE
|
||||
{sh.multi_conditions(f"s_main.{next_col_name} {op} %(value{i + 1})s",
|
||||
values=s["value"], value_key=f"value{i + 1}")}
|
||||
AND s_main.timestamp >= T{i}.stage{i}_timestamp
|
||||
AND s_main.session_id = T1.session_id) AS left_not ON (TRUE)""")
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ function ClickMapCard({
|
|||
const onMarkerClick = (s: string, innerText: string) => {
|
||||
metricStore.changeClickMapSearch(s, innerText)
|
||||
}
|
||||
const mapUrl = metricStore.instance.series[0].filter.filters[0].value[0]
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => clearCurrentSession()
|
||||
|
|
@ -32,12 +33,10 @@ function ClickMapCard({
|
|||
|
||||
React.useEffect(() => {
|
||||
if (visitedEvents.length) {
|
||||
const urlOptions = visitedEvents.map(({ url, host }: any) => ({ label: url, value: url, host }))
|
||||
const url = insightsFilters.url ? insightsFilters.url : host + urlOptions[0].value;
|
||||
const rangeValue = dashboardStore.drillDownPeriod.rangeValue
|
||||
const startDate = dashboardStore.drillDownPeriod.start
|
||||
const endDate = dashboardStore.drillDownPeriod.end
|
||||
fetchInsights({ ...insightsFilters, url, startDate, endDate, rangeValue, clickRage: metricStore.clickMapFilter })
|
||||
fetchInsights({ ...insightsFilters, url: mapUrl || '/', startDate, endDate, rangeValue, clickRage: metricStore.clickMapFilter })
|
||||
}
|
||||
}, [visitedEvents, metricStore.clickMapFilter])
|
||||
|
||||
|
|
@ -62,9 +61,8 @@ function ClickMapCard({
|
|||
return <div className="py-2">Loading session</div>
|
||||
}
|
||||
|
||||
const searchUrl = metricStore.instance.series[0].filter.filters[0].value[0]
|
||||
const jumpToEvent = metricStore.instance.data.events.find((evt: Record<string, any>) => {
|
||||
if (searchUrl) return evt.path.includes(searchUrl)
|
||||
if (mapUrl) return evt.path.includes(mapUrl)
|
||||
return evt
|
||||
}) || { timestamp: metricStore.instance.data.startTs }
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ function Player() {
|
|||
}
|
||||
}, []);
|
||||
|
||||
|
||||
if (!playerContext.player) return null;
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import Highlight from 'react-highlight';
|
|||
|
||||
const inputModeOptions = [
|
||||
{ label: 'Record all inputs', value: 'plain' },
|
||||
{ label: 'Ignore all inputs', value: 'obscured' },
|
||||
{ label: 'Obscure all inputs', value: 'hidden' },
|
||||
{ label: 'Ignore all inputs', value: 'obscured' },
|
||||
];
|
||||
|
||||
const inputModeOptionsMap: any = {}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import CodeSnippet from '../../CodeSnippet';
|
|||
|
||||
const inputModeOptions = [
|
||||
{ label: 'Record all inputs', value: 'plain' },
|
||||
{ label: 'Ignore all inputs', value: 'obscured' },
|
||||
{ label: 'Obscure all inputs', value: 'hidden' },
|
||||
{ label: 'Ignore all inputs', value: 'obscured' },
|
||||
];
|
||||
|
||||
const inputModeOptionsMap = {}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ export default class MessageManager {
|
|||
async loadMessages(isClickmap: boolean = false) {
|
||||
this.setMessagesLoading(true)
|
||||
// TODO: reusable decryptor instance
|
||||
const createNewParser = (shouldDecrypt = true) => {
|
||||
const createNewParser = (shouldDecrypt = true, file) => {
|
||||
const decrypt = shouldDecrypt && this.session.fileKey
|
||||
? (b: Uint8Array) => decryptSessionBytes(b, this.session.fileKey)
|
||||
: (b: Uint8Array) => Promise.resolve(b)
|
||||
|
|
@ -206,11 +206,21 @@ export default class MessageManager {
|
|||
fileReader.append(b)
|
||||
const msgs: Array<Message> = []
|
||||
for (let msg = fileReader.readNext();msg !== null;msg = fileReader.readNext()) {
|
||||
this.distributeMessage(msg, msg._index)
|
||||
msgs.push(msg)
|
||||
}
|
||||
const sorted = msgs.sort((m1, m2) => m1.time - m2.time)
|
||||
|
||||
let indx = sorted[0]._index
|
||||
let outOfOrderCounter = 0
|
||||
sorted.forEach(msg => {
|
||||
if (indx > msg._index) outOfOrderCounter++
|
||||
else indx = msg._index
|
||||
this.distributeMessage(msg, msg._index)
|
||||
})
|
||||
|
||||
if (outOfOrderCounter > 0) console.warn("Unsorted mob file, error count: ", outOfOrderCounter)
|
||||
logger.info("Messages count: ", msgs.length, sorted, file)
|
||||
|
||||
logger.info("Messages count: ", msgs.length, msgs)
|
||||
this._sortMessagesHack(msgs)
|
||||
this.setMessagesLoading(false)
|
||||
})
|
||||
|
|
@ -219,14 +229,14 @@ export default class MessageManager {
|
|||
this.waitingForFiles = true
|
||||
|
||||
const loadMethod = this.session.domURL && this.session.domURL.length > 0
|
||||
? { url: this.session.domURL, parser: createNewParser }
|
||||
: { url: this.session.mobsUrl, parser: () => createNewParser(false)}
|
||||
? { url: this.session.domURL, parser: () => createNewParser(true, 'dom') }
|
||||
: { url: this.session.mobsUrl, parser: () => createNewParser(false, 'dom')}
|
||||
|
||||
loadFiles(loadMethod.url, loadMethod.parser())
|
||||
// EFS fallback
|
||||
.catch((e) =>
|
||||
requestEFSDom(this.session.sessionId)
|
||||
.then(createNewParser(false))
|
||||
.then(createNewParser(false, 'domEFS'))
|
||||
)
|
||||
.then(this.onFileReadSuccess)
|
||||
.catch(this.onFileReadFailed)
|
||||
|
|
@ -235,11 +245,11 @@ export default class MessageManager {
|
|||
// load devtools (TODO: start after the first DOM file download)
|
||||
if (isClickmap) return;
|
||||
this.state.update({ devtoolsLoading: true })
|
||||
loadFiles(this.session.devtoolsURL, createNewParser())
|
||||
loadFiles(this.session.devtoolsURL, createNewParser(true, 'devtools'))
|
||||
// EFS fallback
|
||||
.catch(() =>
|
||||
requestEFSDevtools(this.session.sessionId)
|
||||
.then(createNewParser(false))
|
||||
.then(createNewParser(false, 'devtoolsEFS'))
|
||||
)
|
||||
.then(() => {
|
||||
this.state.update(this.lists.getFullListsState()) // TODO: also in case of dynamic update through assist
|
||||
|
|
@ -406,23 +416,18 @@ export default class MessageManager {
|
|||
this.lists.lists.fetch.insert(getResourceFromNetworkRequest(msg, this.sessionStart))
|
||||
break;
|
||||
case MType.Redux:
|
||||
logger.log('redux', msg)
|
||||
this.lists.lists.redux.append(msg);
|
||||
break;
|
||||
case MType.NgRx:
|
||||
logger.log('ngrx', msg)
|
||||
this.lists.lists.ngrx.append(msg);
|
||||
break;
|
||||
case MType.Vuex:
|
||||
logger.log('vuex', msg)
|
||||
this.lists.lists.vuex.append(msg);
|
||||
break;
|
||||
case MType.Zustand:
|
||||
logger.log('zustand', msg)
|
||||
this.lists.lists.zustand.append(msg)
|
||||
break
|
||||
case MType.MobX:
|
||||
logger.log('mobx', msg)
|
||||
this.lists.lists.mobx.append(msg);
|
||||
break;
|
||||
case MType.GraphQl:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import Cursor from './Cursor'
|
|||
|
||||
import type { Point, Dimensions } from './types';
|
||||
|
||||
|
||||
export type State = Dimensions
|
||||
|
||||
export const INITIAL_STATE: State = {
|
||||
|
|
@ -182,7 +181,7 @@ export default class Screen {
|
|||
getElementBySelector(selector: string) {
|
||||
if (!selector) return null;
|
||||
try {
|
||||
const safeSelector = selector.replace(/:/g, '\\\\3A ').replace(/\//g, '\\/');
|
||||
const safeSelector = selector.replace(/\//g, '\\/');
|
||||
return this.document?.querySelector<HTMLElement>(safeSelector) || null;
|
||||
} catch (e) {
|
||||
console.error("Can not select element. ", e)
|
||||
|
|
@ -218,7 +217,7 @@ export default class Screen {
|
|||
case ScaleMode.AdjustParentHeight:
|
||||
this.scaleRatio = offsetWidth / width
|
||||
translate = "translate(-50%, 0)"
|
||||
posStyles = { top: 0, height: this.document!.documentElement.getBoundingClientRect().height + 'px', }
|
||||
posStyles = { top: 0, height: height + 'px', }
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ export default class TargetMarker {
|
|||
})
|
||||
}
|
||||
|
||||
Object.assign(smallClicksBubble.style, clickmapStyles.clicks({ top, height, isRage: s.clickRage }))
|
||||
Object.assign(smallClicksBubble.style, clickmapStyles.clicks({ top, height, isRage: s.clickRage, left }))
|
||||
|
||||
border.appendChild(smallClicksBubble)
|
||||
overlay.appendChild(bubbleContainer)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const clickmapStyles = {
|
|||
},
|
||||
bubbleContainer: ({ top, left, height }: { top: number; left: number, height: number }) => ({
|
||||
position: 'absolute',
|
||||
top: top > 20 ? top + 'px' : height + 2 + 'px',
|
||||
top: top > 75 ? top + 'px' : height+75 + 'px',
|
||||
width: '250px',
|
||||
left: `${left}px`,
|
||||
padding: '10px',
|
||||
|
|
@ -51,9 +51,9 @@ export const clickmapStyles = {
|
|||
position: 'absolute',
|
||||
zIndex,
|
||||
}),
|
||||
clicks: ({ top, height, isRage }: { top: number; height: number, isRage?: boolean }) => ({
|
||||
clicks: ({ top, height, isRage, left }: { top: number; height: number, isRage?: boolean, left: number }) => ({
|
||||
top: top > 20 ? 0 : `${height}px`,
|
||||
left: 0,
|
||||
left: left < 5 ? '100%' : 0,
|
||||
position: 'absolute',
|
||||
borderRadius: '999px',
|
||||
padding: '6px',
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export default class StackEvent {
|
|||
level: IStackEvent["level"];
|
||||
|
||||
constructor(evt: IStackEvent) {
|
||||
const event = { ...evt, source: evt.source || OPENREPLAY }
|
||||
const event = { ...evt, source: evt.source || OPENREPLAY, payload: evt.payload || {} };
|
||||
Object.assign(this, {
|
||||
...event,
|
||||
isRed: isRed(event),
|
||||
|
|
|
|||
|
|
@ -175,8 +175,14 @@ function main() {
|
|||
install_openreplay
|
||||
sudo mkdir -p /var/lib/openreplay
|
||||
sudo cp -f openreplay-cli /bin/openreplay
|
||||
sudo cp -rf ../../../openreplay /var/lib/openreplay
|
||||
sudo cp -f vars.yaml /var/lib/openreplay
|
||||
[[ ! -d /var/lib/openreplay/openreplay ]] || {
|
||||
cd /var/lib/openreplay/openreplay
|
||||
date +%m-%d-%Y-%H%M%S | sudo tee -a /var/lib/openreplay/or_versions.txt
|
||||
sudo git log -1 2>&1 | sudo tee -a /var/lib/openreplay/or_versions.txt
|
||||
sudo rm -rf /var/lib/openreplay/openreplay
|
||||
cd -
|
||||
}
|
||||
sudo cp -rf $(cd ../.. && pwd) /var/lib/openreplay/openreplay
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ service:
|
|||
metrics: 8888
|
||||
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
enabled: false
|
||||
additionalLabels:
|
||||
release: observability
|
||||
scrapeConfigs:
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ service:
|
|||
metrics: 8888
|
||||
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
enabled: false
|
||||
additionalLabels:
|
||||
release: observability
|
||||
scrapeConfigs:
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ podSecurityContext:
|
|||
# port: 9000
|
||||
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
enabled: false
|
||||
additionalLabels:
|
||||
release: observability
|
||||
scrapeConfigs:
|
||||
|
|
|
|||
|
|
@ -80,8 +80,8 @@ nameOverride: "utilities"
|
|||
fullnameOverride: "utilities-openreplay"
|
||||
|
||||
# 5 3 * * 1 “At 03:05 on Monday.”
|
||||
# refer: https://crontab.guru/#5_3_*_*_1
|
||||
cron: "5 3 * * 1"
|
||||
# refer: https://crontab.guru/#5_3_*_*_*/2
|
||||
cron: "5 3 * * */2"
|
||||
|
||||
# Pod configurations
|
||||
|
||||
|
|
|
|||
|
|
@ -115,4 +115,4 @@ Below is the list of dependencies used in OpenReplay software. Licenses may chan
|
|||
| yq | MIT | Infrastructure |
|
||||
| html2canvas | MIT | JavaScript |
|
||||
| eget | MIT | Infrastructure |
|
||||
|
||||
| @medv/finder | MIT | JavaScript |
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
## 5.0.1
|
||||
|
||||
- Default text input mode is now Obscured
|
||||
- Use `@medv/finder` instead of our own implementation of `getSelector` for better clickmaps experience
|
||||
|
||||
## 5.0.0
|
||||
|
||||
- Added "tel" to supported input types
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@openreplay/tracker",
|
||||
"description": "The OpenReplay tracker main package",
|
||||
"version": "5.0.0",
|
||||
"version": "5.0.1-beta.2",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"replay"
|
||||
|
|
@ -47,6 +47,7 @@
|
|||
"typescript": "^4.9.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@medv/finder": "^3.0.0",
|
||||
"error-stack-parser": "^2.0.6"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
{
|
||||
obscureInputNumbers: true,
|
||||
obscureInputEmails: true,
|
||||
defaultInputMode: InputMode.Plain,
|
||||
defaultInputMode: InputMode.Obscured,
|
||||
obscureInputDates: false,
|
||||
},
|
||||
opts,
|
||||
|
|
|
|||
|
|
@ -3,26 +3,17 @@ import { hasTag, isSVGElement, isDocument } from '../app/guards.js'
|
|||
import { normSpaces, hasOpenreplayAttribute, getLabelAttribute } from '../utils.js'
|
||||
import { MouseMove, MouseClick } from '../app/messages.gen.js'
|
||||
import { getInputLabel } from './input.js'
|
||||
import { finder } from '@medv/finder'
|
||||
|
||||
function _getSelector(target: Element, document: Document): string {
|
||||
let el: Element | null = target
|
||||
let selector: string | null = null
|
||||
do {
|
||||
if (el.id) {
|
||||
return `#${el.id}` + (selector ? ` > ${selector}` : '')
|
||||
}
|
||||
selector =
|
||||
el.className
|
||||
.split(' ')
|
||||
.map((cn) => cn.trim())
|
||||
.filter((cn) => cn !== '')
|
||||
.reduce((sel, cn) => `${sel}.${cn}`, el.tagName.toLowerCase()) +
|
||||
(selector ? ` > ${selector}` : '')
|
||||
if (el === document.body) {
|
||||
return selector
|
||||
}
|
||||
el = el.parentElement
|
||||
} while (el !== document.body && el !== null)
|
||||
const selector = finder(target, {
|
||||
root: document.body,
|
||||
seedMinLength: 3,
|
||||
optimizedMinLength: 2,
|
||||
threshold: 1000,
|
||||
maxNumberOfTries: 10_000,
|
||||
})
|
||||
|
||||
return selector
|
||||
}
|
||||
|
||||
|
|
@ -33,6 +24,8 @@ function isClickable(element: Element): boolean {
|
|||
tag === 'A' ||
|
||||
tag === 'LI' ||
|
||||
tag === 'SELECT' ||
|
||||
tag === 'TR' ||
|
||||
tag === 'TH' ||
|
||||
(element as HTMLElement).onclick != null ||
|
||||
element.getAttribute('role') === 'button'
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue