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:
Angel Garbarino 2022-06-07 12:15:25 -07:00 committed by GitHub
parent 426e3a5583
commit a86644968b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 169 additions and 171 deletions

3
changelog/15852.txt Normal file
View File

@ -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.
```

View File

@ -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 }}

View File

@ -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>

View File

@ -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());
}
}

View File

@ -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}}

View File

@ -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": "*",

View File

@ -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.'
);
});
});