diff --git a/api/chalicelib/core/significance.py b/api/chalicelib/core/significance.py
index 64028a8df..fd824509d 100644
--- a/api/chalicelib/core/significance.py
+++ b/api/chalicelib/core/significance.py
@@ -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:
diff --git a/ee/api/chalicelib/core/significance.py b/ee/api/chalicelib/core/significance.py
index ae1f0c867..52650bfd7 100644
--- a/ee/api/chalicelib/core/significance.py
+++ b/ee/api/chalicelib/core/significance.py
@@ -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:
diff --git a/ee/api/chalicelib/core/significance_exp.py b/ee/api/chalicelib/core/significance_exp.py
index ae1f0c867..52650bfd7 100644
--- a/ee/api/chalicelib/core/significance_exp.py
+++ b/ee/api/chalicelib/core/significance_exp.py
@@ -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:
diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx
index e8ff709c9..c77dbcd95 100644
--- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx
+++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx
@@ -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
Loading session
}
- const searchUrl = metricStore.instance.series[0].filter.filters[0].value[0]
const jumpToEvent = metricStore.instance.data.events.find((evt: Record) => {
- if (searchUrl) return evt.path.includes(searchUrl)
+ if (mapUrl) return evt.path.includes(mapUrl)
return evt
}) || { timestamp: metricStore.instance.data.startTs }
diff --git a/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx b/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx
index 8a51717e5..29da5800c 100644
--- a/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx
+++ b/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx
@@ -16,7 +16,6 @@ function Player() {
}
}, []);
-
if (!playerContext.player) return null;
return (
diff --git a/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx b/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx
index c9629bf7a..116392534 100644
--- a/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx
+++ b/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx
@@ -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 = {}
diff --git a/frontend/app/components/shared/TrackingCodeModal/ProjectCodeSnippet/ProjectCodeSnippet.js b/frontend/app/components/shared/TrackingCodeModal/ProjectCodeSnippet/ProjectCodeSnippet.js
index b8bd87b6a..4b51fc963 100644
--- a/frontend/app/components/shared/TrackingCodeModal/ProjectCodeSnippet/ProjectCodeSnippet.js
+++ b/frontend/app/components/shared/TrackingCodeModal/ProjectCodeSnippet/ProjectCodeSnippet.js
@@ -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 = {}
diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts
index 0d9aaee3d..c54b86fb8 100644
--- a/frontend/app/player/web/MessageManager.ts
+++ b/frontend/app/player/web/MessageManager.ts
@@ -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 = []
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:
diff --git a/frontend/app/player/web/Screen/Screen.ts b/frontend/app/player/web/Screen/Screen.ts
index cca56d402..f27a251f1 100644
--- a/frontend/app/player/web/Screen/Screen.ts
+++ b/frontend/app/player/web/Screen/Screen.ts
@@ -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(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;
}
diff --git a/frontend/app/player/web/addons/TargetMarker.ts b/frontend/app/player/web/addons/TargetMarker.ts
index 6629ceaec..452ddd00f 100644
--- a/frontend/app/player/web/addons/TargetMarker.ts
+++ b/frontend/app/player/web/addons/TargetMarker.ts
@@ -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)
diff --git a/frontend/app/player/web/addons/clickmapStyles.ts b/frontend/app/player/web/addons/clickmapStyles.ts
index 0ab795ea0..f0dc65a9c 100644
--- a/frontend/app/player/web/addons/clickmapStyles.ts
+++ b/frontend/app/player/web/addons/clickmapStyles.ts
@@ -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',
diff --git a/frontend/app/types/session/stackEvent.ts b/frontend/app/types/session/stackEvent.ts
index 8ce375fc2..8bbea2778 100644
--- a/frontend/app/types/session/stackEvent.ts
+++ b/frontend/app/types/session/stackEvent.ts
@@ -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),
diff --git a/scripts/helmcharts/init.sh b/scripts/helmcharts/init.sh
index 69a6944b2..525a00a82 100644
--- a/scripts/helmcharts/init.sh
+++ b/scripts/helmcharts/init.sh
@@ -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
}
}
diff --git a/scripts/helmcharts/openreplay/charts/alerts/values.yaml b/scripts/helmcharts/openreplay/charts/alerts/values.yaml
index a54418a9f..ca76602c1 100644
--- a/scripts/helmcharts/openreplay/charts/alerts/values.yaml
+++ b/scripts/helmcharts/openreplay/charts/alerts/values.yaml
@@ -51,7 +51,7 @@ service:
metrics: 8888
serviceMonitor:
- enabled: true
+ enabled: false
additionalLabels:
release: observability
scrapeConfigs:
diff --git a/scripts/helmcharts/openreplay/charts/chalice/values.yaml b/scripts/helmcharts/openreplay/charts/chalice/values.yaml
index 3269aa503..1a1d496ed 100644
--- a/scripts/helmcharts/openreplay/charts/chalice/values.yaml
+++ b/scripts/helmcharts/openreplay/charts/chalice/values.yaml
@@ -51,7 +51,7 @@ service:
metrics: 8888
serviceMonitor:
- enabled: true
+ enabled: false
additionalLabels:
release: observability
scrapeConfigs:
diff --git a/scripts/helmcharts/openreplay/charts/peers/values.yaml b/scripts/helmcharts/openreplay/charts/peers/values.yaml
index 57fc30bde..0bc4b6b14 100644
--- a/scripts/helmcharts/openreplay/charts/peers/values.yaml
+++ b/scripts/helmcharts/openreplay/charts/peers/values.yaml
@@ -49,7 +49,7 @@ podSecurityContext:
# port: 9000
serviceMonitor:
- enabled: true
+ enabled: false
additionalLabels:
release: observability
scrapeConfigs:
diff --git a/scripts/helmcharts/openreplay/charts/utilities/values.yaml b/scripts/helmcharts/openreplay/charts/utilities/values.yaml
index 97ee29798..a8a2fddaa 100644
--- a/scripts/helmcharts/openreplay/charts/utilities/values.yaml
+++ b/scripts/helmcharts/openreplay/charts/utilities/values.yaml
@@ -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
diff --git a/third-party.md b/third-party.md
index e0b68d9f6..0cfe2cac2 100644
--- a/third-party.md
+++ b/third-party.md
@@ -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 |
diff --git a/tracker/tracker/CHANGELOG.md b/tracker/tracker/CHANGELOG.md
index 559e4e865..6a8e25690 100644
--- a/tracker/tracker/CHANGELOG.md
+++ b/tracker/tracker/CHANGELOG.md
@@ -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
diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json
index c45c15e4a..a67073cc9 100644
--- a/tracker/tracker/package.json
+++ b/tracker/tracker/package.json
@@ -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": {
diff --git a/tracker/tracker/src/main/modules/input.ts b/tracker/tracker/src/main/modules/input.ts
index 15acecaa9..e2e93bff7 100644
--- a/tracker/tracker/src/main/modules/input.ts
+++ b/tracker/tracker/src/main/modules/input.ts
@@ -89,7 +89,7 @@ export default function (app: App, opts: Partial): void {
{
obscureInputNumbers: true,
obscureInputEmails: true,
- defaultInputMode: InputMode.Plain,
+ defaultInputMode: InputMode.Obscured,
obscureInputDates: false,
},
opts,
diff --git a/tracker/tracker/src/main/modules/mouse.ts b/tracker/tracker/src/main/modules/mouse.ts
index b00d6d304..155a14a8d 100644
--- a/tracker/tracker/src/main/modules/mouse.ts
+++ b/tracker/tracker/src/main/modules/mouse.ts
@@ -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'
)