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}}
+
+
+
Your regex matches the subject string
+
+ {{/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');
+ });
+});