diff --git a/changelog/11586.txt b/changelog/11586.txt new file mode 100644 index 000000000..31c40692c --- /dev/null +++ b/changelog/11586.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Add regex validation to Transform Template pattern input +``` diff --git a/ui/app/components/regex-validator.hbs b/ui/app/components/regex-validator.hbs new file mode 100644 index 000000000..18c5cf64f --- /dev/null +++ b/ui/app/components/regex-validator.hbs @@ -0,0 +1,68 @@ +
+
+
+ + {{#if @attr.options.subText}} +

{{@attr.options.subText}} {{#if @attr.options.docLink}}See our documentation for help.{{/if}}

+ {{/if}} +
+
+ + Validation + +
+
+ +
+{{#if this.showTestValue}} +
+ + + + {{#if (and this.testValue @value)}} +
+ {{#if this.regexError}} + + {{else}} +
+
+ {{/if}} +
+ {{/if}} +
+{{/if}} + diff --git a/ui/app/components/regex-validator.js b/ui/app/components/regex-validator.js new file mode 100644 index 000000000..f71539435 --- /dev/null +++ b/ui/app/components/regex-validator.js @@ -0,0 +1,49 @@ +/** + * @module RegexValidator + * RegexValidator components are used to provide input forms for regex values, along with a toggle-able validation input which does not get saved to the model. + * + * @example + * ```js + * const attrExample = { + * name: 'valName', + * options: { + * helpText: 'Shows in tooltip', + * subText: 'Shows underneath label', + * docLink: 'Adds docs link to subText if present', + * defaultValue: 'hello', // Shows if no value on model + * } + * } + * + * ``` + * @param {func} onChange - the action that should trigger when the main input is changed. + * @param {string} value - the value of the main input which will be updated in onChange + * @param {string} labelString - Form label. Anticipated from form-field + * @param {object} attr - attribute from model. Anticipated from form-field. Example of attribute shape above + */ + +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; + +export default class RegexValidator extends Component { + @tracked testValue = ''; + @tracked showTestValue = false; + + get regexError() { + const testString = this.testValue; + if (!testString || !this.args.value) return false; + const regex = new RegExp(this.args.value, 'g'); + const matchArray = testString.toString().match(regex); + return testString !== matchArray?.join(''); + } + + @action + updateTestValue(evt) { + this.testValue = evt.target.value; + } + + @action + toggleTestValue() { + this.showTestValue = !this.showTestValue; + } +} diff --git a/ui/app/models/transform/template.js b/ui/app/models/transform/template.js index 4d8602de8..103589e87 100644 --- a/ui/app/models/transform/template.js +++ b/ui/app/models/transform/template.js @@ -19,6 +19,7 @@ const M = Model.extend({ }), type: attr('string', { defaultValue: 'regex' }), pattern: attr('string', { + editType: 'regex', subText: 'The template’s pattern defines the data format. Expressed in regex.', }), alphabet: attr('array', { diff --git a/ui/app/styles/components/regex-validator.scss b/ui/app/styles/components/regex-validator.scss new file mode 100644 index 000000000..72fb255c7 --- /dev/null +++ b/ui/app/styles/components/regex-validator.scss @@ -0,0 +1,10 @@ +.regex-label-wrapper { + display: flex; + align-items: flex-end; +} +.regex-label { + flex: 1 0 auto; +} +.regex-toggle { + flex: 0 1 auto; +} diff --git a/ui/app/styles/core.scss b/ui/app/styles/core.scss index 3f51210fc..f0b4fb898 100644 --- a/ui/app/styles/core.scss +++ b/ui/app/styles/core.scss @@ -80,6 +80,7 @@ @import './components/radio-card'; @import './components/radial-progress'; @import './components/raft-join'; +@import './components/regex-validator'; @import './components/replication-dashboard'; @import './components/replication-doc-link'; @import './components/replication-header'; diff --git a/ui/lib/core/addon/templates/components/form-field.hbs b/ui/lib/core/addon/templates/components/form-field.hbs index 2d05c7ae3..ac2118aa4 100644 --- a/ui/lib/core/addon/templates/components/form-field.hbs +++ b/ui/lib/core/addon/templates/components/form-field.hbs @@ -13,6 +13,7 @@ "ttl" "stringArray" "json" + "regex" ) ) ) @@ -129,6 +130,18 @@ @initialValue={{or (get model valuePath) attr.options.setDefault}} /> +{{else if (eq attr.options.editType "regex")}} + {{!-- Regex Validated Input --}} + {{else if (eq attr.options.editType "optionalText")}} {{!-- Togglable Text Input --}} ` + ); + assert.dom('.regex-label label').hasText('Regex Example', 'Label is correct'); + assert.dom('[data-test-toggle-input="example-validation-toggle"]').exists('Validation toggle exists'); + assert.dom('[data-test-regex-validator-test-string]').doesNotExist('Test string input does not show'); + + await click('[data-test-toggle-input="example-validation-toggle"]'); + assert.dom('[data-test-regex-validator-test-string]').exists('Test string input shows after toggle'); + assert + .dom('[data-test-regex-validation-message]') + .doesNotExist('Validation message does not show if test string is empty'); + + await fillIn('[data-test-input="example-testval"]', '123a'); + assert.dom('[data-test-regex-validation-message]').exists('Validation message shows after input filled'); + assert + .dom('[data-test-inline-error-message]') + .hasText("Your regex doesn't match the subject string", 'Shows error when regex does not match string'); + + await fillIn('[data-test-input="example-testval"]', '1234'); + assert + .dom('[data-test-inline-success-message]') + .hasText('Your regex matches the subject string', 'Shows success when regex matches'); + + await fillIn('[data-test-input="example-testval"]', '12345'); + assert + .dom('[data-test-inline-error-message]') + .hasText( + "Your regex doesn't match the subject string", + "Shows error if regex doesn't match complete string" + ); + await fillIn('[data-test-input="example"]', '(\\d{5})'); + assert.ok(spy.calledOnce, 'Calls the passed onChange function when main input is changed'); + }); +});