mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
UI: Ember deprecation - reopen class (#25851)
Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
This commit is contained in:
@@ -3,16 +3,16 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<form {{action (perform this.saveModel) on="submit"}}>
|
||||
<form {{on "submit" (perform this.saveModel)}}>
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
<NamespaceReminder @mode="save" @noun="Auth Method" />
|
||||
<MessageError @model={{this.model}} />
|
||||
{{#if this.model.attrs}}
|
||||
{{#each this.model.attrs as |attr|}}
|
||||
<MessageError @model={{@model}} />
|
||||
{{#if @model.attrs}}
|
||||
{{#each @model.attrs as |attr|}}
|
||||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||||
{{/each}}
|
||||
{{else if this.model.fieldGroups}}
|
||||
<FormFieldGroups @model={{this.model}} @mode={{this.mode}} />
|
||||
{{else if @model.fieldGroups}}
|
||||
<FormFieldGroups @model={{@model}} @mode={{this.mode}} />
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import AdapterError from '@ember-data/adapter/error';
|
||||
import { service } from '@ember/service';
|
||||
import Component from '@ember/component';
|
||||
import Component from '@glimmer/component';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { waitFor } from '@ember/test-waiters';
|
||||
|
||||
@@ -14,40 +14,31 @@ import { waitFor } from '@ember/test-waiters';
|
||||
* The `AuthConfigForm/Config` is the base form to configure auth methods.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* {{auth-config-form/config model.model}}
|
||||
* ```
|
||||
* <AuthConfigForm::Config @model={{this.model}} />
|
||||
*
|
||||
* @property model=null {DS.Model} - The corresponding auth model that is being configured.
|
||||
*
|
||||
*/
|
||||
|
||||
const AuthConfigBase = Component.extend({
|
||||
tagName: '',
|
||||
model: null,
|
||||
export default class AuthConfigBase extends Component {
|
||||
@service flashMessages;
|
||||
@service router;
|
||||
|
||||
flashMessages: service(),
|
||||
router: service(),
|
||||
saveModel: task(
|
||||
waitFor(function* () {
|
||||
try {
|
||||
yield this.model.save();
|
||||
} catch (err) {
|
||||
// AdapterErrors are handled by the error-message component
|
||||
// in the form
|
||||
if (err instanceof AdapterError === false) {
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
@task
|
||||
@waitFor
|
||||
*saveModel(evt) {
|
||||
evt.preventDefault();
|
||||
try {
|
||||
yield this.args.model.save();
|
||||
} catch (err) {
|
||||
// AdapterErrors are handled by the error-message component
|
||||
// in the form
|
||||
if (err instanceof AdapterError === false) {
|
||||
throw err;
|
||||
}
|
||||
this.router.transitionTo('vault.cluster.access.methods').followRedirects();
|
||||
this.flashMessages.success('The configuration was saved successfully.');
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
AuthConfigBase.reopenClass({
|
||||
positionalParams: ['model'],
|
||||
});
|
||||
|
||||
export default AuthConfigBase;
|
||||
return;
|
||||
}
|
||||
this.router.transitionTo('vault.cluster.access.methods').followRedirects();
|
||||
this.flashMessages.success('The configuration was saved successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,26 +3,26 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<form {{action (perform this.saveModel) on="submit"}}>
|
||||
<form {{on "submit" (perform this.saveModel)}}>
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
<MessageError @model={{this.model}} @errorMessage={{this.model.errorMessage}} />
|
||||
<MessageError @model={{@model}} @errorMessage={{this.errorMessage}} />
|
||||
<NamespaceReminder @mode="save" @noun="Auth Method" />
|
||||
|
||||
{{#each this.model.tuneAttrs as |attr|}}
|
||||
{{#if (not (includes attr.name this.model.userLockoutConfig.modelAttrs))}}
|
||||
<FormField data-test-field @attr={{attr}} @model={{this.model}} />
|
||||
{{#each @model.tuneAttrs as |attr|}}
|
||||
{{#if (not (includes attr.name @model.userLockoutConfig.modelAttrs))}}
|
||||
<FormField data-test-field @attr={{attr}} @model={{@model}} />
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
{{#if this.model.supportsUserLockoutConfig}}
|
||||
{{#if @model.supportsUserLockoutConfig}}
|
||||
<hr class="has-top-margin-xl has-bottom-margin-l has-background-gray-200" />
|
||||
<Hds::Text::Display @tag="h2" @size="400" @weight="bold" data-test-user-lockout-section>User lockout configuration</Hds::Text::Display>
|
||||
<Hds::Text::Body @tag="p" @size="100" @color="faint" class="has-bottom-margin-m">
|
||||
Specifies the user lockout settings for this auth mount.
|
||||
</Hds::Text::Body>
|
||||
{{#each this.model.tuneAttrs as |attr|}}
|
||||
{{#if (includes attr.name this.model.userLockoutConfig.modelAttrs)}}
|
||||
<FormField @attr={{attr}} @model={{this.model}} />
|
||||
{{#each @model.tuneAttrs as |attr|}}
|
||||
{{#if (includes attr.name @model.userLockoutConfig.modelAttrs)}}
|
||||
<FormField @attr={{attr}} @model={{@model}} />
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
@@ -8,63 +8,61 @@ import AuthConfigComponent from './config';
|
||||
import { service } from '@ember/service';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { waitFor } from '@ember/test-waiters';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
|
||||
/**
|
||||
* @module AuthConfigForm/Options
|
||||
* The `AuthConfigForm/Options` is options portion of the auth config form.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* {{auth-config-form/options model.model}}
|
||||
* ```
|
||||
* <AuthConfigForm::Options @model={{this.model}} />
|
||||
*
|
||||
* @property model=null {DS.Model} - The corresponding auth model that is being configured.
|
||||
*
|
||||
*/
|
||||
|
||||
export default AuthConfigComponent.extend({
|
||||
flashMessages: service(),
|
||||
router: service(),
|
||||
export default class AuthConfigOptions extends AuthConfigComponent {
|
||||
@service flashMessages;
|
||||
@service router;
|
||||
|
||||
saveModel: task(
|
||||
waitFor(function* () {
|
||||
const data = this.model.config.serialize();
|
||||
data.description = this.model.description;
|
||||
@tracked errorMessage;
|
||||
|
||||
if (this.model.supportsUserLockoutConfig) {
|
||||
data.user_lockout_config = {};
|
||||
this.model.userLockoutConfig.apiParams.forEach((attr) => {
|
||||
if (Object.keys(data).includes(attr)) {
|
||||
data.user_lockout_config[attr] = data[attr];
|
||||
delete data[attr];
|
||||
}
|
||||
});
|
||||
}
|
||||
@task
|
||||
@waitFor
|
||||
*saveModel(evt) {
|
||||
evt.preventDefault();
|
||||
this.errorMessage = null;
|
||||
const data = this.args.model.config.serialize();
|
||||
data.description = this.args.model.description;
|
||||
|
||||
// token_type should not be tuneable for the token auth method.
|
||||
if (this.model.methodType === 'token') {
|
||||
delete data.token_type;
|
||||
}
|
||||
|
||||
try {
|
||||
yield this.model.tune(data);
|
||||
} catch (err) {
|
||||
// AdapterErrors are handled by the error-message component
|
||||
// in the form
|
||||
if (err instanceof AdapterError === false) {
|
||||
throw err;
|
||||
if (this.args.model.supportsUserLockoutConfig) {
|
||||
data.user_lockout_config = {};
|
||||
this.args.model.userLockoutConfig.apiParams.forEach((attr) => {
|
||||
if (Object.keys(data).includes(attr)) {
|
||||
data.user_lockout_config[attr] = data[attr];
|
||||
delete data[attr];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// token_type should not be tuneable for the token auth method.
|
||||
if (this.args.model.methodType === 'token') {
|
||||
delete data.token_type;
|
||||
}
|
||||
|
||||
try {
|
||||
yield this.args.model.tune(data);
|
||||
} catch (err) {
|
||||
if (err instanceof AdapterError) {
|
||||
// because we're not calling model.save the model never updates with
|
||||
// the error. Forcing the error message by manually setting the errorMessage
|
||||
try {
|
||||
this.model.set('errorMessage', err.errors?.join(','));
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
// the error, so we set it manually in the component instead.
|
||||
this.errorMessage = errorMessage(err);
|
||||
return;
|
||||
}
|
||||
this.router.transitionTo('vault.cluster.access.methods').followRedirects();
|
||||
this.flashMessages.success('The configuration was saved successfully.');
|
||||
})
|
||||
),
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
this.router.transitionTo('vault.cluster.access.methods').followRedirects();
|
||||
this.flashMessages.success('The configuration was saved successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
{{#let (tabs-for-auth-section this.model this.tabType this.paths) as |tabs|}}
|
||||
{{#let (tabs-for-auth-section @model this.tabType @paths) as |tabs|}}
|
||||
{{#if tabs.length}}
|
||||
<div class="tabs-container box is-sideless is-fullwidth is-paddingless is-marginless">
|
||||
<nav class="tabs" aria-label="navigation to manage {{this.model.type}}">
|
||||
<nav class="tabs" aria-label="navigation to manage {{@model.type}}">
|
||||
<ul>
|
||||
{{#each tabs as |tab|}}
|
||||
<li>
|
||||
<LinkTo @route={{get tab.routeParams 0}} @model={{get tab.routeParams 1}} data-test-auth-section-tab={{true}}>
|
||||
<LinkTo @route={{get tab.routeParams 0}} @model={{get tab.routeParams 1}} data-test-auth-section-tab>
|
||||
{{tab.label}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
@@ -3,16 +3,10 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@ember/component';
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
const SectionTabs = Component.extend({
|
||||
tagName: '',
|
||||
model: null,
|
||||
tabType: 'authSettings',
|
||||
});
|
||||
|
||||
SectionTabs.reopenClass({
|
||||
positionalParams: ['model', 'tabType', 'paths'],
|
||||
});
|
||||
|
||||
export default SectionTabs;
|
||||
export default class SectionTabs extends Component {
|
||||
get tabType() {
|
||||
return this.args.tabType || 'authSettings';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { belongsTo } from '@ember-data/model';
|
||||
import { assert, debug } from '@ember/debug';
|
||||
import { typeOf } from '@ember/utils';
|
||||
import { isArray } from '@ember/array';
|
||||
|
||||
/*
|
||||
*
|
||||
* attachCapabilities
|
||||
*
|
||||
* @param modelClass = An Ember Data model class
|
||||
* @param capabilities - an Object whose keys will added to the model class as related 'capabilities' models
|
||||
* and whose values should be functions that return the id of the related capabilities model
|
||||
*
|
||||
* definition of capabilities be done shorthand with the apiPath tagged template function
|
||||
*
|
||||
*
|
||||
* @usage
|
||||
*
|
||||
* let Model = DS.Model.extend({
|
||||
* backend: attr(),
|
||||
* scope: attr(),
|
||||
* });
|
||||
*
|
||||
* export default attachCapabilities(Model, {
|
||||
* updatePath: apiPath`${'backend'}/scope/${'scope'}/role/${'id'}`,
|
||||
* });
|
||||
*
|
||||
*/
|
||||
export default function attachCapabilities(modelClass, capabilities) {
|
||||
const capabilityKeys = Object.keys(capabilities);
|
||||
const newRelationships = capabilityKeys.reduce((ret, key) => {
|
||||
ret[key] = belongsTo('capabilities', { async: false, inverse: null });
|
||||
return ret;
|
||||
}, {});
|
||||
|
||||
//TODO: move this to the application serializer and do it JIT instead of on app boot
|
||||
debug(`adding new relationships: ${capabilityKeys.join(', ')} to ${modelClass.toString()}`);
|
||||
modelClass.reopen(newRelationships);
|
||||
modelClass.reopenClass({
|
||||
// relatedCapabilities is called in the application serializer's
|
||||
// normalizeResponse hook to add the capabilities relationships to the
|
||||
// JSON-API document used by Ember Data
|
||||
relatedCapabilities(jsonAPIDoc) {
|
||||
let { data, included } = jsonAPIDoc;
|
||||
if (!data) {
|
||||
data = jsonAPIDoc;
|
||||
}
|
||||
if (isArray(data)) {
|
||||
const newData = data.map(this.relatedCapabilities);
|
||||
return {
|
||||
data: newData,
|
||||
included,
|
||||
};
|
||||
}
|
||||
const context = {
|
||||
id: data.id,
|
||||
...data.attributes,
|
||||
};
|
||||
for (const newCapability of capabilityKeys) {
|
||||
const templateFn = capabilities[newCapability];
|
||||
const type = typeOf(templateFn);
|
||||
assert(`expected value of ${newCapability} to be a function but found ${type}.`, type === 'function');
|
||||
data.relationships[newCapability] = {
|
||||
data: {
|
||||
type: 'capabilities',
|
||||
id: templateFn(context),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (included) {
|
||||
return {
|
||||
data,
|
||||
included,
|
||||
};
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
},
|
||||
});
|
||||
return modelClass;
|
||||
}
|
||||
@@ -30,5 +30,5 @@
|
||||
{{/if}}
|
||||
</p.levelRight>
|
||||
</PageHeader>
|
||||
{{section-tabs this.model "authShow"}}
|
||||
<SectionTabs @model={{this.model}} @tabType="authShow" />
|
||||
{{component (concat "auth-method/" this.section) model=this.model}}
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{section-tabs this.model "authShow" this.paths}}
|
||||
<SectionTabs @model={{this.model}} @tabType="authShow" @paths={{this.paths}} />
|
||||
|
||||
{{#if (eq this.section "configuration")}}
|
||||
<Toolbar>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
{{section-tabs this.model}}
|
||||
<SectionTabs @model={{this.model}} />
|
||||
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
~}}
|
||||
|
||||
{{#if (eq this.model.section "options")}}
|
||||
{{auth-config-form/options this.model.model}}
|
||||
<AuthConfigForm::Options @model={{this.model.model}} />
|
||||
{{else}}
|
||||
{{auth-config-form/config this.model.model}}
|
||||
<AuthConfigForm::Config @model={{this.model.model}} />
|
||||
{{/if}}
|
||||
@@ -1,168 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Model from '@ember-data/model';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import attachCapabilities from 'vault/lib/attach-capabilities';
|
||||
import apiPath from 'vault/utils/api-path';
|
||||
|
||||
const MODEL_TYPE = 'test-form-model';
|
||||
|
||||
const makeModelClass = () => {
|
||||
return Model.extend();
|
||||
};
|
||||
|
||||
module('Unit | lib | attach capabilities', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it attaches passed capabilities', function (assert) {
|
||||
let mc = makeModelClass();
|
||||
mc = attachCapabilities(mc, {
|
||||
updatePath: apiPath`update/{'id'}`,
|
||||
deletePath: apiPath`delete/{'id'}`,
|
||||
});
|
||||
let relationship = mc.relationshipsByName.get('updatePath');
|
||||
|
||||
assert.strictEqual(relationship.key, 'updatePath', 'has updatePath relationship');
|
||||
assert.strictEqual(relationship.kind, 'belongsTo', 'kind of relationship is belongsTo');
|
||||
assert.strictEqual(relationship.type, 'capabilities', 'updatePath is a related capabilities model');
|
||||
|
||||
relationship = mc.relationshipsByName.get('deletePath');
|
||||
assert.strictEqual(relationship.key, 'deletePath', 'has deletePath relationship');
|
||||
assert.strictEqual(relationship.kind, 'belongsTo', 'kind of relationship is belongsTo');
|
||||
assert.strictEqual(relationship.type, 'capabilities', 'deletePath is a related capabilities model');
|
||||
});
|
||||
|
||||
test('it adds a static method to the model class', function (assert) {
|
||||
let mc = makeModelClass();
|
||||
mc = attachCapabilities(mc, {
|
||||
updatePath: apiPath`update/{'id'}`,
|
||||
deletePath: apiPath`delete/{'id'}`,
|
||||
});
|
||||
const relatedCapabilities = !!mc.relatedCapabilities && typeof mc.relatedCapabilities === 'function';
|
||||
assert.true(relatedCapabilities, 'model class now has a relatedCapabilities static function');
|
||||
});
|
||||
|
||||
test('calling static method with single response JSON-API document adds expected relationships', function (assert) {
|
||||
let mc = makeModelClass();
|
||||
mc = attachCapabilities(mc, {
|
||||
updatePath: apiPath`update/${'id'}`,
|
||||
deletePath: apiPath`delete/${'id'}`,
|
||||
});
|
||||
const jsonAPIDocSingle = {
|
||||
data: {
|
||||
id: 'test',
|
||||
type: MODEL_TYPE,
|
||||
attributes: {},
|
||||
relationships: {},
|
||||
},
|
||||
included: [],
|
||||
};
|
||||
|
||||
const expected = {
|
||||
data: {
|
||||
id: 'test',
|
||||
type: MODEL_TYPE,
|
||||
attributes: {},
|
||||
relationships: {
|
||||
updatePath: {
|
||||
data: {
|
||||
type: 'capabilities',
|
||||
id: 'update/test',
|
||||
},
|
||||
},
|
||||
deletePath: {
|
||||
data: {
|
||||
type: 'capabilities',
|
||||
id: 'delete/test',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
included: [],
|
||||
};
|
||||
|
||||
mc.relatedCapabilities(jsonAPIDocSingle);
|
||||
|
||||
assert.strictEqual(
|
||||
Object.keys(jsonAPIDocSingle.data.relationships).length,
|
||||
2,
|
||||
'document now has 2 relationships'
|
||||
);
|
||||
assert.deepEqual(jsonAPIDocSingle, expected, 'has the exected new document structure');
|
||||
});
|
||||
|
||||
test('calling static method with an arrary response JSON-API document adds expected relationships', function (assert) {
|
||||
let mc = makeModelClass();
|
||||
mc = attachCapabilities(mc, {
|
||||
updatePath: apiPath`update/${'id'}`,
|
||||
deletePath: apiPath`delete/${'id'}`,
|
||||
});
|
||||
const jsonAPIDocSingle = {
|
||||
data: [
|
||||
{
|
||||
id: 'test',
|
||||
type: MODEL_TYPE,
|
||||
attributes: {},
|
||||
relationships: {},
|
||||
},
|
||||
{
|
||||
id: 'foo',
|
||||
type: MODEL_TYPE,
|
||||
attributes: {},
|
||||
relationships: {},
|
||||
},
|
||||
],
|
||||
included: [],
|
||||
};
|
||||
|
||||
const expected = {
|
||||
data: [
|
||||
{
|
||||
id: 'test',
|
||||
type: MODEL_TYPE,
|
||||
attributes: {},
|
||||
relationships: {
|
||||
updatePath: {
|
||||
data: {
|
||||
type: 'capabilities',
|
||||
id: 'update/test',
|
||||
},
|
||||
},
|
||||
deletePath: {
|
||||
data: {
|
||||
type: 'capabilities',
|
||||
id: 'delete/test',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'foo',
|
||||
type: MODEL_TYPE,
|
||||
attributes: {},
|
||||
relationships: {
|
||||
updatePath: {
|
||||
data: {
|
||||
type: 'capabilities',
|
||||
id: 'update/foo',
|
||||
},
|
||||
},
|
||||
deletePath: {
|
||||
data: {
|
||||
type: 'capabilities',
|
||||
id: 'delete/foo',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
included: [],
|
||||
};
|
||||
mc.relatedCapabilities(jsonAPIDocSingle);
|
||||
assert.deepEqual(jsonAPIDocSingle, expected, 'has the exected new document structure');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user