Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sidebranch: UI transform create and edit clean up #9778

Merged
merged 9 commits into from
Aug 19, 2020
28 changes: 3 additions & 25 deletions ui/app/adapters/transform.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { assign } from '@ember/polyfills';
import { resolve, allSettled } from 'rsvp';
import { allSettled } from 'rsvp';
import ApplicationAdapter from './application';
import { encodePath } from 'vault/utils/path-encoding-helpers';

export default ApplicationAdapter.extend({
// TODO this adapter was copied over, much of this stuff may or may not need to be here.
namespace: 'v1',

// defaultSerializer: 'role',

createOrUpdate(store, type, snapshot) {
const serializer = store.serializerFor('transform'); // TODO replace transform with type.modelName
const serializer = store.serializerFor(type.modelName);
const data = serializer.serialize(snapshot);
const { id } = snapshot;
let url = this.urlForTransformations(snapshot.record.get('backend'), id);
Expand All @@ -35,14 +32,6 @@ export default ApplicationAdapter.extend({
return 'transform';
},

urlForAlphabet(backend, id) {
let url = `${this.buildURL()}/${encodePath(backend)}/alphabet`;
if (id) {
url = url + '/' + encodePath(id);
}
return url;
},

urlForTransformations(backend, id) {
let url = `${this.buildURL()}/${encodePath(backend)}/transformation`;
if (id) {
Expand All @@ -61,14 +50,9 @@ export default ApplicationAdapter.extend({

fetchByQuery(store, query) {
const { id, backend } = query;
let zeroAddressAjax = resolve();
const queryAjax = this.ajax(this.urlForTransformations(backend, id), 'GET', this.optionsForQuery(id));
// TODO: come back to why you need this, carry over.
// if (!id) {
// zeroAddressAjax = this.findAllZeroAddress(store, query);
// }

return allSettled([queryAjax, zeroAddressAjax]).then(results => {
return allSettled([queryAjax]).then(results => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're not waiting on two promises this can probably be simplified to returning the single promise above (this.ajax)

// query result 404d, so throw the adapterError
if (!results[0].value) {
throw results[0].reason;
Expand All @@ -93,12 +77,6 @@ export default ApplicationAdapter.extend({
});
},

findAllZeroAddress(store, query) {
const { backend } = query;
const url = `/v1/${encodePath(backend)}/config/zeroaddress`;
return this.ajax(url, 'GET');
},

query(store, type, query) {
return this.fetchByQuery(store, query);
},
Expand Down
3 changes: 1 addition & 2 deletions ui/app/components/role-edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ export default Component.extend(FocusOnInsertMixin, {
actions: {
createOrUpdate(type, event) {
event.preventDefault();

const modelId = this.get('model.id') || this.get('model.name'); // ARG TODO this is not okay
const modelId = this.get('model.id') || this.get('model.name'); // transform comes in as model.name
// prevent from submitting if there's no key
// maybe do something fancier later
if (type === 'create' && isBlank(modelId)) {
Expand Down
8 changes: 8 additions & 0 deletions ui/app/components/transform-create-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import RoleEdit from './role-edit';

export default RoleEdit.extend({
init() {
this._super(...arguments);
this.set('backendType', 'transform');
},
});
1 change: 0 additions & 1 deletion ui/app/helpers/options-for-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ const SECRET_BACKENDS = {
editComponent: 'role-ssh-edit',
listItemPartial: 'partials/secret-list/ssh-role-item',
},
// TODO: edit or remove listItemPartial and better understand what's happening here
transform: {
displayName: 'Transformation',
navigateTree: false,
Expand Down
18 changes: 6 additions & 12 deletions ui/app/models/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,20 @@ const TWEAK_SOURCE = [
];

const Model = DS.Model.extend({
// TODO: for now, commenting out openApi info, but keeping here just in case we end up using it.
// useOpenAPI: true,
// getHelpUrl: function(backend) {
// return `/v1/${backend}?help=1`;
// },
useOpenAPI: false,
name: attr('string', {
// TODO: make this required for making a transformation
label: 'Name',
fieldValue: 'id',
readOnly: true,
subText: 'The name for your transformation. This cannot be edited later.',
}),
type: attr('string', {
defaultValue: 'fpe',
label: 'Type',
possibleValues: TYPES,
subText:
'Vault provides two types of transformations: Format Preserving Encryption (FPE) is reversible, while Masking is not.',
'Vault provides two types of transformations: Format Preserving Encryption (FPE) is reversible, while Masking is not. This cannot be edited later.',
}),
tweak_source: attr('string', {
defaultValue: 'supplied',
Expand All @@ -70,21 +67,20 @@ const Model = DS.Model.extend({
fallbackComponent: 'string-list',
label: 'Template', // TODO: make this required for making a transformation
models: ['transform/template'],
selectLimit: 1,
subLabel: 'Template Name',
subText:
'Templates allow Vault to determine what and how to capture the value to be transformed. Type to use an existing template or create a new one.',
}),
templates: attr('array'), // TODO: remove once BE changes the returned property to a singular template on the GET request.
allowed_roles: attr('string', {
label: 'Allowed roles',
allowed_roles: attr('array', {
editType: 'searchSelect',
label: 'Allowed roles',
fallbackComponent: 'string-list',
models: ['transform/role'],
subText: 'Search for an existing role, type a new role to create it, or use a wildcard (*).',
}),
transformAttrs: computed('type', function() {
// TODO: group them into sections/groups. Right now, we don't different between required and not required as we do by hiding options.
// will default to design mocks on how to handle as it will likely be a different pattern using client-side validation, which we have not done before
if (this.type === 'masking') {
return ['name', 'type', 'masking_character', 'template', 'templates', 'allowed_roles'];
}
Expand All @@ -93,8 +89,6 @@ const Model = DS.Model.extend({
transformFieldAttrs: computed('transformAttrs', function() {
return expandAttributeMeta(this, this.get('transformAttrs'));
}),
// zeroAddressPath: lazyCapabilities(apiPath`${'backend'}/config/zeroaddress`, 'backend'),
// canEditZeroAddress: alias('zeroAddressPath.canUpdate'),
});

export default attachCapabilities(Model, {
Expand Down
6 changes: 6 additions & 0 deletions ui/app/styles/core/forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ label {
.field-body .field {
margin-bottom: 0;
}
.field {
// cannot use :read-only selector because tag used for other purposes
&.is-readOnly {
background-color: $ui-gray-100;
}
}
.field.has-addons {
flex-wrap: wrap;
.control {
Expand Down
48 changes: 48 additions & 0 deletions ui/app/templates/components/transform-create-form.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<form onsubmit={{action "createOrUpdate" "create"}}>
<div class="box is-sideless is-fullwidth is-marginless">
{{message-error model=model}}
{{!-- TODO: figure out what this ?? --}}
{{!-- <NamespaceReminder @mode={{mode}} @noun="SSH role" /> --}}
{{#each model.transformFieldAttrs as |attr|}}
{{#if (eq attr.name 'templates')}}
{{!-- TODO: for now don't show until backend makes api changes. --}}
{{else if (eq attr.name 'template')}}
<FormField
data-test-field
@attr={{attr}}
@model={{model}}
@selectLimit={{selectLimit}}
/>
{{else}}
<FormField
data-test-field
@attr={{attr}}
@model={{model}}
/>
{{/if}}
{{/each}}
</div>
<div class="field is-grouped-split box is-fullwidth is-bottomless">
<div class="control">
<button
type="submit"
disabled={{buttonDisabled}}
class="button is-primary"
data-test-role-ssh-create=true
>
{{#if (eq mode 'create')}}
Create transformation
{{else if (eq mode 'edit')}}
Save
{{/if}}
</button>
{{#secret-link
mode=(if (eq mode "create") "list" "show")
class="button"
secret=model.id
}}
Cancel
{{/secret-link}}
</div>
</div>
</form>
40 changes: 40 additions & 0 deletions ui/app/templates/components/transform-edit-form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,51 @@
{{!-- TODO: figure out what this ?? --}}
{{!-- <NamespaceReminder @mode={{mode}} @noun="SSH role" /> --}}
{{#each model.transformFieldAttrs as |attr|}}
{{#if (or (eq attr.name 'name') (eq attr.name 'type')) }}
<label for="{{attr.name}}" class="is-label">
{{attr.options.label}}
</label>
{{#if attr.options.subText}}
<p class="sub-text">{{attr.options.subText}}</p>
{{/if}}
{{#if attr.options.possibleValues}}
<div class="control is-expanded field is-readOnly">
<div class="select is-fullwidth">
<select name="{{attr.name}}" id="{{attr.name}}" disabled data-test-input={{attr.name}}>
<option selected={{get model attr.name}} value={{get model attr.name}}>
{{get model attr.name}}
</option>
</select>
</div>
</div>
{{else}}
<input data-test-input={{attr.name}} id={{attr.name}} autocomplete="off" spellcheck="false"
value={{or (get model attr.name) attr.options.defaultValue}} readonly class="field input is-readOnly" type={{attr.type}} />
{{/if}}
{{else if (eq attr.name 'template')}}
<FormField
data-test-field
@attr={{attr}}
@model={{model}}
@initialSelected={{model.templates}}
@selectLimit={{selectLimit}}
/>
{{else if (eq attr.name 'templates')}}
{{!-- TODO: for now don't show until backend makes api changes. --}}
{{else if (eq attr.name 'allowed_roles')}}
<FormField
data-test-field
@attr={{attr}}
@model={{model}}
@initialSelected={{model.allowed_roles}}
/>
{{else}}
<FormField
data-test-field
@attr={{attr}}
@model={{model}}
/>
{{/if}}
{{/each}}
</div>
<div class="field is-grouped-split box is-fullwidth is-bottomless">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

<div class="has-top-margin-xl has-bottom-margin-s">
<label class="title has-border-bottom-light page-header">CLI Commands</label>
{{!-- ARG TODO make these components? --}}
<div class="has-bottom-margin-s">
<h2 class="title is-6">Encode</h2>
<div class="has-bottom-margin-s">
Expand Down
4 changes: 3 additions & 1 deletion ui/app/templates/components/transformation-edit.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@
</Toolbar>
{{/if}}

{{#if (or (eq mode 'edit') (eq mode 'create'))}}
{{#if (eq mode 'edit')}}
<TransformEditForm @mode={{mode}} @model={{model}} />
{{else if (eq mode 'create')}}
<TransformCreateForm @mode={{mode}} @model={{model}} />
{{else}}
<TransformShowTransformation
@model={{model}}
Expand Down
12 changes: 11 additions & 1 deletion ui/lib/core/addon/components/search-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import layout from '../templates/components/search-select';
* @module SearchSelect
* The `SearchSelect` is an implementation of the [ember-power-select-with-create](https://github.com/poteto/ember-cli-flash) used for form elements where options come dynamically from the API.
* @example
* <SearchSelect @id="group-policies" @models={{["policies/acl"]}} @onChange={{onChange}} @inputValue={{get model valuePath}} @helpText="Policies associated with this group" @label="Policies" @fallbackComponent="string-list" />
* <SearchSelect @id="group-policies" @models={{["policies/acl"]}} @onChange={{onChange}} @initialSelected={{array 'selectedValue'}} @selectLimit={{2}} @inputValue={{get model valuePath}} @helpText="Policies associated with this group" @label="Policies" @fallbackComponent="string-list" />
*
* @param id {String} - The name of the form field
* @param models {String} - An array of model types to fetch from the API.
* @param onChange {Func} - The onchange action for this form field.
* @param inputValue {String | Array} - A comma-separated string or an array of strings.
* @param [initialSelected] {Array} - An array of initially selected options to display.
* @param [helpText] {String} - Text to be displayed in the info tooltip for this form field
* @param [subText] {String} - Text to be displayed below the label
* @param [selectLimit] {Number} - A number that sets the limit to how many select options they can choose
* @param label {String} - Label for this form field
* @param [subLabel] {String} - a smaller label below the main Label
* @param fallbackComponent {String} - name of component to be rendered if the API call 403s
Expand Down Expand Up @@ -44,6 +46,9 @@ export default Component.extend({
shouldRenderName: false,
init() {
this._super(...arguments);
if (this.initialSelected) {
return this.set('selectedOptions', this.initialSelected); // need array helper to pass in.
}
this.set('selectedOptions', this.inputValue || []);
},
didRender() {
Expand Down Expand Up @@ -126,6 +131,11 @@ export default Component.extend({
this.handleChange();
},
selectOption(option) {
if (this.selectLimit) {
if (this.selectLimit <= this.selectedOptions.length) {
return;
}
}
this.selectedOptions.pushObject(option);
this.options.removeObject(option);
this.handleChange();
Expand Down
5 changes: 4 additions & 1 deletion ui/lib/core/addon/templates/components/form-field.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@
@onChange={{action (action "setAndBroadcast" valuePath)}} @inputValue={{get model valuePath}}
@helpText={{attr.options.helpText}} @subText={{attr.options.subText}} @label={{labelString}}
@subLabel={{attr.options.subLabel}}
@fallbackComponent={{attr.options.fallbackComponent}} />
@fallbackComponent={{attr.options.fallbackComponent}}
@initialSelected={{initialSelected}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should pass the initialSelected and selectLimit on the attr so it follows convention for how this component is used. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with that is initialSelected isn't known on the model (which is where attr.options comes from). SelectLimit can be though.

@selectLimit={{attr.options.selectLimit}}
/>
</div>
{{else if (eq attr.options.editType "mountAccessor")}}
{{mount-accessor-select
Expand Down
42 changes: 22 additions & 20 deletions ui/lib/core/addon/templates/components/search-select.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,29 @@
{{#if subText}}
<p class="sub-text">{{subText}}</p>
{{/if}}
{{#power-select-with-create
options=options
search=search
onchange=(action "selectOption")
oncreate=(action "createOption")
placeholderComponent=(component "search-select-placeholder")
renderInPlace=true
searchField="searchText"
verticalPosition="below"
showCreateWhen=(action "hideCreateOptionOnSameID")
buildSuggestion=(action "constructSuggestion") as |option|
}}
{{#if shouldRenderName}}
{{option.name}}
<small class="search-select-list-key" data-test-smaller-id="true">
{{#unless (gte selectedOptions.length selectLimit)}}
{{#power-select-with-create
options=options
search=search
onchange=(action "selectOption")
oncreate=(action "createOption")
placeholderComponent=(component "search-select-placeholder")
renderInPlace=true
searchField="searchText"
verticalPosition="below"
showCreateWhen=(action "hideCreateOptionOnSameID")
buildSuggestion=(action "constructSuggestion") as |option|
}}
{{#if shouldRenderName}}
{{option.name}}
<small class="search-select-list-key" data-test-smaller-id="true">
{{option.id}}
</small>
{{else}}
{{option.id}}
</small>
{{else}}
{{option.id}}
{{/if}}
{{/power-select-with-create}}
{{/if}}
{{/power-select-with-create}}
{{/unless}}
<ul class="search-select-list">
{{#each selectedOptions as |selected|}}
<li class="search-select-list-item" data-test-selected-option="true">
Expand Down