Skip to content

Commit

Permalink
UI: Regex validation on transform templates (hashicorp#11586)
Browse files Browse the repository at this point in the history
* Add regex validator component with tests, add to form-field, use in transform template

* Update tests with data-test selectors

* Add changelog
  • Loading branch information
hashishaw authored and AndreyZamyslov committed Jun 10, 2021
1 parent 3e3a91b commit 4f0d315
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 0 deletions.
3 changes: 3 additions & 0 deletions changelog/11586.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
ui: Add regex validation to Transform Template pattern input
```
68 changes: 68 additions & 0 deletions ui/app/components/regex-validator.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<div class="field">
<div class="regex-label-wrapper">
<div class="regex-label">
<label for="{{@attr.name}}" class="is-label">
{{@labelString}}
{{#if @attr.options.helpText}}
{{#info-tooltip}}
<span data-test-help-text>
{{@attr.options.helpText}}
</span>
{{/info-tooltip}}
{{/if}}
</label>
{{#if @attr.options.subText}}
<p class="sub-text">{{@attr.options.subText}} {{#if @attr.options.docLink}}<a href="{{@attr.options.docLink}}" target="_blank" rel="noopener noreferrer">See our documentation</a> for help.{{/if}}</p>
{{/if}}
</div>
<div>
<Toggle
@name={{concat @attr.name "-validation-toggle"}}
@status="success"
@size="small"
@checked={{this.showTestValue}}
@onChange={{this.toggleTestValue}}
>
<span class="has-text-grey">Validation</span>
</Toggle>
</div>
</div>
<input
id={{@attr.name}}
data-test-input={{@attr.name}}
autocomplete="off"
spellcheck="false"
{{on 'change' @onChange}}
value={{@value}}
class="input"
/>
</div>
{{#if this.showTestValue}}
<div data-test-regex-validator-test-string>
<label for="{{@attr.name}}" class="is-label">
Test string
</label>
<input
data-test-input={{concat @attr.name "-testval"}}
id={{concat @attr.name "-testval"}}
autocomplete="off"
spellcheck="false"
value={{this.testValue}}
{{on 'change' this.updateTestValue}}
class="input {{if this.regexError 'has-error'}}" />

{{#if (and this.testValue @value)}}
<div data-test-regex-validation-message>
{{#if this.regexError}}
<AlertInline @type="danger" @message="Your regex doesn't match the subject string" />
{{else}}
<div class="message-inline">
<Icon @glyph="check-circle-fill" class="has-text-success" aria-hidden="true" />
<p data-test-inline-success-message>Your regex matches the subject string</p>
</div>
{{/if}}
</div>
{{/if}}
</div>
{{/if}}

49 changes: 49 additions & 0 deletions ui/app/components/regex-validator.js
Original file line number Diff line number Diff line change
@@ -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
* }
* }
* <RegexValidator @onChange={action 'myAction'} @attr={attrExample} @labelString="Label String" @value="initial value" />
* ```
* @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;
}
}
1 change: 1 addition & 0 deletions ui/app/models/transform/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand Down
10 changes: 10 additions & 0 deletions ui/app/styles/components/regex-validator.scss
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions ui/app/styles/core.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
13 changes: 13 additions & 0 deletions ui/lib/core/addon/templates/components/form-field.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"ttl"
"stringArray"
"json"
"regex"
)
)
)
Expand Down Expand Up @@ -129,6 +130,18 @@
@initialValue={{or (get model valuePath) attr.options.setDefault}}
/>
</div>
{{else if (eq attr.options.editType "regex")}}
{{!-- Regex Validated Input --}}
<RegexValidator
@attr={{attr}}
@labelString={{labelString}}
@value={{or (get model valuePath)
attr.options.defaultValue}}
@onChange={{action
(action "setAndBroadcast" valuePath)
value="target.value"
}}
/>
{{else if (eq attr.options.editType "optionalText")}}
{{!-- Togglable Text Input --}}
<Toggle
Expand Down
60 changes: 60 additions & 0 deletions ui/tests/integration/components/regex-validator-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import EmberObject from '@ember/object';
import sinon from 'sinon';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click, fillIn } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Component | regex-validator', function(hooks) {
setupRenderingTest(hooks);

test('it renders input and validation messages', async function(assert) {
let attr = EmberObject.create({
name: 'example',
});
let spy = sinon.spy();
this.set('onChange', spy);
this.set('attr', attr);
this.set('value', '(\\d{4})');
this.set('labelString', 'Regex Example');

await render(
hbs`<RegexValidator
@onChange={{onChange}}
@attr={{attr}}
@value={{value}}
@labelString={{labelString}}
/>`
);
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');
});
});

0 comments on commit 4f0d315

Please sign in to comment.