From b653da6eafebef4aa18b85cde9e0320826807f58 Mon Sep 17 00:00:00 2001 From: Zalenski Egor <63463140+zalenskiSofteq@users.noreply.github.com> Date: Tue, 23 Nov 2021 18:25:11 +0300 Subject: [PATCH 1/2] #RI-2075 Telemetry for Workbench page --- .../src/components/query-card/QueryCard.tsx | 43 +++++++-- .../QueryCardHeader/QueryCardHeader.tsx | 21 +++++ .../ui/src/components/query/Query/Query.tsx | 41 ++++++++- redisinsight/ui/src/constants/index.ts | 1 - .../ui/src/constants/workbenchPreselects.ts | 90 ------------------- .../ui/src/pages/workbench/WorkbenchPage.tsx | 28 +++++- .../EnablementArea/EnablementArea.tsx | 8 +- .../LazyCodeButton/LazyCodeButton.tsx | 4 +- .../enablament-area/EnablementAreaWrapper.tsx | 34 ++++++- .../components/wb-view/WBViewWrapper.tsx | 8 +- .../contexts/enablementAreaContext.tsx | 4 +- redisinsight/ui/src/telemetry/events.ts | 12 +++ redisinsight/ui/src/telemetry/pageViews.ts | 1 + redisinsight/ui/src/utils/monaco.ts | 6 +- 14 files changed, 187 insertions(+), 114 deletions(-) delete mode 100644 redisinsight/ui/src/constants/workbenchPreselects.ts diff --git a/redisinsight/ui/src/components/query-card/QueryCard.tsx b/redisinsight/ui/src/components/query-card/QueryCard.tsx index 45c4f9ce58..e1a4738b07 100644 --- a/redisinsight/ui/src/components/query-card/QueryCard.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCard.tsx @@ -2,12 +2,15 @@ import React, { useEffect, useState } from 'react' import { useSelector } from 'react-redux' import cx from 'classnames' import { EuiLoadingContent, keys } from '@elastic/eui' +import { useParams } from 'react-router-dom' + import { WBQueryType } from 'uiSrc/pages/workbench/constants' import { getWBQueryType, Nullable, getVisualizationsByCommand, Maybe } from 'uiSrc/utils' - import { appPluginsSelector } from 'uiSrc/slices/app/plugins' import { IPluginVisualization } from 'uiSrc/slices/interfaces' import { CommandExecutionStatus } from 'uiSrc/slices/interfaces/cli' +import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { appRedisCommandsSelector } from 'uiSrc/slices/app/redis-commands' import QueryCardHeader from './QueryCardHeader' import QueryCardCliResult from './QueryCardCliResult' @@ -34,10 +37,9 @@ const getDefaultPlugin = (views: IPluginVisualization[], query: string) => getVisualizationsByCommand(query, views).find((view) => view.default)?.uniqId || '' const QueryCard = (props: Props) => { - const { visualizations = [] } = useSelector(appPluginsSelector) const { id, - query, + query = '', data, status, fromStore, @@ -48,15 +50,19 @@ const QueryCard = (props: Props) => { loading } = props + const { visualizations = [] } = useSelector(appPluginsSelector) + const { commandsArray: REDIS_COMMANDS_ARRAY } = useSelector(appRedisCommandsSelector) + + const { instanceId = '' } = useParams<{ instanceId: string }>() const [isOpen, setIsOpen] = useState(!fromStore) const [isFullScreen, setIsFullScreen] = useState(false) const [result, setResult] = useState>(data) const [queryType, setQueryType] = useState(getWBQueryType(query, visualizations)) const [viewTypeSelected, setViewTypeSelected] = useState(queryType) + const [summaryText, setSummaryText] = useState('') const [selectedViewValue, setSelectedViewValue] = useState( getDefaultPlugin(visualizations, query) || queryType ) - const [summaryText, setSummaryText] = useState('') useEffect(() => { window.addEventListener('keydown', handleEscFullScreen) @@ -72,7 +78,17 @@ const QueryCard = (props: Props) => { } const toggleFullScreen = () => { - setIsFullScreen((value) => !value) + setIsFullScreen((isFull) => { + sendEventTelemetry({ + event: TelemetryEvent.WORKBENCH_RESULTS_IN_FULL_SCREEN, + eventData: { + databaseId: instanceId, + state: isFull ? 'Close' : 'Open' + } + }) + + return !isFull + }) } useEffect(() => { @@ -94,8 +110,25 @@ const QueryCard = (props: Props) => { } }, [data, time]) + const sendEventToggleOpenTelemetry = () => { + const matchedCommand = REDIS_COMMANDS_ARRAY.find((commandName) => + query.toUpperCase().startsWith(commandName)) + + sendEventTelemetry({ + event: isOpen + ? TelemetryEvent.WORKBENCH_RESULTS_COLLAPSED + : TelemetryEvent.WORKBENCH_RESULTS_EXPANDED, + eventData: { + databaseId: instanceId, + command: matchedCommand ?? query.split(' ')?.[0] + } + }) + } + const toggleOpen = () => { if (isFullScreen) return + + sendEventToggleOpenTelemetry() setIsOpen(!isOpen) if (!isOpen && !data) { diff --git a/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx b/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx index 488889c3c3..89ebc41045 100644 --- a/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx @@ -12,11 +12,13 @@ import { EuiToolTip, } from '@elastic/eui' import { format } from 'date-fns' +import { useParams } from 'react-router-dom' import { Theme } from 'uiSrc/constants' import { getVisualizationsByCommand, truncateText, urlForAsset } from 'uiSrc/utils' import { ThemeContext } from 'uiSrc/contexts/themeContext' import { appPluginsSelector } from 'uiSrc/slices/app/plugins' +import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { getViewTypeOptions, WBQueryType } from 'uiSrc/pages/workbench/constants' import DefaultPluginIconDark from 'uiSrc/assets/img/workbench/default_view_dark.svg' @@ -57,6 +59,7 @@ const QueryCardHeader = (props: Props) => { } = props const { visualizations = [] } = useSelector(appPluginsSelector) + const { instanceId = '' } = useParams<{ instanceId: string }>() const { theme } = useContext(ThemeContext) @@ -66,6 +69,12 @@ const QueryCardHeader = (props: Props) => { } const handleCopy = (event: React.MouseEvent, text: string) => { + sendEventTelemetry({ + event: TelemetryEvent.WORKBENCH_COMMAND_COPIED, + eventData: { + databaseId: instanceId + } + }) eventStop(event) navigator.clipboard.writeText(text) } @@ -81,11 +90,23 @@ const QueryCardHeader = (props: Props) => { } const handleQueryDelete = (event: React.MouseEvent) => { + sendEventTelemetry({ + event: TelemetryEvent.WORKBENCH_COMMAND_DELETE_COMMAND, + eventData: { + databaseId: instanceId + } + }) eventStop(event) onQueryDelete() } const handleQueryReRun = (event: React.MouseEvent) => { + sendEventTelemetry({ + event: TelemetryEvent.WORKBENCH_COMMAND_RUN_AGAIN, + eventData: { + databaseId: instanceId + } + }) eventStop(event) onQueryReRun() } diff --git a/redisinsight/ui/src/components/query/Query/Query.tsx b/redisinsight/ui/src/components/query/Query/Query.tsx index ba8d9dfbcf..2c638f2c6e 100644 --- a/redisinsight/ui/src/components/query/Query/Query.tsx +++ b/redisinsight/ui/src/components/query/Query/Query.tsx @@ -1,10 +1,12 @@ import React, { useContext, useEffect } from 'react' import { useSelector } from 'react-redux' import { findIndex } from 'lodash' +import { decode } from 'html-entities' import cx from 'classnames' import { EuiButtonIcon, EuiText, EuiToolTip } from '@elastic/eui' import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api' import MonacoEditor from 'react-monaco-editor' +import { useParams } from 'react-router-dom' import { Theme, @@ -12,11 +14,18 @@ import { redisLanguageConfig, KEYBOARD_SHORTCUTS, } from 'uiSrc/constants' +import { + getMultiCommands, + getRedisCompletionProvider, + getRedisMonarchTokensProvider, + removeMonacoComments, + splitMonacoValuePerLines +} from 'uiSrc/utils' import { ThemeContext } from 'uiSrc/contexts/themeContext' -import { getRedisCompletionProvider, getRedisMonarchTokensProvider } from 'uiSrc/utils' import { WBQueryType } from 'uiSrc/pages/workbench/constants' import { KeyboardShortcut } from 'uiSrc/components' import { appRedisCommandsSelector } from 'uiSrc/slices/app/redis-commands' +import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import styles from './styles.module.scss' @@ -31,6 +40,8 @@ export interface Props { const Query = (props: Props) => { const { query = '', setQuery, onKeyDown, onSubmit, setQueryEl } = props + const { instanceId = '' } = useParams<{ instanceId: string }>() + const { commandsArray: REDIS_COMMANDS_ARRAY, spec: REDIS_COMMANDS_SPEC @@ -54,7 +65,35 @@ const Query = (props: Props) => { onKeyDown?.(e, query) } + const sendEventSubmitTelemetry = (commandInit = query) => { + const eventData = (() => { + const commands = splitMonacoValuePerLines(commandInit) + + const [commandLine, ...rest] = commands.map((command = '') => { + const matchedCommand = REDIS_COMMANDS_ARRAY.find((commandName) => + command.toUpperCase().startsWith(commandName)) + return matchedCommand ?? command.split(' ')?.[0] + }) + const multiCommands = getMultiCommands(rest) + + const command = removeMonacoComments(decode([commandLine, multiCommands].join('\n')).trim()) + + return { + command, + databaseId: instanceId, + multiple: multiCommands ? 'Multiple' : 'Single' + } + })() + + sendEventTelemetry({ + event: TelemetryEvent.WORKBENCH_COMMAND_SUBMITTED, + eventData + }) + } + const handleSubmit = (value?: string) => { + sendEventSubmitTelemetry(value) + onSubmit(value) } diff --git a/redisinsight/ui/src/constants/index.ts b/redisinsight/ui/src/constants/index.ts index 8a6709f55d..e1f4a41100 100644 --- a/redisinsight/ui/src/constants/index.ts +++ b/redisinsight/ui/src/constants/index.ts @@ -8,7 +8,6 @@ export * from './keys' export * from './table' export * from './redisinsight' export * from './commands' -export * from './workbenchPreselects' export * from './monaco' export * from './monacoRedis' export * from './keyboardShortcuts' diff --git a/redisinsight/ui/src/constants/workbenchPreselects.ts b/redisinsight/ui/src/constants/workbenchPreselects.ts deleted file mode 100644 index b0ca4344d1..0000000000 --- a/redisinsight/ui/src/constants/workbenchPreselects.ts +++ /dev/null @@ -1,90 +0,0 @@ -export interface WorkbenchPreselects { - script: string; - title: string; -} - -export const workbenchPreselects = [ - { - title: 'Manual', - script: [ - '// Workbench is the advanced Redis command-line interface that allows to send commands to Redis, read and visualize the replies sent by the server.', - '// Enter multiple commands at different rows to run them at once.', - '// Start a new line with an indent (Tab) to specify arguments for any Redis command in multiple line mode.', - '// Use F1 to see the full list of shortcuts available in Workbench.', - '// Use Ctrl+Space (Cmd+Space) to see the list of commands and information about commands and their arguments in the suggestion list.', - '// Use Ctrl+Shift+Space (Cmd+Shift+Space) to see the list of arguments for commands.' - ].join('\n') - }, - { - title: 'List the Indices', - script: 'FT._LIST', - }, - { - title: 'Index info', - script: 'FT.INFO {index}', - }, - { - title: 'Search', - script: [ - '// Search the index with a textual query', - '// Replace:', - '// {index} with name of index', - '// {"query"} with the text query to search`', - '', - 'FT.SEARCH {index} {“query”}', - ' ', - ' // Optional:', - ' // NOCONTENT // - Return the document ids and not the content', - ' // VERBATIM // - Do not try to use stemming for query expansion but search the query terms verbatim', - ' // NOSTOPWORDS // - Do not filter stopwords from the query', - ' // WITHSCORES // - Return the relative internal score of each document', - ' // WITHPAYLOADS // - Retrieve optional document payloads', - ' // WITHSORTKEYS // - Returns the value of the sorting key', - ' // FILTER {numeric_field} {min} {max} // - Limit results to those having numeric values ranging between min and max. Replace {numeric_field} with the field, {min} with minimum value and {max} with maximum value', - ' // GEOFILTER {geo_field} {lon} {lat} {radius} m|km|mi|ft // - Filter the results to a given radius from lon and lat. Replace {geo_field} with the field, {lon} with the longitude, {lat} with the latitude, {radius} with the radius, select a unit – m, km, mi or ft', - ' // INKEYS {num} {key} // - Limit the result to a given set of keys specified in the list. Replace {num} with the number of keys, {key} with the list of keys', - ' // INFIELDS {num} {field} // - Filter the results to ones appearing only in specific fields of the document. Replace {num} with the number of keys, {field} with the list of fields', - ' // RETURN {num} {field} // - Limit which fields from the document are returned. Replace {num} with the number of fields, {field} with the list of fields', - ' // SUMMARIZE // - Return only the sections of the field which contain the matched text. SUMMARIZE keyword accepts FIELDS, FRAGS, LEN and SEPARATOR', - ' // FIELDS {num} {field} // - List of fields to summarize. Replace {num} with the number of keys, {field} with the list of fields. Used only with SUMMARIZE keyword', - ' // FRAGS {num} // - How many fragments should be returned. If not specified, a default of 3 is used. Used only with SUMMARIZE keyword', - ' // LEN {fragsize} // - Number of context words each fragment should contain. Replace {fragsize} with the number of context words. Used only with SUMMARIZE keyword', - ' // SEPARATOR {separator} // - String used to divide between individual summary snippets. Replace {separator} with the string. Used only with SUMMARIZE keyword', - ' // HIGHLIGHT // - Highlight the found term (and its variants) with a user-defined tag. HIGHLIGHT keyword accepts FIELDS and TAGS', - ' // FIELDS {num} {field} // - Specify fields to highlight. Replace {num} with the number of keys, {field} with the list of fields. Used only with HIGHLIGHT keyword', - ' // TAGS {open} {close} // - Replace {open} with the string to prepend to each term match, {close} with the string to append to each term match. Used only with HIGHLIGHT keyword', - ' // SLOP {slop} // - Allow a maximum of N intervening number of unmatched offsets between phrase terms. Replace {slope} with the number', - ' // INORDER // - Make sure the query terms appear in the same order in the document as in the query, regardless of the offsets between them', - ' // LANGUAGE {language} // - Use a stemmer for the supplied language during search for query expansion. Replace {language} with the language', - ' // EXPANDER {expander} // - Use a custom query expander instead of the stemmer. Replace {expander} with the custom expander', - ' // SCORER {scorer} // - Use a custom scoring function defined by the user. Replace {scorer} with the custom expander', - ' // EXPLAINSCORE // - Return a textual description of how the scores were calculated', - ' // PAYLOAD {payload} // - Add an arbitrary, binary safe payload that will be exposed to custom scoring functions. Replace {payload} with the payload', - ' // SORTBY {field} ASC|DESC // - Results are ordered by the value of this field. Replace {field} with the field, ASC|DESC – with ASC or DESC', - ' // LIMIT offset num // - Limit the results to the offset and number of results given. Replace offset with the offset (zero-indexed), num with the number of items to return starting from the first result', - ].join('\n'), - }, - { - title: 'Aggregate', - script: [ - '// Run a search query on an index and perform aggregate transformations on the results', - '// Replace:', - '// {index} with name of index', - '// {“query”} with the text query to search', - '', - 'FT.AGGREGATE {index} {“query”}', - ' ', - ' // VERBATIM // - Do not try to use stemming for query expansion but search the query terms verbatim', - ' // LOAD {nargs} {property} // - Load document fields from the document objects. This should be avoided as a general rule of thumb. Replace {nargs} with the number of fields, {property} with the list of fields', - ' // GROUPBY {nargs} {property} // Group the results in the pipeline based on one or more properties. Each group should have at least one reducer (REDUCE function). Replace {nargs} with the number of fields, {property} with the list of fields', - ' // REDUCE {func} {nargs} {arg} // Reduce the matching results in each group into a single record, using a reduction function. Replace {func} with {nargs} with the number of arguments, {arg} with the list of arguments', - ' // AS {name:string} // Optional argument to specify a name for reducer. Used only with REDUCE keyword', - ' // SORTBY {nargs} {property} ASC|DESC // Sort the pipeline up until the point of SORTBY, using a list of properties. Replace {nargs} with the number of fields, {property} with the list of fields, ASC|DESC – with ASC or DESC', - ' // MAX {num} // used to optimized sorting, by sorting only for the n-largest elements. Used only with SORTBY keyword. Replace {num} with the number of elements', - ' // APPLY {expr} // Apply a 1-to-1 transformation on one or more properties. Replace {expr} with expression', - ' // AS {alias} // Optional argument to specify a name for expressions. Used only with APPLY keyword', - ' // LIMIT {offset} {num} // Limit the results to the offset and number of results given. Replace {offset} with the offset (zero-indexed), {num} with the number of items to return starting from the first result', - ' // FILTER {expr} // Filter the results using predicate expressions relating to values in each result. Replace {expr} with the expression', - ].join('\n'), - }, -] diff --git a/redisinsight/ui/src/pages/workbench/WorkbenchPage.tsx b/redisinsight/ui/src/pages/workbench/WorkbenchPage.tsx index 81e44b1962..4c92b62040 100644 --- a/redisinsight/ui/src/pages/workbench/WorkbenchPage.tsx +++ b/redisinsight/ui/src/pages/workbench/WorkbenchPage.tsx @@ -1,17 +1,39 @@ -import React, { useEffect } from 'react' +import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { formatLongName, getDbIndex, setTitle } from 'uiSrc/utils' import { PageNames } from 'uiSrc/constants' import { connectedInstanceSelector } from 'uiSrc/slices/instances' import { setLastPageContext } from 'uiSrc/slices/app/context' import { loadPluginsAction } from 'uiSrc/slices/app/plugins' +import { sendPageViewTelemetry, TelemetryPageView } from 'uiSrc/telemetry' +import { appAnalyticsInfoSelector } from 'uiSrc/slices/app/info' +import { useParams } from 'react-router-dom' import WBViewWrapper from './components/wb-view' const WorkbenchPage = () => { - const { name, db } = useSelector(connectedInstanceSelector) + const [isPageViewSent, setIsPageViewSent] = useState(false) + + const { name: connectedInstanceName, db } = useSelector(connectedInstanceSelector) + const { identified: analyticsIdentified } = useSelector(appAnalyticsInfoSelector) + + const { instanceId } = useParams<{ instanceId: string }>() const dispatch = useDispatch() - setTitle(`${formatLongName(name, 33, 0, '...')} ${getDbIndex(db)} - Workbench`) + setTitle(`${formatLongName(connectedInstanceName, 33, 0, '...')} ${getDbIndex(db)} - Workbench`) + + useEffect(() => { + if (connectedInstanceName && !isPageViewSent && analyticsIdentified) { + sendPageView(instanceId) + } + }, [connectedInstanceName, isPageViewSent, analyticsIdentified]) + + const sendPageView = (instanceId: string) => { + sendPageViewTelemetry({ + name: TelemetryPageView.WORKBENCH_PAGE, + databaseId: instanceId + }) + setIsPageViewSent(true) + } useEffect(() => { dispatch(loadPluginsAction()) diff --git a/redisinsight/ui/src/pages/workbench/components/enablament-area/EnablementArea/EnablementArea.tsx b/redisinsight/ui/src/pages/workbench/components/enablament-area/EnablementArea/EnablementArea.tsx index 4e419d29c6..ea65f0ee11 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablament-area/EnablementArea/EnablementArea.tsx +++ b/redisinsight/ui/src/pages/workbench/components/enablament-area/EnablementArea/EnablementArea.tsx @@ -18,10 +18,11 @@ import styles from './styles.module.scss' export interface Props { items: IEnablementAreaItem[]; loading: boolean; - openScript: (script: string) => void; + openScript: (script: string, path: string) => void; + openInternalPage: (page: IInternalPage) => void; } -const EnablementArea = ({ items, openScript, loading }: Props) => { +const EnablementArea = ({ items, openScript, openInternalPage, loading }: Props) => { const [isInternalPageVisible, setIsInternalPageVisible] = useState(false) const [internalPage, setInternalPage] = useState( { backTitle: '', path: '', label: '' } @@ -30,6 +31,7 @@ const EnablementArea = ({ items, openScript, loading }: Props) => { const handleOpenInternalPage = (page: IInternalPage) => { setIsInternalPageVisible(true) setInternalPage(page) + openInternalPage(page) } const handleCloseInternalPage = () => { @@ -44,7 +46,7 @@ const EnablementArea = ({ items, openScript, loading }: Props) => { case EnablementAreaComponent.CodeButton: return args?.path ? - : openScript(args?.content || '')} label={label} {...args} /> + : openScript(args?.content || '', '')} label={label} {...args} /> case EnablementAreaComponent.InternalLink: return ( diff --git a/redisinsight/ui/src/pages/workbench/components/enablament-area/EnablementArea/components/LazyCodeButton/LazyCodeButton.tsx b/redisinsight/ui/src/pages/workbench/components/enablament-area/EnablementArea/components/LazyCodeButton/LazyCodeButton.tsx index db0c7a338d..d71baebcb1 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablament-area/EnablementArea/components/LazyCodeButton/LazyCodeButton.tsx +++ b/redisinsight/ui/src/pages/workbench/components/enablament-area/EnablementArea/components/LazyCodeButton/LazyCodeButton.tsx @@ -11,7 +11,7 @@ export interface Props { } const LazyCodeButton = ({ path = '', ...rest }: Props) => { const [isLoading, setLoading] = useState(false) - const [error, setError] = useState('') + const [, setError] = useState('') const { setScript } = useContext(EnablementAreaContext) const loadContent = async () => { @@ -22,7 +22,7 @@ const LazyCodeButton = ({ path = '', ...rest }: Props) => { const { data, status } = await resourcesService.get(path) if (isStatusSuccessful(status)) { setLoading(false) - setScript(data) + setScript(data, path) } } catch (error) { setLoading(false) diff --git a/redisinsight/ui/src/pages/workbench/components/enablament-area/EnablementAreaWrapper.tsx b/redisinsight/ui/src/pages/workbench/components/enablament-area/EnablementAreaWrapper.tsx index 85e4270c41..a21d0ce30b 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablament-area/EnablementAreaWrapper.tsx +++ b/redisinsight/ui/src/pages/workbench/components/enablament-area/EnablementAreaWrapper.tsx @@ -2,11 +2,14 @@ import React, { useEffect } from 'react' import { monaco } from 'react-monaco-editor' import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api' import { useDispatch, useSelector } from 'react-redux' +import { useParams } from 'react-router-dom' import { Nullable, } from 'uiSrc/utils' +import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { fetchEnablementArea, workbenchEnablementAreaSelector } from 'uiSrc/slices/workbench/wb-enablement-area' import EnablementArea from './EnablementArea' +import { IInternalPage } from '../../contexts/enablementAreaContext' export interface Props { scriptEl: Nullable; @@ -15,13 +18,25 @@ export interface Props { const EnablementAreaWrapper = React.memo(({ scriptEl, setScript }: Props) => { const { loading, items } = useSelector(workbenchEnablementAreaSelector) + const { instanceId = '' } = useParams<{ instanceId: string }>() const dispatch = useDispatch() useEffect(() => { dispatch(fetchEnablementArea()) }, []) - const openScript = (script: string) => { + const sendEventButtonClickedTelemetry = (path: string = '') => { + sendEventTelemetry({ + event: TelemetryEvent.WORKBENCH_ENABLEMENT_AREA_COMMAND_CLICKED, + eventData: { + path, + databaseId: instanceId, + } + }) + } + + const openScript = (script: string, path: string) => { + sendEventButtonClickedTelemetry(path) setScript(script) setTimeout(() => { @@ -30,8 +45,23 @@ const EnablementAreaWrapper = React.memo(({ scriptEl, setScript }: Props) => { }, 0) } + const openInternalPage = ({ path }: IInternalPage) => { + sendEventTelemetry({ + event: TelemetryEvent.WORKBENCH_ENABLEMENT_AREA_GUIDE_OPENED, + eventData: { + path, + databaseId: instanceId, + } + }) + } + return ( - + ) }) diff --git a/redisinsight/ui/src/pages/workbench/components/wb-view/WBViewWrapper.tsx b/redisinsight/ui/src/pages/workbench/components/wb-view/WBViewWrapper.tsx index 13dc96e50e..3899e4b46b 100644 --- a/redisinsight/ui/src/pages/workbench/components/wb-view/WBViewWrapper.tsx +++ b/redisinsight/ui/src/pages/workbench/components/wb-view/WBViewWrapper.tsx @@ -2,7 +2,6 @@ import React, { Ref, useEffect, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { decode } from 'html-entities' import { useParams } from 'react-router-dom' -import { isEmpty, reject } from 'lodash' import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api' import { @@ -13,6 +12,8 @@ import { getWBQueryType, checkUnsupportedModuleCommand, cliParseTextResponse, + splitMonacoValuePerLines, + getMultiCommands, } from 'uiSrc/utils' import { sendWBCommandAction, @@ -158,10 +159,9 @@ const WBViewWrapper = () => { ) => { const { loading } = state const isNewCommand = () => !historyId - const commandsList = commandInit.split(/\n(?=[^\s])/g) + const [command, ...rest] = splitMonacoValuePerLines(commandInit) - const [command, ...rest] = commandsList - const multiCommands = reject(rest, isEmpty).join('\n') + const multiCommands = getMultiCommands(rest) setMultiCommands(multiCommands) let commandLine = decode(command).trim() diff --git a/redisinsight/ui/src/pages/workbench/contexts/enablementAreaContext.tsx b/redisinsight/ui/src/pages/workbench/contexts/enablementAreaContext.tsx index 5d8afe39ed..241ddf33d2 100644 --- a/redisinsight/ui/src/pages/workbench/contexts/enablementAreaContext.tsx +++ b/redisinsight/ui/src/pages/workbench/contexts/enablementAreaContext.tsx @@ -1,7 +1,7 @@ import React from 'react' interface IContext { - setScript: (script: string) => void; + setScript: (script: string, path: string) => void; openPage: (page: IInternalPage) => void; } export interface IInternalPage { @@ -10,7 +10,7 @@ export interface IInternalPage { backTitle: string; } export const defaultValue = { - setScript: (script: string) => script, + setScript: (script: string, path: string) => script, openPage: (page: IInternalPage) => page } const EnablementAreaContext = React.createContext(defaultValue) diff --git a/redisinsight/ui/src/telemetry/events.ts b/redisinsight/ui/src/telemetry/events.ts index 5663aea2c7..ea94ed9ef7 100644 --- a/redisinsight/ui/src/telemetry/events.ts +++ b/redisinsight/ui/src/telemetry/events.ts @@ -48,4 +48,16 @@ export enum TelemetryEvent { COMMAND_HELPER_COMMAND_OPENED = 'COMMAND_HELPER_COMMAND_OPENED', SETTINGS_COLOR_THEME_CHANGED = 'SETTINGS_COLOR_THEME_CHANGED', + + WORKBENCH_ENABLEMENT_AREA_GUIDE_OPENED = 'WORKBENCH_ENABLEMENT_AREA_GUIDE_OPENED', + WORKBENCH_ENABLEMENT_AREA_COMMAND_CLICKED = 'WORKBENCH_ENABLEMENT_AREA_COMMAND_CLICKED', + WORKBENCH_ENABLEMENT_AREA_LINK_CLICKED = 'WORKBENCH_ENABLEMENT_AREA_LINK_CLICKED', + WORKBENCH_COMMAND_SUBMITTED = 'WORKBENCH_COMMAND_SUBMITTED', + + WORKBENCH_COMMAND_COPIED = 'WORKBENCH_COMMAND_COPIED', + WORKBENCH_COMMAND_RUN_AGAIN = 'WORKBENCH_COMMAND_RUN_AGAIN', + WORKBENCH_COMMAND_DELETE_COMMAND = 'WORKBENCH_COMMAND_DELETE_COMMAND', + WORKBENCH_RESULTS_IN_FULL_SCREEN = 'WORKBENCH_RESULTS_IN_FULL_SCREEN', + WORKBENCH_RESULTS_COLLAPSED = 'WORKBENCH_RESULTS_COLLAPSED', + WORKBENCH_RESULTS_EXPANDED = 'WORKBENCH_RESULTS_EXPANDED', } diff --git a/redisinsight/ui/src/telemetry/pageViews.ts b/redisinsight/ui/src/telemetry/pageViews.ts index c7927e1760..b9f7e478d1 100644 --- a/redisinsight/ui/src/telemetry/pageViews.ts +++ b/redisinsight/ui/src/telemetry/pageViews.ts @@ -3,4 +3,5 @@ export enum TelemetryPageView { WELCOME_PAGE = 'Welcome', SETTINGS_PAGE = 'Settings', BROWSER_PAGE = 'Browser', + WORKBENCH_PAGE = 'Workbench', } diff --git a/redisinsight/ui/src/utils/monaco.ts b/redisinsight/ui/src/utils/monaco.ts index 98ee046f0f..d9dde6c099 100644 --- a/redisinsight/ui/src/utils/monaco.ts +++ b/redisinsight/ui/src/utils/monaco.ts @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import { isEmpty, reject } from 'lodash' const COMMENT_SYMBOLS = '//' const BLANK_LINE_REGEX = /^\s*\n/gm @@ -17,6 +17,10 @@ const removeCommentsFromLine = (text: string = '', prefix: string = ''): string return prefix + text.replace(/\/\/.*/, '') } +export const splitMonacoValuePerLines = (command = '') => command.split(/\n(?=[^\s])/g) + +export const getMultiCommands = (commands:string[] = []) => reject(commands, isEmpty).join('\n') ?? '' + export const removeMonacoComments = (text: string = '') => text .split('\n') .filter((line: string) => !COMMENT_LINE_REGEX.test(line)) From 33282bd1f93239c6d1df9d6a57f0a7b9b9ab6e2e Mon Sep 17 00:00:00 2001 From: Zalenski Egor <63463140+zalenskiSofteq@users.noreply.github.com> Date: Tue, 23 Nov 2021 18:45:34 +0300 Subject: [PATCH 2/2] #RI-2075 | fix pr comments --- redisinsight/ui/src/pages/workbench/WorkbenchPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/redisinsight/ui/src/pages/workbench/WorkbenchPage.tsx b/redisinsight/ui/src/pages/workbench/WorkbenchPage.tsx index 4c92b62040..2781f65118 100644 --- a/redisinsight/ui/src/pages/workbench/WorkbenchPage.tsx +++ b/redisinsight/ui/src/pages/workbench/WorkbenchPage.tsx @@ -1,5 +1,7 @@ import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' +import { useParams } from 'react-router-dom' + import { formatLongName, getDbIndex, setTitle } from 'uiSrc/utils' import { PageNames } from 'uiSrc/constants' import { connectedInstanceSelector } from 'uiSrc/slices/instances' @@ -7,7 +9,6 @@ import { setLastPageContext } from 'uiSrc/slices/app/context' import { loadPluginsAction } from 'uiSrc/slices/app/plugins' import { sendPageViewTelemetry, TelemetryPageView } from 'uiSrc/telemetry' import { appAnalyticsInfoSelector } from 'uiSrc/slices/app/info' -import { useParams } from 'react-router-dom' import WBViewWrapper from './components/wb-view' const WorkbenchPage = () => {