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' )