diff --git a/redisinsight/ui/.eslintrc.js b/redisinsight/ui/.eslintrc.js index f8f6e8953f..fe13557a08 100644 --- a/redisinsight/ui/.eslintrc.js +++ b/redisinsight/ui/.eslintrc.js @@ -79,6 +79,11 @@ module.exports = { 'index', ], pathGroups: [ + { + pattern: 'apiSrc/**', + group: 'internal', + position: 'after' + }, { pattern: '{.,..}/*.scss', // same directory only // pattern: '{.,..}/**/*\.scss' // same & outside directories (e.g. import '../foo/foo.scss') @@ -86,7 +91,8 @@ module.exports = { position: 'after' } ], - warnOnUnassignedImports: true + warnOnUnassignedImports: true, + pathGroupsExcludedImportTypes: ['builtin'] }, ], }, diff --git a/redisinsight/ui/src/components/query-card/QueryCardCliPlugin/QueryCardCliPlugin.tsx b/redisinsight/ui/src/components/query-card/QueryCardCliPlugin/QueryCardCliPlugin.tsx index d4375cf20d..b323a54d42 100644 --- a/redisinsight/ui/src/components/query-card/QueryCardCliPlugin/QueryCardCliPlugin.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCardCliPlugin/QueryCardCliPlugin.tsx @@ -1,6 +1,7 @@ import React, { useContext, useEffect, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import cx from 'classnames' +import { v4 as uuidv4 } from 'uuid' import { EuiFlexItem, EuiIcon, EuiLoadingContent, EuiTextColor } from '@elastic/eui' import { pluginApi } from 'uiSrc/services/PluginAPI' import { ThemeContext } from 'uiSrc/contexts/themeContext' @@ -210,7 +211,7 @@ const QueryCardCliPlugin = (props: Props) => { useEffect(() => { const view = visualizations.find((visualization: IPluginVisualization) => visualization.uniqId === id) if (view) { - generatedIframeNameRef.current = `${view.plugin.name}-${Date.now()}` + generatedIframeNameRef.current = `${view.plugin.name}-${uuidv4()}` setCurrentView(view) const { plugin } = view diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/EnablementArea.tsx b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/EnablementArea.tsx index 6bd438c066..aa126ff77e 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/EnablementArea.tsx +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/EnablementArea.tsx @@ -3,6 +3,7 @@ import { useHistory, useLocation } from 'react-router-dom' import { useSelector, useDispatch } from 'react-redux' import cx from 'classnames' import { EuiListGroup, EuiLoadingContent } from '@elastic/eui' +import { CodeButtonParams, ExecuteButtonMode } from 'uiSrc/pages/workbench/components/enablement-area/interfaces' import { EnablementAreaComponent, IEnablementAreaItem } from 'uiSrc/slices/interfaces' import { EnablementAreaProvider, IInternalPage } from 'uiSrc/pages/workbench/contexts/enablementAreaContext' import { appContextWorkbenchEA, resetWorkbenchEAItem } from 'uiSrc/slices/app/context' @@ -14,7 +15,7 @@ import { InternalLink, LazyCodeButton, LazyInternalPage, - PlainText + PlainText, } from './components' import styles from './styles.module.scss' @@ -25,7 +26,11 @@ export interface Props { guides: Record tutorials: Record loading: boolean - openScript: (script: string, path?: string, name?: string) => void + openScript: ( + script: string, + execute?: { mode?: ExecuteButtonMode, params?: CodeButtonParams }, + file?: { path?: string, name?: string } + ) => void onOpenInternalPage: (page: IInternalPage) => void isCodeBtnDisabled?: boolean } diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/Code/Code.spec.tsx b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/Code/Code.spec.tsx index 09dd5b1cf8..fb7b3984e8 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/Code/Code.spec.tsx +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/Code/Code.spec.tsx @@ -1,8 +1,9 @@ import React from 'react' import { instance, mock } from 'ts-mockito' -import { fireEvent, render } from 'uiSrc/utils/test-utils' -import { EnablementAreaProvider, defaultValue } from 'uiSrc/pages/workbench/contexts/enablementAreaContext' import { MONACO_MANUAL } from 'uiSrc/constants' +import { ExecuteButtonMode } from 'uiSrc/pages/workbench/components/enablement-area/interfaces' +import { defaultValue, EnablementAreaProvider } from 'uiSrc/pages/workbench/contexts/enablementAreaContext' +import { fireEvent, render, screen } from 'uiSrc/utils/test-utils' import Code, { Props } from './Code' @@ -29,6 +30,21 @@ describe('Code', () => { const link = queryByTestId(`preselect-${label}`) fireEvent.click(link as Element) - expect(setScript).toBeCalledWith(MONACO_MANUAL) + expect(setScript).toBeCalledWith(MONACO_MANUAL, {}, undefined) + }) + + it('should correctly set script with auto execute', () => { + const setScript = jest.fn() + const label = 'Manual' + + render( + + {MONACO_MANUAL} + + ) + + screen.debug() + fireEvent.click(screen.queryByTestId(`preselect-auto-${label}`) as Element) + expect(setScript).toBeCalledWith(MONACO_MANUAL, { mode: ExecuteButtonMode.Auto }, undefined) }) }) diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/Code/Code.tsx b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/Code/Code.tsx index 00e97e0eb9..edee32b42c 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/Code/Code.tsx +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/Code/Code.tsx @@ -1,33 +1,48 @@ -import React, { useContext } from 'react' import { startCase } from 'lodash' +import React, { useContext } from 'react' import { useLocation } from 'react-router-dom' - +import { getFileInfo, parseParams } from 'uiSrc/pages/workbench/components/enablement-area/EnablementArea/utils' +import { CodeButtonParams, ExecuteButtonMode } from 'uiSrc/pages/workbench/components/enablement-area/interfaces' import EnablementAreaContext from 'uiSrc/pages/workbench/contexts/enablementAreaContext' -import { getFileInfo } from 'uiSrc/pages/workbench/components/enablement-area/EnablementArea/utils/getFileInfo' +import { Maybe } from 'uiSrc/utils' import CodeButton from '../CodeButton' export interface Props { - label: string; - children: string; + label: string + children: string + params?: string + mode?: ExecuteButtonMode } -const Code = ({ children, ...rest }: Props) => { +const Code = ({ children, params, mode, ...rest }: Props) => { const { search } = useLocation() const { setScript, isCodeBtnDisabled } = useContext(EnablementAreaContext) - const loadContent = () => { + const loadContent = (execute: { mode?: ExecuteButtonMode, params?: CodeButtonParams }) => { const pagePath = new URLSearchParams(search).get('item') + let file: Maybe<{ path: string, name: string }> + if (pagePath) { const pageInfo = getFileInfo(pagePath) - setScript(children, `${pageInfo.location}/${pageInfo.name}`, startCase(rest.label)) - } else { - setScript(children) + file = { + path: `${pageInfo.location}/${pageInfo.name}`, + name: startCase(rest.label) + } } + + setScript(children, execute, file) } return ( - + ) } diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CodeButton/CodeButton.spec.tsx b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CodeButton/CodeButton.spec.tsx index 3f1d306003..267406242b 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CodeButton/CodeButton.spec.tsx +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CodeButton/CodeButton.spec.tsx @@ -1,5 +1,6 @@ import React from 'react' import { instance, mock } from 'ts-mockito' +import { ExecuteButtonMode } from 'uiSrc/pages/workbench/components/enablement-area/interfaces' import { fireEvent, render, screen } from 'uiSrc/utils/test-utils' import CodeButton, { Props } from './CodeButton' @@ -14,6 +15,14 @@ describe('CodeButton', () => { expect(component).toBeTruthy() expect(container).toHaveTextContent(label) }) + + it('should not render auto-execute button', () => { + const label = 'Manual' + render() + + expect(screen.queryByTestId(`preselect-auto-${label}`)).not.toBeInTheDocument() + }) + it('should call onClick function', () => { const onClick = jest.fn() const label = 'Manual' @@ -23,4 +32,22 @@ describe('CodeButton', () => { expect(onClick).toBeCalled() }) + + it('should call onClick with auto execute param', () => { + const onClick = jest.fn() + const label = 'Auto' + + render( + + ) + fireEvent.click(screen.getByTestId(`preselect-auto-${label}`)) + + expect(onClick).toBeCalledWith({ mode: ExecuteButtonMode.Auto, params: {} }) + }) }) diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CodeButton/CodeButton.tsx b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CodeButton/CodeButton.tsx index e09ca36393..c3dd6d81ed 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CodeButton/CodeButton.tsx +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CodeButton/CodeButton.tsx @@ -1,32 +1,48 @@ +import { EuiButton, EuiIcon } from '@elastic/eui' +import cx from 'classnames' import React from 'react' -import { EuiButton } from '@elastic/eui' +import { CodeButtonParams, ExecuteButtonMode } from 'uiSrc/pages/workbench/components/enablement-area/interfaces' import { truncateText } from 'uiSrc/utils' import styles from './styles.module.scss' export interface Props { - onClick: () => void + onClick: (execute: { mode?: ExecuteButtonMode, params?: CodeButtonParams }) => void label: string isLoading?: boolean disabled?: boolean className?: string + params?: CodeButtonParams + mode?: ExecuteButtonMode +} +const CodeButton = ({ onClick, label, isLoading, className, disabled, params, mode, ...rest }: Props) => { + const isAutoExecute = mode === ExecuteButtonMode.Auto + + return ( + onClick({ mode, params })} + fullWidth + color="secondary" + className={cx(className, styles.button)} + textProps={{ className: styles.buttonText }} + data-testid={`preselect-${isAutoExecute ? 'auto-' : ''}${label}`} + disabled={disabled} + {...rest} + > + <> + {truncateText(label, 86)} + {isAutoExecute && ( + + )} + + + ) } -const CodeButton = ({ onClick, label, isLoading, className, disabled, ...rest }: Props) => ( - - {truncateText(label, 86)} - -) export default CodeButton diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CodeButton/styles.module.scss b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CodeButton/styles.module.scss index 645b37b7cd..16a131f35f 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CodeButton/styles.module.scss +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CodeButton/styles.module.scss @@ -2,18 +2,27 @@ .button { justify-content: space-between; + position: relative; &[class*='euiButton--secondary']:not([class*='isDisabled']) { - &:hover, - &:focus, - &:focus-within { + &:hover { background-color: var(--euiColorSecondary) !important; border-color: var(--euiColorSecondary) !important; + color: var(--euiColorPrimaryText) !important; } } - &:not(:hover) { - span { - color: var(--euiTextSubduedColor); - } + + :global(.euiButton__content) { + padding: 0 24px !important; + } + + .autoExecuteIcon { + position: absolute; + top: 50%; + right: 4px; + transform: translateY(-50%); + + width: 16px; + height: 16px; } } diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/LazyCodeButton/LazyCodeButton.tsx b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/LazyCodeButton/LazyCodeButton.tsx index d7b3a47aef..7c66fee5a2 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/LazyCodeButton/LazyCodeButton.tsx +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/LazyCodeButton/LazyCodeButton.tsx @@ -27,7 +27,7 @@ const LazyCodeButton = ({ path = '', ...rest }: Props) => { if (isStatusSuccessful(status)) { setLoading(false) const pageInfo = getFileInfo(path) - setScript(data, pageInfo.location, startCase(pageInfo.name)) + setScript(data, {}, { path: pageInfo.location, name: startCase(pageInfo.name) }) } } catch (error) { setLoading(false) diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/index.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/index.ts new file mode 100644 index 0000000000..cde58b5afd --- /dev/null +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/index.ts @@ -0,0 +1,5 @@ +export * from './parseParams' +export * from './getFileInfo' +export * from './remarkImage' +export * from './rehypeLinks' +export * from './remarkRedisCode' diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/parseParams.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/parseParams.ts new file mode 100644 index 0000000000..6a8ff43a53 --- /dev/null +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/parseParams.ts @@ -0,0 +1,17 @@ +import { CodeButtonParams } from 'uiSrc/pages/workbench/components/enablement-area/interfaces' + +export const parseParams = (params?: string): CodeButtonParams | undefined => { + if (params?.match(/(^\[).+(]$)/g)) { + return params + ?.replace(/^\[|]$/g, '') + ?.split(';') + .reduce((prev: {}, next: string) => { + const [key, value] = next.split('=') + return { + ...prev, + [key]: value + } + }, {}) + } + return undefined +} diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/remarkRedisCode.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/remarkRedisCode.ts index 377464eca3..a3c5f855d3 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/remarkRedisCode.ts +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/remarkRedisCode.ts @@ -1,14 +1,30 @@ import { visit } from 'unist-util-visit' +import { ExecuteButtonMode } from 'uiSrc/pages/workbench/components/enablement-area/interfaces' + +enum ButtonLang { + Redis = 'redis', + RedisAuto = 'redis-auto' +} + +const PARAMS_SEPARATOR = ':' export const remarkRedisCode = (): (tree: Node) => void => (tree: any) => { // Find code node in syntax tree visit(tree, 'code', (codeNode) => { const { value, meta, lang } = codeNode + + if (!lang) return + // Check that it has a language unsupported by our editor - if (lang === 'redis') { + if (lang.startsWith(ButtonLang.Redis)) { + const execute = lang.startsWith(ButtonLang.RedisAuto) + ? ExecuteButtonMode.Auto + : ExecuteButtonMode.Manual + const [, params] = lang?.split(PARAMS_SEPARATOR) + codeNode.type = 'html' // Replace it with our custom component - codeNode.value = `{${JSON.stringify(value)}}` + codeNode.value = `{${JSON.stringify(value)}}` } }) } diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/getFileInfo.spec.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/tests/getFileInfo.spec.ts similarity index 97% rename from redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/getFileInfo.spec.ts rename to redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/tests/getFileInfo.spec.ts index 2970081b32..db882e513a 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/getFileInfo.spec.ts +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/tests/getFileInfo.spec.ts @@ -1,5 +1,5 @@ import { MOCK_GUIDES_ITEMS } from 'uiSrc/constants' -import { getFileInfo, getPagesInsideGroup } from './getFileInfo' +import { getFileInfo, getPagesInsideGroup } from '../getFileInfo' const getFileInfoTests = [ { diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/tests/parseParams.spec.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/tests/parseParams.spec.ts new file mode 100644 index 0000000000..7e73a0612e --- /dev/null +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/tests/parseParams.spec.ts @@ -0,0 +1,17 @@ +import { parseParams } from '../parseParams' + +const parseParamsTests: any[] = [ + ['[]', undefined], + ['[execute=auto]', { execute: 'auto' }], + ['[execute=auto;]', { execute: 'auto' }], + ['[execute=auto;mode=group]', { execute: 'auto', mode: 'group' }], + ['[execute=auto;mode=group;]', { execute: 'auto', mode: 'group' }], +] + +describe('parseParams', () => { + it.each(parseParamsTests)('for input: %s (params), should be output: %s', + (params, expected) => { + const result = parseParams(params) + expect(result).toEqual(expected) + }) +}) diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/tests/remarkRedisCode.spec.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/tests/remarkRedisCode.spec.ts new file mode 100644 index 0000000000..ad2b31ca82 --- /dev/null +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/tests/remarkRedisCode.spec.ts @@ -0,0 +1,65 @@ +import { ExecuteButtonMode } from 'uiSrc/pages/workbench/components/enablement-area/interfaces' +import { visit } from 'unist-util-visit' +import { remarkRedisCode } from '../remarkRedisCode' + +jest.mock('unist-util-visit') + +const getValue = (meta: string, execute = ExecuteButtonMode.Manual, params?: string, value?: string) => + `{${JSON.stringify(value)}}` + +describe('remarkRedisCode', () => { + it('should not modify codeNode if lang not redis', () => { + const codeNode = { + lang: 'html', + value: '1', + meta: '2' + }; + // mock implementation + (visit as jest.Mock) + .mockImplementation((_tree: any, _name: string, callback: (codeNode: any) => void) => { callback(codeNode) }) + + const remark = remarkRedisCode() + remark({} as Node) + expect(codeNode).toEqual({ + ...codeNode + }) + }) + + it('should properly modify codeNode with lang redis', () => { + const codeNode = { + lang: 'redis', + value: '1', + meta: '2' + }; + // mock implementation + (visit as jest.Mock) + .mockImplementation((_tree: any, _name: string, callback: (codeNode: any) => void) => { callback(codeNode) }) + + const remark = remarkRedisCode() + remark({} as Node) + expect(codeNode).toEqual({ + ...codeNode, + type: 'html', + value: getValue(codeNode.meta, ExecuteButtonMode.Manual, undefined, '1') + }) + }) + + it('should properly modify codeNode with lang redis-auto', () => { + const codeNode = { + lang: 'redis-auto', + value: '1', + meta: '2' + }; + // mock implementation + (visit as jest.Mock) + .mockImplementation((_tree: any, _name: string, callback: (codeNode: any) => void) => { callback(codeNode) }) + + const remark = remarkRedisCode() + remark({} as Node) + expect(codeNode).toEqual({ + ...codeNode, + type: 'html', + value: getValue(codeNode.meta, ExecuteButtonMode.Auto, undefined, '1') + }) + }) +}) diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementAreaWrapper.tsx b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementAreaWrapper.tsx index c1b7dd51e9..a4d2bfd30b 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementAreaWrapper.tsx +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementAreaWrapper.tsx @@ -1,19 +1,20 @@ -import React, { useEffect } from 'react' -import { monaco } from 'react-monaco-editor' import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui' import cx from 'classnames' import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api' +import React, { useEffect } from 'react' +import { monaco } from 'react-monaco-editor' import { useDispatch, useSelector } from 'react-redux' import { useParams } from 'react-router-dom' - -import { Nullable, } from 'uiSrc/utils' -import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { CodeButtonParams, ExecuteButtonMode } from 'uiSrc/pages/workbench/components/enablement-area/interfaces' +import { IInternalPage } from 'uiSrc/pages/workbench/contexts/enablementAreaContext' import { fetchGuides, workbenchGuidesSelector } from 'uiSrc/slices/workbench/wb-guides' import { fetchTutorials, workbenchTutorialsSelector } from 'uiSrc/slices/workbench/wb-tutorials' +import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' + +import { Nullable, } from 'uiSrc/utils' import EnablementArea from './EnablementArea' import EnablementAreaCollapse from './EnablementAreaCollapse/EnablementAreaCollapse' -import { IInternalPage } from '../../contexts/enablementAreaContext' import styles from './styles.module.scss' @@ -22,10 +23,12 @@ export interface Props { setIsMinimized: (value: boolean) => void scriptEl: Nullable setScript: (script: string) => void + onSubmit: (query: string, commandId?: Nullable, clearEditor?: boolean) => void isCodeBtnDisabled?: boolean } -const EnablementAreaWrapper = ({ isMinimized, setIsMinimized, scriptEl, setScript, isCodeBtnDisabled }: Props) => { +const EnablementAreaWrapper = (props: Props) => { + const { isMinimized, setIsMinimized, scriptEl, setScript, isCodeBtnDisabled, onSubmit } = props const { loading: loadingGuides, items: guides } = useSelector(workbenchGuidesSelector) const { loading: loadingTutorials, items: tutorials } = useSelector(workbenchTutorialsSelector) const { instanceId = '' } = useParams<{ instanceId: string }>() @@ -39,7 +42,7 @@ const EnablementAreaWrapper = ({ isMinimized, setIsMinimized, scriptEl, setScrip dispatch(fetchTutorials()) }, []) - const sendEventButtonClickedTelemetry = (data: Record) => { + const sendEventButtonClickedTelemetry = (data?: Record) => { sendEventTelemetry({ event: TelemetryEvent.WORKBENCH_ENABLEMENT_AREA_COMMAND_CLICKED, eventData: { @@ -49,10 +52,19 @@ const EnablementAreaWrapper = ({ isMinimized, setIsMinimized, scriptEl, setScrip }) } - const openScript = (script: string, path?: string, name?: string) => { - sendEventButtonClickedTelemetry({ path, name }) - setScript(script) + const openScript = ( + script: string, + execute: { mode?: ExecuteButtonMode, params?: CodeButtonParams } = { mode: ExecuteButtonMode.Manual }, + file?: { path?: string, name?: string } + ) => { + sendEventButtonClickedTelemetry(file) + + if (execute.mode === ExecuteButtonMode.Auto) { + onSubmit(script, null, false) + return + } + setScript(script) setTimeout(() => { scriptEl?.focus() scriptEl?.setSelection(new monaco.Selection(0, 0, 0, 0)) diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/interfaces.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/interfaces.ts new file mode 100644 index 0000000000..6c650316e7 --- /dev/null +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/interfaces.ts @@ -0,0 +1,8 @@ +export interface CodeButtonParams { + // +} + +export enum ExecuteButtonMode { + Auto = 'auto', + Manual = 'manual' +} diff --git a/redisinsight/ui/src/pages/workbench/components/wb-results/WBResultsWrapper.tsx b/redisinsight/ui/src/pages/workbench/components/wb-results/WBResultsWrapper.tsx index a3c4f16a2f..7c4e89d60a 100644 --- a/redisinsight/ui/src/pages/workbench/components/wb-results/WBResultsWrapper.tsx +++ b/redisinsight/ui/src/pages/workbench/components/wb-results/WBResultsWrapper.tsx @@ -18,4 +18,4 @@ const WBResultsWrapper = (props: Props) => ( ) -export default WBResultsWrapper +export default React.memo(WBResultsWrapper) diff --git a/redisinsight/ui/src/pages/workbench/components/wb-view/WBView/WBView.tsx b/redisinsight/ui/src/pages/workbench/components/wb-view/WBView/WBView.tsx index 78f9a0e8ff..d443a0403d 100644 --- a/redisinsight/ui/src/pages/workbench/components/wb-view/WBView/WBView.tsx +++ b/redisinsight/ui/src/pages/workbench/components/wb-view/WBView/WBView.tsx @@ -90,6 +90,7 @@ const WBView = (props: Props) => { isMinimized={isMinimized} setIsMinimized={setIsMinimized} setScript={setScript} + onSubmit={onSubmit} scriptEl={scriptEl} isCodeBtnDisabled={isCodeBtnDisabled} /> 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 5939a6469a..1f38027d62 100644 --- a/redisinsight/ui/src/pages/workbench/components/wb-view/WBViewWrapper.tsx +++ b/redisinsight/ui/src/pages/workbench/components/wb-view/WBViewWrapper.tsx @@ -256,9 +256,9 @@ const WBViewWrapper = () => { if (state.loading || (!value && !script)) return handleSubmit(value, commandId) - setTimeout(() => { - (cleanupWB && clearEditor) && resetCommand() - }, 0) + if (cleanupWB && clearEditor) { + resetCommand() + } } return ( diff --git a/redisinsight/ui/src/pages/workbench/contexts/enablementAreaContext.tsx b/redisinsight/ui/src/pages/workbench/contexts/enablementAreaContext.tsx index 0150c3d671..d655881949 100644 --- a/redisinsight/ui/src/pages/workbench/contexts/enablementAreaContext.tsx +++ b/redisinsight/ui/src/pages/workbench/contexts/enablementAreaContext.tsx @@ -1,8 +1,13 @@ import React from 'react' +import { CodeButtonParams, ExecuteButtonMode } from 'uiSrc/pages/workbench/components/enablement-area/interfaces' interface IContext { - setScript: (script: string, path?: string, name?: string) => void; - openPage: (page: IInternalPage) => void; + setScript: ( + script: string, + execute?: { mode?: ExecuteButtonMode, params?: CodeButtonParams }, + file?: { path?: string, name?: string } + ) => void + openPage: (page: IInternalPage) => void isCodeBtnDisabled?: boolean } export interface IInternalPage { diff --git a/tests/e2e/pageObjects/workbench-page.ts b/tests/e2e/pageObjects/workbench-page.ts index fa93d89114..fd71910d6c 100644 --- a/tests/e2e/pageObjects/workbench-page.ts +++ b/tests/e2e/pageObjects/workbench-page.ts @@ -62,6 +62,8 @@ export class WorkbenchPage { rawModeBtn = Selector('[data-testid="btn-change-mode"]'); groupMode = Selector('[data-testid=btn-change-group-mode]'); copyCommand = Selector('[data-testid=copy-command]'); + redisStackTimeSeriesLoadMorePoints = Selector('[data-testid=preselect-Load more data points]'); + documentHashCreateButton = Selector('[data-testid=preselect-auto-Create]'); //ICONS noCommandHistoryIcon = Selector('[data-testid=wb_no-results__icon]'); //LINKS @@ -105,6 +107,7 @@ export class WorkbenchPage { workbenchCommandInHistory = Selector(this.cssWorkbenchCommandInHistory); workbenchCommandSuccessResultInHistory = Selector(this.cssWorkbenchCommandSuccessResultInHistory); workbenchCommandFailedResultInHistory = Selector(this.cssWorkbenchCommandFailedResultInHistory); + commandExecutionDateAndTime = Selector('[data-testid=command-execution-date-time]'); //MONACO ELEMENTS monacoCommandDetails = Selector('div.suggest-details-container'); monacoCloseCommandDetails = Selector('span.codicon-close'); diff --git a/tests/e2e/tests/regression/workbench/autoexecute-button.e2e.ts b/tests/e2e/tests/regression/workbench/autoexecute-button.e2e.ts new file mode 100644 index 0000000000..9e9778fe89 --- /dev/null +++ b/tests/e2e/tests/regression/workbench/autoexecute-button.e2e.ts @@ -0,0 +1,43 @@ +import { acceptLicenseTermsAndAddDatabaseApi } from '../../../helpers/database'; +import { WorkbenchPage, MyRedisDatabasePage } from '../../../pageObjects'; +import { env, rte } from '../../../helpers/constants'; +import { commonUrl, ossStandaloneConfig } from '../../../helpers/conf'; +import { deleteStandaloneDatabaseApi } from '../../../helpers/api/api-database'; + +const myRedisDatabasePage = new MyRedisDatabasePage(); +const workbenchPage = new WorkbenchPage(); + +fixture `Workbench Auto-Execute button` + .meta({ type: 'regression', rte: rte.standalone, env: env.web }) + .page(commonUrl) + .beforeEach(async t => { + await acceptLicenseTermsAndAddDatabaseApi(ossStandaloneConfig, ossStandaloneConfig.databaseName); + await t.click(myRedisDatabasePage.workbenchButton); + }) + .afterEach(async() => { + // Clear and delete database + await deleteStandaloneDatabaseApi(ossStandaloneConfig); + }); +// Test is skipped until Enablement area will be updated with auto-execute buttons +test.skip('Verify that when user clicks on auto-execute button, command is run', async t => { + const command = 'INFO'; + // Verify that clicking on auto-executed button, command is not inserted to Editor + await t.typeText(workbenchPage.queryInput, command); + // Verify that admin can use redis-auto format in .md file for Guides for auto-executed button + await t.click(workbenchPage.documentButtonInQuickGuides); + await t.click(workbenchPage.internalLinkWorkingWithHashes); + await t.click(workbenchPage.preselectHashCreate); + await t.expect(workbenchPage.queryInput.textContent).eql(command, 'Editor is changed'); + // Verify that admin can use redis-auto format in .md file for Tutorials for auto-executed button + await t.click(workbenchPage.redisStackTutorialsButton); + await t.click(workbenchPage.timeSeriesLink); + // Verify that when user runs auto-executed commands, selected group mode setting is considered + await t.click(workbenchPage.groupMode); + // Verify that when user runs auto-executed commands, selected raw mode setting is considered + await t.click(workbenchPage.rawModeBtn); + await t.click(workbenchPage.redisStackTimeSeriesLoadMorePoints); + // Verify that user can see auto-executed command result in command history + const regExp = new RegExp('66[1-9] Command(s) - \d+ success, \d+ error(s)'); + await t.expect(workbenchPage.queryCardCommand.textContent).match(regExp, 'Not valid summary for group mode'); + await t.expect(workbenchPage.commandExecutionDateAndTime.find('span').withExactText('-r')).ok('Not valid summary for raw mode'); +});