From 1afdd7ac1d12c067ba2fe1576b1b4818cb61cf32 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Wed, 3 Feb 2021 10:45:07 -0800 Subject: [PATCH 01/19] Ace editor POC --- awx/ui_next/package-lock.json | 30 ++++++++- awx/ui_next/package.json | 2 + .../CodeMirrorInput/CodeMirrorInput.jsx | 66 +++++++++++++------ 3 files changed, 75 insertions(+), 23 deletions(-) diff --git a/awx/ui_next/package-lock.json b/awx/ui_next/package-lock.json index d4ed859ea9..8fe91ad419 100644 --- a/awx/ui_next/package-lock.json +++ b/awx/ui_next/package-lock.json @@ -2786,6 +2786,11 @@ "negotiator": "0.6.2" } }, + "ace-builds": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.12.tgz", + "integrity": "sha512-G+chJctFPiiLGvs3+/Mly3apXTcfgE45dT5yp12BcWZ1kUs+gm0qd3/fv4gsz6fVag4mM0moHVpjHDIgph6Psg==" + }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -6230,6 +6235,11 @@ } } }, + "diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "diff-sequences": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", @@ -11295,11 +11305,15 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, "lodash.memoize": { "version": "4.1.2", @@ -14175,6 +14189,18 @@ "prop-types": "^15.6.2" } }, + "react-ace": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-9.3.0.tgz", + "integrity": "sha512-RWPDwVobLvyD0wDoHHQqEnn9pNQBhMnmo6LmRACkaXxAg3UQZpse6x9JFLC5EXyWby+P3uolIlQPct4NFEBPNg==", + "requires": { + "ace-builds": "^1.4.6", + "diff-match-patch": "^1.0.4", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.7.2" + } + }, "react-app-polyfill": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz", diff --git a/awx/ui_next/package.json b/awx/ui_next/package.json index 24371abfc7..233c0e0ac2 100644 --- a/awx/ui_next/package.json +++ b/awx/ui_next/package.json @@ -11,6 +11,7 @@ "@patternfly/react-core": "^4.90.2", "@patternfly/react-icons": "4.7.22", "@patternfly/react-table": "^4.19.15", + "ace-builds": "^1.4.12", "ansi-to-html": "^0.6.11", "axios": "^0.21.1", "codemirror": "^5.47.0", @@ -22,6 +23,7 @@ "js-yaml": "^3.13.1", "prop-types": "^15.6.2", "react": "^16.13.1", + "react-ace": "^9.3.0", "react-codemirror2": "^6.0.0", "react-dom": "^16.13.1", "react-router-dom": "^5.1.2", diff --git a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx b/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx index a07b6feca5..a3d5c77324 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx @@ -1,16 +1,25 @@ import React, { useState, useEffect } from 'react'; import { oneOf, bool, number, string, func } from 'prop-types'; +import AceEditor from 'react-ace'; +// import * as ace from 'ace-builds'; +import 'ace-builds/src-noconflict/mode-json'; +import 'ace-builds/src-noconflict/mode-javascript'; +import 'ace-builds/src-noconflict/mode-yaml'; +import 'ace-builds/src-noconflict/theme-github'; import { Controlled as ReactCodeMirror } from 'react-codemirror2'; import styled from 'styled-components'; -import 'codemirror/mode/javascript/javascript'; -import 'codemirror/mode/yaml/yaml'; -import 'codemirror/mode/jinja2/jinja2'; -import 'codemirror/lib/codemirror.css'; -import 'codemirror/addon/display/placeholder'; +// import 'codemirror/mode/javascript/javascript'; +// import 'codemirror/mode/yaml/yaml'; +// import 'codemirror/mode/jinja2/jinja2'; +// import 'codemirror/lib/codemirror.css'; +// import 'codemirror/addon/display/placeholder'; +// require("ace/edit_session").EditSession.prototype.$useWorker=false const LINE_HEIGHT = 24; const PADDING = 12; +// ace.config.set('basePath', 'path'); + const CodeMirror = styled(ReactCodeMirror)` && { height: initial; @@ -95,22 +104,37 @@ function CodeMirrorInput({ } return ( - onChange(val)} - mode={mode} - hasErrors={hasErrors} - options={{ - smartIndent: false, - lineNumbers: true, - lineWrapping: true, - placeholder, - readOnly, - }} - fullHeight={fullHeight} - rows={rows} - /> + <> + {/* onChange(val)} + mode={mode} + hasErrors={hasErrors} + options={{ + smartIndent: false, + lineNumbers: true, + lineWrapping: true, + placeholder, + readOnly, + }} + fullHeight={fullHeight} + rows={rows} + /> */} + + ); } CodeMirrorInput.propTypes = { From 4e9c6a956d0bca549e649b79c6c42f93f4b49a40 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Fri, 5 Feb 2021 11:58:23 -0800 Subject: [PATCH 02/19] add code editor focus/blur keyboard controls --- .../CodeMirrorInput/CodeMirrorInput.jsx | 86 +++++++++++++------ 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx b/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx index a3d5c77324..f32d9d7d32 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; import { oneOf, bool, number, string, func } from 'prop-types'; import AceEditor from 'react-ace'; // import * as ace from 'ace-builds'; @@ -79,6 +79,7 @@ const CodeMirror = styled(ReactCodeMirror)` `; function CodeMirrorInput({ + id, value, onChange, mode, @@ -89,19 +90,33 @@ function CodeMirrorInput({ className, placeholder, }) { - // Workaround for CodeMirror bug: If CodeMirror renders in a modal on the - // modal's initial render, it appears as an empty box due to mis-calculated - // element height. Forcing an initial render before mounting - // fixes this. - const [isInitialized, setIsInitialized] = useState(false); - useEffect(() => { - if (!isInitialized) { - setIsInitialized(true); + const wrapper = useRef(null); + const editor = useRef(null); + + useEffect(function removeTextareaTabIndex() { + const editorInput = editor.current.refEditor?.querySelector('textarea'); + if (editorInput) { + editorInput.tabIndex = -1; } - }, [isInitialized]); - if (!isInitialized) { - return
; - } + }, []); + + const listen = useCallback(event => { + if (wrapper.current === document.activeElement && event.key === 'Enter') { + const editorInput = editor.current.refEditor?.querySelector('textarea'); + if (editorInput) { + editorInput.focus(); + } + } + }, []); + + useEffect(function addKeyEventListeners() { + const wrapperEl = wrapper.current; + wrapperEl.addEventListener('keydown', listen); + + return () => { + wrapperEl.removeEventListener('keydown', listen); + }; + }); return ( <> @@ -121,19 +136,38 @@ function CodeMirrorInput({ fullHeight={fullHeight} rows={rows} /> */} - +
+ { + wrapper.current.focus(); + }, + }, + { + name: 'tab escape', + bindKey: { win: 'Shift-Tab', mac: 'Shift-Tab' }, + exec: () => { + wrapper.current.focus(); + }, + }, + ]} + ref={editor} + /> +
); } From 5c38011ad562aae93d9aef3084251a526452ff9f Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Mon, 8 Feb 2021 11:47:58 -0800 Subject: [PATCH 03/19] styling Ace CodeEditor --- .../CodeMirrorInput/CodeMirrorInput.jsx | 161 +++++++----------- 1 file changed, 58 insertions(+), 103 deletions(-) diff --git a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx b/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx index f32d9d7d32..e560e18776 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx @@ -1,35 +1,24 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react'; +import React, { useEffect, useRef, useCallback } from 'react'; import { oneOf, bool, number, string, func } from 'prop-types'; -import AceEditor from 'react-ace'; -// import * as ace from 'ace-builds'; +import ReactAceEditor from 'react-ace'; import 'ace-builds/src-noconflict/mode-json'; import 'ace-builds/src-noconflict/mode-javascript'; import 'ace-builds/src-noconflict/mode-yaml'; +import 'ace-builds/src-noconflict/mode-django'; import 'ace-builds/src-noconflict/theme-github'; -import { Controlled as ReactCodeMirror } from 'react-codemirror2'; import styled from 'styled-components'; -// import 'codemirror/mode/javascript/javascript'; -// import 'codemirror/mode/yaml/yaml'; -// import 'codemirror/mode/jinja2/jinja2'; -// import 'codemirror/lib/codemirror.css'; -// import 'codemirror/addon/display/placeholder'; -// require("ace/edit_session").EditSession.prototype.$useWorker=false const LINE_HEIGHT = 24; const PADDING = 12; -// ace.config.set('basePath', 'path'); +const AceEditor = styled(ReactAceEditor)` + font-family: var(--pf-global--FontFamily--monospace); + max-height: 90vh; -const CodeMirror = styled(ReactCodeMirror)` - && { - height: initial; - padding: 0; - } - - & > .CodeMirror { - height: ${props => - props.fullHeight ? 'auto' : `${props.rows * LINE_HEIGHT + PADDING}px`}; - font-family: var(--pf-global--FontFamily--monospace); + & .ace_gutter, + & .ace_scroller { + padding-top: 4px; + padding-bottom: 4px; } ${props => @@ -43,42 +32,9 @@ const CodeMirror = styled(ReactCodeMirror)` background: var(--pf-c-form-control--invalid--Background); border-bottom-width: var(--pf-c-form-control--invalid--BorderBottomWidth); }`} - - ${props => - props.options && - props.options.readOnly && - ` - &, - &:hover { - --pf-c-form-control--BorderBottomColor: var(--pf-global--BorderColor--300); - } - - .CodeMirror-cursors { - display: none; - } - - .CodeMirror-lines { - cursor: default; - } - - .CodeMirror-scroll { - background-color: var(--pf-c-form-control--disabled--BackgroundColor); - } - `} - ${props => - props.options && - props.options.placeholder && - ` - .CodeMirror-empty { - pre.CodeMirror-placeholder { - color: var(--pf-c-form-control--placeholder--Color); - height: 100% !important; - } - } - `} `; -function CodeMirrorInput({ +function CodeInput({ id, value, onChange, @@ -88,7 +44,6 @@ function CodeMirrorInput({ rows, fullHeight, className, - placeholder, }) { const wrapper = useRef(null); const editor = useRef(null); @@ -101,11 +56,16 @@ function CodeMirrorInput({ }, []); const listen = useCallback(event => { - if (wrapper.current === document.activeElement && event.key === 'Enter') { + if ( + (wrapper.current === document.activeElement && event.key === 'Enter') || + event.key === ' ' + ) { const editorInput = editor.current.refEditor?.querySelector('textarea'); - if (editorInput) { - editorInput.focus(); + if (!editorInput) { + return; } + editorInput.focus(); + event.stopPropagation(); } }, []); @@ -118,57 +78,52 @@ function CodeMirrorInput({ }; }); + const aceModes = { + javascript: 'json', + yaml: 'yaml', + jinja2: 'django', + }; + + const numRows = fullHeight ? value.split('\n').length : rows; + return ( - <> - {/* + onChange(val)} - mode={mode} + name={id || 'code-editor'} + editorProps={{ $blockScrolling: true }} + fontSize={16} + width="100%" + height={`${numRows * LINE_HEIGHT + PADDING}px`} hasErrors={hasErrors} - options={{ - smartIndent: false, - lineNumbers: true, - lineWrapping: true, - placeholder, + setOptions={{ readOnly, + useWorker: false, }} - fullHeight={fullHeight} - rows={rows} - /> */} -
- { - wrapper.current.focus(); - }, + commands={[ + { + name: 'escape', + bindKey: { win: 'Esc', mac: 'Esc' }, + exec: () => { + wrapper.current.focus(); }, - { - name: 'tab escape', - bindKey: { win: 'Shift-Tab', mac: 'Shift-Tab' }, - exec: () => { - wrapper.current.focus(); - }, + }, + { + name: 'tab escape', + bindKey: { win: 'Shift-Tab', mac: 'Shift-Tab' }, + exec: () => { + wrapper.current.focus(); }, - ]} - ref={editor} - /> -
- + }, + ]} + ref={editor} + /> +
); } CodeMirrorInput.propTypes = { @@ -190,4 +145,4 @@ CodeMirrorInput.defaultProps = { className: '', }; -export default CodeMirrorInput; +export default CodeInput; From 070c67ffe8a20ef0a85830af77ee333aae3044d8 Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Wed, 10 Feb 2021 11:14:04 -0800 Subject: [PATCH 04/19] rename CodeMirror to CodeEditor --- .../AdHocCommands/AdHocDetailsStep.jsx | 2 +- .../CodeEditor.jsx} | 13 ++++--- .../components/CodeEditor/CodeEditor.test.jsx | 30 +++++++++++++++ .../CodeEditorField.jsx} | 12 +++--- .../VariablesDetail.jsx | 4 +- .../VariablesDetail.test.jsx | 20 +++++----- .../VariablesField.jsx | 4 +- .../VariablesField.test.jsx | 18 ++++----- .../VariablesInput.jsx | 4 +- .../constants.js | 0 .../{CodeMirrorInput => CodeEditor}/index.js | 6 +-- .../CodeMirrorInput/CodeMirrorInput.test.jsx | 30 --------------- .../src/components/DetailList/CodeDetail.jsx | 4 +- .../src/components/HostForm/HostForm.jsx | 2 +- .../LaunchPrompt/steps/OtherPromptsStep.jsx | 2 +- .../components/PromptDetail/PromptDetail.jsx | 2 +- .../PromptInventorySourceDetail.jsx | 2 +- .../PromptDetail/PromptJobTemplateDetail.jsx | 2 +- .../PromptWFJobTemplateDetail.jsx | 2 +- .../ScheduleDetail/ScheduleDetail.jsx | 2 +- .../ActivityStreamDetailButton.jsx | 2 +- .../CredentialDetail/CredentialDetail.jsx | 4 +- .../CredentialDetail.test.jsx | 2 +- .../CredentialTypeDetails.jsx | 2 +- .../shared/CredentialTypeForm.jsx | 2 +- .../screens/Host/HostDetail/HostDetail.jsx | 2 +- .../src/screens/Host/HostFacts/HostFacts.jsx | 2 +- .../ContainerGroupDetails.jsx | 2 +- .../shared/ContainerGroupForm.jsx | 2 +- .../InventoryDetail/InventoryDetail.jsx | 2 +- .../InventoryGroupDetail.jsx | 2 +- .../InventoryHostDetail.jsx | 2 +- .../InventoryHostFacts/InventoryHostFacts.jsx | 2 +- .../InventorySourceDetail.jsx | 2 +- .../SmartInventoryDetail.jsx | 2 +- .../SmartInventoryHostDetail.jsx | 2 +- .../Inventory/shared/InventoryForm.jsx | 2 +- .../Inventory/shared/InventoryGroupForm.jsx | 2 +- .../InventorySourceSubForms/SharedFields.jsx | 2 +- .../Inventory/shared/SmartInventoryForm.jsx | 2 +- .../src/screens/Job/JobDetail/JobDetail.jsx | 2 +- .../screens/Job/JobOutput/HostEventModal.jsx | 28 +++++++------- .../Job/JobOutput/HostEventModal.test.jsx | 38 +++++++++---------- .../shared/CustomMessagesSubForm.jsx | 30 +++++++-------- .../shared/TypeInputsSubForm.jsx | 4 +- .../GitHub/GitHubEdit/GitHubEdit.test.jsx | 2 +- .../GitHubEnterpriseEdit.test.jsx | 2 +- .../GitHubEnterpriseOrgEdit.test.jsx | 4 +- .../GitHubEnterpriseTeamEdit.test.jsx | 4 +- .../GitHubOrgEdit/GitHubOrgEdit.test.jsx | 2 +- .../GitHubTeamEdit/GitHubTeamEdit.test.jsx | 2 +- .../GoogleOAuth2Edit.test.jsx | 2 +- .../Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx | 2 +- .../screens/Setting/shared/SharedFields.jsx | 4 +- .../Setting/shared/SharedFields.test.jsx | 8 ++-- .../Setting/shared/settingTestUtils.js | 2 +- .../JobTemplateDetail/JobTemplateDetail.jsx | 2 +- .../WorkflowJobTemplateDetail.jsx | 2 +- .../Template/shared/JobTemplateForm.jsx | 2 +- .../shared/WorkflowJobTemplateForm.jsx | 2 +- 60 files changed, 170 insertions(+), 177 deletions(-) rename awx/ui_next/src/components/{CodeMirrorInput/CodeMirrorInput.jsx => CodeEditor/CodeEditor.jsx} (94%) create mode 100644 awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx rename awx/ui_next/src/components/{CodeMirrorInput/CodeMirrorField.jsx => CodeEditor/CodeEditorField.jsx} (86%) rename awx/ui_next/src/components/{CodeMirrorInput => CodeEditor}/VariablesDetail.jsx (98%) rename awx/ui_next/src/components/{CodeMirrorInput => CodeEditor}/VariablesDetail.test.jsx (79%) rename awx/ui_next/src/components/{CodeMirrorInput => CodeEditor}/VariablesField.jsx (97%) rename awx/ui_next/src/components/{CodeMirrorInput => CodeEditor}/VariablesField.test.jsx (87%) rename awx/ui_next/src/components/{CodeMirrorInput => CodeEditor}/VariablesInput.jsx (97%) rename awx/ui_next/src/components/{CodeMirrorInput => CodeEditor}/constants.js (100%) rename awx/ui_next/src/components/{CodeMirrorInput => CodeEditor}/index.js (56%) delete mode 100644 awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.test.jsx diff --git a/awx/ui_next/src/components/AdHocCommands/AdHocDetailsStep.jsx b/awx/ui_next/src/components/AdHocCommands/AdHocDetailsStep.jsx index 46a3617d7e..229024bfbe 100644 --- a/awx/ui_next/src/components/AdHocCommands/AdHocDetailsStep.jsx +++ b/awx/ui_next/src/components/AdHocCommands/AdHocDetailsStep.jsx @@ -10,7 +10,7 @@ import styled from 'styled-components'; import { BrandName } from '../../variables'; import AnsibleSelect from '../AnsibleSelect'; import FormField from '../FormField'; -import { VariablesField } from '../CodeMirrorInput'; +import { VariablesField } from '../CodeEditor'; import { FormColumnLayout, FormFullWidthLayout, diff --git a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx similarity index 94% rename from awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx rename to awx/ui_next/src/components/CodeEditor/CodeEditor.jsx index e560e18776..7db780cb75 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef, useCallback } from 'react'; import { oneOf, bool, number, string, func } from 'prop-types'; -import ReactAceEditor from 'react-ace'; +import ReactAce from 'react-ace'; import 'ace-builds/src-noconflict/mode-json'; import 'ace-builds/src-noconflict/mode-javascript'; import 'ace-builds/src-noconflict/mode-yaml'; @@ -11,7 +11,7 @@ import styled from 'styled-components'; const LINE_HEIGHT = 24; const PADDING = 12; -const AceEditor = styled(ReactAceEditor)` +const AceEditor = styled(ReactAce)` font-family: var(--pf-global--FontFamily--monospace); max-height: 90vh; @@ -33,8 +33,9 @@ const AceEditor = styled(ReactAceEditor)` border-bottom-width: var(--pf-c-form-control--invalid--BorderBottomWidth); }`} `; +AceEditor.displayName = 'AceEditor'; -function CodeInput({ +function CodeEditor({ id, value, onChange, @@ -126,7 +127,7 @@ function CodeInput({ ); } -CodeMirrorInput.propTypes = { +CodeEditor.propTypes = { value: string.isRequired, onChange: func, mode: oneOf(['javascript', 'yaml', 'jinja2']).isRequired, @@ -136,7 +137,7 @@ CodeMirrorInput.propTypes = { rows: number, className: string, }; -CodeMirrorInput.defaultProps = { +CodeEditor.defaultProps = { readOnly: false, onChange: () => {}, rows: 6, @@ -145,4 +146,4 @@ CodeMirrorInput.defaultProps = { className: '', }; -export default CodeInput; +export default CodeEditor; diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx new file mode 100644 index 0000000000..2da3604574 --- /dev/null +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import CodeEditor from './CodeEditor'; + +describe('CodeEditor', () => { + beforeEach(() => { + document.body.createTextRange = jest.fn(); + }); + + it('should trigger onChange prop', () => { + const onChange = jest.fn(); + const wrapper = mount( + + ); + const aceEditor = wrapper.find('AceEditor'); + expect(aceEditor.prop('mode')).toEqual('yaml'); + expect(aceEditor.prop('setOptions').readOnly).toEqual(false); + aceEditor.prop('onChange')('newvalue'); + expect(onChange).toHaveBeenCalledWith('newvalue'); + }); + + it('should render in read only mode', () => { + const onChange = jest.fn(); + const wrapper = mount( + + ); + const aceEditor = wrapper.find('AceEditor'); + expect(aceEditor.prop('setOptions').readOnly).toEqual(true); + }); +}); diff --git a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorField.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditorField.jsx similarity index 86% rename from awx/ui_next/src/components/CodeMirrorInput/CodeMirrorField.jsx rename to awx/ui_next/src/components/CodeEditor/CodeEditorField.jsx index 35ebf32721..61dec6a04f 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorField.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditorField.jsx @@ -11,10 +11,10 @@ import { } from 'prop-types'; import { useField } from 'formik'; import { FormGroup } from '@patternfly/react-core'; -import CodeMirrorInput from './CodeMirrorInput'; +import CodeEditor from './CodeEditor'; import Popover from '../Popover'; -function CodeMirrorField({ +function CodeEditorField({ id, name, label, @@ -39,7 +39,7 @@ function CodeMirrorField({ label={label} labelIcon={} > - ); } -CodeMirrorField.propTypes = { +CodeEditorField.propTypes = { helperText: string, id: string.isRequired, name: string.isRequired, @@ -63,7 +63,7 @@ CodeMirrorField.propTypes = { rows: number, }; -CodeMirrorField.defaultProps = { +CodeEditorField.defaultProps = { helperText: '', validate: () => {}, isRequired: false, @@ -71,4 +71,4 @@ CodeMirrorField.defaultProps = { rows: 5, }; -export default CodeMirrorField; +export default CodeEditorField; diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx b/awx/ui_next/src/components/CodeEditor/VariablesDetail.jsx similarity index 98% rename from awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx rename to awx/ui_next/src/components/CodeEditor/VariablesDetail.jsx index b5501fb0b7..f5cfd91373 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx +++ b/awx/ui_next/src/components/CodeEditor/VariablesDetail.jsx @@ -12,7 +12,7 @@ import { isJsonObject, isJsonString, } from '../../util/yaml'; -import CodeMirrorInput from './CodeMirrorInput'; +import CodeEditor from './CodeEditor'; import { JSON_MODE, YAML_MODE } from './constants'; function getValueAsMode(value, mode) { @@ -99,7 +99,7 @@ function VariablesDetail({ dataCy, helpText, value, label, rows, fullHeight }) { fullWidth css="grid-column: 1 / -1; margin-top: -20px" > - ', () => { - test('should render readonly CodeMirrorInput', () => { + test('should render readonly CodeEditor', () => { const wrapper = mountWithContexts( ); - const input = wrapper.find('VariablesDetail___StyledCodeMirrorInput'); + const input = wrapper.find('VariablesDetail___StyledCodeEditor'); expect(input).toHaveLength(1); expect(input.prop('mode')).toEqual('yaml'); expect(input.prop('value')).toEqual('---foo: bar'); @@ -21,7 +21,7 @@ describe('', () => { const wrapper = mountWithContexts( ); - const input = wrapper.find('VariablesDetail___StyledCodeMirrorInput'); + const input = wrapper.find('VariablesDetail___StyledCodeEditor'); expect(input).toHaveLength(1); expect(input.prop('mode')).toEqual('javascript'); expect(input.prop('value')).toEqual('{"foo": "bar"}'); @@ -32,12 +32,12 @@ describe('', () => { ); wrapper.find('MultiButtonToggle').invoke('onChange')('javascript'); - const input = wrapper.find('VariablesDetail___StyledCodeMirrorInput'); + const input = wrapper.find('VariablesDetail___StyledCodeEditor'); expect(input.prop('mode')).toEqual('javascript'); expect(input.prop('value')).toEqual('{\n "foo": "bar"\n}'); wrapper.find('MultiButtonToggle').invoke('onChange')('yaml'); - const input2 = wrapper.find('VariablesDetail___StyledCodeMirrorInput'); + const input2 = wrapper.find('VariablesDetail___StyledCodeEditor'); expect(input2.prop('mode')).toEqual('yaml'); expect(input2.prop('value')).toEqual('foo: bar\n'); }); @@ -46,9 +46,7 @@ describe('', () => { const wrapper = mountWithContexts( ); - expect(wrapper.find('VariablesDetail___StyledCodeMirrorInput').length).toBe( - 1 - ); + expect(wrapper.find('VariablesDetail___StyledCodeEditor').length).toBe(1); expect(wrapper.find('div.pf-c-form__label').text()).toBe('Variables'); }); @@ -63,7 +61,7 @@ describe('', () => { value: '---bar: baz', }); wrapper.update(); - const input = wrapper.find('VariablesDetail___StyledCodeMirrorInput'); + const input = wrapper.find('VariablesDetail___StyledCodeEditor'); expect(input.prop('mode')).toEqual('javascript'); expect(input.prop('value')).toEqual('{\n "bar": "baz"\n}'); }); @@ -72,7 +70,7 @@ describe('', () => { const wrapper = mountWithContexts( ); - const input = wrapper.find('VariablesDetail___StyledCodeMirrorInput'); + const input = wrapper.find('VariablesDetail___StyledCodeEditor'); expect(input.prop('value')).toEqual('---'); }); @@ -84,7 +82,7 @@ describe('', () => { wrapper.find('MultiButtonToggle').invoke('onChange')('javascript'); }); wrapper.setProps({ value: '' }); - const input = wrapper.find('VariablesDetail___StyledCodeMirrorInput'); + const input = wrapper.find('VariablesDetail___StyledCodeEditor'); expect(input.prop('value')).toEqual('{}'); }); }); diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx b/awx/ui_next/src/components/CodeEditor/VariablesField.jsx similarity index 97% rename from awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx rename to awx/ui_next/src/components/CodeEditor/VariablesField.jsx index 1db21fdf04..8fbf1e4bfa 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx +++ b/awx/ui_next/src/components/CodeEditor/VariablesField.jsx @@ -8,7 +8,7 @@ import { Split, SplitItem } from '@patternfly/react-core'; import { CheckboxField } from '../FormField'; import MultiButtonToggle from '../MultiButtonToggle'; import { yamlToJson, jsonToYaml, isJsonString } from '../../util/yaml'; -import CodeMirrorInput from './CodeMirrorInput'; +import CodeEditor from './CodeEditor'; import Popover from '../Popover'; import { JSON_MODE, YAML_MODE } from './constants'; @@ -76,7 +76,7 @@ function VariablesField({ /> )} - { document.body.createTextRange = jest.fn(); }); - it('should render code mirror input', () => { + it('should render code editor', () => { const value = '---\n'; const wrapper = mountWithContexts( @@ -18,8 +18,8 @@ describe('VariablesField', () => { )} ); - const codemirror = wrapper.find('Controlled'); - expect(codemirror.prop('value')).toEqual(value); + const codeEditor = wrapper.find('CodeEditor'); + expect(codeEditor.prop('value')).toEqual(value); }); it('should render yaml/json toggles', async () => { @@ -39,7 +39,7 @@ describe('VariablesField', () => { buttons.at(1).simulate('click'); }); wrapper.update(); - expect(wrapper.find('CodeMirrorInput').prop('mode')).toEqual('javascript'); + expect(wrapper.find('CodeEditor').prop('mode')).toEqual('javascript'); const buttons2 = wrapper.find('Button'); expect(buttons2.at(0).prop('variant')).toEqual('secondary'); expect(buttons2.at(1).prop('variant')).toEqual('primary'); @@ -47,7 +47,7 @@ describe('VariablesField', () => { buttons2.at(0).simulate('click'); }); wrapper.update(); - expect(wrapper.find('CodeMirrorInput').prop('mode')).toEqual('yaml'); + expect(wrapper.find('CodeEditor').prop('mode')).toEqual('yaml'); }); it('should set Formik error if yaml is invalid', async () => { @@ -65,7 +65,7 @@ describe('VariablesField', () => { .simulate('click'); wrapper.update(); - const field = wrapper.find('CodeMirrorInput'); + const field = wrapper.find('CodeEditor'); expect(field.prop('hasErrors')).toEqual(true); expect(wrapper.find('.pf-m-error')).toHaveLength(1); }); @@ -102,9 +102,7 @@ describe('VariablesField', () => { ); await act(async () => { - wrapper.find('CodeMirrorInput').invoke('onChange')( - '---\nnewval: changed' - ); + wrapper.find('CodeEditor').invoke('onChange')('---\nnewval: changed'); wrapper.find('form').simulate('submit'); }); @@ -129,6 +127,6 @@ describe('VariablesField', () => { ); - expect(wrapper.find('CodeMirrorInput').prop('mode')).toEqual('javascript'); + expect(wrapper.find('CodeEditor').prop('mode')).toEqual('javascript'); }); }); diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx b/awx/ui_next/src/components/CodeEditor/VariablesInput.jsx similarity index 97% rename from awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx rename to awx/ui_next/src/components/CodeEditor/VariablesInput.jsx index a43962bd76..354e263b69 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx +++ b/awx/ui_next/src/components/CodeEditor/VariablesInput.jsx @@ -4,7 +4,7 @@ import { Split, SplitItem } from '@patternfly/react-core'; import styled from 'styled-components'; import { yamlToJson, jsonToYaml, isJsonString } from '../../util/yaml'; import MultiButtonToggle from '../MultiButtonToggle'; -import CodeMirrorInput from './CodeMirrorInput'; +import CodeEditor from './CodeEditor'; import { JSON_MODE, YAML_MODE } from './constants'; function formatJson(jsonString) { @@ -63,7 +63,7 @@ function VariablesInput(props) { /> - { - beforeEach(() => { - document.body.createTextRange = jest.fn(); - }); - - it('should trigger onChange prop', () => { - const onChange = jest.fn(); - const wrapper = mount( - - ); - const codemirror = wrapper.find('Controlled'); - expect(codemirror.prop('mode')).toEqual('yaml'); - expect(codemirror.prop('options').readOnly).toEqual(false); - codemirror.prop('onBeforeChange')(null, null, 'newvalue'); - expect(onChange).toHaveBeenCalledWith('newvalue'); - }); - - it('should render in read only mode', () => { - const onChange = jest.fn(); - const wrapper = mount( - - ); - const codemirror = wrapper.find('Controlled'); - expect(codemirror.prop('options').readOnly).toEqual(true); - }); -}); diff --git a/awx/ui_next/src/components/DetailList/CodeDetail.jsx b/awx/ui_next/src/components/DetailList/CodeDetail.jsx index 6d78ef043a..08c935b18e 100644 --- a/awx/ui_next/src/components/DetailList/CodeDetail.jsx +++ b/awx/ui_next/src/components/DetailList/CodeDetail.jsx @@ -11,7 +11,7 @@ import { } from 'prop-types'; import { TextListItemVariants } from '@patternfly/react-core'; import { DetailName, DetailValue } from './Detail'; -import CodeMirrorInput from '../CodeMirrorInput'; +import CodeEditor from '../CodeEditor'; import Popover from '../Popover'; function CodeDetail({ @@ -52,7 +52,7 @@ function CodeDetail({ css="grid-column: 1 / -1; margin-top: -20px" data-cy={valueCy} > - ', () => { expect(sshKeyUnlockDetail.length).toBe(1); expect(sshKeyUnlockDetail.find('CredentialChip').length).toBe(1); expect( - wrapper.find('CodeMirrorInput#credential-ssh_key_unlock-metadata').props() + wrapper.find('CodeEditor#credential-ssh_key_unlock-metadata').props() .value ).toBe(JSON.stringify(mockInputSource.metadata, null, 2)); expectDetailToMatch( diff --git a/awx/ui_next/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.jsx b/awx/ui_next/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.jsx index 1b99c16867..0cdf678d67 100644 --- a/awx/ui_next/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.jsx +++ b/awx/ui_next/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.jsx @@ -4,7 +4,7 @@ import { t } from '@lingui/macro'; import { Link, useHistory } from 'react-router-dom'; import { Button } from '@patternfly/react-core'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; import AlertModal from '../../../components/AlertModal'; import { CardBody, CardActionsRow } from '../../../components/Card'; import DeleteButton from '../../../components/DeleteButton'; diff --git a/awx/ui_next/src/screens/CredentialType/shared/CredentialTypeForm.jsx b/awx/ui_next/src/screens/CredentialType/shared/CredentialTypeForm.jsx index 8bb5f113ef..a5e9c540a9 100644 --- a/awx/ui_next/src/screens/CredentialType/shared/CredentialTypeForm.jsx +++ b/awx/ui_next/src/screens/CredentialType/shared/CredentialTypeForm.jsx @@ -5,7 +5,7 @@ import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; import { Form } from '@patternfly/react-core'; -import { VariablesField } from '../../../components/CodeMirrorInput'; +import { VariablesField } from '../../../components/CodeEditor'; import FormField, { FormSubmitError } from '../../../components/FormField'; import FormActionGroup from '../../../components/FormActionGroup'; import { required } from '../../../util/validators'; diff --git a/awx/ui_next/src/screens/Host/HostDetail/HostDetail.jsx b/awx/ui_next/src/screens/Host/HostDetail/HostDetail.jsx index ae140d3786..aecb17a53e 100644 --- a/awx/ui_next/src/screens/Host/HostDetail/HostDetail.jsx +++ b/awx/ui_next/src/screens/Host/HostDetail/HostDetail.jsx @@ -13,7 +13,7 @@ import { Detail, UserDateDetail, } from '../../../components/DetailList'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; import Sparkline from '../../../components/Sparkline'; import DeleteButton from '../../../components/DeleteButton'; import { HostsAPI } from '../../../api'; diff --git a/awx/ui_next/src/screens/Host/HostFacts/HostFacts.jsx b/awx/ui_next/src/screens/Host/HostFacts/HostFacts.jsx index 1a107ab28e..f33c989f5b 100644 --- a/awx/ui_next/src/screens/Host/HostFacts/HostFacts.jsx +++ b/awx/ui_next/src/screens/Host/HostFacts/HostFacts.jsx @@ -4,7 +4,7 @@ import { t } from '@lingui/macro'; import { Host } from '../../../types'; import { CardBody } from '../../../components/Card'; import { DetailList } from '../../../components/DetailList'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; import ContentError from '../../../components/ContentError'; import ContentLoading from '../../../components/ContentLoading'; import useRequest from '../../../util/useRequest'; diff --git a/awx/ui_next/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.jsx b/awx/ui_next/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.jsx index 6cce625c78..6cf0bedf87 100644 --- a/awx/ui_next/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.jsx @@ -4,7 +4,7 @@ import { t } from '@lingui/macro'; import { Link, useHistory } from 'react-router-dom'; import { Button, Label } from '@patternfly/react-core'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; import AlertModal from '../../../components/AlertModal'; import { CardBody, CardActionsRow } from '../../../components/Card'; import DeleteButton from '../../../components/DeleteButton'; diff --git a/awx/ui_next/src/screens/InstanceGroup/shared/ContainerGroupForm.jsx b/awx/ui_next/src/screens/InstanceGroup/shared/ContainerGroupForm.jsx index e41ab0a1b0..719c907d1b 100644 --- a/awx/ui_next/src/screens/InstanceGroup/shared/ContainerGroupForm.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/shared/ContainerGroupForm.jsx @@ -19,7 +19,7 @@ import { SubFormLayout, } from '../../../components/FormLayout'; import CredentialLookup from '../../../components/Lookup/CredentialLookup'; -import { VariablesField } from '../../../components/CodeMirrorInput'; +import { VariablesField } from '../../../components/CodeEditor'; function ContainerGroupFormFields({ i18n, instanceGroup }) { const { setFieldValue } = useFormikContext(); diff --git a/awx/ui_next/src/screens/Inventory/InventoryDetail/InventoryDetail.jsx b/awx/ui_next/src/screens/Inventory/InventoryDetail/InventoryDetail.jsx index 09dfe7ed54..ec441d7390 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryDetail/InventoryDetail.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryDetail/InventoryDetail.jsx @@ -10,7 +10,7 @@ import { Detail, UserDateDetail, } from '../../../components/DetailList'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; import DeleteButton from '../../../components/DeleteButton'; import ErrorDetail from '../../../components/ErrorDetail'; import ContentError from '../../../components/ContentError'; diff --git a/awx/ui_next/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.jsx b/awx/ui_next/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.jsx index f8ce4879c8..8a4fa92fdf 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.jsx @@ -4,7 +4,7 @@ import { t } from '@lingui/macro'; import { Button } from '@patternfly/react-core'; import { withI18n } from '@lingui/react'; import { useHistory, useParams } from 'react-router-dom'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; import { CardBody, CardActionsRow } from '../../../components/Card'; import ErrorDetail from '../../../components/ErrorDetail'; import AlertModal from '../../../components/AlertModal'; diff --git a/awx/ui_next/src/screens/Inventory/InventoryHostDetail/InventoryHostDetail.jsx b/awx/ui_next/src/screens/Inventory/InventoryHostDetail/InventoryHostDetail.jsx index a2b48fecd1..c90c20885f 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryHostDetail/InventoryHostDetail.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryHostDetail/InventoryHostDetail.jsx @@ -13,7 +13,7 @@ import { Detail, UserDateDetail, } from '../../../components/DetailList'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; import Sparkline from '../../../components/Sparkline'; import DeleteButton from '../../../components/DeleteButton'; import { HostsAPI } from '../../../api'; diff --git a/awx/ui_next/src/screens/Inventory/InventoryHostFacts/InventoryHostFacts.jsx b/awx/ui_next/src/screens/Inventory/InventoryHostFacts/InventoryHostFacts.jsx index 2f24b4cb38..6bffd37ba4 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryHostFacts/InventoryHostFacts.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryHostFacts/InventoryHostFacts.jsx @@ -4,7 +4,7 @@ import { t } from '@lingui/macro'; import { Host } from '../../../types'; import { CardBody } from '../../../components/Card'; import { DetailList } from '../../../components/DetailList'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; import ContentError from '../../../components/ContentError'; import ContentLoading from '../../../components/ContentLoading'; import useRequest from '../../../util/useRequest'; diff --git a/awx/ui_next/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.jsx b/awx/ui_next/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.jsx index 4383998994..efc2a8ff73 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.jsx @@ -6,7 +6,7 @@ import { t } from '@lingui/macro'; import { Button, List, ListItem } from '@patternfly/react-core'; import AlertModal from '../../../components/AlertModal'; import { CardBody, CardActionsRow } from '../../../components/Card'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; import ContentError from '../../../components/ContentError'; import ContentLoading from '../../../components/ContentLoading'; import CredentialChip from '../../../components/CredentialChip'; diff --git a/awx/ui_next/src/screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.jsx b/awx/ui_next/src/screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.jsx index 8aee3165e5..fa29c0ad61 100644 --- a/awx/ui_next/src/screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.jsx +++ b/awx/ui_next/src/screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.jsx @@ -12,7 +12,7 @@ import useRequest, { useDismissableError } from '../../../util/useRequest'; import AlertModal from '../../../components/AlertModal'; import { CardBody, CardActionsRow } from '../../../components/Card'; import ChipGroup from '../../../components/ChipGroup'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; import ContentError from '../../../components/ContentError'; import ContentLoading from '../../../components/ContentLoading'; import DeleteButton from '../../../components/DeleteButton'; diff --git a/awx/ui_next/src/screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.jsx b/awx/ui_next/src/screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.jsx index ca992575b1..3abb48d0ca 100644 --- a/awx/ui_next/src/screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.jsx +++ b/awx/ui_next/src/screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.jsx @@ -10,7 +10,7 @@ import { UserDateDetail, } from '../../../components/DetailList'; import Sparkline from '../../../components/Sparkline'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; function SmartInventoryHostDetail({ host, i18n }) { const { diff --git a/awx/ui_next/src/screens/Inventory/shared/InventoryForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventoryForm.jsx index 0cce85e9ca..c02edb8ea5 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventoryForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventoryForm.jsx @@ -5,7 +5,7 @@ import { t } from '@lingui/macro'; import { func, number, shape } from 'prop-types'; import { Form } from '@patternfly/react-core'; -import { VariablesField } from '../../../components/CodeMirrorInput'; +import { VariablesField } from '../../../components/CodeEditor'; import FormField, { FormSubmitError } from '../../../components/FormField'; import FormActionGroup from '../../../components/FormActionGroup'; import { required } from '../../../util/validators'; diff --git a/awx/ui_next/src/screens/Inventory/shared/InventoryGroupForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventoryGroupForm.jsx index 5fa41a2921..ac894d918b 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventoryGroupForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventoryGroupForm.jsx @@ -7,7 +7,7 @@ import { t } from '@lingui/macro'; import { CardBody } from '../../../components/Card'; import FormField, { FormSubmitError } from '../../../components/FormField'; import FormActionGroup from '../../../components/FormActionGroup/FormActionGroup'; -import { VariablesField } from '../../../components/CodeMirrorInput'; +import { VariablesField } from '../../../components/CodeEditor'; import { required } from '../../../util/validators'; import { FormColumnLayout, diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SharedFields.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SharedFields.jsx index 6501b2bf4b..31f5a4f12e 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SharedFields.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SharedFields.jsx @@ -5,7 +5,7 @@ import { useField } from 'formik'; import { FormGroup } from '@patternfly/react-core'; import { minMaxValue, regExp } from '../../../../util/validators'; import AnsibleSelect from '../../../../components/AnsibleSelect'; -import { VariablesField } from '../../../../components/CodeMirrorInput'; +import { VariablesField } from '../../../../components/CodeEditor'; import FormField, { CheckboxField } from '../../../../components/FormField'; import { FormFullWidthLayout, diff --git a/awx/ui_next/src/screens/Inventory/shared/SmartInventoryForm.jsx b/awx/ui_next/src/screens/Inventory/shared/SmartInventoryForm.jsx index a851fbfdc4..fca698aef2 100644 --- a/awx/ui_next/src/screens/Inventory/shared/SmartInventoryForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/SmartInventoryForm.jsx @@ -6,7 +6,7 @@ import { useLocation } from 'react-router-dom'; import { func, shape, arrayOf } from 'prop-types'; import { Form } from '@patternfly/react-core'; import { InstanceGroup } from '../../../types'; -import { VariablesField } from '../../../components/CodeMirrorInput'; +import { VariablesField } from '../../../components/CodeEditor'; import ContentError from '../../../components/ContentError'; import ContentLoading from '../../../components/ContentLoading'; import FormActionGroup from '../../../components/FormActionGroup'; diff --git a/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx b/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx index ad0be4c9e6..176ca95556 100644 --- a/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx +++ b/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx @@ -16,7 +16,7 @@ import { import { CardBody, CardActionsRow } from '../../../components/Card'; import ChipGroup from '../../../components/ChipGroup'; import CredentialChip from '../../../components/CredentialChip'; -import { VariablesInput as _VariablesInput } from '../../../components/CodeMirrorInput'; +import { VariablesInput as _VariablesInput } from '../../../components/CodeEditor'; import DeleteButton from '../../../components/DeleteButton'; import ErrorDetail from '../../../components/ErrorDetail'; import { diff --git a/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.jsx b/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.jsx index f82e12eb00..20f363a849 100644 --- a/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.jsx +++ b/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.jsx @@ -8,7 +8,7 @@ import { AllHtmlEntities } from 'html-entities'; import StatusIcon from '../../../components/StatusIcon'; import { DetailList, Detail } from '../../../components/DetailList'; import ContentEmpty from '../../../components/ContentEmpty'; -import CodeMirrorInput from '../../../components/CodeMirrorInput'; +import CodeEditor from '../../../components/CodeEditor'; const entities = new AllHtmlEntities(); @@ -45,18 +45,18 @@ const processEventStatus = event => { return status; }; -const processCodeMirrorValue = value => { - let codeMirrorValue; +const processCodeEditorValue = value => { + let codeEditorValue; if (value === undefined) { - codeMirrorValue = false; + codeEditorValue = false; } else if (value === '') { - codeMirrorValue = ' '; + codeEditorValue = ' '; } else if (typeof value === 'string') { - codeMirrorValue = entities.encode(value); + codeEditorValue = entities.encode(value); } else { - codeMirrorValue = value; + codeEditorValue = value; } - return codeMirrorValue; + return codeEditorValue; }; const processStdOutValue = hostEvent => { @@ -90,9 +90,9 @@ function HostEventModal({ onClose, hostEvent = {}, isOpen = false, i18n }) { setActiveTabKey(tabIndex); }; - const jsonObj = processCodeMirrorValue(hostEvent?.event_data?.res); - const stdErr = processCodeMirrorValue(hostEvent?.event_data?.res?.stderr); - const stdOut = processCodeMirrorValue(processStdOutValue(hostEvent)); + const jsonObj = processCodeEditorValue(hostEvent?.event_data?.res); + const stdErr = processCodeEditorValue(hostEvent?.event_data?.res?.stderr); + const stdOut = processCodeEditorValue(processStdOutValue(hostEvent)); return ( {activeTabKey === 1 && jsonObj ? ( - {activeTabKey === 2 && stdOut ? ( - {activeTabKey === 3 && stdErr ? ( - {}} diff --git a/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.test.jsx b/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.test.jsx index bb4b538142..3fe04f05a8 100644 --- a/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.test.jsx +++ b/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.test.jsx @@ -188,12 +188,12 @@ describe('HostEventModal', () => { expect(jsonSection.find('EmptyState').length).toBe(1); wrapper.find('button[aria-label="JSON tab"]').simulate('click'); findSections(wrapper); - expect(jsonSection.find('CodeMirrorInput').length).toBe(1); + expect(jsonSection.find('CodeEditor').length).toBe(1); - const codemirror = jsonSection.find('CodeMirrorInput Controlled'); - expect(codemirror.prop('mode')).toBe('javascript'); - expect(codemirror.prop('options').readOnly).toBe(true); - expect(codemirror.prop('value')).toEqual(jsonValue); + const codeEditor = jsonSection.find('CodeEditor'); + expect(codeEditor.prop('mode')).toBe('javascript'); + expect(codeEditor.prop('readOnly')).toBe(true); + expect(codeEditor.prop('value')).toEqual(jsonValue); }); test('should display Standard Out tab content on tab click', () => { @@ -205,12 +205,12 @@ describe('HostEventModal', () => { expect(standardOutSection.find('EmptyState').length).toBe(1); wrapper.find('button[aria-label="Standard out tab"]').simulate('click'); findSections(wrapper); - expect(standardOutSection.find('CodeMirrorInput').length).toBe(1); + expect(standardOutSection.find('CodeEditor').length).toBe(1); - const codemirror = standardOutSection.find('CodeMirrorInput Controlled'); - expect(codemirror.prop('mode')).toBe('javascript'); - expect(codemirror.prop('options').readOnly).toBe(true); - expect(codemirror.prop('value')).toEqual(hostEvent.event_data.res.stdout); + const codeEditor = standardOutSection.find('CodeEditor'); + expect(codeEditor.prop('mode')).toBe('javascript'); + expect(codeEditor.prop('readOnly')).toBe(true); + expect(codeEditor.prop('value')).toEqual(hostEvent.event_data.res.stdout); }); test('should display Standard Error tab content on tab click', () => { @@ -229,12 +229,12 @@ describe('HostEventModal', () => { expect(standardErrorSection.find('EmptyState').length).toBe(1); wrapper.find('button[aria-label="Standard error tab"]').simulate('click'); findSections(wrapper); - expect(standardErrorSection.find('CodeMirrorInput').length).toBe(1); + expect(standardErrorSection.find('CodeEditor').length).toBe(1); - const codemirror = standardErrorSection.find('CodeMirrorInput Controlled'); - expect(codemirror.prop('mode')).toBe('javascript'); - expect(codemirror.prop('options').readOnly).toBe(true); - expect(codemirror.prop('value')).toEqual(' '); + const codeEditor = standardErrorSection.find('CodeEditor'); + expect(codeEditor.prop('mode')).toBe('javascript'); + expect(codeEditor.prop('readOnly')).toBe(true); + expect(codeEditor.prop('value')).toEqual(' '); }); test('should call onClose when close button is clicked', () => { @@ -263,8 +263,8 @@ describe('HostEventModal', () => { ); wrapper.find('button[aria-label="Standard out tab"]').simulate('click'); findSections(wrapper); - const codemirror = standardOutSection.find('CodeMirrorInput Controlled'); - expect(codemirror.prop('value')).toEqual('foo bar'); + const codeEditor = standardOutSection.find('CodeEditor'); + expect(codeEditor.prop('value')).toEqual('foo bar'); }); test('should render standard out of yum task', () => { @@ -282,7 +282,7 @@ describe('HostEventModal', () => { ); wrapper.find('button[aria-label="Standard out tab"]').simulate('click'); findSections(wrapper); - const codemirror = standardOutSection.find('CodeMirrorInput Controlled'); - expect(codemirror.prop('value')).toEqual('baz'); + const codeEditor = standardOutSection.find('CodeEditor'); + expect(codeEditor.prop('value')).toEqual('baz'); }); }); diff --git a/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx b/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx index b452ed4b62..fca1b0317d 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx @@ -8,7 +8,7 @@ import { FormFullWidthLayout, SubFormLayout, } from '../../../components/FormLayout'; -import CodeMirrorField from '../../../components/CodeMirrorInput/CodeMirrorField'; +import CodeEditorField from '../../../components/CodeEditor/CodeEditorField'; function CustomMessagesSubForm({ defaultMessages, type, i18n }) { const [useCustomField, , useCustomHelpers] = useField('useCustomMessages'); @@ -102,7 +102,7 @@ function CustomMessagesSubForm({ defaultMessages, type, i18n }) { {showMessages && ( - )} {showBodies && ( - )} {showMessages && ( - )} {showBodies && ( - )} {showMessages && ( - )} {showBodies && ( - )} {showMessages && ( - )} {showBodies && ( - )} {showMessages && ( - )} {showBodies && ( - )} {showMessages && ( - )} {showBodies && ( - )} {showMessages && ( - )} {showBodies && ( - - ', () => { target: { value: 'new key', name: 'SOCIAL_AUTH_GITHUB_KEY' }, }); wrapper - .find('CodeMirrorInput#SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP') + .find('CodeEditor#SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP') .invoke('onChange')('{\n"Default":{\n"users":\nfalse\n}\n}'); }); wrapper.update(); diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/GitHubEnterpriseEdit.test.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/GitHubEnterpriseEdit.test.jsx index f0bb46ab23..1ed4336511 100644 --- a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/GitHubEnterpriseEdit.test.jsx +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/GitHubEnterpriseEdit.test.jsx @@ -122,7 +122,7 @@ describe('', () => { }, }); wrapper - .find('CodeMirrorInput#SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP') + .find('CodeEditor#SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP') .invoke('onChange')('{\n"Default":{\n"users":\nfalse\n}\n}'); }); wrapper.update(); diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/GitHubEnterpriseOrgEdit.test.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/GitHubEnterpriseOrgEdit.test.jsx index 278dd5d37e..9c3d3bd344 100644 --- a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/GitHubEnterpriseOrgEdit.test.jsx +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/GitHubEnterpriseOrgEdit.test.jsx @@ -135,9 +135,7 @@ describe('', () => { }, }); wrapper - .find( - 'CodeMirrorInput#SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP' - ) + .find('CodeEditor#SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP') .invoke('onChange')('{\n"Default":{\n"users":\nfalse\n}\n}'); }); wrapper.update(); diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/GitHubEnterpriseTeamEdit.test.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/GitHubEnterpriseTeamEdit.test.jsx index 764c9fa377..a87782a81d 100644 --- a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/GitHubEnterpriseTeamEdit.test.jsx +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/GitHubEnterpriseTeamEdit.test.jsx @@ -129,9 +129,7 @@ describe('', () => { }, }); wrapper - .find( - 'CodeMirrorInput#SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP' - ) + .find('CodeEditor#SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP') .invoke('onChange')('{\n"Default":{\n"users":\nfalse\n}\n}'); }); wrapper.update(); diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubOrgEdit/GitHubOrgEdit.test.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubOrgEdit/GitHubOrgEdit.test.jsx index 57396c43d1..21f6e4180d 100644 --- a/awx/ui_next/src/screens/Setting/GitHub/GitHubOrgEdit/GitHubOrgEdit.test.jsx +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubOrgEdit/GitHubOrgEdit.test.jsx @@ -113,7 +113,7 @@ describe('', () => { target: { value: 'new org', name: 'SOCIAL_AUTH_GITHUB_ORG_NAME' }, }); wrapper - .find('CodeMirrorInput#SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP') + .find('CodeEditor#SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP') .invoke('onChange')('{\n"Default":{\n"users":\nfalse\n}\n}'); }); wrapper.update(); diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubTeamEdit/GitHubTeamEdit.test.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubTeamEdit/GitHubTeamEdit.test.jsx index bbc36f8948..9c9e971cf1 100644 --- a/awx/ui_next/src/screens/Setting/GitHub/GitHubTeamEdit/GitHubTeamEdit.test.jsx +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubTeamEdit/GitHubTeamEdit.test.jsx @@ -108,7 +108,7 @@ describe('', () => { target: { value: '12345', name: 'SOCIAL_AUTH_GITHUB_TEAM_ID' }, }); wrapper - .find('CodeMirrorInput#SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP') + .find('CodeEditor#SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP') .invoke('onChange')('{\n"Default":{\n"users":\ntrue\n}\n}'); }); wrapper.update(); diff --git a/awx/ui_next/src/screens/Setting/GoogleOAuth2/GoogleOAuth2Edit/GoogleOAuth2Edit.test.jsx b/awx/ui_next/src/screens/Setting/GoogleOAuth2/GoogleOAuth2Edit/GoogleOAuth2Edit.test.jsx index 68a292232c..c01dae3201 100644 --- a/awx/ui_next/src/screens/Setting/GoogleOAuth2/GoogleOAuth2Edit/GoogleOAuth2Edit.test.jsx +++ b/awx/ui_next/src/screens/Setting/GoogleOAuth2/GoogleOAuth2Edit/GoogleOAuth2Edit.test.jsx @@ -122,7 +122,7 @@ describe('', () => { target: { value: 'new key', name: 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY' }, }); wrapper - .find('CodeMirrorInput#SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP') + .find('CodeEditor#SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP') .invoke('onChange')('{\n"Default":{\n"users":\nfalse\n}\n}'); }); wrapper.update(); diff --git a/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx b/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx index 71f998e341..e41e0c26e3 100644 --- a/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx +++ b/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx @@ -153,7 +153,7 @@ describe('', () => { name: 'AUTH_LDAP_SERVER_URI', }, }); - wrapper.find('CodeMirrorInput#AUTH_LDAP_TEAM_MAP').invoke('onChange')( + wrapper.find('CodeEditor#AUTH_LDAP_TEAM_MAP').invoke('onChange')( '{\n"LDAP Sales":{\n"organization":\n"mock org"\n}\n}' ); }); diff --git a/awx/ui_next/src/screens/Setting/shared/SharedFields.jsx b/awx/ui_next/src/screens/Setting/shared/SharedFields.jsx index 59474acd8c..b5e118088a 100644 --- a/awx/ui_next/src/screens/Setting/shared/SharedFields.jsx +++ b/awx/ui_next/src/screens/Setting/shared/SharedFields.jsx @@ -14,7 +14,7 @@ import { import FileUploadIcon from '@patternfly/react-icons/dist/js/icons/file-upload-icon'; import styled from 'styled-components'; import AnsibleSelect from '../../../components/AnsibleSelect'; -import CodeMirrorInput from '../../../components/CodeMirrorInput'; +import CodeEditor from '../../../components/CodeEditor'; import { PasswordInput } from '../../../components/FormField'; import { FormFullWidthLayout } from '../../../components/FormLayout'; import Popover from '../../../components/Popover'; @@ -284,7 +284,7 @@ const ObjectField = withI18n()(({ i18n, name, config, isRequired = false }) => { popoverContent={config.help_text} validated={isValid ? 'default' : 'error'} > - { )} ); - expect(wrapper.find('CodeMirrorInput')).toHaveLength(1); - expect(wrapper.find('CodeMirrorInput').prop('value')).toBe( + expect(wrapper.find('CodeEditor')).toHaveLength(1); + expect(wrapper.find('CodeEditor').prop('value')).toBe( '["one", "two", "three"]' ); await act(async () => { - wrapper.find('CodeMirrorInput').invoke('onChange')('[]'); + wrapper.find('CodeEditor').invoke('onChange')('[]'); }); wrapper.update(); - expect(wrapper.find('CodeMirrorInput').prop('value')).toBe('[]'); + expect(wrapper.find('CodeEditor').prop('value')).toBe('[]'); }); test('FileUploadField renders the expected content', async () => { diff --git a/awx/ui_next/src/screens/Setting/shared/settingTestUtils.js b/awx/ui_next/src/screens/Setting/shared/settingTestUtils.js index ab81162984..af2da46345 100644 --- a/awx/ui_next/src/screens/Setting/shared/settingTestUtils.js +++ b/awx/ui_next/src/screens/Setting/shared/settingTestUtils.js @@ -8,6 +8,6 @@ export function assertVariableDetail(wrapper, label, value) { wrapper.find(`CodeDetail[label="${label}"] .pf-c-form__label`).text() ).toBe(label); expect( - wrapper.find(`CodeDetail[label="${label}"] CodeMirrorInput`).prop('value') + wrapper.find(`CodeDetail[label="${label}"] CodeEditor`).prop('value') ).toBe(value); } diff --git a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx index 4a2b47605f..d61819adcc 100644 --- a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx +++ b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx @@ -27,7 +27,7 @@ import { import DeleteButton from '../../../components/DeleteButton'; import ErrorDetail from '../../../components/ErrorDetail'; import { LaunchButton } from '../../../components/LaunchButton'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; import { JobTemplatesAPI } from '../../../api'; import useRequest, { useDismissableError } from '../../../util/useRequest'; diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.jsx index 4f8237e8c2..7d21bafb3f 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.jsx @@ -16,7 +16,7 @@ import { WorkflowJobTemplatesAPI } from '../../../api'; import AlertModal from '../../../components/AlertModal'; import { CardBody, CardActionsRow } from '../../../components/Card'; import ChipGroup from '../../../components/ChipGroup'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; +import { VariablesDetail } from '../../../components/CodeEditor'; import DeleteButton from '../../../components/DeleteButton'; import { DetailList, diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx index b1ba6aac3d..e28b12ef47 100644 --- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx +++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx @@ -29,7 +29,7 @@ import { FormCheckboxLayout, SubFormLayout, } from '../../../components/FormLayout'; -import { VariablesField } from '../../../components/CodeMirrorInput'; +import { VariablesField } from '../../../components/CodeEditor'; import { required } from '../../../util/validators'; import { JobTemplate } from '../../../types'; import { diff --git a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx index 5ee34bbe12..6108b0c8f7 100644 --- a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx +++ b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx @@ -26,7 +26,7 @@ import { InventoryLookup, ExecutionEnvironmentLookup, } from '../../../components/Lookup'; -import { VariablesField } from '../../../components/CodeMirrorInput'; +import { VariablesField } from '../../../components/CodeEditor'; import FormActionGroup from '../../../components/FormActionGroup'; import ContentError from '../../../components/ContentError'; import CheckboxField from '../../../components/FormField/CheckboxField'; From 19f4de0d052ee9b86d3794cab63a895625246e7f Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Tue, 16 Feb 2021 16:57:36 -0800 Subject: [PATCH 05/19] add keyboard navigation help text to CodeEditor --- .../src/components/CodeEditor/CodeEditor.jsx | 118 +++++++++++------- 1 file changed, 76 insertions(+), 42 deletions(-) diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx index 7db780cb75..b60934a665 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useCallback } from 'react'; +import React, { useEffect, useRef, useCallback, useState } from 'react'; import { oneOf, bool, number, string, func } from 'prop-types'; import ReactAce from 'react-ace'; import 'ace-builds/src-noconflict/mode-json'; @@ -6,11 +6,23 @@ import 'ace-builds/src-noconflict/mode-javascript'; import 'ace-builds/src-noconflict/mode-yaml'; import 'ace-builds/src-noconflict/mode-django'; import 'ace-builds/src-noconflict/theme-github'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; import styled from 'styled-components'; const LINE_HEIGHT = 24; const PADDING = 12; +const FocusWrapper = styled.div` + && + .pf-c-form__helper-text { + display: none; + } + + &:focus-within + .pf-c-form__helper-text { + display: block; + } +`; + const AceEditor = styled(ReactAce)` font-family: var(--pf-global--FontFamily--monospace); max-height: 90vh; @@ -45,16 +57,21 @@ function CodeEditor({ rows, fullHeight, className, + i18n, }) { + const [isKeyboardFocused, setIsKeyboardFocused] = useState(false); const wrapper = useRef(null); const editor = useRef(null); - useEffect(function removeTextareaTabIndex() { - const editorInput = editor.current.refEditor?.querySelector('textarea'); - if (editorInput) { - editorInput.tabIndex = -1; - } - }, []); + useEffect( + function removeTextareaTabIndex() { + const editorInput = editor.current.refEditor?.querySelector('textarea'); + if (editorInput && !readOnly) { + editorInput.tabIndex = -1; + } + }, + [readOnly] + ); const listen = useCallback(event => { if ( @@ -88,43 +105,60 @@ function CodeEditor({ const numRows = fullHeight ? value.split('\n').length : rows; return ( - /* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */ -
- + { + if (e.target === e.currentTarget) { + setIsKeyboardFocused(true); + } + if (e.target.className.includes('ace_scrollbar')) { + setIsKeyboardFocused(false); + } }} - commands={[ - { - name: 'escape', - bindKey: { win: 'Esc', mac: 'Esc' }, - exec: () => { - wrapper.current.focus(); + > + { + wrapper.current.focus(); + }, }, - }, - { - name: 'tab escape', - bindKey: { win: 'Shift-Tab', mac: 'Shift-Tab' }, - exec: () => { - wrapper.current.focus(); + { + name: 'tab escape', + bindKey: { win: 'Shift-Tab', mac: 'Shift-Tab' }, + exec: () => { + wrapper.current.focus(); + }, }, - }, - ]} - ref={editor} - /> -
+ ]} + ref={editor} + /> + + {isKeyboardFocused && ( +
+ {i18n._(t`Press Enter to edit. Press ESC to stop editing.`)} +
+ )} + ); } CodeEditor.propTypes = { @@ -146,4 +180,4 @@ CodeEditor.defaultProps = { className: '', }; -export default CodeEditor; +export default withI18n()(CodeEditor); From 411d69204b3aad324e7075b61173f673f88559be Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Thu, 18 Feb 2021 11:59:02 -0800 Subject: [PATCH 06/19] remove codemirror dependencies --- awx/ui_next/package-lock.json | 10 ---------- awx/ui_next/package.json | 2 -- 2 files changed, 12 deletions(-) diff --git a/awx/ui_next/package-lock.json b/awx/ui_next/package-lock.json index 8fe91ad419..daf0c1d466 100644 --- a/awx/ui_next/package-lock.json +++ b/awx/ui_next/package-lock.json @@ -4952,11 +4952,6 @@ "q": "^1.1.2" } }, - "codemirror": { - "version": "5.58.3", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.58.3.tgz", - "integrity": "sha512-KBhB+juiyOOgn0AqtRmWyAT3yoElkuvWTI6hsHa9E6GQrl6bk/fdAYcvuqW1/upO9T9rtEtapWdw4XYcNiVDEA==" - }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -14229,11 +14224,6 @@ } } }, - "react-codemirror2": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-6.0.1.tgz", - "integrity": "sha512-rutEKVgvFhWcy/GeVA1hFbqrO89qLqgqdhUr7YhYgIzdyICdlRQv+ztuNvOFQMXrO0fLt0VkaYOdMdYdQgsSUA==" - }, "react-dev-utils": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz", diff --git a/awx/ui_next/package.json b/awx/ui_next/package.json index 233c0e0ac2..99c5a04d1d 100644 --- a/awx/ui_next/package.json +++ b/awx/ui_next/package.json @@ -14,7 +14,6 @@ "ace-builds": "^1.4.12", "ansi-to-html": "^0.6.11", "axios": "^0.21.1", - "codemirror": "^5.47.0", "d3": "^5.12.0", "dagre": "^0.8.4", "formik": "^2.1.2", @@ -24,7 +23,6 @@ "prop-types": "^15.6.2", "react": "^16.13.1", "react-ace": "^9.3.0", - "react-codemirror2": "^6.0.0", "react-dom": "^16.13.1", "react-router-dom": "^5.1.2", "react-virtualized": "^9.21.1", From 221021a798546df4bea57a44a6048101fd574013 Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Fri, 19 Feb 2021 15:30:27 -0800 Subject: [PATCH 07/19] disable interactive elements of CodeEditor in readOnly mode --- awx/ui_next/src/components/CodeEditor/CodeEditor.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx index b60934a665..465d02f909 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx @@ -44,6 +44,14 @@ const AceEditor = styled(ReactAce)` background: var(--pf-c-form-control--invalid--Background); border-bottom-width: var(--pf-c-form-control--invalid--BorderBottomWidth); }`} + + ${props => + props.setOptions.readOnly && + ` + && .ace_cursor { + opacity: 0; + } + `} `; AceEditor.displayName = 'AceEditor'; @@ -132,6 +140,8 @@ function CodeEditor({ hasErrors={hasErrors} setOptions={{ readOnly, + highlightActiveLine: !readOnly, + highlightGutterLine: !readOnly, useWorker: false, }} commands={[ From 2995cde7cb795febc645ddaf884ba10c62b12aa6 Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Mon, 22 Feb 2021 14:51:05 -0800 Subject: [PATCH 08/19] add AceEditor to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17e1829cc7..4da80092a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This is a list of high-level changes for each release of AWX. A full list of com - Added pending workflow approval count to the application header https://github.com/ansible/awx/pull/9334 - Added user interface for management jobs: https://github.com/ansible/awx/pull/9224 - Added toast message to show notification template test result to notification templates list https://github.com/ansible/awx/pull/9318 +- Replaced CodeMirror with AceEditor for editing template variables and notification templates https://github.com/ansible/awx/pull/9281 # 17.1.0 (March 9th, 2021) - Addressed a security issue in AWX (CVE-2021-20253) From c975f65bbc96e586e0401f8cbd9f22e6edda9a7c Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Mon, 22 Feb 2021 16:43:04 -0800 Subject: [PATCH 09/19] CodeEditor: fix hidden error message --- awx/ui_next/src/components/CodeEditor/CodeEditor.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx index 465d02f909..4681a142c3 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx @@ -14,11 +14,11 @@ const LINE_HEIGHT = 24; const PADDING = 12; const FocusWrapper = styled.div` - && + .pf-c-form__helper-text { + && + .keyboard-help-text { display: none; } - &:focus-within + .pf-c-form__helper-text { + &:focus-within + .keyboard-help-text { display: block; } `; @@ -164,7 +164,10 @@ function CodeEditor({ /> {isKeyboardFocused && ( -
+
{i18n._(t`Press Enter to edit. Press ESC to stop editing.`)}
)} From 143d41fb2aed41877f72df479f180580c65038fe Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Thu, 4 Mar 2021 11:03:27 -0800 Subject: [PATCH 10/19] add value assertions to code editor tests --- awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx | 6 ++++-- .../src/components/CodeEditor/VariablesField.test.jsx | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx index 2da3604574..be12a6d5ee 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx @@ -10,11 +10,12 @@ describe('CodeEditor', () => { it('should trigger onChange prop', () => { const onChange = jest.fn(); const wrapper = mount( - + ); const aceEditor = wrapper.find('AceEditor'); expect(aceEditor.prop('mode')).toEqual('yaml'); expect(aceEditor.prop('setOptions').readOnly).toEqual(false); + expect(aceEditor.prop('value')).toEqual('---'); aceEditor.prop('onChange')('newvalue'); expect(onChange).toHaveBeenCalledWith('newvalue'); }); @@ -22,9 +23,10 @@ describe('CodeEditor', () => { it('should render in read only mode', () => { const onChange = jest.fn(); const wrapper = mount( - + ); const aceEditor = wrapper.find('AceEditor'); expect(aceEditor.prop('setOptions').readOnly).toEqual(true); + expect(aceEditor.prop('value')).toEqual('---'); }); }); diff --git a/awx/ui_next/src/components/CodeEditor/VariablesField.test.jsx b/awx/ui_next/src/components/CodeEditor/VariablesField.test.jsx index abbb091715..d1048ef4d1 100644 --- a/awx/ui_next/src/components/CodeEditor/VariablesField.test.jsx +++ b/awx/ui_next/src/components/CodeEditor/VariablesField.test.jsx @@ -40,6 +40,7 @@ describe('VariablesField', () => { }); wrapper.update(); expect(wrapper.find('CodeEditor').prop('mode')).toEqual('javascript'); + expect(wrapper.find('CodeEditor').prop('value')).toEqual('{}'); const buttons2 = wrapper.find('Button'); expect(buttons2.at(0).prop('variant')).toEqual('secondary'); expect(buttons2.at(1).prop('variant')).toEqual('primary'); @@ -69,6 +70,7 @@ describe('VariablesField', () => { expect(field.prop('hasErrors')).toEqual(true); expect(wrapper.find('.pf-m-error')).toHaveLength(1); }); + it('should render tooltip', () => { const value = '---\n'; const wrapper = mountWithContexts( From 4e55c98bc6fdc9ea4ff92043d74b8f5f02834205 Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Fri, 5 Mar 2021 14:16:33 -0800 Subject: [PATCH 11/19] add more code editor tests --- .../src/components/CodeEditor/CodeEditor.test.jsx | 14 +++++++++++--- .../components/CodeEditor/VariablesField.test.jsx | 11 ++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx index be12a6d5ee..0af1599feb 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx @@ -7,15 +7,23 @@ describe('CodeEditor', () => { document.body.createTextRange = jest.fn(); }); + it('should pass value and mode through to ace editor', () => { + const onChange = jest.fn(); + const wrapper = mount( + + ); + const aceEditor = wrapper.find('AceEditor'); + expect(aceEditor.prop('mode')).toEqual('yaml'); + expect(aceEditor.prop('setOptions').readOnly).toEqual(false); + expect(aceEditor.prop('value')).toEqual('---\nfoo: bar'); + }); + it('should trigger onChange prop', () => { const onChange = jest.fn(); const wrapper = mount( ); const aceEditor = wrapper.find('AceEditor'); - expect(aceEditor.prop('mode')).toEqual('yaml'); - expect(aceEditor.prop('setOptions').readOnly).toEqual(false); - expect(aceEditor.prop('value')).toEqual('---'); aceEditor.prop('onChange')('newvalue'); expect(onChange).toHaveBeenCalledWith('newvalue'); }); diff --git a/awx/ui_next/src/components/CodeEditor/VariablesField.test.jsx b/awx/ui_next/src/components/CodeEditor/VariablesField.test.jsx index d1048ef4d1..e07ff9d40b 100644 --- a/awx/ui_next/src/components/CodeEditor/VariablesField.test.jsx +++ b/awx/ui_next/src/components/CodeEditor/VariablesField.test.jsx @@ -22,8 +22,8 @@ describe('VariablesField', () => { expect(codeEditor.prop('value')).toEqual(value); }); - it('should render yaml/json toggles', async () => { - const value = '---\n'; + it('should toggle between yaml/json', async () => { + const value = '---\nfoo: bar\nbaz: 3'; const wrapper = mountWithContexts( {() => ( @@ -40,7 +40,9 @@ describe('VariablesField', () => { }); wrapper.update(); expect(wrapper.find('CodeEditor').prop('mode')).toEqual('javascript'); - expect(wrapper.find('CodeEditor').prop('value')).toEqual('{}'); + expect(wrapper.find('CodeEditor').prop('value')).toEqual( + '{\n "foo": "bar",\n "baz": 3\n}' + ); const buttons2 = wrapper.find('Button'); expect(buttons2.at(0).prop('variant')).toEqual('secondary'); expect(buttons2.at(1).prop('variant')).toEqual('primary'); @@ -49,6 +51,9 @@ describe('VariablesField', () => { }); wrapper.update(); expect(wrapper.find('CodeEditor').prop('mode')).toEqual('yaml'); + expect(wrapper.find('CodeEditor').prop('value')).toEqual( + 'foo: bar\nbaz: 3\n' + ); }); it('should set Formik error if yaml is invalid', async () => { From 6b2cee2f69b5e501738d78efaf47e780ed35bc8b Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Mon, 8 Mar 2021 11:31:03 -0800 Subject: [PATCH 12/19] add tests ensuring forms pass correct value to CodeEditor fields --- .../SmartInventoryEdit.test.jsx | 4 +++ .../Inventory/shared/InventoryForm.test.jsx | 1 + .../shared/NotificationTemplateForm.test.jsx | 27 +++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/awx/ui_next/src/screens/Inventory/SmartInventoryEdit/SmartInventoryEdit.test.jsx b/awx/ui_next/src/screens/Inventory/SmartInventoryEdit/SmartInventoryEdit.test.jsx index 90ed4abb6f..789d8067c9 100644 --- a/awx/ui_next/src/screens/Inventory/SmartInventoryEdit/SmartInventoryEdit.test.jsx +++ b/awx/ui_next/src/screens/Inventory/SmartInventoryEdit/SmartInventoryEdit.test.jsx @@ -74,6 +74,10 @@ describe('', () => { wrapper.unmount(); }); + test('should render CodeEditor field', () => { + expect(wrapper.find('CodeEditor').prop('value')).toEqual('---'); + }); + test('should fetch related instance groups on initial render', async () => { expect(InventoriesAPI.readInstanceGroups).toHaveBeenCalledTimes(1); }); diff --git a/awx/ui_next/src/screens/Inventory/shared/InventoryForm.test.jsx b/awx/ui_next/src/screens/Inventory/shared/InventoryForm.test.jsx index 86ea72e52c..dc2b5bd246 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventoryForm.test.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventoryForm.test.jsx @@ -94,6 +94,7 @@ describe('', () => { 1 ); expect(wrapper.find('VariablesField[label="Variables"]').length).toBe(1); + expect(wrapper.find('CodeEditor').prop('value')).toEqual('---'); }); test('should update form values', async () => { diff --git a/awx/ui_next/src/screens/NotificationTemplate/shared/NotificationTemplateForm.test.jsx b/awx/ui_next/src/screens/NotificationTemplate/shared/NotificationTemplateForm.test.jsx index c828b1450f..5b90fd5e8d 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/shared/NotificationTemplateForm.test.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/shared/NotificationTemplateForm.test.jsx @@ -79,6 +79,33 @@ describe('', () => { ).toEqual(defaultMessages); }); + test('should render custom messages fields', () => { + const wrapper = mountWithContexts( + + ); + + expect( + wrapper + .find('CodeEditor') + .at(0) + .prop('value') + ).toEqual('Started'); + }); + test('should submit', async () => { const handleSubmit = jest.fn(); const wrapper = mountWithContexts( From c2a2bf39d5fbd99c0ebfa48570e08f240dab1abc Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Tue, 9 Mar 2021 11:07:20 -0800 Subject: [PATCH 13/19] don't show CodeEditor control help text in readonly mode --- awx/ui_next/src/components/CodeEditor/CodeEditor.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx index 4681a142c3..9ac4fb5331 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx @@ -163,7 +163,7 @@ function CodeEditor({ ref={editor} /> - {isKeyboardFocused && ( + {isKeyboardFocused && !readOnly && (
Date: Tue, 9 Mar 2021 11:27:43 -0800 Subject: [PATCH 14/19] CodeEditor: don't type newline when pressing enter to start edit mode --- awx/ui_next/src/components/CodeEditor/CodeEditor.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx index 9ac4fb5331..e32a370475 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx @@ -90,8 +90,9 @@ function CodeEditor({ if (!editorInput) { return; } - editorInput.focus(); + event.preventDefault(); event.stopPropagation(); + editorInput.focus(); } }, []); From 6f7a717664fb064ea29bf961a88278525fb3a1c3 Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Tue, 9 Mar 2021 15:00:18 -0800 Subject: [PATCH 15/19] hide CodeEditor touch controls menu --- awx/ui_next/src/components/CodeEditor/CodeEditor.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx index e32a370475..c4985e1047 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx @@ -33,6 +33,10 @@ const AceEditor = styled(ReactAce)` padding-bottom: 4px; } + & .ace_mobile-menu { + display: none; + } + ${props => props.hasErrors && ` From 4ea7c8a534671332389e72599c8f54b4f1d1211e Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Thu, 11 Mar 2021 16:20:05 -0800 Subject: [PATCH 16/19] CodeEditor bugfixes * fix typing space character * hide cursor when editor doesn't have user focus * show help text any time editor is in focus * fix content shifting when help text appears/disappears * remove 80 character "print limit" line --- .../src/components/CodeEditor/CodeEditor.jsx | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx index c4985e1047..4d4b0ac401 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx @@ -15,11 +15,16 @@ const PADDING = 12; const FocusWrapper = styled.div` && + .keyboard-help-text { - display: none; + opacity: 0; + transition: opacity 0.1s linear; } &:focus-within + .keyboard-help-text { - display: block; + opacity: 1; + } + + & .ace_hidden-cursors .ace_cursor { + opacity: 0; } `; @@ -71,7 +76,6 @@ function CodeEditor({ className, i18n, }) { - const [isKeyboardFocused, setIsKeyboardFocused] = useState(false); const wrapper = useRef(null); const editor = useRef(null); @@ -86,10 +90,7 @@ function CodeEditor({ ); const listen = useCallback(event => { - if ( - (wrapper.current === document.activeElement && event.key === 'Enter') || - event.key === ' ' - ) { + if (wrapper.current === document.activeElement && event.key === 'Enter') { const editorInput = editor.current.refEditor?.querySelector('textarea'); if (!editorInput) { return; @@ -119,18 +120,7 @@ function CodeEditor({ return ( <> - { - if (e.target === e.currentTarget) { - setIsKeyboardFocused(true); - } - if (e.target.className.includes('ace_scrollbar')) { - setIsKeyboardFocused(false); - } - }} - > + - {isKeyboardFocused && !readOnly && ( + {!readOnly && (
Date: Fri, 12 Mar 2021 08:28:26 -0800 Subject: [PATCH 17/19] fix lint error --- awx/ui_next/src/components/CodeEditor/CodeEditor.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx index 4d4b0ac401..564854937b 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useCallback, useState } from 'react'; +import React, { useEffect, useRef, useCallback } from 'react'; import { oneOf, bool, number, string, func } from 'prop-types'; import ReactAce from 'react-ace'; import 'ace-builds/src-noconflict/mode-json'; From 05f93032f51c2a9e5358ef25bcbdb05c1f86fdcb Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Fri, 12 Mar 2021 08:54:19 -0800 Subject: [PATCH 18/19] fix CodeEditor tests --- awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx index 0af1599feb..6f834d9108 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { mount } from 'enzyme'; +import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; import CodeEditor from './CodeEditor'; describe('CodeEditor', () => { @@ -9,7 +9,7 @@ describe('CodeEditor', () => { it('should pass value and mode through to ace editor', () => { const onChange = jest.fn(); - const wrapper = mount( + const wrapper = mountWithContexts( ); const aceEditor = wrapper.find('AceEditor'); @@ -20,7 +20,7 @@ describe('CodeEditor', () => { it('should trigger onChange prop', () => { const onChange = jest.fn(); - const wrapper = mount( + const wrapper = mountWithContexts( ); const aceEditor = wrapper.find('AceEditor'); @@ -30,7 +30,7 @@ describe('CodeEditor', () => { it('should render in read only mode', () => { const onChange = jest.fn(); - const wrapper = mount( + const wrapper = mountWithContexts( ); const aceEditor = wrapper.find('AceEditor'); From c90dfbb7e024ee9bdd7f9969dfcfe0f3fda4fb36 Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Fri, 12 Mar 2021 12:26:07 -0800 Subject: [PATCH 19/19] debounce CodeEditor onChange for performance improvement --- awx/ui_next/src/components/CodeEditor/CodeEditor.jsx | 3 ++- awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx index 564854937b..0218a83c3c 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.jsx @@ -9,6 +9,7 @@ import 'ace-builds/src-noconflict/theme-github'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; import styled from 'styled-components'; +import debounce from '../../util/debounce'; const LINE_HEIGHT = 24; const PADDING = 12; @@ -125,7 +126,7 @@ function CodeEditor({ mode={aceModes[mode] || 'text'} className={`pf-c-form-control ${className}`} theme="github" - onChange={onChange} + onChange={debounce(onChange, 250)} value={value} name={id || 'code-editor'} editorProps={{ $blockScrolling: true }} diff --git a/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx b/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx index 6f834d9108..69f498d2e3 100644 --- a/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx +++ b/awx/ui_next/src/components/CodeEditor/CodeEditor.test.jsx @@ -1,6 +1,9 @@ import React from 'react'; import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; import CodeEditor from './CodeEditor'; +import debounce from '../../util/debounce'; + +jest.mock('../../util/debounce'); describe('CodeEditor', () => { beforeEach(() => { @@ -19,6 +22,7 @@ describe('CodeEditor', () => { }); it('should trigger onChange prop', () => { + debounce.mockImplementation(fn => fn); const onChange = jest.fn(); const wrapper = mountWithContexts(