From ad28dfd6c457127284aca94ab63d4316b40a2e22 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Fri, 31 Mar 2023 16:14:24 +0200 Subject: [PATCH 01/14] refactor(mobs): remove unused generation flag --- mobs/ios_messages.rb | 16 ++++++++-------- mobs/run.rb | 5 ++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/mobs/ios_messages.rb b/mobs/ios_messages.rb index 57e737714..342137877 100644 --- a/mobs/ios_messages.rb +++ b/mobs/ios_messages.rb @@ -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' diff --git a/mobs/run.rb b/mobs/run.rb index 398068f95..a66e685a0 100644 --- a/mobs/run.rb +++ b/mobs/run.rb @@ -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 } From 422026a80721ef128e7581833019d6a93c446d8a Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Fri, 31 Mar 2023 16:30:34 +0200 Subject: [PATCH 02/14] fix(player): fix initial visual offset jump --- frontend/app/player/web/MessageManager.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index eee29593e..1eea15095 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -470,7 +470,10 @@ export default class MessageManager { default: switch (msg.tp) { case MType.CreateDocument: - if (!this.firstVisualEventSet) this.state.update({ firstVisualEvent: msg.time }); + if (!this.firstVisualEventSet) { + this.state.update({ firstVisualEvent: msg.time }); + this.firstVisualEventSet = true; + } this.windowNodeCounter.reset(); this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); break; From 629dd348702a69a2b807761e45932293cd1055a5 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 31 Mar 2023 17:26:11 +0200 Subject: [PATCH 03/14] change(ui) - search url improvements, using hook --- .../shared/SessionSearch/SessionSearch.tsx | 6 +- .../SessionSearchField/SessionSearchField.tsx | 1 - .../SessionSearchQueryParamHandler.tsx | 39 ------- .../SessionSearchQueryParamHandler/index.ts | 1 - .../app/hooks/useSessionSearchQueryHandler.ts | 36 +++++++ frontend/app/svg/ca-no-sessions-in-vault.svg | 100 +++++++----------- 6 files changed, 78 insertions(+), 105 deletions(-) delete mode 100644 frontend/app/components/shared/SessionSearchQueryParamHandler/SessionSearchQueryParamHandler.tsx delete mode 100644 frontend/app/components/shared/SessionSearchQueryParamHandler/index.ts create mode 100644 frontend/app/hooks/useSessionSearchQueryHandler.ts diff --git a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx index 84fb770a8..1d25974cf 100644 --- a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx +++ b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx @@ -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 && ( <> - {hasEvents || hasFilters ? (
diff --git a/frontend/app/components/shared/SessionSearchField/SessionSearchField.tsx b/frontend/app/components/shared/SessionSearchField/SessionSearchField.tsx index 84745c8ba..64904e358 100644 --- a/frontend/app/components/shared/SessionSearchField/SessionSearchField.tsx +++ b/frontend/app/components/shared/SessionSearchField/SessionSearchField.tsx @@ -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) { diff --git a/frontend/app/components/shared/SessionSearchQueryParamHandler/SessionSearchQueryParamHandler.tsx b/frontend/app/components/shared/SessionSearchQueryParamHandler/SessionSearchQueryParamHandler.tsx deleted file mode 100644 index e38729b2a..000000000 --- a/frontend/app/components/shared/SessionSearchQueryParamHandler/SessionSearchQueryParamHandler.tsx +++ /dev/null @@ -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); diff --git a/frontend/app/components/shared/SessionSearchQueryParamHandler/index.ts b/frontend/app/components/shared/SessionSearchQueryParamHandler/index.ts deleted file mode 100644 index c13bb493d..000000000 --- a/frontend/app/components/shared/SessionSearchQueryParamHandler/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SessionSearchQueryParamHandler'; \ No newline at end of file diff --git a/frontend/app/hooks/useSessionSearchQueryHandler.ts b/frontend/app/hooks/useSessionSearchQueryHandler.ts new file mode 100644 index 000000000..555914e49 --- /dev/null +++ b/frontend/app/hooks/useSessionSearchQueryHandler.ts @@ -0,0 +1,36 @@ +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 = () => { + console.log('called...'); + 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; diff --git a/frontend/app/svg/ca-no-sessions-in-vault.svg b/frontend/app/svg/ca-no-sessions-in-vault.svg index 69470f8f6..ef3e41f0e 100644 --- a/frontend/app/svg/ca-no-sessions-in-vault.svg +++ b/frontend/app/svg/ca-no-sessions-in-vault.svg @@ -1,72 +1,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - + + From 9f4ac12f93e2f22265fe06b4c5e05be851a7fe5d Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Fri, 31 Mar 2023 17:27:43 +0200 Subject: [PATCH 04/14] fix(player): fix initial visual offset jump check --- frontend/app/components/Session/WebPlayer.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index af0b51b41..98dfd3f72 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -31,6 +31,7 @@ function WebPlayer(props: any) { const [activeTab, setActiveTab] = useState(''); const [showNoteModal, setShowNote] = useState(false); const [noteItem, setNoteItem] = useState(undefined); + const [visuallyAdjusted, setAdjusted] = useState(false); // @ts-ignore const [contextValue, setContextValue] = useState(defaultContextValue); @@ -84,8 +85,9 @@ function WebPlayer(props: any) { if (activeTab === '' && !showNoteModal && isPlayerReady && contextValue.player) { contextValue.player.play() - if (visualOffset !== 0) { + if (visualOffset !== 0 && !visuallyAdjusted) { contextValue.player.jump(visualOffset) + setAdjusted(true) } } }, [activeTab, isPlayerReady, showNoteModal, visualOffset]) From 80f40ceda5357060c7d76273631e11be7e9bf431 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 31 Mar 2023 17:29:33 +0200 Subject: [PATCH 05/14] fix(ui) - dashboard filter message --- .../components/DashboardList/DashboardList.tsx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index a4551f854..efc275e61 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -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={
-
- {dashboardsSearch !== '' ? ( - 'No matching results' - ) : ( -
-
You haven't created any dashboards yet
-
- A Dashboard is a collection of Cards that can be shared across teams. -
-
- )} +
+ {dashboardsSearch !== '' ? 'No matching results' : "You haven't created any dashboards yet"} +
+
+ A Dashboard is a collection of Cards that can be shared across teams.
} From 5afa5d4f7cfc97fcc3bbb9257f7bd6b3b920de77 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 31 Mar 2023 17:31:40 +0200 Subject: [PATCH 06/14] change(ui) - removed log --- frontend/app/hooks/useSessionSearchQueryHandler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/app/hooks/useSessionSearchQueryHandler.ts b/frontend/app/hooks/useSessionSearchQueryHandler.ts index 555914e49..40b1e80cb 100644 --- a/frontend/app/hooks/useSessionSearchQueryHandler.ts +++ b/frontend/app/hooks/useSessionSearchQueryHandler.ts @@ -13,7 +13,6 @@ const useSessionSearchQueryHandler = (props: Props) => { useEffect(() => { const applyFilterFromQuery = () => { - console.log('called...'); const filter = getFiltersFromQuery(history.location.search, appliedFilter); applyFilter(filter, true, false); }; From 7887e0cafa2831f68335de13a77833032a09290d Mon Sep 17 00:00:00 2001 From: Rajesh Rajendran Date: Fri, 31 Mar 2023 17:41:10 +0200 Subject: [PATCH 07/14] cli improvements (#1089) * fix(init): Check directory recursion Signed-off-by: rjshrjndrn * chore(cli): Reload if only change detected Signed-off-by: rjshrjndrn --------- Signed-off-by: rjshrjndrn --- scripts/helmcharts/init.sh | 5 +++-- scripts/helmcharts/openreplay-cli | 12 +++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/scripts/helmcharts/init.sh b/scripts/helmcharts/init.sh index 5652240de..70843d15b 100644 --- a/scripts/helmcharts/init.sh +++ b/scripts/helmcharts/init.sh @@ -175,13 +175,14 @@ function main() { sudo mkdir -p /var/lib/openreplay sudo cp -f openreplay-cli /bin/openreplay install_openreplay - [[ ! -d /var/lib/openreplay/openreplay ]] || { + # If you install multiple times using init.sh, Only keep the latest installation + if [[ ! -d /var/lib/openreplay/openreplay ]] && [[ $(pwd) != "/var/lib/openreplay/openreplay/scripts/helmcharts/" ]] ; then 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 - - } + fi sudo cp -rf $(cd ../.. && pwd) /var/lib/openreplay/openreplay sudo cp -rf ./vars.yaml /var/lib/openreplay/ } diff --git a/scripts/helmcharts/openreplay-cli b/scripts/helmcharts/openreplay-cli index d518cc186..fc1f86365 100755 --- a/scripts/helmcharts/openreplay-cli +++ b/scripts/helmcharts/openreplay-cli @@ -377,12 +377,18 @@ do Or ${BWHITE}helm upgrade openreplay -n app openreplay/scripts/helmcharts/openreplay -f openreplay/scripts/helmcharts/vars.yaml --debug --atomic" exit 100 } + /var/lib/openreplay/busybox md5sum /var/lib/openreplay/vars.yaml > "${tmp_dir}/var.yaml.md5" sudo vim -n ${OR_DIR}/vars.yaml - /var/lib/openreplay/yq 'true' /var/lib/openreplay/vars.yaml || { - log debug "seems like the edit is not correct. Rerun ${BWHITE}openreplay -e${YELLOW} after fixing the issue." + /var/lib/openreplay/yq 'true' /var/lib/openreplay/vars.yaml &> /dev/null || { + log debug "seems like the edit is not correct. Rerun ${BWHITE}openreplay -e${YELLOW} and fix the issue in config file." + clean_tmp_dir exit 100 } - reload + if /var/lib/openreplay/busybox md5sum -c "${tmp_dir}/var.yaml.md5"; then + log info "No change detected in ${BWHITE}${OR_DIR}/vars.yaml${GREEN}. Not reloading" + else + reload + fi clean_tmp_dir exit 0 ;; From f65487ca17a489fd88ecabe5c05bad0e46e4c8cd Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 31 Mar 2023 17:50:29 +0200 Subject: [PATCH 08/14] change(ui) - preferences width and alignment --- frontend/app/components/Client/Client.js | 46 +++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/frontend/app/components/Client/Client.js b/frontend/app/components/Client/Client.js index ab16c6b40..6cba4b50a 100644 --- a/frontend/app/components/Client/Client.js +++ b/frontend/app/components/Client/Client.js @@ -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 = () => ( - - - - - - - - - - + + + + + + + + + + - ) + ); render() { - const { match: { params: { activeTab } } } = this.props; + const { + match: { + params: { activeTab }, + }, + } = this.props; return ( -
-
-
+
+
-
- { activeTab && this.renderActiveTab() } +
+
+ {activeTab && this.renderActiveTab()} +
-
); } From e2c4e048ea8540ec19922a649626608268d6dff4 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Sat, 1 Apr 2023 00:43:35 +0200 Subject: [PATCH 09/14] fix(player): fix and centralize CSS pseudoclasses rewriting --- .../app/player/web/managers/DOM/DOMManager.ts | 7 +--- .../player/web/managers/DOM/FocusManager.ts | 7 ++-- .../player/web/managers/DOM/StylesManager.ts | 14 +++---- .../player/web/managers/MouseMoveManager.ts | 14 ++----- .../web/messages/JSONRawMessageReader.ts | 4 +- .../app/player/web/messages/MFileReader.ts | 4 +- .../player/web/messages/rewriter/constants.ts | 2 + .../rewriteMessage.ts} | 41 +++++++++++-------- .../web/messages/{ => rewriter}/urlResolve.ts | 0 9 files changed, 44 insertions(+), 49 deletions(-) create mode 100644 frontend/app/player/web/messages/rewriter/constants.ts rename frontend/app/player/web/messages/{urlBasedResolver.ts => rewriter/rewriteMessage.ts} (55%) rename frontend/app/player/web/messages/{ => rewriter}/urlResolve.ts (100%) diff --git a/frontend/app/player/web/managers/DOM/DOMManager.ts b/frontend/app/player/web/managers/DOM/DOMManager.ts index 5ae59c5c4..8e0fb8b6b 100644 --- a/frontend/app/player/web/managers/DOM/DOMManager.ts +++ b/frontend/app/player/web/managers/DOM/DOMManager.ts @@ -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 { 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() } diff --git a/frontend/app/player/web/managers/DOM/FocusManager.ts b/frontend/app/player/web/managers/DOM/FocusManager.ts index c75f3ddc3..5a808766c 100644 --- a/frontend/app/player/web/managers/DOM/FocusManager.ts +++ b/frontend/app/player/web/managers/DOM/FocusManager.ts @@ -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 { constructor(private readonly vElements: Map) {super()} @@ -11,7 +10,7 @@ export default class FocusManager extends ListWalker { 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 { 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) } } \ No newline at end of file diff --git a/frontend/app/player/web/managers/DOM/StylesManager.ts b/frontend/app/player/web/managers/DOM/StylesManager.ts index 295b95d2f..92f51aed9 100644 --- a/frontend/app/player/web/managers/DOM/StylesManager.ts +++ b/frontend/app/player/web/managers/DOM/StylesManager.ts @@ -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); diff --git a/frontend/app/player/web/managers/MouseMoveManager.ts b/frontend/app/player/web/managers/MouseMoveManager.ts index fa320ab13..2cb508f59 100644 --- a/frontend/app/player/web/managers/MouseMoveManager.ts +++ b/frontend/app/player/web/managers/MouseMoveManager.ts @@ -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 { private hoverElements: Array = [] 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 { 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) }) } diff --git a/frontend/app/player/web/messages/JSONRawMessageReader.ts b/frontend/app/player/web/messages/JSONRawMessageReader.ts index ca193c326..c2499f4c3 100644 --- a/frontend/app/player/web/messages/JSONRawMessageReader.ts +++ b/frontend/app/player/web/messages/JSONRawMessageReader.ts @@ -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) } } diff --git a/frontend/app/player/web/messages/MFileReader.ts b/frontend/app/player/web/messages/MFileReader.ts index b5fdde85c..a7411b155 100644 --- a/frontend/app/player/web/messages/MFileReader.ts +++ b/frontend/app/player/web/messages/MFileReader.ts @@ -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, }) diff --git a/frontend/app/player/web/messages/rewriter/constants.ts b/frontend/app/player/web/messages/rewriter/constants.ts new file mode 100644 index 000000000..abd05897a --- /dev/null +++ b/frontend/app/player/web/messages/rewriter/constants.ts @@ -0,0 +1,2 @@ +export const HOVER_CLASSNAME = "-openreplay-hover" +export const FOCUS_CLASSNAME = "-openreplay-focus" diff --git a/frontend/app/player/web/messages/urlBasedResolver.ts b/frontend/app/player/web/messages/rewriter/rewriteMessage.ts similarity index 55% rename from frontend/app/player/web/messages/urlBasedResolver.ts rename to frontend/app/player/web/messages/rewriter/rewriteMessage.ts index 53bf1ed81..d488c8362 100644 --- a/frontend/app/player/web/messages/urlBasedResolver.ts +++ b/frontend/app/player/web/messages/rewriter/rewriteMessage.ts @@ -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 = Extract; -// type ResolversMap = { -// [Key in MType]: (event: PickMessage) => 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 } \ No newline at end of file diff --git a/frontend/app/player/web/messages/urlResolve.ts b/frontend/app/player/web/messages/rewriter/urlResolve.ts similarity index 100% rename from frontend/app/player/web/messages/urlResolve.ts rename to frontend/app/player/web/messages/rewriter/urlResolve.ts From 07fc2a12126acc65eade11c0a609553ea70f60af Mon Sep 17 00:00:00 2001 From: Rajesh Rajendran Date: Sat, 1 Apr 2023 10:19:07 +0200 Subject: [PATCH 10/14] fix(init): check for non existent directory (#1092) Signed-off-by: rjshrjndrn --- scripts/helmcharts/init.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/helmcharts/init.sh b/scripts/helmcharts/init.sh index 70843d15b..3baa699b0 100644 --- a/scripts/helmcharts/init.sh +++ b/scripts/helmcharts/init.sh @@ -176,7 +176,7 @@ function main() { sudo cp -f openreplay-cli /bin/openreplay install_openreplay # If you install multiple times using init.sh, Only keep the latest installation - if [[ ! -d /var/lib/openreplay/openreplay ]] && [[ $(pwd) != "/var/lib/openreplay/openreplay/scripts/helmcharts/" ]] ; then + if [[ -d /var/lib/openreplay/openreplay ]]; then 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 From e9b119d34abcff37351605c62630dc8666237235 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 3 Apr 2023 10:28:55 +0200 Subject: [PATCH 11/14] change(ui) - bookmark and vault graphics --- frontend/app/svg/ca-no-bookmarked-session.svg | 73 ++++++++----- frontend/app/svg/ca-no-sessions-in-vault.svg | 100 +++++++++++------- 2 files changed, 109 insertions(+), 64 deletions(-) diff --git a/frontend/app/svg/ca-no-bookmarked-session.svg b/frontend/app/svg/ca-no-bookmarked-session.svg index fc69149aa..ef3e41f0e 100644 --- a/frontend/app/svg/ca-no-bookmarked-session.svg +++ b/frontend/app/svg/ca-no-bookmarked-session.svg @@ -1,27 +1,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/app/svg/ca-no-sessions-in-vault.svg b/frontend/app/svg/ca-no-sessions-in-vault.svg index ef3e41f0e..69470f8f6 100644 --- a/frontend/app/svg/ca-no-sessions-in-vault.svg +++ b/frontend/app/svg/ca-no-sessions-in-vault.svg @@ -1,48 +1,72 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + + + + + + + + + + + + + + + + From e82271a4db1600508dd077bd61afb00179dce573 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 3 Apr 2023 10:31:58 +0200 Subject: [PATCH 12/14] change(ui) - errors show message --- .../shared/DevTools/ConsoleRow/ConsoleRow.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx index fc818b550..e035e92f6 100644 --- a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx +++ b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import cn from 'classnames'; -import { Icon } from 'UI'; +import { Icon, TextEllipsis } from 'UI'; import JumpButton from 'Shared/DevTools/JumpButton'; interface Props { @@ -17,7 +17,6 @@ function ConsoleRow(props: Props) { const [expanded, setExpanded] = useState(false); const lines = log.value?.split('\n').filter((l: any) => !!l) || []; const canExpand = lines.length > 1; - const clickable = canExpand || !!log.errorId; const toggleExpand = () => { @@ -34,7 +33,6 @@ function ConsoleRow(props: Props) { warn: log.isYellow, error: log.isRed, 'cursor-pointer': clickable, - 'cursor-pointer underline decoration-dotted decoration-gray-200': !!log.errorId, } )} onClick={clickable ? () => (!!log.errorId ? props.onClick() : toggleExpand()) : undefined} @@ -43,11 +41,14 @@ function ConsoleRow(props: Props) {
-
- {canExpand && ( - - )} - {renderWithNL(lines.pop())} +
+
+ {canExpand && ( + + )} + {renderWithNL(lines.pop())} +
+ {log.errorId && }
{canExpand && expanded && From 77ea6e196040454d751863bbc33d895bf7d79b56 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 4 Apr 2023 12:50:35 +0100 Subject: [PATCH 13/14] feat(chalice): role check on member update feat(chalice): removed member's email update --- api/chalicelib/core/users.py | 61 +++++++++++++++++++++++------------- api/schemas.py | 3 +- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/api/chalicelib/core/users.py b/api/chalicelib/core/users.py index c4933f92c..f87273a0e 100644 --- a/api/chalicelib/core/users.py +++ b/api/chalicelib/core/users.py @@ -115,7 +115,7 @@ def reset_member(tenant_id, editor_id, user_id_to_update): return {"data": {"invitationLink": generate_new_invitation(user_id_to_update)}} -def update(tenant_id, user_id, changes): +def update(tenant_id, user_id, changes, output=True): AUTH_KEYS = ["password", "invitationToken", "invitedAt", "changePwdExpireAt", "changePwdToken"] if len(changes.keys()) == 0: return None @@ -167,7 +167,8 @@ def update(tenant_id, user_id, changes): (CASE WHEN users.role = 'member' THEN TRUE ELSE FALSE END) AS member;""", {"user_id": user_id, **changes}) ) - + if not output: + return None return get(user_id=user_id, tenant_id=tenant_id) @@ -291,35 +292,29 @@ def edit(user_id_to_update, tenant_id, changes: schemas.EditUserSchema, editor_i return {"data": user} -def edit_member(user_id_to_update, tenant_id, changes: schemas.EditUserSchema, editor_id): +def edit_member(user_id_to_update, tenant_id, changes: schemas.EditMemberSchema, editor_id): user = get_member(user_id=user_id_to_update, tenant_id=tenant_id) - if editor_id != user_id_to_update or changes.admin is not None and changes.admin != user["admin"]: - admin = get(tenant_id=tenant_id, user_id=editor_id) + _changes = {} + if editor_id != user_id_to_update: + admin = get_user_role(tenant_id=tenant_id, user_id=editor_id) if not admin["superAdmin"] and not admin["admin"]: return {"errors": ["unauthorized"]} - _changes = {} - if editor_id == user_id_to_update: - if changes.admin is not None: - if user["superAdmin"]: - changes.admin = None - elif changes.admin != user["admin"]: - return {"errors": ["cannot change your own role"]} + if admin["admin"] and user["superAdmin"]: + return {"errors": ["only a superAdmin can edit his own details"]} + else: + if user["superAdmin"]: + changes.admin = None + elif changes.admin != user["admin"]: + return {"errors": ["cannot change your own admin privileges"]} - if changes.email is not None and changes.email != user["email"]: - if email_exists(changes.email): - return {"errors": ["email already exists."]} - if get_deleted_user_by_email(changes.email) is not None: - return {"errors": ["email previously deleted."]} - _changes["email"] = changes.email - - if changes.name is not None and len(changes.name) > 0: + if changes.name and len(changes.name) > 0: _changes["name"] = changes.name if changes.admin is not None: _changes["role"] = "admin" if changes.admin else "member" if len(_changes.keys()) > 0: - update(tenant_id=tenant_id, user_id=user_id_to_update, changes=_changes) + update(tenant_id=tenant_id, user_id=user_id_to_update, changes=_changes, output=False) return {"data": get_member(user_id=user_id_to_update, tenant_id=tenant_id)} return {"data": user} @@ -630,3 +625,27 @@ def authenticate(email, password, for_change_password=False): **r } return None + + +def get_user_role(tenant_id, user_id): + with pg_client.PostgresClient() as cur: + cur.execute( + cur.mogrify( + f"""SELECT + users.user_id, + users.email, + users.role, + users.name, + users.created_at, + (CASE WHEN users.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, + (CASE WHEN users.role = 'admin' THEN TRUE ELSE FALSE END) AS admin, + (CASE WHEN users.role = 'member' THEN TRUE ELSE FALSE END) AS member + FROM public.users + WHERE users.deleted_at IS NULL + AND users.user_id=%(user_id)s + LIMIT 1""", + {"user_id": user_id}) + ) + u = helper.dict_to_camel_case(cur.fetchone()) + + return u diff --git a/api/schemas.py b/api/schemas.py index 992729870..a7f1eb683 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -42,6 +42,7 @@ class EditUserSchema(BaseModel): email: Optional[EmailStr] = Field(None) admin: Optional[bool] = Field(None) + _transform_name = validator('name', pre=True, allow_reuse=True)(remove_whitespace) _transform_email = validator('email', pre=True, allow_reuse=True)(transform_email) @@ -152,8 +153,6 @@ class EditMemberSchema(EditUserSchema): name: str = Field(...) email: EmailStr = Field(...) admin: bool = Field(False) - _transform_name = validator('name', pre=True, allow_reuse=True)(remove_whitespace) - _transform_email = validator('email', pre=True, allow_reuse=True)(transform_email) class EditPasswordByInvitationSchema(BaseModel): From b10390c5097e0269eb4e04b3b3e47cfdf22fee29 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 4 Apr 2023 13:44:05 +0100 Subject: [PATCH 14/14] feat(chalice): EE role check on member update feat(chalice): EE removed member's email update --- api/chalicelib/core/users.py | 6 +-- ee/api/chalicelib/core/users.py | 70 +++++++++++++++++++++------------ 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/api/chalicelib/core/users.py b/api/chalicelib/core/users.py index f87273a0e..766a562be 100644 --- a/api/chalicelib/core/users.py +++ b/api/chalicelib/core/users.py @@ -300,7 +300,7 @@ def edit_member(user_id_to_update, tenant_id, changes: schemas.EditMemberSchema, if not admin["superAdmin"] and not admin["admin"]: return {"errors": ["unauthorized"]} if admin["admin"] and user["superAdmin"]: - return {"errors": ["only a superAdmin can edit his own details"]} + return {"errors": ["only the owner can edit his own details"]} else: if user["superAdmin"]: changes.admin = None @@ -646,6 +646,4 @@ def get_user_role(tenant_id, user_id): LIMIT 1""", {"user_id": user_id}) ) - u = helper.dict_to_camel_case(cur.fetchone()) - - return u + return helper.dict_to_camel_case(cur.fetchone()) diff --git a/ee/api/chalicelib/core/users.py b/ee/api/chalicelib/core/users.py index ff357113f..c1a0b7534 100644 --- a/ee/api/chalicelib/core/users.py +++ b/ee/api/chalicelib/core/users.py @@ -128,7 +128,7 @@ def reset_member(tenant_id, editor_id, user_id_to_update): return {"data": {"invitationLink": generate_new_invitation(user_id_to_update)}} -def update(tenant_id, user_id, changes): +def update(tenant_id, user_id, changes, output=True): AUTH_KEYS = ["password", "invitationToken", "invitedAt", "changePwdExpireAt", "changePwdToken"] if len(changes.keys()) == 0: return None @@ -197,7 +197,8 @@ def update(tenant_id, user_id, changes): AND roles.role_id=users.role_id) AS role_name;""", {"tenant_id": tenant_id, "user_id": user_id, **changes}) ) - + if not output: + return None return get(user_id=user_id, tenant_id=tenant_id) @@ -344,33 +345,29 @@ def edit(user_id_to_update, tenant_id, changes: schemas_ee.EditUserSchema, edito return {"data": user} -def edit_member(user_id_to_update, tenant_id, changes: schemas_ee.EditUserSchema, editor_id): +def edit_member(user_id_to_update, tenant_id, changes: schemas_ee.EditMemberSchema, editor_id): user = get_member(user_id=user_id_to_update, tenant_id=tenant_id) - if editor_id != user_id_to_update or changes.admin is not None and changes.admin != user["admin"]: - admin = get(tenant_id=tenant_id, user_id=editor_id) + _changes = {} + if editor_id != user_id_to_update: + admin = get_user_role(tenant_id=tenant_id, user_id=editor_id) if not admin["superAdmin"] and not admin["admin"]: return {"errors": ["unauthorized"]} - _changes = {} - if editor_id == user_id_to_update: - if changes.admin is not None: - if user["superAdmin"]: - changes.admin = None - elif changes.admin != user["admin"]: - return {"errors": ["cannot change your own role"]} - if changes.roleId is not None: - if user["superAdmin"]: + if admin["admin"] and user["superAdmin"]: + return {"errors": ["only the owner can edit his own details"]} + else: + if user["superAdmin"]: + changes.admin = None + elif changes.admin != user["admin"]: + return {"errors": ["cannot change your own admin privileges"]} + if changes.roleId: + if user["superAdmin"] and changes.roleId != user["roleId"]: changes.roleId = None - elif changes.roleId != user["roleId"]: + return {"errors": ["owner's role cannot be changed"]} + + if changes.roleId != user["roleId"]: return {"errors": ["cannot change your own role"]} - if changes.email is not None and changes.email != user["email"]: - if email_exists(changes.email): - return {"errors": ["email already exists."]} - if get_deleted_user_by_email(changes.email) is not None: - return {"errors": ["email previously deleted."]} - _changes["email"] = changes.email - - if changes.name is not None and len(changes.name) > 0: + if changes.name and len(changes.name) > 0: _changes["name"] = changes.name if changes.admin is not None: @@ -380,8 +377,8 @@ def edit_member(user_id_to_update, tenant_id, changes: schemas_ee.EditUserSchema _changes["roleId"] = changes.roleId if len(_changes.keys()) > 0: - update(tenant_id=tenant_id, user_id=user_id_to_update, changes=_changes) - return {"data": get_member(tenant_id=tenant_id, user_id=user_id_to_update)} + update(tenant_id=tenant_id, user_id=user_id_to_update, changes=_changes, output=False) + return {"data": get_member(user_id=user_id_to_update, tenant_id=tenant_id)} return {"data": user} @@ -853,3 +850,26 @@ def __hard_delete_user(user_id): WHERE users.user_id = %(user_id)s AND users.deleted_at IS NOT NULL ;""", {"user_id": user_id}) cur.execute(query) + + +def get_user_role(tenant_id, user_id): + with pg_client.PostgresClient() as cur: + cur.execute( + cur.mogrify( + f"""SELECT + users.user_id, + users.email, + users.role, + users.name, + users.created_at, + (CASE WHEN users.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, + (CASE WHEN users.role = 'admin' THEN TRUE ELSE FALSE END) AS admin, + (CASE WHEN users.role = 'member' THEN TRUE ELSE FALSE END) AS member + FROM public.users + WHERE users.deleted_at IS NULL + AND users.user_id=%(user_id)s + AND users.tenant_id=%(tenant_id)s + LIMIT 1""", + {"tenant_id": tenant_id, "user_id": user_id}) + ) + return helper.dict_to_camel_case(cur.fetchone())