Change tooltip for token_bound_certs and glimmerize string-list component (#15852)
* wip * wip * glimmerization done? * fix tests * tooltip and test * changelog * clean up * cleanup * cleanup
This commit is contained in:
parent
426e3a5583
commit
a86644968b
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ui: Changed the tokenBoundCidrs tooltip content to clarify that comma separated values are not accepted in this field.
|
||||
```
|
|
@ -196,12 +196,13 @@
|
|||
</Toggle>
|
||||
{{else if (eq @attr.options.editType "stringArray")}}
|
||||
<StringList
|
||||
@data-test-input={{@attr.name}}
|
||||
data-test-input={{@attr.name}}
|
||||
@label={{this.labelString}}
|
||||
@warning={{@attr.options.warning}}
|
||||
@helpText={{@attr.options.helpText}}
|
||||
@inputValue={{get @model this.valuePath}}
|
||||
@onChange={{this.setAndBroadcast}}
|
||||
@attrName={{@attr.name}}
|
||||
/>
|
||||
{{else if (eq @attr.options.sensitive true)}}
|
||||
{{! Masked Input }}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<div
|
||||
class="field string-list form-section"
|
||||
data-test-component="string-list"
|
||||
{{did-insert this.autoSize}}
|
||||
{{did-update this.autoSizeUpdate}}
|
||||
data-test-input
|
||||
...attributes
|
||||
>
|
||||
{{#if @label}}
|
||||
<label class="title is-label" data-test-string-list-label="true">
|
||||
{{@label}}
|
||||
{{#if this.helpText}}
|
||||
<InfoTooltip>{{this.helpText}}</InfoTooltip>
|
||||
{{/if}}
|
||||
</label>
|
||||
{{/if}}
|
||||
{{#if @warning}}
|
||||
<AlertBanner @type="warning" @message={{@warning}} />
|
||||
{{/if}}
|
||||
{{#each this.inputList as |data index|}}
|
||||
<div class="field is-grouped" data-test-string-list-row={{index}}>
|
||||
<div class="control is-expanded">
|
||||
<Textarea
|
||||
data-test-string-list-input={{index}}
|
||||
class="input"
|
||||
@value={{data.value}}
|
||||
name={{concat this.elementId "-" index}}
|
||||
id={{concat this.elementId "-" index}}
|
||||
{{on "keyup" (action "inputChanged" index)}}
|
||||
{{on "change" (action "inputChanged" index)}}
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
{{#if (eq (inc index) this.inputList.length)}}
|
||||
<button
|
||||
type="button"
|
||||
class="button is-outlined is-primary"
|
||||
data-test-string-list-button="add"
|
||||
{{on "click" this.addInput}}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
{{else}}
|
||||
<button
|
||||
type="button"
|
||||
class="button is-expanded is-icon"
|
||||
data-test-string-list-button="delete"
|
||||
{{on "click" (fn this.removeInput index)}}
|
||||
>
|
||||
<Icon @name="trash" />
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
|
@ -1,75 +1,32 @@
|
|||
import ArrayProxy from '@ember/array/proxy';
|
||||
import Component from '@ember/component';
|
||||
import { set, computed } from '@ember/object';
|
||||
import Component from '@glimmer/component';
|
||||
import autosize from 'autosize';
|
||||
import layout from '../templates/components/string-list';
|
||||
import { action } from '@ember/object';
|
||||
import { set } from '@ember/object';
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
'data-test-component': 'string-list',
|
||||
attributeBindings: ['data-test-component', 'data-test-input'],
|
||||
classNames: ['field', 'string-list', 'form-section'],
|
||||
/**
|
||||
* @module StringList
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <StringList @label={label} @onChange={{this.setAndBroadcast}} @inputValue={{this.valuePath}}/>
|
||||
* ```
|
||||
* @param {string} label - Text displayed in the header above all the inputs.
|
||||
* @param {function} onChange - Function called when any of the inputs change.
|
||||
* @param {string} inputValue - A string or an array of strings.
|
||||
* @param {string} warning - Text displayed as a warning.
|
||||
* @param {string} helpText - Text displayed as a tooltip.
|
||||
* @param {string} type=array - Optional type for inputValue.
|
||||
* @param {string} attrName - We use this to check the type so we can modify the tooltip content.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @public
|
||||
* @param String
|
||||
*
|
||||
* Optional - Text displayed in the header above all of the inputs
|
||||
*
|
||||
*/
|
||||
label: null,
|
||||
export default class StringList extends Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
/*
|
||||
* @public
|
||||
* @param Function
|
||||
*
|
||||
* Function called when any of the inputs change
|
||||
* accepts a single param `value` that is the
|
||||
* result of calling `toVal()`.
|
||||
*
|
||||
*/
|
||||
onChange: () => {},
|
||||
|
||||
/*
|
||||
* @public
|
||||
* @param String | Array
|
||||
* A comma-separated string or an array of strings.
|
||||
* Defaults to an empty array.
|
||||
*
|
||||
*/
|
||||
inputValue: computed(function () {
|
||||
return [];
|
||||
}),
|
||||
|
||||
/*
|
||||
*
|
||||
* @public
|
||||
* @param String - ['array'|'string]
|
||||
*
|
||||
* Optional type for `inputValue` - defaults to `'array'`
|
||||
* Needs to match type of `inputValue` because it is set by the component on init.
|
||||
*
|
||||
*/
|
||||
type: 'array',
|
||||
|
||||
/*
|
||||
*
|
||||
* @private
|
||||
* @param Ember.ArrayProxy
|
||||
*
|
||||
* mutable array that contains objects in the form of
|
||||
* {
|
||||
* value: 'somestring',
|
||||
* }
|
||||
*
|
||||
* used to track the state of values bound to the various inputs
|
||||
*
|
||||
*/
|
||||
/* eslint-disable ember/no-side-effects */
|
||||
inputList: computed('content', function () {
|
||||
return ArrayProxy.create({
|
||||
content: [],
|
||||
this.inputList = ArrayProxy.create({
|
||||
// trim the `value` when accessing objects
|
||||
content: [],
|
||||
objectAtContent: function (idx) {
|
||||
const obj = this.content.objectAt(idx);
|
||||
if (obj && obj.value) {
|
||||
|
@ -78,32 +35,28 @@ export default Component.extend({
|
|||
return obj;
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.type = this.args.type || 'array';
|
||||
this.setType();
|
||||
this.toList();
|
||||
this.send('addInput');
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
autosize(this.element.querySelector('textarea'));
|
||||
},
|
||||
|
||||
didUpdate() {
|
||||
this._super(...arguments);
|
||||
autosize.update(this.element.querySelector('textarea'));
|
||||
},
|
||||
this.addInput();
|
||||
}
|
||||
|
||||
setType() {
|
||||
const list = this.inputList;
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
this.set('type', typeof list);
|
||||
},
|
||||
this.type = typeof list;
|
||||
}
|
||||
|
||||
toList() {
|
||||
let input = this.args.inputValue || [];
|
||||
const inputList = this.inputList;
|
||||
if (typeof input === 'string') {
|
||||
input = input.split(',');
|
||||
}
|
||||
inputList.addObjects(input.map((value) => ({ value })));
|
||||
}
|
||||
|
||||
toVal() {
|
||||
const inputs = this.inputList.filter((x) => x.value).mapBy('value');
|
||||
|
@ -111,37 +64,47 @@ export default Component.extend({
|
|||
return inputs.join(',');
|
||||
}
|
||||
return inputs;
|
||||
},
|
||||
}
|
||||
|
||||
toList() {
|
||||
let input = this.inputValue || [];
|
||||
const inputList = this.inputList;
|
||||
if (typeof input === 'string') {
|
||||
input = input.split(',');
|
||||
get helpText() {
|
||||
if (this.args.attrName === 'tokenBoundCidrs') {
|
||||
return 'Specifies the blocks of IP addresses which are allowed to use the generated token. One entry per row.';
|
||||
} else {
|
||||
return this.args.helpText;
|
||||
}
|
||||
inputList.addObjects(input.map((value) => ({ value })));
|
||||
},
|
||||
}
|
||||
|
||||
actions: {
|
||||
inputChanged(idx, event) {
|
||||
const inputObj = this.inputList.objectAt(idx);
|
||||
const onChange = this.onChange;
|
||||
set(inputObj, 'value', event.target.value);
|
||||
onChange(this.toVal());
|
||||
},
|
||||
@action
|
||||
autoSize(element) {
|
||||
autosize(element.querySelector('textarea'));
|
||||
}
|
||||
|
||||
addInput() {
|
||||
const inputList = this.inputList;
|
||||
if (inputList.get('lastObject.value') !== '') {
|
||||
inputList.pushObject({ value: '' });
|
||||
}
|
||||
},
|
||||
@action
|
||||
autoSizeUpdate(element) {
|
||||
autosize.update(element.querySelector('textarea'));
|
||||
}
|
||||
|
||||
removeInput(idx) {
|
||||
const onChange = this.onChange;
|
||||
const inputs = this.inputList;
|
||||
inputs.removeObject(inputs.objectAt(idx));
|
||||
onChange(this.toVal());
|
||||
},
|
||||
},
|
||||
});
|
||||
@action
|
||||
inputChanged(idx, event) {
|
||||
const inputObj = this.inputList.objectAt(idx);
|
||||
const onChange = this.args.onChange;
|
||||
set(inputObj, 'value', event.target.value);
|
||||
onChange(this.toVal());
|
||||
}
|
||||
|
||||
@action
|
||||
addInput() {
|
||||
const inputList = this.inputList;
|
||||
if (inputList.get('lastObject.value') !== '') {
|
||||
inputList.pushObject({ value: '' });
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
removeInput(idx) {
|
||||
const onChange = this.args.onChange;
|
||||
const inputs = this.inputList;
|
||||
inputs.removeObject(inputs.objectAt(idx));
|
||||
onChange(this.toVal());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
{{#if this.label}}
|
||||
<label class="title is-label" data-test-string-list-label="true">
|
||||
{{this.label}}
|
||||
{{#if this.helpText}}
|
||||
<InfoTooltip>{{this.helpText}}</InfoTooltip>
|
||||
{{/if}}
|
||||
</label>
|
||||
{{/if}}
|
||||
{{#if this.warning}}
|
||||
<AlertBanner @type="warning" @message={{this.warning}} />
|
||||
{{/if}}
|
||||
{{#each this.inputList as |data index|}}
|
||||
<div class="field is-grouped" data-test-string-list-row={{index}}>
|
||||
<div class="control is-expanded">
|
||||
<Textarea
|
||||
data-test-string-list-input={{index}}
|
||||
class="input"
|
||||
@value={{data.value}}
|
||||
name={{concat this.elementId "-" index}}
|
||||
id={{concat this.elementId "-" index}}
|
||||
{{on "keyup" (action "inputChanged" index)}}
|
||||
{{on "change" (action "inputChanged" index)}}
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
{{#if (eq (inc index) this.inputList.length)}}
|
||||
<button type="button" class="button is-outlined is-primary" data-test-string-list-button="add" {{action "addInput"}}>
|
||||
Add
|
||||
</button>
|
||||
{{else}}
|
||||
<button
|
||||
type="button"
|
||||
class="button is-expanded is-icon"
|
||||
data-test-string-list-button="delete"
|
||||
{{action "removeInput" index}}
|
||||
>
|
||||
<Icon @name="trash" />
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
|
@ -17,6 +17,7 @@
|
|||
"ember-composable-helpers": "*",
|
||||
"ember-concurrency": "*",
|
||||
"ember-maybe-in-element": "*",
|
||||
"ember/render-modifiers": "*",
|
||||
"ember-power-select": "*",
|
||||
"ember-router-helpers": "*",
|
||||
"ember-svg-jar": "*",
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, click, fillIn, triggerKeyEvent } from '@ember/test-helpers';
|
||||
import { render, click, fillIn, triggerKeyEvent, triggerEvent } from '@ember/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | string list', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.spy = sinon.spy();
|
||||
});
|
||||
|
||||
const assertBlank = function (assert) {
|
||||
assert.dom('[data-test-string-list-input]').exists({ count: 1 }, 'renders 1 input');
|
||||
assert.dom('[data-test-string-list-input]').hasValue('', 'the input is blank');
|
||||
|
@ -26,56 +31,55 @@ module('Integration | Component | string list', function (hooks) {
|
|||
|
||||
test('it renders the label', async function (assert) {
|
||||
assert.expect(4);
|
||||
await render(hbs`{{string-list label="foo"}}`);
|
||||
await render(hbs`<StringList @label="foo" @onChange={{this.spy}} />`);
|
||||
assert.dom('[data-test-string-list-label]').hasText('foo', 'renders the label when provided');
|
||||
|
||||
await render(hbs`{{string-list}}`);
|
||||
await render(hbs`<StringList />`);
|
||||
assert.dom('[data-test-string-list-label]').doesNotExist('does not render the label');
|
||||
assertBlank(assert);
|
||||
});
|
||||
|
||||
test('it renders inputValue from empty string', async function (assert) {
|
||||
assert.expect(2);
|
||||
await render(hbs`{{string-list inputValue=""}}`);
|
||||
await render(hbs`<StringList @inputValue="" />`);
|
||||
assertBlank(assert);
|
||||
});
|
||||
|
||||
test('it renders inputValue from string with one value', async function (assert) {
|
||||
assert.expect(3);
|
||||
await render(hbs`{{string-list inputValue="foo"}}`);
|
||||
await render(hbs`<StringList @inputValue="foo" />`);
|
||||
assertFoo(assert);
|
||||
});
|
||||
|
||||
test('it renders inputValue from comma-separated string', async function (assert) {
|
||||
assert.expect(4);
|
||||
await render(hbs`{{string-list inputValue="foo,bar"}}`);
|
||||
await render(hbs`<StringList @inputValue="foo,bar" />`);
|
||||
assertFooBar(assert);
|
||||
});
|
||||
|
||||
test('it renders inputValue from a blank array', async function (assert) {
|
||||
assert.expect(2);
|
||||
this.set('inputValue', []);
|
||||
await render(hbs`{{string-list inputValue=inputValue}}`);
|
||||
await render(hbs`<StringList @inputValue={{inputValue}} />`);
|
||||
assertBlank(assert);
|
||||
});
|
||||
|
||||
test('it renders inputValue array with a single item', async function (assert) {
|
||||
assert.expect(3);
|
||||
this.set('inputValue', ['foo']);
|
||||
await render(hbs`{{string-list inputValue=inputValue}}`);
|
||||
await render(hbs`<StringList @inputValue={{inputValue}} />`);
|
||||
assertFoo(assert);
|
||||
});
|
||||
|
||||
test('it renders inputValue array with a multiple items', async function (assert) {
|
||||
assert.expect(4);
|
||||
this.set('inputValue', ['foo', 'bar']);
|
||||
await render(hbs`{{string-list inputValue=inputValue}}`);
|
||||
await render(hbs`<StringList @inputValue={{inputValue}} />`);
|
||||
assertFooBar(assert);
|
||||
});
|
||||
|
||||
test('it adds a new row only when the last row is not blank', async function (assert) {
|
||||
assert.expect(5);
|
||||
await render(hbs`{{string-list inputValue=""}}`);
|
||||
await render(hbs`<StringList @inputValue="" @onChange={{this.spy}}/>`);
|
||||
await click('[data-test-string-list-button="add"]');
|
||||
assertBlank(assert);
|
||||
await fillIn('[data-test-string-list-input="0"]', 'foo');
|
||||
|
@ -85,7 +89,7 @@ module('Integration | Component | string list', function (hooks) {
|
|||
});
|
||||
|
||||
test('it trims input values', async function (assert) {
|
||||
await render(hbs`{{string-list inputValue=""}}`);
|
||||
await render(hbs`<StringList @inputValue="" @onChange={{this.spy}}/>`);
|
||||
await fillIn('[data-test-string-list-input="0"]', 'foo');
|
||||
await triggerKeyEvent('[data-test-string-list-input="0"]', 'keyup', 14);
|
||||
assert.dom('[data-test-string-list-input="0"]').hasValue('foo');
|
||||
|
@ -97,7 +101,7 @@ module('Integration | Component | string list', function (hooks) {
|
|||
this.set('onChange', function (val) {
|
||||
assert.deepEqual(val, ['foo', 'bar'], 'calls onChange with expected value');
|
||||
});
|
||||
await render(hbs`{{string-list inputValue=inputValue onChange=(action onChange)}}`);
|
||||
await render(hbs`<StringList @inputValue={{inputValue}} @onChange={{onChange}} />`);
|
||||
await fillIn('[data-test-string-list-input="1"]', 'bar');
|
||||
await triggerKeyEvent('[data-test-string-list-input="1"]', 'keyup', 14);
|
||||
});
|
||||
|
@ -108,7 +112,7 @@ module('Integration | Component | string list', function (hooks) {
|
|||
this.set('onChange', function (val) {
|
||||
assert.equal(val, 'foo,bar', 'calls onChange with expected value');
|
||||
});
|
||||
await render(hbs`{{string-list inputValue=inputValue onChange=(action onChange)}}`);
|
||||
await render(hbs`<StringList @inputValue={{inputValue}} @onChange={{onChange}}/>`);
|
||||
await fillIn('[data-test-string-list-input="1"]', 'bar');
|
||||
await triggerKeyEvent('[data-test-string-list-input="1"]', 'keyup', 14);
|
||||
});
|
||||
|
@ -119,11 +123,23 @@ module('Integration | Component | string list', function (hooks) {
|
|||
this.set('onChange', function (val) {
|
||||
assert.equal(val, 'bar', 'calls onChange with expected value');
|
||||
});
|
||||
await render(hbs`{{string-list inputValue=inputValue onChange=(action onChange)}}`);
|
||||
await render(hbs`<StringList @inputValue={{inputValue}} @onChange={{onChange}} />`);
|
||||
|
||||
await click('[data-test-string-list-row="0"] [data-test-string-list-button="delete"]');
|
||||
assert.dom('[data-test-string-list-input]').exists({ count: 2 }, 'renders 2 inputs');
|
||||
assert.dom('[data-test-string-list-input="0"]').hasValue('bar');
|
||||
assert.dom('[data-test-string-list-input="1"]').hasValue('');
|
||||
});
|
||||
|
||||
test('it replaces helpText if name is tokenBoundCidrs', async function (assert) {
|
||||
assert.expect(1);
|
||||
await render(hbs`<StringList @label={{'blah'}} @helpText={{'blah'}} @attrName={{'tokenBoundCidrs'}} />`);
|
||||
let tooltipTrigger = document.querySelector('[data-test-tool-tip-trigger]');
|
||||
await triggerEvent(tooltipTrigger, 'mouseenter');
|
||||
assert
|
||||
.dom('[data-test-info-tooltip-content]')
|
||||
.hasText(
|
||||
'Specifies the blocks of IP addresses which are allowed to use the generated token. One entry per row.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue